Back-End

23 set, 2016

Duas maneiras de mapear um sistema do zero usando Doctrine2

Publicidade

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.