Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама
Новини 03 вересня 2026

Forte: парсинг та редагування Laravel Blade-шаблонів

Laravel-застосунки часто потребують однакових змін у кожному Blade-файлі - наприклад, перейменування компонента або нормалізації директиви. Однорядковий sed замінює кожен збіг, включно з тими, що всередині рядків, коментарів та атрибутів, які ви не хотіли міняти. Forte, написаний John Koster, парсить .blade.php у типізоване синтаксичне дерево, дає змогу запитувати дерево та рендерить переписаний шаблон назад.

Koster також є автором prettier-plugin-blade. Версія 3 цього форматера працює на Forte.

Використовуйте Forte, коли потрібно з'ясувати, що містять ваші в'юшки, застосувати одну зміну до сотень файлів або зламати білд, коли шаблон порушує конвенції вашої команди.

Встановлення

Forte потребує PHP 8.2, розширення dom та Laravel 10-13:

composer require fortephp/forte

Сервіс-провайдер реєструється автоматично, тому фасад Forte доступний одразу.

Парсинг

Forte::parse() приймає рядок, Forte::parseFile() приймає шлях:

use Forte\Facades\Forte;
$doc = Forte::parse('<div class="mt-4">Hello, {{ $name }}!</div>');
$doc = Forte::parseFile('resources/views/welcome.blade.php');

Обидва методи проходять через лексер, який перетворює вихідний код у токени, та tree builder, який збирає вузли. Document зберігає результат.

Якщо тег не закритий або @if без @endif, парсер записує діагностику до документа і повертає часткове дерево. Ви можете запитувати та переписувати це дерево, як і будь-яке інше. Два зламані в'юшки у застосунку з чотирма сотнями в'юшок створюють дві діагностики, а решта 398 парсяться нормально.

Якщо розпарсити файл і відрендерити його назад без змін, ви отримаєте ті самі байти, включно з пробілами.

Запити до дерева

Методи запитів повертають ліниві колекції, тому ви можете фільтрувати та трансформувати їх так само, як будь-яку іншу Laravel-колекцію:

$forms = $doc->queryElements('form');
$conditionals = $doc->queryBlockDirectives(['if', 'unless']);
$components = $doc->queryComponents(['x-alert', 'livewire:*']);

Для одного відомого вузла є прямі пошуки:

$navigation = $doc->elementById('primary-navigation');
$hasHead = $doc->hasElement('head');

isDynamic() повідомляє, чи значення прийшло з прив'язаного атрибута, а attributeTokens('class') розбиває список класів на масив:

$form = $doc->firstElement('form');
$method = $form?->attribute('method');
$isDynamic = $method?->isDynamic() ?? false;
$classes = $form?->attributeTokens('class') ?? [];

Forte будує DOMDocument з дерева і запускає вираз через PHP DOMXPath, що і покриває вимогу ext-dom. Blade-конструкції стають елементами у просторі імен forte, тому блоки @if - це forte:if, а ехо - forte:echo:

$divs = $doc->xpath('//div[@class]')->get();
$conditionals = $doc->xpath('//forte:if')->get();

Збіги повертаються як вузли Forte, а не об'єкти DOMElement, тому ви можете передати результат запиту до перезапису. Пошук кожного <a> всередині <nav>, що не має href, - це один вираз замість рекурсивного проходу.

Переписування

apply(), rewrite() та rewriteWith() кожен повертають новий Document і залишають оригінал без змін, тому ви можете зберегти обидва і порівняти їх.

rewriteWith() обробляє разові зміни через замикання. Колбек отримує NodePath, а не сам вузол:

use Forte\Rewriting\NodePath;
$newDoc = $doc->rewriteWith(function (NodePath $path) {
 if ($path->isTag('a') && str_starts_with($path->getAttribute('href') ?? '', 'http')) {
 $path->setAttribute('target', '_blank');
 $path->setAttribute('rel', 'noopener noreferrer');
 }
});
echo $newDoc->render();

NodePath надає доступ до батьківського елемента, сусідів, предків та глибини вузла, на який він вказує, а також до методів getAttribute(), setAttribute(), removeAttribute(), addClass(), renameTag(), replaceWith(), remove(), insertBefore() та insertAfter(). skipChildren() і stopTraversal() завершують обхід достроково, коли ви знайшли потрібний вузол.

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

Для чогось довшого за замикання, напишіть візитор:

use Forte\Rewriting\Visitor;
use Forte\Rewriting\NodePath;
class NormalizeAlerts extends Visitor
{
 public function enter(NodePath $path): void
 {
 if (! $path->isTag('div') || ! $path->hasAttribute('data-alert')) {
 return;
 }
 $level = $path->getAttribute('data-alert') ?? 'info';
 $path->renameTag('x-alert');
 $path->setAttribute('type', $level);
 $path->removeAttribute('data-alert');
 }
}

