Neste artigo demonstro uma maneira de integrar o framework de mapeamento objeto relacional Doctrine com o Zend Framework. Esta integração pode ser útil em projetos que necessitam de uma ferramento mais consistente mara fazer o mapeamento objeto relacional, tendo em vista que o Zend_Db ainda não possui boas funcionalidades para este objetivo. O Doctrine também se mostra consistente na gestão de memória, liberando recursos conforme eles não são mais necessários, além de outros recursos interessantes para as aplicações.
Introdução ao Doctrine
O Doctrine é uma ferramenta de mapeamento objeto-relacional (ORM) para PHP 5.2.3+. Um de seus principais recursos é a opção de se escrever consultas de bancos de dados em um dialeto SQL proprietário orientado a objetos chamado Doctrine Query Language (DQL), que foi inspirada 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 extendem 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
. Esta 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.
Estas 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 básicas 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 estas configurações.
O primeiro passo é criar a estrutura do projeto com o comando:
zf create project zf_doctrine
Caso nenhum erro ocorra este comando gerará a seguinte estrutura:
A pasta application será onde o código específico da aplicação será armazenado. Este 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 do framework é 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:
Após isso é necessário criar a pasta db
e dentro dela as seguintes pastas:
- fixtures - Pasta onde 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 estas 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 estas pastas a estrutura será a seguinte:
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
Nestas 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, em seguida 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
Neste 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, a linha seguinte indica ao prefixo das classes geradas, que deverá ser Model_
, como explicado. E 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 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']);
Este 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 esse 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 esta 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
Neste arquivo é feita a definição de uma tabela chamada “Carro” para o banco de dados, onde esta 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 este 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 este 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 definí-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();
}
Neste método é criado um novo carro com o nome “Ferrari” e a cor “Vermelha”, e então é inserido este carro no banco de dados. Após isso uma consulta de todos os carros do banco de dados é executada e, então, esta consulta é passada para a View, onde é feita uma listagem destes carros e apresentado ao usuário.
Para fazer esta 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 no browser e acessar o projeto, que o seguinte conteúdo será exibido:
Caso todo este 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, e unindo 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.
Código-Fonte
O link abaixo é o pacote com todo o código-fonte do projeto desenvolvido neste artigo, junto com a biblioteca Doctrine.
Zend Framework e Doctrine 2
Para o pessoal interessado em integrar o Zend Framework com o Doctrine 2, recomendo verificar o repositório ZendFramework1-Doctrine2 do Guilherme Blanco, que contém um exemplo de integração.