Fernando Mantoan Desenvolvimento e tecnologia da informação no geral

Arquitetura de Software com CodeIgniter – Integrando o Doctrine

Postado em 07 de junho de 2009. Categorias: CodeIgniter, PHP | 14 Comentários

Neste quarto artigo sobre uma Arquitetura de Software com o CodeIgniter, mostrarei como fazer a integração do framework de persistência Doctrine com o framework CodeIgniter. Como explicado anteriormente, o Doctrine foi escolhido por fornecer formas robustas e flexíveis de se realizar consultas com uma sintaxe orientada a objetos, o que os desenvolvedores do projeto chamam de Doctrine Query Language (DQL), o que lembra a Hibernate Query Language (HQL) do Java. Além disso, ele fornece classes de persistência para cada tabela do banco de dados.

Agora saindo um pouco da teoria, é hora de por a mão na massa e começar a integração do framework. Antes de mais nada é necessário fazer o download da última versão estável do Doctrine, de preferência baixar a “Sandbox”, é possível encontrá-la na página oficial do projeto.

Integrando o Doctrine

A primeira etapa é criar uma pasta chamada doctrine dentro de system/database, do CodeIgniter. Após o download do pacote do Doctrine ser concluído, basta extraí-lo e copiar todo o conteúdo localizado na pasta lib deste pacote para a pasta system/database/doctrine. Agora, esta pasta deve conter uma pasta chamada Doctrine e um script PHP chamado Doctrine.php.

A segunda etapa é configurar algumas propriedades do Doctrine para que ele possa ser carregado e para deixá-lo apto a se conectar com um banco de dados. Dentro do arquivo system/application/config/database.php, basta localizar a linha com o seguinte trecho:

$db['default']['cachedir'] = ""; 

Logo abaixo desta linha, insira o seguinte trecho:

//Configura os dados da fonte de dados, declarados acima
$db['default']['dsn'] = $db['default']['dbdriver'] .
							'://' . $db['default']['username'] .
							':' . $db['default']['password'] .
							'@' . $db['default']['hostname'] .
							'/' . $db['default']['database'];
//Inclui a classe do Doctrine
require_once realpath(dirname(__FILE__) . '/../..') . DIRECTORY_SEPARATOR . 'database/doctrine/Doctrine.php';
//Seta o autoloader de classes
spl_autoload_register(array('Doctrine', 'autoload'));
//Carrega a conexão do Doctrine com a string de DSN configurada e o banco de dados escolhido
Doctrine_Manager::connection($db['default']['dsn'], $db['default']['database']);
//Seta a forma de se carregar os models para o tipo "conservative" ou "lazy initiation"
Doctrine_Manager::getInstance()->setAttribute('model_loading', 'conservative');
//Carrega os modelos para o autoloader
Doctrine::loadModels(realpath(dirname(__FILE__) . '/..') . DIRECTORY_SEPARATOR . 'models');

Este código define um Nome de Fonte de Dados (Data Source Name – DSN), depois inclui a classe Doctrine, configura o autoloader do framework, inicia a conexão com o banco de dados pelo DSN definido e no banco de dados definido neste arquivo de configuração (no trecho $db['default']['database'] = “”), seta para que o carregamento de classes seja “lazy”, ou seja, as classes agregadas são carregadas de acordo com a necessidade e, por último, seta a pasta para o Doctrine procurar por seus Models.

Após definir este trecho de código, deve-se garantir que o index.php da aplicação carregue o arquivo de configuração do banco de dados. Para que isso seja feito, basta entrar no arquivo index.php na raíz do CodeIgniter e modificar suas últimas duas linhas, deixando assim:

require_once APPPATH . 'config/database.php';
require_once BASEPATH . 'codeigniter/CodeIgniter'.EXT;

Configurando a Interface de Linha de Comando

