Створення динамічних веб-сторінок за допомогою Twig і MySQL

Створення динамічних веб-сторінок за допомогою Twig і MySQL

Вступ: чому шаблонізатори — це не надмірність, а необхідність

Якщо ви коли-небудь бачили PHP-файл, де HTML перемішаний із запитами до бази даних, умовними операторами й іноді навіть стилями — ви знаєте, як це виглядає. Це виглядає як щось, що важко підтримувати, ще важче передавати іншому розробнику й майже неможливо масштабувати без болю.

Більшість проектів починаються як «просто швидко зроблю». І в цьому немає нічого поганого на старті. Але коли проект росте, коли з'являється команда, коли клієнт хоче змінити зовнішній вигляд без торкання логіки — той самий «швидко зроблений» код стає джерелом усіх проблем.

Twig вирішує саме цю проблему. Це шаблонізатор для PHP, розроблений командою Symfony і використовуваний у сотнях тисяч проектів по всьому світу. Він дозволяє чітко розділити бізнес-логіку та відображення — і це не абстрактна перевага з документації, а реальна різниця у щоденній роботі.

MySQL у цьому контексті — це те, звідки беруться дані. База даних для більшості веб-проектів: надійна, швидка при правильному використанні, з великою спільнотою та інструментарієм. Разом Twig і MySQL формують дуже практичний стек для побудови динамічних веб-сторінок — від простого корпоративного сайту до складних каталогів і особистих кабінетів.

У цій статті ми пройдемо повний шлях: від з'єднання з базою даних до побудови реальних компонентів у Twig. Без зайвого теоретизування — тільки те, що реально знадобиться у роботі.


Частина 1: Що таке Twig і чому саме він

Коротка передісторія

Twig з'явився як частина екосистеми Symfony, але швидко вийшов за її межі. Сьогодні він використовується в Drupal (починаючи з 8-ї версії), у Craft CMS, у безлічі кастомних PHP-проектів і фреймворків. Це стандарт де-факто для PHP-шаблонізації.

Ключова ідея Twig — відокремити логіку від представлення настільки, щоб дизайнер або верстальник міг безпечно редагувати шаблони, не розуміючи PHP. Шаблони Twig навмисно обмежені: у них немає доступу до PHP-функцій безпосередньо, не можна виконати довільний код, і це правильно.

Синтаксис, який читається як природна мова

Різниця між «голим» PHP і Twig стає очевидною, щойно бачиш їх поруч. У класичному PHP-шаблоні кожен рядок виводу потребує відкриваючих і закриваючих тегів, кожен рядок тексту треба вручну обгортати в функцію захисту від XSS. Цикл перебору масиву займає п'ять рядків там, де Twig витрачає один. Умовний блок — те саме. Результат: PHP-шаблон виглядає як безладна суміш мов, Twig-шаблон читається майже як звичайний текст.

Для верстальника або дизайнера, що не пише PHP, різниця принципова. Він може редагувати Twig-шаблон, розуміти його структуру і вносити зміни без ризику зламати логіку — просто тому, що логіки в шаблоні немає.

Три типи синтаксичних конструкцій

Twig оперує трьома видами виразів, і цього достатньо для будь-якого шаблону.

Подвійні фігурні дужки використовуються для виведення значень. Коли пишеш ім'я змінної між ними — Twig підставить її вміст у HTML. При цьому автоматично відбувається екранування: будь-який потенційно небезпечний символ перетворюється на безпечний HTML-еквівалент.

Фігурна дужка зі знаком відсотка — це логічні блоки: умови, цикли, оголошення блоків для успадкування, підключення інших шаблонів. Тут описується структура і поведінка.

Фігурна дужка з решіткою — коментарі. Вони не потрапляють у згенерований HTML взагалі, на відміну від HTML-коментарів, що видно у вихідному коді сторінки.

Автоматичне екранування — безпека без зусиль

Одна з найважливіших відмінностей від «голого» PHP: Twig автоматично екранує всі виведені значення. Якщо у базі даних зберігається рядок із JavaScript-кодом або HTML-тегами, Twig виведе його як звичайний текст, а не як виконуваний скрипт чи розмітку. У PHP без шаблонізатора розробник мав би пам'ятати про виклик захисної функції у кожному місці виведення. Люди забувають. Twig — ні.

Якщо потрібно вивести «сирий» HTML навмисно — є спеціальний фільтр raw, але це явна, усвідомлена дія, а не дефолтна поведінка. Безпека за замовчуванням — саме так і має бути.


Частина 2: Встановлення і початкове налаштування

Встановлення через Composer

