Snippets úteis do Zend Framework

Compartilho aqui alguns snippets e comandos úteis do Zend Framework, para referência futura e quem sabe ajudar alguém que precise.

Retornar o código de resposta 404 (Not found).

 $this->_response->clearBody()->clearHeaders()->setHttpResponseCode(404)->sendResponse(); 


Redirecionar para URL com âncora (sharp/cerquilha/jogo da velha)

 $opt = array('module' => 'admin', 'controller' => 'galeria', 'action' => 'editar', 'id' => 1); $url = $this->_helper->getHelper('Url')->url($opt); $url .= "#descricao"; $this->_helper->redirector->gotoUrl($url); 


Trocar o layout no controller

 $this->_helper->layout->setLayout('servicos'); 


Timestamp no formato de banco de dados

 Zend_Date::now()->toString('yyyy-MM-dd HH:mm:ss'); 


Desabilitar layout

 $this->_helper->layout()->disableLayout(); 


Desabilitar renderização da view

$this->_helper->viewRenderer->setNoRender(true);


Debugar consultas no banco de dados

 $db = Zend_Db_Table::getDefaultAdapter(); $profiler = $db->getProfiler()->setEnabled(true); print_r($profiler->getLastQueryProfile()); 


Dados do usuário logado (caso esteja usando Zend_Auth)

Zend_Auth::getInstance()->getIdentity();


Utilizando transações:

$db = Zend_Db_Table::getDefaultAdapter();

try{
	$db->beginTransaction();
	// query
	$db->commit();
}
catch(Exception $e){
	$db->rollBack();
}

Tutorial de Instalação e Configuração do Doctrine 2.2

Olá! Esta é a primeira vez que arrumo tempo para testar a versão 2 deste famoso ORM para PHP, o Doctrine. Criei este tutorial para servir como referência futura e também para eventualmente auxiliar alguém que esteja estudando. Vale lembrar que você precisa ter o PHP 5.3.0 ou superior instalado na máquina devido aos namespaces utilizados no Doctrine 2.

 

Download e Instalação

Baixe a última versão (no momento em que escrevo é a 2.2): Crie uma pasta no seu servidor e extraia o conteúdo do arquivo recém baixado para dentro dela. Sua estrutura deverá ficar mais ou menos assim:

sua_pasta
|-- bin
|-- Doctrine
|-- entities

Logo após, crie na raiz um arquivo chamado "bootstrap.php". Este arquivo irá conter a configuração básica do Doctrine 2, bem como fornecerá uma instância do Entity Manager. Opa, Entity Manager? É meu amigo, dessa vez a semelhança com o Hibernate está maior ainda. As entidades tem até anotações! Claro que você pode definir as entidades com XML (argh) e YAML. Mas pelo menos pra mim, nada como definir como classes PHP! Crie também um banco de dados para testar o Doctrine 2.

 
// bootstrap.php 

use DoctrineORMToolsSetup; 
require_once "entities/Cidade.php"; 
require_once "entities/Pessoa.php"; 

require_once "Doctrine/ORM/Tools/Setup.php"; 

Setup::registerAutoloadPEAR(); 

$debug = true; 
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/entities"), $debug); 

// Configuração de acesso ao banco de dados 
$conn = array( 
    'driver' => 'pdo_mysql', 
    'user' => 'usuario', 
    'password' => 'senha', 
    'dbname' => 'doctrine2_test' 
); 

// Obtendo uma instância do Entity Manager 
$entityManager = DoctrineORMEntityManager::create($conn, $config); 

 

Definindo as entidades

Agora que você já configurou o bootstrap, vamos criar as entidades (classes) que serão usadas para a persistência no banco de dados. Na pasta "entities", criada anteriormente, crie os arquivos "Cidade.php" e "Pessoa.php" com o conteúdo abaixo:

 
// entities/Cidade.php 

use DoctrineCommonCollectionsArrayCollection; 

/** * @Entity @Table(name="cidade") */ 
class Cidade { 
    /** @Id @Column(type="integer") @GeneratedValue */ 
    protected $id; 

    /** @Column(type="string") */ 
    protected $nome; 

    /** @Column(type="string") */ 
    protected $uf; 

    /** * @OneToMany(targetEntity="Pessoa", mappedBy="cidade") * 
    @var Pessoa[] */ 
    protected $habitantesCidade = null; 

    public function __construct() { 
        $this->habitantesCidade = new ArrayCollection(); 
    } 

    public function getId() { 
        return $this->id; 
    } 

    public function getNome() { 
        return $this->nome; 
    } 

    public function setNome($nome) { 
        $this->nome = $nome; 
    } 

    public function getUf() { 
        return $this->uf; 
    } 

    public function setUf($uf) { 
        $this->uf = $uf; 
    } 

    public function adicionarHabitante($pessoa) { 
        $this->habitantesCidade[] = $pessoa; 
    } 
}

 


 
// entities/Pessoa.php 

/** * @Entity @Table(name="pessoa") */ 
class Pessoa { 
    /** @Id @Column(type="integer") @GeneratedValue */ 
    protected $id; 

    /** @Column(type="string") */ 
    protected $nome; 

    /** @Column(type="datetime") */ 
    protected $dataHoraCadastro; 

    /** * @ManyToOne(targetEntity="Cidade", inversedBy="habitantesCidade") */ 
    protected $cidade; 

