Jaime Neto | desenvolvimento para web


Neste sábado (28 de Abril de 2012) foi realizado o FLISOL 2012 em Patos/PB. Fui convidado para dar uma palestra sobre Zend Framework junto ao pessoal do PHP-PB, e o tema que apresentei foi “PHP robusto com Zend Framework”.

PHP robusto com Zend Framework

A organização do evento está de parabéns, pois foi muito bem organizado e nós, palestrantes, fomos muito bem recebidos.
Não conhecia a cidade de Patos/PB e gostei muito. É maior e menos quente do que eu pensava – ainda assim, é bem quente!

Disponibilizei os slides da palestra no meu recém-criado slideshare: http://www.slideshare.net/jaimecpn.
Link direto para esta palestra: http://www.slideshare.net/jaimecpn/php-robusto-com-zend-framework.

Espero que o pessoal que assistiu tenha gostado, e que venha a ser útil para quem vier a acessar os slides.

 


No tags


Se você trabalha com WAMP, sabe como é chato toda vez que iniciar o Windows ter que iniciá-lo para que possa usar o Apache e o Mysql. Então, vai uma dica rápida para iniciar esses serviços junto com o Windows, que não é apenas copiar o link de inicialização do WAMP para a pasta “Inicializar” (que nem sempre funciona, ou fica exibindo aquela tela de permissão).

Eis o passo a passo:

  1. No Windows 7, clique em Iniciar e, na barra de pesquisa por programas e arquivos, digite: services.msc
  2. Clique no arquivo que vai aparecer, ou simplesmente aperte o ENTER.
  3. Na janela que se abre, procure os serviços de nome: wampapache e wampmysqld
  4. Perceba que na coluna “Tipo de Inicialização”, eles estão como “Manual”
  5. Clique com botão direito em um deles, e selecione “Propriedades”
  6. Mude “Tipo de Inicialização” para “Automático” e clique “OK”
  7. Faça o mesmo para o outro serviço.

Prontinho… quando você reiniciar o Windows os serviços já estarão inicializados. Perceba, no entanto, que o que estará sendo iniciado automaticamente são os serviços Apache e Mysql, e não o Wamp em si. Ou seja, o iconezinho dele não vai estar lá do lado do relógio. Mas você vai poder usar os serviços normalmente, e só se realmente precisar mexer no WAMP é que vai ter que iniciá-lo.

Repare que essa dica serve pra o caso de você querer inicializar automaticamente qualquer desses outros serviços da lista. Prático, não? ^__^


· ·


Precisei criar um calendário HTML dinâmico, e depois de pesquisar bastante, e achar várias soluções que não resolviam todas as minha necessidades, resolvi criar eu mesmo uma classe para isso. Ou melhor, um View Helper para Zend Framework. Assim, eu poderia usar em vários outros projetos, diante da necessidade. Só que para ele poder realmente ser bem aproveitado, precisaria ser fácil de customizar, tanto visualmente, quanto, com relação à sua funcionalidade.

Pensei então nas possíveis utilizações para uma classe dessas, e decidi que ela precisava:

  1. ser facilmente alterada, visualmente, apenas mexendo no CSS;
  2. receber e exibir diferentes formatos de data;
  3. ter opções de navegação, para se alterar o mês e ano exibidos;
  4. ter datas selecionáveis, para que se altere algo mais na página;
  5. poder, de alguma forma, receber conteúdo específico para cada data, como nome dos feriados, por exemplo.

Para resolver o primeiro ponto, decidi que a classe deveria imprimir o HTML com várias classes CSS para cada possível caso de alteração visual que possa ser necessário. Ou seja, classes que diferenciem:

  • cada dia da semana;
  • dias da semana e dias de fim de semana;
  • dia atual, dias passados e dias futuros;
  • dia selecionado pelo usuário (ponto 4);
  • dias de outro mês, mês passado e mês futuro;
  • dias definidos pelo usuário.

O segundo ponto diz respeito a como vai vir a informação que define a data selecionada, por exemplo, e as datas que o usuário irá especificar classes de css (ultimo da lista do ponto 1) e conteúdo específico (ponto 5), e também de como serão exibidos os nomes do mês em exibição, dos meses nas opções de navegação (ponto 3), e os dias da semana.

