Tutorial de Magento 2: Cómo construir un módulo completo

Publicado: 2022-03-11

Magento es actualmente la plataforma de comercio electrónico de código abierto más grande del mundo. Debido a su base de código ampliable y rica en funciones, los comerciantes con operaciones grandes y pequeñas en todo el mundo lo han estado utilizando para una amplia variedad de proyectos.

Magento 1 existe desde hace ocho años, y su sucesor, Magento 2, se lanzó a fines de 2015, mejorando los puntos débiles de la versión anterior, como:

  • Desempeño mejorado
  • Conjunto oficial de pruebas automatizadas
  • Mejor interfaz de usuario de back-end
  • Base de código front-end nueva y más moderna
  • Una forma más modular de desarrollar módulos, con archivos contenidos dentro del código de Magento en lugar de estar dispersos por todos lados
  • Número reducido de conflictos entre módulos que intentan personalizar la misma funcionalidad

Un logotipo estilizado de Magento 2

Un poco más de un año después, y la mejora es visible, aunque no todos los problemas mencionados se han resuelto por completo. Ahora es completamente seguro decir que Magento 2 es una pieza de software mucho más robusta que su predecesor. Algunas de las mejoras presentes en Magento 2 son:

  • Pruebas unitarias y de integración, incluida una forma oficial y documentada de crearlas para módulos personalizados
  • Módulos que están realmente modularizados, con todos sus archivos ubicados en un solo directorio
  • Un sistema de plantillas más rico, que permite al desarrollador de temas crear una jerarquía de plantillas de n niveles
  • Una serie de patrones de diseño útiles adoptados en todo el código, que mejoran la calidad del código y reducen la probabilidad de errores creados por los módulos. Estos incluyen inyección automática de dependencias, contratos de servicio, repositorios y fábricas, por nombrar algunos.
  • Integración nativa con Varnish como un sistema de almacenamiento en caché de página completa, así como con Redis para el manejo de sesiones y caché
  • Compatibilidad con PHP7

La curva de aprendizaje de Magento 2, con todos estos cambios, se ha vuelto aún más pronunciada. En esta guía, tengo la intención de mostrarle cómo desarrollar su primer módulo Magento 2 y orientarlo en la dirección correcta para continuar sus estudios. ¡Hagámoslo!

Requisitos previos del tutorial de Magento 2

Es importante que tenga una buena comprensión de las siguientes tecnologías/conceptos para seguir el resto de este artículo:

  • Programación orientada a objetos (POO)
  • PHP
  • Espacios de nombres
  • mysql
  • Uso básico de bash

De todos los anteriores, OOP es probablemente el más importante. Magento fue creado inicialmente por un equipo de desarrolladores de Java experimentados, y su legado ciertamente se puede ver en toda la base de código. En caso de que no esté muy seguro de sus habilidades de programación orientada a objetos, podría ser una buena idea revisarlas antes de comenzar a trabajar con la plataforma.

Descripción general de la arquitectura de Magento 2

La arquitectura de Magento se diseñó con la intención de que el código fuente fuera lo más modular y extensible posible. El objetivo final de ese enfoque es permitir que se adapte y personalice fácilmente de acuerdo con las necesidades de cada proyecto.

Personalizar normalmente significa cambiar el comportamiento del código de la plataforma. En la mayoría de los sistemas, esto significa cambiar el código "principal". En Magento, si está siguiendo las mejores prácticas, esto es algo que puede evitar la mayor parte del tiempo, lo que hace posible que una tienda se mantenga actualizada con los últimos parches de seguridad y lanzamientos de funciones de manera confiable.