    public function getId() { 
        return $this->id; 
    } 

    public function getNome() { 
        return $this->nome; 
    } 

    public function setNome($nome) { 
        $this->nome = $nome; 
    } 

    public function getDataHoraCadastro() { 
        return $this->dataHoraCadastro; 
    } 

    public function setDataHoraCadastro(DateTime $dataHoraCadastro) { 
        $this->dataHoraCadastro = $dataHoraCadastro; 
    } 

    public function getCidade() { 
        return $this->cidade; 
    } 

    public function setCidade($cidade) { 
        $this->cidade = $cidade; 
    } 
}

 

Criação das tabelas

A criação das tabelas no banco de dados será feita através da linha de comando. Caso não queira usar a linha de comando, o arquivo com os comandos SQL se encontra para download no final do post, juntamente com o projeto completo.

 

Utilizando a linha de comando

Se você deseja usar a linha de comando do Doctrine 2, faça uma pequena alteração no arquivo bin/doctrine, adicionando a seguinte linha antes do include:

 set_include_path(get_include_path() . PATH_SEPARATOR . '../'); 

Crie na raiz um arquivo chamado config-cli.php, que irá conter a configuração para rodar o Doctrine Tool através do terminal do Linux (ou prompt do Windows).

 
// config-cli.php 

require_once "bootstrap.php"; 

$helperSet = new SymfonyComponentConsoleHelperHelperSet(array( 'em' => new DoctrineORMToolsConsoleHelperEntityManagerHelper($entityManager) )); 

Agora você já pode acessar a pasta "bin" através do terminal e gerar o SQL necessário para criar as tabelas usadas no exemplo. Para isto, basta entrar na pasta "bin" e rodar o comando:

fonini@valhalla:$ ./doctrine orm:schema-tool:create --dump-sql

Ou se preferir, você pode deixar o Doctrine conectar ao banco de dados e criar as tabelas para você. Para isto, basta remover o trecho "–dump-sql" do comando. Criado o banco de dados, vamos testar a persistência de uma entidade (vulgo insert). Crie um arquivo com o nome de sua preferência na raíz, com o seguinte conteúdo:

 
require "bootstrap.php"; 

$cidade = new Cidade(); 
$cidade->setNome('Marau'); 
$cidade->setUf('RS'); 

$entityManager->persist($cidade); 
$entityManager->flush(); 

echo "Cidade criada com o ID ".$cidade->getId()."n"; 

Rode o arquivo. Legal, não? Se não houve nenhum erro, dê uma olhada no banco de dados, que o seu registro estará lá. Caso houve algum erro, debugue até a morte para encontrar o maldito e deixar funcionando. Veja agora um exemplo de persistência de um objeto da classe Pessoa com um objeto da classe Cidade associado:

 
require "bootstrap.php"; 

$cidade = new Cidade(); 
$cidade->setNome('Marau'); 
$cidade->setUf('RS'); 

$pessoa = new Pessoa(); 
$pessoa->setNome('Jonnas Fonini'); 
$pessoa->setDataHoraCadastro(new DateTime("now")); 
$pessoa->setCidade($cidade); 

$entityManager->persist($cidade); 
$entityManager->persist($pessoa); 
$entityManager->flush(); 

echo "Pessoa criada com o ID ".$pessoa->getId()." e associada com a Cidade ".$cidade->getId(); 

 

Localizando uma entidade pelo ID

 
require "bootstrap.php"; 

$pessoa = $entityManager->find("Pessoa", 1); 
print 'Nome: ' . $pessoa->getNome() . ' '; 
print 'Cidade: ' . $pessoa->getCidade()->getNome(); 

Outros exemplos, como listagem, atualização e exclusão você encontra no arquivo disponível no final do post.

 

Conclusão

Apesar de ser a primeira vez que uso o Doctrine 2, posso dizer que está demais! Está bem mais organizado que a primeira versão, sem contar que as anotações facilitam muito o mapeamento do banco. Pra quem não gosta, pode usar XML e YAML. O bootstrap também está mais limpo e exige bem menos configuração.

 

Download

Baixe aqui os arquivos usados no exemplo. Grande abraço a todos e até a próxima.

HTTPRequest, classe PHP para retornar conteúdo remoto em servidores sem cURL

Ataquei de freelancer recentemente, tendo que desenvolver um site. O cliente já havia contratado um popular provedor de acesso à internet da região para hospedar o site. Nada além do apocalipse poderia acontecer. Vou explicar. Os provedores da região na qual resido deveriam se preocupar um pouco mais em oferecer acesso de qualidade e deixar o ramo de hospedagem de aplicações web para quem realmente entende. O maldito servidor estava simplesmente “pelado”, extensões importantes faltando e metade dos recursos úteis desabilitados.

Aonde já se viu um servidor sem cURL? Pois é, ainda fiz uma última tentativa para obter conteúdo remoto (previsão do tempo, infelizmente ainda é uma triste realidade por aqui o cliente exigir isso no site) usando file_get_contents(), mas adivinhem: allow_url_fopen desabilitado.

Felizmente existem os sockets! Dei uma fuçada na net, juntei alguns snippets e criei uma classe para facilitar o serviço. Podem conferir o código e outras informações no meu Github.

Grande abraço!

Desenvolvendo um módulo de contato no Symfony 1.4

Olá pessoal, o tutorial de hoje mostra como fazer um módulo com formulário de contato e envio dos dados por email usando o framework Symfony. Você já deve possuir um projeto criado para poder proseeguir.

Primeiro, crie o módulo vazio com o comando abaixo:

fonini@valhalla:$ ./symfony generate:module frontend contato

O comando acima cria um módulo vazio, localizado em apps/frontend/modules/contato. O próximo passo é criar a classe do formulário. Esta classe irá conter todos os widgets (entenda por inputs, textareas, selects) que irão compor o formulário.
Crie um arquivo chamado ContatoForm.class.php em lib/form, com o conteúdo abaixo:

// lib/form/ContatoForm.class.php
class ContactForm extends BaseForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'nome'    => new sfWidgetFormInputText(),
      'email'   => new sfWidgetFormInputText(),
      'assunto' => new sfWidgetFormInputText(),
      'mensagem' => new sfWidgetFormTextarea(),
    ));

    
    $this->widgetSchema->setLabels(array(
      'nome'    => 'Nome',
      'email'   => 'Email',
      'assunto' => 'Assunto',
      'mensagem' => 'Mensagem'
    ));

    $this->setValidators(array(
      'nome'    => new sfValidatorString(array('required' => true)),
      'email'   => new sfValidatorEmail(),
      'assunto' => new sfValidatorString(array('required' => true)),
      'mensagem' => new sfValidatorString(array('min_length' => 10)),
    ));

    // se esta opção não for setada, serão geradas tabelas no HTML
    $this->widgetSchema->setFormFormatterName('list');

    $this->widgetSchema->setNameFormat('contato[%s]');
  }
}

Com a classe do formulário pronta, basta passar uma instância dela para o template. Edite o arquivo apps/frontend/modules/contato/actions/actions.class.php e adapte-o para o código que segue:

// apps/frontend/modules/contato/actions/actions.class.php
class contatoActions extends sfActions
{
  public function executeIndex(sfWebRequest $request)
  {
    $this->form = new ContatoForm();

    if ($request->isMethod('post'))
    {
      $this->form->bind($request->getParameter('contato'));

      if ($this->form->isValid())
      {
        $this->redirect('contato/enviar?'.http_build_query($this->form->getValues()));
      }
    }
  }
}

Agora definiremos o template que irá receber a instância da classe ContatoForm, gerando os campos do formulário. Edite o arquivo apps/frontend/modules/contato/templates/indexSucess.php. Faço um parentêses aqui. Para cada action que você definir (no arquivo de actions, obviamente), você pode criar um arquivo nomedaactionSuccess.php. O conteúdo desse arquivo será exibido ao executar a action (a menos que ela redirecione para outra action). Exemplo: uma action enviaemail deve possuir um template enviaemailSuccess.php.

// apps/frontend/modules/contato/templates/indexSuccess.php

<form action="<?php echo url_for('contato/index') ?>" method="post">
	<ul>
                <?php echo $form; ?>
		<li>
			<input type="submit" value="Enviar" />
                </li>
	</ul>
</form>

Você deve ter percebido que a action do formulário está apontando para contato/enviar. Ou seja, ao ser submetido, o método enviar da classe contatoActions será invocado, logo teremos que criá-lo. Volte para o arquivo apps/frontend/modules/contato/actions/actions.class.php e adicione os métodos abaixo, o método que enviará o email e o método que será chamado se o envio for bem sucedido. Mude as configurações para seu servidor de envio. Outros parâmetros na documentação da Swift Mailer.

// apps/frontend/modules/contato/actions/actions.class.php

public function executeEnviar(sfWebRequest $request)
{
  $transport = Swift_SmtpTransport::newInstance('smtp.seudominio.com', 25)
    ->setUsername('seuemail@dominio.com')
    ->setPassword('senha');

  $mailer = Swift_Mailer::newInstance($transport);

  $message = Swift_Message::newInstance()
    ->setSubject( $request->getParameter('assunto') )
    ->setFrom(array( $request->getParameter('email')  => $request->getParameter('nome') ))
    ->setTo(array('email_contato@seudominio.com.br'))
    ->setReplyTo( $request->;getParameter('email') )
    ->setBody( $request->getParameter('mensagem') );

  if ($mailer->send($message)){
    $this->redirect('contato/feito');
  }
}

public function executeFeito()
{
}

Agora basta criar um template para o método executeFeito, mostrando uma mensagem de envio bem sucedido.

<!-- apps/frontend/modules/contato/templates/feitoSuccess.php -->
<span style="font-weight: bold; font-size: 14px;">Contato enviado com sucesso. Obrigado!</span>

Tudo pronto! Teste seu módulo agora: http://localhost/seuprojeto/index.php/contato.

Download

Caso algo tenha dado errado, baixe os arquivos utilizados e coloque-os em suas devidas pastas. Melhor ainda que substituir os arquivos é tentar encontrar a causa do problema e quem sabe melhorar o módulo, que está bem básico.

Download dos arquivos

Grande abraço e até a próxima!

Enviando emails autenticados do Gmail com PHP usando Swift Mailer

O Swift Mailer é uma biblioteca para envio de emails, usando PHP5. Conheci a biblioteca inicialmente no framework Symfony, pois a mesma é responsável pela tarefa de enviar emails no framework, pois seu uso é muito simples e a biblioteca é muito poderosa.

