Zend Form – Injeção de dependências

No post sobre Zend Form eu dei um panorama geral da utilização do componente, mas alguns detalhes tiveram de ser separados em conteúdos à parte. Este é o caso da injeção de dependência no form.

O que é injeção de dependência?

É uma forma de encapsular uma funcionalidade ou comportamento. Pensemos no seguinte exemplo: cadastro de um novo usuário com a possibilidade de seleção do seu papel. Desde que não estejam definidos de forma estática, os papéis (roles) custam uma consulta no banco de dados, no entanto o form não deve ter conhecimento do  mesmo. Podemos fazer isso de duas formas.

Injetando o EntityManager (ou semelhante)

Injetar a classe responsável por recuperar informações no banco de dados é uma saída óbvia. O construtor do Form recebe sempre 2 parâmetros, o $name e $options com isso podemos injetar nossa dependência em $options. Veja o exemplo a seguir:

# No form
namespace Application\Form;

use Doctrine\ORM\EntityManager;
use User\Entity\Role; // supondo que esta entidade existe
use Zend\Form\Element\Select;
use Zend\Form\Form;

class UserForm extends Form
{
    public function __construct($name = null, array $options = [])
    {
        // lançando excessão para caso o entityManager não esteja presente
        if (! isset($options['entityManager'])) {
            throw new \Exception('É preciso passar o EntityManager');
        }

        parent::__construct($name, $options);

        /** @var EntityManager $entityManager */
        $entityManager = $options['entityManager'];

        // recupera todos os roles do banco de dados
        $roleCollection = $entityManager->getRepository(Role::class)->findAll();

        // inicia o array para as opções do select
        $roles = [];

        foreach ($roleCollection as $role) {
            // adiciona cada um dos roles para o select
            $roles[$role->getId()] = $role->getName();
        }

        $this->add([
            'name' => 'role',
            'type' => Select::class,
            'options' => [
                'label' => 'Role',
                // e por fim define os roles no elemento do form
                'value_options' => $roles,
                'empty_option' => 'Select a role'
            ],
            'attributes' => [
                'class' => 'form-control ',
                'id' => 'role'
            ]
        ]);
    }
}

# No controller

$form = new \Application\Form\UserForm('user', $entityManager);

Perceba que espera-se o EntityManager para que possamos realizar a consulta para trazer todos os roles registrados no banco de dados. Porém, apesar de termos resolvido a dependência, não foi uma solução elegante, além de tudo, perigosa. Isso porque nossa classe de form tem conhecimento de algo que não deveria. Nela pode ser utilizado o EntityManager como quiser! Podem ser localizados dados no banco, adicionados, editados e até removidos. E é com base nisso que temos a segunda e melhor implementação da injeção de dependência.

Injetando somente A dependência

Como já disse, não é necessário a classe do form conhecer o entityManager, ele pode simplesmente receber o que precisa, um array com os roles.

Form

namespace Application\Form;

use Zend\Form\Element\Select;
use Zend\Form\Form;

class UserForm extends Form
{
    public function __construct($name = null, array $options = [])
    {
        if (! isset($options['roles'])) {
            throw new \Exception('É preciso passar a lista de roles');
        }

        parent::__construct($name, $options);

        $this->add([
            'name' => 'role',
            'type' => Select::class,
            'options' => [
                'label' => 'Role',
                'value_options' => $options['roles'],
                'empty_option' => 'Select a role'
            ],
            'attributes' => [
                'class' => 'form-control ',
                'id' => 'role'
            ]
        ]);
    }
}

Module.php

Registramos um novo serviço para o form.

# module/Application/src/Module.php
public function getServiceConfig()
{
    return [
        'factories' => [
            //...
            \Application\Form\UserForm::class => function (ServiceManager $serviceManager) {
                $entityManager = $serviceManager->get(\Doctrine\ORM\EntityManager::class);
                $roles = [];
                $rolesCollection = $entityManager->getRepository(\User\Form\Role::class)->findAll();

                foreach ($rolesCollection as $role) {
                    $roles[$role->getId()] = $role->getName();
                }

                return new \Application\Form\UserForm('user', ['roles' => $roles]);
            }
        ]
    ];
}

Controller

Agora basta chamar o serviço e pronto.

$form = $this->getServiceManager()->get(\Application\Form\UserForm::class);

Simples não?

Concluindo

Apesar de uma forma elegante de resolver problemas comuns e possibilitar a testabilidade de nosso software, a injeção de depedências deve ser utilizada com sabedoria. É muito fácil injetar erroneamente um serviço todo sendo que somente o que precisa é de um determinado resultado. O maior problema disso é fazer com que a classe que recebeu uma dependência tenha conhecimento demais.

Menu