Magento 2 Interfaces é uma das grandes melhorias que foram adicionadas através do Dependency Injection. Na Engenharia do Software é extremamente importante o uso de Interfaces para abstrações do seu código, para reduzir as dependências nele.
Neste artigo, explicaremos passo-a-passo como trabalhar com Interfaces no Magento 2 e os ganhos que isso traz para o seu módulo.
Entendendo as Interfaces
Interfaces são contratos de serviços. Um contrato de serviço é um conjunto de interfaces PHP definidas para um módulo. Um contrato de serviço inclui interface de dados, preservando a integridade dos dados e interface de serviços, que ocultam detalhes da lógica de negócios de solicitantes de serviço, como controllers, web services e outros módulos.
Se os desenvolvedores definem interfaces de dados e serviços de acordo com um Design Pattern, o resultado é uma API durável e bem definida que outros módulos e extensões de terceiros podem implementar por meio de recursos do Magento.
Magento 2 Interfaces
O Magento 2 trabalha com interfaces por meio do seu arquivo di.xml. Nele são informadas as interfaces e suas respectivas classes concretas.
No módulo de catalog nós temos o seguinte di.xml (apenas uma parte dele):
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Catalog\Api\Data\ProductInterface" type="Magento\Catalog\Model\Product" /> <preference for="Magento\Catalog\Api\ProductRepositoryInterface" type="Magento\Catalog\Model\ProductRepository" /> <preference for="Magento\Catalog\Api\CategoryAttributeRepositoryInterface" type="Magento\Catalog\Model\Category\AttributeRepository" /> <preference for="Magento\Catalog\Api\Data\CategoryAttributeInterface" type="Magento\Catalog\Model\Category\Attribute" /> <preference for="Magento\Catalog\Api\CategoryAttributeOptionManagementInterface" type="Magento\Catalog\Model\Category\Attribute\OptionManagement" /> <preference for="Magento\Catalog\Model\ProductTypes\ConfigInterface" type="Magento\Catalog\Model\ProductTypes\Config" />
Como acima, temos a tag preference com dois atributos FOR e TYPE.
No atributo FOR é informado a interface que será usada e no atributo TYPE a classe concreta utilizada. Ou seja, isso é o mesmo que dizer para o Magento 2: toda vez que interface Magento\Catalog\Api\Data\ProductInterface
for usada, você deverá pegar executar os métodos dela que estão em Magento\Catalog\Model\Product
com as suas respectivas regras de negócios.
Lembrando: numa interface não há regras de negócios. Todas as regras de negócios ficam dentro da classe concreta que implementa a interface.
Mas, antes de implementarmos as nossas interfaces customizadas e entender o funcionamento dela, precisamos analisar a maneira como muitos módulos são escritos hoje em Magento 2.
Importante: Todas as alterações que forem efetuadas em __construct() e/ou no di.xml, será necessário rodar os comandos abaixo:
php bin/magento cache:clean php bin/magento setup:di:compile
Trabalhando de Maneira Errada:
Em nosso exemplo, usamos um módulo customizado que desenvolvemos para fins didáticos:
Nesse módulo, nós criamos um diretório chamado Wrong onde temos as seguintes classes concretas:
<?php namespace ForMage\Learning\Wrong; class ColorBlue { public function getColor() { return "Blue"; } }
<?php namespace ForMage\Learning\Wrong; class SizeNormal { public function getSize() { return "Normal"; } }
Como acima, na classe ColorBlue temos o método public function getColor()
que nos retorna “Blue” e na classe SizeNormal temos o método public function getSize()
que nos retorna “Normal”.
<?php namespace ForMage\Learning\Controller\Page; use Magento\Framework\App\Action\Context; use Magento\Framework\App\ResponseInterface; use ForMage\Learning\Wrong\ColorBlue; use ForMage\Learning\Wrong\SizeNormal; class Index extends \Magento\Framework\App\Action\Action { protected $size; protected $color; public function __construct( Context $context, SizeNormal $size, ColorBlue $color ) { parent::__construct($context); $this->size = $size; $this->color = $color; } public function execute() { echo "My shirt is {$this->color->getColor()} and its size is {$this->size->getSize()}"; } }
No nosso controller, nós demos um USE dessas duas classes. Fizemos o load delas no construtor do controller e chamamos seus métodos dentro do nosso execute()
Acessando a nossa rota: /learning/page/index temos o seguinte resultado:
My Shirt is Blue and its size is Normal
Beleza. Resultado exibido.
Mas, e se quisermos alterar as regras de negócio? Como faremos isso?
Pra isso, criamos as classes:
<?php namespace ForMage\Learning\Wrong; class ColorYellow { public function getColor() { return "Yellow"; } }
<?php namespace ForMage\Learning\Wrong; class SizeBig { public function getSize() { return "Big"; } }
Alterando o nosso controller:
<?php namespace ForMage\Learning\Controller\Page; use Magento\Framework\App\Action\Context; use Magento\Framework\App\ResponseInterface; use ForMage\Learning\Wrong\ColorYellow; use ForMage\Learning\Wrong\SizeBig; class Index extends \Magento\Framework\App\Action\Action { protected $size; protected $color; public function __construct( Context $context, SizeBig $size, ColorYellow $color ) { parent::__construct($context); $this->size = $size; $this->color = $color; } public function execute() { echo "My shirt is {$this->color->getColor()} and its size is {$this->size->getSize()}"; } }
E acessando a nossa rota temos:
My shirt is Yellow and its size is Big
Alteração bem sucedida!
Mas, perceba a quantidade de alterações que tivemos que fazer para alcançar essa modificação nas regras de negócio. Primeiro alteramos dois USE e o CONSTRUCT de
use ForMage\Learning\Wrong\ColorBlue; use ForMage\Learning\Wrong\SizeNormal;
public function __construct( Context $context, SizeNormal $size, ColorBlue $color ) { parent::__construct($context); $this->size = $size; $this->color = $color; }
para
use ForMage\Learning\Wrong\ColorYellow; use ForMage\Learning\Wrong\SizeBig;
public function __construct( Context $context, SizeBig $size, ColorYellow $color ) { parent::__construct($context); $this->size = $size; $this->color = $color; }
Perceba o tamanho da alteração que tivemos que efetuar para alcançar a nossa mudança de regra de negócio. Trabalhar dessa maneira é trabalhar sem as boas práticas de desenvolvimento, sem pensar na sustentabilidade do código, na manutenibilidade de software.
Trabalhando com Interfaces:
Agora vamos entender como resolveríamos esse mesmo problema usando interface e perceber o poder que há em trabalhar com Dependency Injection no Magento 2.
1). Criando as Interfaces:
Voltando ao nosso módulo, criaremos um diretório chamado Api com as seguintes interfaces:
<?php namespace ForMage\Learning\Api; interface ColorInterface { public function getColor(); }
<?php namespace ForMage\Learning\Api; interface SizeInterface { public function getSize(); }
2.) Criando as nossas classes concretas:
Bom, criamos então as nossas interfaces. Agora, vamos as para as nossas classes concretas. Criamos o diretório Model para elas:
<?php namespace ForMage\Learning\Model; use ForMage\Learning\Api\ColorInterface; class ColorBlack implements ColorInterface { public function getColor() { return "Black"; } }
<?php namespace ForMage\Learning\Model; use ForMage\Learning\Api\SizeInterface; class SizeGreat implements SizeInterface { public function getSize() { return "Great"; } }
Note que a nossa classe \ForMage\Learning\Model\ColorBlack
implementa a interface \ForMage\Learning\Api\ColorInterface
implementando o seu método public function getColor();
e a classe \ForMage\Learning\Model\SizeGreat
implementa da interface \ForMage\Learning\Api\SizeInterface
implementando o seu método public function getSize();
Acima, acabamos de fazer um contrato de serviço. Isso nos garante que os métodos que estão em nossas interfaces estão, também, em nossas classes concretas com suas respectivas regras de negócios. Isso traz maiores garantias ao nosso código.
3.) Trabalhando no di.xml
Em nosso DI que está em ForMage/Learning/etc/frontend/di.xml
temos:
<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="ForMage\Learning\Api\SizeInterface" type="ForMage\Learning\Model\SizeGreat" /> <preference for="ForMage\Learning\Api\ColorInterface" type="ForMage\Learning\Model\ColorBlack" /> </config>
Assim, estamos falando para o Magento 2: toda vez que chamarem a interface ForMage\Learning\Api\SizeInterface
você deverá pegar os métodos dela que estarão em ForMage\Learning\Model\SizeGreat
com sua respectiva regra de negócio, o mesmo valendo de ForMage\Learning\Api\ColorInterface
para ForMage\Learning\Model\ColorBlack
4.) Alterando o Controller
Voltando ao nosso controller, fazemos as seguintes alterações:
<?php namespace ForMage\Learning\Controller\Page; use Magento\Framework\App\Action\Context; use Magento\Framework\App\ResponseInterface; use ForMage\Learning\Api\SizeInterface; use ForMage\Learning\Api\ColorInterface; class Index extends \Magento\Framework\App\Action\Action { protected $size; protected $color; public function __construct( Context $context, SizeInterface $size, ColorInterface $color ) { parent::__construct($context); $this->size = $size; $this->color = $color; } public function execute() { echo "My shirt is {$this->color->getColor()} and its size is {$this->size->getSize()}"; } }
Ao executarmos a nossa rota, teremos o seguinte resultado:
My shirt is Black and its size is Great
Ok. Estamos trabalhando com interface agora! Mas, e se eu quiser mudar a minha regra de negócio?
Para isso, criando mais duas classes concretas em nossa Model:
<?php namespace ForMage\Learning\Model; use ForMage\Learning\Api\ColorInterface; class ColorWhite implements ColorInterface { public function getColor() { return "White"; } }
<?php namespace ForMage\Learning\Model; use ForMage\Learning\Api\SizeInterface; class SizeSmall implements SizeInterface { public function getSize() { return "Small"; } }
Agora, temos duas novas classes concretas implementando as nossas interfaces e suas regras de negócios são diferentes das duas primeiras classes.
Mas, pra exibirmos as nossas novas regras de negócio teremos que efetuar alguma modificação em nosso Controller? Claro que não! Aqui está o grande benefício de se trabalhar com interfaces no Magento 2!
Para isso, acesse o seu di.xml e faça apenas uma pequena alteração nele:
<preference for="ForMage\Learning\Api\SizeInterface" type="ForMage\Learning\Model\SizeSmall" /> <preference for="ForMage\Learning\Api\ColorInterface" type="ForMage\Learning\Model\ColorWhite" />
Agora, estamos falando para o falando que ao usarem a SizeInterface, ele deverá pegar a regra que negócio de seu método que está em SizeSmall e ao usar a ColorInterface deverá pega a regra de negócio de seu método que está em ColorWhite.
Apenas essa alteração!
Acessando a nossa rota novamente temos:
My shirt is White and its size is Small.
Sim, dá mais trabalho no processo de desenvolvimento, porém facilita bastante no processo de manutenção e sustentabilidade do código, sem falar que o seu módulo estará trabalhando com as boas práticas de desenvolvimento!
Espero que esse conteúdo seja proveitoso para o seu dia-a-dia como dev Magento 2.
Para mais informações sobre Contratos de Serviços e Interfaces Públicas e API
Dúvidas? Posta aí!
Aquele abraço!