Tenho notado que muitas empresas estão adotando cada vez mais o uso do Google Apps como serviço de webmail, dada a facilidade de uso e configuração do sistema. O primeiro exemplo mostra como enviar emails autenticados a partir de uma conta de email do Google (Gmail ou Apps) e os outros tratam do envio de mensagens com anexo. Usei a versão 4.0.6 para o exemplo.

Enviando emails com autenticação no Gmail (incluindo Google Apps)

require('lib/swift_required.php');

$transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 465, 'ssl')
	->setUsername('usuario@gmail.com')
	->setPassword('senha');

$mailer = Swift_Mailer::newInstance($transport);

$message = Swift_Message::newInstance('Assunto')
	->setFrom(array('seuemail@dominio.com.br' => 'Seu Nome'))
	->setTo(array('fulano@teste.com.br'))
	->setReplyTo('seuemail@dominio.com.br')
	->setBody('Conteudo da mensagem');

if ($mailer->send($message)){
	echo 'Mensagem enviada com sucesso';
}
else{
	echo 'Problema ao enviar mensagem. Tente novamente mais tarde';
}


Enviando emails com imagens embutidas (inline)

Útil para enviar emails com imagens que não serão bloqueadas pelos softwares leitores de email, já que estão embutidas no código e não em servidores remotos.

require('lib/swift_required.php');

$transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 465, 'ssl')
	->setUsername('usuario@gmail.com')
	->setPassword('senha');

$mailer = Swift_Mailer::newInstance($transport);

$message = Swift_Message::newInstance('Assunto')
	->setFrom(array('seuemail@dominio.com.br' => 'Seu Nome'))
	->setTo(array('fulano@teste.com.br'))
	->setReplyTo('seuemail@dominio.com.br');

$imagem_inline = $message->embed(Swift_Image::fromPath('logotipo.png'));

$message->setBody(
	'<html>'.
	' <head></head>'.
	' <body>' .
	'  <img src="'.$imagem_inline.'" alt="Imagem embutida na mensagem" />'.
	'  <br /><br />Texto da mensagem'.
	' </body>'.
	'</html>',
  'text/html' //Definimos o tipo da mensagem para text/html, ao invés de texto puro
);

if ($mailer->send($message)){
	echo 'Mensagem enviada com sucesso';
}
else{
	echo 'Problema ao enviar mensagem. Tente novamente mais tarde';
}


Enviando emails com anexo

require_once 'lib/swift_required.php';

$transport = Swift_SmtpTransport::newInstance('smtp.seudominio.com', 25)
	->setUsername('seuemail@dominio.com')
	->setPassword('senha');

$mailer = Swift_Mailer::newInstance($transport);

$message = Swift_Message::newInstance()
	->setSubject('Email com PDF anexado')
	->setFrom(array('seuemail@seudominio.com' => 'Seu nome'))
	->setTo(array('contato1@teste.org', 'contato2@teste.org'))
	->setBody('Leia a apostila em anexo')
	->attach(Swift_Attachment::fromPath('apostilas/apostila_inicial.pdf'));

if ( ! $mailer->send($message)){
	echo 'Erro ao enviar email';
}

Bom, esses foram alguns exemplos bem básicos do uso da biblioteca Swift Mailer. Consulte a documentação oficial para mais informações e um guia de referência completo.

Abraço e até a próxima!

Validadores do Doctrine (validators)

Neste post mostrarei os validadores presentes no Doctrine (usei a versão 1.2.3). O Doctrine facilita a validação dos dados, bastando apenas definir os validadores para cada atributo ao invés de fazer vários malabarismos com funções do PHP e expressões regulares. Você pode definir os validadores na classe que estende a classe base. Dessa maneira, você pode gerar o model novamente e suas alterações não serão perdidas. Primeiro listarei os validadores e ao final mostrarei alguns exemplos. Vamos lá.

Importante
Para que seus dados sejam validados, cheque se seu “bootstrap.php” contém a diretiva abaixo. Caso contrário, seus dados não serão validados.

$manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL);

notnull
Garante que o valor não seja nulo na aplicação e no banco de dados.

// models/SuaClasse.php

class SuaClasse extends BaseSuaClasse
{
    // ...

	public function setTableDefinition(){
		parent::setTableDefinition();

		// ...

		$this->hasColumn('nome', 'string', 70, array(
				'notnull' => true
			)
		);
	}
}

Válido: teste, jonnas
Inválido: valores nulos

email
Checa se o valor é um endereço de email válido.

	$this->hasColumn('email', 'string', 100, array(
				'email' => true
			)
	);

Válido: contato@fonini.net
Inválido: fulano!#@server

notblank
Este validador é semelhante ao validador notnull, exceto que além de não validar valores nulos, também não valida strings vazias.

	$this->hasColumn('nome', 'string', 100, array(
				'notblank' => true
			)
	);

Válido: teste, jonnas
Inválido: valores nulos, ”

nospace
Checa se o valor não contém espaços.

	$this->hasColumn('campo_sem_espacos', 'string', 100, array(
				'nospace' => true
			)
	);

Válido: JonnasFonini
Inválido: Jonnas Fonini

past
Checa se o valor é uma data que já ocorreu, o dia de ontem, por exemplo.

	$this->hasColumn('data', 'timestamp', null, array(
				'past' => true
			)
	);

Válido: Sendo hoje 14/09/2010, 01/01/2010 é válida
Inválido: Sendo hoje 14/09/2010, a mesma e posteriores são inválidas

