Back-End

27 abr, 2010

Integrando o Doctrine com o Zend Framework

Publicidade

O Doctrine é uma ferramenta de mapeamento
objeto-relacional (ORM) para PHP 5.2.3+. Um de seus principais
recursos é a opção de escrever consultas de bancos de dados em
um dialeto SQL proprietário orientado a objetos chamado Doctrine
Query Language (DQL), que foi inspirado na Hibernate Query
Language (HQL) do framework Hibernate para Java. Isso
fornece aos desenvolvedores uma alternativa poderosa ao SQL, que
mantém a flexibilidade sem duplicações desnecessárias de código.

Os Models gerados pelo Doctrine estendem da
classe Doctrine Record, onde todos eles herdam métodos típicos de
um Active Record, como os métodos save, delete,
etc, além de permitir que o Doctrine participe e monitore o ciclo de
vida dos registros. Outros componentes do Doctrine fazem os trabalhos
mais pesados, como por exemplo a classe Doctrine_Table. Essa classe
possui os métodos típicos de um Data Mapper, como, por
exemplo, createQuery, find, findAll, findBy*
etc.

No Doctrine, as consultas são baseadas em classes
ao invés de tabelas, para demonstrar um exemplo, considere a
seguinte consulta:

FROM Usuario
LEFT JOIN u.Enderecos WHERE u.id = 1

Com ela pode-se notar o seguinte:

  • É feita uma
    seleção do Model Usuario, e não da tabela;

  • É possível
    referenciar os campos do Model;

  • São feitos joins
    com associações;

  • Não existe condição de join, as
    associações entre as classes e como elas estão no banco de dados
    são conhecidas pelo Doctrine.

Essas informações foram traduzidas do Manual
Oficial do Doctrine
, com algumas modificações para clarear o
entendimento.

Definição do Projeto com o Zend Framework

Agora que já foi feita uma breve explicação
sobre o Doctrine, é hora de gerar um projeto de exemplo com o Zend
Framework. O projeto criado será bem simples, apenas para
exemplificar algumas operações com o Doctrine. Neste ponto,
presumo que você, leitor, tenha o Zend Framework e sua ferramenta de
linha de comando configurados em seu sistema operacional. Caso não
tenha feito isso, recomendo o tutorial
do Adler Medrado
, onde ele ensina como fazer essas configurações.

O primeiro passo é criar a estrutura do projeto
com o comando:

zf create project zf_doctrine

Caso nenhum erro ocorra, esse comando gerará a seguinte estrutura:

Estrutura básica de um projeto Zend Framework

A pasta application será onde o código específico da
aplicação será armazenado. Esse código inclui as configurações
da aplicação para ambiente de desenvolvimento, produção ou demais
ambientes configurados; os Controllers responsáveis por
intermediar o fluxo entre as Views, que cuidam da apresentação
ao usuário, e os Models responsáveis pela lógica de negócio
da aplicação.

A pasta docs é utilizada para armazenar as
documentações da aplicação e dos componentes. library é
onde ficam armazenados os plugins, componentes e demais
bibliotecas de terceiros. Ela é utilizada para armazenar o Doctrine
e toda estrutura de classes do Zend Framework. A pasta public
é a pasta acessível pelo servidor web, onde é feita toda a
chamada para inicializar a aplicação, seguindo o padrão Front
Controller. E a pasta testes é onde são armazenados os
testes unitários.

Integração

A primeira coisa a se fazer é o download
do framework Doctrine. Na data em que este artigo foi escrito,
a última versão estável era a 1.2.1. Você pode
baixá-la neste
link
. Após ter efetuado o download e extraído o pacote,
é necessário copiar todo o conteúdo da pasta lib para a
pasta library do projeto Zend Framework criado, resultando na
seguinte estrutura:

Estrutura com o Doctrine extraído

Após isso, é necessário criar a pasta db e dentro dela as
seguintes pastas:

  • fixtures –
    Pasta que armazenará arquivos de definição de dados para popular
    o banco de dados;

  • migrations
    – Arquivos de migração entre diferentes versões de banco de
    dados;

  • schema –
    Definição de toda estrutura do banco de dados, seguindo o formato
    YAML;

  • sql – Armazena a estrutura SQL que
    define o banco de dados.

