Zend_Db_Table #3 – Lógica de Persistência

O componente Zend_Db_Table possui diversos mecanismos interessantes que poucas pessoas conhecem. Neste post irei falar da classe Zend_Db_Table_Row e a lógica de persistência.

Para isto é necessário extender a classe Zend_Db_Table_Row e configura-la na table pelo atributo $_rowClass ou usando o método $table->setRowClass($className). A row fornece 6 métodos em branco para serem sobrescritos de acordo com a necessidade. Para cada uma das operações insert, update e delete, existe um método que é executado antes e outro depois.

_insert
executado antes do insert
_postInsert
executado após o insert
_update
executado antes do update
_postUpdate
executado após o update
_delete
executado antes do delete
_postDelete
executado após o delete
 
class User extends Zend_Db_Table_Row 
{
    /**
     * Criptografa a senha do usuário.
     *
     * @param string $password
     * @return string
     */
    static public function makePassword($password)
    {
        return md5($password);
    }    
    
    /**
     * Executa antes de inserir a row.
     *
     */
    protected function _insert()
    {
        // define o grupo de usuario padrao
        $this->role = 'member';
        
        // seta a data de registro usando uma expressao do banco de dados
        $this->register_date = new Zend_Db_Expr('NOW()');
        
        // armazena a senha com md5
        $this->password = self::makePassword($this->password);
    }
    
    /**
     * Executa antes de atualizar a row.
     *
     */
    protected function _update()
    {
        /**
         * Se a senha foi alterada, precisa criptografar novamente.
		 * Verifica isso usando o array $_cleanData que contém os dados do banco
		 * sem alterações.
         */

        // a senha atual é diferente da que estava no banco? 
        if ($this->_cleanData['password'] != $this->password) {
            // criptografa a nova senha
            $this->password = self::makePassword($this->password);
        }
    }    
}

class Users extends Zend_Db_Table 
{
    protected $_rowClass = 'User';
}

Com a lógica definida, basta usar tranquilamente de qualquer forma que a table permite. Seja utilizando os métodos da table ou da row, a lógica sempre será executada.

$users = new Users();

// criando um usuario
$user = $users->createRow();
$user->username = 'deco';
$user->password = '12345';
$user->save(); // insert

// atualizando
$user->password = 'novasenha';
$user->save(); // update

// inserindo usando metodos da table
$user2 = array(
    'password' => '4321',
    'username' => 'joselito'
);
$users->insert($user2);

Utilizar este componente corretamente ajuda a diminuir os riscos de bugs da aplicação. Estas operações são garantidas também no relacionamento de tabelas, assunto que irei falar no próximo post da série.

Zend_Db_Table #2 – Consultando com Zend_Db_Table_Select

O componente Zend_Db_Table possui um mecanismo de consultas orientado a objetos que torna muito mais prático recuperar registros do banco. Esse mecanismo utliza o objeto Zend_Db_Table_Select e alguns métodos da própria table. Veja a assinatura do método fetchAll:

/**
 * Fetches all rows.
 *
 * Honors the Zend_Db_Adapter fetch mode.
 *
 * @param string|array|Zend_Db_Table_Select $where  OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
 * @param string|array                      $order  OPTIONAL An SQL ORDER clause.
 * @param int                               $count  OPTIONAL An SQL LIMIT count.
 * @param int                               $offset OPTIONAL An SQL LIMIT offset.
 * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
 */
public function fetchAll($where = null, $order = null, $count = null, $offset = null);

São 4 parâmetros e mesmo assim, em várias situações você precisaria de mais alguns. Repare que o primeiro parâmetro $where também aceita o objeto Zend_Db_Table_Select, que será a forma que iremos ver a seguir. Ignore todos os parâmetros deste método pois são deprecated. Considere o método fetchAll como abaixo:

/**
 * Fetches all rows.
 *
 * Honors the Zend_Db_Adapter fetch mode.
 *
 * @param Zend_Db_Table_Select $select  OPTIONAL Zend_Db_Table_Select object.
 * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
 */
public function fetchAll(Zend_Db_Table_Select $select = null);

Para criar o objeto Zend_Db_Table_Select basta utilizar o método select() da table.

$table = new MyTable();
$select = $table->select();

O objeto select possui diversos métodos para criar uma consulta, veja os principais.

public function where($cond, $value = null, $type = null);
public function orWhere($cond, $value = null, $type = null);
public function order($spec);
public function limit($count = null, $offset = null);
public function limitPage($page, $rowCount);

Na prática:

class Users extends Zend_Db_Table 
{ }

$users = new Users();