enter() запускається до того, як відвідуються діти вузла, а leave() - після. Більшість проходів потребують лише enter(). Використовуйте leave(), коли зміна залежить від того, що сталося з дітьми, наприклад, розгортання елемента після того, як його вміст було переписано.

Є також RewriteBuilder для декларативної версії: виберіть вузли через XPath, потім поставте у чергу мутації для застосування до збігів.

Створення нових вузлів

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

use Forte\Rewriting\Builders\Builder;
Builder::element('div')->class('wrapper')->text('Hello');
Builder::directive('if', '($show)');
Builder::echo('$name');

Передайте результат до replaceWith(), insertBefore() або insertAfter().

Аудит, кодмоди та CI-перевірки

З'ясування вмісту в'юшок

Перед видаленням компонента знайдіть кожну в'юшку, що все ще його рендерить:

use Forte\Facades\Forte;
use Illuminate\Support\Facades\File;
foreach (File::allFiles(resource_path('views')) as $file) {
 if (! str_ends_with($file->getFilename(), '.blade.php')) {
 continue;
 }
 $uses = Forte::parseFile($file->getPathname())
 ->queryComponents(['x-alert'])
 ->count();
 if ($uses > 0) {
 echo "{$file->getRelativePathname()}: {$uses}\n";
 }
}

grep -rc 'x-alert' resources/views відповідає на схоже питання в один рядок, але рахує згадки в коментарях, у рядках @php та в атрибуті class="x-alert-icon" поряд із справжніми. Підрахунок вище - це кількість разів, коли компонент рендериться.

Виконання однієї зміни в сотнях в'юшок

Додавання loading="lazy" до кожного <img>, що не має атрибута loading, - це прохід, який можна запустити, переглянути як diff і перезапустити після коригування:

use Forte\Facades\Forte;
use Forte\Rewriting\NodePath;
use Illuminate\Support\Facades\File;
foreach (File::allFiles(resource_path('views')) as $file) {
 if (! str_ends_with($file->getFilename(), '.blade.php')) {
 continue;
 }
 $doc = Forte::parseFile($file->getPathname());
 $updated = $doc->rewriteWith(function (NodePath $path) {
 if ($path->isTag('img') && ! $path->hasAttribute('loading')) {
 $path->setAttribute('loading', 'lazy');
 }
 });
 file_put_contents($file->getPathname(), $updated->render());
}

<img>, написаний всередині коментаря, рядка або блоку @php, парситься як інший тип вузла, тому isTag('img') для нього повертає false. Зробити це правильно з регулярним виразом вимагає більше уваги, ніж решта роботи.

Якщо прохід був неправильним, виправте скрипт, запустіть git restore resources/views і спробуйте знову.

Провал білда при порушенні конвенції

POST-форма без @csrf - це один вираз, тому перевірка вміщається в тест:

use Forte\Facades\Forte;
use Illuminate\Support\Facades\File;
test('every POST form has a CSRF token', function () {
 $offenders = [];
 foreach (File::allFiles(resource_path('views')) as $file) {
 if (! str_ends_with($file->getFilename(), '.blade.php')) {
 continue;
 }
 $missing = Forte::parseFile($file->getPathname())
 ->xpath('//form[@method="POST"][not(.//forte:csrf)]')
 ->count();
 if ($missing > 0) {
 $offenders[] = $file->getRelativePathname();
 }
 }
 expect($offenders)->toBeEmpty();
});

Цей вираз читається зліва направо: кожна <form> з method="POST", зберігаючи ті, що не мають @csrf десь всередині. .// - це частина "десь всередині", тому форма, чий @csrf знаходиться в сусідній формі, все ще рахується як порушник.

@foreach, чия перша дитина не має wire:key, також є одним виразом:

$missingKeys = $doc->xpath('//forte:foreach[*[1][not(@*[name()="wire:key"])]]')->count();

//forte:foreach знаходить кожен блок @foreach. *[1] - це перший дочірній елемент цього блоку. not(@*[name()="wire:key"]) зберігає блоки, чия перша дитина не має атрибута wire:key. Форма @*[name()="..."] потрібна, оскільки XPath читає двокрапку в wire:key як роздільник простору імен.

Обидва вирази є тестами у власному тестовому наборі Forte. Другий залежить від вкладеності та від того, яка дитина йде першою.

Коли регулярного виразу достатньо

