Dev (Back & Front)ARTIGO

Duas maneiras de mapear um sistema do zero usando Doctrine2

Neste artigo, vou explanar mais detalhadamente dois métodos de realizar isto elaborando um projeto simples, na prática.

Como vimos neste artigo, é possível obter classes de entidades existentes em nosso banco de dados com o Doctrine. Neste artigo, vou explanar mais detalhadamente dois métodos de realizar isto elaborando um projeto simples, na prática. O primeiro é via cógido PHP; o outro é através de linha de comando.

Iremos utilizar a seguinte hierarquia de pasta:

eder01

Na hierarquia acima, criei a pasta src, que conterá nosso código de desenvolvimento com as classes do projeto organizadas em subpastas. A subpasta Entity terá, como o nome sugere, as entidades de fato. A pasta vendor conterá as bibliotecas PHP que vamos utilizar ao longo do desenvolvimento.

O Composer automaticamente cria esta pasta, porém criei para modos didático afim de organizar nosso código.

Agora, através do composer vamos obter as bibliotecas que precisamos. Crie o arquivo composer.json na raiz do nosso projeto, conforme abaixo:

Caso precise, veja detalhes da instalação do composer aqui:

{
 "require": {
   "doctrine/orm": "2.4.*",
   "symfony/yaml": "2.*"
 },
 "autoload": {
   "psr-4": {
     "App\\": "src"
   }
 }
}

O arquivo tem o doctrine/orm e o symfony/yaml como bibliotecas requeridas, e no autoloader utilizamos a especificação PSR-4, tendo em vista que este padrão nos dá flexibilidade de criar o namespace ‘App’ dentro da pasta ‘src’, algo que não seria possível com o PSR-0. Caso queira mais informações sobre especificação PSR-4, clique aqui.

Coloquei o Symfony/Yaml como dependência porque o Doctrine sugere tal biblioteca e precisaremos dela em nosso projeto para exportar as entidades no formato yml.

No terminal rodamos o composer:

eder02

E na pasta vendo estarão nossas bibliotecas e o autoloader:

eder03

Agora já temos nossa hierarquia de pasta, a definição de nosso autoloader e a biblioteca Doctrine devidamente configurada. Vamos ver nosso banco de dados.

Sistema de cadastro de atletas

Digamos que faremos um sistema de cadastro de atletas e eles podem estar em uma ou mais modalidades, ter um ou mais e-mails e telefones. Criei o banco de dados em MySQL e modelei utilizando o Workbench, que é open source e pode ser encontrado aqui.

Aqui é um dos passos de grande importância no desenvolvimento de software. Uma modelagem de banco de dado mal elaborada acarreta diversos problemas no decorrer do desenvolvimento e um reparo de erro de modelagem pode custar caro se não for identificado antes do desenvolvimento de fato.

Veja nossa modelagem feita:

eder04

Veja que do relacionamento entre atleta e modalidade surgiu uma tabela cuja a finalidade é apenas guardar o id de cada entidade, afim de definir um vínculo de uma com a outra. Qual o motivo pelo qual não fizemos o mesmo com o e-mail e com o telefone? Porque se trata de um relacionamento “N pra N”: um atleta pode ter muitas modalidades e uma modalidade pode ter muitos atletas. Com o telefone é diferente. Um atleta pode ter muitos telefones, mas um telefone pertence a apenas um atleta; a mesma coisa com o e-mail. O objetivo não é aprofundar na normalização de banco de dados mas fiz questão de ressaltar bem esse passo, pois o considero muito importante.

Segue o código de nosso modelo:

CREATE SCHEMA IF NOT EXISTS `imasters` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `imasters` ;