$countryId = 1;
$select = $users->select()
                ->where('country_id = ?', $countryId)
                ->order('username asc')
                ->limit(0, 10);

$rowset = $users->fetchAll($select);

Repare que o objeto select utiliza Fluent Interface. O código acima é equivalente a SQL:

SELECT * FROM `users`
WHERE country_id = '1'
ORDER BY `username` ASC
LIMIT 0,10

A classe table possui outro método de pesquisa chamado fetchRow que funciona parecido com o fetchAll, a única diferença é que retorna apenas uma row ou NULL ao invés do rowset.

$select = $users->select()
                ->where('email = ?', $email);

$user = $users->fetchRow($select);

if ($user != null) {
    echo $user->username; 
}

Apesar dos métodos fetchAll e fetchRow serem públicos, é mais coerente escrever códigos com select apenas na table.

class Users extends Zend_Db_Table 
{
    
    public function fetchRowByEmail($email)
    {
        $select = $this->select()
                       ->where('email = ?', $email);

        return $this->fetchRow($select);        
    }
}

Consulte o manual do Zend Framework sobre Zend_Db_Table e Zend_Db_Select para ver outros comandos. No próximo post irei escrever sobre lógica de persistência com Zend_Db_Table.

Zend_Db_Table #1 – Introdução

Zend_Db_Table é um componente do Zend Framework que implementa a pattern Table Data Gateway.

Vejamos um exemplo de tabela no MySQL:

CREATE TABLE `test`.`category` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(60) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE (`name`)
)

Agora a representação da tabela no PHP com Zend_Db_Table:

require_once 'Zend/Db/Table.php';

class Categories extends Zend_Db_Table 
{
    protected $_name = 'category';
}

Desta forma você já pode utilizar os diversos métodos da classe Zend_Db_Table. Além da pattern Table Data Gateway o pacote implementa a pattern Active Record para representar os registros da tabela com a classe Zend_Db_Table_Row. Veja abaixo alguns exemplos da utilização da Table e Row:

// iniciando o banco
$config = array(
    'dbname'     => 'test',
    'host'		 => 'localhost',
    'username'	 => 'deco',
    'password'	 => '123'
); 
require_once 'Zend/Db.php';
$db = Zend_Db::factory('MYSQLI', $config);

// setando o banco para ser utilizado em todas as table's
require_once 'Zend/Db/Table.php';
Zend_Db_Table::setDefaultAdapter($db);

// criando a tabela de categorias
$categories = new Categories();

// procurando uma categoria pelo ID
$cat = $categories->find(1)->current();
if ($cat) {
    echo $cat->name;
}

// procurando várias categorias pelos ID's
$cats = $categories->find(array(1, 2, 3));
foreach ($cats as $cat) {
    echo $cat->name;
}

// Criando uma nova categoria
$cat = $categories->createRow();
$cat->name = 'Programação';
$cat->save();

// atualizando os dados de uma categoria
$cat = $categories->find(1)->current();
if ($cat) {
    $cat->name = 'Programming';
    $cat->save();
}

// excluindo uma categoria
$cat = $categories->find(1)->current();
if ($cat) {
    $cat->delete();
}

No próximo post irei falar sobre os métodos de busca e Zend_Db_Table_Select.

Compactando a Saída do PHP com GZIP

Uma maneira de reduzir o tempo de carregamento de uma página é reduzindo seu tamanho. Entre tantas maneiras, a melhor é usar o mod deflate do apache. Mas só é válido se o seu servidor tiver o mod instalado. Caso contrário, umas das melhores alternativas seria utilizar o ob_gzhandler do PHP.

A maneira mais fácil de ativar é colocando a linha abaixo no .htaccess:
php_value output_handler ob_gzhandler

Caso seu servidor não suporte arquivos .htaccess, basta incluir o código abaixo no início do arquivo PHP:

ob_start('ob_gzhandler');

Para testar se está funcionando corretamente visite a ferramenta online GIDZipTest.

Veja também a função ob_gzhandler no manual PHP.net.

Problemas com caracteres e Zend_Form

Um problema comum para quem está começando a usar o Zend_Form é o escape de aspas. Na verdade o Zend_Form não vem com nenhum filtro ativado por padrão. Isso ocorre por causa da configuração magic quotes que vem habilitada no PHP. Segundo o manual do php é uma feature deprecated e será removida na versão 6.0. Para que o Zend_Form funcione corretamente você deve desativar esta funcionalidade em seu PHP.ini ou adicionar php_value magic_quotes_gpc off no seu arquivo .htaccess e os dados do Zend_Form serão retornados corretamente.