Magento 2 es un sistema Model View ViewModel (MVVM). Si bien está estrechamente relacionado con su hermano Model View Controller (MVC), una arquitectura MVVM proporciona una separación más sólida entre las capas Model y View. A continuación se muestra una explicación de cada una de las capas de un sistema MVVM:

  • El modelo contiene la lógica comercial de la aplicación y depende de una clase asociada, el ResourceModel, para el acceso a la base de datos. Los modelos se basan en contratos de servicio para exponer su funcionalidad a las otras capas de la aplicación.
  • La vista es la estructura y el diseño de lo que un usuario ve en una pantalla: el HTML real. Esto se logra en los archivos PHTML distribuidos con módulos. Los archivos PHTML están asociados a cada ViewModel en los archivos XML de diseño , que se denominarían carpetas en el dialecto MVVM. Los archivos de diseño también pueden asignar archivos JavaScript para usar en la página final.
  • ViewModel interactúa con la capa Model, exponiendo solo la información necesaria a la capa View. En Magento 2, esto es manejado por las clases de bloque del módulo. Tenga en cuenta que esto solía ser parte del rol de controlador de un sistema MVC. En MVVM, el controlador solo es responsable de manejar el flujo del usuario, lo que significa que recibe solicitudes y le dice al sistema que presente una vista o que redirija al usuario a otra ruta.

Un módulo de Magento 2 consta de algunos, si no todos, los elementos de la arquitectura descrita anteriormente. La arquitectura general se describe a continuación (fuente):

Diagrama de la arquitectura completa de Magento 2

Un módulo de Magento 2 puede, a su vez, definir dependencias externas utilizando Composer, el administrador de dependencias de PHP. En el diagrama anterior, puede ver que los módulos principales de Magento 2 dependen de Zend Framework, Symfony y otras bibliotecas de terceros.

A continuación se muestra la estructura de Magento/Cms, un módulo central de Magento 2 responsable de manejar la creación de páginas y bloques estáticos.

Diseño de directorio del módulo Magento/Cms

Cada carpeta contiene una parte de la arquitectura, de la siguiente manera:

  • Api: contratos de servicio, definición de interfaces de servicio e interfaces de datos
  • Bloque: Los ViewModels de nuestra arquitectura MVVM
  • Controlador: Controladores, responsables de manejar el flujo del usuario mientras interactúa con el sistema
  • etc.: Archivos XML de configuración: el módulo se define a sí mismo y sus partes (rutas, modelos, bloques, observadores y trabajos cron) dentro de esta carpeta. Los archivos etc. también pueden ser utilizados por módulos no centrales para anular la funcionalidad de los módulos principales.
  • Ayudante: Clases auxiliares que contienen código utilizado en más de una capa de aplicación. Por ejemplo, en el módulo Cms, las clases auxiliares son responsables de preparar HTML para su presentación en el navegador.
  • i18n: contiene archivos CSV de internacionalización, utilizados para la traducción
  • Modelo: para modelos y modelos de recursos
  • Observador: contiene observadores o modelos que están "observando" eventos del sistema. Por lo general, cuando se activa un evento de este tipo, el observador instancia un modelo para manejar la lógica comercial necesaria para dicho evento.
  • Configuración: clases de migración, responsables de la creación de esquemas y datos
  • Prueba: pruebas unitarias
  • Ui: elementos de la interfaz de usuario como cuadrículas y formularios utilizados en la aplicación de administración
  • ver: Archivos de diseño (XML) y archivos de plantilla (PHTML) para la aplicación de administración y front-end

También es interesante notar que, en la práctica, todo el funcionamiento interno de Magento 2 vive dentro de un módulo. En la imagen de arriba, puedes ver, por ejemplo, Magento_Checkout , responsable del proceso de pago, y Magento_Catalog , responsable del manejo de productos y categorías. Básicamente, lo que esto nos dice es que aprender a trabajar con módulos es la parte más importante para convertirse en un desarrollador de Magento 2.

Muy bien, después de esta introducción relativamente breve a la arquitectura del sistema y la estructura del módulo, hagamos algo más concreto, ¿de acuerdo? A continuación, repasaremos el tutorial tradicional de Weblog para que se sienta cómodo con Magento 2 y esté encaminado para convertirse en un desarrollador de Magento 2. Antes de eso, necesitamos configurar un entorno de desarrollo. ¡Hagámoslo!

Configuración del entorno de desarrollo del módulo Magento 2

