Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Turbo и живые компоненты

Живой поиск, форма без перезагрузки, обновление части страницы обычно означают много JavaScript. Набор пакетов Symfony UX даёт то же самое средствами Twig и PHP: страница остаётся серверной, но ведёт себя как приложение.

Turbo

composer require symfony/ux-turbo

После установки переходы по ссылкам и отправка форм перестают перезагружать страницу целиком: Turbo запрашивает новую страницу в фоне и заменяет содержимое body. Код менять не нужно.

Одно условие: после успешной отправки формы контроллер должен перенаправлять, а при ошибках проверки — отвечать кодом 422. Форма Symfony делает это сама, если передать её в render().

Turbo Frames

Рамка обновляет только свою часть страницы:

<turbo-frame id="comments">
    {% for comment in comments %}
        <p>{{ comment.text }}</p>
    {% endfor %}
 
    <a href="{{ path('post_comments', {id: post.id, page: page + 1}) }}">Ещё</a>
</turbo-frame>

Щелчок по ссылке внутри рамки загрузит новую страницу, но заменит только элемент с тем же id. Остальная страница не тронется.

Компоненты Twig

composer require symfony/ux-twig-component

Компонент — класс и шаблон, которые вместе описывают кусок интерфейса:

// src/Twig/Components/Alert.php
namespace App\Twig\Components;
 
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
 
#[AsTwigComponent]
class Alert
{
    public string $type = 'info';
    public string $message;
}
{# templates/components/Alert.html.twig #}
<div class="alert alert-{{ type }}">{{ message }}</div>
<twig:Alert type="success" message="Статья сохранена" />

Живой компонент

composer require symfony/ux-live-component

Живой компонент перерисовывается на сервере, когда меняются его данные:

// src/Twig/Components/ProductSearch.php
namespace App\Twig\Components;
 
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
 
#[AsLiveComponent]
class ProductSearch
{
    use DefaultActionTrait;
 
    #[LiveProp(writable: true)]
    public string $query = '';
 
    public function __construct(private ProductRepository $products) {}
 
    public function getProducts(): array
    {
        return $this->products->search($this->query);
    }
}
{# templates/components/ProductSearch.html.twig #}
<div {{ attributes }}>
    <input type="search" data-model="query" placeholder="Поиск">
 
    <ul>
        {% for product in this.products %}
            <li>{{ product.title }}</li>
        {% endfor %}
    </ul>
</div>
<twig:ProductSearch />

Посетитель печатает — компонент отправляет запрос, сервер выполняет поиск и возвращает новую разметку. Ни строки своего JavaScript.

Действия

Метод с атрибутом #[LiveAction] можно вызвать из шаблона:

use Symfony\UX\LiveComponent\Attribute\LiveAction;
 
#[LiveProp]
public int $count = 0;
 
#[LiveAction]
public function increment(): void
{
    $this->count++;
}
<button data-action="live#action" data-live-action-param="increment">+1</button>
<span>{{ count }}</span>

Частая ошибка

Свойство с writable: true может изменить посетитель — в том числе подставить значение, которого нет в интерфейсе. Не делайте изменяемыми идентификаторы, цены и роли, а в действиях проверяйте права так же, как в контроллере. Свойства без writable защищены подписью, и подделать их нельзя.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.