Учебное пособие по Magento 2: как создать полный модуль

Опубликовано: 2022-03-11

Magento в настоящее время является крупнейшей в мире платформой электронной коммерции с открытым исходным кодом. Благодаря многофункциональной и расширяемой кодовой базе продавцы с большими и малыми операциями по всему миру используют его для самых разных проектов.

Magento 1 существует уже восемь лет, а его преемник, Magento 2, был выпущен в конце 2015 года, исправив слабые места более ранней версии, такие как:

  • Улучшенная производительность
  • Официальный набор автоматизированных тестов
  • Улучшенный внутренний интерфейс
  • Новая, более современная клиентская кодовая база
  • Более модульный способ разработки модулей с файлами, содержащимися внутри кода Magento, а не разбросанными повсюду.
  • Уменьшено количество конфликтов между модулями, пытающимися настроить одну и ту же функциональность.

Стилизованный логотип Magento 2

Прошло чуть больше года, и улучшения видны, даже несмотря на то, что не все упомянутые проблемы были полностью решены. Теперь можно с полной уверенностью сказать, что Magento 2 является гораздо более надежным программным обеспечением, чем его предшественник. Вот некоторые из улучшений, присутствующих в Magento 2:

  • Модульные и интеграционные тесты, включая официальный и задокументированный способ их создания для пользовательских модулей.
  • Модули, которые действительно имеют модульную структуру, все файлы которых размещены в одном каталоге.
  • Более богатая система шаблонов, позволяющая разработчику темы создавать n-уровневую иерархию шаблонов.
  • Ряд полезных шаблонов проектирования, принятых во всем коде, улучшающих качество кода и снижающих вероятность ошибок, создаваемых модулями. К ним относятся автоматическое внедрение зависимостей, сервисные контракты, репозитории и фабрики, и это лишь некоторые из них.
  • Встроенная интеграция с Varnish в качестве системы полного кэширования страниц, а также с Redis для обработки сеансов и кэшей.
  • поддержка PHP 7

Кривая обучения Magento 2 со всеми этими изменениями стала еще круче. В этом руководстве я намерен показать вам, как разработать свой первый модуль Magento 2, и указать правильное направление для продолжения учебы. Давайте приступим!

Требования к учебнику по Magento 2

Важно, чтобы вы хорошо разбирались в следующих технологиях/концепциях, чтобы следовать остальной части этой статьи:

  • Объектно-ориентированное программирование (ООП)
  • PHP
  • Пространства имен
  • MySQL
  • Базовое использование bash

Из всего вышеперечисленного ООП, пожалуй, самый важный. Первоначально Magento был создан командой опытных разработчиков Java, и их наследие, безусловно, можно увидеть во всей кодовой базе. Если вы не очень уверены в своих навыках ООП, было бы неплохо просмотреть его перед началом работы с платформой.

Обзор архитектуры Magento 2

Архитектура Magento была разработана с целью сделать исходный код как можно более модульным и расширяемым. Конечная цель этого подхода состоит в том, чтобы его можно было легко адаптировать и настроить в соответствии с потребностями каждого проекта.

Настройка обычно означает изменение поведения кода платформы. В большинстве систем это означает изменение «основного» кода. В Magento, если вы следуете передовым методам, этого можно избежать в большинстве случаев, что позволяет магазину надежно обновлять последние исправления безопасности и выпуски функций.

Magento 2 — это система Model View ViewModel (MVVM). Несмотря на то, что архитектура MVVM тесно связана с родственным контроллером представления модели (MVC), она обеспечивает более надежное разделение между уровнями модели и представления. Ниже приведено объяснение каждого из уровней системы MVVM:

  • Модель содержит бизнес-логику приложения и зависит от связанного класса — ResourceModel — для доступа к базе данных. Модели полагаются на сервисные контракты , чтобы предоставить свои функциональные возможности другим уровням приложения.
  • Представление — это структура и макет того, что пользователь видит на экране — фактический HTML. Это достигается в файлах PHTML, распространяемых вместе с модулями. Файлы PHTML связаны с каждой ViewModel в XML-файлах макета , которые на диалекте MVVM называются связывателями . Файлы макета также могут назначать файлы JavaScript для использования на конечной странице.
  • ViewModel взаимодействует со слоем модели, предоставляя слою представления только необходимую информацию. В Magento 2 это обрабатывается классами блоков модуля. Обратите внимание, что это обычно было частью роли контроллера системы MVC. В MVVM контроллер отвечает только за обработку пользовательского потока, что означает, что он получает запросы и либо сообщает системе об отображении представления, либо перенаправляет пользователя на другой маршрут.

Модуль Magento 2 состоит из некоторых, если не из всех, элементов архитектуры, описанной выше. Общая архитектура описана ниже (источник):

Схема полной архитектуры Magento 2

Модуль Magento 2, в свою очередь, может определять внешние зависимости с помощью Composer, менеджера зависимостей PHP. На диаграмме выше вы видите, что основные модули Magento 2 зависят от Zend Framework, Symfony, а также других сторонних библиотек.

Ниже представлена ​​структура Magento/Cms, базового модуля Magento 2, отвечающего за обработку создания страниц и статических блоков.

Структура каталога модуля Magento/Cms

