Relacionamento de entidade com Doctrine é um recurso poderoso e que facilita e muito nosso trabalho com a manipulação de entidades.

Este post é uma continuação do Aprenda a construir um CRUD simples, fácil e rápido com Doctrine onde sua leitura é fundamental, pois é a base para este post.

Em nosso projeto temos todo o CRUD de produtos implementado, porem é natural que um produto seje ligado a  uma categoria.

Para que possamos construir esse vinculo “relacionamento” devemos criar a entidade Category em “src\Category.php“.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php 

namespace DiegoBrocanelli;

/**
 * @Entity @Table(name="categorys")
 */
class Category
{
    /**
     * @Id @Column(type="integer") @GeneratedValue
     */
    protected $id;
 
    /**
     * @Column(type="string")
     */
    protected $name;

    public function getId()
    {
        return $this->id;
    }
 
    public function getName()
    {
        return $this->name;
    }
 
    public function setName($name)
    {
        $this->name = $name;
    }
}

Vamos descrever em detalhes a implementação utilizada na entidade.

  • class Category
    • @Entity
      • Responsável por informar ao doctrine que a classe é uma entidade.
    •  @Table(name=”categorys”)
      • Informamos que a entidade representa a tabela ‘categorys’ no banco de dados.

Gerando o banco de dados

Após a criação da entidade, devemos executar o seguinte comando no terminal para a criação do banco de dados:

1
vendor\bin\doctrine orm:schema-tool:create

Como retorno, será exibido uma mensagem similar ao demonstrado abaixo:

1
2
3
4
ATTENTION: This operation should not be executed in a production environment.

Creating database schema...
Database schema created successfully!

Cadastrando categorias

Para que seja possível vincular um produto a uma categoria, antes devemos cadastrar no banco de dados as categorias que desejamos.

Segue abaixo a estrutura que será utilizada para persistir as informações no banco de dados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

// Importamos o autoload do composer
require_once __DIR__.'/../config/bootstrap.php';
 
// Como vamos trabalhar com envio de dados passados pelo terminal
// apenas garanto que haja valor para que seja processado a ação.
if (isset($argv)) {
    // coletamos o nome passado pelo usuário no terminal
    $newCategoryName = $argv[1];
 
    // Instanciamos nossa entidade categorys
    $category = new DiegoBrocanelli\Category();
 
    // Passamos o novo nome para a entidade
    $category->setName($newCategoryName);
 
    // Persistimos seus dados
    $entityManager->persist($category);
 
    // Descarregamos a ação
    $entityManager->flush();
    
    // Para melhor visualização do resultado, retornamos uma mensagem 
    // com o id do registro salvo no DB. 
    echo 'Created category with ID '.$category->getId()."\n";
}

Como podemos observar, a forma como foi construído o processo é simples e bem descritiva.

Primeiro recebemos o nome desejado, para que em seguida seja instanciado nossa Entidade Category, informamos os dados e inserimos no banco de dados.

Para podemos validar o código, vamos cadastrar nossa primeira categoria. Para tal ação basta inserir o seguinte comando no terminal:

1
php src\CreateCategory.php livros

Como resultado teremos nossa primeira categoria cadastrada!

1
Created category with ID 1

Alterando a entidade produtos para receber uma categoria

Criamos a entidade Category, cadastramos nossa primeira categoria, porem agora devemos criar o vinculo ao qual desejamos entre Produto e Categoria.

Abra a entidade Products em “src\Products.php” e vamos implementar nossas alterações desejadas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php 
 
namespace DiegoBrocanelli;
 
/**
 * @Entity @Table(name="products")
 */
class Products
{
    /**
     * @Id @Column(type="integer") @GeneratedValue
     */
    protected $id;
 
    /**
     * @Column(type="string")
     */
    protected $name;

    /**
     * @ManyToOne(targetEntity="Category")
     */
    protected $category;
 
    /**
     * @Column(type="datetime")
     */
    protected $created;
 
    public function getId()
    {
        return $this->id;
    }
 
    public function getName()
    {
        return $this->name;
    }
 
    public function setName($name)
    {
        $this->name = $name;
    }
 
    public function setCreated(\DateTime $created)
    {
        $this->created = $created;
    }
 
    public function getCreated()
    {
        return $this->created;
    }

    public function setCategory($category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }
}

As novidades que encontramos em nossa entidade é o atributo category e os métodos setCategory() e getCategory(), vamos analisar suas implementações:

  • private $category;
    • @ManyToOne(targetEntity=”Category”)
      • Com essa annontation, informamos ao Doctrine que desejamos criar um relacionamento muitos para um, vinculando a entidade Category.
  • public function setCategory($category)
    • Método responsável por inserir a categoria na entidade Products.
  • public function getCategory()
    • Método responsável por retornar a categoria do produto.