CREATE TABLE IF NOT EXISTS `imasters`.`atleta` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `nome` VARCHAR(80) NOT NULL,
  `cpf` CHAR(11) NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `imasters`.`modalidade` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `nome` VARCHAR(60) NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `imasters`.`telefone` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `atleta_id` INT NOT NULL,
  `numero` INT(9) NOT NULL,
  `ddd` INT(3) NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_telefone_atleta1_idx` (`atleta_id` ASC),
  CONSTRAINT `fk_telefone_atleta1`
    FOREIGN KEY (`atleta_id`)
    REFERENCES `imasters`.`atleta` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `imasters`.`email` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `atleta_id` INT NOT NULL,
  `email` VARCHAR(50) NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_email_atleta1_idx` (`atleta_id` ASC),
  CONSTRAINT `fk_email_atleta1`
    FOREIGN KEY (`atleta_id`)
    REFERENCES `imasters`.`atleta` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS `imasters`.`atleta_modalidade` (
  `atleta_id` INT NOT NULL,
  `modalidade_id` INT NOT NULL,
  PRIMARY KEY (`atleta_id`, `modalidade_id`),
  INDEX `fk_atleta_has_modalidade_modalidade1_idx` (`modalidade_id` ASC),
  INDEX `fk_atleta_has_modalidade_atleta_idx` (`atleta_id` ASC),
  CONSTRAINT `fk_atleta_has_modalidade_atleta`
    FOREIGN KEY (`atleta_id`)
    REFERENCES `imasters`.`atleta` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_atleta_has_modalidade_modalidade1`
    FOREIGN KEY (`modalidade_id`)
    REFERENCES `imasters`.`modalidade` (`id`)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

Engenharia reversa

Com o banco de dados feito, agora podemos criar nossas entidades. Vamos utilizar o Doctrine para exportar do banco de dados cada tabela (ou as mais importantes) e transformá-las em classes para que possamos trabalhar.

Vamos criar na raiz no nosso projeto o arquivo bootstrap.php. Ele é responsável em criar o uma instância EntityManager, que irá gerenciar as ações das entidades.

<?php
require_once "vendor/autoload.php";
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
$paths = array("/src");
$isDevMode = true;
$dbParams = array(
   'dbname' => 'imasters',
   'user' => 'root',
   'password' => 'root',
   'host' => '127.0.0.1',
   'driver' => 'pdo_mysql',
);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);

Este é um arquivo padrão no Doctrine, onde eu basicamente informo em qual path está o código do meu projeto, qual os dados do banco que irei trabalhar e qual ambiente que estou desenvolvendo.

O Doctrine permite quatro tipo de exportações de mapeamento: XML, YML, PHP e Annotation. Todos com suas devidas características trazem o mapeamento do banco transcrevendo-o em arquivos para serem utilizados.

Há uma maneira de exportar com PHP, chamando o arquivo .php pelo browser ou pelo console, porém o Doctrine tem uma ferramenta que permite, através de linha de comando, realizar o mesmo procedimento, dentre outros.

Para tal ferramenta funcionar, é requerido o arquivo cli-config.php. Nele conterá o HelperSet, com informações que já configuramos no arquivo bootstrap.php.

Vejamos abaixo as duas formas:

O arquivo export.php (exportando via código)

<?php
// Requerimos o arquivo bootstrap.php com as configurações de banco de dados e retornando o EntityManager
require_once 'bootstrap.php';

/*
* Estamos agora recuperando os metadados com o DatabaseDriver
*/
$entityManager->getConfiguration()->setMetadataDriverImpl(
   new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
       $entityManager->getConnection()->getSchemaManager()
   )
);
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
$cmf->setEntityManager($entityManager);
$metadata = $cmf->getAllMetadata();

$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
/*
* Obtemos uma instancia Exporter e exportamos os arquivos a pasta determinada como segundo parâmetro.
*
*/
$exporter = $cme->getExporter('xml', 'src/Entity');
//$exporter->setEntityGenerator(new \Doctrine\ORM\Tools\EntityGenerator());
$exporter->setMetadata($metadata);
$exporter->export();

Observações:

  1. No código $exporter = $cme->getExporter(‘yml’, ‘src/Entity’); onde está ‘yml’, você pode substituir pelo modo que desejar (php, xml ou annotation). O segundo parâmetro diz respeito à pasta onde você quer exportar as Entidades;
  2. Veja que deixei o código: $exporter->setEntityGenerator( new\Doctrine\ORM\Tools\EntityGenerator() ); comentado, precismos deste código para exportar arquivos com Annotation do Doctrine. Eu, particularmente, sempre exporto para o modo Annotation. É uma escolha pessoal e talvez o costume de já haver trabalhado bastante com hibernate no Java.

Veja abaixo o resultado:

eder05

O arquivo cli-config.php (exportando via linha de comando)

<?php
// Requerimos o arquivo bootstrap.php com as configurações de banco de dados e retornando o EntityManager
require_once 'bootstrap.php';
/*
* Aqui estamos setando o HelperSet, o Doctrine exige esta configuração. o DB que é a conexão e EM é o EntityManager.
* No arquivo boostrap o configuramos.
* */
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
   'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($entityManager->getConnection()),
   'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager)
));
return $helperSet;

Em nosso console:

eder06

Note que coloquei a exportação do modo Annotation na pasta Entity, pois, caso eu colocasse na pasta src, o código iria reclamar que já existe os arquivos exportados e tentaria substituí-los.

No nosso projeto você verá algo como:

eder07

Agora é só se preocupar com a lógica de negócio.

Aqui se encerra nosso artigo, tudo de bom para todos. Até a próxima.

Sugestões e dúvidas? Podem comentar abaixo, que eu procurarei responder a todos.

Eder Taveira é engenheiro de software formado pela Universidade Federal do Pará. Também é pós graduado em: Análise de Sistemas Orientados a Objetos, Arquitetura de Software e Desenvolvimento Mobile. Atua há 15 anos no mercado de tecnologia trabalhando inclusive em empresas internacionais como Ford Motors, Turing e The Home Depot. Em plataformas como Udemy e YouTube, Eder tem disponível cursos e dicas voltados para a área de T.I.

Ver perfil