future
Checa se o valor é uma data que ainda não aconteceu, amanhã, por exemplo.

	$this->hasColumn('data', 'timestamp', null, array(
				'future' => true
			)
	);

Válido: Sendo hoje 14/09/2010, 15/09/2010 é válida
Inválido: Sendo hoje 14/09/2010, a mesma e anteriores são inválidas

minlength
Checa se o valor passado tem o tamanho mínimo necessário.

	$this->hasColumn('senha', 'string', 32, array(
				'minlength' => 6
			)
	);

Válido: abcdef, abcdefg
Inválido: abc

ip
Checa se o valor passado é um endereço IP válido.

	$this->hasColumn('endereco_ip', 'string', 15, array(
				'ip' => true
			)
	);

Válido: 192.168.250.222, 201.10.34.21
Inválido: 345.10.10.290

htmlcolor
Checa se o valor passado é uma cor HTML em hexadecimal.

	$this->hasColumn('cor', 'string', 7, array(
				'htmlcolor' => true
			)
	);

Válido: #000FFF, FF0000
Inválido: #GGG111, #FFF (cores abreviadas são inválidas)

range
Checa se o valor numérico está contido no intervalo especificado.

	$this->hasColumn('idade', 'integer', null, array(
				'range' => array(18, 70)
			)
	);

Válido: 19, 50, 69
Inválido: 10, 18, 70

unique
Checa se o valor já existe no banco de dados.

	$this->hasColumn('rg', 'string', 15, array(
				'unique' => true
			)
	);

regexp
Checa se o valor casa com a expressão regular passada.

	$this->hasColumn('somente_letras', 'string', 15, array(
				'regexp' => '/^[a-zA-Z]+$/'
			)
	);

Válido: flex
Inválido: flex4

creditcard
Checa se o valor é um número de cartão de crédito válido.

	$this->hasColumn('cartao', 'integer', 16, array(
				'creditcard' => true
			)
	);

Válido: Cartões Visa, Master Card, American Express, Discover e Diners
Inválido: Outras bandeiras, exceto as citadas acima

readonly
Evita que o valor de uma coluna da tabela seja alterado.

	$this->hasColumn('coluna_que_nao_pode_ser_alterada', 'string', 25, array(
				'readonly' => true
			)
	);

unsigned
Checa se o valor númerico passado possui sinal, de menos, por exemplo.

	$this->hasColumn('idade', 'integer', 3, array(
				'unsigned' => true
			)
	);

Válido: 70, 100
Inválido: -20

usstate
Checa se a string passada é a sigla válida de um estado americano. Confira a lista completa aqui.

	$this->hasColumn('estado_americano', 'string', 2, array(
				'usstate' => true
			)
	);

Válido: AL, CA
Inválido: QW

country
Checa se a string passada é a sigla válida de um país. Confira a lista completa aqui.

	$this->hasColumn('pais', 'string', 2, array(
				'country' => true
			)
	);

Válido: br, BR, ar
Inválido: xx

No próximo post mostrarei como fazer um validador de estados brasileiros e outro de CEP’s.

Grande abraço e até a próxima!

Tutorial de Doctrine, um ORM para PHP

O Doctrine é um mapeador objeto-relacional (ORM) para PHP, aos moldes do Hibernate para Java. Este ORM elimina a tarefa de escrever consultas SQL básicas, além de facilitar a conexão com o banco de dados, através da extensão PDO. O Doctrine permite que você crie seu model escrevendo suas classes em PHP (seguindo alguns padrões pré-definidos), através de arquivos YAML ou ele se encarrega da geração do model através das tabelas já criadas no banco de dados. Uma vez que o model está criado, você pode definir validadores para campos específicos da tabela realizando essas alterações na classe gerada que estende cada model. Dessa forma, seu model fica preservado, você apenas altera a classe estendida, tornando fácil desfazer modificações.

Instalação

Baixe a última versão estável no site do projeto. No momento em que escrevo este post, a última versão estável é a 1.2.3. A versão 2 escontra-se em fase beta. Quem sabe quando ela for lançada sai mais um post. Crie uma pasta para seu projeto no seu webserver e dentro dela crie uma pasta lib. Após criar a pasta, extraia o Doctrine para ela. A estrutura deverá ficar semelhante a esta:
seu_projeto/lib/Doctrine
seu_projeto/lib/vendor
seu_projeto/lib/Doctrine.php

Crie também uma pasta chamada “models” na raíz do projeto, dando permissão 777 a ela. Esta pasta irá conter o model da aplicação.
seu_projeto/models

Configuração

Crie um arquivo na pasta raíz do projeto chamado “bootstrap.php”. Esse arquivo irá conter as configurações e parâmetros de funcionamento do Doctrine. Abaixo está o arquivo que eu uso. Você pode definir outros parâmetros consultando a documentação oficial.

// seu_projeto/bootstrap.php

require_once(dirname(__FILE__) . '/lib/Doctrine.php');

spl_autoload_register(array('Doctrine', 'autoload'));
spl_autoload_register(array('Doctrine_Core', 'modelsAutoload'));

$manager = Doctrine_Manager::getInstance();

// Configurações do banco de dados. O banco deve ser criado previamente.
$user = 'postgres';
$password = 'teste';
$host = 'localhost';
$dbname = 'testdoctrine';
$driver = 'pgsql';