Каждая папка содержит одну часть архитектуры следующим образом:

  • API: сервисные контракты, определяющие сервисные интерфейсы и интерфейсы данных.
  • Блок: ViewModels нашей архитектуры MVVM
  • Контроллер: Контроллеры, отвечающие за обработку потока пользователя при взаимодействии с системой.
  • etc: Конфигурационные файлы XML — модуль определяет себя и свои части (маршруты, модели, блоки, наблюдатели и задания cron) в этой папке. Файлы etc также могут использоваться неосновными модулями для переопределения функциональности основных модулей.
  • Helper: вспомогательные классы, которые содержат код, используемый более чем в одном уровне приложения. Например, в модуле Cms вспомогательные классы отвечают за подготовку HTML для представления в браузере.
  • i18n: Содержит CSV-файлы интернационализации, используемые для перевода.
  • Модель: для моделей и моделей ресурсов
  • Наблюдатель: содержит наблюдателей или модели, которые «наблюдают» за системными событиями. Обычно, когда такое событие запускается, наблюдатель создает экземпляр модели для обработки необходимой бизнес-логики для такого события.
  • Настройка: классы миграции, отвечающие за создание схемы и данных.
  • Тест: Модульные тесты
  • Пользовательский интерфейс : элементы пользовательского интерфейса, такие как сетки и формы, используемые в приложении администратора.
  • представление: файлы макета (XML) и файлы шаблонов (PHTML) для внешнего интерфейса и приложения администратора.

Также интересно отметить, что на практике вся внутренняя работа Magento 2 находится внутри модуля. На изображении выше вы можете видеть, например, Magento_Checkout , отвечающий за процесс оформления заказа, и Magento_Catalog , отвечающий за обработку продуктов и категорий. По сути, это говорит нам о том, что обучение работе с модулями является наиболее важной частью становления разработчиком Magento 2.

Хорошо, после этого относительно краткого введения в архитектуру системы и структуру модулей, давайте сделаем что-нибудь более конкретное, не так ли? Далее мы рассмотрим традиционный учебник по блогу, чтобы вы освоились с Magento 2 и на пути к тому, чтобы стать разработчиком Magento 2. Перед этим нам нужно настроить среду разработки. Давайте приступим!

Настройка среды разработки модулей Magento 2

На момент написания этой статьи мы могли использовать официальный Magento 2 DevBox, который представляет собой контейнер Magento 2 Docker. Я до сих пор считаю Docker в macOS непригодным для использования, по крайней мере, с системой, которая сильно зависит от быстрого дискового ввода-вывода, такого как Magento 2. Итак, мы сделаем это традиционным способом: установим все пакеты изначально на нашей собственной машине.

Настройка сервера

Установка всего, конечно, немного более утомительна, но конечным результатом будет молниеносная среда разработки Magento. Поверьте мне, вы сэкономите часы работы, не полагаясь на Docker при разработке Magento 2.

В этом руководстве предполагается среда на macOS с установленным на ней Brew. Если это не так для вас, основы останутся прежними, изменится только способ установки пакетов. Начнем с установки всех пакетов:

 brew install mysql nginxb php70 php70-imagick php70-intl php70-mcrypt

Затем запустите службы:

 brew services start mysql brew services start php70 sudo brew services start nginx

Хорошо, теперь мы укажем домен на наш петлевой адрес. Откройте файл hosts в любом редакторе, но убедитесь, что у вас есть права суперпользователя. Выполнение этого с Vim будет:

 sudo vim /etc/hosts

Затем добавьте следующую строку:

 127.0.0.1 magento2.dev

Теперь создадим vhost в Nginx:

 vim /usr/local/etc/nginx/sites-available/magento2dev.conf

Добавьте следующее содержимое:

 server { listen 80; server_name magento2.dev; set $MAGE_ROOT /Users/yourusername/www/magento2dev; set $MAGE_MODE developer; # Default magento Nginx config starts below root $MAGE_ROOT/pub; index index.php; autoindex off; charset off; add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block'; location / { try_files $uri $uri/ /index.php?$args; } location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; } location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/ { try_files $uri $uri/ /get.php?$args; location ~ ^/media/theme_customization/.*\.xml { deny all; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; try_files $uri $uri/ /get.php?$args; } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; try_files $uri $uri/ /get.php?$args; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; } location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # Default magento Nginx config finishes below client_max_body_size 20M; }

Если вы раньше не имели дело с Nginx, этот файл может вас напугать, поэтому давайте объясним здесь некоторые моменты, так как он также прольет свет на некоторые внутренние механизмы Magento. Первые строки просто сообщают Nginx, что мы используем HTTP-порт по умолчанию, а наш домен — magento2.dev :

 listen 80; server_name magento2.dev;

Затем мы устанавливаем некоторые переменные среды. Первый $MAGE_ROOT содержит путь к нашей кодовой базе. Обратите внимание, что вам нужно будет изменить корневой путь, чтобы он соответствовал вашему имени пользователя / пути к папке, где бы вы ни планировали разместить источник:

 set $MAGE_ROOT /Users/yourusername/www/magento2dev;

Вторая переменная $MAGE_MODE устанавливает режим работы нашего магазина. Поскольку мы разрабатываем модуль, мы будем использовать режим разработчика. Это позволяет нам писать код быстрее, так как нам не придется компилировать или развертывать статические файлы во время разработки. Остальные режимы — производственный и по умолчанию. Реальное использование последнего пока не ясно.

 set $MAGE_MODE developer;

После установки этих переменных мы определяем корневой путь виртуального хоста. Обратите внимание, что мы добавляем к переменной $MAGE_ROOT папку /pub , делая доступной для Интернета только часть нашего магазина.

 root $MAGE_ROOT/pub;

Затем мы определяем наш индексный файл — файл, который nginx загрузит, когда запрошенный файл не существует — как index.php. Этот скрипт, $MAGE_ROOT/pub/index.php , является основной точкой входа для клиентов, посещающих как корзину покупок, так и приложения администратора. Независимо от запрошенного URL-адреса, index.php будет загружен, и начнется процесс диспетчеризации маршрутизатора.

 index index.php;

Далее мы отключаем некоторые функции Nginx. Во-первых, мы отключаем autoindex , который будет отображать список файлов, когда вы запрашиваете папку, но не указываете файл, а индекс отсутствует. Во-вторых, мы отключаем charset , что позволит Nginx автоматически добавлять заголовки Charset в ответ.

 autoindex off; charset off;

Далее мы определяем несколько заголовков безопасности:

 add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block';

Это расположение, / , указывает на нашу корневую папку $MAGE_ROOT/pub и в основном перенаправляет любой полученный запрос на наш фронт-контроллер index.php вместе с аргументами запроса:

 location / { try_files $uri $uri/ /index.php?$args; }

Следующая часть может быть немного запутанной, но она довольно проста. Несколько строк назад мы определили наш корень как $MAGE_ROOT/pub . Это рекомендуемая и более безопасная установка, так как большая часть кода не видна из Интернета. Но это не единственный способ настроить веб-сервер. На самом деле, большинство общих веб-серверов имеют одну настройку по умолчанию, которая заключается в том, чтобы ваш веб-сервер указывал на вашу веб-папку. Для таких пользователей команда Magento подготовила этот файл для тех случаев, когда корень определяется как $MAGE_ROOT со следующим фрагментом:

 location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; }

