Encodera

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

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

Оглавление

Модели представления и блоки

Шаблон должен только выводить данные. Готовит их отдельный класс — модель представления (view model): он обращается к настройкам, базе данных, другим модулям и отдаёт шаблону готовые значения. Шаблон остаётся коротким, а логику можно проверить тестом.

Где лежит модель представления

Классы кладут в папку ViewModel модуля. Файл создают вручную.

app/code/Acme/Hello/
├── Controller/Index/Index.php
├── ViewModel/
│   └── Greeting.php        новый файл
├── etc/
└── view/frontend/
    ├── layout/hello_index_index.xml
    └── templates/greeting.phtml

Класс

// app/code/Acme/Hello/ViewModel/Greeting.php
namespace Acme\Hello\ViewModel;
 
use Magento\Framework\View\Element\Block\ArgumentInterface;
 
class Greeting implements ArgumentInterface
{
    public function getBenefits(): array
    {
        return ['Доставка за 2 дня', 'Оплата при получении', 'Возврат 30 дней'];
    }
}

Модель представления — обычный класс. Единственное требование — интерфейс ArgumentInterface: он не содержит методов и лишь разрешает передавать объект в блок.

Подключение в макете

<!-- app/code/Acme/Hello/view/frontend/layout/hello_index_index.xml -->
<block name="hello.greeting" template="Acme_Hello::greeting.phtml">
    <arguments>
        <argument name="view_model" xsi:type="object">Acme\Hello\ViewModel\Greeting</argument>
    </arguments>
</block>

Тип object означает, что Magento сам создаст объект указанного класса и передаст его блоку под именем view_model.

Использование в шаблоне

<!-- app/code/Acme/Hello/view/frontend/templates/greeting.phtml -->
<?php
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Acme\Hello\ViewModel\Greeting $viewModel */
$viewModel = $block->getData('view_model');
?>
<ul>
    <?php foreach ($viewModel->getBenefits() as $benefit): ?>
        <li><?= $escaper->escapeHtml($benefit) ?></li>
    <?php endforeach; ?>
</ul>

Выполните bin/magento cache:clean и откройте /hello: на странице появится список из трёх пунктов.

Зависимости

Всё, что нужно модели представления, она перечисляет в конструкторе — Magento передаст объекты сам:

// app/code/Acme/Hello/ViewModel/Greeting.php
use Magento\Store\Model\StoreManagerInterface;
 
public function __construct(private StoreManagerInterface $storeManager) {}
 
public function getStoreName(): string
{
    return (string) $this->storeManager->getStore()->getName();
}

Метод возвращает название текущей витрины. В шаблоне его выводят как $escaper->escapeHtml($viewModel->getStoreName()). После изменения конструктора удалите содержимое папки generated.

Свой класс блока

У блока тоже есть класс. Если в макете он не указан, используется Magento\Framework\View\Element\Template. Свой класс наследуют от него и кладут в папку Block:

// app/code/Acme/Hello/Block/Greeting.php
namespace Acme\Hello\Block;
 
use Magento\Framework\View\Element\Template;
 
class Greeting extends Template
{
    public function getGreeting(): string
    {
        return (string) __('Здравствуйте!');
    }
}

В макете класс задают атрибутом class="Acme\Hello\Block\Greeting", а в шаблоне метод вызывают через $block->getGreeting().

СпособКогда использовать
Модель представленияПочти всегда: класс простой и ни от чего не наследуется
Свой класс блокаКогда нужно изменить поведение самого блока, например правила кеширования

В старых модулях ядра данные готовят блоки, в новых — модели представления.

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

Логику пишут прямо в шаблоне: запрос к базе или вызов ObjectManager::getInstance(). Такой код нельзя переиспользовать, проверить тестом и заменить из другого модуля. В шаблоне оставляйте только вывод, условия и циклы, а всё остальное переносите в модель представления.

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

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