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.

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 é 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.

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.

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.

Divulgação de Sites

Setembro 26, 2008

Passiando pela web acabei encontrando um excelente artigo sobre divulgação de sites. Explica sobre buscadores, meta tags, popularidade, PageRank do Google, adWords, diretórios e várias dicas para você botar em prática. Vale a pena dar uma lida.

A compatibilidade com os navegadores praticamente se estabilizou, eis que surge o Google Chrome e os desenvolvedores se perguntam “mais um?”. Leia o artigo History of the browser user-agent string e descubra um pouco da história dessa palhaçada toda.

Ao contrário que muitos imaginam, o componente Zend_Loader não foi feito para substituir o required_once. Ele serve para carregar arquivos e classes dinamicamente de forma segura. Os casos normais de includes com os nomes de arquivos estáticos devem ser mantidos como abaixo.

require_once 'Zend/Controller/Front.php';
include 'hello.phtml';

O Zend_Loader deve ser usado quando o nome do arquivo for variável.

require_once 'Zend/Loader.php';

$className = 'Db_' . $_GET['adapter'];
Zend_Loader::loadClass($className);

$hello = $_GET['hello'] . '.phtml';
Zend_Loader::loadFile($hello);

Você pode passar um segundo parâmetro $dirs como string ou array de strings contendo diretórios para pesquisar.

$dirs = 'my/lib/';
Zend_Loader::loadClass($className, $dirs);

E para o método loadFile você pode passar um terceiro parâmetro $once como bool para utilizar como include_once.

Existe também o método registerAutoload para carregar as classes automaticamente de acordo com o padrão de classes e diretórios (My_Class => My/Class.php).

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

$front = Zend_Controller_Front::getInstance();

Você pode criar sua classe de autoload bastando adicionar o método estático autoload e registrar a classe no Zend_Loader.

class MyLoader
{
    static public function autoload($class)
    {

    }
}

require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload('MyLoader');

O autoload é recomendado para a parte concreta do projeto, pois ajuda no desenvolvimento e tráz melhorias de performance. Na implementação de componentes deve se mander o uso do require_once.

Principais novidades que eu vi até agora:

  • File upload Form Element
  • Captcha Form Element
  • Zend_Session save handler para banco de dados
  • Integração com o Dojo
  • SOAP
  • Componente de paginação
  • Sistema de log com integração ao plugin FireBug do Firefox (veja este post)

Várias outras features foram adicionadas ao framework. Para baixa-lo visite a página de download do zend framework.

O 000webhost.com é um host gratuito com PHP 5, mysql, cPanel, 250 MB de espaço e 100 GB de transferência mensal. Além de aceitar várias configurações que os outros servidores não aceitam, incluindo arquivos .htaccess para usar rewrite do apache, PHP mail() e SendMail, sockets, possibilidade de utilizar vários domínios e muito mais. Tudo isso sem qualquer anúncio, vale a pena conferir.