Os outros pontos são auto-explicativos. Então, vamos ver o resultado:

class My_View_Helper_Calendar extends Zend_View_Helper_Abstract
{
    protected $date;
    protected $now;
    protected $locale = 'pt_BR';
 
    const INSERT_BEFORE = -1;
    const INSERT_PREPEND = 0;
    const INSERT_APPEND = 1;
    const INSERT_AFTER = 2;
 
    protected $options = array(
        'showPrevMonthLink'  => false,
        'showNextMonthLink'  => false,
        'showOtherMonthDays' => false,
        'dateBaseUrl'        => '#',
        'monthBaseUrl'       => '#',
        'insertsPosition'    => -1
    );
 
    protected $protectedCssClasses = array(
        'selected-date', 'today', 'past-day', 'future-day',
        'other-mohth', 'prev-month', 'next-month'
    );
 
    protected $cssClasses = array();
    protected $inserts = array();
 
    static protected $formats = array(
        'input'    => 'yyyy-MM-dd',
        'weekdays' => Zend_Date::WEEKDAY_NARROW,
        'header'   => 'MMMM yyyy',
        'prevAndNextMonth' => Zend_Date::MONTH_NAME_SHORT
    );
 
    static public function getFormat($name)
    {
        return isset(self::$formats[$name])
                ? self::$formats[$name]
                : null;
    }
 
    /**
     * Set object state from options array
     *
     * @param array $options
     * @return My_View_Helper_Calendar
     */
    public function setOptions(array $options)
    {
        foreach($options as $name => $value)
        {
            // Define o formato que a data será inserida
            if ($name == 'format') {
                self::$formats['input'] = $value;
            }
 
            // Define o idioma
            if ($name == 'locale' && Zend_Locale::isLocale($value)) {
                $this->locale = $value;
            }
 
            // Define as classes de css
            if ($name == 'cssClasses') {
                $this->setCssClasses($value);
            }
 
            // Insere conteúdo nas células de acordo com as datas
            if ($name == 'inserts') {
                $this->insertHtml($value);
            }
 
            // Define a posição em que o conteúdo será inserido
            if ($name == 'insertsPosition') {
                $this->setInsertsPosition($value);
            }
 
            $this->setOption($name, $value);
        }
        return $this;
    }
 
    /**
     * Altera alguma configuração do calendário
     *
     * @param string $name
     * @param mixed $value
     * @return My_View_Helper_Calendar
     */
    public function setOption($name, $value)
    {
        if (isset($this->options[$name])) {
            $this->options[$name] = $value;
        }
 
        return $this;
    }
 
    /**
     * Retorna uma configuração pelo nome
     *
     * @param string $name
     * @return mixed
     */
    public function getOption($name)
    {
        return isset($this->options[$name])
                ? $this->options[$name]
                : null;
    }
 
    /**
     * Define as classes de CSS para datas específicas
     *
     * @param array $cssClasses
     * @return My_View_Helper_Calendar
     */
    public function setCssClasses(array $cssClasses)
    {
        foreach($cssClasses as $cssClass => $dates) {
            $this->addCssClass($cssClass, $dates);
        }
        return $this;
    }
 
    /**
     * Adiciona uma classe de CSS para uma data específica
     *
     * @param string $cssClass
     * @param mixed $dates
     * @return My_View_Helper_Calendar
     */
    public function addCssClass($cssClass, $dates)
    {
        if (!in_array($cssClass, $this->protectedCssClasses)) {
            if (is_string($dates)) {
                $dates = array($dates);
            }
            if (is_array($dates)) {
                foreach($dates as $date) {
                    if (is_string($date) && Zend_Date::isDate(
                        $date, $this->getFormat('input')))
                    {
                        $this->cssClasses[$cssClass][] = $date;
                    }
                }
            }
        }
        return $this;
    }
 
    /**
     * Retorna a lista de classes de css definidas
     *
     * @return array
     */
    public function getCssClasses()
    {
        return $this->cssClasses;
    }
 
    /**
     * Retorna todas as classes de css, tanto as padrão quanto 
     * as definidas
     *
     * @return array
     */
    public function getAllCssClasses()
    {
        return array_merge($this->protectedCssClasses,
                           array_keys($this->getCssClasses()));
    }
 