En el momento de escribir este artículo, pudimos usar el DevBox oficial de Magento 2, que es un contenedor Docker de Magento 2. Docker en macOS es algo que todavía considero inutilizable, al menos con un sistema que depende en gran medida de E/S de disco rápidas como Magento 2. Por lo tanto, lo haremos de la manera tradicional: instalar todos los paquetes de forma nativa en nuestra propia máquina.

Configuración del servidor

Instalar todo seguramente es un poco más tedioso, pero el resultado final será un entorno de desarrollo de Magento ultrarrápido. Créame, ahorrará horas de trabajo al no depender de Docker para su desarrollo de Magento 2.

Este tutorial asume un entorno en macOS con Brew instalado. Si ese no es su caso, los conceptos básicos seguirán siendo los mismos, cambiando solo la forma en que instala los paquetes. Comencemos con la instalación de todos los paquetes:

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

A continuación, inicie los servicios:

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

Bien, ahora apuntaremos un dominio a nuestra dirección de loopback. Abra el archivo de hosts en cualquier editor, pero asegúrese de tener permisos de superusuario. Hacer eso con Vim sería:

 sudo vim /etc/hosts

Luego agregue la siguiente línea:

 127.0.0.1 magento2.dev

Ahora crearemos un vhost en Nginx:

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

Agrega el siguiente contenido:

 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; }

Si no ha tratado con Nginx antes, este archivo puede asustarlo, así que permítanos explicarle los detalles aquí, ya que también arrojará algo de luz sobre el funcionamiento interno de Magento. Las primeras líneas simplemente le dicen a Nginx que estamos usando el puerto HTTP predeterminado y que nuestro dominio es magento2.dev :

 listen 80; server_name magento2.dev;

Luego establecemos algunas variables de entorno. El primero $MAGE_ROOT , contiene la ruta a nuestro código base. Tenga en cuenta que deberá cambiar la ruta raíz para que coincida con la ruta de su nombre de usuario/carpeta, donde sea que planee colocar la fuente:

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

La segunda variable, $MAGE_MODE , establece el modo de ejecución de nuestra tienda. Como estamos desarrollando un módulo, usaremos el modo desarrollador. Esto nos permite codificar más rápido, ya que no tendremos que compilar ni implementar archivos estáticos durante el desarrollo. Los otros modos son producción y defecto. El uso real de este último aún no está claro.

 set $MAGE_MODE developer;

Después de configurar estas variables, definimos la ruta raíz de vhost. Tenga en cuenta que agregamos el sufijo a la variable $MAGE_ROOT con la carpeta /pub , lo que hace que solo una parte de nuestra tienda esté disponible en la web.

 root $MAGE_ROOT/pub;

Luego definimos nuestro archivo de índice (el archivo que nginx se cargará cuando el archivo solicitado no exista) como index.php. Este script, $MAGE_ROOT/pub/index.php , es el principal punto de entrada para los clientes que visitan tanto el carrito de compras como las aplicaciones de administración. Independientemente de la URL solicitada, index.php se cargará y se iniciará el proceso de envío del enrutador.

 index index.php;

A continuación, desactivamos algunas funciones de Nginx. Primero, desactivamos autoindex , que mostraría una lista de archivos cuando solicita una carpeta, pero no especifica un archivo y no hay índice presente. En segundo lugar, desactivamos charset , lo que permitiría a Nginx agregar automáticamente encabezados de Charset a la respuesta.

 autoindex off; charset off;

A continuación, definimos algunos encabezados de seguridad:

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

Esta ubicación, / , apunta a nuestra carpeta raíz $MAGE_ROOT/pub , y básicamente redirige cualquier solicitud recibida a nuestro controlador frontal index.php, junto con los argumentos de la solicitud:

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

