Tutorial Magento 2: Como construir um módulo completo
Publicados: 2022-03-11Magento é atualmente a maior plataforma de comércio eletrônico de código aberto do mundo. Devido à sua base de código rica em recursos e extensível, comerciantes com operações grandes e pequenas em todo o mundo o utilizam para uma ampla variedade de projetos.
O Magento 1 existe há oito anos, e seu sucessor, Magento 2, foi lançado no final de 2015, melhorando pontos fracos da versão anterior, como:
- Performance melhorada
- Pacote oficial de testes automatizados
- Melhor interface de usuário de back-end
- Base de código de front-end nova e mais moderna
- Uma maneira mais modular de desenvolver módulos, com arquivos contidos dentro do código Magento em vez de estarem espalhados por todo o lugar
- Redução do número de conflitos entre módulos tentando customizar a mesma funcionalidade
Com pouco mais de um ano, a melhora é visível, embora nem todos os problemas mencionados tenham sido totalmente resolvidos. Agora é completamente seguro dizer que o Magento 2 é um software muito mais robusto que seu antecessor. Algumas das melhorias presentes no Magento 2 são:
- Testes unitários e de integração, incluindo uma maneira oficial e documentada de criá-los para módulos personalizados
- Módulos que são realmente modularizados, tendo todos os seus arquivos colocados em um único diretório
- Um sistema de templates mais rico, permitindo que o desenvolvedor do tema crie uma hierarquia de templates de n níveis
- Uma série de padrões de design úteis adotados em todo o código, melhorando a qualidade do código e diminuindo a probabilidade de erros criados pelos módulos — incluem injeção automática de dependência, contratos de serviço, repositórios e fábricas, para citar alguns.
- Integração nativa ao Varnish como um sistema de cache de página inteira, bem como Redis para tratamento de sessão e cache
- Suporte a PHP 7
A curva de aprendizado do Magento 2, com todas essas mudanças, tornou-se ainda mais acentuada. Neste guia, pretendo mostrar como desenvolver seu primeiro módulo Magento 2 e apontar a direção certa para continuar seus estudos. Vamos lá!
Pré-requisitos do Tutorial Magento 2
É importante que você tenha um bom entendimento das seguintes tecnologias/conceitos para seguir o restante deste artigo:
- Programação Orientada a Objetos (OOP)
- PHP
- Namespaces
- MySQL
- Uso básico do bash
De todos os itens acima, OOP é provavelmente o mais importante. Magento foi inicialmente criado por uma equipe de desenvolvedores Java experientes, e seu legado certamente pode ser visto em toda a base de código. Caso você não esteja muito confiante sobre suas habilidades de POO, pode ser uma boa ideia revisá-lo antes de começar seu trabalho com a plataforma.
Visão geral da arquitetura do Magento 2
A arquitetura do Magento foi projetada com a intenção de tornar o código fonte o mais modularizado e extensível possível. O objetivo final dessa abordagem é permitir que ela seja facilmente adaptada e customizada de acordo com as necessidades de cada projeto.
Customizar geralmente significa mudar o comportamento do código da plataforma. Na maioria dos sistemas, isso significa alterar o código “núcleo”. No Magento, se você estiver seguindo as melhores práticas, isso é algo que você pode evitar na maioria das vezes, possibilitando que uma loja mantenha-se atualizada com os últimos patches de segurança e lançamentos de recursos de maneira confiável.
Magento 2 é um sistema Model View ViewModel (MVVM). Embora esteja intimamente relacionado ao seu irmão Model View Controller (MVC), uma arquitetura MVVM fornece uma separação mais robusta entre as camadas Model e View. Abaixo está uma explicação de cada uma das camadas de um sistema MVVM:
- O modelo contém a lógica de negócios do aplicativo e depende de uma classe associada — o ResourceModel — para acesso ao banco de dados. Os modelos dependem de contratos de serviço para expor sua funcionalidade às outras camadas do aplicativo.
- A Visualização é a estrutura e o layout do que um usuário vê em uma tela - o HTML real. Isso é obtido nos arquivos PHTML distribuídos com os módulos. Os arquivos PHTML são associados a cada ViewModel nos arquivos Layout XML , que seriam chamados de binders no dialeto MVVM. Os arquivos de layout também podem atribuir arquivos JavaScript para serem usados na página final.
- O ViewModel interage com a camada Model, expondo apenas as informações necessárias à camada View. No Magento 2, isso é tratado pelas classes Block do módulo. Observe que isso geralmente fazia parte da função de Controlador de um sistema MVC. No MVVM, o controlador é responsável apenas por manipular o fluxo do usuário, o que significa que ele recebe solicitações e informa ao sistema para renderizar uma visualização ou redirecionar o usuário para outra rota.
Um módulo Magento 2 consiste em alguns, se não todos, os elementos da arquitetura descrita acima. A arquitetura geral é descrita abaixo (fonte):
Um módulo Magento 2 pode, por sua vez, definir dependências externas usando o Composer, o gerenciador de dependências do PHP. No diagrama acima, você vê que os módulos principais do Magento 2 dependem do Zend Framework, do Symfony e de outras bibliotecas de terceiros.
Abaixo está a estrutura do Magento/Cms, um módulo principal do Magento 2 responsável por lidar com a criação de páginas e blocos estáticos.
Cada pasta contém uma parte da arquitetura, como segue:
- Api: Contratos de serviço, definindo interfaces de serviço e interfaces de dados
- Bloco: Os ViewModels da nossa arquitetura MVVM
- Controlador: Controladores, responsáveis por manipular o fluxo do usuário enquanto interage com o sistema
- etc: Arquivos XML de configuração—O módulo define a si mesmo e suas partes (rotas, modelos, blocos, observadores e tarefas cron) dentro desta pasta. Os arquivos etc também podem ser usados por módulos não principais para substituir a funcionalidade dos módulos principais.
- Helper: Classes auxiliares que contêm código usado em mais de uma camada de aplicação. Por exemplo, no módulo Cms, as classes auxiliares são responsáveis por preparar o HTML para apresentação ao navegador.
- i18n: Contém arquivos CSV de internacionalização, usados para tradução
- Modelo: para modelos e ResourceModels
- Observer: Contém Observadores ou Modelos que estão “observando” eventos do sistema. Normalmente, quando um evento desse tipo é acionado, o observador instancia um Modelo para tratar da lógica de negócios necessária para tal evento.
- Configuração: classes de migração, responsáveis pela criação de esquemas e dados
- Teste: testes de unidade
- Ui: elementos da interface do usuário, como grades e formulários usados no aplicativo de administração
- view: Arquivos de layout (XML) e arquivos de modelo (PHTML) para o aplicativo de front-end e administrador
Também é interessante notar que, na prática, todo o funcionamento interno do Magento 2 vive dentro de um módulo. Na imagem acima, você pode ver, por exemplo, Magento_Checkout , responsável pelo processo de checkout, e Magento_Catalog , responsável pelo manuseio de produtos e categorias. Basicamente, o que isso nos diz é que aprender a trabalhar com módulos é a parte mais importante para se tornar um desenvolvedor Magento 2.
Tudo bem, depois desta introdução relativamente breve à arquitetura do sistema e estrutura do módulo, vamos fazer algo mais concreto, certo? Em seguida, passaremos pelo tutorial tradicional do Weblog para deixá-lo confortável com o Magento 2 e no caminho certo para se tornar um desenvolvedor do Magento 2. Antes disso, precisamos configurar um ambiente de desenvolvimento. Vamos lá!
Configurando o ambiente de desenvolvimento do módulo Magento 2
No momento da redação deste artigo, pudemos usar o Magento 2 DevBox oficial, que é um contêiner Magento 2 Docker. Docker no macOS é algo que ainda considero inutilizável, pelo menos com um sistema que depende muito de E/S de disco rápido como o Magento 2. Então, faremos da maneira tradicional: Instalar todos os pacotes nativamente em nossa própria máquina.
Configurando o Servidor
Instalar tudo certamente é um pouco mais tedioso, mas o resultado final será um ambiente de desenvolvimento Magento extremamente rápido. Acredite, você economizará horas de trabalho ao não depender do Docker para o desenvolvimento do Magento 2.
Este tutorial pressupõe um ambiente no macOS com o Brew instalado. Se esse não for o seu caso, o básico permanecerá o mesmo, alterando apenas a forma como você instala os pacotes. Vamos começar instalando todos os pacotes:
brew install mysql nginxb php70 php70-imagick php70-intl php70-mcryptEm seguida, inicie os serviços:
brew services start mysql brew services start php70 sudo brew services start nginxOk, agora vamos apontar um domínio para nosso endereço de loopback. Abra o arquivo hosts em qualquer editor, mas certifique-se de ter permissões de superusuário. Fazer isso com o Vim seria:
sudo vim /etc/hostsEm seguida, adicione a seguinte linha:
127.0.0.1 magento2.devAgora vamos criar um vhost no Nginx:
vim /usr/local/etc/nginx/sites-available/magento2dev.confAdicione o seguinte conteúdo:
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; } Se você não lidou com o Nginx antes, este arquivo pode assustá-lo, então vamos explicar os pequenos detalhes aqui, pois também irá esclarecer alguns dos funcionamentos internos do Magento. As primeiras linhas simplesmente informam ao Nginx que estamos usando a porta HTTP padrão e nosso domínio é magento2.dev :
listen 80; server_name magento2.dev; Em seguida, definimos algumas variáveis de ambiente. O primeiro — $MAGE_ROOT — contém o caminho para nossa base de código. Observe que você precisará alterar o caminho raiz para corresponder ao seu nome de usuário/caminho da pasta, onde quer que você planeje colocar a fonte:
set $MAGE_ROOT /Users/yourusername/www/magento2dev; A segunda variável $MAGE_MODE — define o modo de tempo de execução para nossa loja. Como estamos desenvolvendo um módulo, usaremos o modo de desenvolvedor. Isso nos permite codificar mais rápido, pois não precisaremos compilar ou implantar arquivos estáticos durante o desenvolvimento. Os outros modos são produção e padrão. O uso real para este último ainda não está claro.
set $MAGE_MODE developer; Depois que essas variáveis são definidas, definimos o caminho raiz do vhost. Observe que sufixamos a variável $MAGE_ROOT com a pasta /pub , disponibilizando apenas parte de nossa loja para a web.
root $MAGE_ROOT/pub; Em seguida, definimos nosso arquivo de índice—o arquivo nginx será carregado quando o arquivo solicitado não existir—como index.php. Este script, $MAGE_ROOT/pub/index.php , é o principal ponto de entrada para os clientes que visitam o carrinho de compras e os aplicativos de administração. Independentemente da URL solicitada, index.php será carregado e o processo de despacho do roteador será iniciado.
index index.php; Em seguida, desativamos alguns recursos do Nginx. Primeiro, desativamos o autoindex , que exibiria uma lista de arquivos quando você solicitasse uma pasta, mas não especificasse um arquivo e nenhum índice estivesse presente. Segundo, desativamos charset , o que permitiria ao Nginx adicionar automaticamente cabeçalhos Charset à resposta.
autoindex off; charset off;Em seguida, definimos alguns cabeçalhos de segurança:
add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block'; Este local, / , é apontado para nossa pasta raiz $MAGE_ROOT/pub e basicamente redireciona qualquer solicitação recebida para nosso front controller index.php, junto com os argumentos da solicitação:
location / { try_files $uri $uri/ /index.php?$args; } A próxima parte pode ser um pouco confusa, mas é bem simples. Algumas linhas atrás, definimos nossa raiz como $MAGE_ROOT/pub . Essa é a configuração recomendada e mais segura, pois a maior parte do código não é visível na web. Mas não é a única maneira de configurar o servidor web. Na verdade, a maioria dos servidores web compartilhados tem uma configuração padrão, que é ter seu servidor web apontando para sua pasta web. Para esses usuários, a equipe do Magento preparou este arquivo para esses casos, quando a raiz é definida como $MAGE_ROOT com o seguinte trecho:
location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; } Tenha em mente que, sempre que possível, é melhor ter seu servidor web apontando para a pasta $MAGE_ROOT/pub . Sua loja ficará mais segura dessa forma.
Em seguida, temos o local estático $MAGE_ROOT/pub/static . Esta pasta está inicialmente vazia e é preenchida automaticamente com os arquivos estáticos dos módulos e temas, como arquivos de imagem, CSS, JS, etc. Aqui, basicamente definimos alguns valores de cache para os arquivos estáticos e, quando o arquivo solicitado não existir, redirecione-o para $MAGE_ROOT/pub/static.php . Esse script irá, entre outras coisas, analisar a solicitação e copiar ou fazer um link simbólico do arquivo especificado do módulo ou tema correspondente, dependendo do modo de tempo de execução definido. Dessa forma, os arquivos estáticos do seu módulo residirão dentro da pasta de nossos módulos, mas serão servidos diretamente da pasta pública 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"; }Em seguida, negamos o acesso à web a algumas pastas e arquivos restritos:
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; }E a última parte é onde carregamos o php-fpm e dizemos para ele executar index.php sempre que o usuário o acessar:
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; }Com isso fora do nosso caminho, salve o arquivo e habilite-o digitando os seguintes comandos:
ln -s /usr/local/etc/nginx/sites-available/magento2dev.conf \ /usr/local/etc/nginx/sites-enabled/magento2dev.conf sudo brew services restart nginxComo instalar o Magento 2
Ok, neste ponto sua máquina atende aos requisitos do Magento 2, faltando apenas a fera em si. Vá até o site do Magento e crie uma conta se ainda não tiver uma. Depois disso, vá para a página de download e baixe a versão mais recente (2.1.5, no momento da redação):
Selecione o formato .tar.bz2 e baixe-o. Em seguida, prossiga para extraí-lo e defina as permissões corretas de pasta e arquivo para que o Magento 2 possa 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/magentoAgora, para instalar as tabelas do banco de dados e criar os arquivos de configuração necessários, executaremos este comando no 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 Lembre-se de alterar o nome do banco de dados ( db-name ), usuário ( db-user ) e senha ( db-password ) para corresponder ao que você usou durante a instalação do MySQL, e pronto! Este comando irá instalar todos os módulos do Magento 2, criando as tabelas e arquivos de configuração necessários. Após terminar, abra seu navegador e vá para http://magento2.dev/. Você deve ver uma instalação limpa do Magento 2 com o tema Luma padrão:
Se você for para http://magento2.dev/admin, deverá ver a página de login do aplicativo Admin:
Em seguida, use as credenciais abaixo para fazer o login:
Usuário: admin Senha: admin123
Finalmente estamos prontos para começar a escrever nosso código!
Criando nosso primeiro módulo Magento 2
Para concluir nosso módulo, teremos que criar os seguintes arquivos e eu o guiarei por todo o processo. Nós vamos precisar:
- Alguns arquivos de registro clichê, para deixar o Magento ciente do nosso módulo Blog
- Um arquivo de interface, para definir nosso contrato de dados para o Post
- Um Post Model, para representar um Post em todo o nosso código, implementando a interface de dados Post
- Um Post Resource Model, para vincular o Post Model ao banco de dados
- Uma Coleção de Posts, para recuperar vários posts de uma só vez do banco de dados com a ajuda do Resource Model
- Duas classes de migração, para configurar nosso esquema e conteúdo de tabela
- Duas ações: uma para listar todas as postagens e outra para mostrar cada postagem individualmente
- Dois arquivos de Blocos, Exibições e Layout: um de cada para a ação de lista e um de cada para a exibição
Primeiro, vamos dar uma olhada rápida na estrutura de pastas do código-fonte principal, para que possamos definir onde colocar nosso código. A maneira como instalamos tem todo o código principal do Magento 2, junto com todas as suas dependências, vivendo dentro da pasta do vendor do compositor.
Cadastrando nosso módulo
Manteremos nosso código em uma pasta separada, app/code . O nome de cada módulo está no formato Namespace_ModuleName e sua localização no sistema de arquivos deve refletir esse nome, app/code/Namespace/ModuleName para este exemplo. Seguindo esse padrão, nomearemos nosso módulo como Toptal_Blog e colocaremos nossos arquivos em app/code/Toptal/Blog . Vá em frente e crie essa estrutura de pastas.
Agora, precisamos criar alguns arquivos clichê para ter nosso módulo registrado no Magento. Primeiro, crie app/code/Toptal/Blog/composer.json :
{}Este arquivo será carregado pelo Composer toda vez que você o executar. Mesmo que não estejamos realmente usando o Composer com nosso módulo, devemos criá-lo para manter o Composer feliz.
Agora vamos registrar nosso módulo no Magento. Vá em frente e crie app/code/Toptal/Blog/registration.php :
<?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Toptal_Blog', __DIR__ ); Aqui, estamos chamando o método register da classe ComponentRegistrar , enviando dois parâmetros: a string 'module' , que é o tipo de componente que estamos registrando, e o nome do nosso módulo, 'Toptal_Blog' . Com essas informações, o autoloader do Magento estará ciente de nosso namespace e saberá onde procurar nossas classes e arquivos XML.
Uma coisa interessante a notar aqui é que temos o tipo do componente ( MODULE ) sendo enviado como parâmetro para a função \Magento\Framework\Component\ComponentRegistrar::register . Não só podemos registrar módulos, podemos registrar outros tipos de componentes. Por exemplo, temas, bibliotecas externas e pacotes de idiomas também são registrados usando esse mesmo método.
Continuando, vamos criar nosso último arquivo 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 arquivo contém algumas informações muito importantes sobre nosso módulo. Eles estão:
- O nome do módulo está presente novamente, expondo o nome do nosso módulo à configuração do Magento.
- A versão de configuração do Magento, que será usada pelo Magento para decidir quando executar scripts de migração de banco de dados.
- As dependências do nosso módulo—Como estamos escrevendo um módulo simples, dependemos apenas de dois módulos principais do Magento:
Magento_DirectoryeMagento_Config.
Agora, temos um módulo que deve ser reconhecido pelo Magento 2. Vamos verificá-lo usando a CLI do Magento 2.
Primeiro, precisamos desabilitar o cache do Magento. Os mecanismos de cache do Magento merecem um artigo dedicado a eles. Por enquanto, como estamos desenvolvendo um módulo e queremos que nossas alterações sejam reconhecidas pelo Magento instantaneamente, sem a necessidade de limpar o cache o tempo todo, vamos simplesmente desativá-lo. Na linha de comando, execute:
./bin/magento cache:disableEntão vamos ver se o Magento já está ciente de nossas modificações olhando o status dos módulos. Basta executar o seguinte comando:
./bin/magento module:statusO resultado do último deve ser semelhante a:
Nosso módulo está lá, mas como mostra a saída, ele ainda está desabilitado. Para habilitá-lo, execute:
./bin/magento module:enable Toptal_Blog Isso deveria ter feito isso. Para ter certeza, você pode chamar module:status novamente e procurar o nome do nosso módulo na lista habilitada:
Manipulando o armazenamento de dados
Agora que habilitamos nosso módulo, precisamos criar a tabela de banco de dados que contém nossas postagens no blog. Este é o esquema para a tabela que queremos criar:
| Campo | Tipo | Nulo | Chave | Predefinição |
|---|---|---|---|---|
| post_id | int(10) não assinado | NÃO | PRI | NULO |
| título | texto | NÃO | NULO | |
| contente | texto | NÃO | NULO | |
| criado em | carimbo de data/hora | NÃO | CURRENT_TIMESTAMP |
Conseguimos isso criando a classe InstallSchema , que é responsável por gerenciar a instalação de nossa migração de esquema. O arquivo está localizado em app/code/Toptal/Blog/Setup/InstallSchema.php e possui o seguinte conteúdo:
<?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(); } } Se você analisar o método de install , notará que ele simplesmente cria nossa tabela e adiciona suas colunas uma a uma.