Имейте в виду, что, когда это возможно, лучше всего, если ваш веб-сервер указывает на папку $MAGE_ROOT/pub . Так ваш магазин будет более безопасным.

Далее у нас есть статическое местоположение $MAGE_ROOT/pub/static . Эта папка изначально пуста и автоматически заполняется статическими файлами модулей и тем, такими как файлы изображений, CSS, JS и т. д. Здесь мы в основном определяем некоторые значения кеша для статических файлов и, когда запрошенный файл не существует, перенаправьте его на $MAGE_ROOT/pub/static.php . Этот сценарий, среди прочего, проанализирует запрос и скопирует или создаст символическую ссылку на указанный файл из соответствующего модуля или темы, в зависимости от определенного режима выполнения. Таким образом, статические файлы вашего модуля будут находиться в папке наших модулей, но будут обслуживаться непосредственно из общедоступной папки vhost:

 location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; }

Затем мы запрещаем веб-доступ к некоторым папкам и файлам с ограниченным доступом:

 location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; }

И последний бит — это то, где мы загружаем php-fpm и говорим ему выполнять index.php всякий раз, когда пользователь нажимает на него:

 location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }

После этого сохраните файл, а затем включите его, введя следующие команды:

 ln -s /usr/local/etc/nginx/sites-available/magento2dev.conf \ /usr/local/etc/nginx/sites-enabled/magento2dev.conf sudo brew services restart nginx

Как установить Magento 2

Хорошо, на данный момент ваша машина соответствует требованиям Magento 2, за исключением самого зверя. Перейдите на веб-сайт Magento и создайте учетную запись, если у вас ее еще нет. После этого переходим на страницу загрузки и скачиваем последнюю версию (2.1.5, на момент написания):

Страница загрузки Magento 2

Выберите формат .tar.bz2 и загрузите его. Затем извлеките его и установите правильные права доступа к папке и файлу, чтобы Magento 2 мог работать:

 mkdir ~/www/magento2dev cd ~/www/magento2dev tar -xjf ~/Downloads/Magento-CE-2.1.5-2017-02-20-05-39-14.tar.bz2 find var vendor pub/static pub/media app/etc -type f -exec chmod u+w {} \; find var vendor pub/static pub/media app/etc -type d -exec chmod u+w {} \; chmod u+x bin/magento

Теперь, чтобы установить таблицы базы данных и создать необходимые файлы конфигурации, мы выполним эту команду из терминала:

 ./bin/magento setup:install --base-url=http://magento2.dev/ \ --db-host=127.0.0.1 --db-name=magento2 --db-user=root \ --db-password=123 --admin-firstname=Magento --admin-lastname=User \ [email protected] --admin-user=admin \ --admin-password=admin123 --language=en_US --currency=USD \ --timezone=America/Chicago --use-rewrites=1 --backend-frontname=admin

Не забудьте изменить имя базы данных ( db-name ), пользователя ( db-user ) и пароль ( db-password ), чтобы они соответствовали тем, которые вы использовали во время установки MySQL, и все! Эта команда установит все модули Magento 2, создав необходимые таблицы и файлы конфигурации. После завершения откройте браузер и перейдите на http://magento2.dev/. Вы должны увидеть чистую установку Magento 2 с темой Luma по умолчанию:

Домашняя страница в теме Luma по умолчанию

Если вы перейдете на http://magento2.dev/admin, вы должны увидеть страницу входа в приложение администратора:

Страница входа в приложение администратора

Затем используйте указанные ниже учетные данные для входа:

Пользователь: admin Пароль: admin123

Наконец-то мы готовы начать писать наш код!

Создание нашего первого модуля Magento 2

