Doctrine – Relação muitos para muitos no Zend Framework 3

Vou demonstrar através de exemplos práticos um relacionamento ManyToMany ou em nosso querido português “muitos para muitos” no Doctrine com Zend Framework 3+
Este tutorial foi resultado de estudos referente a ferramenta, portanto serei breve e sem muitos detalhes já que não possuo conhecimento avançado sobre a ferramenta.

Primeiro vamos gerar nossas entidades:
Veiculo

<?php

namespace MODVeiculo\Entity;

use Doctrine\ORM\Mapping as ORM;
use Zend\Hydrator;

/**
 * ModveiculoVeiculos
 *
 * @ORM\Table(name="modveiculo_veiculos")
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 * @ORM\Entity(repositoryClass="MODVeiculo\Entity\VeiculoRepository")
 */
class Veiculo
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;   
    
    /**
     * @var string
     *
     * @ORM\Column(name="identificacao_placa", type="string", length=8, nullable=true)
     */
    private $identificacaoPlaca;
}
?>

Imei – É a identificação única de rastreadores, nesse caso usaremos esse tipo de relação pois o mesmo rastreador pode fazer parte de mais veículos dentro de uma frota, é claro que não ao mesmo tempo, mas vamos supor que um cliente adquiriu este serviço e depois de um tempo deixou de utilizar, obviamente você não jogará o rastreador fora, muito pelo contrário você vai aproveitar e utilizar ele em outro veículo, mas se você precisa manter o histórico de rastreamento para o cliente (antigo) e os novos dados para o novo cliente, você precisa relacionar esse imei com mais de um veículo. Outro caso prático seria o rastreador de um determinado veículo queimar e você precisar trocar esse rastreador, quando você for realizar a consulta em seu banco de dados través do novo imei os dados do imei antigo não serão exibidos, ou seja, o histórico de rastreio iria zerar, dessa forma fazemos com que ele busque os dados de todos os rastreadores que já estiveram ativos neste veículo.

<?php

namespace MODImei\Entity;

use MODVeiculo\Entity\Veiculo;

use Doctrine\ORM\Mapping as ORM;
use Zend\Hydrator;

/**
 * Imei
 *
 * @ORM\Table(name="modimei_imei")
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 * @ORM\Entity(repositoryClass="MODImei\Entity\ImeiRepository")
 */
class Imei
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="numero", type="string", length=45, nullable=true)
     */
    private $numero;
}
?>

Agora nossa tabela de relacionamento (Você não vai criar entidade para ela)

Os super-entendedores já identificaram que existe um erro gravíssimo nessa tabela, que no final do post vou explicar do que se trata, aos olhos dos mortais vai passar desapercebido. Na verdade são dois erros, o segundo mais comum.

Antes de continuar preciso avisar alguns detalhes caso você esteja totalmente por fora, no caso do Doctrine se você esta fazendo apenas um relacionamento muitos para muitos você não precisa criar uma nova entidade para fazer este relacionamento, ele ocorrerá de forma totalmente automática, bastando informar o nome da tabela que relaciona as entidades.

Primeiramente você vai precisar determinar qual entidade é a proprietária e qual é o lado inverso da história, até ai tudo bem, mas como determinar quem é proprietário? Nos tutoriais abobalhados que você encontra por ai sempre verá os exemplos com Artigos e Categorias, neste caso é obvio identificar quem vai ser o proprietário, vai ser Artigos certo? Mas na pratica nem sempre é assim, você acaba encontrando algumas situações complicadas, por exemplo, neste caso de Veículos e Imeis os dois tem o mesmo grau de importância, pois o rastreador no sistema é uma entidade totalmente independente e importante, se não fosse o rastreador você jamais identificaria um veículo.

Mas ainda sim eu escolhi o veículo para ser proprietário, pois por mais que o Imei (rastreador) seja mais importante eu acabo realizando mais consultas nos veículos para apresentação dentro do sistema, portanto isso poderia me ajudar a economizar as consultas já que trazendo os veículos eu já traria os rastreadores que neles se encontram, os super-entendedores podem estar me julgando neste momento, mas até então essa filosofia e linha de raciocínio tem me ajudado e não me prejudicado.

Portanto vamos deixar de falatório e montar nossa entidade proprietária, vamos na entitudade Veiculo e adicionar mais uma propriedade.

Relacionamento muito para muitos bidirecional

Veiculo

