Olá, tudo bem?!

No ecossistema PHP temos disponíveis diversos projetos super bacanas, e para trabalhar na camada de banco de dados temos a nossa disposição o Doctrine, o mais popular projeto voltado para Database Storage e Object Mapping.

O que você pode estar pensando é “Meu Framework X já tem implementações para isso”, sim de fato todos os grandes frameworks de mercado tem disponível componentes para abstração de banco de dados, porem uma das grandes vantagens do Doctrine é justamente ele poder ser utilizado em praticamente qualquer framework ou estrutura de projeto.

A partir do momento que você estrutura sua aplicação para utilizar o Doctrine nada o impedirá que no futuro você migre toda a lógica para outro projeto, essa facilidade vai lhe poupar muitas dores de cabeça quando chegar o momento de refatorar sua aplicação.

Sem mais delongas, vamos analisar a estrutura que será utilizada neste post, abaixo segue estrutura de diretórios e arquivo.

  • exemplo-doctrine
    • config
      • Será armazenado toda a lógica de configuração da aplicação.
    • db
      • Responsável por armazenar o banco de dados (SQLite) e o dump que será gerado.
    • src
      • Responsável por conter toda a lógica da nossa aplicação.
    • composer.json
      • Configurações do composer para o projeto.

Conforme apresentado acima, crie a estrutura de diretórios necessários para a execução deste post.

Instalando as dependências necessárias

Abra o arquivo composer.json e insira o seguinte código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "require": {
    "doctrine/orm": "^2.5",
    "doctrine/dbal": "^2.5",
    "symfony/yaml": "^3.2"
  },
  "autoload":{
    "psr-4":{
      "DiegoBrocanelli\\":"src/"
    }
  }
}

Como podemos observar, estamos utilizando os componentes do Doctrine e uma dependência  do Symfony. Após a inserção das configurações, acesse a raiz do projeto e execute o seguinte comando pelo terminal:

1
composer install

Aguarde o processo de instalação que pode levar alguns minutos dependendo da sua conexão de internet.

Configurando o projeto

Agora devemos criar nossas configurações necessárias para utilização do componente, acesse o diretório ‘exemplo-doctrine/config’ e crie o arquivo ‘bootstrap.php’ . Neste arquivo iremos centralizar as configurações necessárias para execução do Doctrine, segue código abaixo:

 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
<?php 

// Importação do autoload do composer
require_once __DIR__.'/../vendor/autoload.php';

// Importação dos pacotes necessários
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;

// Criar uma configuração ORM do Doctrine simples "default" para utilizar Annotations
$isDevMode = true;
$configuration = Setup::createAnnotationMetadataConfiguration(
    [__DIR__.'/../src'], 
    $isDevMode
);

// Configurações do banco de dados
// Estamos utilizando o SQLite, para facilitar a reprodução do post
$connection = [
    'driver' => 'pdo_sqlite', // Vamos utilizar o drive pdo do sqlite
    'path'   => __DIR__.'/../db/db.sqlite' // caminho onde será armazenado o DB.
];

// Obtemos o Entity Manager
$entityManager = EntityManager::create($connection, $configuration);

Observação: O Doctrine suporta configurações por Annontation, XML ou YAML, porem na minha opinião as annontations é a forma mais clara e fácil de definir sua entidade, gerando o menor número de arquivos possível.

Após a criação das configurações, devemos criar um arquivo que nos auxiliará nas execuções dos comandos pelo  terminal. Crie o arquivo ‘cli-config.php_no diretório ‘exemplo-doctrine/config’ , segue código abaixo:

1
2
3
4
5
6
7
<?php 
// Importação do autoloader do composer
require __DIR__.'/bootstrap.php';

// Retorna o componente que nos auxilia na utilização do Schema tool
// Necessário para gerar Tabelas para trabalhar com metadados
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);

Criação da entidade Products

Com as devidas configurações realizadas, o próximo passo é criar a entidade que representara nossa tabela do banco de dados, para isso crie a classe ‘Products.php’ dentro do diretório ‘exemplo-doctrine/src’, segue código abaixo:

 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
<?php 