Чтобы завершить наш модуль, нам нужно будет создать следующие файлы, и я проведу вас через весь процесс. Нам понадобится:

  • Несколько шаблонных регистрационных файлов, чтобы Magento знал о нашем модуле блога.
  • Один файл интерфейса, чтобы определить наш контракт данных для почты.
  • Модель поста для представления поста в нашем коде, реализующая интерфейс данных поста.
  • Модель пост-ресурса, чтобы связать пост-модель с базой данных.
  • Коллекция сообщений для одновременного извлечения нескольких сообщений из базы данных с помощью модели ресурсов.
  • Два класса миграции для настройки схемы и содержимого нашей таблицы.
  • Два действия: одно для отображения всех сообщений, а другое для отображения каждого сообщения по отдельности.
  • По два файла блоков, представлений и макетов: по одному для действия со списком и по одному для представления.

Во-первых, давайте быстро взглянем на структуру папок основного исходного кода, чтобы мы могли определить, где разместить наш код. В способе, который мы установили, весь код ядра Magento 2 вместе со всеми его зависимостями находится в папке vendor композитора.

Макет каталога основного кода Magento 2

Регистрация нашего модуля

Мы будем хранить наш код в отдельной папке app/code . Имя каждого модуля имеет форму Namespace_ModuleName , и его расположение в файловой системе должно отражать это имя, app/code/Namespace/ModuleName для этого примера. Следуя этому шаблону, мы назовем наш модуль Toptal_Blog и поместим наши файлы в app/code/Toptal/Blog . Идите вперед и создайте эту структуру папок.

Макет каталога нашего модуля Toptal_Blog

Теперь нам нужно создать несколько шаблонных файлов, чтобы наш модуль был зарегистрирован в Magento. Сначала создайте app/code/Toptal/Blog/composer.json :

 {}

Этот файл будет загружаться Composer каждый раз, когда вы его запускаете. Несмотря на то, что на самом деле мы не используем Composer с нашим модулем, мы должны создать его, чтобы Composer был доволен.

Теперь мы зарегистрируем наш модуль в Magento. Идем дальше и создаем app/code/Toptal/Blog/registration.php :

 <?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Toptal_Blog', __DIR__ );

Здесь мы вызываем метод register класса ComponentRegistrar , отправляя два параметра: строку 'module' , которая является типом регистрируемого компонента, и имя нашего модуля « 'Toptal_Blog' . С этой информацией автозагрузчик Magento будет знать о нашем пространстве имен и будет знать, где искать наши классы и XML-файлы.

Здесь следует отметить одну интересную вещь: у нас есть тип компонента ( MODULE ), отправляемый в качестве параметра функции \Magento\Framework\Component\ComponentRegistrar::register . Мы можем регистрировать не только модули, но и другие типы компонентов. Например, темы, внешние библиотеки и языковые пакеты также регистрируются с использованием того же метода.

Продолжая, давайте создадим наш последний файл регистрации, app/code/Toptal/Blog/etc/module.xml :

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> <module name="Toptal_Blog" setup_version="0.1.0"> <sequence> <module name="Magento_Directory" /> <module name="Magento_Config" /> </sequence> </module> </config>

Этот файл содержит очень важную информацию о нашем модуле. Они есть:

  • Имя модуля снова присутствует, раскрывая имя нашего модуля для конфигурации Magento.
  • Версия установки Magento, которая будет использоваться Magento для принятия решения о запуске сценариев миграции базы данных.
  • Зависимости нашего модуля. Поскольку мы пишем простой модуль, мы зависим только от двух основных модулей Magento: Magento_Directory и Magento_Config .

Теперь у нас есть модуль, который должен быть распознан Magento 2. Давайте проверим его с помощью интерфейса командной строки Magento 2.

Во-первых, нам нужно отключить кеш Magento. Механизмы кэширования Magento заслуживают отдельной статьи, посвященной им самим. На данный момент, поскольку мы разрабатываем модуль и хотим, чтобы наши изменения мгновенно распознавались Magento без необходимости постоянно очищать кеш, мы просто отключим его. Из командной строки запустите:

 ./bin/magento cache:disable

Затем давайте посмотрим, знает ли Magento о наших изменениях, посмотрев статус модулей. Просто выполните следующую команду:

 ./bin/magento module:status

Результат последнего должен быть похож на:

Вывод команды состояния, показывающий, что модуль Toptal_Blog отключен

Наш модуль есть, но, как видно из вывода, он по-прежнему отключен. Чтобы включить его, запустите:

 ./bin/magento module:enable Toptal_Blog

Это должно было сделать это. Чтобы быть уверенным, вы можете снова вызвать module:status и найти имя нашего модуля в списке включенных:

Вывод команды состояния, показывающий, что модуль Toptal_Blog включен

Работа с хранилищем данных

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

Поле Тип Нулевой Ключ По умолчанию
post_id целое (10) без знака НЕТ ПРИ НУЛЕВОЙ
заглавие текст НЕТ НУЛЕВОЙ
содержание текст НЕТ НУЛЕВОЙ
создано в отметка времени НЕТ CURRENT_TIMESTAMP