Twig встановлюється через Composer — стандартний менеджер залежностей для PHP. Одна команда в терміналі завантажує бібліотеку і всі її залежності, а також налаштовує автозавантаження класів. Якщо Composer ще не встановлений — його можна завантажити з офіційного сайту getcomposer.org. Для більшості сучасних хостингів і серверів він вже доступний як системна команда.

Встановлюємо актуальну третю версію Twig — вона підтримується і отримує оновлення безпеки.

Ініціалізація середовища

Після встановлення Twig потребує двох речей для початку роботи: завантажувача шаблонів і об'єкта середовища.

Завантажувач файлової системи отримує шлях до папки, де зберігаються шаблони. Twig шукатиме всі файли відносно цієї папки. Зазвичай шаблони зберігаються в папці templates або views у корені проекту.

Об'єкт середовища — це основний інтерфейс для роботи з Twig. Він приймає завантажувач і масив налаштувань. Найважливіші з них: шлях до папки кешу для скомпільованих шаблонів, режим налагодження і прапор автоматичного перекомпілювання при змінах.

Кешування — одна з ключових оптимізацій Twig. Він компілює шаблони в PHP-код і зберігає результат на диску. На продакшені перший запит компілює шаблон, всі наступні просто виконують готовий PHP-код. Це дуже швидко. Режим налагодження і автоматичне перекомпілювання потрібні лише під час розробки — на продакшені їх треба вимкнути.

Правильна структура проекту

Хороша структура проекту — це не педантизм, а інвестиція в підтримуваність. Для проекту на Twig і MySQL рекомендується наступний поділ.

Публічна папка (public або web) — єдина точка входу, доступна з інтернету. Тут лежить index.php і статичні ресурси: CSS, JavaScript, зображення. Все інше розташоване за межами публічної папки і недоступне безпосередньо через браузер.

Папка src містить PHP-код: класи для роботи з базою даних, бізнес-логіку, репозиторії. Тут немає жодного HTML.

Папка templates містить виключно шаблони Twig. Вони організовані в підпапки за розділами сайту: catalog, pages, components. Базовий шаблон зазвичай лежить у корені папки.

Папка var або cache зберігає скомпільовані шаблони і тимчасові файли. Вона генерується автоматично і не потрапляє до системи контролю версій.

Папка vendor — залежності Composer. Теж не потрапляє до репозиторію.

Така структура чітко розділяє відповідальності: логіка в src, відображення в templates, публічний доступ тільки через public.


Частина 3: Підключення до MySQL через PDO

Чому PDO, а не mysqli

PHP має кілька способів працювати з MySQL. Старий інтерфейс mysql_* давно застарів і видалений у PHP 7. Залишаються два варіанти: mysqli і PDO.

PDO — правильний вибір для нових проектів. По-перше, він підтримує різні бази даних — MySQL, PostgreSQL, SQLite — з єдиним інтерфейсом. Якщо колись знадобиться мігрувати на іншу СУБД, зміниться лише рядок підключення, а весь інший код залишиться. По-друге, підготовлені вирази в PDO — природна і зручна частина API, а не щось, що треба додатково налаштовувати. По-третє, PDO видає зрозумілі виключення при помилках, а не числові коди, які треба шукати в документації.

Клас підключення до бази

Гарна практика — обернути підключення до бази в окремий клас з методом, що повертає єдиний екземпляр з'єднання на весь запит. Це патерн Singleton, і тут він цілком доречний: одне підключення на запит, не більше.

У рядку підключення (DSN) вказуємо хост, назву бази і кодування. Важливий момент щодо кодування: потрібно використовувати utf8mb4, а не просто utf8. Стандартний utf8 у MySQL підтримує лише три байти на символ і не може зберігати emoji та деякі рідкісні Unicode-символи. utf8mb4 — це справжній повний Unicode.

Три налаштування при створенні з'єднання критично важливі. Перше — режим помилок як виключення: PDO кидатиме виключення при будь-якій помилці, а не просто повертатиме false. Це робить обробку помилок зрозумілою. Друге — режим вибірки за замовчуванням як асоціативний масив: результати запитів повертаються як масиви з назвами стовпців як ключами, що природно використовуються в Twig. Третє — вимкнення емуляції підготовлених виразів: PDO використовуватиме справжні prepared statements на рівні СУБД, а не їх емуляцію, що важливо для безпеки.

Захист від SQL-ін'єкцій через підготовлені вирази

Найважливіше правило при роботі з базою даних — ніколи не підставляти дані користувача напряму в текст SQL-запиту. Ніколи. Навіть якщо здається, що дані безпечні.

Проблема виглядає так: якщо взяти значення з форми і просто «склеїти» його з рядком запиту, зловмисник може передати як значення фрагмент SQL-коду. Наприклад, рядок, що закриває умову і додає власний запит. Це SQL-ін'єкція — одна з найпоширеніших веб-вразливостей.