// ... entity Veiculo

    /**
     * Many Users have Many Groups.
     * @ManyToMany(targetEntity="MODImei\Entity\Imei", inversedBy="veiculos")
     * @JoinTable(name="modimei_imei_veiculo")
     */
     private $imeis;

    public function __construct(array $options = array()) {
        $this->imeis = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function setImei(\MODImei\Entity\Imei $imeis){
        $imeis->setVeiculos($this);
        $this->imeis->add($imeis);
    }

// ...

Imei – Como estamos falando de um relacionamento bidirecional precisamos também mexer em nossa tabela de imei, se não nem precisaríamos.

    /**
     * Many Imeis have Many Users.
     * @ORM\ManyToMany(targetEntity="MODVeiculo\Entity\Veiculo", mappedBy="imeis")
     */
    private $veiculos;

    public function __construct(array $options = array())
    {
        $this->veiculos = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function setVeiculos(\MODVeiculo\Entity\Veiculo $veiculos) {
        $this->veiculos->add($veiculos);
        return $this;
    }

Como sou apressadinho eu já quero sair inserindo dados nessas tabelas, no meu caso estou utilizando de Fixtures (Dados de demonstração) que você pode gerar com algumas ferramentas interessantes, dentre elas para o Zend Framework 3+ o dkorsak, falo a respeito dele neste artigo:
Trabalhando com Data Fixture no Zend Framework 3

Então vamos a minha Fixture:

<?php

namespace MODVeiculo\Fixture;

use Doctrine\Common\DataFixtures\AbstractFixture;
use \Doctrine\Common\Persistence\ObjectManager;
use \Doctrine\Common\DataFixtures\OrderedFixtureInterface;

use MODVeiculo\Entity\Veiculo;

class LoadVeiculo extends AbstractFixture implements OrderedFixtureInterface{
    
    public function getOrder() {
        return 9;
    }

    public function load(ObjectManager $manager) {
        
        //1        
        $idEmpresa = $manager->getReference(\MODEmpresa\Entity\Empresa::class, 1);
        $idCliente = $manager->getReference('MODCliente\Entity\Cliente', 4);
        $idTipoVeiculo = $manager->getReference(\MODVeiculo\Entity\TipoVeiculo::class, 1);
        $imei = $manager->getReference(\MODImei\Entity\Imei::class, 1); // busco o imei
                
        $veiculo = new Veiculo();
        $veiculo->setIdCliente($idCliente)
                ->setIdEmpresa($idEmpresa)
                ->setIdTipoVeiculo($idTipoVeiculo)
                ->setTelefone('1496969696') 
                ->setDescricao('BLA BLA BLA LEGAL TIO')
                ->setIdentificacaoPlaca('CCC3333')
                ->setMonitorado(true)
                //->setImei('111222333444666')
                ->setIcone('carrinho');

        $veiculo->setImei($imei); // <- olha aqui viado
        
        $manager->persist($veiculo);
    }
}
?>

Esqueçam os demais relacionamentos e se atentem aos imeis ok? Bom, ao tentar inserir os dados foi me retornado o seguinte erro:

Como podemos notar ele tentou gravar em nossa tabela de relacionamentos, mas não conseguiu, pois ele tentou gravar os campos veiculo_id e imei_id onde ele assumiu por padrão que o nome das colunas de relacionamento seriam estes, mas não! Nossos campos são id_veiculo e id_imei. Este era o erro mais comum que havia citado anteriormente portanto você acaba de adquirir mais um conhecimento a respeito destes relacionamentos, se você não seguir o padrão pré-estabelecido pelo Doctrine você terá que informar os campos de relacionamento, portanto vamos alterar nossa entidade Veiculo

Veiculo

// ... entity Veiculo

    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\ManyToMany(targetEntity="MODImei\Entity\Imei", inversedBy="veiculos")
     * @ORM\JoinTable(name="modimei_imei_veiculo",
     *  joinColumns={@ORM\JoinColumn(name="id_veiculo", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="id_imei", referencedColumnName="id")})
     */
     private $imeis;

    public function __construct(array $options = array()) {
        $this->imeis = new \Doctrine\Common\Collections\ArrayCollection();
    }

    public function setImei(\MODImei\Entity\Imei $imeis){
        $imeis->setVeiculos($this);
        $this->imeis->add($imeis);
    }

// ...

Pronto, agora nós já temos os dados sendo inseridos no banco de dados, mas lembra que eu informei que os super gênios já sabiam que havia um erro grave nesse relacionamento? Pois então geralmente nossa tendência é sempre armazenar mais dados na tabela, por exemplo, eu queria adicionar a data de transferência daquele IMEI (rastreador) para aquele determinado veículo, mas ao fazer isso já deixou de ser um relacionamento muitos para muitos, pois é! Ao ficar muito preocupado de como faria esse relacionamento no Doctrine com Zend Framework deixei passar esse erro bobo a respeito de relacionamentos em banco de dados. No caso se você quiser adicionar mais informações ele deixa de ser um relacionamento muitos para muitos e passa ser uma nova entidade!

Então pelo menos nesse meu caso é o que vai acontecer, esse relacionamento de muitos para muitos deixará de existir para surgir uma nova entidade. Ainda sim eu achei os estudos a respeito dos relacionamentos no Doctrine 100% aproveitosos e resolvi compartilhar com a Web e também para ter um repositório particular.

Comments

  1. By Cosme

    Responder

    • By adriano

      Responder

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *