Jaime Neto | desenvolvimento para web

Você já se pegou numa situação em que está trabalhando em mais de um computador ao mesmo tempo e fica pra lá e pra cá trocando de teclado e mouse? Aí você pensa “Como seria bom se eu pudesse usar o mesmo teclado e o mesmo mouse para essas máquinas”. Pois isso é possivel, utilizando o software chamado Synergy. E o melhor, você pode compartilhar entre máquinas com sistemas operacionais diferentes, por exemplo, uma máquina com Windows, outra com Ubuntu e um Mac, todos usando os mesmos teclado e mouse.

Então vamos lá. Vamos ver como fazer pra utilizar o Synergy.

Versão para Windows

Vá ao site e baixe o instalador de acordo com sua versão do windows (32-bit ou 64-bit). Aqui vou usar a versão 1.4.3, que apesar de estar na versão Beta, funcionou bem nos testes que fiz.

http://synergy-foss.org/download/

Após instalado, e iniciado, você deve decidir que máquina vai ser servidora, ou seja, aquela que você vai usar o teclado e mouse para controlar as outras máquinas, e quais serão clientes.

Para a máquina servidora, clique no checkbox Server, em seguinda clique em Configure Server…

Vai aparecer um grid para você organizar a distribuição das máquinas, no centro, por padrão, fica a máquina que você está configurando, mas você pode movê-la pra outro quadro do grid. Clique na tela do canto superior direito e arraste para qualquer lugar no grid. Na imagem acima, que estou usando como exemplo, estou usando meu notebook como servidor, e ao lado direito, tenho um desktop usando Ubuntu. Essa configuração vai definir como você vai passar a controlar um computador ou o outro. No caso acima, quando eu passar o cursor do mouse além do canto direito da tela do meu nomebook, começo a controlar o desktop. Funciona exatamente como quando você utiliza dois monitores para uma mesma máquina, só que nesse caso são dois sistemas operacionais em duas máquinas distintas.

Após clicar na tela e arrastar para o grid, você deve configurar o nome da máquina cliente que acabou de adicionar. Dê dois cliques na telinha do grid, e digite o nome no campo Screen name. O nome deve ser o mesmo que você colocar quando for configurar a máquina cliente. Dê uma fuçada nas outras opções de configurações se quiser, mas não é necessário mexer mais em nada.

Clique OK e OK.

Se a máquina windows for cliente, na pimeira tela, clique no checkbox Client e digite o endereço IP da máquina servidora onde diz Name of the server. Se sua rede estiver bem configurada, em vez do ip você pode usar o nome das máquinas em vez do IP, mas com o IP é certo funcionar bem.

Clique Start. E aproveite.

 

Versão para Ubuntu

O ubuntu já tem o Synergy no repositório, o que significa que não há necessidade de baixar o instalador no site. Basta iniciar o terminal e digitar:

apt-get install synergy quicksynergy.

Fazendo isso você vai instalar o synergy, que no ubuntu funciona em linha de comando e o quicksinergy, que é o ambiente grafico para o synergy. Agora é só ir em Aplicativos > Acessorios > Synergy para iniciá-lo.

Semelhante a versão para windows, no quicksynergy você tem a máquina que está usando como servidor representada por esta casinha no centro, e quatro opções de posicionamento das máquinas, onde você pode definir os nomes delas, que deve ser o mesmo nome utilizado quando forem configuradas como máquinas clientes. Clique Executar, e sua máquina já estará funcionando como servidor. Agora é só configurar e iniciar as máquinas clientes.

Se a máquina que está usando o Ubuntu for uma máquina cliente, clique na aba Usar, no campo Hostname/endereço IP do servidor digite o IP da máquina servidora, ou o nome da máquina, se a rede estiver bem configurada, e em Nome da tela, digite o nome da máquina que você está configurando, que deve ser o mesmo nome utilizado ao configurar as máquinas clientes no Synergy na máquina servidora. Agora é só clicar em Executar e pronto. Você já pode utilizar o teclado e o mouse da máquina servidora para todos os computadores configurados como clientes.

 

· · ·

Não é muito raro vermos em alguns sites, principalmente no rodapé, a informação do tempo decorrido para exibir uma página. E nos sites em PHP, geralmente é usado a função microtime para calcular esse tempo, chamando-a antes e depois do trecho de código que queremos calcular o tempo de execução, e depois subtraindo o primeiro do último.

Isso é muito útil, principalmente para trabalharmos na melhora da performance do código. Foi daí que me deu a idéia de criar um plugin para Zend Framework para facilitar o uso dessa função, e eis o resultado:

class My_Controller_Plugin_ElapsedTime
    extends Zend_Controller_Plugin_Abstract
{
  /*
   * Constantes que serão usadas para identificar a partir de
   * que parte da execução deseja-se calcular o tempo decorrido.
   * O nome das constantes é baseado no nome das funções que
   * inicializam os valores correspondentes na propriedade
   * $_startTime, logo abaixo
   */
  const EVENT_ROUTE_STARTUP = 1;
  const EVENT_ROUTE_SHUTDOWN = 2;
  const EVENT_DISPATCH_LOOP_STARTUP = 3;
  const EVENT_PRE_DISPATCH = 4;
  const EVENT_POST_DISPATCH = 5;
  const EVENT_DISPATCH_LOOP_SHUTDOWN = 6;
 
  // Guarda o tempo de inicialização de cada parte da execução
  static protected $_startTime = array(
    self::EVENT_ROUTE_STARTUP => null,
    self::EVENT_ROUTE_SHUTDOWN => null,
    self::EVENT_DISPATCH_LOOP_STARTUP => null,
    self::EVENT_PRE_DISPATCH => null,
    self::EVENT_POST_DISPATCH => null,
    self::EVENT_DISPATCH_LOOP_SHUTDOWN => null
  );
 
  // Define o tempo de inicialização antes de as rotas serem
  // inicializadas
  public function routeStartup(
     Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_ROUTE_STARTUP] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização depois de as rotas serem
  // inicializadas
  public function routeShutdown(
     Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_ROUTE_SHUTDOWN] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização antes de iniciar o loop
  // de dispatch
  public function dispatchLoopStartup(
      Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_DISPATCH_LOOP_STARTUP] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização antes do dispatch de
  // uma action
  public function preDispatch(
      Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_PRE_DISPATCH] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização depois do dispatch de
  // uma action
  public function postDispatch(
      Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_POST_DISPATCH] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização depois de finalizar o
  // loop de dispatch
  public function dispatchLoopShutdown()
  {
    self::$_startTime[self::EVENT_DISPATCH_LOOP_SHUTDOWN] =
        $this->microtime();
  }
 
  /**
   * Retorna o tempo de inicialização de cada evento do plugin,
   * ou de um evento específico, que pode ser passado como
   * parâmetro baseado no valor das contantes desta classe
   *
   * @param int $event
   * @return mixed Float se receber $event, senão, array
   */
  public function getStartTime($event=null)
  {
    if ($event) {
      if (!isset(self::$_startTime[$event])) {
        throw new Zend_Exception('Invalid value for $event: '
           . $event);
      }
      return self::$_startTime[$event];
    }
    return self::$_startTime;
  }
 
  /**
   * Calcula o tempo decorrido de um trecho e código.
   *
   * @param int $event
   * @return float
   */
  public function getElapsedTime(
     $event=self::EVENT_ROUTE_STARTUP)
  {
    $startTime = $this->getStartTime($event);
 
    // Lança uma excessão Caso o evento não tenha sido
    // inicializado ainda
    if (!$startTime) {
      throw new Zend_Exception('Event has not been '
        . 'initialized yet');
    }
 
    // Pega o microtime atual e substrai dele o tempo
    // inicial para calcular o tempo decorrido
    $endTime = $this->microtime();
    $elapsedTime = $endTime - $startTime;
 
    return $elapsedTime;
  }
 
  /**
   * Pega o microtime em formato float, levando em conta as
   * variações sofridas pela função de acordo com a versão do
   * PHP
   */
  private function _microtime()
  {
    if (version_compare(PHP_VERSION, '5.0.0', '>=')) {
      // O parâmetro já converte o restulado da função para
      // float a partir da versão 5.0.0
      return microtime(true);
    } else {
      // Antes da versão 5.0.0, a conversão para float tinha
      // que ser feita manualmente
      list($usec, $sec) = explode(" ", microtime());
      return ((float)$usec + (float)$sec);
    }
  }
}

Ufa, parece coisa demais pra uma coisa tão simples, não é? Mas tudo isso nos dá a possibilidade de medir o tempo de execução a partir de várias partes diferentes do código, que é uma facilidade que os plugins do ZF nos oferece, então, por que não aproveitar isso pra fazer um plugin mais completo, não é?

Feito o plugin, agora vamos ao View Helper, que vai se encarregar de pegar o valor definido no plugin e subtraí-lo de um novo microtime para exibir na página:

class My_View_Helper_ElapsedTime
     extends Zend_View_Helper_Abstract
{
  /**
   * Exibe o tempo decorrido de um trecho de código
   * baseado nos valores inicializados no plugin
   * 'My_Controller_Plugin_ElapsedTime'
   *
   * @param int $precision
   * @param string|Zend_Locale $locale
   * @return float
   * @throws Zend_Exception
   */
  public function elapsedTime($precision = null,
      $locale = null, $event =
      My_Controller_Plugin_ElapsedTime::EVENT_ROUTE_STARTUP)
  {
    $pluginClass = 'My_Controller_Plugin_ElapsedTime';
 
    // Verifica se o plugin foi registrado
    if (!Zend_Controller_Front::getInstance()
            ->hasPlugin($pluginClass)) {
      throw new Exception("Plugin '{$_pluginClass}' is not '
          . 'registered");
    }
 
    // Primeiro vamos pegar o plugin lá do front controller,
    // que já foi inicializado
    $elapsedTimePlugin = Zend_Controller_Front::getInstance()
         ->getPlugin($pluginClass);
 
    // Agora vamos pegar o valor calculado do tempo decorrido,
    // passando como parâmetro uma daquelas constantes definidas
    // na classe do plugin, pra dizer a partir de que ponto do
    // código onde calcular o tempo de execução. O padrão é de
    // antes da inicialização das rotas.
    $elapsedTime = $elapsedTimePlugin->getElapsedTime($event);
 
    // Retorna o tempo decorrido formatado, levando em conta o
    // número de decimais a ser exibido e a localização, para
    // apresentar os símbolos de separação de milhares e de
    // decimais corretos
    return Zend_Locale_Format::toFloat($elapsedTime, array(
      'precision' => $precision, 'locale' => $locale));
    }
 
}

Agora é só chamar o view helper lá no script pra exibir o tempo? Não, ainda falta uma coisa. É preciso inicializar o plugin primeiro. Basta colocar lá no application.ini uma linhazinha de código, que ele será inicializado automaticamente:

resources.frontController.plugins[] = My_Controller_Plugin_ElapsedTime

Agora sim, vamos ao script phtml chamar nosso view helper e ver ele funcionando:

Essa página foi carregada em <?php echo $this->elapsedTime(2) ?> segundos

Agora vamos experimentar alterar os parâmetros pra ver os tempo decorrido a partir de diferentes trechos do código:

<ul><?php
$events = array(
  1 => 'routeStartup',
  2 => 'routeShutdown',
  3 => 'dispatchLoopStartup',
  4 => 'preDispatch',
  5 => 'postDispatch',
//  6 => 'dispatchLoopShutdown'
);
foreach($events as $eventId => $eventName) {
  echo '<li>' . $eventName . ': '
     . $this->elapsedTime(2, 'pt_BR', $eventId)
     . ' segundos</li>';
}
?></ul>

O último tipo de evento (dispatchLoopShutdown) ficou comentado por uma boa razão. Quando a view é executada, esse ponto do código ainda não foi alcançado, portanto será lançada uma excessão, como foi definido na classe do plugin. Esse caso é usado mais para fins de debug mesmo, e para ver o resultado desse cálculo, é preciso chamar o plugin diretamente do front controller. Um exemplo, é chamá-lo no index.php no fim do arquivo:

$application->bootstrap()->run();
echo Zend_Controller_Front::getInstance()
    ->getPlugin('Case_Controller_Plugin_ElapsedTime')
    ->getElapsedTime(
            Case_Controller_Plugin_ElapsedTime::EVENT_DISPATCH_LOOP_SHUTDOWN
    );

Prontinho. Façam bom proveito!

· · ·

Já por várias vezes trabalhando em sites, tive a necessidade de converter strings para um formato sem acentos, cedilha, espaços, caracteres especiais, ou letras maiúsculas. Seja pra renomear um arquivo, renomear o título de uma notícia para a url ou criar um alias para um nome de usuário, a idéia é sempre a mesma, ou no mínimo muito semelhante. Isso é chamado transliteração, ou mais comumente, em inglês transliteration.

Procurando na internet achei várias expressões regulares que fazem o trabalho, mas nenhuma dela tinha tudo que eu queria, então resolvi fazer uma classe seguindo o modelo de classes do Zend Framework, e acabei fazendo duas: uma de filtro, e um view helper, que usa o filtro.

class My_Filter_Transliterate implements Zend_Filter_Interface 
{
  public function filter($string)
  {
     // Lista de caracteres que devem ser substituídos
     $a = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ$ßàáâãäåæ@çèéêë&amp;'
        . 'ìíîïðñòóôõöøùúûüýýþÿŔŕ°ºª,.;:\|/"^~*%# ()[]{}=!?`‘’' 
        . "'";
 
     // Lista que irá substituir os caracteres acima
     $b = 'aaaaaaaceeeeiiiidnoooooouuuuybssaaaaaaaaceeeee'
        . 'iiiidnoooooouuuuyybyRrooa--------------------------' 
        . '-';
 
     // Efetua a substituição
     $string = strtr($string, $a, $b); 
 
     // Deixa tudo minúsculo
     $string = strtolower($string);
 
     // Evita hífens repetidos
     $string = preg_replace('/--+/', '-', $string); 
     return $string;
  }
}

Pronto, tá feito o filtro. Agora pra usar em qualquer lugar, é só instanciar o filtro e chamar o método filter(). Como no exemplo:

$filterTransliterate = new My_Filter_Transliterate();
$string = 'Já não era sem tempo de Jaime lançar um post novo '
        . 'nesse blog!!!';
echo $filterTransliterate->filter($string);
// ja-nao-era-sem-tempo-de-jaime-lancar-um-post-novo-nesse-blog-

Agora, só pra facilitar ainda mais, vamos criar um view helper pra usarmos nos nossos scripts.

class My_View_Helper_Transliterate 
    extends Zend_View_Helper_Abstract
{
  public function transliterate($value)
  {
      $filterTransliterate = new My_Filter_Transliterate();
      return = $filterAlias->filter($value);
  }
}

Simples assim… Agora, dentro dos arquivos de layout ou view, é só chamar como no exemplo abaixo:

<?php echo transliterate($this->post->title) ?>

· · ·

Uma das coisas chatas em se usar o Zend_Db_Table é quando você precisa de informações que se encontram em tabelas associadas à tabela que você está usando. Para isso, o Zend_Db_Table oferece a opção de você configurar a variável $_referenceMap para definir os relacionamentos entre as tabelas, e usar métodos como findDependentRowset(), ou algo do tipo find<tableclass>By<rule>(), que são na verdade métodos de Zend_Db_Table_Row (Saber mais).

A utilização desses métodos pode ser boa no caso de poucos dados, mas se você estiver querendo pegar as informações completas das tabelas associadas de uma lista muito grande de registros, a quantidade de acessos ao banco para pegar essas informações vai ser proporcional à quantidade registros. Veja o seguinte cenário como exemplo:

Você quer pegar de uma tabela Usuarios a lista dos usuários com o nome da cidade em que vivem. Sendo que em Usuarios Você só tem o código da cidade, que é chave estrangeira para a tabela Cidades, onde está o nome.

Agora imagine que você quer pegar essa informação de todos os 300 usuários da sua lista. Você teria que fazer uma consulta ao banco para pegar a lista de usuários, iterar sobre a lista e fazer uma consulta para cada usuário para pegar o nome da cidade usando os métodos de Zend_Db_Table_Row, totalizando 301 acessos ao banco de dados e muito trabalho de código.

Por esse motivo, eu sempre me perguntei porque a classe Zend_Db_Table não tinha métodos para trazer as informações das tabelas associadas juntamente com a tabela em questão. E isso é o que vai ser mostrado neste post como ser feito.

A idéia é aproveitar a estrutura original da Zend_Db_Table, sem alterá-la, para que se necessário possa ser usada da forma usual. Então, para começar, vamos criar o método principal, que vai montar o select com as joins baseadas em $_referenceMap.

Vou colocar logo o código completo e explicando com comentários:

/**
* Gera automaticamente um Zend_Db_Table_Select fazendo join nas tabelas
* baseado nas informações do atributo $_referenceMap
*
* @param array $columns Lista de campos a serem retornados
*  do banco de dados (usado para ganho de performance).
*  Se for  null, todos as colunos serão retornados.
* @param array $referenceTables Lista de "rules" das tabelas a serem
*  acrescentadas na query (usado para ganho de performance)
*  Se null, todas as tabelas serão acrescentadas na query.
* return Zend_Db_Table_Select
*/
public function selectWithJoins(array $columns = null,
 array $referenceTables = null)
{
 $select = $this->select();
 
 // Verifica se há restrições nos campos a serem acrescentados
 $retrieveColumns = '*';
 if($columns) {
   $tableCols = $this->info('cols');
   $retrieveColumns = array_intersect($tableCols, $columns);
 }
 $select->from($this->_name, $retrieveColumns);
 
 // Verifica se há tabelas a serem acrescentadas com join na query
 // baseado no $_referenceMap
 if (count($this->_referenceMap) > 0) {
   $select->setIntegrityCheck(false);
   if (!isset($tableCols)) {
     $tableCols = $this->info('cols');
   }
   foreach($this->_referenceMap as $rule=>$rm) {
     // Verifica se há restrições nas tabelas a serem
     // acrescentadas
     if (null === $referenceTables ||
       in_array($rule, $referenceTables))
     {
       $refTableClass = new $rm['refTableClass'];
 
       // Campos das tabelas a serem acrescentadas
       // são renomeadas
       // para "NomeDaRule_NomeDoCampo"
       $refTableColumns = array();
       foreach ($refTableClass->info('cols') as $col) {
         $colAlias = "{$rule}_{$col}";
         // Verifica se há restrições nos campos a
         // serem acrescentados
         if (!$columns || in_array($colAlias, $columns)) {
           $refTableColumns[$colAlias] = $col;
         }
       }
 
       // Se não há campos a serem acrescentados,
       // a tabela não será acrescentada
       if (count($refTableColumns)) {
         $refTableName = $refTableClass->info(self::NAME);
         if (is_string($rm['refColumns'])) {
         // Se a chave primária é única
         $joinOnString =
           "{$refTableName}.{$rm['refColumns']}"
           . " = {$this->_name}.{$rm['columns']}";
         } else if (is_array($rm['refColumns'])) {
           // Se a chave primária é concatenada
           $joinOnArray = array();
           foreach($rm['refColumns'] as $key=>$rc) {
             $joinOnArray[] = "{$refTableName}.{$rc} = "
             . "{$this->_name}.{$rm['columns'][$k]}";
           }
           $joinOnString = implode(' AND ', $joinOnArray);
         }
 
         $select->joinLeft(
           $refTableName,
           $joinOnString,
           $refTableColumns
         );
      }
    }
   }
 }
 
 return $select;
}

Pronto, feito o select, já é possível gerar a query com os joins e usar, por exemplo, em um Zend_Paginator, ou fazer um fetchAll() e usar o resultado normalmente.
Mas, pra ficar ainda mais prático, vamos acrescentar o método fetch lá na nossa classe e também um método find pra retornar um único registro, baseado na chave primária.

/**
* Faz uma busca no banco de dados com joins nas tabelas relacionadas
* baseado no atributo $_referenceMap
*
* @param string|array $where  OPTIONAL An SQL WHERE clause.
* @param string|array $order  OPTIONAL An SQL ORDER BY clause.
* @param array    $columns OPTIONAL Campos a serem retornados
* @param array    $referenceTables OPTIONAL Tabelas a serem acrescentadas
* @param mixed    $fetchMode OPTIONAL Formato do resultado da busca
* @return mixed     Depende do valor de $fetchMode.
*/
public function fetchWithJoins($where = null, $order = null,
array $columns = null, array $referenceTables = null,
$fetchMode = Zend_Db::FETCH_OBJ)
{
$select = $this->selectWithJoins($columns, $referenceTables);
 
if ($where !== null) {
  $this->_where($select, $where);
}
 
if ($order) {
  $select->order($order);
}
 
return $this->getAdapter()->fetchAll($select, null, $fetchMode);
}
 
/**
* Faz uma busca usando a chave primária no banco de dados com joins
* nas tabelas relacionadas baseado no atributo $_referenceMap
*
* @param string|array $pkey  Valor da chave primária
* @param array    $columns OPTIONAL Campos a serem retornados
* @param array    $referenceTables OPTIONAL Tabelas a serem acrescentadas
* @param mixed    $fetchMode OPTIONAL Formato do resultado da busca
* @return mixed     Depende do valor de $fetchMode.
*/
public function findWithJoins($pkey, array $columns = null,
array $referenceTables = null, $fetchMode = null)
{
// Ver posts anteriores para entender este método 
$where = $this->_generateRestrictionsFromPrimaryKeys($pkey);
 
$result = $this->fetchWithJoins($where, null, $columns,
        $referenceTables, $fetchMode);
return count($result) == 1 ? current($result) : $result;
}

Agora fica fácil fazer uma busca com joins nas tabelas. Vamos reaproveitar o exemplo de Usuarios e Cidades.
Digamos que essas sejam as respectivas classes:

class Cidades extends My_Db_Table_Abstract
{
 protected $_name    = 'cidades';
 protected $_primary = 'id_cidade';
 
 protected $_dependentTables = array('Usuarios');
}
 
class Usuarios extends My_Db_Table_Abstract
{
 protected $_name    = 'usuarios';
 protected $_primary = 'id_usuario';
 
 protected $_referenceMap = array(
   'Cidade' => array(
     'columns'       => 'id_cidade',
     'refTableClass' => 'Cidades',
     'refColumns'    => 'id_cidade'
   ),
 )
}

Perceba que os relacionamentos estão bem definidos em $_referenceMap. Agora vamos fazer a busca que no interessa.

$idUsuario = 1;
$tbUsuarios = new Usuarios();
$usuario = $tbUsuarios->findWithJoins($idUsuario);

Se considerarmos que a tabela usuarios só tem os campos id_usuario, nome e id_cidade, e a tabela cidades tem id_cidade, nome e estado, teremos um resultado como o seguinte:

$usuarios = StdClass(
  'id_usuario'       => 1,
  'nome'             => 'Fulano de Tal',
  'id_cidade'        => 40,
  'Cidade_id_cidade' => 40,
  'Cidade_nome'      => 'João Pessoa',
  'Cidade_estado'    => 'PB'
);

Veja que os campos de cidade ganharam um prefixo com o nome da rule que foi definida no atributo $_referenceMap. Isso é pra evitar conflito entre nomes de campos iguais, como ocorre neste exemplo com id_cidade e nome, por exemplo.
Para obter mais resultados, você pode usar da mesma forma o método fetchWithJoins().

No caso de uma classe que faça raferência a muitas outras tabelas, essa consulta pode ficar muito pesada, e muitas vezes nem nos interessa num certo momento todos aqueles dados, então, pode ser passado como parâmetro apenas as tabelas que temos interesse, ou mesmo os campos que nos interessa, diminuindo, dessa forma, a carga sobre o banco de dados, já que diminuirá significativamente a quantidade de dados a serem retornados na consulta.

No exemplo seguinte, a query retornará apenas os campos nome e Cidade_nome de todos os usuários do estado da Paraíba ordenados pelo nome da cidade:

$where = array('estado = ?' => 'PB');
$order = array('cidades.nome ASC');
$usuarios = $tbUsuarios->fetchWithJoins($where, $order, 
                         array('nome', 'Cidade_nome'));

Imaginando que a tabela Usuarios faça referências a outras tabelas, e para a consulta só me interessam os dados da tabela de cidades (além das de usuarios, claro), o exemplo seguinte mostra como limitar os joins apenas as tabelas de interesse, utilizando as rules definidas em $_referenceMap:

$order = array('usuarios.nome ASC');
$usuarios = $tbUsuarios->fetchWithJoins(null, $order, null, 
                         array('Cidade'));

Perceba que a classe como está aqui apresentada só faz join em um nível de relacionamento. Por exemplo, se a tabela Cidades fizesse referência a uma tabela Estados, os dados de estados não entrariam no resultado dos exemplos citados. Quem sabe numa versão futura…

Ok. Agora ficou fácil fazer joins sem precisar ficar montando os selects toda hora, nem ficar fazendo centenas de acessos desnecessários ao banco.

Ainda assim, isso deve ser usado com cautela, já que consultas com join também pesam no banco, devendo-se optar sempre que possível pela utilização de Views de banco de dados ou tabelas tamporárias. Vale também uma olhada nas dicas de performance no site do Zend Framework a respeito da utilização da Zend_Db_Table (Saber mais).

Espero que tenham gostado! ^__^

·

Tá… o Zend_Db_Table já me dá métodos pra inserir, alterar, excluir e fazer busca de registros. Mas… e se eu só quiser saber quantos registros tenho na tabela? Ele não tem por padrão um método para me dizer isso. Eu precisaria fazer um fetchAll() e depois um count() no resultado, ou montar uma query pra me devolver esse resultado, ou passar como parâmetro no bind uma string 'COUNT(*)'… Mas tudo isso parece pouco prático, quando poderíamos ter um método count() pra fazer isso pra nós…

Então é isso que fiz… Implementei na minha classe My_Db_Table_Abstract a interface Coutable, que ficou dessa forma:

class My_Db_Table_Abstract extends Zend_Db_Table_Abstract 
  <strong>implements Countable</strong>
{
  /* Aqui ficam os métodos citados nos posts anteriores */
 
  public function count($where = null)
  {
     $select = $this->select();
 
     if ($where !== null) {
         $this->_where($select, $where);
     }
 
     $select->from($this->_name, array('count' => 'COUNT(*)'));
 
     $count = $this->getAdapter()->fetchRow($select);
     return intval($count['count']);
  }
}

Pra ficar ainda mais completo, o método pode receber como parâmetro algumas restrições, caso não se queria contar a quantidade total, e sim algo mais específico. Agora, fica bem mais fácil:

$tbUsuarios = new Usuarios();
$qtty = $tbUsuarios->count();

Se quisermos, por exemplo, saber quantos usuários usam gmail:

$qtty = $tbUsuarios->count("email LIKE '%@gmail.com'");

·

Continuando as melhorias na utilização da Zend_Db_Table, do Zend Framework. Outra melhoria a ser feita é nos métodos update() e delete(). O que há de comum entre esses dois métodos é que ambos recebem como parâmetro uma ou mais restrições para identificar em que registro a alteração ou exclusão será feita. Sendo que a grande maioria das vezes que você usa esses métodos, o registro é relativo à chave primária da tabela, e mais raramente à alguma outra coluna. Como no exemplo:

$id = $this->_request->getPost('id');
 
$tbUsuarios = new Usuarios();
// Imaginemos que esses dados vieram de um formulário
$dados = array(
  'nome' => 'Fulaninho de Tal',
  'email' => 'fulaninho@email.com'
);
$where = $tbUsuarios->getAdapter()->quoteInto('id = ?', $id);
// Vamos atualizar o usuário
$tbUsuarios->update($dados, $where);
 
// E agora excluímos
$tbUsuarios->delete($where);

Pra não ter que toda vez montar a restrição para esses métodos, adicionei à minha classe My_Db_Table_Abstract. métodos de update e delete para um registro específico, onde o parâmetro de restrição é sempre a chave primária. Vejamos:

class My_Db_Table_Abstract extends Zend_Db_Table_Abstract
{
  protected function _filterData(array $data)
  {
    /* Permanece a mesma coisa */
  }
 
  /**
   * Gera restricoes SQL de acordo com a(s) chave(s) primária(s)
   * @param  String|array $pkey chave(s) primária(s)
   *                            [nome_do_campo => valor]
   * @return String|array SQL where
   **/
  protected function _generateRestrictionsFromPrimaryKeys($pkey)
  {
    $where = array();
    if (is_array($this->_primary)) {
      foreach($this->_primary as $key) {
        if (is_array($pkey)) {
          $where[] = $this->getAdapter()
            ->quoteInto($this->_name.'.'.$key . ' = ?',
                        $pkey[$key]);
        } else {
          $where = $this->getAdapter()
            ->quoteInto($this->_name.'.'.$key . ' = ?',
                        $pkey);
        }
      }
    } else {
      $where = $this->getAdapter()
        ->quoteInto($this->_name.'.'.$this->_primary . ' = ?',
                    $pkey);
    }
    return $where;
  }
 
  public function insert(array $data)
  {
    /* Permanece a mesma coisa */
  }
 
  public function update(array $data, $where)
  {
    $data = $this->_filterData($data);
    return parent::update($data, $where);
  }
 
  /**
   * Update de um registro único, baseado na chave primária
   * @param array $data Dados a serem atualizados
   * @param string|array $pkey Valor da chave primária
   **/
   public function updateRow(array $data, $pkey)
   {
    $where = $this->_generateRestrictionsFromPrimaryKeys($pkey);
    return $this->update($data, $where);
  }
 
  /**
   * Delete de um registro único, baseado na chave primária
   * @param string|array $pkey Valor da chave primária
   **/
  public function deleteRow($pkey)
  {
    $where = $this->_generateRestrictionsFromPrimaryKeys($pkey);
    return $this->delete($where);
  }
}

Veja que o método updateRow() é uma variação do método update(), com a diferença de que este recebe apenas o valor da chave primária, assim como o método deleteRow(). Esse valor pode ser um array, caso a tabela tenha chave primária concatenada.

O código agora resume-se a:

// para atualizar um usuário
$tbUsuarios->updateRow($dados, $id);
 
// para excluí-lo
$tbUsuarios->deleteRow($id);

Nada mais de ficar montando os “wheres” toda vez que for atualizar ou excluir um registro. Agora é só jogar o valor da chave primária lá e pronto.

·

Faz muito tempo que não posto nada no blog, então resolvi começar a compartilhar algumas soluções que venho desenvolvendo para Zend Framework, que pode ser útil pra muita gente que o utiliza.

Uma das grandes críticas que vejo sobre o ZF é o uso do Zend_Db_Table, por diversos motivos, então, para começar vou postar algumas soluções que venho desenvolvendo para facilitar meu trabalho no uso dessa abstração de banco de dados.

Para começar, uma das coisas que me aborrecia sempre que ia dar um insert ou update na tabela, era quando eu passava os dados do formulário para um desses métodos, e o ZF me jogava uma excessão dizendo que não existe uma coluna de nome tal, que foi passada no array de dados. Pra deixar mais claro, vamos pegar o seguinte cenário:

Temos uma tabela “Usuarios”, com os campos “id”, “nome” e “email”, por exemplo. E uma classe modelo Usuarios que herda de Zend_Db_Table_Abstract. Ao inserirmos os dados de um usuário, o formulário está mandando também o nome do botão “Salvar”.

$tbUsuarios = new Usuarios();
$dados = $this->_request->getPost();
/*
  Digamos que esse seja o valor de $dados:
  $dados = array(
    'nome' => 'Fulano de Tal',
    'email' => 'fulano@email.com',
    'salvar' => 'Salvar'
  )
*/
unset($dados['salvar']);
$tbUsuarios->insert($dados);

Veja que foi necessário um unset() para remover a chave que não corresponde à colunas da tabela. Óbvio que esse é um exemplo simplificado, e pode parecer uma bobagem, mas imagine um cenário maior e mais complicado onde cada chave não correspondente à uma coluna teria que ser excluída do array a fim de evitar a excessão lançada pelo Zend_Db_Table. E, acredite, isso pode se tornar um pé-no-saco, principalmente quando você tem que fazer alterações no formulário e acaba colocando mais uma chave que não corresponde à uma coluna, daí vem mais um unset().

Então, entendido o problema, vejamos a forma que encontrei para que não seja mais necessário nos preocuparmos com dados não correspondentes à colunas:

require_once 'Zend/Db/Table/Abstract.php';
 
class My_Db_Table_Abstract extends Zend_Db_Table_Abstract
{
  /**
   * Filtra os itens do array de dados, verificando se as
   * chaves correspondem a campos da tabela
   *
   * @param array $data Array de dados
   * @return array Array de dados filtrados
  **/
  protected function _filterData(array $data)
  {
    $cols = $this->info('cols');
    $filteredData = array();
    if($data) {
      foreach($data as $key=>$value) {
        if (in_array($key, $cols)) {
          $filteredData[$key] = $value;
        }
      }
    }
    return $filteredData;
  }
 
  public function insert(array $data)
  {
    $data = $this->_filterData($data);
    return parent::insert($data);
  }
 
  public function update(array $data, $where)
  {
    $data = $this->_filterData($data);
    return parent::update($data, $where);
  }
}

Agora é só fazer com que a classe Usuarios herde de My_Db_Table_Abstract e pronto.
Veja que, desta forma, as chaves do array não correspondentes à colunas da tabela serão automaticamente filtrados, e não será mais exibida excessão relativa a esse caso específico.

·

O Peacekeeper é uma ferramenta online para verificação e comparação de desempenho de browsers. Funciona de forma bastante simples, você entra no site e clica em ‘Benchmark Your Browser’. Se não tiver java instalado, ele vai pedir para instalar. Depois é só mandar rodar e esperar uns 5 minutos, no máximo. Ele vai medir o desempenho do browser que você está usando e irá fornecer um link para você usar com outros browsers e fazer o comparativo.

Eu fiz a experiência, e eis o resultado:

O Safari 4.0 e o Chrome estão de parabéns em desempenho. Isso não fez com que eu trocasse o firefox por eles, já que nenhum oferece todos os recursos (complementos) que eu gosto de usar no firefox, mas a diferença é impressionante.

Fiz o teste antes de atualizar o firefox para a nova versão (3.11) e fiz novamente depois da atualização, pra minha surpresa o desempenho caiu bastante…

O Opera que usei era uma versão que tinha instalado no meu pen drive U3. Não sei se isso influenciou muito no resultado do desempenho, mas me desapontou bastante… mais lento que o IE8!? Não dá, né…

Ainda assim, para mim o firefox permanece como a melhor opção, pois facilita muito meu trabalho graças aos complementos úteis que tenho instalados: Firebug, FirePHP, HTML Validator, Web Developper, Fireshot, IE Tab, Yslow, LoremIpsum Content Generator entre outros… Isso sem falar dos outros que não estão relacionados a meu trabalho, mas que são bastante úteis: Xmarks, TwitterFox, Gmail Manager, Better Gmail 2, Smart Bookmarks Bar, Cooliris, entre outros…

· · · · ·

mar/09

11

Instalando eeebuntu no eeepc

Pois é, comprei meu eeepc 900 depois de muito namorar com a prateleira e usando ele, percebi que o sistema operacional que vem nele, o Xandros (Linux) não é muito bom. Resolvi então testar o eeebuntu, que é uma versão do ubuntu feita especialmente para o eeepc. Pesquisando nos fórums, vi que tem alguma particularidades na instalação dele.

Antes de qualquer coisa, lembre de fazer backup de sua pasta /home e o que mais você deseja salvar do eeepc, pois vamos formatá-lo, ok?

Vamos aos passos:

1. Baixe o eeebuntu (http://www.eeebuntu.org).

O eeebuntu está disponível em três versões:

  • Standard é o mais completo, visual desktop, já com diversos aplicativos instalados, incluindo Firefox, Open Office, Banshee, Thunderbird, VLC, etc.
  • Netbook é similar ao Standard, só que com as opções mais facilmente acessíveis, criado especialmente para telas pequenas como as do eeepc. (Obs: Se você pretende instalar o compiz, esqueça essa versão…)
  • Base é a versão mais enxuta de todas, vem com pouquíssima coisa instalada, ideal para quem tem pouco espaço e para usuários avançados que preferem instalar cada coisa separadamente.

2. Fazendo boot pelo pendrive

Existem três formas básicas de se instalar um novo sistema operacional no eeepc: Usando um drive externo de CD, usando um SD Card, ou usando um pendrive. Eu usei a terceira opção.
A primeira coisa é encontrar um pendrive de pelo menos 1 GB, e gravar nele uma imagem da ISO que você baixou anteriormente.

Para gravar a imagem no pendrive, eu utilizei o programa unetbootin, pra Windows (uh!? é… vai ter que usar outro computador, ou encontre outro programa pra fazer isso… :P ), que pode ser encontrado facilmente no baixaki ou no superdownloads. Tendo baixado o programa, é só executá-lo (não precisa instalar), selecionar a imagem do eeebuntu, e selecionar o pendrive em que vai ser gravada a imagem (lembre de fazer backup do que tiver no pendrive antes, pois ele será formatado!).

Feita a imagem, plugue o pendrive no eeepc, ligue-o e na primeira tela que aparece quando é ligado, aperte ESC (repetidas vezes, pra garantir) para aparecer a tela em que você pode escolher fazer o boot pelo pendrive. Se não der certo, reinicie e na tela inicial aperte F2 (repetidas vezes) para entrar no setup. Vá em Boot, Selecione Boot Device Priority e ponha Removable Dev como 1st Boot device. Salve e saia, e tente novamente o ESC.
Pronto, o eeebuntu deverá iniciar a partir do pendrive.

3. Instalando o eeebuntu

Agora estamos no eeebuntu via pendrive. Logo de cara você vai ver o ícone de instalação, a não ser que você esteja instalando a versão Netbook, nesse caso, vá pelo gerenciador de arquivos até a pasta Desktop e clique no ícone de instalação lá.

Siga normal com a instalação até a tela onde você pode particionar os discos. Selecione a opção “manual” e apague todas as partições criadas pelo Xandros. Com os discos vazios, selecione como formato de arquivos ext2, que é mais indicado para discos SSD, pois o ext3, apesar de oferecer mais segurança aos dados, faz muito mais acessos ao disco, o que não é bom para discos SSD. Se o seu eeepc tiver dois SSD, como o meu (um de 4 GB e outro de 16 GB), selecione o primeiro para ser o /, e o segundo para ser o /home, assim você vai estar deixando seus arquivos separados dos arquivos de programas. Siga em frente. Ignore a a mensagem dizendo que não foi escolhida nenhuma partição SWAP, e termine a instalação.

Agora vamos atualizar os repositórios. Vá em Sistema -> Administração -> Canais de Software e clique para adicionar. Digite deb http://repos.eeebuntu.org intrepid main non-free contrib. Agora vá em Sistema -> Administração -> Gerenciador de Atualizações e instale as atualizações.

Pronto, seu eeebuntu deve estar rodando bonitinho no seu eeepc. E pode ficar ainda mais bonito, se você instalar o compiz, mas não faça isso se tiver instalado a versão Netbook, pois não vai dar certo.

Mais dicas nos links:
http://ubuntuforum-br.org/index.php?topic=39896.0
http://wiki.eeeuser.com/getting_ubuntu_8.04_to_work_perfectly
http://wiki.ubuntu-br.org/ColocarHomeEmNovaParticao.

· ·

mar/09

11

O início…

Olá,

Finalmente tomei coragem para criar meu blog, pois pesquisando aqui e ali na web para resolver problemas de programação, linux, e outros, me dei conta que as vezes acabo procurando a mesma coisa mais de uma vez. Foi quando pensei que seria uma boa idéia pôr todas essas informações que as vezes custo a encontrar tudo num só lugar, tanto pra facilitar minha própria vida quando precisar delas de novo, quanto para compartilhar com aqueles que procuram essas mesmas informações.

Quem quiser entrar em contato, é só mandar um email para contatoARROBAjaimenetoPONTOcom.

Abraços!

No tags

Theme Design by devolux.nh2.me