La siguiente parte puede ser un poco confusa, pero es bastante simple. Hace unas líneas, definimos nuestra raíz como $MAGE_ROOT/pub . Esa es la configuración recomendada y más segura, ya que la mayor parte del código no es visible desde la web. Pero no es la única forma de configurar el servidor web. En realidad, la mayoría de los servidores web compartidos tienen una configuración predeterminada, que es tener su servidor web apuntando a su carpeta web. Para esos usuarios, el equipo de Magento preparó este archivo para esos casos, cuando la raíz se define como $MAGE_ROOT con el siguiente fragmento:

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

Tenga en cuenta que, siempre que sea posible, es mejor si tiene su servidor web apuntando a la $MAGE_ROOT/pub . Tu tienda estará más segura de esta manera.

A continuación, tenemos la ubicación estática $MAGE_ROOT/pub/static . Esta carpeta inicialmente está vacía y se llena automáticamente con los archivos estáticos de los módulos y temas, como archivos de imagen, CSS, JS, etc. Aquí, básicamente definimos algunos valores de caché para los archivos estáticos y, cuando el archivo solicitado no existe, redirígelo a $MAGE_ROOT/pub/static.php . Ese script, entre otras cosas, analizará la solicitud y copiará o enlazará el archivo especificado del módulo o tema correspondiente, según el modo de tiempo de ejecución definido. De esta forma, los archivos estáticos de su módulo residirán dentro de la carpeta de nuestros módulos, pero se servirán directamente desde la carpeta pública de 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"; }

A continuación, denegamos el acceso web a algunas carpetas y archivos restringidos:

 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; }

Y el último bit es donde cargamos php-fpm y le decimos que ejecute index.php cada vez que el usuario lo presione:

 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; }

Con eso fuera de nuestro camino, guarde el archivo y luego actívelo escribiendo los siguientes comandos:

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

Cómo instalar Magento 2

De acuerdo, en este punto su máquina cumple con los requisitos de Magento 2, y solo falta la bestia misma. Dirígete al sitio web de Magento y crea una cuenta si aún no tienes una. Después de eso, vaya a la página de descarga y descargue la última versión (2.1.5, en el momento de escribir este artículo):

Página de descarga de Magento 2

Seleccione el formato .tar.bz2 y descárguelo. Luego proceda a extraerlo y establezca la carpeta correcta y los permisos de archivo para que Magento 2 pueda funcionar:

 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

Ahora, para instalar las tablas de la base de datos y crear los archivos de configuración necesarios, ejecutaremos este comando desde la terminal:

 ./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

Recuerde cambiar el nombre de la base de datos ( db-name ), el usuario ( db-user ) y la contraseña ( db-password ) para que coincida con el que usó durante la instalación de MySQL, ¡y eso es todo! Este comando instalará todos los módulos de Magento 2 y creará las tablas y los archivos de configuración necesarios. Una vez que haya terminado, abra su navegador y diríjase a http://magento2.dev/. Debería ver una instalación limpia de Magento 2 con el tema Luma predeterminado:

Página de inicio en el tema Luma predeterminado

Si se dirige a http://magento2.dev/admin, debería ver la página de inicio de sesión de la aplicación Admin:

Página de inicio de sesión de la aplicación de administración

Luego use las credenciales a continuación para iniciar sesión:

Usuario: admin Contraseña: admin123

¡Finalmente estamos listos para comenzar a escribir nuestro código!

Creando nuestro primer módulo Magento 2

Para completar nuestro módulo, tendremos que crear los siguientes archivos y te guiaré a través de todo el proceso. Necesitaremos:

  • Algunos archivos de registro repetitivos, para que Magento conozca nuestro módulo Blog
  • Un archivo de interfaz, para definir nuestro contrato de datos para el Correo
  • Un modelo de publicación, para representar una publicación a lo largo de nuestro código, implementando la interfaz de datos de la publicación
  • Un modelo de recurso de publicación, para vincular el modelo de publicación a la base de datos
  • Una colección de publicaciones, para recuperar varias publicaciones a la vez de la base de datos con la ayuda del modelo de recursos
  • Dos clases de migración, para configurar el esquema y el contenido de nuestra tabla.
  • Dos acciones: una para enumerar todas las publicaciones y otra para mostrar cada publicación individualmente
  • Dos de cada uno de los archivos de bloques, vistas y diseño: uno de cada uno para la acción de lista y uno de cada uno para la vista