namespace DiegoBrocanelli;

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

    /**
     * @Column(type="string")
     */
    protected $name;

    /**
     * @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;
    }
}

Como podemos observar, o código acima é bem simples contendo apenas atributos e métodos getters e setters, atentando ao fato de não termos criado PHPDoc, para que assim foquemos apenas nos conceitos das annontations minimizando conflito de entendimento. É por meio dessas annontations que iremos ‘informar’ ao doctrine nossa entidade e suas especificações, segue abaixo maiores detalhes sobre as annontations utilizadas:

  • Class Products
    • @Entity
      • Responsável por informar ao Doctrine que esta nossa classe é nossa entidade.
    • @Table(name=”products”)
      • Informamos que nossa entidade representa a tabela ‘products’< contida no banco de dados.
  • Column id
    • @Id
      • Informa que essa coluna é primary key.
    • @Column(type=”integer”)
      • efine o tipo “inteiro” para a coluna.
    • @GeneratedValue
      • Informa o desejo de auto increment< para os valores dessa coluna.
  • Column name
    • @Column(type=”string”)
      • Define o tipo ‘string’ para a coluna.
  • Column created
    • @Column(type=”datetime”)
      • Define o tipo Datetime< para a coluna.
      • Observação: Assim que implementarmos a pesquisa ficará mais claro a estrutura do objeto gerado pelo Doctrine.

Após nossa entidade devidamente criada, podemos dar início a implementação dos nosso recursos.

Criando nosso banco de dados

Após a criação da entidade devemos realizar a criação do banco de dados, acesse a raiz do projeto e execute o seguinte comando:

1
vendor\bin\doctrine orm:schema-tool:create
  • vendor\bin\doctrine
    • Esta parcela do comando referencia a utilização do Doctrine que instalamos com o Composer.
  • orm:schema-tool:creat
    • Comando para criação do banco de dados, lembrando que o arquivo será armazenado em ‘exemplo-doctrine/db/db.sqlite’<.

Após execução do comando o retorno deve ser semelhante ao apresentado abaixo:

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

Creating database schema...
Database schema created successfully!

Como o próprio retorno nos informa, nunca devemos utilizar esta ação em produção, apenas em ambiente de desenvolvimento!

Inserindo registros no banco de dados

Para a implementação do recurso de insert,  crie a classe _‘CreateProduct.php’ dentro do diretório _‘exemplo-doctrine/src’, segue código abaixo:

 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
<?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
  $newProductName = $argv[1];

  // Instanciamos nossa entidade Products
  $product = new DiegoBrocanelli\Product();

  // 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')));

  // 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";
}

Para realizarmos a inserção de dados, execute o seguinte comando pelo terminal na raiz do projeto:

1
php src\CreateProduct.php produto_1

Resultado:

1
Created Product with ID 1

Como podemos observar, com poucas linhas de código criamos um recurso simples para inserção de registro no banco de dados. 🙂

Pesquisando todos os registros cadastrados

Para a implementação do recurso de select,  crie a classe _‘ListProduct.php’ dentro do diretório _‘exemplo-doctrine/src’, segue código abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?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\Product');
  
  // 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) {
      // Exibimos o resultado de cada registro encontrado
      var_dump($product);
  }
}

Exemplo de retorno de pesquisa:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class DiegoBrocanelli\Product#64 (3) {
  protected $id =>
  int(1)
  protected $name =>
  string(8) "'produto_1"
  protected $created =>
  class DateTime#61 (3) {
    public $date =>
    string(26) "2017-05-16 23:53:49.000000"
    public $timezone_type =>
    int(3)
    public $timezone =>
    string(17) "America/Sao_Paulo"
  }
}

Como podemos observar, com apenas a chamada do método findAll() obtemos todos os registros inseridos em nossa tabela no banco de dados, o atributo ‘created’ é um objeto do tipo DateTime conforme configurado na entidade.

Pesquisando um produto em específico

Para a implementação do recurso que irá pesquisar um produto em especifico crie a classe ‘ShowProduct.php’ dentro do diretório‘exemplo-doctrine/src’, segue código abaixo:

 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
<?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)) {
  // Recebemos o id informado
  $id = (int)$argv[1];
  
  // Nosso entity manager nos fornece acesso ao método find()
  // no qual nos permite informar nossa entidade e o id para pesquisa
  $product = $entityManager->find('DiegoBrocanelli\Product', $id);

  // Caso não seja encontrado nenhum produto com o id desejado
  // será retornado mensagem informando o usuário
  if($product === null){
    echo "No product found. \n";
    exit(1);
  }

  // Caso o produto seja encontrado exibimos seu nome e data de criação
  echo sprintf('-%s\n', $product->getName() . ' - ' . $product->getCreated());
}

Como podemos observar, com o método find() podemos informar nossa entidade e o id do registro ao qual desejamos encontra na tabela.

Atualizar produtos

Para a implementação do recurso de update< crie a classe ‘UpdateProduct.php’ dentro do diretório <‘exemplo-doctrine/src’, segue código abaixo:

 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 

// 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)) {
  // Recebemos o id do registro a ser atualizado
  $id      = $argv[1];
  // Recebemos o novo nome do produto
  $newName = $argv[2];

  // Pesquisamos para valizar existência do produto no banco de dados
  $product = $entityManager->find('DiegoBrocanelli\Product', $id);

  // Caso produto não seja localizado, será retornado mensagem infromando o usuário
  if ($product === null) {
    echo "Product $id does not exist.\n";
    exit(1);
  }

  // Informamos o nome atual
  echo 'Old name: ' . $product->getName() ."\n";

  // Inserimos o novo nome desejado para o produto
  $product->setName($newName);

  // Executamos a ação de update
  $entityManager->flush();

  // Retornamos para o usuário o produto com seu novo nome
  echo 'New name: ' . $product->getName() ."\n";
}

Como podemos observar, o processo de update ocorre sem mistérios de forma clara e fácil apenas manipulando o objeto e passando para que o Doctrine< faça todo o trabalho.

Remover produtos

Para a implementação do recurso de _delete <_crie a classe ‘DeleteProduct.php’ dentro do diretório <‘exemplo-doctrine/src’, segue código abaixo:

 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
<?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)) {
  // Recebemos o id do registro a ser atualizado
  $id = $argv[1];

  // Pesquisamos para valizar existência do produto no banco de dados
  $product = $entityManager->find('DiegoBrocanelli\Product', $id);

  // Caso produto não seja localizado, será retornado mensagem infromando o usuário
  if ($product === null) {
    echo "Product $id does not exist.\n";
    exit(1);
  }

  // Executamos a ação de remoção
  $entityManager->remove($product);

  // Efetiva a ação de remoção
  $entityManager->flush();

  // Retorna mensagem informativa de ação realizada com sucesso para o usuário
  echo 'Product remove successfully!';
}

Como podemos observar, o processo de remoção é similar a todos os demais processo, o Doctrine tem métodos descritivos que facilitam sua utilização 🙂

Remover o banco de dados (Drop database)

No dia a dia temos a necessidade de apagar nosso banco de dados, para isso o Doctrine< disponibiliza um recurso para nos auxiliar. Na raiz do nosso projeto execute o seguinte comando:

Execute este comando com parcimônia, após sua execução seu banco de dados será APAGADO!

1
2
// Comando para APAGAR o banco de dados
vendor\bin\doctrine orm:schema:drop --force

Após execução do comando será exibido uma mensagem de retorno semelhante a descrita abaixo:

1
2
Dropping database schema...
Database schema dropped successfully!

Gerar dump da nossa base de dados

Um dos inúmeros recursos bacanas que o D_octrine<_ dispõem é a criação de um arquivo dump< da base de dados,  arquivo que conterá toda estrutura da tabela products<, para geramos nosso arquivo execute o seguinte comando na raiz do projeto:

1
2
// Comando para geração de arquivo de dump da base de dados
vendor\bin\doctrine orm:schema-tool:create --dump-sql > db/dump.sql

Após execução com sucesso do comando, será criado nosso arquivo no diretório ‘exemplo-doctrine/db/dump.sql’<, contendo a seguinte estrutura:

1
2
3
4
5
CREATE TABLE products (
  id INTEGER NOT NULL, 
  name VARCHAR(255) NOT NULL, 
  created DATETIME NOT NULL, PRIMARY KEY(id)
);

Observação: O retorno se dá um uma única linha, porém para que fique melhor sua visualização no post foi quebrado em linhas.

No decorrer deste post podemos observar o quão fácil é a utilização do Doctrine, infelizmente apenas abordamos uma pequena fração de todo o potencial e recursos que ele nos fornece. Caso tenha interesse em seguir com seus estudos indico que adquira o livro escrito pelo Elton MinnetoDoctrine na prática” antes que alguém pense o contrário não estou ganhando nenhum centavo para indicar o livro, estou fazendo pois é o melhor livro de Doctrine< em PT-BR escrito por um profissional que respeito e admiro.

Espero que tenham apreciado o post, ficou um pouco maior do que eu desejava, porem para abordar todo o ciclo de utilização tentei ser o mais descritivo possível para auxiliar a máximo a compreensão das etapas, como dito anteriormente este post é introdutório onde existem uma infinidade de recursos não explorados que valem muito a pena serem estudados.

Duvidas, sugestões, crítica ou elogios recomendo que deixe nos comentários para que assim possamos interagir e gerar mais conhecimento.

Até a próxima, abraços! 🙂