Для одного перейменування в тридцяти в'юшках sed або структурний пошук вашої IDE плюс ретельне читання diff - це менше роботи, ніж написання візитора. Те саме стосується зміни, яку ви робите один раз і більше ніколи не перевіряєте.

Forte варто налаштовувати у цих випадках:

  • Правило залежить від структури: "всередині форми", "перша дитина", "вкладено в @foreach". Grep знаходить рядки, тому не може виразити це.
  • Зміна зачіпає стільки файлів, що читання всього diff вручну займає більше часу, ніж написання проходу.
  • Перевірка запускається на кожному коміті, де збіг всередині коментаря або рядка ламає білд без причини.

Chisel та Reload

Два інші пакети побудовані на Forte, і ви можете використовувати обидва без написання візитора. Chisel - це prettier-plugin-blade v3, переписаний на новому парсері; проєкт повідомляє, що він форматує складні реальні шаблони в 140 разів швидше за попередню версію. Потребує Node 18 або новіше:

npm i -D prettier prettier-plugin-blade@^3

Форматування Blade тепер має більше опцій, включно з власною підтримкою Blade у Laravel Pint та format-on-save у PhpStorm.

Reload - це Vite-плагін, який патчить зміни Blade на сторінку без повного оновлення. Його документація називає його експериментальним, і він повертається до повного оновлення після max_patches_before_reload інкрементальних патчів:

composer require fortephp/reload --dev

Він стежить за resources/views/**/*.blade.php і інструментує елементи, компоненти, директиви та включення. Опція refresh у laravel-vite-plugin вже перезавантажує сторінку при зміні Blade-файлу. Reload патчить DOM замість цього.

Forte має ліцензію MIT і наразі у версії v1.1.0. Вихідний код на GitHub, документація та інтерактивний playground на fortephp.com.

2

Читати в документації

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

PayZephyr
Новини 12 вересня 2026

PayZephyr: Єдиний API для роботи з Stripe, Paystack та PayPal

PayZephyr - Laravel-пакет від Nwaneri Chukwunyere Kenneth, який об'єднує вісім платіжних провайдерів під одним зручним API. Підтримує автоматичне перемикання, захист від подвійної оплати, підписки та повернення коштів.

2
Laravel Telescope
Новини 11 вересня 2026

Artisan-команди для дебагу в Laravel Telescope 5.24.0

Laravel Telescope 5.24.0 додає дві нові Artisan-команди для роботи із записами із терміналу. Тепер можна переглядати запити, винятки, джоби та запити до бази даних без відкриття веб-інтерфейсу, а також отримувати дані у форматі JSON для скриптів та AI-агентів.

3

Вакансії за темою

BRAZY
25 днів тому

Middle Laravel/ JS Developer (Contract, 2–3 months)

Middle PHP/Laravel розробник для short-term контракту (2-3 місяці) на Affiliate проєкті. Основне завдання — розробка backend функціонала на Laravel, робота з PostgreSQL, Redis, Docker, а також реалізація landing page на Blade/JavaScript/jQuery. Вимогаються 2+ років досвіду з PHP/Laravel, знання JavaScript, досвід з iGaming/AdTech/affiliate платформами. Робота в remote режимі з можливістю перекриття європейського або US часового поясу.

RemoDevs
26 днів тому

Senior PHP Fullstack Developer

Senior PHP Fullstack Developer для платіжної системи. Розробка на PHP 8.2 із Symfony, Vue.js 3, React та TypeScript. Відповідальність за завершення проектів у 6-тижневих циклах, code review, планування та наставництво команди. Робота в remote-команді розміром 1-4 осіб з підтримкою клієнтів і розвитком платформи для платежів.

Frozeneon
27 днів тому

Statamic Developer (PHP/Laravel)

Розробник Statamic з досвідом PHP/Laravel 12/13. Розробка та обслуговування сайтів на Statamic: робота з шаблонами Antlers/Blade, налаштування SEO-метаданих, оптимізація продуктивності. Вимоги: досвід зі Statamic (Collections, Entries, Blueprints), HTML5/CSS, основи Laravel, впевнене володіння Git та розуміння On-page SEO.

Пакети за темою

Rector

rector/rector

Автоматичний рефакторинг і оновлення коду: піднімає синтаксис до нової версії PHP, застосовує набори правил для Laravel і виправляє застарілі API масово, а не вручну по файлах.

10,405 2.6.3 1

Laravel Breeze

laravel/breeze

Мінімальний стартовий набір автентифікації: Blade, Livewire або React/Vue через Inertia. Публікує звичайний, зрозумілий код у ваш застосунок, який далі правите як свій.

3,047 v2.4.2 13 1