Правильне рішення — підготовлені вирази. Спочатку готуємо шаблон запиту, де замість конкретних значень стоять плейсхолдери — символи питання або іменовані параметри з двокрапкою. Потім виконуємо запит, передаючи значення окремо від тексту запиту. PDO підставить їх безпечно, незалежно від їх вмісту. Якщо хтось передасть у параметрі SQL-код для знищення таблиці — це буде оброблено як звичайний текстовий рядок, а не команда.

Іменовані параметри читабельніші при кількох параметрах: замість того, щоб відраховувати, який символ питання відповідає якому значенню, можна явно вказати назву кожного параметра.


Частина 4: Шаблонна система Twig — ключові концепції

Успадкування шаблонів

Найпотужніша функція Twig для реальних проектів — успадкування шаблонів. Ідея проста і елегантна: є базовий шаблон зі спільною структурою всього сайту, а окремі сторінки «успадковують» його і заповнюють лише змінні частини.

Базовий шаблон визначає загальний HTML-скелет: doctype, head із мета-тегами і підключенням стилів, header із навігацією, основну область вмісту, footer, підключення скриптів. Усередині цього скелету позначаються «блоки» — місця, де дочірні шаблони можуть вставити свій вміст. Блоки мають назви: title, content, stylesheets, javascripts. Вони можуть містити вміст за замовчуванням або бути порожніми.

Дочірній шаблон починається з директиви extends і вказує назву батьківського. Далі оголошуються тільки ті блоки, що потрібно заповнити або перевизначити. Все інше — шапка, підвал, підключення ресурсів — бере з базового шаблону автоматично.

Результат: при зміні навігації або підвалу достатньо відредагувати один файл — базовий шаблон. Всі сторінки сайту оновляться автоматично. Це саме та перевага, що робить підтримку великих проектів керованою.

Функція parent() дозволяє вставити вміст батьківського блоку і додати до нього щось. Наприклад, для заголовку сторінки зручно писати назву поточного розділу і через роздільник — назву сайту з батьківського шаблону.

Директива include для компонентів

Якщо успадкування вирішує задачу спільного макету, include вирішує задачу компонентів — фрагментів HTML, що повторюються в різних місцях.

Картка товару, що відображається і на сторінці каталогу, і в результатах пошуку, і в блоці «схожі товари» — типовий кандидат на окремий шаблон, що підключається через include. Пагінація, форма підписки, блок соціальних мереж — те саме.

Include підтримує передачу змінних у підключений шаблон. Це робить компоненти параметризованими і багаторазово використовуваними.

Фільтри і функції Twig

Twig має велику бібліотеку вбудованих фільтрів для обробки даних прямо в шаблоні.

Текстові фільтри: upper і lower для регістру, slice для обрізання рядка до потрібної довжини, striptags для видалення HTML-тегів, trim для видалення пробілів по краях. Фільтр default корисний для виведення запасного значення, якщо змінна порожня або не визначена.

Числові фільтри: number_format форматує числа з розрядниками і десятковими знаками, що зручно для цін і статистики.

Фільтри для дат: date форматує дату і час у будь-якому форматі, підтримує всі специфікатори PHP.

Фільтри для масивів: length повертає кількість елементів, join об'єднує масив у рядок із роздільником, first і last повертають перший і останній елементи.

Фільтри можна застосовувати ланцюжком — кожен наступний отримує результат попереднього. Наприклад, спочатку прибрати HTML-теги, потім обрізати до ста символів, потім зробити першу літеру великою.

Власні фільтри і функції

Стандартних можливостей Twig буває недостатньо. Часто потрібна специфічна для проекту логіка форматування: ціна в гривнях з правильним форматом, генерація URL за певними правилами, перевірка прав доступу.

Twig дозволяє реєструвати власні фільтри і функції в PHP-коді і потім використовувати їх у шаблонах як вбудовані. Фільтр — це PHP-функція, що отримує значення і повертає перетворений результат. Функція Twig — це PHP-функція, що може приймати аргументи і повертати будь-яке значення.

Наприклад, власний фільтр для форматування ціни отримає число і поверне рядок у форматі «1 299,00 грн» з урахуванням локальних правил форматування. Після реєстрації цей фільтр доступний у всіх шаблонах так само, як і вбудовані.


Частина 5: Реальний приклад — каталог товарів

Зберемо все разом у реальний приклад. Побудуємо каталог товарів з фільтрацією за категорією і пагінацією. Це типова задача, з якою стикається більшість веб-проектів.

Структура бази даних

Для каталогу потрібні дві таблиці: категорії і товари.

Таблиця категорій містить ідентифікатор, назву і slug — людиночитабельний ідентифікатор для URL. Наприклад, категорія «Ноутбуки» матиме slug laptops. Поле slug повинно мати унікальний індекс, щоб не допустити дублювання.

