Já falei bastante aqui sobre o Zend Framework, mas estava fazendo muita falta ao menos um que falasse sobre banco de dados, não é mesmo? Nesse artigo vou falar sobre a integração do framework com o MySQL, por ser um dos mais utilizados com o PHP.
Requisitos
Para conectar ao banco, utilizarei o pdo_mysql. Para isso, é necessário habilitar no php.ini as extensões php_pdo e php_pdo_mysql.
Configurações do banco
Tudo ok no php, é necessário agora indicar pro framework como ele conectará ao mysql. Para facilitar o processo, já tem criada uma classe de Resource (parte do módulo Zend_Application, para carregar funcionalidades com o bootstrap) pronta para que o Zend_Db crie a conexão, bastando então alterar o arquivo de configurações (se criado pelo Zend_Tool, estará em application/configs/application.ini), adicionando com as suas informações:
resources.db.adapter = "pdo_mysql" resources.db.params.host = "localhost" resources.db.params.username = "username" resources.db.params.password = "password" resources.db.params.dbname = "database"
Pronto, temos uma conexão com o banco funcionando.
O Banco de Dados
Como a idéia é mostrar o funcionamento do Zend_Db com o MySQL, farei um exemplo simples com um crud de usuários. Então, será um banco simples:
CREATE TABLE IF NOT EXISTS `user` ( `usr_id` int(11) unsigned NOT NULL AUTO_INCREMENT, `usr_name` varchar(255) NOT NULL, `usr_register` date NOT NULL, PRIMARY KEY (`usr_id`) )
Models
Antes de ir para a parte realmente importante, é recomendável usar o Module_Autoloader para não precisar ficar cheio de includes em todas as nossas classes tanto de banco quanto de negócio. Então, inclua no arquivo application/Bootstrap.php:
protected function _initAutoload()
{
$autoloader = new Zend_Application_Module_Autoloader(array(
'namespace' => 'Default_',
'basePath' => dirname(__FILE__),
));
return $autoloader;
}
Para usar o Zend_Db, usarei uma abordagem semelhante ao manual do framework, de forma que separaremos as classes de negócio das classes de tabela com o uso do pattern Data Mapper. Da forma que criei, as classes de negócio devem ficar na pasta application/models, os mappers na pasta application/models/Mapper e as classes de tabela em application/models/Table.
Primeiro, a classe para a tabela de usuários (application/models/Table/User.php):
<?php
class Default_Model_Table_User extends Zend_Db_Table_Abstract {
protected $_name = 'user';
protected $_primary = 'usr_id';
}
Vamos às explicações:
- o nome: por conta do autoloader que foi colocado no Bootstrap, é necessário nomear que nem as próprias classes do framework, com base nas pastas… como o arquivo está em application/models/Table/User.php, então deve ficar Default_Model_Table_User
- todas as classes de tabela devem estender Zend_Db_Table_Abstract
- o nome da tabela deve ser obrigatoriamente setado em $_name. A chave primária não é obrigatória pois o Zend é capaz de descobrir, mas por via das dúvidas eu coloquei.
Em seguida, temos que configurar o mapper, a classe que faz a comunicação entre as classes de negócio e de tabela. Primeiro, uma classe abstrata que eu fiz para evitar repetições em sistemas maiores, e contém o que todos os mappers deveriam ter:
<?php
abstract class Default_Model_Mapper_Abstract
{
// liga o mapper à classe de tabela
protected $_dbTable;
// indica o nome da entidade (ex.: User), que é usado tanto no
// modelo quanto na classe de tabela
protected $_model;
public function getDbTable()
{
if (null === $this->_dbTable)
{
$this->setDbTable("Default_Model_Table_".$this->getModel());
}
return $this->_dbTable;
}
public function getModel()
{
if (null === $this->_model)
{
// ok, eu sei que isso daria erro... mas é justamente para forçar o erro
// para saber que faltou setar o modelo no mapper
$this->setModel('Abstract');
}
return $this->_model;
}
public function setDbTable($dbTable)
{
if (is_string($dbTable))
{
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract)
{
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
public function setModel($model)
{
$this->_model = $model;
}
}
Não vou comentar muito porque acho que os comentários já explicam… agora uma implementação para o mapper do usuario:
<?php
class Default_Model_Mapper_User extends Default_Model_Mapper_Abstract {
public function __construct()
{
$this->setModel("User");
}
public function add( Default_Model_User $user )
{
$data = array(
'usr_name' => $user->getName(),
'usr_register' => new Zend_Db_Expr('NOW()')
);
return $this->getDbTable()->insert($data);
}
public function edit( Default_Model_User $user )
{
$data = array(
'usr_name' => $user->getName()
);
return $this->getDbTable()->update($data, array('usr_id = ?' => $user->getId()));
}
public function remove( $id )
{
return $this->getDbTable()->delete( array('usr_id = ?' => $id) );
}
public function find($id, Default_Model_User $user)
{
$result = $this->getDbTable()->find($id);
if (0 == count($result)) {
return;
}
$row = $result->current();
$user->setId($row->usr_id);
$user->setName($row->usr_name);
$user->setRegister($row->usr_register);
return $user;
}
public function fetchAll()
{
$table = $this->getDbTable();
$resultSet = $this->getDbTable()->fetchAll();
$entries = array();
foreach ($resultSet as $row) {
$entry = new Default_Model_User();
$entry->setId($row->usr_id);
$entry->setName($row->usr_name);
$entry->setRegister($row->usr_register);
$entry->setMapper($this);
$entries[] = $entry;
}
return $entries;
}
}
Vendo o código, podemos perceber algumas coisas:
- através do método getDbTable(), podemos fazer várias operações de bancos de dados, tanto as da classe Zend_Db_Table quanto as do Zend_Db_Select (que no caso eu não usei)
- ao invés de fazer esse tipo de operação dentro da própria classe de negócio, é retornado um objeto da mesma: isso é muito útil em questão de manutenção, já que se precisarmos alterar algo no banco de dados ou até mesmo substituí-lo por outro componente, não precisaremos mexer na classe de negócio
- Graças a indução de tipo, o serviço do item passado torna-se bem mais fácil!
Agora só falta a classe de negócios: assim como os mappers, eu fiz uma classe abstrata. Ela ficou assim:
>?php
abstract class Default_Model_Abstract {
// define a classe de mapper ligada ao model
protected $_mapper;
// indica qual o modelo usado
protected $_model;
public function __construct(array $options = null)
{
if (is_array($options))
{
$this->setOptions($options);
}
}
public function __get($name)
{
$method = 'get' . $name;
if (('mapper' == $name) || !method_exists($this, $method))
{
throw new Exception('Invalid property');
}
return $this->$method();
}
public function getMapper()
{
if (null === $this->_mapper)
{
$model = "Default_Model_Mapper_".$this->getModel();
$this->setMapper(new $model());
}
return $this->_mapper;
}
public function getModel()
{
if (null === $this->_model)
{
$this->setModel('Abstract');
}
return $this->_model;
}
public function __set($name, $value)
{
$method = 'set' . $name;
if (('mapper' == $name) || !method_exists($this, $method))
{
throw new Exception('Invalid property');
}
$this->$method($value);
}
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value)
{
$method = 'set' . ucfirst($key);
if (in_array($method, $methods))
{
$this->$method($value);
}
}
return $this;
}
public function setMapper($mapper)
{
$this->_mapper = $mapper;
return $this;
}
public function setModel($model)
{
$this->_model = $model;
}
public function add()
{
$this->getMapper()->add($this);
}
public function edit()
{
$this->getMapper()->edit($this);
}
public function remove()
{
$this->getMapper()->remove($this->getId());
}
public function find($id)
{
$this->getMapper()->find($id, $this);
return $this;
}
public function fetchAll()
{
return $this->getMapper()->fetchAll();
}
}
Acho que não há muito o que comentar, é a mesma idéia da classe abstrata do mapper. A única observação a se fazer é que os métodos abaixo dos setters e getters poderiam estar na classe que estenderá, mas eu preferi já deixar aí para não dar mais trabalho depois.
A última classe (finalmente!) é a de usuários:
<?php
class Default_Model_User extends Default_Model_Abstract {
protected $id;
protected $name;
protected $register;
public function __construct(array $options = null)
{
$this->setModel("User");
parent::__construct($options);
}
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function getRegister() {
return $this->register;
}
public function setId($id) {
$this->id = $id;
}
public function setName($name) {
$this->name = $name;
}
public function setRegister($register) {
$this->register = $register;
}
}
Simples não? uma classe apenas com os atributos básicos, setters e getters.
Concluindo
É isso ae, foi apenas uma de tantas possibilidades que deve haver para conectar ao banco. Acredito que essa seja uma boa opção para quem quer aproveitar os recursos que o framework implementou no Zend_Application.