O Doctrine possui uma interface executada em linha de comando que pode auxiliar muito na geração dos models, migrações e etc. É sempre bom deixar esta interface funcionando, em qualquer projeto que utilize o Doctrine. Primeiramente, deve-se criar o arquivo doctrine na pasta system/application, sem extensão nenhuma mesmo, e dar um chmod para que ele possa ser executado (chmod a+x). Este arquivo deve ter o seguinte conteúdo:

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

Agora, é necessário criar o arquivo doctrine.php na pasta system/application e colocar o seguinte conteúdo nele:

<?php
require_once 'config/database.php';
//Configura o Doctrine Cli
$config = array('data_fixtures_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '/fixtures',
				'models_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '/models',
				'migrations_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '/migrations',
				'sql_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '/sql',
				'yaml_schema_path' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '/schema');
$cli = new Doctrine_Cli($config);
$cli->run($_SERVER['argv']); ?>

Ele será responsável por configurar cada pasta onde o Cli poderá atuar para realizar suas funcionalidades. A próxima etapa é criar os seguintes diretórios dentro de system/application:

  • fixtures
  • migrations
  • schema
  • sql

Agora é necessário setar o banco de dados que a aplicação utilizará, por exemplo, tarefas_ci:

$db['default']['database'] = "tarefas_ci";

Após estes passos estarem concluídos, a interface com a linha de comando deverá estar funcionando. Para testá-la, acesse um terminal e digite os seguintes comandos (para sistemas GNU/Linux), onde “suapasta” corresponde ao endereço físico da pasta da aplicação:

$ cd suapasta/system/application
$ php doctrine

Caso apareça uma mensagem dizendo “No direct script access allowed” será necessário acessar o arquivo database.php no diretório system/application/config e comentar o trecho:

 if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 

Importante: deixe este trecho comentando somente para utilizar o Cli, deixe-o descomentado principalmente no ambiente de produção, por questões de segurança. O resultado de executar o Cli é o seguinte:

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

Testando a Integração

Após várias etapas é hora de efetuar operações para testar a interface de linha de comando do Doctrine e a integração do mesmo com o CodeIgniter. O banco de dados utilizado nesta etapa é o seguinte:

CREATE DATABASE tarefas_ci;
USE tarefas_ci;
CREATE TABLE `tarefas` (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
titulo VARCHAR(200) NOT NULL,
situacao ENUM('A', 'F') DEFAULT 'A'
)ENGINE=InnoDB;

A configuração do banco de dados, encontrada no arquivo database.php da pasta system/application/config, deverá ser algo como o seguinte, onde os atributos hostname, username e password deverão ser configurados de acordo com os dados do banco:

//Host do banco de dados
$db['default']['hostname'] = "localhost";
//Usuário para se conectar com o banco
$db['default']['username'] = "root";
//Senha do usuário do banco de dados
$db['default']['password'] = "";
//Banco de dados a se conectar
$db['default']['database'] = "tarefas_ci";
//Driver de conexão com o banco
$db['default']['dbdriver'] = "mysql";

Primeiramente, é necessário criar o schema para o Doctrine poder gerar os models de acordo com o banco de dados, este arquivo segue o padrão YAML para definição dos dados. Para isso basta criar o arquivo schema.yml na pasta system/application/schema e colocar o seguinte conteúdo nele:

 Tarefas:
  columns:
    id:
      primary: true
      autoincrement: true
      type: integer(10)
    titulo:
      type: string(200)
    situacao:
      type: enum
      length: 2
      values: ['A', 'F']

Agora, basta executar o seguinte comando, para os models serem criados em system/application/models:

$ php doctrine generate-models-yaml
generate-models-yaml - Generated models successfully from YAML schema

Ao verificar o conteúdo da pasta system/application/models, os seguintes arquivos devem estar presentes: Tarefas.php e generated/BaseTarefas.php. O conteúdo destes arquivos são, respectivamente:

abstract class BaseTarefas extends Doctrine_Record
{
	public function setTableDefinition()
	{
		$this->setTableName('tarefas');
		$this->hasColumn('id', 'integer', 10, array('primary' => true, 'autoincrement' => true, 'type' => 'integer', 'length' => '10'));
		$this->hasColumn('titulo', 'string', 200, array('type' => 'string', 'length' => '200'));
		$this->hasColumn('situacao', 'enum', 2, array('type' => 'enum', 'length' => 2, 'values' => array(0 => 'A', 1 => 'F')));
	}
}
class Tarefas extends BaseTarefas
{
}