Таблиця товарів містить ідентифікатор, посилання на категорію, назву, slug, опис, ціну, кількість на складі, прапор активності і дату створення. Зовнішній ключ на таблицю категорій гарантує цілісність даних — не можна додати товар у неіснуючу категорію.

Після створення таблиць обов'язково додаємо індекси. Поле category_id у таблиці товарів — кандидат на індекс, бо ми будемо фільтрувати за ним. Поле active теж потрапляє у WHERE кожного запиту до каталогу. Без індексів при тисячах товарів кожен запит виконуватиме повне сканування таблиці.

Патерн Repository — клас для роботи з даними

Прямо в контролері писати SQL-запити — погана практика. Якщо завтра знадобиться змінити умову вибірки або додати кешування, доведеться шукати по всьому проекту, де ці запити розкидані.

Патерн Repository виносить всю роботу з базою в окремий клас. Контролер не знає, як саме дані отримуються — він просто просить репозиторій повернути список товарів або окремий товар.

Метод для отримання списку товарів приймає номер сторінки, кількість на сторінці і необов'язковий ідентифікатор категорії. Всередині він формує SQL-запит з JOIN до таблиці категорій, щоб отримати назву категорії разом із товаром в одному запиті. Якщо переданий ідентифікатор категорії — додається умова фільтрації. Наприкінці додається сортування і LIMIT з OFFSET для пагінації.

Важливий нюанс: для параметрів LIMIT і OFFSET потрібно явно вказувати тип як ціле число при прив'язці до запиту. MySQL не приймає рядки у цих позиціях, а PDO без явного типу передає всі параметри як рядки.

Окремий метод підраховує загальну кількість товарів — це потрібно для обчислення кількості сторінок у пагінації. Він також підтримує фільтрацію за категорією, щоб відображати правильну кількість при активному фільтрі.

Точка входу — збираємо все разом

Файл index.php у публічній папці є точкою входу. Він відповідає за: ініціалізацію Twig, підключення до бази, зчитування параметрів із URL, отримання даних через репозиторій і передачу їх у шаблон.

Параметр сторінки береться з рядка запиту і перетворюється на ціле число з мінімальним значенням один — щоб запобігти відхиленням типу page=0 або page=-5. Параметр категорії — аналогічно, але може бути відсутнім.

Після отримання даних — список товарів і загальна кількість — обчислюємо кількість сторінок: ділимо загальну кількість на розмір сторінки і округляємо вгору.

Виклик методу render передає шаблону всі потрібні дані: список товарів, поточну сторінку, загальну кількість сторінок, ідентифікатор активної категорії. Шаблон отримує готові дані і не знає, звідки вони взялися.

Шаблони для каталогу

Шаблон списку товарів успадковує базовий і перевизначає блоки title і content. В блоці вмісту першою справою перевіряємо, чи є товари взагалі. Якщо масив порожній — виводимо повідомлення «нічого не знайдено». Якщо є — перебираємо циклом і для кожного товару підключаємо шаблон картки, передаючи об'єкт товару.

Шаблон картки товару — автономний компонент. Він отримує один товар і відображає його: зображення, назву категорії, назву товару, скорочений опис, ціну і статус наявності. Опис скорочується фільтрами прямо в шаблоні: спочатку прибираємо HTML-теги, потім обрізаємо до ста символів, а якщо текст обрізаний — додаємо три крапки.

Для кожного поля, що виводиться, Twig автоматично застосовує екранування — нічого додатково робити не потрібно.

Статус наявності виводиться умовно: якщо поле stock більше нуля — «В наявності», інакше — «Немає в наявності». CSS-клас при цьому теж різний, що дозволяє стилізувати ці стани по-різному.

Пагінація в шаблоні

Шаблон пагінації — хороший приклад того, що можна робити в Twig без жодного PHP. Він отримує три змінні: поточну сторінку, загальну кількість сторінок і ідентифікатор категорії для збереження фільтра в посиланнях.

Якщо сторінок лише одна — нічого не виводимо. Інакше будуємо навігацію: якщо поточна сторінка не перша — посилання «Попередня». Цикл від одиниці до загальної кількості сторінок генерує кнопки, але не всі підряд: завжди показуємо першу і останню, дві сторінки навколо поточної, а де є розрив — виводимо три крапки. Якщо поточна сторінка не остання — посилання «Наступна».

Посилання містять і параметр сторінки, і параметр категорії, якщо він активний — щоб при переході на наступну сторінку фільтр не скидався.


Частина 6: Оптимізація — швидкість і правильні практики

Проблема N+1 запитів