Após as devidas implementações finalizadas, devemos implementar as novas estruturas no processo de cadastro de produto “Insert“, para isso abra o arquivo CreateProduct.php em “scr/CreateProduct.php” e implemente as seguintes modificações.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
// Importamos o autoload do composer
require_once __DIR__.'/../config/bootstrap.php';
 
// Como vamos trabalhar com envio de dados passados pelo terminal
// apenas garanto que haja valor para que seja processado a ação.
if(isset($argv)){
    // Coletamos o nome do produto
    $newProductName = $argv[1];
    // Coletamos o id da categoria
    $categoryId     = $argv[2];

    // Instanciamos nossa entidade Products
    $product = new DiegoBrocanelli\Products();
 
    // Passamos o novo nome para a entidade
    $product->setName($newProductName);
 
    // Passamos a data de criação para a aentidade
    $product->setCreated(new \DateTime(date('Y-m-d H:i:s')));

    // Realizamos a pesquisa da categoria desejada.
    $category = $entityManager->find('DiegoBrocanelli\Category', $categoryId);

    // Verificamos se a categoria desejada existe no banco de dados
    if(is_null($category)){
        echo 'Categoria desejada não encontrada!';
        die;
    }

    // Passamos o objeto Category para nosso produto.
    $product->setCategory($category);
 
    // Persistimos seus dados
    $entityManager->persist($product);
 
    // Descarregamos a ação
    $entityManager->flush();
    
    // Para melhor visualização do resultado, retornamos uma mensagem 
    // com o id do registro salvo no DB. 
    echo 'Created Product with ID '.$product->getId()."\n";
}

A base da estrutura de insert de produtos continua a mesma, o diferencial encontra-se em receber o id da categoria desejada, pesquisar seus dados no banco de dados e caso seja encontrado inserimos no objeto Products para seu devido cadastro no banco.

Vamos cadastrar nosso primeiro produto, para isso insira o seguinte comando no terminal:

1
 php src\CreateProduct.php PHP_OO 1

Como retorno teremos nosso primeiro produto cadastrado e vinculado a uma categoria 🙂

1
Created Product with ID 1

Caso seja realizado a tentativa de cadastro com um categoria inexistente, será retornado a mensagem:

1
Categoria desejada não encontrada!

Viram como foi simples as implementações para criarmos uma estrutura de entidades interligadas e realizar os cadastro de ambos 🙂

Pesquisando produtos

Podemos realizar a pesquisa de produtos cadastrados, e para nossa felicidade não temos que mudar em nada a estrutura contida no arquivo ListProduct.php encontrado em “src/ListProduct.php” pois a estrutura de pesquisa permanece a mesma, porem vamos apenas alterar a forma de exibição dos dados no terminal, para melhor exibição dos dados.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
// Importamos o autoload do composer
require_once __DIR__.'/../config/bootstrap.php';
 
// Como vamos trabalhar com envio de dados passados pelo terminal
// apenas garanto que haja valor para que seja processado a ação.
if (isset($entityManager)) {
  //Importamos o repository que nos auxiliará com a pesquisa.
  $productRepository = $entityManager->getRepository('DiegoBrocanelli\Products');
  
  // Podemos acessar o método findAll() responsável por retornar todos os 
  // registros cadastrados em nossa tabela products
  $products = $productRepository->findAll();

  // Realizamos uma iteração de dados
  foreach ($products as $product) {
      var_dump(get_class($product->getCategory()));die;
      // Exibimos o resultado de cada registro encontrado
      echo 'Nome do Produto: ' . $product->getName()                . "\n";
      echo 'Categoria: '       . $product->getCategory()->getName() . "\n";
      echo "+-------------------------------------------------------------+ \n";
  }
}

Como podemos observar, o objeto product acessa seu método getCategory() que por sua vez tem acesso ao objeto que fornece acesso ao método getName() implementado na classe Category.

O Doctrine retorna uma classe Proxy que nos fornece acesso a classe Category,  a partir disso o atributo fornece acesso a qualquer método implementado em category, respeitando_ assim a estrutura OO de nossas classes._

O exemplo descrito no post é simples, pois tem o intuito de ser direto e produtivo, o Dotrine tem um leque de opções para trabalhar com os diversos tipos de relacionamentos de entidade.

Espero que tenha apreciado o conteúdo e que também agregue valor aos seus projetos, lembrando que uma boa arquitetura da camada de modelo construída com Doctrine tonar mais simples sua manutenção, e caso encontre necessário o porte para outros frameworks ou estruturas, tudo será mais simples e com menos dores de cabeça e acredite quando eu lhe digo que você apreciará de ter esse fardo tirado da suas costas.

Dicas, sugestões, criticas ou elogios recomento que deixem nos comentários, para que assim possamos compartilhar ainda mais conhecimento.

Grande abraço e até a próxima! 🙂