Agora é necessário criar o arquivo TarefasDAO.php na pasta system/application/models, ele terá o seguinte conteúdo:

<?php
class TarefasDAO
{
	/**
	 * Seleciona as tarefas do banco de dados, retornando um array de objetos
	 * @access public
	 * @return array = Array de objetos Tarefa
	 */
	public function allData()
	{
		$query = new Doctrine_Query();
		$query->from('Tarefas t');
		$query->orderby('t.id DESC');
		return $query->execute();
	}
}
?>

Agora, pode-se carregar alguns dados para a aplicação, para popular o banco e posteriormente fazer algumas consultas. O primeiro passo para fazer isso é criar uma fixture que é utilizado para o Doctrine popular o banco, para isto basta criar o arquivo tarefas.yml na pasta system/application/fixtures e colocar o seguinte conteúdo nele:

Tarefas:
  teste:
    titulo: teste
    situacao: A
  teste2:
    titulo: teste2
    situacao: F

Então basta executar o seguinte comando no terminal:

$ php 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 "tarefas_ci" named "tarefas_ci"
build-all-reload - Generated models successfully from YAML schema
build-all-reload - Successfully created database for connection "tarefas_ci" named "tarefas_ci"
build-all-reload - Created tables successfully
build-all-reload - Data was successfully loaded

Neste ponto o banco está devidamente configurado e possui 2 registros inseridos. Agora basta verificar se o Doctrine foi corretamente integrado no CodeIgniter. No Controller welcome.php localizado na pasta system/application/controllers, coloque o seguinte conteúdo:

<?php
class Welcome extends Controller
{
	public function Welcome()
	{
		parent::Controller();
		$this->load->library('smartyview');
	}
	public function index()
	{
		$dao = new TarefasDAO();
		$tarefas = $dao->allData();
		$this->smartyview->assign_by_ref('tarefas', $tarefas);
		$this->smartyview->display('index.tpl');
	}
}
?>

Nele pega-se a classe DAO, então é executado o método que retorna um array de todas as tarefas ordenadas pelo ID em ordem decrescente, passa-se este array de tarefas para a View e a exibe ao usuário. Agora basta então criar a View, ela ficará localizada na pasta system/application/views com o nome index.tpl, e terá o seguinte conteúdo:

<html>
	<head>
		<title>Arquitetura CI</title>
	</head>
	<body>
		<h1>Lista de Tarefas</h1>
		<ul>
		{foreach from=$tarefas item=tarefa}
		<li>{$tarefa.id}, {$tarefa.titulo} - {if $tarefa.situacao eq 'A'}Aberta{else}Fechada{/if}</li>
		{/foreach}
		</ul>
	</body>
</html>

Aqui é feito um laço no array de objetos de Tarefas retornado pela DAO e a cada iteração é impresso, em um elemento de uma lista desordenada, o ID da tarefa, seu título e a situação, que pode ser Aberta ou Fechada.

Bom, com isso encerro mais um artigo sobre a arquitetura de desenvolvimento de software com CodeIgniter, Smarty e Doctrine. No próximo artigo me focarei em impelementar o padrão Facade no framework CodeIgniter, seguido pela mudança de nomenclatura em algumas camadas e, finalmente, pelo desenvolvimento de um sistema de controle de tarefas. Aguardem, até a próxima.

14 Comentários +

Responder

James

Postado em 03/01/2010 às 17:25

Parabéns pela matéria, porém quando vou executar o via linha de comando ocorre o seguinte erro:

Fatal error: Uncaught exception ‘LogicException’ with message ‘Passed array does not specify an existing static method’ in D:\www\gestor\system\application\config\database.php:62 Stack trace: #0 D:\www\gestor\system\application\config\database.php(62): spl_autoload_register() #1 D:\www\gestor\system\application\doctrine.php(2): require_once(‘D:\www\gestor\s…’) #2 {main} thrown in D:\www\gestor\system\application\config\database.php on line 62

    Responder

    Fernando

    Postado em 04/01/2010 às 11:10

    Hehe, só para constatar, o problema era no seu arquivo “Doctrine.php”… :) Que bom que conseguimos resolver, abraços!

Responder

Hugo

Postado em 02/02/2010 às 15:04

Estou tendo dificuldades para setar o doctrine com charset utf8, o meu banco está correto, está tudo certo e nada de os caracteres sairem corretos, alguém uma luz?

    Responder

    Fernando

    Postado em 02/02/2010 às 23:07

    @Hugo os caracteres saem errados no browser ou no console do mysql? Se for no browser, verifique se o charset do HTML está correto.

Responder

Filipe

Postado em 02/02/2010 às 15:46

Opa Fernando,
parabens pelo artigo, cara me desculpe a pergunta se ela parecer idiota, porém nunca utilizei um framework de desenvolvimento em PHP, estou com grandes dificuldades para executar o Doctrine no windows, ele simplesmente não reconhece o comando?! Acho que nubiei em algo!
Valeu

    Responder

    Fernando

    Postado em 02/02/2010 às 23:08

    @Filipe Olá, olha quanto a isso não posso te ajudar, pois utilizo o Linux para desenvolvimento. Qual o erro que está acontecendo?

Responder

Hugo

Postado em 03/02/2010 às 09:35

Fernando está correto, as tabelas, o banco, o html está tudo como UTF8, sempre trabalhei assim, mas com o doctrine não está dando certo, atualizei para o 1.2 e mesmo assim ainda não consegui solucionar o problema.

Responder

Hugo

Postado em 03/02/2010 às 09:36

Os caracteres saem errados no browser

Responder

Hugo

Postado em 03/02/2010 às 09:39

Opa, corrigindo o que eu disse Fernando, no console sai com o caractere errado

Responder

Higor Morais

Postado em 31/05/2010 às 10:26

Bom dia, fernando.

Cara, eu to executando para ele criar umas tabelas usando a parte de linha de comando do doctrine, mas infelizmente as tabelas não estão sendo geradas, o banco de dados é criado, mas as tabelas não, vc já passou por uma situação parecida?

Valeu

    Responder

    Fernando

    Postado em 31/05/2010 às 14:10

    Cara nunca vi isso, não ocorre nenhum erro?

Responder

Gedson Marcelino

Postado em 25/07/2010 às 01:43

Fernando, estou tendo o mesmo problema do Higor Morais. Não dá nenhum erro e também não gera as tabelas. Mas descobri que isso tá acontecendo porque a classe ReflecitionClass no PHP não está encontrando a classe quando faz a instância no arquivo Doctrine/Core.php no método isValidModelClass. Já atualizei o PHP, mas não resolveu. =/
Se souber de alguma coisa me dá um toque, plz!
Obrigado.

Responder

Gedson Marcelino

Postado em 25/07/2010 às 12:49

Opa! Consegui indentificar o problema. O autoload não estava encontrando as classes na pasta models. Para tapar o buraco temporariamente criei um autoload e registrei, segue abaixo:

function jedi_autoload( $classe )
{
$path = dirname(__FILE__).’/../models’;
$path_base = dirname(__FILE__).’/../models/generated’;

if ( preg_match(‘/^Base.*/’, $classe ) && file_exists($path_base.’/’.$classe.’.php’) )
{
require_once $path_base.’/’.$classe.’.php’;
}
elseif ( file_exists($path.’/’.$classe.’.php’) )
{
require_once $path.’/’.$classe.’.php’;
}

}

Responder

Lucas Kreutz

Postado em 28/07/2010 às 22:14

Ótimo artigo.

Obs: Funciona perfeitamente no windows, só não colocar a linha “#!/usr/bin/env php” no arquivo doctrine.

Parabéns.

Deixe um Comentário