Несмотря на рост популярности JavaScript-фреймворков вроде React и Angular, создание пользовательского интерфейса на бэкенде по-прежнему остается распространенной задачей во многих веб-приложениях. Обычно для этого используются шаблонизаторы, такие как Twig, Blade или Latte, часто применяющие другой язык, близкий или связанный с основным языком приложения. Затем движок шаблонов преобразует код шаблона в язык приложения и, как правило, кэширует сгенерированный код для дальнейшего повторного использования.
В экосистеме PHP предпринималось много попыток создать пользовательский интерфейс приложения с помощью библиотеки классов.
Некоторые из самых популярных дополняют библиотеки шаблонов. spatie/html расширяет Laravel Blade функциями для построения HTML-элементов. Symfony Forms и Nette Forms могут создавать формы, а также валидировать и обрабатывать их значения.
Существуют и другие, менее известные и используемые PHP-пакеты, целью которых является построение UI приложения с помощью библиотеки классов. ATK UI, Viewi, ui-awesome/html, danielescherzer/html-builder и Sirius HTML — лишь немногие примеры таких библиотек.
Тем не менее, они не дотягивают до продвинутых возможностей таких библиотек шаблонов, как Blade или Twig . Даже предоставляя плавный (fluent) API, эти библиотеки обычно могут создавать лишь фрагменты HTML или отдельные HTML-компоненты, не имея возможности связать их воедино в полноценный блок интерфейса. В результате пользователю приходится дополнять их движком шаблонов или обычными вызовами PHP для конкатенации строк, итеративного или условного рендеринга.
Библиотека HTML Builder
HTML Builder — это PHP-библиотека, предоставляющая классы для представления HTML-компонентов, а также API для построения интерфейсов с использованием этих классов. UI строится с помощью дерева вызовов, где каждый компонент получает свои дочерние элементы в качестве параметров. HTML-теги создаются вызовом методов, названных в честь этих тегов, в то время как атрибуты задаются с помощью методов, начинающихся с set , за которым следует имя атрибута в PascalCase.
$this->html->div(
$this->html->div(
$this->html->label($this->html->text('Collation'))
->setFor('collation')
)->setStyle('width: 30%;'),
$this->html->div(
$this->html->select(
$this->html->option('(collation)')
->setValue('')
->setSelected(true),
$this->html->each($collations, fn(array $groupCollations, $group) =>
$this->html->list(
$this->html->when($group === '', fn() =>
$this->html->each($groupCollations, $this->html->option(...))
),
$this->html->when($group !== '', fn() =>
$this->html->optgroup(
$this->html->each($groupCollations, $this->html->option(...))
)->setLabel($group)
)
)
)
)->setName('collation')
)->setStyle('width: 70%;')
)->setStyle('display: flex; justify-content: space-between;')
Это тот же шаблон, построенный с помощью Twig .
<div style="display: flex; justify-content: space-between;">
<div style="width: 30%;">
<label for="collation">Collation</label>
</div>
<div style="width: 70%;">
<select name="collation">
<option value="" selected>(collation)</option>
{% for group, groupCollations in groups %}
{% if group == '' %}
{% for collation in groupCollations %}
<option>{{ collation }}</option>
{% endfor %}
{% else %}
<optgroup label="{{ group }}">
{% for collation in groupCollations %}
<option>{{ collation }}</option>
{% endfor %}
</optgroup>
{% endif %}
{% endfor %}
</select>
</div>
</div>
Виртуальные компоненты
То, что действительно делает библиотеку HTML Builder столь же мощной, как и шаблонизаторы, — это ее виртуальные компоненты.
Виртуальный компонент принимает данные приложения на вход и генерирует ноль, один или несколько компонентов на выход. Он не создает дополнительных HTML-тегов, поэтому его вывод состоит исключительно из результатов работы сгенерированных компонентов.
Между виртуальными и реальными компонентами существует двунаправленная связь. Реальный компонент может содержать в качестве дочерних как реальные, так и виртуальные компоненты, а виртуальный компонент может генерировать коллекцию реальных и виртуальных компонентов.
Виртуальные компоненты позволяют библиотеке HTML Builder генерировать HTML-элементы динамически — функция, недоступная в упомянутых выше библиотеках.
Типы виртуальных компонентов
Библиотека HTML Builder предоставляет 5 типов виртуальных компонентов.
Примечание: замыкание (closure), передаваемое в виртуальный компонент, должно возвращать один компонент, который может быть как реальным, так и виртуальным.
Компонент When
Компонент When имитирует конструкцию if в языках шаблонов. Он принимает булево условие и замыкание в качестве параметров и вызывает замыкание только тогда, когда условие истинно.
$this->html->when($user->isLoggedIn,
fn() => $this->html->div($this->html->text($user->email)));
Компонент List
Компонент List объединяет набор компонентов в один. Это позволяет передавать список компонентов туда, где ожидается один компонент, например, в качестве возвращаемого значения функции.
$this->html->when($user->isLoggedIn,
fn() => $this->html->list(
$this->html->b($this->html->text('Welcome ')),
$this->html->div($this->html->text($user->email))
);
Компонент Pick
Компонент Pick имитирует конструкцию switch в PHP. Он принимает несколько компонентов When на вход и вызывает замыкание, предоставленное первым истинным условием.
$this->html->pick(
$this->html->when($user === null,
fn() => $this->html->html('Welcome')),
$this->html->when($user->isAdmin,
fn() => $this->html->html("Welcome <b>{$user->name}</b>")),
$this->html->when(true,
fn() => $this->html->html("Welcome {$user->name}"))
);
Компонент Each
Компонент Each принимает массив элементов и замыкание в качестве параметров и вызывает замыкание для каждого элемента массива. Затем он генерирует соответствующее количество компонентов, каждый из которых может быть как реальным, так и виртуальным.
$this->html->select(
$this->html->option('(Default)')->setValue(0),
$this->html->each($choices,
fn($choice) => $this->html->option($choice->label))
);
Компонент Each также может принимать итератор или генератор в качестве параметра, а замыкание может принимать значение ключа массива в качестве второго параметра.
$this->html->select(
$this->html->option('(Default)')->setValue(0),
$this->html->each($choices,
fn($choice, $id) => $this->html->option($choice->label)->setValue($id))
);
Компонент Loop
Компонент Loop аналогичен компоненту Each , но его вторым параметром является объект LoopItem , который предоставляет различную информацию о текущем цикле.
$this->html->select(
$this->html->option('(Default)')->setValue(0),
$this->html->loop($choices,
fn($choice, $loop) => $this->list(
$this->when($loop->current->group !== $loop->prev?->group,
fn() => $this->html->option($loop->current->group)
->setClass('group-title disabled')),
$this->html->option($choice->label)->setValue($id))
)
);
Примечание: Компонент
Loopбыл вдохновлен этой статьей о Twig.
Сравнение с движками шаблонов
Библиотека HTML Builder сочетает в себе удобный fluent PHP API и легковесный объектно-ориентированный подход к генерации HTML.
Ее виртуальные компоненты поддерживают динамический контент, откладывая выполнение замыканий до момента рендеринга. Благодаря этому создание пользовательского интерфейса полностью остается в рамках PHP с использованием единообразного fluent API от начала и до конца, без необходимости выполнения отдельного этапа компиляции шаблонов.
Сочетание обычных и виртуальных компонентов предоставляет строительные блоки, необходимые для реализации основных функций, обычно встречающихся в шаблонизаторах (включая условный рендеринг и итерации), при сохранении fluent API. В сочетании с объектно-ориентированными возможностями PHP, такими как наследование, трейты, перегрузка методов и композиция, этот подход предлагает гибкий способ структурирования и повторного использования кода интерфейса.
Подобно движкам шаблонов, HTML Builder также может расширяться пользовательскими функциями для удовлетворения специфических потребностей приложения.
Зачем использовать HTML Builder?
При использовании библиотеки HTML Builder пользовательский интерфейс приложения организуется в виде библиотеки PHP-классов, а не набора файлов шаблонов. Это делает UI независимым от какого-либо конкретного фреймворка приложения и устраняет необходимость в отдельном движке шаблонов.
Классы интерфейса могут принимать структурированные объекты и доменно-специфичные типы данных вместо того, чтобы полагаться исключительно на простые массивы. Это позволяет более явно определять интерфейсы между UI и другими компонентами приложения, улучшая разделение ответственности (separation of concerns) и упрощая понимание зависимостей.
UI на основе классов также может извлечь выгоду из привычных практик разработки программного обеспечения, включая инкапсуляцию, наследование, повторное использование кода и модульное тестирование (unit testing). Для приложений, где интерфейс поддерживается как часть кодовой базы, такой подход может упростить организацию, поддержку и тестирование кода.
Что меняется с HTML Builder?
Обычно движки шаблонов компилируют шаблоны в PHP-код и кэшируют скомпилированный результат для повторного использования. Это снижает накладные расходы на многократную обработку шаблонов и может дать преимущество в производительности по сравнению с объектно-ориентированным подходом к генерации HTML.
HTML Builder создает объекты для HTML-компонентов и использует такие возможности, как магические методы, замыкания и вызовы методов для компоновки финального результата. Эти механизмы вносят определенные накладные расходы во время выполнения по сравнению с выполнением предварительно скомпилированного PHP-кода.
Тем не менее, производительность зависит от реализации, структуры интерфейса и объема работы, связанной с его рендерингом. Компромисс заключается в выборе подхода, который делает упор на объектно-ориентированную композицию, расширяемость и повторное использование кода, а не на минимизацию накладных расходов во время выполнения.
Некоторые движки шаблонов также предоставляют песочницу (sandbox), которая позволяет выполнять ненадежные шаблоны с ограниченными возможностями, повышая безопасность. Разработчики могут определять, какие функции шаблонам разрешено вызывать. Эта функция особенно полезна, когда приложению необходимо отрисовывать шаблоны, предоставленные третьими лицами.
HTML Builder не предоставляет песочницу, поскольку разработана для непосредственного построения HTML из PHP-кода, а не для выполнения ненадежных шаблонов. Классы интерфейса предназначены для того, чтобы быть частью кодовой базы приложения, а не служить средой с ограниченным доступом для выполнения внешних шаблонов.
Библиотека UI Builder
Библиотека UI Builder расширяет библиотеку HTML Builder функциями для создания компонентов интерфейса на основе CSS-фреймворков. Она предоставляет набор примитивов для создания таких компонентов, как кнопки, карточки, вкладки, пагинация и многое другое, а также для настройки их свойств.
Библиотека UI Builder в настоящее время используется для построения интерфейса приложения Jaxon DbAdmin, и пример ниже взят из его кодовой базы.
$this->html->build(
$this->html->buttonGroup(
$this->html->button(
$this->html->text($this->trans->lang('Columns ')),
$this->html->when($columnCount > 0, fn() =>
$this->html->badge("$columnCount")->primary())
)->outline()
->secondary()
->fullWidth(),
$this->html->button(
$this->html->text($this->trans->lang('Filters ')),
$this->html->when($filterCount > 0, fn() =>
$this->html->badge("$filterCount")->primary())
)->outline()
->secondary()
->fullWidth(),
$this->html->button(
$this->html->text($this->trans->lang('Order ')),
$this->html->when($sorterCount > 0, fn() =>
$this->html->badge("$sorterCount")->primary())
)->outline()
->secondary()
->fullWidth()
)->fullWidth()
);
Библиотека UI Builder разделена на несколько PHP-пакетов.
Пакет lagdo/ui-builder предоставляет интерфейс BuilderInterface , определяющий функции, которые должен реализовывать каждый построитель (builder). Он также предоставляет базовые классы для UI-компонентов и реализует общий набор функций, разделяемых различными построителями.
Каждый поддерживаемый CSS-фреймворк имеет собственный пакет, который реализует класс Builder и соответствующие классы UI-компонентов. Каждый класс компонента определяет, как генерируется HTML компонента для целевого фреймворка, используя функции, предоставляемые пакетами lagdo/ui-builder и lagdo/html-builder.
Специфичный для фреймворка класс Builder расширяет класс AbstractBuilder , предоставляемый пакетом UI Builder, и должен лишь указать имена используемых им классов компонентов.
В настоящее время поддерживаются следующие CSS-фреймворки:
Пакет Bootstrap 3 устарел (deprecated).
Пакеты для Tailwind CSS и Bulma находятся в разработке и будут доступны в ближайшее время.
Заключительные мысли
Создание пользовательского интерфейса приложения исключительно на PHP всегда было непростой задачей. PHP-библиотекам, разработанным для этих целей, традиционно не хватало многих функций, предлагаемых шаблонизаторами, из-за чего разработчикам часто приходилось дополнять их движком шаблонов или прямыми вызовами PHP.
Благодаря своим виртуальным компонентам библиотека HTML Builder устраняет этот пробел. Ее fluent API и методы, названные в честь HTML-тегов, упрощают создание сложных интерфейсов приложений целиком на PHP.
Библиотека UI Builder делает еще один шаг в этом направлении, расширяя библиотеку HTML Builder компонетами интерфейса более высокого уровня, такими как кнопки, карточки, вкладки и пагинация. Пакеты, специфичные для конкретных фреймворков, генерируют соответствующий HTML для таких CSS-фреймворков, как Bootstrap , Tailwind CSS и Bulma .
Комментарии (0)
Пока нет комментариев — будьте первым.