Мы достигаем этого, создавая класс InstallSchema , который отвечает за управление установкой миграции нашей схемы. Файл находится по адресу app/code/Toptal/Blog/Setup/InstallSchema.php и имеет следующее содержимое:

 <?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\InstallSchemaInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\SchemaSetupInterface; use \Magento\Framework\DB\Ddl\Table; /** * Class InstallSchema * * @package Toptal\Blog\Setup */ class InstallSchema implements InstallSchemaInterface { /** * Install Blog Posts table * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $tableName = $setup->getTable('toptal_blog_post'); if ($setup->getConnection()->isTableExists($tableName) != true) { $table = $setup->getConnection() ->newTable($tableName) ->addColumn( 'post_id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true ], 'ID' ) ->addColumn( 'title', Table::TYPE_TEXT, null, ['nullable' => false], 'Title' ) ->addColumn( 'content', Table::TYPE_TEXT, null, ['nullable' => false], 'Content' ) ->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 'Created At' ) ->setComment('Toptal Blog - Posts'); $setup->getConnection()->createTable($table); } $setup->endSetup(); } }

Если вы проанализируете метод install , вы заметите, что он просто создает нашу таблицу и добавляет ее столбцы один за другим.

Чтобы определить, когда запускать миграцию схемы, Magento хранит таблицу со всеми текущими версиями установки для каждого модуля, и всякий раз, когда версия модуля изменяется, его классы миграции инициализируются. Это таблица setup_module , и если вы посмотрите на содержимое этой таблицы, вы увидите, что пока нет ссылки на наш модуль. Итак, давайте изменим это. В терминале выполните следующую команду:

 ./bin/magento setup:upgrade

Это покажет вам список всех модулей и их сценариев миграции, которые были выполнены, включая наш:

Вывод команды обновления, показывающий выполнение нашей миграции

Теперь из вашего предпочтительного клиента MySQL вы можете проверить, действительно ли была создана таблица:

Демонстрация нашей таблицы в клиенте MySQL

А в таблице setup_module теперь ссылка на наш модуль, его схему и версию данных:

Содержимое таблицы setup_module

Хорошо, а как насчет обновлений схемы? Давайте добавим несколько сообщений в эту таблицу через обновление, чтобы показать вам, как это сделать. Во-первых, setup_version в нашем etc/module.xml :

Подсветка измененного значения в нашем файле module.xml

Теперь мы создаем наш файл app/code/Toptal/Blog/Setup/UpgradeData.php , который отвечает за миграцию данных (не схемы):

 <?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\UpgradeDataInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\ModuleDataSetupInterface; /** * Class UpgradeData * * @package Toptal\Blog\Setup */ class UpgradeData implements UpgradeDataInterface { /** * Creates sample blog posts * * @param ModuleDataSetupInterface $setup * @param ModuleContextInterface $context * @return void */ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) { $tableName = $setup->getTable('toptal_blog_post'); $data = [ [ 'title' => 'Post 1 Title', 'content' => 'Content of the first post.', ], [ 'title' => 'Post 2 Title', 'content' => 'Content of the second post.', ], ]; $setup ->getConnection() ->insertMultiple($tableName, $data); } $setup->endSetup(); } }