Одна з найпоширеніших і найнебезпечніших помилок при роботі з базою даних — проблема N+1. Назва описує суть: є один запит для отримання списку, а потім для кожного елементу списку виконується ще один запит. Разом виходить N+1 запит замість одного.

Виглядає це невинно: отримали список товарів, а потім у циклі для кожного товару зробили запит, щоб дізнатися назву категорії. При десяти товарах на сторінці — одинадцять запитів. При ста товарах — сто один. При тисячі — тисяча один.

Вирішення просте і завжди однакове: використовувати JOIN. Замість того, щоб спочатку отримати товари, а потім для кожного окремо отримувати категорію, робимо один запит, що об'єднує обидві таблиці. MySQL повертає всі потрібні дані за один раз, і це завжди швидше, ніж N окремих запитів.

Це правило поширюється на будь-які зв'язані таблиці. Якщо є список статей і для кожної потрібен автор — JOIN. Список замовлень і статуси — JOIN. Список товарів і теги — трохи складніший JOIN через проміжну таблицю, але принцип той самий.

Кешування даних, що рідко змінюються

Деякі дані змінюються рідко, але запитуються часто. Список категорій навігації — типовий приклад: він оновлюється раз на тиждень, але присутній на кожній сторінці сайту.

Для таких даних доцільне кешування: перший запит іде в базу і зберігає результат у файл або пам'ять. Наступні запити читають кеш, не звертаючись до бази взагалі. Кеш інвалідується або за часом (наприклад, через десять хвилин), або явно при зміні даних.

Найпростіша реалізація — зберігання в JSON-файл. Якщо файл існує і він молодший за поріг — повертаємо його вміст. Якщо ні — виконуємо запит, зберігаємо результат у файл і повертаємо його.

Для серйозних проектів із великим навантаженням рекомендується Redis або Memcached — кешування в оперативній пам'яті, що на порядки швидше за файли. Але для більшості проектів файловий кеш цілком достатній.

Індекси в MySQL

Twig відповідає за швидкий рендеринг шаблонів, але якщо самі запити до бази повільні — це не допоможе. Продуктивність запитів MySQL майже повністю визначається наявністю правильних індексів.

Без індексу MySQL виконує повне сканування таблиці — переглядає кожен рядок і перевіряє умову. При тисячі рядків це практично непомітно. При мільйоні — секунди замість мілісекунд. При десяти мільйонах — хвилини.

Правило просте: кожне поле в умові WHERE, кожне поле в JOIN і кожне поле в ORDER BY є кандидатом на індекс. Але не варто індексувати все поспіль — кожен індекс займає місце і сповільнює операції запису.

Команда EXPLAIN перед будь-яким SELECT показує план виконання запиту: чи використовуються індекси, скільки рядків MySQL переглядає. Якщо в рядку type стоїть ALL і в rows мільйонні числа — індексу немає або він не використовується.

Для каталогу з фільтром за категорією і сортуванням за датою ефективним є складений індекс по трьох полях: category_id, active і created_at. MySQL зможе використати його для фільтрації і сортування одночасно, не звертаючись до самої таблиці.

Кешування скомпільованих шаблонів Twig

Twig компілює шаблони в PHP-код при першому зверненні. На продакшені кеш компільованих шаблонів — це обов'язкова умова, а не опція. Без нього кожен запит парсить і компілює шаблон заново.

Важливо при деплої нової версії сайту очищати папку кешу шаблонів. Якщо цього не зробити, Twig рендеруватиме старі версії шаблонів навіть після оновлення файлів. Автоматизуйте очищення кешу як частину процесу деплою — один рядок у скрипті деплою, що видаляє вміст папки.

На продакшені також потрібно вимкнути режим налагодження і автоматичне перекомпілювання. Автоматичне перекомпілювання перевіряє час зміни файлу шаблону при кожному запиті — це зайва операція у виробничому середовищі, де шаблони не змінюються між запитами.


Частина 7: Робота з формами і збереженням даних

Динамічні сторінки — це не лише читання з бази, а й запис. Форми зворотного зв'язку, реєстрація, оформлення замовлення — всюди потрібна валідація вхідних даних і безпечне збереження.

Захист форм від CSRF-атак

CSRF (Cross-Site Request Forgery) — атака, при якій зловмисний сайт змушує браузер жертви відправити запит на ваш сайт від її імені. Жертва заходить на шкідливу сторінку, та непомітно відправляє POST-запит на ваш сайт з авторизаційними кукі жертви. Якщо форма не захищена — запит виконається.

Захист простий: генеруємо випадковий токен і зберігаємо його в сесії при відображенні форми. Токен додаємо в форму як прихований параметр. При обробці форми перевіряємо: токен із форми має збігатися з токеном у сесії. Якщо не збігається — запит відхиляємо.