Primero, echemos un vistazo rápido a la estructura de carpetas del código fuente central, para que podamos definir dónde colocar nuestro código. La forma en que instalamos tiene todo el código central de Magento 2, junto con todas sus dependencias, viviendo dentro de la carpeta de vendor del compositor.

Diseño de directorio del código central de Magento 2

Registrando Nuestro Módulo

Mantendremos nuestro código en una carpeta separada, app/code . El nombre de cada módulo tiene el formato Namespace_ModuleName y su ubicación en el sistema de archivos debe reflejar ese nombre, app/code/Namespace/ModuleName para este ejemplo. Siguiendo ese patrón, nombraremos nuestro módulo Toptal_Blog y colocaremos nuestros archivos en app/code/Toptal/Blog . Continúe y cree esa estructura de carpetas.

Diseño de directorio de nuestro módulo Toptal_Blog

Ahora, necesitamos crear algunos archivos repetitivos para que nuestro módulo se registre con Magento. Primero, cree app/code/Toptal/Blog/composer.json :

 {}

Composer cargará este archivo cada vez que lo ejecute. Aunque en realidad no estamos usando Composer con nuestro módulo, debemos crearlo para mantener contento a Composer.

Ahora registraremos nuestro módulo con Magento. Continúe y cree app/code/Toptal/Blog/registration.php :

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

Aquí, estamos llamando al método de register de la clase ComponentRegistrar , enviando dos parámetros: la cadena 'module' , que es el tipo de componente que estamos registrando, y el nombre de nuestro módulo, 'Toptal_Blog' . Con esa información, el cargador automático de Magento conocerá nuestro espacio de nombres y sabrá dónde buscar nuestras clases y archivos XML.

Una cosa interesante a tener en cuenta aquí es que tenemos el tipo de componente ( MODULE ) que se envía como parámetro a la función \Magento\Framework\Component\ComponentRegistrar::register . No solo podemos registrar módulos, podemos registrar otro tipo de componentes. Por ejemplo, los temas, las bibliotecas externas y los paquetes de idiomas también se registran con este mismo método.

Continuando, creemos nuestro último archivo de registro, 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>

Este archivo contiene información muy importante sobre nuestro módulo. Ellos son:

  • El nombre del módulo vuelve a estar presente, exponiendo nuestro nombre de módulo a la configuración de Magento.
  • La versión de instalación de Magento, que utilizará Magento para decidir cuándo ejecutar los scripts de migración de la base de datos.
  • Las dependencias de nuestro módulo: como estamos escribiendo un módulo simple, dependemos solo de dos módulos principales de Magento: Magento_Directory y Magento_Config .

Ahora, tenemos un módulo que debería ser reconocible por Magento 2. Verifiquémoslo usando la CLI de Magento 2.

Primero, debemos deshabilitar el caché de Magento. Los mecanismos de caché de Magento merecen un artículo dedicado a ellos. De momento, como estamos desarrollando un módulo y queremos que Magento reconozca nuestros cambios al instante sin necesidad de borrar la caché en todo momento, simplemente lo desactivaremos. Desde la línea de comando, ejecute:

 ./bin/magento cache:disable

Luego, veamos si Magento ya está al tanto de nuestras modificaciones al observar el estado de los módulos. Simplemente ejecute el siguiente comando:

 ./bin/magento module:status

El resultado de la última debe ser similar a:

Salida del comando de estado, que muestra que el módulo Toptal_Blog está deshabilitado

Nuestro módulo está ahí, pero como muestra el resultado, aún está deshabilitado. Para habilitarlo, ejecute:

 ./bin/magento module:enable Toptal_Blog

Eso debería haberlo hecho. Para estar seguro, puede volver a llamar a module:status y buscar el nombre de nuestro módulo en la lista habilitada:

Salida del comando de estado, que muestra que el módulo Toptal_Blog está habilitado

Manejo del almacenamiento de datos