Depois de criar essas pastas, também pode ser
criada a pasta para armazenar os scripts de linha de comando
do Doctrine. Dentro de application é criada uma pasta com o
nome de scripts. Após criar todas essas pastas, a estrutura
será a seguinte:

Estrutura final de diretórios

Após toda a estrutura estar definida, é hora de começar a
configurar o projeto. A primeira coisa a se fazer é abrir o arquivo
application/configs/application.ini e acrescentar o seguinte
conteúdo nele, na seção [production]:

autoloaderNamespaces[] = "Doctrine"
doctrine.dsn = "mysql://usuario:senha@host/banco"
doctrine.models_path = APPLICATION_PATH "/models"
doctrine.data_fixtures_path = APPLICATION_PATH "/../db/fixtures"
doctrine.migrations_path = APPLICATION_PATH "/../db/migrations"
doctrine.sql_path = APPLICATION_PATH "/../db/sql"
doctrine.yaml_schema_path = APPLICATION_PATH "/../db/schema"
; Conservative Model Loading
doctrine.model_autoloading = 2

Nessas linhas é definido o Namespace utilizado pelo Doctrine
para o autoLoader carregar as classes automaticamente. Logo em
seguida, é definido um Nome de Fonte de Dados (Data Source Name
– DSN) para a localização do banco de dados. Depois são
definidas as pastas onde são armazenados os models, as
fixtures, a sql, e o schema, e, por último, é
definida a forma como o Doctrine carregará os Models. Neste
caso, é utilizado o Conservative, que carrega os Models
conforme eles sejam necessários.

Logo abaixo da linha doctrine.model_autoloading,
é necessário definir algumas configurações para a ferramenta de
linha de comando do Doctrine (Doctrine Cli), basta colocar o seguinte
conteúdo:

[doctrineCLI : production]
doctrine.generate_models_options.pearStyle = true
doctrine.generate_models_options.generateTableClasses = false
doctrine.generate_models_options.generateBaseClasses = true
doctrine.generate_models_options.baseClassPrefix = "Base_"
doctrine.generate_models_options.baseClassesDirectory =
doctrine.generate_models_options.classPrefixFiles = false
doctrine.generate_models_options.classPrefix = "Model_"
; Agressive Model Loading
doctrine.model_autoloading = 1

Nesse ambiente é necessário configurar as formas como os Models
serão gerados. A primeira linha configura a nomenclatura no formato
PEAR, onde o nome da classe contém como prefixos a estrutura de
diretório onde ela está localizada, por exemplo, Model_Produto,
estará na pasta model/Produto.php, no caso da nomenclatura do
Zend Framework, a pasta models terá como prefixo Model_,
seguindo o padrão do Autoloader do framework. A
segunda linha indica ao Doctrine CLI para não gerar as classes Table
automaticamente. A terceira linha configura o Doctrine CLI para que
gere classes base, onde a quarta linha configura o prefixo das
classes Base e a linha seguinte define um diretório vazio, fazendo
com que as classes geradas sejam armazenadas no diretório
models/base ao invés de models/Model/Base. A próxima
linha indica que os nomes de arquivos não devem conter prefixos, e a outra linha indica ao prefixo das classes geradas, que deverá ser
Model_, como explicado. A última linha define que a forma
de carregar os Models será Agressive, ou seja,
eles são carregados na inicialização do Doctrine.

Após definir toda essa estrutura, é hora de
configurar a inicialização do Zend Framework para que ele carregue
e configure corretamente o Doctrine e as demais classes necessárias
pela aplicação. Para isso, basta abrir o arquivo
application/Bootstrap.php e colocar o seguinte conteúdo
nele:

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'basePath' => APPLICATION_PATH,
'namespace' => ''
));
return $autoloader;
}
protected function _initDoctrine()
{
$this->getApplication()->getAutoloader()
->pushAutoloader(array('Doctrine', 'autoload'));
spl_autoload_register(array('Doctrine', 'modelsAutoload'));
$doctrineConfig = $this->getOption('doctrine');
$manager = Doctrine_Manager::getInstance();
$manager->setAttribute(Doctrine::ATTR_AUTO_ACCESSOR_OVERRIDE, true);
$manager->setAttribute(
Doctrine::ATTR_MODEL_LOADING,
$doctrineConfig['model_autoloading']
);
Doctrine::loadModels($doctrineConfig['models_path']);
$conn = Doctrine_Manager::connection($doctrineConfig['dsn'], 'doctrine');
$conn->setAttribute(Doctrine::ATTR_USE_NATIVE_ENUM, true);
return $conn;
}
}