Зловмисний сайт не може знати токен із сесії жертви, тому не зможе підробити коректний запит.

Валідація і відображення помилок

Обробка форми починається з перевірки CSRF-токена. Потім дані очищаємо: видаляємо HTML-теги, прибираємо зайві пробіли. Далі перевіряємо бізнес-правила: мінімальна довжина, коректний формат email, обов'язкові поля.

Помилки збираємо в масив з назвами полів як ключами. Якщо масив порожній — зберігаємо дані і перенаправляємо на сторінку успіху. Якщо є помилки — повертаємо шаблон форми, передаючи і помилки, і введені дані.

У шаблоні кожне поле перевіряє, чи є помилка для нього, і якщо є — виводить повідомлення та додає CSS-клас для виділення. Значення полів заповнюються введеними даними через фільтр default з порожнім рядком за замовчуванням. Це забезпечує правильну поведінку і при першому відкритті форми, і після невдалої відправки.

Дрібна, але важлива деталь: користувач не втрачає те, що вже написав. Це очевидна вимога з точки зору UX, але її часто забувають при реалізації.

Безпечне збереження паролів

Якщо форма передбачає реєстрацію або зміну пароля — ніколи не зберігайте паролі у відкритому вигляді або через слабкі алгоритми хешування.

PHP має вбудовані функції password_hash і password_verify саме для цього. password_hash застосовує сучасний адаптивний алгоритм bcrypt і автоматично додає випадкову «сіль». password_verify безпечно порівнює пароль із хешем. Ці функції роблять правильно без жодних додаткових налаштувань — просто використовуйте їх.

MD5, SHA1 і навіть SHA256 без солі не є безпечними для зберігання паролів. Bcrypt через password_hash — так.


Частина 8: Розширені можливості Twig

Макроси — компоненти з параметрами

Include підходить для підключення самостійних блоків. Але іноді потрібен компонент, що може варіюватися залежно від контексту: поле форми з різними типами, лейблами, помилками. Копіювати HTML поля форми для кожного поля — поганий шлях.

Макроси Twig — це аналог функцій для шаблонів. Оголошуємо макрос із параметрами, всередині пишемо HTML-логіку. Потім імпортуємо файл з макросами і викликаємо потрібний, передаючи аргументи.

Наприклад, макрос для поля форми приймає ім'я поля, лейбл, поточне значення, тип, прапор обов'язковості і текст помилки. Всередині будує потрібну HTML-структуру залежно від параметрів. Один виклик макросу замінює десять рядків HTML, що повторюються.

Форма з десятьма полями перетворюється з сімдесяти рядків монотонного HTML на десять чистих викликів макросу. Якщо треба змінити HTML-структуру поля — змінюємо в одному місці.

Глобальні змінні

Деякі дані потрібні у кожному шаблоні: інформація про поточного авторизованого користувача, налаштування сайту, кількість товарів у кошику. Передавати їх у кожному виклику render — марна праця.

Twig підтримує глобальні змінні, доступні у всіх шаблонах без явної передачі. Вони реєструються один раз при ініціалізації середовища і потім просто доступні у шаблоні.

Поточний користувач як глобальна змінна — дуже зручно: навігаційні шаблони можуть показувати кнопку входу або посилання на профіль без того, щоб кожен контролер передавав цю інформацію.

Тести в Twig

Twig має синтаксичну конструкцію для логічних перевірок — тести. Вони роблять умови більш читабельними і виразними.

Перевірка is empty замінює порівняння з порожнім масивом або рядком. Перевірка is defined дозволяє перевірити, чи оголошена змінна взагалі — корисно для необов'язкових параметрів. Перевірка is odd і is even для парності числа. Перевірка is iterable дозволяє впевнитись, що змінна є масивом, перш ніж перебирати її циклом.

Такий синтаксис наближається до природної мови і робить шаблони легкими для читання навіть тим, хто не знайомий з Twig.


Частина 9: Налагодження і інструменти розробника

Розширення для налагодження

У режимі розробки Twig має спеціальне розширення Debug, яке потрібно явно підключити. Воно активує функцію dump, що виводить вміст будь-якої змінної у зручно відформатованому вигляді прямо на сторінці.

Можна передати dump конкретну змінну — вона виведе її структуру, типи, вкладеність. Можна викликати без аргументів — виведе всі змінні, доступні в поточному шаблоні. Це значно зручніше за var_dump у PHP, особливо для складних вкладених масивів: результат оформлений і згорнутий.

На продакшені розширення Debug потрібно вимкнути разом із загальним режимом налагодження.

Профілювання запитів до MySQL

Коли сторінка завантажується повільно, перший крок — зрозуміти, скільки запитів виконується і скільки часу займає кожен.