Ahora que hemos habilitado nuestro módulo, necesitamos crear la tabla de la base de datos que contiene las publicaciones de nuestro blog. Este es el esquema de la tabla que queremos crear:

Campo Escribe Nulo Llave Por defecto
ID del mensaje int(10) sin signo NO PRI NULO
título texto NO NULO
contenido texto NO NULO
Creado en marca de tiempo NO FECHA Y HORA ACTUAL

Esto lo logramos creando la clase InstallSchema , que se encarga de gestionar la instalación de nuestro esquema de migración. El archivo se encuentra en app/code/Toptal/Blog/Setup/InstallSchema.php y tiene el siguiente contenido:

 <?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(); } }

Si analiza el método de install , notará que simplemente crea nuestra tabla y agrega sus columnas una por una.

Para determinar cuándo ejecutar una migración de esquema, Magento mantiene una tabla con todas las versiones de configuración actuales para cada módulo, y cada vez que cambia la versión de un módulo, se inicializan sus clases de migración. Esta tabla es setup_module , y si observa el contenido de esa tabla, verá que no hay ninguna referencia a nuestro módulo hasta el momento. Entonces, cambiemos eso. Desde una terminal, ejecute el siguiente comando:

 ./bin/magento setup:upgrade

Eso le mostrará una lista de todos los módulos y sus scripts de migración que se ejecutaron, incluido el nuestro:

Salida del comando de actualización, que muestra cómo se realiza nuestra migración

Ahora, desde tu cliente MySQL de preferencia, puedes comprobar si la tabla realmente ha sido creada:

Demostración de nuestra tabla en el cliente MySQL

Y en la tabla setup_module , ahora hay una referencia a nuestro módulo, su esquema y versión de datos:

Contenido de la tabla setup_module

Ok, ¿y qué pasa con las actualizaciones de esquema? Agreguemos algunas publicaciones a esa tabla a través de una actualización para mostrarle cómo hacerlo. Primero, setup_version en nuestro etc/module.xml :

Destacado del valor modificado en nuestro archivo module.xml

Ahora creamos nuestro archivo app/code/Toptal/Blog/Setup/UpgradeData.php , que es responsable de las migraciones de datos (no de esquema):

 <?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(); } }

Puede ver que es muy similar a nuestra clase Install. La única diferencia es que implementa UpgradeDataInterface en lugar de InstallSchemaInterface , y el método principal se llama upgrade . Con este método, verifica la versión instalada del módulo actual y, cuando es más pequeña que la suya, inicia los cambios que necesita realizar. En nuestro ejemplo, estamos comprobando si la versión actual es menor que 0.1.1 en la siguiente línea usando la función version_compare :

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

La llamada $context->getVersion() devolverá 0.1.0 cuando se llame al comando setup:upgrade CLI por primera vez. Luego, los datos de muestra se cargan en la base de datos y nuestra versión se eleva a 0.1.1. Para que esto funcione, continúe y active una setup:upgrade :

 ./bin/magento setup:upgrade

Y luego verifique los resultados en la tabla de publicaciones:

Contenido de nuestra tabla

Y en la tabla setup_module :

Contenido actualizado de la tabla setup_module

Tenga en cuenta que, aunque agregamos datos a nuestra tabla mediante el proceso de migración, también habría sido posible cambiar el esquema. El proceso es el mismo; solo usaría UpgradeSchemaInterface en lugar de UpgradeDataInterface .

Definición del modelo para publicaciones

Continuando, si recuerda nuestra descripción general de la arquitectura, nuestro próximo bloque de construcción sería la publicación de blog ResourceModel. El modelo de recursos es muy simple y simplemente establece la tabla a la que se "conectará" el modelo, junto con cuál es su clave principal. Crearemos nuestro ResourceModel en app/code/Toptal/Blog/Model/ResourceModel/Post.php con los siguientes contenidos:

 <?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'); } }

Todas las operaciones de ResourceModel, a menos que necesite algo diferente de las operaciones CRUD habituales, son manejadas por la clase principal AbstractDb .