Вы можете видеть, что он очень похож на наш класс Install. Единственное отличие состоит в том, что он реализует UpgradeDataInterface вместо InstallSchemaInterface , а основной метод называется upgrade . С помощью этого метода вы проверяете установленную версию текущего модуля и, если она меньше вашей, запускаете необходимые изменения. В нашем примере мы проверяем, меньше ли текущая версия, чем 0.1.1, в следующей строке, используя функцию version_compare :

 if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) {

$context->getVersion() вернет 0.1.0 при первом вызове команды CLI setup:upgrade . Затем образцы данных загружаются в базу данных, и наша версия повышается до 0.1.1. Чтобы запустить это, запустите setup:upgrade :

 ./bin/magento setup:upgrade

А затем проверьте результаты в таблице сообщений:

Содержимое нашей таблицы

И в таблице setup_module :

Обновлено содержимое таблицы setup_module

Обратите внимание, что, несмотря на то, что мы добавили данные в нашу таблицу с помощью процесса миграции, можно было бы также изменить схему. Процесс тот же; вы бы использовали только UpgradeSchemaInterface вместо UpgradeDataInterface .

Определение модели для сообщений

Двигаясь дальше, если вы помните наш обзор архитектуры, нашим следующим строительным блоком будет запись в блоге ResourceModel. Модель ресурсов очень проста и просто указывает таблицу, к которой модель будет «подключаться», а также ее первичный ключ. Мы создадим нашу ResourceModel в app/code/Toptal/Blog/Model/ResourceModel/Post.php со следующим содержимым:

 <?php namespace Toptal\Blog\Model\ResourceModel; use \Magento\Framework\Model\ResourceModel\Db\AbstractDb; class Post extends AbstractDb { /** * Post Abstract Resource Constructor * @return void */ protected function _construct() { $this->_init('toptal_blog_post', 'post_id'); } }

Все операции ResourceModel, если вам не нужно что-то отличное от обычных операций CRUD, обрабатываются родительским классом AbstractDb .

Нам также понадобится еще одна ResourceModel, коллекция. Коллекция будет отвечать за запросы к базе данных для нескольких сообщений с использованием нашей ResourceModel и за возврат серии моделей, созданных и заполненных информацией. Создаем файл app/code/Toptal/Blog/Model/ResourceModel/Post/Collection.php следующего содержания:

 <?php namespace Toptal\Blog\Model\ResourceModel\Post; use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; class Collection extends AbstractCollection { /** * Remittance File Collection Constructor * @return void */ protected function _construct() { $this->_init('Toptal\Blog\Model\Post', 'Toptal\Blog\Model\ResourceModel\Post'); } }

Обратите внимание, что в конструкторе мы просто упоминаем модель, которая будет представлять сущность записи в нашем коде, и модель ресурсов, которая будет получать информацию из базы данных.

Недостающая часть этого слоя — это сама Post Model. Модель должна содержать все атрибуты, которые мы определили в нашей схеме, а также любую бизнес-логику, которая может вам понадобиться. Следуя шаблону Magento 2, нам нужно создать интерфейс данных, из которого будет расширяться наша модель. Мы размещаем интерфейс в app/code/Toptal/Blog/Api/Data/PostInterface.php , и он должен содержать имена полей таблицы вместе с методами доступа к ним:

 <?php namespace Toptal\Blog\Api\Data; interface PostInterface { /**#@+ * Constants for keys of data array. Identical to the name of the getter in snake case */ const POST_; const TITLE = 'title'; const CONTENT = 'content'; const CREATED_AT = 'created_at'; /**#@-*/ /** * Get Title * * @return string|null */ public function getTitle(); /** * Get Content * * @return string|null */ public function getContent(); /** * Get Created At * * @return string|null */ public function getCreatedAt(); /** * Get ID * * @return int|null */ public function getId(); /** * Set Title * * @param string $title * @return $this */ public function setTitle($title); /** * Set Content * * @param string $content * @return $this */ public function setContent($content); /** * Set Crated At * * @param int $createdAt * @return $this */ public function setCreatedAt($createdAt); /** * Set ID * * @param int $id * @return $this */ public function setId($id); }

Теперь о реализации модели в app/code/Toptal/Blog/Model/Post.php . Мы создадим методы, определенные в интерфейсе. Также мы укажем тег кеша через константу CACHE_TAG и в конструкторе укажем ResourceModel, который будет отвечать за доступ к базе данных для нашей модели.

 <?php namespace Toptal\Blog\Model; use \Magento\Framework\Model\AbstractModel; use \Magento\Framework\DataObject\IdentityInterface; use \Toptal\Blog\Api\Data\PostInterface; /** * Class File * @package Toptal\Blog\Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Post extends AbstractModel implements PostInterface, IdentityInterface { /** * Cache tag */ const CACHE_TAG = 'toptal_blog_post'; /** * Post Initialization * @return void */ protected function _construct() { $this->_init('Toptal\Blog\Model\ResourceModel\Post'); } /** * Get Title * * @return string|null */ public function getTitle() { return $this->getData(self::TITLE); } /** * Get Content * * @return string|null */ public function getContent() { return $this->getData(self::CONTENT); } /** * Get Created At * * @return string|null */ public function getCreatedAt() { return $this->getData(self::CREATED_AT); } /** * Get ID * * @return int|null */ public function getId() { return $this->getData(self::POST_ID); } /** * Return identities * @return string[] */ public function getIdentities() { return [self::CACHE_TAG . '_' . $this->getId()]; } /** * Set Title * * @param string $title * @return $this */ public function setTitle($title) { return $this->setData(self::TITLE, $title); } /** * Set Content * * @param string $content * @return $this */ public function setContent($content) { return $this->setData(self::CONTENT, $content); } /** * Set Created At * * @param string $createdAt * @return $this */ public function setCreatedAt($createdAt) { return $this->setData(self::CREATED_AT, $createdAt); } /** * Set ID * * @param int $id * @return $this */ public function setId($id) { return $this->setData(self::POST_ID, $id); } }

Creating Views

Now we are moving one layer up, and will start the implementation of our ViewModel and Controller. To define a route in the front-end (shopping cart) application, we need to create the file app/code/Toptal/Blog/etc/frontend/routes.xml with the following contents:

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router> <route frontName="blog"> <module name="Toptal_Blog"/> </route> </router> </config>

List of Posts at the Index Page

Here, we are basically telling Magento that our module, Toptal_Blog , will be responsible for responding to routes under http://magento2.dev/blog (notice the frontName attribute of the route). Next up is the action, at app/code/Toptal/Blog/Controller/Index/Index.php :

 <?php namespace Toptal\Blog\Controller\Index; use \Magento\Framework\App\Action\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; use \Magento\Framework\Exception\LocalizedException; class Index extends Action { /** * @var PageFactory */ protected $resultPageFactory; /** * @param Context $context * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->resultPageFactory = $resultPageFactory; } /** * Prints the blog from informed order id * @return Page * @throws LocalizedException */ public function execute() { $resultPage = $this->resultPageFactory->create(); return $resultPage; } }

Our action is defining two methods. Let us take a closer look at them:

  • The constructor method simply sends the $context parameter to its parent method, and sets the $resultPageFactory parameter to an attribute for later use. At this point it is useful to know the Dependency Injection design pattern, as that is what is happening here. In Magento 2's case we have automatic dependency injection. This means that whenever a class instantiation occurs, Magento will automatically try to instantiate all of the class constructor parameters (dependencies) and inject it for you as constructor parameters. It identifies which classes to instantiate for each parameter by inspecting the type hints, in this case Context and PageFactory .

  • The execute method is responsible for the action execution itself. In our case, we are simply telling Magento to render its layout by returning a Magento\Framework\View\Result\Page object. This will trigger the layout rendering process, which we will create in a bit.

Now you should see a blank page at the url http://magento2.dev/blog/index/index. We still need to define the layout structure for that route, and its corresponding Block (our ViewModel) and the template file which will present the data to our user.

The layout structure for the front-end application is defined under view/frontend/layout , and the file name must reflect our route. As our route is blog/index/index , the layout file for that route will be app/code/Toptal/Blog/view/frontend/layout/blog_index_index.xml :

 <?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Toptal\Blog\Block\Posts" name="posts.list" template="Toptal_Blog::post/list.phtml" /> </referenceContainer> </body> </page>