O primeiro método configura o Autoloader do Zend Framework,
para que ele cuide do carregamento das classes necessárias na
aplicação. O segundo método configura o Doctrine. Primeiramente
ele adiciona o Autoloader do Doctrine ao Autoloader do
Zend Framework, permitindo ao Doctrine carregar as classes
específicas de seus componentes. A próxima linha configura o
Autoloader de Models do Doctrine, para que o Doctrine
também cuide do carregamento de seus Models, conforme for
necessário na aplicação. As linhas seguintes cuidam de:

  • Pegar as
    configurações definidas no arquivo application.ini;

  • Definir os
    atributos do Doctrine, onde o primeiro atributo definido é a
    sobrescrita de acessores e o segundo é a forma como os Models
    são carregados;

  • Carregar os
    Models, conforme a forma definida na linha anterior;

  • Gerar uma conexão
    ao banco de dados, baseada na dsn definida no arquivo de
    configuração;

  • Utilizar a forma nativa de ENUM.

Com isso, o arquivo de inicialização do Zend
Framework está devidamente configurado. A última etapa é criar a
ferramenta de linha de comando do Doctrine. Para fazer isso, basta
criar o arquivo application/scripts/doctrine.php e colocar o
seguinte conteúdo nele:

<?php
//Caminho para a pasta da aplicação
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/..'));
//Ambiente em que a aplicação será executada
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'doctrineCLI'));
//Adiciona a "library" no include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path()
)));
require_once 'Zend/Application.php';
//Cria a aplicação, faz o bootstrap e a executa
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);
$application->getBootstrap()->bootstrap('autoload');
$application->getBootstrap()->bootstrap('doctrine');
$config = $application->getOption('doctrine');
$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']);

Esse arquivo irá definir a pasta da aplicação, o ambiente
doctrineCLI, para pegar as configurações específicas da
ferramenta de linha de comando, conforme definido no arquivo
application.ini, adicionar a biblioteca de componentes no
include_path, inicializar a aplicação, assim como o Autoload
do Zend Framework e o Doctrine, pegar as configurações definidas em
application.ini, e, finalmente, chamar o Doctrine CLI para
cuidar de todo o resto. Por último, é necessário criar o arquivo
executável responsável por chamar esta ferramenta definida. Para
isso, basta criar o arquivo application/scripts/doctrine e
colocar o seguinte conteúdo nele:

#!/usr/bin/env /usr/bin/php
<?php
chdir(dirname(__FILE__));
include('doctrine.php');

O próximo passo é dar permissão de execução a este arquivo:

$ chmod a+x zf_doctrine/application/scripts/doctrine

Testando

Antes de mais nada, a primeira coisa a se fazer é
testar o cliente de linha de comando. Para isso, em um terminal,
execute o seguinte comando:

$ zf_doctrine/application/scripts/doctrine

A saída deste comando deverá ser a seguinte:

Doctrine Command Line Interface
doctrine.php build-all
doctrine.php build-all-load
doctrine.php build-all-reload
doctrine.php compile
doctrine.php create-db
doctrine.php create-tables
doctrine.php dql
doctrine.php drop-db
doctrine.php dump-data
doctrine.php generate-migration
doctrine.php generate-migrations-db
doctrine.php generate-migrations-diff
doctrine.php generate-migrations-models
doctrine.php generate-models-db
doctrine.php generate-models-yaml
doctrine.php generate-sql
doctrine.php generate-yaml-db
doctrine.php generate-yaml-models
doctrine.php load-data
doctrine.php migrate
doctrine.php rebuild-db

Se preferir, adicione essa ferramenta no include_path de sua
aplicação, seguindo a mesma idéia explanada pelo Adler Medrado.
Caso o comando retorne a saída demonstrada acima, a ferramenta da
linha de comando deverá estar funcionando corretamente. Agora é
hora de criar um banco de dados chamado carros_doctrine. Após
criá-lo, crie o arquivo db/schema/schema.yml, e coloque o
seguinte conteúdo nele:

Carro:
connection: 0
tableName: carro
columns:
id:
type: integer(4)
fixed: false
unsigned: true
primary: true
autoincrement: true
nome:
type: string(150)
fixed: false
unsigned: false
primary: false
notnull: true
autoincrement: false
cor:
type: string(150)
fixed: false
unsigned: false
primary: false
notnull: true
autoincrement: false