    /**
     * Adiciona conteúdo html a uma data específica
     *
     * @param string $date
     * @param string $html
     * @return My_View_Helper_Calendar
     */
    public function insertHtml($date, $html=null)
    {
        if (is_array($date)) {
            foreach($date as $d => $h) {
                $this->insertHtml($d, $h);
            }
        } else if (Zend_Date::isDate($date, $this->getFormat('input'))) {
            if (is_string($html)) {
                $this->inserts[$date][] = $h;
            } else if (is_array($html)) {
                foreach($html as $h) {
                    if (is_string($h)) {
                        $this->inserts[$date][] = $h;
                    }
                }
            }
        }
 
        return $this;
    }
 
    /**
     * Retorna o conteúdo HTML adicionado para uma data específica
     *
     * @param string $date
     * @return array
     */
    public function getInserts($date=null)
    {
        if ($date) {
            if (isset($this->inserts[$date])) {
                return $this->inserts[$date];
            }
        } else {
            return $this->inserts;
        }
    }
 
    /**
     * Define a posição do conteúdo inserido nas datas
     * -1 = Antes da tag do link da data
     * 0 = Antes da data, dentro da tag do link
     * 1 = Depois da data, dentro da tag do link
     * 1 = Depois da tag do link da data
     *
     * @param int -1 = Before, 0 = Prepend, 1 = Append, 2 = After
     * @return My_View_Helper_Calendar
     */
    public function setInsertsPosition($position)
    {
        if (in_array($position, 
            array(self::INSERT_APPEND, self::INSERT_PREPEND))) 
        {
            $this->setOption('insertsPosition', $position);
        }
        return $this;
    }
 
 
    /**
     * Returna a posição em que o conteúdo será inserido
     *
     * @return int
     */
    public function getInsertsPosition()
    {
        return $this->getOption('insertsPosition');
    }
 
    /**
     * Define a data selecionada
     *
     * @param string $date
     * @return My_View_Helper_Calendar
     */
    public function setDate($date)
    {
        if ($date instanceof Zend_Date) {
            $this->date = $date;
        } else {
            $this->date = new Zend_Date($date, self::getFormat('input'),
                                        $this->locale);
        }
        return $this;
    }
 
    /**
     * Exibe um calendário HTML
     *
     * @param string $date Data selecionada
     * @param array $options
     * @return string
     */
    public function calendar($date=null, array $options=null)
    {
        if ($options) {
            $this->setOptions($options);
        }
 
        $this->now = Zend_Date::now($this->locale);
 
        if ($date) {
            $this->setDate($date);
        } else {
            $this->date = clone $this->now;
        }
 
        return $this->render();
    }
 
    /**
     * Gera o código HTML do calendário
     *
     * @return string
     */
    public function render()
    {
        // Cria um objeto Zend_Date para a data inicial
        $dateInit = clone $this->date;
        $dateInit->setDay(1); // Altera para o primeiro dia do mês
        $weekday = $dateInit->get(Zend_Date::WEEKDAY_DIGIT);
        $month = $this->date->get(Zend_Date::MONTH);
 
        // Calcula o primeiro dia da tabela a ser exibido
        $firstDay = clone $dateInit->subDay($weekday);
 
        // Cria uma div para o calendário com um id único
        $xhtml = '<div class="calendar" '
                . 'id="calendar_' . $this->date->get('yyyy-MM') . '">'
                . '<div class="calendar-header">';
 
        // Verifica se o calendário está configurado para exibir os 
        // links de navegação para exibir o mês anterior
        if ($this->getOption('showPrevMonthLink'))
        {
            $prevMonth = clone $this->date;
 
            // Cria um objeto para o mês anterior
            $prevMonth->subMonth(1); 
 
            $xhtml .= '<span class="prev-month-link">'
                  . '<a href="'
                  . $this->getOption('monthBaseUrl')
                  . $prevMonth->get('yyyy-MM').'" title="'
                  . $prevMonth->get(self::getFormat('header'))
                  .'">'
                  . $prevMonth->get(self::getFormat('prevAndNextMonth'))
                  . '</a></span>';
        }
 
        // Insere o nome do mês atual de acordo com o formato definido
        $xhtml .= '<span class="current-month">'
                . $this->date->get(self::getFormat('header'))
                . '</span>';
 
        // Verifica se o calendário está configurado para exibir os
        // links de navegação para exibir o mês seguinte
        if ($this->getOption('showNextMonthLink'))
        {
            $nextMonth = clone $this->date;
 
            // Cria um objeto para o mês seguinte
            $nextMonth->addMonth(1); 
 
            $xhtml .= '<span class="next-month-link">'
                  . '<a href="'
                  . $this->getOption('monthBaseUrl')
                  . $nextMonth->get('yyyy-MM').'" title="'
                  . $nextMonth->get(self::getFormat('header'))
                  .'">'
                  . $nextMonth->get(self::getFormat('prevAndNextMonth'))
                  . '</a></span>';
        }
 
        // Cria a tabela do calendário
        $xhtml .= '</div><table cellspacing="0" cellpadding="0" '
                . 'border="0"><thead>';
 
        $tmpDate = clone $firstDay;
 
        // Adiciona sete colunas com os nomes dos dias da semana
        for($i=0; $i<=6; $i++) {
            $cssClasses = array();
 
            // Define as classes de CSS da semana e da coluna
            if ($i == 0) {
                // Classe CSS para a primeira coluna
                $cssClasses[] = 'column-first';
            } else if ($i == 6) {
                // Classe CSS para a última coluna
                $cssClasses[] = 'column-last';
            }
 
            // Adiciona classes CSS diferentes para dias da semana
            // e fim-de-semana
            $cssClasses[] = (in_array(
                $tmpDate->get(Zend_Date::WEEKDAY_DIGIT), array(0, 6)))
                ? 'weekend-day' : 'week-day';
 
            // Adiciona uma classe específica para cada dia da semana
            $cssClasses[] = strtolower(
                $tmpDate->get(Zend_Date::WEEKDAY_NAME, 'en_US'));
 
            // Insere as classes CSS na célula e imprime o dia da semana
            // de acordo com o formato definido
            $cssClassesString = $cssClasses 
                ? ' class="'.implode(' ', $cssClasses).'"' 
                : '';
 
            $xhtml .= '<th' . $cssClassesString . '>'
                    . $tmpDate->get(self::getFormat('weekdays'))
                    . '</th>';
 
            // Avança para o dia seguinte e repete o processo
            $tmpDate->addDay(1);
        }
        $tmpDate = $firstDay;
        $xhtml .= '</thead><tbody>';
 
        // Cria sete linhas na tabela para as semanas
        for($i=0; $i<6; $i++) {
            $xhtml .= '<tr>';
 
            // Cria sete colunas para as datas
            for($j=0; $j<=6; $j++) {
                $day = $tmpDate->get(Zend_Date::DAY);
 
                $cssClasses = array();
 
                // Define as classes de CSS da semana e da coluna
                if ($j == 0) {
                    $cssClasses[] = 'column-first';
                } else if ($j == 6) {
                    $cssClasses[] = 'column-last';
                }
 
                // Adiciona classes CSS diferentes para dias da semana
                // e fim-de-semana
                $cssClasses[] = (in_array(
                    $tmpDate->get(Zend_Date::WEEKDAY_DIGIT),array(0,6)))
                    ? 'weekend-day' : 'week-day';
 
                //Adiciona uma classe específica para cada dia da semana
                $cssClasses[] = strtolower(
                        $tmpDate->get(Zend_Date::WEEKDAY_NAME, 'en_US'));
 
                // Adiciona classes CSS diferentes para hoje, dias 
                //passados e futuros
                switch($tmpDate->compareDate($this->now)) {
                    case -1: $cssClasses[] = 'past-day'; break;
                    case 0: $cssClasses[] = 'today'; break;
                    case 1: $cssClasses[] = 'future-day'; break;
                }
 
                // Adiciona uma classe CSS para a data selecionada
                if ($tmpDate->compareDate($this->date) == 0) {
                    $cssClasses[] = 'selected-date';
                }
 
                // Adiciona classes CSS para dias de outros meses
                if ($tmpDate->get(Zend_Date::MONTH) != $month) {
                    $cssClasses[] = 'other-month';
 
                    if (!$this->getOption('showOtherMonthDays')) {
                        $day = '&nbsp;';
                    }
 
                    // Adiciona classes CSS diferentes para dias de 
                    // meses passado e futuro
                    if ($tmpDate->get(Zend_Date::MONTH) < $month) {
                        $cssClasses[] = 'prev-month';
                    } else if ($tmpDate->get(Zend_Date::MONTH)>$month){
                        $cssClasses[] = 'next-month';
                    }
                }
 
                // Insere classes CSS definidos para datas específicas
                $addCssClasses = $this->getCssClasses();
                if ($addCssClasses) {
                    foreach($addCssClasses as $cssClass => $d) {
                        if ((is_string($d) && $tmpDate->get(
                          $this->getFormat('input')) == $d) ||
                          (is_array($d) && in_array(
                          $tmpDate->get($this->getFormat('input')),$d)))
                        {
                            $cssClasses[] = $cssClass;
                        }
                    }
                }
 
                // Insere conteúdo HTML definido para datas específicas
                $htmlInserts = '';
                $inserts = $this->getInserts(
                    $tmpDate->get($this->getFormat('input')));
                if ($inserts) {
                    foreach($inserts as $insert) {
                        $htmlInserts .= $insert . PHP_EOL;
                    }
                }
 
                // Creia a célula da tabela para a data
                $xhtml .= '<td '
                        . 'id="calendar_day_' 
                        . $tmpDate->get('yyyy-MM-dd') . '" '
                        . 'class="' . implode(' ', $cssClasses) . '">'
                        . ($this->getInsertsPosition() == 
                          self::INSERT_BEFORE ? $htmlInserts : '')
                        . '<a class="day" href="'
                        . $this->getOption('dateBaseUrl')
                        . '/'
                        . $tmpDate->get('yyyy-MM-dd')
                        .'">'
                        . ($this->getInsertsPosition() == 
                          self::INSERT_PREPEND ? $htmlInserts : '')
                        . $day
                        . ($this->getInsertsPosition() == 
                          self::INSERT_APPEND ? $htmlInserts : '')
                        . '</a>'
                        . ($this->getInsertsPosition() == 
                          self::INSERT_AFTER ? $htmlInserts : '')
                        . '</td>';
 
                $tmpDate->addDay(1);
            }
            $xhtml .= '</tr>';
        }
 
        $xhtml .= '</tbody></table></div>';
 
        return $xhtml;
    }
}

Bom, a classe é essa aí. Parece muito grande pra uma coisa aparentemente tão simples, mas acredito que o importante é a praticidade que ela proporciona para fazermos o calendário da forma que queremos. Então, vamos ver como utilizá-la:

<?php echo $this->calendar('2012-02-17', array(
    // Faz com que o link para o mês passado seja exibido
    'showPrevMonthLink'  => true,
    // Faz com que o link para o mês seguinte seja exibido
    'showNextMonthLink'  => true,
    // Faz com que os dias de outros meses sejam exibidos
    'showOtherMonthDays' => true,
    // Define a url base para os links das datas
    'dateBaseUrl'        => 'http://www.jaimeneto.com/data/',
    // Define a url base para os links de navegação dos meses
    'monthBaseUrl'       => 'http://www.jaimeneto.com/data/',
    // Define uma classe css "feriado" para as datas no array
    'cssClasses' => array(
        'feriado' => array(
            '2012-02-01', // Dia do Publicitário
            '2012-02-02', // Dia de Iemanjá
            '2012-02-22', // Carnaval
            '2012-02-16',  // Dia do Repórter
            '2012-03-08'  // Dia da Mulher
        )
    ),
    // Dá um destaque maior inserindo o nome do feriado do carnaval
    'inserts' => array(
        '2012-02-22' => array('<div>Carnaval</div>')
    ),
    // Define que o html deve ser inserido dentro do link, após a data
    'insertsPosition'    => My_View_Helper_Calendar::INSERT_APPEND
)) ?>

Vamos decorar o calendário agora com CSS, aproveitando todo o esforço para separar cada parte em classes agora.