$conn = Doctrine_Manager::connection($driver.'://'.$user.':'.$password.'@'.$host.'/'.$dbname);
$conn->setOption('username', $user);
$conn->setOption('password', $password);
$manager->setAttribute(Doctrine_Core::ATTR_EXPORT, Doctrine_Core::EXPORT_ALL);
$manager->setAttribute(Doctrine_Core::ATTR_MODEL_LOADING, Doctrine_Core::MODEL_LOADING_CONSERVATIVE);
$manager->setAttribute(Doctrine_Core::ATTR_AUTOLOAD_TABLE_CLASSES, true);
$manager->setAttribute(Doctrine_Core::ATTR_VALIDATE, Doctrine_Core::VALIDATE_ALL);

// Permite o override dos metodos do model.
$manager->setAttribute(Doctrine_Core::ATTR_AUTO_ACCESSOR_OVERRIDE, true);

// Formato das sequências (uso para PostgreSQL)
$manager->setAttribute(Doctrine_Core::ATTR_SEQNAME_FORMAT, '%s_seq');

// Carrega os models da pasta especificada, no caso "models"
Doctrine_Core::loadModels('models');

Criação dos models

Com seu projeto configurado, o próximo passo é criar o model. Vou mostrar as 3 formas disponíveis.

Gerando as classes através das tabelas já existentes

Crie um arquivo chamado “test.php” que será usado neste e nos próximos passos. Deverá ter o seguinte conteúdo:

require_once('bootstrap.php');

Agora, basta adicionar as linhas abaixo ao seu arquivo “test.php”. Comente-a após a geração, pois senão os models serão gerados novamente a cada execução.

// seu_projeto/test.php
require_once('bootstrap.php');

Doctrine_Core::generateModelsFromDb('models', array('doctrine'), array(
	'generateTableClasses' => true
	)
);

Você pode usar como exemplo as tabelas abaixo:

CREATE TABLE cidade (
    id bigint NOT NULL,
    nome character(500) NOT NULL,
    uf character(2) NOT NULL
);

CREATE TABLE pessoa (
    id bigint NOT NULL,
    nome character(500) NOT NULL,
    data_nasc date,
    id_cidade bigint
);

ALTER TABLE ONLY cidade ADD CONSTRAINT cidade_pkey PRIMARY KEY (id);
ALTER TABLE ONLY pessoa ADD CONSTRAINT pessoa_pkey PRIMARY KEY (id);
ALTER TABLE ONLY pessoa ADD CONSTRAINT 
pessoa_id_cidade_cidade_id FOREIGN KEY (id_cidade) REFERENCES cidade(id);

Gerando as classes através de um arquivo YAML

Um arquivo YAML (extensão .yml) é um arquivo semelhante a um arquivo XML e eu diria muito mais poderoso (minha opinião). Ao invés de tags, os dados são representados pela identação dos atributos e seus valores (o pessoal do Python ama isso). A identação deve ser feita com espaços e não com tabulações. Para mais informações, consulte a Wikipédia.
Esta forma permite representar seus models no arquivo YAML.
Se você ainda não tem classes definidas, crie o arquivo “schemas.yml” na raíz do projeto com o conteúdo abaixo:

Cidade:
  connection: 0
  tableName: cidade
  columns:
    id:
      fixed: false
      notnull: true
      primary: true
      autoincrement: true
      type: integer(8)
    nome:
      fixed: true
      notnull: true
      type: string(500)
    uf:
      fixed: true
      notnull: true
      type: string(2)
  relations:
    Pessoa:
      local: id
      foreign: id_cidade
      type: many
Pessoa:
  connection: 0
  tableName: pessoa
  columns:
    id:
      fixed: false
      notnull: true
      primary: true
      autoincrement: true
      type: integer(8)
    nome:
      fixed: true
      notnull: true
      type: string(500)
    data_nasc: date(25)
    id_cidade: integer(8)
  relations:
    Cidade:
      local: id_cidade
      foreign: id
      type: one

Caso você já tenha as classes definidas, basta remover o código de geração das classes do arquivo “test.php” e adicionar as linhas abaixo:

// seu_projeto/test.php
require_once('bootstrap.php');
Doctrine_Core::generateYamlFromModels('schema.yml', 'models');

Após executar o test.php, você verá um arquivo chamado “schema.yml” na pasta raíz, com a definição do model. Crie agora um arquivo chamado “generate.php” com o código abaixo. Esse arquivo será responsável por remover as tabelas do banco de dados (se existirem) e recriá-las com base no arquivo “schema.yml”, gerando também as classes PHP. Portanto, muito cuidado, só execute quando for estritamente necessário, sob a pena de perder os dados do banco.

// seu_projeto/generate.php
require_once('bootstrap.php');

//Remova as próximas 2 linhas caso não queira remover e criar seu banco a cada execução.
Doctrine_Core::dropDatabases();
Doctrine_Core::createDatabases();
Doctrine_Core::generateModelsFromYaml('schema.yml', 'models');
Doctrine_Core::createTablesFromModels('models');

Definindo manualmente suas classes PHP

Usarei como exemplo duas classes, Pessoa e Cidade, sendo que uma pessoa pertence a uma cidade. Basta criar dois arquivos na pasta “models”, Pessoa.php e Cidade.php, com o código abaixo:

// seu_projeto/models/Cidade.php