Nesse arquivo, é feita a definição de uma tabela chamada “Carro”
para o banco de dados, onde a tabela terá as colunas:

  • id – Que
    será um campo do tipo Inteiro, chave primária, não aceitará
    valores nulos e nem valores negativos (unsigned) e será
    um campo auto-incremental;

  • nome – Será
    um campo do tipo String, com o tamanho de 150 caracteres e que
    não pode ser nulo;

  • cor – Será um campo do tipo String,
    com o tamanho de 150 caracteres e que não pode ser nulo.

Após criar o banco e definir esse arquivo, basta
executar a ferramenta de linha de comando para que ela gere o banco
de dados e os Models. Para isso, basta executar o seguinte
comando:

$ zf_doctrine/application/scripts/doctrine build-all-reload
build-all-reload - Are you sure you wish to drop your databases? (y/n)
y
build-all-reload - Successfully dropped database for connection named 'doctrine'
build-all-reload - Generated models successfully from YAML schema
build-all-reload - Successfully created database for connection named 'doctrine'
build-all-reload - Created tables successfully
build-all-reload - Data was successfully loaded

Após executar esse comando, basta verificar se a pasta models
está devidamente preenchida com o arquivo Carro.php e
Base/Carro.php.

Agora, caro queira gerar o SQL da aplicação,
basta executar o seguinte comando:

$ zf_doctrine/application/scripts/doctrine generate-sql
generate-sql - Generated SQL successfully for models

Após isso, o arquivo db/sql/schema.sql será criado e conterá
a SQL do banco de dados, como o trecho a seguir:

CREATE TABLE carro (
id INT UNSIGNED AUTO_INCREMENT,
nome VARCHAR(150) NOT NULL,
cor VARCHAR(150) NOT NULL,
PRIMARY KEY(id)
) ENGINE = INNODB;

Agora, o último teste para verificar se tudo está corretamente
integrado é fazer algumas chamadas no Controller e verificar
se tudo é executado corretamente. Dentro do arquivo
application/controllers/IndexController.php, existe o método
indexAction(), basta defini-lo como a seguir:

public function indexAction()
{
$carro = new Model_Carro();
$carro->nome = "Ferrari";
$carro->cor = "Vermelha";
$carro->save();
$query = new Doctrine_Query();
$query->from('Model_Carro c');
$query->orderBy('c.id DESC');
$this->view->carros = $query->execute();
}

Nesse método, é criado um novo carro com o nome “Ferrari”
e a cor “Vermelha”, e então é inserido esse carro no
banco de dados. Após isso, uma consulta de todos os carros do banco
de dados é executada e, então, essa consulta é passada para a
View, onde é feita uma listagem desses carros e apresentada
ao usuário.

Para fazer essa listagem, altere o arquivo
application/views/scripts/index/index.phtml e deixe-o com o
seguinte conteúdo:

<?php foreach ( $this->carros as $carro ): ?>
<h1>Carro #<?php echo $carro->id; ?></h1>
<p>Nome: <?php echo $carro->nome; ?></p>
<p>Cor: <?php echo $carro->cor; ?></p>
<hr />
<?php endforeach; ?>

Nele é feito um laço para percorrer todos os carros imprimindo os
dados de cada um ao usuário. Agora basta ir ao browser e
acessar o projeto, que o seguinte conteúdo será exibido:

Listagem dos Carros

Caso todo esse processo ocorra sem erros, o Doctrine está devidamente
integrado ao Zend Framework.

Conclusão

O Doctrine é uma ferramenta robusta e consistente
para mapeamento objeto relacional. Ao unir todas as suas
funcionalidades com os componentes do Zend Framework, é possível
obter uma arquitetura altamente consistente e com ferramentas de alto
nível. Apesar de ser uma solução minimalista, a integração se
mostra devidamente eficaz, provendo todas as funcionalidades das
ferramentas de linha de comando e dos componentes de ambos os
frameworks. Existem diversas formas de se integrar o Doctrine
com o Zend Framework, esta pode não ser a melhor, porém funciona
sem maiores problemas. Logo abaixo, coloco alguns links com
outras formas de integração, assim como o screencast que
utilizei como base para elaborar este artigo. Até a próxima.