body { font-family:arial;font-size:14px }
.calendar { width: 300px;cursor:default; }
.calendar a { text-decoration:none; color:#000; }
.calendar a:hover { text-decoration:underline; }
.calendar .calendar-header { border:1px solid #000;border-bottom:0;
    background-color:#ccc;text-align:center;text-transform:uppercase;
    line-height: 20px; }
.calendar .calendar-header .current-month { font-weight:bold; }
.calendar .calendar-header .prev-month-link { float:left;margin:0 3px; }
.calendar .calendar-header .next-month-link { float:right;margin:0 3px; }
.calendar table { border-collapse:collapse;width:100%; }
.calendar table td, .calendar table th { border:1px solid #000;
    text-align:center; line-height: 20px; }
.calendar table td a { display:block; margin:2px; 
    border: 1px solid transparent; }
.calendar table .weekend-day,.calendar table th {background-color:#eee;}
.calendar table td.other-month a { color:#ccc; }
.calendar table td.today { background-color:orange; }
.calendar table td.selected-date a { background-color:yellow; }
.calendar table td.feriado a { color:blue;border-color:blue; }
.calendar table td.other-month.feriado a {color:#ccc;border-color:#ccc;}

Vamos ver o resultado disso tudo:

Beleza, agora está pronto pra uso. Só pra finalizar, e pra mostrar que dá pra fazer uma coisa realmente elegante com isso, apenas mexendo no CSS, e, neste caso, usando o plugin qTip do Jquery, vou mostrar aqui o resultado do propósito final pra eu trabalhar tanto nessa classe. Ela foi criada para o site de eventos letz.com.br, e queríamos que as datas com eventos marcadas pelo usuário ficassem destacadas no calendário, e que quando ele passasse o cursor sobre a data, aparecece os eventos num balão. Veja como ficou:

Pronto. É isso, galera. Espero que tenham gostado e que seja útil pra alguém! ~__^


·

jan/12

29

Usando o Kindle Fire no Brasil


Pois é… depois de muito tempo relutando em comprar um tablet, simplesmente por não ver utilidade real em se ter um, finalmente resolvi comprá-lo, e o escolhido foi o Kindle Fire da Amazon. Por quê? Bem… primeiro, porque os produtos da amazon são sempre de grande qualidade, segundo porque eu acho 7″ um tamanho ideal para tablet, terceiro porque o sistema é android, quarto porque o preço é extremamente atraente (199 dólares), quinto porque eu tive a sorte de que uma pessoa conhecida estava vindo dos EUA e poderia trazê-lo pra mim, já que ele não é vendido no Brasil. Acho que já tinha motivos suficientes, né? ^__^

Ok, recebido o Kindle Fire, hora de testar. Gostei demais dele, o tamanho é realmente perfeito, o touch screen excelente, super fácil e intuitivo de usar. Gostei muito da personalização que a Amazon deu para o android, apesar de alguns ícones não ficarem muito bons na tela principal, onde aparecem maiores. Achei um pouquinho mais pesado do que eu gostaria, mas nada que comprometa sua utilização. É muito bom pra ler, acho até que seja o melhor dos tablets para isso, mas diferente das outras versões do Kindle, que foram criados especificamente para leitura, este tem brilho na tela, dificultando a leitura se não tiver uma iluminação adequada, ainda assim é excelente, e para ler quadrinhos então… Viciante! Com relação ao uso de internet, fiquei impressionado com o curto espaço de tempo que ele leva pra conectar ao ser ligado, tornando-o ainda mais prático, pois não e preciso esperar muito para estar online. Ah… não posso esquecer da bateria que dura que é uma beleza! Há algumas desvantagens em relação ao Galaxy Tab: não tem 3G, nem GPS, nem Câmera e não roda jogos mais pesados, por exemplo… Mas ainda assim é muito bom, se essas coisas não fazem muita diferença pra você, ou se você não quer pagar uma fortuna pelo concorrente da Samsumg.

Ok… hora de instalar uns aplicativos. Acessando a Amazon store e… opa… que é isso? PôÔÔôôÔÔ!!!! A Amazon não deixa instalar nada a partir de um ip fora dos EUA. Fora isso, pra comprar os aplicativos é preciso ter um cartão de crédito com endereço de cobrança (billing address) estadunidense. E agora? Vamos testar o android market padrão… Humm… ele redireciona para a Amazon store, não deixa nem eu entrar no Android Market. Putz!

Bom, não gosto de fazer isso, mas a situação pede medidas desesperadas: JAILBREAK NELE! Vamos desbloquear esse danado pra poder instalar os aplicativos que eu quero. Não tenho culpa de a Amazon amarrar minhas mãos, né… >:(

Como fazer isso? Eis os passos (leve em conta que quando esse jailbreak foi feito, a versão dele era 6.2.1):

1 – Permitir instalação de software de fontes desconhecidas

Dê um toque no canto superior direito para aparecer as opções de configuração, vá em “More…”,  “Device”, e mude “Allow Installation of Applications From Unknown Sorces” para “ON”, “OK”.

2 – Instale o driver do android no seu computador

Em seu computador baixe o arquivo android.zip e descompacte em C:\Users\NOME_DO_USUARIO\.android (C:\Documents And Settings\NOME_DO_USUARIO\.android para Windows XP).

Conecte o Kindle ao seu Computador via USB (Ah… só lembrando… no meu Kindle não veio cabo nenhum acompanhando ele, veio só o carregador mesmo…, então, você deve usar um que você tenha, ou comprar um. :P ).

Clique no botão “Iniciar” do windows, clique com o botão direito em “Computador”, e clique em “Gerenciar”. Na janela que vai abrir, vá em “Gerenciador de dispositivos”. Você deverá ver o Kindle lá na lista. Clique com o botão direito nele e selecione “Atualizar Driver”, “Procurar software de driver no computador”, “Permitir que eu escolha em uma lista de drivers de dispositivo no computador”, Clique em “Com Disco…”, “Procurar…” e navegue até a pasta C:\Users\NOME_DO_USUARIO\.android\Driver\ e clique em “Abrir”. “OK”. Você verá o driver chamado “Android Composite ADB Interface”, Clique em “Avançar”, e escolha a opção para instalar de qualquer forma. Aguarde enquanto o driver é instalado. Deverá aparecer uma mensagem de sucesso. Clique em “Fechar”. Na lista de Dispositivos, você deverá ver “Android Phone” e dentro dele “Android Composite ADB Interface”, que é o seu Kindle.

3 – Baixar o Kindle ADB e certifique-se de que o driver do android foi bem instalado

Ainda em seu computador, baixe o arquivo KindleADB.zip e descompacte em C:\KindleADB.

Execute o Prompt de Comando (clicando em “Iniciar” e digite “cmd” + Enter, ou no XP, vá em “Executar” e digite “cmd”), digite as duas linhas de comandos abaixo:

cd c:\KindleADB
adb devices

Aqui deve aparecer “List of devices attatched” e o código correspondente ao seu kindle logo abaixo. Caso não apareça, reinicie o computador, remova o driver do android e tente instalá-lo novamente.

4 – SuperUser e kindleroot

Volte para o navegador e baixe os arquivos kindleroot_androidpolice.apk e superuser-2.zip.  Extraia os arquivos dentro de superuser-2.zip na pasta c:\KindleADB, sem criar uma subpasta.

Plugue novamente o kindle no seu computador (se já estiver plugado, desconecte e plugue novamente). Copie o arquivo kindleroot_androidpolice.apk para dentro da memória do kindle. Disconecte seu kindle.

5 – Instale o “Root Explorer”

Usando o navegador do Kindle Fire, acesse http://bit.ly/kindlerootexplorer, toque em “Download Now”, depois em “Slow Download”, espere 20 segundos, e finalmente em “download file now”. Você verá que aparecerá um número na barra de notificações do Kindle, no topo. Clique lá, e verá que o arquivo está sendo baixado. Quando terminar de baixar, clique nele, e depois em “Install” e em “Done”.

6 -Fazendo o “Root” no Kindle

Vá em “apps” e inicie o “Root Explorer”. Vá em “sdcard”, toque em “kindleroot_androidpolice.apk”, “Install”, “Install” novamente, “Open”, “Agree”, “Root”, “OK”. Conecte o Kindle Fire no seu computador, caso não esteja conectado.

No computador, volte para o “Prompt de comando”, na pasta do KindleADB. Certifique-se de que o kindle ainda está OK, digitando “adb devices” e vendo se ele aparece direitinho lá, e digite a seguinte lista de comandos:

adb root
adb remount
adb push su /system/xbin/su
adb shell chwon 0.0 /system/xbin/su
adb shell chmod 06755 /system/xbin/su
adb install Superuser.apk
adb reboot

Atenção, esses comandos podem mudar. Verifique no guia oficial: http://www.androidpolice.com/2012/01/17/download-burritoroot-2-gives-root-access-back-to-kindle-fire-users-after-6-2-2-update/

A partir daqui, seu Kindle Fire já deve estar “Rooted”, ou seja, desbloqueado para instalar aplicações de fontes “alternativas”.

Para se certificar de que funcionou, instale o RootCheck. Baixe o arquivo em http://www.4shared.com/android/GWe3egQr/rootcheck.htm, instale, vá em “apps”, inicie o aplicativo, “Agree”, “Verify Root Access”, “Allow”. Deve aparecer a frase “Congratulations! You have root access!” caso esteja tudo certinho.

7 – Instale o “Android Market

Para completar o tutorial, vamos instalar o aplicativo que vamos usar para acessar o Android Market, e de lá, baixar os outros aplicativos que queremos. Usando o navegdor do Kindle Fire, acesse http://bit.ly/kindlemarket, clique em “Direct Download”, clique na barra de notificação para acompanhar o download. Quando terminar, toque em “Home”, vá em “Apps”, e inicie o “Root Explorer”. Caso ainda não tenha feito, clique em “Allow” para permitir acesso de superusuário.

Navegue para sdcard”, “Download” e toque e segure em “KindleFireAndroidMarket.zip”. Selecione “Extract All”, “Go to extracted”, “KindleFireAndroidMarket”, “GoogleServicesFramework.apk”, “Install”, “Install” de novo, “Done”. Toque e segure em “Market.apk”, selecione “Move”, “Back” quadro vezes, entre em “system”, “app”. No topo você verá um botão “Mount r/w”, clique nele, ele vai mudar para “Mount R/O”. Toque em “Paste”. Procure por “Market.apk”, toque e segure, selecione “Permissions”, deixe selecionados apenas os dois primeiros quadros da primeira linha, e o primeiro quadro da segunda e da terceira linha. Toque “OK”. Novamente toque em “Market.apk” (sem segurar, dessa vez), “Install”, “OK”, “Install” novamente, “Done”. Agora faça um reboot no Kindle Fire: Aperte e segure o botão de ligar, e aperte em “Shut Down”, depois ligue novamente. Vá novamente ao “Root Explorer”, “sdcard”, “extracted”, “KindleFireAndroidMarket”, “MarketOpener.apk”, “Install”, “Install” novamente, “Done”. Toque em “Home” para ir para a tela principal, vá em “apps”, e você verá lá o ícone do Android Market. Toque nele. Ele vai pedir pra você adicionar sua conta do Google. Aperte “Yes” e prossiga com os passos. Pode demorar um pouco, então você pode voltar para a “Home” e acessar novamente o Android Market. Ufa… pronto, agora você pode instalar os aplicativos a partir do Android Market.

Caso alguma coisa dê errado, aproveite as fontes de consulta que usei no meu Kindle (vídeos em inglês):

Agora, é só desfrutar… ^__^


·


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;
         }
       }
 
       $usedAliases = array($this->_name);
 
       // Se não há campos a serem acrescentados,
       // a tabela não será acrescentada
       if (count($refTableColumns)) {
         $refTableName = $refTableClass->info(self::NAME);
 
         // Se refTable for igual a outra tabela, muda os aliases
         $refTableAlias = $refTableName;
         $i = 2;
         while(in_array($refTableAlias, $usedAliases)) {
           $refTableAlias = $refTableName . '_' . $i++;
         }
         $usedAliases[] = $refTableAlias;
 
         if (is_string($rm['refColumns'])) {
         // Se a chave primária é única
         $joinOnString =
           "{$refTableAlias}.{$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[] = "{$refTableAlias}.{$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.


·

Older posts >>

Theme Design by devolux.nh2.me