Here, we must define three very important structures in the Magento layout structure: Blocks, Containers, and Templates.

  • Blocks are the ViewModel part of our MVVM architecture, which was explained in earlier sections. They are the building blocks of our template structure.

  • Containers contain and output Blocks. They hold blocks together in nice hierarchical structures, and help in making things make sense when the layout for a page is being processed.

  • Templates are PHMTL (mixed HTML and PHP) files used by a special type of block in Magento. You can make calls to methods of a $block variable from within a template. The variable is always defined in the template context. You will be invoking your Block's methods by doing so, and thus allowing you to pull information from the ViewModel layer to the actual presentation.

With that extra information at hand, we can analyze the XML layout structure above. This layout structure is basically telling Magento that, when a request is made to the blog/index/index route, a Block of the type Toptal\Blog\Block\Posts is to be added to the content container, and the template which will be used to render it is Toptal_blog::post/list.phtml .

This leads us to the creation of our two remaining files. Our Block, located at app/code/Toptal/Blog/Block/Posts.php :

<?php namespace Toptal\Blog\Block; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Element\Template\Context; use \Toptal\Blog\Model\ResourceModel\Post\Collection as PostCollection; use \Toptal\Blog\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory; use \Toptal\Blog\Model\Post; class Posts extends Template { /** * CollectionFactory * @var null|CollectionFactory */ protected $_postCollectionFactory = null; /** * Constructor * * @param Context $context * @param PostCollectionFactory $postCollectionFactory * @param array $data */ public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { $this->_postCollectionFactory = $postCollectionFactory; parent::__construct($context, $data); } /** * @return Post[] */ public function getPosts() { /** @var PostCollection $postCollection */ $postCollection = $this->_postCollectionFactory->create(); $postCollection->addFieldToSelect('*')->load(); return $postCollection->getItems(); } /** * For a given post, returns its url * @param Post $post * @return string */ public function getPostUrl( Post $post ) { return '/blog/post/view/id/' . $post->getId(); } }

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

Если вы помните, мы не определили Toptal\Blog\Model\ResourceModel\Post\CollectionFactory . Мы определили только Toptal\Blog\Model\ResourceModel\Post\Collection . Так как это вообще работает? Для каждого класса, который вы определяете в своем модуле, Magento 2 автоматически создаст для вас Фабрику. Фабрики имеют два метода: create , который будет возвращать новый экземпляр для каждого вызова, и get , который всегда будет возвращать один и тот же экземпляр при каждом вызове — используется для реализации шаблона Singleton.