Найпростіший підхід — обгортка над класом PDO, що записує кожен запит: час початку, час завершення, текст запиту. Після обробки запиту вся статистика доступна для відображення на сторінці або запису в лог. Це дає повну картину: скільки запитів, яких, скільки часу кожен.

Інший підхід для середовища розробки — увімкнути загальний лог запитів у самому MySQL. Він записує абсолютно всі запити у файл. Зручно, якщо потрібно побачити запити від кількох джерел одночасно. Але для продакшену цей режим неприйнятний — він суттєво навантажує сервер і генерує гігабайти логів.

MySQL Workbench і візуальний аналіз

MySQL Workbench — безкоштовний GUI-клієнт від Oracle. Для розробника, що працює з MySQL, він корисний кількома речами.

Редактор запитів дозволяє зручно писати і виконувати SQL, переглядати результати, зберігати часто використовувані запити.

Вкладка схеми бази показує структуру таблиць і зв'язки між ними у вигляді діаграми. Дуже корисно для розуміння чужого проекту або проектування нової схеми.

Функція Visual Explain перетворює результат команди EXPLAIN у наочну схему. Там одразу видно: де MySQL сканує повну таблицю, де використовує індекс, де відбувається сортування, скільки рядків обробляється на кожному кроці. П'ять хвилин у Visual Explain заощаджують годину відладки «наосліп».

Xdebug для покрокового налагодження

Для серйозного налагодження PHP-логіки незамінний Xdebug у поєднанні з PhpStorm або VS Code. Він дозволяє поставити breakpoint у будь-якому рядку PHP-коду і виконувати програму покроково: рядок за рядком, спостерігаючи, як змінюються значення змінних.

Особливо корисно при налагодженні складної бізнес-логіки формування даних перед передачею у шаблон. Ви точно бачите, що отримує шаблон — ще до того, як він починає рендеритись. Це суттєво скорочує час пошуку причини неочікуваного відображення.


Частина 10: Типові помилки і як їх уникнути

За роки роботи з Twig і MySQL можна виділити кілька помилок, що зустрічаються найчастіше і коштують найдорожче.

Бізнес-логіка в шаблонах. Якщо у Twig-шаблоні є складні обчислення, запити до бази або обробка даних — щось пішло не так. Шаблон має отримувати готові дані і тільки відображати їх. Складна логіка належить PHP-коду. Правило перевірки просте: якщо шаблон не можна безпечно дати верстальнику для редагування — він містить занадто багато логіки.

Відсутність обробки порожніх результатів. Коли база повертає порожній масив, шаблон повинен це обробляти явно і виводити зрозуміле повідомлення. Відсутність цієї перевірки призводить або до порожньої сторінки без жодного пояснення, або до помилки при спробі Twig перебрати порожній масив.

Неправильне кешування на продакшені. Часто зустрічається ситуація: зробили зміни в шаблоні, задеплоїли — а на сайті нічого не змінилось. Причина — не очищений кеш Twig. Автоматизуйте очищення кешу як частину процесу деплою. Це одна команда, а сама ситуація «чому не оновилось» дуже неприємна і заплутує і розробника, і клієнта.

Неконтрольоване використання фільтра raw. Цей фільтр вимикає автоматичне екранування Twig. Якщо застосувати його до даних із форми чи бази — відкривається вразливість XSS. Використовуйте raw тільки для HTML, що ви самі згенерували в PHP і повністю контролюєте.

Проблема N+1 у замаскованому вигляді. Іноді N+1 не очевидний одразу — наприклад, коли запити відбуваються у методах об'єктів, що викликаються у циклі шаблону. Якщо відчуваєте, що сторінка повільніша, ніж мала б бути — перевіряйте кількість запитів до бази. Інструменти профілювання (описані вище) дадуть відповідь за хвилину.

Ігнорування індексів MySQL. Запити без індексів на малих таблицях можуть виконуватись миттєво, а на великих — секундами. База даних зростає поступово, і в якийсь момент сторінка, що завжди відкривалась миттєво, починає завантажуватись по дві-три секунди. Причина — відсутній індекс, що не давався взнаки при маленькому обсязі даних. Перевіряйте EXPLAIN для критичних запитів заздалегідь.

Зберігання паролів у слабкому форматі. Базова безпека: паролі зберігаємо тільки через password_hash, перевіряємо через password_verify. Ніяких MD5, SHA1 або, тим більше, відкритого тексту. Якщо база даних потрапить у чужі руки — паролі користувачів не мають бути під загрозою.


Частина 11: Коли Twig і MySQL достатньо, а коли потрібно більше

Де цей стек добре працює

Twig у поєднанні з MySQL — відмінний вибір для широкого класу проектів, що охоплюють більшість практичних потреб.