class Cidade extends Doctrine_Record{
	public function setTableDefinition(){
		$this->hasColumn('id', 'integer', null, array(
			'fixed' => false, 
			'notnull' => true, 
			'primary' => true, 
			'autoincrement' => true
			)
		);
		$this->hasColumn('nome', 'string', 500, array('fixed' => true, 'notnull' => true));
		$this->hasColumn('uf', 'string', 2, array('fixed' => true, 'notnull' =>; true));
	}
}
// seu_projeto/models/Pessoa.php

class Pessoa extends Doctrine_Record{
	public function setTableDefinition(){
		$this->hasColumn('id', 'integer', null, array(
			'fixed' => false, 
			'notnull' => true,
			'primary' => true, 
			'autoincrement' => true
			)
		);
		$this->hasColumn('nome', 'string', 500, array('fixed' => true, 'notnull' => true));
		$this->hasColumn('data_nasc', 'date');
		$this->hasColumn('id_cidade', 'integer');
	}

	public function setUp(){
		$this->hasOne('Cidade', array(
				'local' => 'id_cidade',
				'foreign' => 'id'
			)
		);
	}
}

Após a definição das classes, rode o arquivo “test.php” com a linha “Doctrine_Core::generateYamlFromModels(‘schema.yml’, ‘models’);”. Após rodar esse arquivo, execute o “generate.php”, que irá apagar o banco de dados, criá-lo novamente e criar as tabelas referentes as classes.

Realizando consultas

Com o model pronto, vamos realizar algumas consultas.

Inserindo pessoa com uma cidade associada a ela

$cidade = new Cidade();
$cidade->nome = 'Marau';
$cidade->uf = 'RS';
$cidade->save();

$pessoa = new Pessoa();
$pessoa->nome = "Jonnas Fonini";
$pessoa->data_nasc = date('1990-04-12');
$pessoa->id_cidade = $cidade;
$pessoa->save();

Consultando informações de uma pessoa

$pessoa = Doctrine_Core::getTable('Pessoa')->find(1);
echo 'Id: '.$pessoa->id.'<br />';
echo 'Nome: '.$pessoa->nome.'<br />';
echo 'Data Nasc.: '.$pessoa->data_nasc.'<br />';
echo 'Cidade: '.$pessoa->Cidade->nome;

Atualizando informações de uma pessoa

$pessoa = Doctrine_Core::getTable('Pessoa')->find(1);
$pessoa->data_nasc = date('Y-m-d');
$pessoa->save();

Removendo uma pessoa

$pessoa = Doctrine_Core::getTable('Pessoa')->find(1);
$pessoa->delete();

Download

Clique aqui para baixar o projeto já pronto. Mude as configurações do banco de dados no arquivo “bootstrap.php”.

Conclusão

O Doctrine facilita muito a manipulação do banco de dados em PHP, poupando a você o trabalho de escrever consultas SQL. Incremente ainda mais seus estudos estudando o framework Symfony, um dos mais completos da atualizade e que faz uso do Doctrine. Pretendo criar mais posts sobre o Doctrine em breve.
Grande abraço a todos e até a próxima.

#soudev

sfNicEditPlugin: Adicione um editor de texto rico aos seus forms no Symfony

Em meados de 2009 usei o framework Symfony para desenvolver um sistema em PHP para um projeto do qual eu era bolsista. Terminado o projeto, abandonei o framework. Agora reiniciei meus estudos e estou gostando bastante. Tanto que já desenvolvi meu primeiro plugin para compartilhar com a comunidade e já tenho muitos outros em mente.

O nome do plugin é sfNicEditPlugin. Ele adiciona uma instância do editor de texto rico NicEdit a um textarea. Esta é a primeira versão do plugin, ainda faltam alguns parâmetros que o NicEdit aceita, outros já estão disponíveis.

Você pode encontrar o plugin no meu GitHub ou na página de plugins do Symfony. Em ambos os locais você encontra instruções de instalação em inglês. Aqui no blog vou publicar a versão em português.

Instalação

Instalação (via pacote PEAR)

fonini@valhalla:$ symfony plugin:install sfNicEditPlugin

Instalação via Git

fonini@valhalla:$ git clone git://github.com/fonini/sfNicEditPlugin.git

Ou baixe o plugin aqui e extraia para a pasta plugins.

Você deve ativar o plugin, editando o arquivo config/ProjectConfiguration.class.php.

class ProjectConfiguration extends sfProjectConfiguration{
	public function setup(){
		$this->enablePlugins(array('sfDoctrinePlugin', 'sfNicEditPlugin', '...'));
	}
}

Após ativar o plugin, você deve publicar os arquivos CSS e JS utilizados por ele. Rode o seguinte comando:

fonini@valhalla:$ symfony plugin:publish-assets

Por último, limpe o cache:

fonini@valhalla:$ symfony cc

Usando o widget

Basta você editar a classe que gera o form em que você vai usar o NicEdit, por exemplo lib/form/doctrine/NewsForm.class.php.

public function configure(){
	$this->setWidget('text', new sfWidgetFormTextareaNicEdit(array('fullPanel' => true), array('cols' => 100, 'rows' => 20)));
}

Pretendo disponibilizar uma nova versão em breve com todos os parâmetros de configuração disponíves no NicEdit. Entre em contato em caso de dúvida. Abraço!

PHP – Trabalhando com PDO (PHP Data Objects)