Третий параметр нашего блока, $data , является необязательным массивом. Поскольку он является необязательным и не имеет подсказки типа, он не будет вводиться системой автоматического ввода. Важно отметить, что необязательные параметры конструктора всегда должны располагаться последними в параметрах. Например, конструктор нашего родительского класса Magento\Framework\View\Element\Template имеет следующие параметры:

 public function __construct( Template\Context $context, array $data = [] ) { ...

Поскольку мы хотели добавить нашу CollectionFactory в параметры конструктора после расширения класса Template, нам пришлось сделать это перед необязательным параметром, иначе инъекция не сработает:

 public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { ...

В методе getPosts , к которому позже будет обращаться наш шаблон, мы просто вызываем метод create из PostCollectionFactory , который вернет нам свежую PostCollection и позволит нам получить наши сообщения из базы данных и отправить их в наш ответ.

И чтобы закончить макет этого маршрута, вот наш шаблон PHTML, app/code/Toptal/Blog/view/frontend/templates/post/list.phtml :

 <?php /** @var Toptal\Blog\Block\Posts $block */ ?> <h1>Toptal Posts</h1> <?php foreach($block->getPosts() as $post): ?> <?php /** @var Toptal\Blog\Model\Post */ ?> <h2><a href="<?php echo $block->getPostUrl($post);?>"><?php echo $post->getTitle(); ?></a></h2> <p><?php echo $post->getContent(); ?></p> <?php endforeach; ?>

Обратите внимание, что здесь мы видим, как слой View обращается к нашему ModelView ( $block->getPosts() ), который, в свою очередь, использует ResourceModel (коллекцию) для извлечения наших моделей ( Toptal\Blog\Model\Post ) из базы данных. В каждом шаблоне, всякий раз, когда вы хотите получить доступ к методам его блока, будет определена переменная $block ожидающая ваших вызовов.

Теперь вы сможете увидеть список сообщений, просто снова нажав на наш маршрут.

Наша индексная страница, показывающая список постов

Просмотр отдельных сообщений

Теперь, если вы нажмете на заголовок сообщения, вы получите 404, так что давайте это исправим. Со всей нашей структурой это становится довольно просто. Нам нужно будет только создать следующее:

  • Новое действие, отвечающее за обработку запросов к маршруту blog/post/view .
  • Блок для рендеринга поста
  • Шаблон PHTML, отвечающий за само представление
  • Файл макета для маршрута блог/публикация/просмотр, объединяющий эти последние части.

Наше новое действие довольно простое. Он просто получит id параметра из запроса и зарегистрирует его в основном реестре Magento, центральном хранилище информации, доступной в течение одного цикла запроса. Сделав это, мы позже сделаем идентификатор доступным для блока. Файл должен находиться по адресу app/code/Toptal/Blog/Controller/Post/View.php , и вот его содержимое:

 <?php namespace Toptal\Blog\Controller\Post; use \Magento\Framework\App\Action\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; use \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\Registry; class View extends Action { const REGISTRY_KEY_POST_; /** * Core registry * @var Registry */ protected $_coreRegistry; /** * @var PageFactory */ protected $_resultPageFactory; /** * @param Context $context * @param Registry $coreRegistry * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, Registry $coreRegistry, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->_coreRegistry = $coreRegistry; $this->_resultPageFactory = $resultPageFactory; } /** * Saves the blog id to the register and renders the page * @return Page * @throws LocalizedException */ public function execute() { $this->_coreRegistry->register(self::REGISTRY_KEY_POST_ID, (int) $this->_request->getParam('id')); $resultPage = $this->_resultPageFactory->create(); return $resultPage; } }

Обратите внимание, что мы добавили параметр $coreRegistry в нашу __construct и сохранили его как атрибут для последующего использования. В методе execute мы получаем параметр id из запроса и регистрируем его. Мы также используем константу класса self::REGISTRY_KEY_POST_ID в качестве ключа к реестру, и мы будем использовать эту же константу в нашем блоке для ссылки на идентификатор в реестре.

Давайте создадим блок в app/code/Toptal/Blog/Block/View.php со следующим содержимым:

 <?php namespace Toptal\Blog\Block; use \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Element\Template\Context; use \Magento\Framework\Registry; use \Toptal\Blog\Model\Post; use \Toptal\Blog\Model\PostFactory; use \Toptal\Blog\Controller\Post\View as ViewAction; class View extends Template { /** * Core registry * @var Registry */ protected $_coreRegistry; /** * Post * @var null|Post */ protected $_post = null; /** * PostFactory * @var null|PostFactory */ protected $_postFactory = null; /** * Constructor * @param Context $context * @param Registry $coreRegistry * @param PostFactory $postCollectionFactory * @param array $data */ public function __construct( Context $context, Registry $coreRegistry, PostFactory $postFactory, array $data = [] ) { $this->_postFactory = $postFactory; $this->_coreRegistry = $coreRegistry; parent::__construct($context, $data); } /** * Lazy loads the requested post * @return Post * @throws LocalizedException */ public function getPost() { if ($this->_post === null) { /** @var Post $post */ $post = $this->_postFactory->create(); $post->load($this->_getPostId()); if (!$post->getId()) { throw new LocalizedException(__('Post not found')); } $this->_post = $post; } return $this->_post; } /** * Retrieves the post id from the registry * @return int */ protected function _getPostId() { return (int) $this->_coreRegistry->registry( ViewAction::REGISTRY_KEY_POST_ID ); } }

В блоке представления мы определяем защищенный метод _getPostId , который будет просто получать идентификатор сообщения из основного реестра. Публичный метод getPost , в свою очередь, будет лениво загружать сообщение и выдавать исключение, если сообщение не существует. Создание здесь исключения заставит Magento показать экран ошибок по умолчанию, что может быть не лучшим решением в таком случае, но мы оставим его таким для простоты.

Переходим к нашему шаблону PHTML. Добавьте app/code/Toptal/Blog/view/frontend/templates/post/view.phtml со следующим содержимым:

 <?php /** @var Toptal\Blog\Block\View $block */ ?> <h1><?php echo $block->getPost()->getTitle(); ?></h1> <p><?php echo $block->getPost()->getContent(); ?></p>

Красиво и просто, просто обратившись к методу getPost блока View, который мы создали ранее.

И, чтобы собрать все это вместе, мы создаем файл макета для нашего нового маршрута в app/code/Toptal/Blog/view/frontend/layout/blog_post_view.xml со следующим содержимым:

 <?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Toptal\Blog\Block\View" name="post.view" template="Toptal_Blog::post/view.phtml" /> </referenceContainer> </body> </page>

Это делает то же самое, что мы делали раньше. Он просто добавляет Toptal\Blog\Block\View в контейнер content с Toptal_Blog::post/view.phtml в качестве связанного шаблона.

Чтобы увидеть его в действии, просто направьте свой браузер на http://magento2.dev/blog/post/view/id/1, чтобы успешно загрузить сообщение. Вы должны увидеть экран, подобный приведенному ниже:

Страница для отображения отдельных сообщений

И, как вы можете видеть, после создания нашей исходной структуры очень просто добавить функции в платформу, и большая часть нашего исходного кода повторно используется в процессе.

Если вы хотите быстро протестировать модуль, вот общий результат нашей работы.

Куда пойти отсюда

Если вы следили за мной до сих пор, поздравляю! Я уверен, что вы очень близки к тому, чтобы стать разработчиком Magento 2. Мы разработали довольно продвинутый пользовательский модуль Magento 2, и, несмотря на то, что он прост в своих функциях, мы проделали большую работу.

Некоторые вещи были исключены из этой статьи для простоты. Назвать несколько:

  • Администрирование редактирует формы и сетки для управления содержанием нашего блога.
  • Категории блогов, теги и комментарии
  • Репозитории и несколько сервисных контрактов, которые мы могли бы установить
  • Упаковка модулей в виде расширений Magento 2

В любом случае, вот несколько полезных ссылок, по которым вы сможете еще больше углубить свои знания:

  • Блог Алана Сторма о Magento 2 — У Алана Сторма, вероятно, самый поучительный контент, когда дело доходит до изучения Magento.
  • Блог Алана Кента
  • Документация Magento: Magento 2 Dev Docs

Я предоставил вам всестороннее введение во все важные аспекты создания модуля в Magento 2, а также несколько дополнительных ресурсов, если они вам понадобятся. Теперь вам решать, получить код или перейти к комментариям, если вы хотите взвесить.