Знаете, сейчас, в эпоху хороших фреймворков принято презирать
всякие {название CMS, которую считаете ужасной} и прочие битриксы.
И не мудрено, ведь эти вещи изначально создавались как будто не для
программистов а для кодеров. Это можно оправдать наличием большого
количества легаси, т.к. они писались давным-давно в
далёкой-далёкой галактике. Они решают множество нужных и
полезных задач, имеют огромные коммьюнити и тысячи плагинов, но
когда ты смотришь под капот - медленно седеющие волосы на голове
начинают шевелиться в такт "архитектуре".
Дисклеймер
-
Все нижеуказанное не несет никакой ценности для опытных Magento
разработчиков, т.к. не содержит ничего для них нового. Эта статья
написана скорее с целью популяризации Magento 2, и немного -
хвастовства в виде "смотри как оно умеет". Также, возможно, статья
подойдет новичкам в качестве туториала.
-
Я не спорю, что помимо плюсов есть еще и минусы, и не говорю что
Magento идеальна во всех местах.
Я один раз заглянул в проект на ModX (и пусть меня закидают
ссаными тапками камнями), и мой мир никогда не будет
прежним. Возможно, поэтому, когда в разговоре меня спрашивают, с
чем я работаю, а в ответ слышат "magento", сразу кривятся носы, а
мнение, скорее всего, падает куда-то очень низко.
Мне обидно от этого, ведь Magento уже давно не (да и никогда до
конца не была) топ-примером того, как никогда делать не стоит.
Напротив, начиная со второй версии, разработчики двигаются по
правильному пути. Да, есть еще много легаси, а некоторые решения
заставляют округлить зенки и накрыть лицо пятерней, но в целом, на
данный момент, на magento 2 можно и легко (и во многом система сама
диктует) построить грамотную архитектуру проекта/модуля.
Итак, топ вещей, которые реализованы не (намного) хуже, а где-то
и лучше, чем в популярных фреймворках:
Мощная система DI
Мы не задумываемся над созданием объектов, система делает все за
нас. Мы просто указываем список нужных параметров в конструкторе с
типами, и используем их
<?phpnamespace Vendor\Module\Model;use Vendor\Module\Api\FooInterface;use Vendor\Module\Model\Bar;class SomeModel{ private $foo; private $bar; private $param public function __construct(FooInterface $foo, Bar $bar, string $param) { $this->foo = $foo; $this->bar = $bar; $this->param = $param; }}
Хочу отметить, что тонкие настройки аргументов, указание классов
для интерфейсов и многое другое - описывается в xml файле
di.xml:
<?xml version="1.0" encoding="UTF-8"?><config xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <!-- body --></config>
1. Подмена классов и указание реализации для интерфейсов
указываем preference для интерфейса, и потом везде в коде
используем только интерфейс
<preference for="Vendor\Module\Api\FooInterface" type="Vendor\Module\Model\Foo" />
Также можно сделать наследника какого-то класса, и сказать, что
теперь везде должен использоваться наследник. Соответственно во
всех конструкторах и использованиях ObjectManager объект
родительского класса подменится на объект дочернего.
<preference for="Vendor\Module\Model\Bar" type="Vendor\Module\Model\BarChild" />
2. Указание аргументов
Мы можем указать для конкретного класса список его аргументов,
или подменить какой-либо параметр
<type name="Vendor\Module\Model\SomeModel"> <arguments> <argument name="bar" xsi:type="object">Vendor\Module\Model\BarChild</argument> <argument name="param" xsi:type="string">some string</argument> </arguments></type>
3. Виртуальные типы
Виртуальный тип - это обычный класс с набором своих личных
параметров. Такие виртуальные типы физически не существуют, и могут
быть использованы только как аргументы в других классах
<virtualType name="Vendor\Module\Model\SomeModelForSomethingElse" type="Vendor\Module\Model\SomeModel"> <arguments> <argument name="param" xsi:type="string">another string</argument> </arguments></virtualType><type name="Vendor\Module\Model\SomeService"> <arguments> <argument name="someModel" xsi:type="object">Vendor\Module\Model\SomeModelForSomethingElse</argument> </arguments></type>
4. Плагины
Практически на любой существующий публичный метод класса можно
повесить плагины. Можно выбрать, когда будет выполняться плагин -
до (префикс before), во время (префикс around) или после (префикс
after) выполнения метода. Давайте рассмотрим пример:
<?phpnamespace Vendor\Module\Model;class SomeService{ public function doSomething(Foo $foo, Bar $bar, string $param, int $number) { // something }}
Задаём плагин:
<type name="Vendor\Module\Model\SomeService"> <plugin name="Vendor_Module::MyCustomPlugin" type="Vendor\Module\Plugin\SomeServicePlugin" /></type>
Код плагина (методы плагина должны называться: префикс +
название основного метода с большой буквы):
<?phpnamespace Vendor\Module\Plugin;use Vendor\Module\Model\SomeService;class SomeServicePlugin{ // вызывается до вызова основного метода. Мы можем изменить входные параметры. public function beforeDoSomething(SomeService $subject, Foo $foo, Bar $bar, string $param, int $number) { $param = 'change param'; // если изменять параметры не нужно - то можно ничего не возвращать return [$foo, $bar, $param, $number]; } // метод вызывается "во время" вызова основного метода. В параметрах - Closure - это собственно основной метод public function aroundDoSomething(SomeService $subject, \Closure $proceed, Foo $foo, Bar $bar, string $param, int $number) { // do something $result = $proceed($foo, $bar, $param, $number); // вызываем основной метод // do something else return $result; } // метод вызывается после вызова основного метода. Мы можем менять результат public function afterDoSomething(SomeService $subject, $result, Foo $foo, Bar $bar, string $param, int $number) { $result += 1; // меняем результат return $result; }}
Также в каждый метод плагина передаётся сам покрываемый плагином
объект. Это удобно, если нам нужно вызвать какой-то другой его
метод в процессе выполнения плагина.
5. ObjectManager
Если нам нужно создавать объекты на лету в процессе выполнения
алгоритма - для этого есть ObjectManager. Он имеет два метода - get
и create. get - берет существующий экземпляр класса (или создает
его, если еще не создавал ранее), а create - создаёт.
Взгляните на это объявление пула процессоров:
<?phpnamespace Vendor\Module\Model;use Magento\Framework\ObjectManagerInterface;class ProcessorPool{ private $objectManager; private $processors; public function __construct(ObjectManagerInterface $objectManager, array $processors) { $this->objectManager = $objectManager; $this->processors = $processors; } public function getProcessor(string $code) { return $this->objectManager->get($this->processors[$code]); }}
И xml:
<type name="Vendor\Module\Model\ProcessorPool"> <arguments> <argument name="processors" xsi:type="array"> <item name="foo" xsi:type="string">Vendor\Module\Model\Processor\FooProcessor</item> <item name="bar" xsi:type="string">Vendor\Module\Model\Processor\BarProcessor</item> </argument> </arguments></type>
С учетом того, что все di.xml всех модулей мержатся в одну
большую инструкцию для ObjectManager - мы можем добиться довольно
высокой гибкости кода. Где-то в другом модуле мы можем описать еще
один процессор, и заменить один из существующих:
<type name="Vendor\Module\Model\ServicePool"> <arguments> <argument name="processors" xsi:type="array"> <item name="foo" xsi:type="string">Vendor\NewModule\Model\Processor\NewFooProcessor</item> <item name="something" xsi:type="string">Vendor\NewModule\Model\Processor\SomethingProcessor</item> </argument> </arguments></type>
6. Автогенерация фабрик и прокси
В некоторых случаях нам нужно всегда создавать новый объект,
например - entity-модель. Для этого лучше всего подходят фабрики.
Но нам не нужно писать код фабрики руками, для нас это сделает
встроенный автогенератор. Достаточно в конструкторе просто дописать
слово Factory к требуемому классу, и мы получим его фабрику. Это
работает также и с интерфейсами:
<?phpnamespace Vendor\Module\Model;use Vendor\Module\Api\Data\SomeModelInterfaceFactory;class SomeModelRepository{ private $someModelFactory; // класс \Vendor\Module\Api\Data\SomeModelInterfaceFactory будет сгенерирован автоматически public function __construct(SomeModelInterfaceFactory $someModelFactory) { $this->someModelFactory = $someModelFactory; } public function doSomething() { // создаст объект класса, имплементирующего \Vendor\Module\Api\Data\SomeModelInterface со всеми его зависимостями $someModel = $this->someModelFactory->create(); }}
Если у нас есть какой-то сервис, который при инициализации
тратит много ресурсов, и мы не уверены, будем ли мы точно его
использовать в другом классе - мы можем обернуть его в Proxy. Мы
можем дописать "\Proxy" точно также, как и "Factory" к типу
параметра в конструкторе, но это нарушит расширяемость кода,
поэтому лучше задавать Proxy через di.xml
<?phpnamespace Vendor\Module\Model;class SomeModelClass{ private $someBigService; public function __construct(SomeBigService $someBigService) { $this->someBigService = $someBigService; } public function doSomething(bool $condition) { // мы не уверены, что сервис нам понадобится, и т.к. его инициализация занимает много времени // лучше создавать его только тогда, когда он точно нужен if ($condition) { $this->someBigService->execute(); } }}
di.xml
<type name="Vendor\Module\Model\SomeModelClass"> <arguments> <argument name="someBigService" xsi:type="object">Vendor\Module\Model\SomeBigService\Proxy</argument> </arguments></type>
И, вуаля, объект SomeBigService создастся только в момент вызова
execute().
Layout
Layout, на мой взгляд, одно из самых гибких реализаций для
сборки страницы. Реализация xml layouts в Magento позволяет
расширить или изменить страницу из множества мест, будь то модуль,
тема, или другой участок кода. Layout файл в Magento обычно
называется примерно так:
catalog_product_view.xml
Название файла - это handle, где catalog - перекликается с
названием модуля (это название роута в конфигурации модуля),
product - название папки, в которой лежит контроллер, а view -
название контроллера. Это типовое наименование позволяет не
указывать в контроллере, какой именно Layout выбирать, и
подставляется автоматически. Однако ничто не мешает назвать layout
по своему, и указать его название в контроллере. Т.к. это xml - мы
можем подключить сколько угодно layout в контроллере, и они все
будут смержены в один.
Для добавления на страницу продукта какого-то расширяемого
блока, нам достаточно написать:
<?xml version="1.0"?><!-- Vendor/Module/view/frontend/layout/catalog_product_view.xml --><page xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <!-- Мы имеем аттрибуты before и after, чтобы указать место, в котором нужно будет вывести блок --> <container name="vendor.module.some.container" htmlTag="div" htmlClass="my_custom_container" before="-"> <block class="Magento\Framework\View\Element\Template" name="vendor.module.custom_product_block" template="Vendor_Module::product/view/custom_block.phtml" /> </container> </referenceContainer> </body></page>
Я намеренно обернул блок в контейнер. Теперь в другом модуле мы
можем без проблем добавить в то же место еще один блок, перед или
после существующего:
<?xml version="1.0"?><!-- Vendor/AnotherModule/view/frontend/layout/catalog_product_view.xml --><page xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="vendor.module.some.container"> <block class="Magento\Framework\View\Element\Template" name="vendor.another_module.custom_product_block" template="Vendor_AnotherModule::another_custom_block.phtml" after="vendor.module.custom_product_block"/> </referenceContainer> </body></page>
Layouts также являются "наследуемыми":
<?xml version="1.0"?><page xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <!-- Подключаем какой-то другой layout --> <update handle="my_custom_layout_handle" /> <!-- можно удалять ненужное --> <referenceContainer name="container1" remove="true"/> <referenceBlock name="child_block1" remove="true"/> <!-- Или переместить: например container3 внутрь container2 --> <move element="container3" destination="container2"/> </body></page>
Т.к. практически все отображение в Magento использует layouts,
то можно смело утверждать (с некоторыми оговорками), что это
даёт возможность кастомизировать что угодно и где угодно (ну
почти).
ViewModel
Когда разрабатываешь систему, которая требует отображения
множества различных данных на фронте, в том числе - повторяющиеся
на некоторых страницах данные, приходится постоянно пробрасывать
эти самые данные через Controller во View. Это может быть
утомительно, и приходится придумывать различного рода уловки,
расширения, чтобы получать эти данные на уровне View, не повторяясь
в контроллерах.
Долгое время в Magento использовались классы блоков (например
Magento\Catalog\Block\Product\View), которые предоставляли доступ к
данным из template - вида
<?php // Vendor/Module/view/frontend/templates/some_template.phtml// внутри template доступна переменная $block, являющаяся объектом указанного в layoutкласса?><?= $block->getSomeData() ?>
Но с годами блоки разрастались, обрастали наследниками (т.к.
содержали в себе много нужного в других местах кода), и
превращались в полотна из несвязанных между собой методов. В
Magento 2.2 были представлены ViewModel, как возможность
использовать сразу несколько моделей внутри template, и построить
свой код архитектурно грамотнее:
<?xml version="1.0"?><page xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <block class="Magento\Framework\View\Element\Template" name="some_name" template="Vendor_Module::some_template.phtml"> <arguments> <argument name="some_view_model" xsi:type="object">Vendor\Module\ViewModel\SomeViewModel</argument> <argument name="another_view_model" xsi:type="object">Vendor\Module\ViewModel\AnotherViewModel</argument> </arguments> </block> </body></page>
и в template:
<?php/** @var Vendor\Module\ViewModel\SomeViewModel $someViewModel */$someViewModel = $block->getSomeViewModel();/** @var Vendor\Module\ViewModel\AnotherViewModel $anotherViewModel */$anotherViewModel = $block->getAnotherViewModel();?>
Соответственно, одну ViewModel можно использовать на многих
страницах, не завися и не задумываясь о контроллере.
Declarative Schema
Самый популярный способ накатывать обновления на БД - это
миграции. И я видел множество проектов, где папки с миграциями, да
и сами миграции выглядели довольно внушительно. И вдобавок (если уж
говорить о продуктовой разработке, когда пользователи получают
новые версии продукта для обновления, как раз Magento-случай), если
разворачивать все с нуля - все миграции должны быть выполнены.
Declarative Schema - это, по сути, текущее состояние таблицы,
описанное в xml файле. Нам не нужно писать и поддерживать миграции,
мы просто меняем xml файлик. Magento сама считывает его, сравнивает
с текущим состоянием таблицы в БД, и меняет таблицу, если это
необходимо.
Конечно, далеко не все миграции - это изменение структуры БД,
поэтому наряду с Declarative Schema существует еще и механизм
патчей (php-классов, выполняющих какую-то логику).
UI компоненты
Т.к. xml - довольно гибкий формат, он позволяет нам достаточно
многое. Помните layoutы? Xml Layouts + DI позволяют нам строить
дерево JS компонентов для рендеринга страницы также гибко, как и
дерево PHP блоков. Взгляните на layout для чекаута (осторожно,
очень много текста)
checkout_index_index.xml
<?xml version="1.0"?><!--/** * Copyright Magento, Inc. All rights reserved. * See COPYING.txt for license details. */--><page xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" layout="checkout" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Magento\Checkout\Block\Onepage" name="checkout.root" template="Magento_Checkout::onepage.phtml" cacheable="false"> <arguments> <argument name="jsLayout" xsi:type="array"> <item name="types" xsi:type="array"> <item name="form.input" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/abstract</item> <item name="config" xsi:type="array"> <item name="provider" xsi:type="string">checkoutProvider</item> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkoutProvider</item> </item> <item name="template" xsi:type="string">ui/form/field</item> <item name="elementTmpl" xsi:type="string">ui/form/element/input</item> </item> </item> </item> <item name="components" xsi:type="array"> <item name="checkout" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Checkout/onepage</item> </item> <item name="children" xsi:type="array"> <item name="errors" xsi:type="array"> <item name="sortOrder" xsi:type="string">0</item> <item name="component" xsi:type="string">Magento_Ui/js/view/messages</item> <item name="displayArea" xsi:type="string">messages</item> </item> <item name="authentication" xsi:type="array"> <item name="sortOrder" xsi:type="string">1</item> <item name="component" xsi:type="string">Magento_Checkout/js/view/authentication</item> <item name="displayArea" xsi:type="string">authentication</item> <item name="children" xsi:type="array"> <!--Additional authentication fields--> <item name="errors" xsi:type="array"> <item name="sortOrder" xsi:type="string">0</item> <item name="component" xsi:type="string">Magento_Checkout/js/view/authentication-messages</item> <item name="displayArea" xsi:type="string">messages</item> </item> </item> </item> <item name="progressBar" xsi:type="array"> <item name="sortOrder" xsi:type="string">0</item> <item name="component" xsi:type="string">Magento_Checkout/js/view/progress-bar</item> <item name="displayArea" xsi:type="string">progressBar</item> <item name="config" xsi:type="array"> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkout.steps.shipping-step.shippingAddress</item> <item name="1" xsi:type="string">checkout.steps.billing-step.payment</item> </item> </item> </item> <item name="estimation" xsi:type="array"> <item name="sortOrder" xsi:type="string">10</item> <item name="component" xsi:type="string">Magento_Checkout/js/view/estimation</item> <item name="displayArea" xsi:type="string">estimation</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Checkout/estimation</item> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkout.sidebar</item> </item> </item> </item> <item name="steps" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">steps</item> <item name="children" xsi:type="array"> <item name="shipping-step" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="sortOrder" xsi:type="string">1</item> <item name="children" xsi:type="array"> <item name="step-config" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="children" xsi:type="array"> <item name="shipping-rates-validation" xsi:type="array"> <item name="children" xsi:type="array"> <!--Step configuration components--> </item> </item> </item> </item> <item name="shippingAddress" xsi:type="array"> <item name="config" xsi:type="array"> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkout.steps.shipping-step.step-config</item> <item name="1" xsi:type="string">checkoutProvider</item> </item> <item name="popUpForm" xsi:type="array"> <item name="element" xsi:type="string">#opc-new-shipping-address</item> <item name="options" xsi:type="array"> <item name="type" xsi:type="string">popup</item> <item name="responsive" xsi:type="boolean">true</item> <item name="innerScroll" xsi:type="boolean">true</item> <item name="title" xsi:type="string" translate="true">Shipping Address</item> <item name="trigger" xsi:type="string">opc-new-shipping-address</item> <item name="buttons" xsi:type="array"> <item name="save" xsi:type="array"> <item name="text" xsi:type="string" translate="true">Ship Here</item> <item name="class" xsi:type="string">action primary action-save-address</item> </item> <item name="cancel" xsi:type="array"> <item name="text" xsi:type="string" translate="true">Cancel</item> <item name="class" xsi:type="string">action secondary action-hide-popup</item> </item> </item> </item> </item> </item> <item name="component" xsi:type="string">Magento_Checkout/js/view/shipping</item> <item name="provider" xsi:type="string">checkoutProvider</item> <item name="sortOrder" xsi:type="string">10</item> <item name="children" xsi:type="array"> <item name="customer-email" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/form/element/email</item> <item name="displayArea" xsi:type="string">customer-email</item> <item name="tooltip" xsi:type="array"> <item name="description" xsi:type="string" translate="true">We'll send your order confirmation here.</item> </item> <item name="children" xsi:type="array"> <item name="before-login-form" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-login-form</item> <item name="children" xsi:type="array"> <!-- before login form fields --> </item> </item> <item name="additional-login-form-fields" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">additional-login-form-fields</item> <item name="children" xsi:type="array"> <!-- additional login form fields --> </item> </item> </item> </item> <item name="before-form" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-form</item> <item name="children" xsi:type="array"> <!-- before form fields --> </item> </item> <item name="before-fields" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-fields</item> <item name="children" xsi:type="array"> <!-- before fields --> </item> </item> <item name="address-list" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/shipping-address/list</item> <item name="displayArea" xsi:type="string">address-list</item> </item> <item name="address-list-additional-addresses" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">address-list-additional-addresses</item> <item name="children" xsi:type="array"> <!-- address-list-additional-addresses --> </item> </item> <item name="before-shipping-method-form" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-shipping-method-form</item> <item name="children" xsi:type="array"> <!-- address-list-additional-addresses --> </item> </item> <item name="shipping-address-fieldset" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="config" xsi:type="array"> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkoutProvider</item> </item> </item> <item name="displayArea" xsi:type="string">additional-fieldsets</item> <item name="children" xsi:type="array"> <!-- The following items override configuration of corresponding address attributes --> <item name="region" xsi:type="array"> <!-- Make region attribute invisible on frontend. Corresponding input element is created by region_id field --> <item name="visible" xsi:type="boolean">false</item> </item> <item name="region_id" xsi:type="array"> <item name="component" xsi:type="string">Magento_Ui/js/form/element/region</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">ui/form/field</item> <item name="elementTmpl" xsi:type="string">ui/form/element/select</item> <item name="customEntry" xsi:type="string">shippingAddress.region</item> </item> <item name="validation" xsi:type="array"> <item name="required-entry" xsi:type="boolean">true</item> </item> <!-- Value of region_id field is filtered by the value of county_id attribute --> <item name="filterBy" xsi:type="array"> <item name="target" xsi:type="string"><![CDATA[${ $.provider }:${ $.parentScope }.country_id]]></item> <item name="field" xsi:type="string">country_id</item> </item> </item> <item name="postcode" xsi:type="array"> <!-- post-code field has custom UI component --> <item name="component" xsi:type="string">Magento_Ui/js/form/element/post-code</item> <item name="validation" xsi:type="array"> <item name="required-entry" xsi:type="boolean">true</item> </item> </item> <item name="company" xsi:type="array"> <item name="validation" xsi:type="array"> <item name="min_text_length" xsi:type="number">0</item> </item> </item> <item name="fax" xsi:type="array"> <item name="validation" xsi:type="array"> <item name="min_text_length" xsi:type="number">0</item> </item> </item> <item name="telephone" xsi:type="array"> <item name="config" xsi:type="array"> <item name="tooltip" xsi:type="array"> <item name="description" xsi:type="string" translate="true">For delivery questions.</item> </item> </item> </item> </item> </item> </item> </item> </item> </item> <item name="billing-step" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="sortOrder" xsi:type="string">2</item> <item name="children" xsi:type="array"> <item name="payment" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/payment</item> <item name="config" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Payment</item> <item name="sortOrder" xsi:type="string">20</item> </item> <item name="children" xsi:type="array"> <item name="renders" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="children" xsi:type="array"> <!-- merge payment method renders here --> </item> </item> <item name="additional-payment-validators" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="children" xsi:type="array"> <!-- merge payment validators here --> <item name="email-validator" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/payment/email-validator</item> </item> </item> </item> <item name="customer-email" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/form/element/email</item> <item name="displayArea" xsi:type="string">customer-email</item> <item name="tooltip" xsi:type="array"> <item name="description" xsi:type="string" translate="true">We'll send your order confirmation here.</item> </item> <item name="children" xsi:type="array"> <item name="before-login-form" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-login-form</item> <item name="children" xsi:type="array"> <!-- before login form fields --> </item> </item> <item name="additional-login-form-fields" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">additional-login-form-fields</item> <item name="children" xsi:type="array"> <!-- additional login form fields --> </item> </item> </item> </item> <item name="place-order-captcha" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/checkout/placeOrderCaptcha</item> <item name="displayArea" xsi:type="string">place-order-captcha</item> <item name="formId" xsi:type="string">payment_processing_request</item> <item name="configSource" xsi:type="string">checkoutConfig</item> </item> <item name="beforeMethods" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">beforeMethods</item> <item name="children" xsi:type="array"> <!-- merge additional data before payment methods here --> </item> <item name="validation" xsi:type="array"> <item name="validate-select" xsi:type="string">true</item> </item> <!-- Value of region_id field is filtered by the value of county_id attribute --> <item name="filterBy" xsi:type="array"> <item name="target" xsi:type="string">${ $.provider }:${ $.parentScope }.country_id</item> <item name="field" xsi:type="string">country_id</item> </item> </item> <item name="payments-list" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/payment/list</item> <item name="displayArea" xsi:type="string">payment-methods-list</item> <item name="config" xsi:type="array"> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkout.steps.billing-step.payment.renders</item> <item name="1" xsi:type="string">checkout.steps.billing-step.payment.additional-payment-validators</item> </item> </item> <item name="children" xsi:type="array"> <item name="before-place-order" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">before-place-order</item> <item name="dataScope" xsi:type="string">before-place-order</item> <item name="provider" xsi:type="string">checkoutProvider</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Checkout/payment/before-place-order</item> </item> </item> </item> </item> <!-- merge your payment methods here --> <item name="afterMethods" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="displayArea" xsi:type="string">afterMethods</item> <item name="children" xsi:type="array"> <!-- merge additional data after payment methods here --> </item> </item> </item> </item> </item> </item> </item> </item> <item name="sidebar" xsi:type="array"> <item name="sortOrder" xsi:type="string">50</item> <item name="component" xsi:type="string">Magento_Checkout/js/view/sidebar</item> <item name="displayArea" xsi:type="string">sidebar</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Checkout/sidebar</item> <item name="deps" xsi:type="array"> <item name="0" xsi:type="string">checkout.steps</item> </item> </item> <item name="children" xsi:type="array"> <item name="summary" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary</item> <item name="displayArea" xsi:type="string">summary</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Checkout/summary</item> </item> <item name="children" xsi:type="array"> <item name="totals" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/totals</item> <item name="displayArea" xsi:type="string">totals</item> <item name="config" xsi:type="array"> <item name="template" xsi:type="string">Magento_Checkout/summary/totals</item> </item> <item name="children" xsi:type="array"> <!-- sort order for this totals is configured on admin panel--> <!-- Stores->Configuration->SALES->Sales->General->Checkout Totals Sort Order --> <item name="subtotal" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/subtotal</item> <item name="config" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Cart Subtotal</item> </item> </item> <item name="shipping" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/shipping</item> <item name="config" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Shipping</item> <item name="notCalculatedMessage" xsi:type="string" translate="true">Not yet calculated</item> </item> </item> <item name="grand-total" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/grand-total</item> <item name="config" xsi:type="array"> <item name="title" xsi:type="string" translate="true">Order Total</item> </item> </item> </item> </item> <item name="itemsBefore" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="children" xsi:type="array"> <!-- merge your components here --> </item> </item> <item name="cart_items" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/cart-items</item> <item name="children" xsi:type="array"> <item name="details" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/item/details</item> <item name="children" xsi:type="array"> <item name="thumbnail" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/item/details/thumbnail</item> <item name="displayArea" xsi:type="string">before_details</item> </item> <item name="subtotal" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/item/details/subtotal</item> <item name="displayArea" xsi:type="string">after_details</item> </item> <item name="message" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/summary/item/details/message</item> <item name="displayArea" xsi:type="string">item_message</item> </item> </item> </item> </item> </item> <item name="itemsAfter" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> <item name="children" xsi:type="array"> <!-- merge your components here --> </item> </item> </item> </item> <item name="shipping-information" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/shipping-information</item> <item name="config" xsi:type="array"> <item name="deps" xsi:type="string">checkout.steps.shipping-step.shippingAddress</item> </item> <item name="displayArea" xsi:type="string">shipping-information</item> <item name="children" xsi:type="array"> <item name="ship-to" xsi:type="array"> <item name="component" xsi:type="string">Magento_Checkout/js/view/shipping-information/list</item> <item name="displayArea" xsi:type="string">ship-to</item> </item> </item> </item> </item> </item> </item> </item> <item name="checkoutProvider" xsi:type="array"> <item name="component" xsi:type="string">uiComponent</item> </item> </item> </argument> </arguments> </block> </referenceContainer> <referenceContainer name="page.messages" remove="true"/> </body></page>
Мы задаем инструкции для рендеринга страницы так, как будто мы
делаем это в PHP Array, но с более наглядным отображением, и
возможностью расширения. Благодаря xml и Magento 2 мы можем
подменить любой кусочек или добавить что-то свое.
В админке таким же образом можно расширить любую UI Component
форму, грид или что-то еще (да, еще остались формы и гриды,
написанные "по-старому" в PHP классах), например вот таким образом
можно добавить колонку на UI Grid:
<?xml version="1.0" encoding="UTF-8"?><!--/** * Copyright Magento, Inc. All rights reserved. * See COPYING.txt for license details. */--><listing xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> <columns name="sales_order_columns"> <column name="refunded_to_store_credit" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <label translate="true">Refunded to Store Credit</label> <visible>false</visible> </settings> </column> </columns></listing>
Напоследок хочу отметить то, что коммьюнити не стоит на месте в
плане новинок, например последние на данный момент версии Magento
поддерживают только php 7.3 и 7.4, elasticsearch 7.4/7.6
P.S. Я хочу еще раз подчеркнуть, что в статье приводятся
достоинства, которые, в свою очередь, не лишены недостатков :). Я
не преследовал цель заставить всех поверить, что Magento -
квинтэссенция всего, и нужно срочно бросать свое
{cms/frameworkName} и переходить на Magento. Я лишь хочу показать,
что Magento 2 не для "кодинга", а очень даже для
"программирования".