Модели представления и блоки
Шаблон должен только выводить данные. Готовит их отдельный класс — модель представления (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(). Такой код нельзя переиспользовать, проверить тестом и заменить из другого модуля. В шаблоне оставляйте только вывод, условия и циклы, а всё остальное переносите в модель представления.