También necesitaremos otro ResourceModel, una Colección. La Colección será responsable de consultar la base de datos para múltiples publicaciones utilizando nuestro ResourceModel y devolver una serie de Modelos instanciados y llenos de información. Creamos el archivo app/code/Toptal/Blog/Model/ResourceModel/Post/Collection.php con el siguiente contenido:

 <?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'); } }

Tenga en cuenta que en el constructor simplemente mencionamos el Modelo, que representará la entidad de publicación en todo nuestro código, y el Modelo de recursos, que obtendrá la información en la base de datos.

La pieza que falta para esta capa es el modelo de publicación en sí. El modelo debe contener todos los atributos que hemos definido en nuestro esquema, junto con cualquier lógica comercial que pueda necesitar. Siguiendo el patrón de Magento 2, necesitamos crear una interfaz de datos desde la cual se extenderá nuestro modelo. Colocamos la interfaz en app/code/Toptal/Blog/Api/Data/PostInterface.php y debería contener los nombres de los campos de la tabla, junto con los métodos para acceder a ellos:

 <?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); }

Ahora a la implementación del modelo, en app/code/Toptal/Blog/Model/Post.php . Crearemos los métodos definidos en la interfaz. También especificaremos una etiqueta de caché a través de la constante CACHE_TAG y, en el constructor, especificaremos el ResourceModel que será responsable del acceso a la base de datos de nuestro modelo.

 <?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(); } }

Esta clase es bastante simple y su objetivo es únicamente cargar las publicaciones que se mostrarán y proporcionar un método getPostUrl a la plantilla. Sin embargo, hay algunas cosas que notar.

Si recuerdas, no hemos definido una clase Toptal\Blog\Model\ResourceModel\Post\CollectionFactory . Solo definimos Toptal\Blog\Model\ResourceModel\Post\Collection . Entonces, ¿cómo funciona esto? Para cada clase que defina en su módulo, Magento 2 creará automáticamente una fábrica para usted. Las fábricas tienen dos métodos: create , que devolverá una nueva instancia para cada llamada, y get , que siempre devolverá la misma instancia cada vez que se llame, que se utiliza para implementar el patrón Singleton.

El tercer parámetro de nuestro bloque, $data , es una matriz opcional. Como es opcional y no tiene indicación de tipo, no será inyectado por el sistema de inyección automática. Es importante notar que los parámetros opcionales del constructor siempre deben colocarse en último lugar en los parámetros. Por ejemplo, el constructor de Magento\Framework\View\Element\Template , nuestra clase principal, tiene estos parámetros:

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

Como queríamos agregar nuestra CollectionFactory a los parámetros del constructor después de extender la clase Plantilla, teníamos que hacerlo antes del parámetro opcional, de lo contrario, la inyección no funcionaría:

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

En el método getPosts , al que nuestra plantilla accederá más tarde, simplemente llamamos al método create de PostCollectionFactory , que nos devolverá una PostCollection nueva y nos permitirá obtener nuestras publicaciones de la base de datos y enviarlas a nuestra respuesta.

Y para terminar el diseño de esta ruta, aquí está nuestra plantilla 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; ?>

Tenga en cuenta que aquí podemos ver la capa Vista accediendo a nuestro ModelView ( $block->getPosts() ) que a su vez usa un ResourceModel (la colección) para obtener nuestros modelos ( Toptal\Blog\Model\Post ) de la base de datos. En cada plantilla, cada vez que desee acceder a los métodos de su bloque, habrá una variable $block definida y esperando sus llamadas.

Ahora debería poder ver la lista de publicaciones simplemente presionando nuestra ruta nuevamente.

Nuestra página de índice, que muestra la lista de publicaciones.

Ver publicaciones individuales

Ahora, si hace clic en el título de una publicación, obtendrá un 404, así que arreglemos eso. Con toda nuestra estructura en su lugar, esto se vuelve bastante simple. Solo necesitaremos crear lo siguiente:

  • Una nueva acción, responsable de gestionar las solicitudes a la ruta blog/post/view
  • Un bloque para renderizar la publicación.
  • Una plantilla PHTML, responsable de la vista en sí
  • Un archivo de diseño para la ruta blog/post/view, juntando estas últimas piezas.