Olá pessoal, voltando com um novo post depois de muito tempo sem blogar. Hoje vou falar sobre PDO (PHP Data Objects), uma extensão presente a partir da versão 5 do PHP que permite desenvolver códigos de acesso a banco de dados portáveis, mudando apenas uma linha de código.

Imagine um sistema cheio de mysql_connect’s, mysql_query’s e de uma hora pra outra você se vê obrigado a mudar o banco de dados pra PostgreSQL, por exemplo. Imagine o trabalho de alterar toda essa parte do seu projeto? O PDO veio para solucionar esse e muitos outros problemas, como o de SQL Injection, já que usa prepared statements (falarei deles mais abaixo). No caso de você ter um sistema inteiro usando PDO e decide mudar de SGBD, basta alterar apenas a linha de conexão com o tipo de banco de dados desejado.

Conexão

	try{
		$conn = new PDO('mysql:host=localhost;dbname=seuBD', 'root', '12345');
	}
	catch (PDOException $e){
		print 'Erro: ' . $e->getMessage();
	}

Este é um exemplo de conexão. Você pode criar uma classe utilizando o padrão Singleton, instanciando apenas uma conexão PDO para a aplicação inteira. Se você quisesse alterar o SGBD para PostgreSQL, bastava apenas substituir a palavra mysql por pgsql.

Para descobrir quais drivers do PDO você tem disponíveis, execute o código abaixo:

	foreach(PDO::getAvailableDrivers() as $driver){
		echo $driver.'<br />';
	}

Por padrão, o PDO não exibe erros de consultas escritas incorretamente, por exemplo. Para exibir os erros e facilitar o desenvolvimento da aplicação, basta passar os seguintes parâmetros ao objeto:

	$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);

Selecionando dados

Agora que você já tem seu objeto PDO instanciado, é só usá-lo para realizar as consultas. Veja um exemplo de select:

	$clientes = $conn->prepare('SELECT id, nome FROM clientes');
	$clientes->execute();

	while ($dadosClientes = $clientes->fetch()){
		echo 'ID: ' . $dadosClientes['id'] . ' Nome: ' . $dadosClientes['nome'] . '<br />';
	}


Viu como é simples? Você também pode passar parâmetros para a consulta da seguinte forma:

	$clientes = $conn->prepare('SELECT id, nome FROM clientes WHERE id = :id AND nome = :nome');
	$clientes->bindParam(':id', $_POST['id'], PDO::PARAM_INT);
	$clientes->bindParam(':nome', $_POST['nome']);
	$clientes->execute();

	while ($dadosClientes = $clientes->fetch()){
		echo 'ID: ' . $dadosClientes['id'] . ' Nome: ' . $dadosClientes['nome'] . '<br />';
	}

A grande vantagem do PDO é que se você usar prepared statements, não precisará se preocupar com SQL Injection, pois o PDO já faz todo o serviço de filtragem dos dados de entrada para você, de forma muito segura.

A lógica é a mesma para executar outros tipos de consultas. Veja um exemplo de insert:

	$clientes = $conn->prepare('INSERT INTO clientes (nome) VALUES (:nome));
	$clientes->bindParam(':nome', $_POST['nome']);
	$clientes->execute();

	if ($clientes->rowCount() > 0){
		echo 'Registro inserido com sucesso';
	}

Para mais detalhes, consulte a documentação oficial.

Espero que tenham gostado do post. Um abraço e até a próxima!

Method Chaining com PHP

Method Chaining (encadeamento de métodos) é uma técnica de programação que permite reduzir o tamanho de seus códigos. Em determinadas classes, você precisa chamar vários métodos diferentes em diversas linhas. Com method chaining, você poderá chamar esses métodos em cadeia, em somente uma linha. Por exemplo, veja a seguinte classe de formatação de strings:

class String{
	private $str;

	public function __construct($str){
		$this->str = $str;
	}
	
	public function toLower(){
		$this->str = strtolower($this->str);
	}
	
	public function capitalize(){
		$this->str = ucfirst($this->str);
	}
	
	public function bold(){
		$this->str = '<strong>'.$this->str.'</strong>';
	}
	
	public function getStr(){
		return $this->str;
	}
}

$string = new String("TEXTO DE EXEMPLO");
$string->toLower();
$string->capitalize();
$string->bold();
echo $string->getStr();

Essa é forma que grande parte dos programadores escrevem seus códigos. Com method chaining, nossa classe ficaria assim:

class String{
	private $str;
	
	public function __construct($str){
		$this->str = $str;
	}
	
	public function toLower(){
		$this->str = strtolower($this->str);
		return $this;
	}
	
	public function capitalize(){
		$this->str = ucfirst($this->str);
		return $this;
	}
	
	public function bold(){
		$this->str = '<strong>'.$this->str.'</strong>';
		return $this;
	}
	
	public function getStr(){
		return $this->str;
	}
}

$string = new String("TEXTO DE EXEMPLO");
echo $string->toLower()->capitalize()->bold()->getStr();
//Retornará <strong>Texto de exemplo</strong>;

Bem mais simples, não? A diferença da outra classe, é que você retorna a instância do objeto a cada método chamado, permitindo o encadeamento.

Bom, como você pode ver, essa classe praticamente não tem muita utilidade, mas permite ter uma boa visão de como funciona a técnica. Agora é com você.

Abraço e até a próxima!