Корпоративні сайти з динамічним контентом: новини, блог, команда, портфоліо, акції. Все це зручно керувати через базу даних і відображати через Twig. Редактори можуть оновлювати контент, розробники не торкаються шаблонів.

Каталоги і довідники: товари, послуги, нерухомість, автомобілі. Будь-яке структуроване відображення даних з фільтрацією, пошуком і пагінацією.

B2B-портали і особисті кабінети: авторизація, персоналізовані дані, різні ролі користувачів. Серверний рендеринг через Twig тут спрощує SEO і доступність.

Проекти на Symfony або подібних PHP-фреймворках: Twig — їх рідний шаблонізатор, повністю інтегрований в екосистему.

Де варто розглянути доповнення або альтернативи

Якщо проект — переважно статичний контент із рідкими змінами: розгляньте статичні генератори на кшталт Hugo або Jekyll, або headless CMS. Вони швидші, дешевші в обслуговуванні і зазвичай краще для SEO при правильному налаштуванні.

Якщо потрібна висока інтерактивність на стороні клієнта — SPA-поведінка, реальний час, складні інтерфейси з миттєвою реакцією без перезавантаження сторінки: Twig може залишитись для серверного рендерингу критичних сторінок, але фронтенд варто будувати на React, Vue або Svelte з API-бекендом. Twig і сучасний JavaScript-фреймворк не суперечать одне одному — вони можуть співіснувати в одному проекті.

Якщо потрібен повнотекстовий пошук по великим обсягам: MySQL має базові можливості повнотекстового пошуку, але для складних пошукових задач краще Elasticsearch або Meilisearch поруч із MySQL. MySQL зберігає всі дані, пошуковий рушій відповідає за пошук.

Якщо масштаб дуже великий і навантаження виходить за межі одного сервера: MySQL чудово масштабується через реплікацію і шардинг, але це вже окрема архітектурна задача. Для переважної більшості проектів одного добре налаштованого сервера з правильними індексами достатньо на роки вперед.


Висновок: практичний інструмент для реальних задач

Twig і MySQL — це не модна технологія і не срібна куля. Це перевірений, надійний стек для побудови динамічних веб-сторінок, що вже багато років використовується у production-проектах різного масштабу.

Їх головна сила — у простоті концепції. Дані з MySQL передаються в шаблон Twig, шаблон відображає їх безпечно і читабельно. Логіка і представлення розділені. Команда може працювати паралельно: бекенд-розробник пише запити й готує дані, фронтенд-розробник будує шаблони, не заглиблюючись у PHP. Верстальник редагує HTML, не ризикуючи зламати логіку.

Якщо ви починаєте новий проект або рефакторите існуючий — Twig дасть відчутний приріст у якості коду і зручності роботи вже з перших шаблонів. Правильна робота з PDO і підготовленими виразами закриє більшість питань безпеки за замовчуванням. Система успадкування шаблонів усуне дублювання HTML по всьому проекту.

Є кілька речей, що варто запам'ятати і завжди тримати в голові.

Twig автоматично екранує вивід — використовуйте фільтр raw тільки там, де дійсно впевнені в джерелі даних.

PDO з підготовленими виразами — єдиний правильний спосіб передавати будь-які зовнішні дані у SQL-запити. Не лише дані з форм, а й будь-які параметри, що надходять ззовні.

Успадкування шаблонів разом із компонентами через include і параметризованими макросами — три інструменти, що разом вирішують дев'яносто відсотків задач структурування HTML у великих проектах.

Кеш Twig на продакшені плюс правильні індекси в MySQL дають хорошу швидкодію без складних архітектурних рішень. Більшість проблем із продуктивністю вирішуються саме тут.

Проблема N+1 — перша причина повільних сторінок, що не пояснюються складними запитами. Перевіряйте кількість запитів при відладці, використовуйте JOIN там, де це потрібно.

Технологія добре документована, активно підтримується і має велику спільноту. Офіційна документація Twig — одна з найкращих у PHP-екосистемі: детальна, з прикладами, добре структурована. Якщо щось пішло не так — відповідь майже завжди знаходиться там або на Stack Overflow за лічені хвилини.

Поділитися
У пошуках підрядника з розробки сайту?
Заходьте до нашого online каталогу Веб-студій та вибирайте партнера за рядом критеріїв: бал, портфоліо, відгуки, кейси та статті. Або організуйте тендер в даному каталозі, вибравши компанії, що вам сподобалися.
Більше не потрібно шукати та обдзвонювати діджитал-агентства!
Створіть тендер та отримайте пропозиції від найкращих веб-студій України.
У каталозі 1700+ діджитал-агентств, які готові допомогти в реалізації ваших завдань. Вибирайте та економте до 30% свого часу та бюджету! Це безкоштовно та займе менше 3-х хвилин.
Створити тендер
Bug