Nuestra nueva acción es bastante simple. Simplemente recibirá la id del parámetro de la solicitud y la registrará en el registro central de Magento, un repositorio central de información que está disponible durante un ciclo de solicitud único. Al hacer esto, haremos que la ID esté disponible para el bloque más adelante. El archivo debe estar ubicado en app/code/Toptal/Blog/Controller/Post/View.php y estos son sus contenidos:

 <?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; } }

Tenga en cuenta que hemos agregado el parámetro $coreRegistry a nuestro __construct y lo hemos guardado como un atributo para su uso posterior. En el método de execute , recuperamos el parámetro id de la solicitud y lo registramos. También usamos una constante de clase, self::REGISTRY_KEY_POST_ID como clave para el registro, y usaremos esta misma constante en nuestro bloque para referirnos a la identificación en el registro.

Vamos a crear el bloque, en app/code/Toptal/Blog/Block/View.php con los siguientes contenidos:

 <?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 ); } }

En el bloque de vista, definimos un método protegido _getPostId , que simplemente recuperará la ID de la publicación del registro central. El método getPost público, a su vez, cargará la publicación de forma diferida y lanzará una excepción si la publicación no existe. Lanzar una excepción aquí hará que Magento muestre su pantalla de error predeterminada, lo que podría no ser la mejor solución en tal caso, pero lo mantendremos así por simplicidad.

En nuestra plantilla PHTML. Agregue app/code/Toptal/Blog/view/frontend/templates/post/view.phtml con los siguientes contenidos:

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

Agradable y simple, simplemente accediendo al método getPost del bloque Ver que creamos anteriormente.

Y, para ponerlo todo junto, creamos un archivo de diseño para nuestra nueva ruta en app/code/Toptal/Blog/view/frontend/layout/blog_post_view.xml con el siguiente contenido:

 <?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>

Esto hace lo mismo que hicimos antes. Simplemente agrega Toptal\Blog\Block\View al contenedor de content , con Toptal_Blog::post/view.phtml como plantilla asociada.

Para verlo en acción, simplemente dirija su navegador a http://magento2.dev/blog/post/view/id/1 para cargar correctamente una publicación. Debería ver una pantalla como la siguiente:

Página para mostrar publicaciones individuales

Y como puede ver, después de crear nuestra estructura inicial, es realmente simple agregar funciones a la plataforma, y ​​la mayor parte de nuestro código inicial se reutiliza en el proceso.

En caso de que quiera probar rápidamente el módulo, aquí está el resultado total de nuestro trabajo.

A dónde ir desde aquí

Si me has seguido hasta aquí, ¡felicidades! Estoy seguro de que está bastante cerca de convertirse en un desarrollador de Magento 2. Hemos desarrollado un módulo personalizado de Magento 2 bastante avanzado y, aunque sus características son simples, se ha cubierto mucho terreno.

Algunas cosas quedaron fuera de este artículo, en aras de la simplicidad. Para nombrar unos pocos:

  • Admin edite formularios y cuadrículas para administrar el contenido de nuestro blog
  • Blogs categorías, etiquetas y comentarios
  • Repositorios y algunos contratos de servicio que podríamos haber establecido
  • Módulos empaquetados como extensiones de Magento 2

En cualquier caso, aquí tienes algunos enlaces útiles donde podrás profundizar aún más tus conocimientos:

  • Blog de Alan Storm sobre Magento 2: Alan Storm tiene probablemente el contenido más didáctico cuando se trata de aprender Magento.
  • Blog de Alan Kent
  • Documentación de Magento: The Magento 2 Dev Docs

Le brindé una introducción completa a todos los aspectos relevantes de cómo crear un módulo en Magento 2, y algunos recursos adicionales en caso de que los necesite. Ahora depende de usted obtener la codificación, o dirigirse a los comentarios si desea opinar.