Para determinar quando executar uma migração de esquema, o Magento mantém uma tabela com todas as versões de configuração atuais de cada módulo e, sempre que uma versão do módulo muda, suas classes de migração são inicializadas. Esta tabela é setup_module , e se você der uma olhada no conteúdo dessa tabela, verá que não há referência ao nosso módulo até agora. Então, vamos mudar isso. De um terminal, dispare o seguinte comando:
./bin/magento setup:upgradeIsso mostrará uma lista de todos os módulos e seus scripts de migração que foram executados, incluindo o nosso:
Agora, a partir do seu cliente MySQL de preferência, você pode verificar se a tabela foi realmente criada:
E na tabela setup_module , agora há uma referência ao nosso módulo, seu esquema e versão de dados:
Ok, e as atualizações de esquema? Vamos adicionar algumas postagens a essa tabela por meio de uma atualização para mostrar como fazer isso. Primeiro, coloque o setup_version em nosso arquivo etc/module.xml :
Agora criamos nosso arquivo app/code/Toptal/Blog/Setup/UpgradeData.php , que é responsável pelas migrações de dados (não 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(); } } Você pode ver que é muito semelhante à nossa classe Install. A única diferença é que ele implementa um UpgradeDataInterface em vez de InstallSchemaInterface , e o método principal é chamado upgrade . Com este método, você verifica a versão instalada do módulo atual e, quando menor que a sua, ativa as alterações que precisa fazer. Em nosso exemplo, estamos verificando se a versão atual é menor que 0.1.1 na linha a seguir usando a função version_compare :
if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) { A chamada $context->getVersion() retornará 0.1.0 quando o comando setup:upgrade CLI for chamado pela primeira vez. Em seguida, os dados de amostra são carregados no banco de dados e nossa versão é aumentada para 0.1.1. Para fazer isso funcionar, vá em frente e dispare um setup:upgrade :
./bin/magento setup:upgradeE depois confira os resultados na tabela de posts:
E na tabela setup_module :
Observe que, embora tenhamos adicionado dados à nossa tabela usando o processo de migração, também seria possível alterar o esquema. O processo é o mesmo; você só usaria o UpgradeSchemaInterface em vez do UpgradeDataInterface .
Definindo o modelo para postagens
Seguindo em frente, se você se lembrar de nossa visão geral da arquitetura, nosso próximo bloco de construção seria a postagem do blog ResourceModel. O Resource Model é muito simples e simplesmente indica a tabela para a qual o Model se “conectará”, junto com qual é sua chave primária. Vamos criar nosso ResourceModel em app/code/Toptal/Blog/Model/ResourceModel/Post.php com o seguinte conteúdo:
<?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 as operações ResourceModel, a menos que você precise de algo diferente das operações CRUD usuais, são tratadas pela classe pai AbstractDb .
Também precisaremos de outro ResourceModel, um Collection. A Coleção será responsável por consultar o banco de dados para várias postagens usando nosso ResourceModel e devolver uma série de Modelos instanciados e preenchidos com informações. Criamos o arquivo app/code/Toptal/Blog/Model/ResourceModel/Post/Collection.php com o seguinte conteúdo:
<?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'); } }Observe que no construtor simplesmente mencionamos o Model, que representará a entidade post em todo o nosso código, e o ResourceModel, que buscará as informações no banco de dados.
A peça que falta para esta camada é o próprio Post Model. O modelo deve conter todos os atributos que definimos em nosso esquema, juntamente com qualquer lógica de negócios que você possa precisar. Seguindo o padrão do Magento 2, precisamos criar uma Interface de Dados da qual nosso modelo se estenderá. Colocamos a interface em app/code/Toptal/Blog/Api/Data/PostInterface.php , e ela deve conter os nomes dos campos da tabela, juntamente com os métodos para acessá-los:
<?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); } Agora para a implementação do modelo, em app/code/Toptal/Blog/Model/Post.php . Vamos criar os métodos definidos na interface. Também especificaremos uma tag de cache através da constante CACHE_TAG e, no construtor, especificaremos o ResourceModel que será responsável pelo acesso ao banco de dados do nosso 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
$contextparameter to its parent method, and sets the$resultPageFactoryparameter 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 caseContextandPageFactory.The
executemethod is responsible for the action execution itself. In our case, we are simply telling Magento to render its layout by returning aMagento\Framework\View\Result\Pageobject. 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
$blockvariable 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(); } } Essa classe é bastante simples, e seu objetivo é apenas carregar os posts a serem mostrados e fornecer um método getPostUrl para o template. Há algumas coisas a notar embora.
Se você se lembra, não definimos uma classe Toptal\Blog\Model\ResourceModel\Post\CollectionFactory . Definimos apenas o Toptal\Blog\Model\ResourceModel\Post\Collection . Então, como isso está funcionando? Para cada classe que você definir em seu módulo, o Magento 2 criará automaticamente uma fábrica para você. As fábricas têm dois métodos: create , que retornará uma nova instância para cada chamada, e get , que sempre retornará a mesma instância sempre que chamada — usado para implementar o padrão Singleton.
O terceiro parâmetro do nosso Bloco, $data , é um array opcional. Como é opcional e não possui dica de tipo, não será injetado pelo sistema de injeção automática. É importante notar que os parâmetros construtores opcionais devem sempre ser posicionados por último nos parâmetros. Por exemplo, o construtor do Magento\Framework\View\Element\Template , nossa classe pai, tem estes parâmetros:
public function __construct( Template\Context $context, array $data = [] ) { ... Como queríamos adicionar nossa CollectionFactory aos parâmetros do construtor após estender a classe Template, tivemos que fazer isso antes do parâmetro opcional, caso contrário a injeção não funcionaria:
public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { ... No método getPosts , que será acessado posteriormente pelo nosso template, simplesmente chamamos o método create do PostCollectionFactory , que nos retornará um PostCollection novo e nos permitirá buscar nossos posts do banco de dados e enviá-los para nossa resposta.
E para finalizar o layout desta rota, aqui está nosso template 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; ?> Observe que aqui podemos ver a camada View acessando nosso ModelView ( $block->getPosts() ) que por sua vez usa um ResourceModel (a coleção) para buscar nossos modelos ( Toptal\Blog\Model\Post ) do banco de dados. Em cada template, sempre que você quiser acessar os métodos de seu bloco, haverá uma variável $block definida e aguardando suas chamadas.
Agora você poderá ver a lista de postagens simplesmente acessando nossa rota novamente.
Como visualizar postagens individuais
Agora, se você clicar no título de um post, receberá um 404, então vamos corrigir isso. Com toda a nossa estrutura montada, isso se torna bastante simples. Precisaremos apenas criar o seguinte:
- Uma nova ação, responsável por tratar as requisições para a rota
blog/post/view - Um bloco para renderizar o post
- Um modelo PHTML, responsável pela visualização em si
- Um arquivo de layout para a rota blog/post/view, juntando essas últimas peças.
Nossa nova ação é bastante simples. Ele simplesmente receberá o id do parâmetro da solicitação e o registrará no registro principal do Magento, um repositório central para informações disponíveis em um único ciclo de solicitação. Ao fazer isso, disponibilizaremos o ID para o bloco posteriormente. O arquivo deve estar localizado em app/code/Toptal/Blog/Controller/Post/View.php e este é o seu conteúdo:
<?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; } } Observe que adicionamos o parâmetro $coreRegistry ao nosso __construct e o salvamos como um atributo para uso posterior. No método execute , recuperamos o parâmetro id da solicitação e o registramos. Também usamos uma constante de classe, self::REGISTRY_KEY_POST_ID como chave para o registro, e usaremos essa mesma constante em nosso bloco para nos referirmos ao id no registro.
Vamos criar o bloco, em app/code/Toptal/Blog/Block/View.php com o seguinte conteúdo:
<?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 ); } } No bloco de visualização, definimos um método protegido _getPostId , que simplesmente recuperará o ID do post do registro principal. O método público getPost , por sua vez, carregará o post com preguiça e lançará uma exceção se o post não existir. Lançar uma exceção aqui fará com que o Magento mostre sua tela de erro padrão, que pode não ser a melhor solução nesse caso, mas vamos mantê-la assim por uma questão de simplicidade.
Para o nosso modelo PHTML. Adicione app/code/Toptal/Blog/view/frontend/templates/post/view.phtml com o seguinte conteúdo:
<?php /** @var Toptal\Blog\Block\View $block */ ?> <h1><?php echo $block->getPost()->getTitle(); ?></h1> <p><?php echo $block->getPost()->getContent(); ?></p> Agradável e simples, basta acessar o método getPost do bloco View que criamos anteriormente.
E, para juntar tudo, criamos um arquivo de layout para nossa nova rota em app/code/Toptal/Blog/view/frontend/layout/blog_post_view.xml com o seguinte conteúdo:
<?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> Isso faz a mesma coisa que fizemos antes. Ele simplesmente adiciona Toptal\Blog\Block\View ao contêiner de content , com Toptal_Blog::post/view.phtml como o modelo associado.
Para vê-lo em ação, basta direcionar seu navegador para http://magento2.dev/blog/post/view/id/1 para carregar um post com sucesso. Você deverá ver uma tela como a abaixo:
E como você pode ver, depois de criar nossa estrutura inicial, é muito simples adicionar recursos à plataforma, e a maior parte do nosso código inicial é reutilizada no processo.
Caso você queira testar rapidamente o módulo, aqui está o resultado total do nosso trabalho.
Para onde ir a partir daqui
Se você me acompanhou até aqui, parabéns! Tenho certeza que você está bem perto de se tornar um desenvolvedor Magento 2. Desenvolvemos um módulo personalizado Magento 2 bastante avançado e, embora seja simples em seus recursos, muito terreno foi coberto.
Algumas coisas foram omitidas deste artigo, por uma questão de simplicidade. Para nomear alguns:
- Admin editar formulários e grades para gerenciar o conteúdo do nosso blog
- Categorias de blogs, tags e comentários
- Repositórios e alguns contratos de serviço que poderíamos ter estabelecido
- Empacotando módulos como extensões do Magento 2
De qualquer forma, aqui estão alguns links úteis onde você pode aprofundar ainda mais seus conhecimentos:
- Alan Storm Blog no Magento 2 — Alan Storm provavelmente tem o conteúdo mais didático quando se trata de aprender Magento.
- Blog de Alan Kent
- Documentação do Magento: The Magento 2 Dev Docs
Forneci uma introdução abrangente a todos os aspectos relevantes de como criar um módulo no Magento 2 e alguns recursos adicionais, caso você precise deles. Agora cabe a você obter a codificação, ou vá até os comentários, se quiser ponderar.
