No history yet

Архитектура Hyva ViewModel

ViewModel: мост между PHP и Alpine.js

В архитектуре Hyva ViewModel играет ключевую роль. Это простой PHP-класс, который служит связующим звеном между сложной логикой бэкенда Magento 2 и реактивным фронтендом на Alpine.js. Представьте его как куратора данных: он собирает, обрабатывает и подготавливает информацию на сервере, а затем передает ее в чистом и удобном виде в ваш .phtml шаблон, где ее уже подхватывает Alpine.js.

Основная задача ViewModel — отделить логику получения и подготовки данных от логики их отображения. Это делает шаблоны чище, а код — более структурированным и тестируемым.

Как подключить ViewModel

Существует два основных способа сделать ViewModel доступной в вашем шаблоне. Первый — это классический подход Magento через Layout XML. Вы объявляете ViewModel как аргумент для блока.

<referenceBlock name="your.block.name">
    <arguments>
        <argument name="viewModel" xsi:type="object">Vendor\Module\ViewModel\MyViewModel</argument>
    </arguments>
</referenceBlock>

Затем в шаблоне вы получаете его через $block->getViewModel(). Этот способ работает, но Hyva предлагает более изящное и современное решение.

Второй, и предпочтительный, способ — это прямо в .phtml файле. Hyva анализирует PHPDoc-блок @var в начале шаблона и автоматически создает экземпляр указанного класса. Это устраняет необходимость в громоздких XML-конфигурациях для каждого ViewModel.

<?php
/** @var \Vendor\Module\ViewModel\MyViewModel $myViewModel */
/** @var \Hyva\Theme\ViewModel\Heroicons $heroicons */
?>

<!-- Теперь $myViewModel и $heroicons доступны здесь -->

Этот подход не только чище, но и позволяет легко подключать несколько ViewModel в один шаблон, например, ваш кастомный ViewModel и стандартный Heroicons для отображения иконок.

Создаем динамический компонент цены

Давайте создадим практический пример: компонент, который отображает цену товара и показывает специальную цену со скидкой по клику. Мы создадим собственный ViewModel, который будет готовить данные о ценах продукта.

Сначала создадим сам класс ViewModel. Он будет принимать объект продукта и возвращать массив с данными о ценах.

<?php
declare(strict_types=1);

namespace Vendor\Module\ViewModel;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\Pricing\Helper\Data as PricingHelper;
use Magento\Framework\View\Element\Block\ArgumentInterface;

class ProductPrice implements ArgumentInterface
{
    private PricingHelper $pricingHelper;

    public function __construct(PricingHelper $pricingHelper)
    {
        $this->pricingHelper = $pricingHelper;
    }

    public function getPriceData(ProductInterface $product): array
    {
        $finalPrice = $product->getFinalPrice();
        $regularPrice = $product->getPriceInfo()->getPrice('regular_price')->getAmount()->getValue();

        return [
            'regularPrice' => $this->pricingHelper->currency($regularPrice, true, false),
            'finalPrice' => $this->pricingHelper->currency($finalPrice, true, false),
            'hasSpecialPrice' => $finalPrice < $regularPrice,
            'discountPercent' => $this->calculateDiscount($regularPrice, $finalPrice)
        ];
    }

    private function calculateDiscount(float $regular, float $final): int
    {
        if ($regular > $final) {
            return (int)round((($regular - $final) / $regular) * 100);
        }
        return 0;
    }
}

Теперь используем этот ViewModel в нашем .phtml шаблоне. Мы внедрим его автоматически и передадим данные в Alpine.js компонент через x-data. Для безопасного преобразования PHP-массива в мы используем специальный метод hyva_json_encode. Он надежнее стандартного json_encode, так как правильно обрабатывает возможные ошибки.

<?php
/** @var \Vendor\Module\ViewModel\ProductPrice $priceViewModel */
/** @var \Magento\Catalog\Api\Data\ProductInterface $product */
?>

<div x-data="{
    priceData: <?= $escaper->escapeHtml(hyva_json_encode($priceViewModel->getPriceData($product))) ?>,
    showDiscount: false
}">
    <div @click="showDiscount = !showDiscount" class="cursor-pointer">
        <span x-text="priceData.finalPrice" class="font-bold text-lg"></span>
        <template x-if="priceData.hasSpecialPrice">
            <span x-text="priceData.regularPrice" class="line-through text-gray-500 ml-2"></span>
        </template>
    </div>
    <div x-show="showDiscount && priceData.hasSpecialPrice" x-transition class="mt-2 text-green-600">
        Скидка <span x-text="priceData.discountPercent"></span>%!
    </div>
</div>

В этом примере вся логика форматирования цен и расчета скидки инкапсулирована в ViewModel. Шаблон остается чистым: он только передает данные и описывает, как на них должен реагировать интерфейс.

Этот подход, сочетающий мощь PHP на бэкенде и реактивность Alpine.js на фронтенде через элегантный мост в виде ViewModel, является основой философии Hyva. Он позволяет создавать сложные, динамические интерфейсы, сохраняя при этом код чистым, производительным и удобным для поддержки.

Quiz Questions 1/4

Какова основная роль ViewModel в архитектуре Hyva?

Quiz Questions 2/4

Какой метод рекомендуется использовать в Hyva для безопасной передачи данных из PHP ViewModel в компонент Alpine.js (x-data)?

Теперь вы знаете, как создавать и использовать ViewModel для эффективной передачи данных из Magento в Alpine.js.