KISS - Keep it simple, stupid!

Inserindo índices automaticamente nos relacionamentos

postado por Lucas Gameiro em 07/06/2011 11:27:01

Durante o desenvolvimento de uma aplicação legada, percebi que muitos gastos do banco de dados era devido a falta de índices nas tabelas do banco de dados.

Para não criar todos os índices na mão resolvi desenvolver um shell para o cakephp que os insira sempre que um relacionamento for detectado.

O script pode ser encontrado aqui no meu github.

Segue o código para quem não trabalha com git:

<?php
App::import('Model');
class InsertIndexShell extends Shell {
    public function main() {
        $models = $this->getModels();
        $this->insertIndexes($models);
    }
 
    private function getModels()
    {
        return App::objects('model');
    }
 
    private function insertIndexes($models)
    {
        foreach($models as $model)
        {
            App::import('Model',$model);
            $this->{$model} = new $model();
            $this->{$model}->recursive = -1;
            $this->fieldsToIndex($this->{$model});
        }
    }
 
    private function fieldsToIndex($modelObj)
    {
        foreach($modelObj->belongsTo as $relacionamento)
        {
            $field = $modelObj->_schema[$relacionamento['foreignKey']];
            if(!empty($field) && !array_key_exists('key',$field))
            {
                if($modelObj->query("ALTER TABLE `{$modelObj->tablePrefix}{$modelObj->table}` ADD INDEX (`{$relacionamento['foreignKey']}`)"))
                {
                    $this->out("Indice {$relacionamento['foreignKey']} adicionado em {$modelObj->table}");
                }
                else
                {
                    $this->out("impossivel adicionar indice {$relacionamento['foreignKey']} em {$modelObj->table}");
                }
            }
        }
    }
}
?>

Obs1.: Está específico para o MySQL.
Obs2.: Na próxima versão verificarei as colunas que estão sendo utilizadas nos conditions.

Tags: , , , , ,
Topo

Sem comentários

CakePHP Component para o Google URL Shortener

postado por Fabrício Ferracioli em 19/04/2011 22:57:06

Fala Galera!

Hoje comecei um Component para o CakePHP que utiliza as funções básicas do Google URL Shortener, ou goo.gl.
Essas duas funções já estão prontas, e futuramente pretendo adicionar a função de estatísticas de cliques, também disponível na API.

O projeto se encontra nesse repositório do GitHub. Espero que seja útil para vocês, e caso alguém tenha alguma opinião ou dúvida, utilize os comentários.

Update: um exemplo de utilização sempre cai bem, não é?

<?php
class ExamplesController extends AppController
{
    var $name = 'Examples';
    //sua chave gerada pelo google pode ser fornecida aqui
    var $components = array('GoogleUrlShortener' => array('apiKey' => 'sua-chave-da-api-aqui'));
 
    function action()
    {
        //essa é outra maneira de fornecer sua chave da api
        $this->GoogleUrlShortener->apiKey = 'sua-chave-da-api';
        $results = $this->GoogleUrlShortener->generateShortUrl($this->data['longUrl']);
        $original = $this->GoogleUrlShortener->getOriginalUrl($results['id']);
    }
}
?>

Agora sim, fica simples de usar!

Tags: , , , ,
Topo

Sem comentários

Lib para lidar com arquivos Zip no CakePHP

postado por Gabriel Gilini em 26/04/2010 17:11:42

Em um projeto recente, precisei extrair arquivos zip no server, e no
processo criei uma classe para abstrair a manipulação dos arquivos.

Como no Dreamhost o PHP não vem com a zlib habilitada, não tem como
usar a ZipArchive, o que facilitaria muito as coisas. Mas o bom é que
o zlib é instalado no server, e os comandos do PHP de execução de
programas são habilitados, então criei a classe utilizando o `exec’
para chamar o `unzip’ e extrair os arquivos.

A classe está bem simples porque só implementei o necessário para meu
projeto, mas é um esqueleto para quem precisar de algo mais completo.

Evitei o uso de expressões regulares por questão de performance. Aí vai o código:

Coloquem no diretório APP/lib, e usem assim:

Tags: , , ,
Topo

2 Comentários

Validação de campos de texto com caracteres acentuados

postado por Fabrício Ferracioli em 02/04/2010 14:00:56

Recentemente tive um problema com a validação de um campo textual que me deu um pouco de dor de cabeça. Como acredito que esse pode ser um problema comum, vou compartilhar a solução aqui no blog.
Toda entrada textual deve ser representada com uma codificação de caracteres específica. Essas codificações são diversas, mas as mais conhecidas e utilizadas são ASCII, ISO 8859-1 e UTF-8. Cada uma possui diferentes capacidades e características, mas já adianto que a mais atraente delas é o UTF-8 (ou Unicode), sendo inclusive uma recomendação de utilização do W3C.

Todo programador também sabe da importância de validar uma entrada do usuário antes de realizar qualquer operação com ela, e uma regra de validação comum é a quantidade de caracteres em uma entrada de texto. O CakePHP fornece regras de validação como o minLength, maxLength e between para facilitar a vida do programador.

Agora imagine que você precisa validar um campo textual que deve ter entre 5 e 10 caracteres. Simples, defina a seguinte regra em seu modelo:

var $validate = array(
    'campo' => array(
        'rule' => array('between', 5, 10),
        'message' => 'Este campo precisa ter entre 5 e 10 caracteres.'
    )
);

Perfeito!

Calma que não é bem assim. Imagine que o campo foi preenchido com o valor ‘php é foda’, uma string de tamanho 10. Curiosamente, essa string não passa na regra de validação. Por que?

Ao observar a documentação das três regras que mencionei, vocês irão perceber que existe uma observação dizendo que o tamanho do dado é a quantidade de bytes utilizada para representá-lo. Na Web a maior parte dos textos é codificada em UTF-8, ISO-8859-1, entre outros encodings, que podem utilizar mais de um byte para representar caracteres acentuados, e esse é o motivo da regra de validação não funcionar para esse caso.

Também não adianta usar a função strlen() do PHP, porque ela possui o mesmo comportamento.

O que fazer então?

É claro que existe uma alternativa, que é a função mb_strlen. Ela recebe 2 parâmetros, sendo o 2 opcional, mas de grande importância, que é justamente a codificação utilizada para a string do primeiro parâmetro.

O código

    mb_strlen('php é foda', 'utf-8');

retorna exatamente 10, o valor que desejamos.

Desse modo, nossa função de validação seria:

function validateInputLength($input)
{
    $encoding = mb_detect_encoding($input['campo']);
    $lowerLimit = 5;
    $upperLimit = 10;
    return mb_strlen($input['campo'], $encoding) >= $lowerLimit && mb_strlen($input['campo'], $encoding) <= $upperLimit;
}

Note o uso da função mb_detect_encoding, o que torna a função capaz de manipular qualquer tipo de string, não sendo dependente de nenhuma codificação de caracteres.

Apesar de ser uma questão simples, acredito que essa solução pode ajudar bastante.
Alguém já teve problemas semelhantes? Como resolveram?

Tags: , , , ,
Topo

6 Comentários

Redirecionamento de Erros: descubra os 404 e diminua a insatisfação do usuário

postado por Lucas Gameiro em 22/03/2010 16:55:43

Uma das coisas mais frustrantes da navegação na internet é quando você vê um link que te interessa e quando clica é redirecionado pra um erro.

O 404 é o mais comum deles, isso porque ele é muito fácil de acontecer. Se alguém citou uma página do seu site que não existe mais, ou algum erro no script constroi uma url errada dinamicamente o 404 é invevitável você não vai percebê-lo rapidamente.

O CakePHP através do método link do helper Html já previne alguns problemas porém ainda não é possível administrar os erros de maneira fácil.
Pensando nisso, eu desenvolvi um sistema para informar quando os 404 acontecem e criar redirecionamentos para que eles não se repitam. Isto não é difícil de ser feito já que o CakePHP tem boas maneiras de controlar este erro.

A primeira coisa a se fazer é criar a tabela onde ficaram armazenados estes erros.

CREATE TABLE `redirects` (
    `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `num_errors` int(10) UNSIGNED NOT NULL,
    `page_from` varchar(255) NOT NULL,
    `page_to` varchar(255),
    `num_redirects` int(10) UNSIGNED,
    PRIMARY KEY (`id`)
)

Após a tabela criamos o modelo com métodos para contar e manipular os erros e os redirecionamentos: (/models/redirect.php)

<?php
class Redirect extends AppModel {
    var $name = 'Redirect';  
    public function manageError($url){
        $redirects = $this->find(
            'first',
            array(
                'conditions' => array('Redirect.page_from' => $url)
            )
        );
        if(!$redirects){
            $redirects['Redirect']['num_errors'] = 0;
            $redirects['Redirect']['page_from'] = $url;
            $redirects['Redirect']['num_redirects'] = 0;
            $redirects['Redirect']['page_to'] = null;
        }
        $redirects['Redirect']['num_errors']++;
        $this->set(
            $redirects
        );
        return $this->save();
    }
    public function manageRedirect($urlfrom, $urlTo){
        $redirect = $this->find(
            'first',
            array(
                'conditions' => array(
                    'Redirect.page_from' => $urlfrom
                )
            )
        );
        $redirect['Redirect']['num_redirects']++;
        $this->set($redirect);
        return $this->save();
    }
}
?>

Com isso, já podemos criar o armazenamento, redirecionamento e contagem dos erros. Esta será feita escrevendo a classe AppError que é feita exatamente para manipular estes erros. (/app_error.php)

<?php
    class AppError extends ErrorHandler{
        var $Error;
        public function error404($params){
            $this->Redirect = ClassRegistry::init('Redirect');
            $page = $this->Redirect->find(
                'first',
                array(
                    'conditions' => array(
                        'page_from' => $params['url']
                    )
                )
            );
            if($page && $page['Redirect']['page_to']){
                $this->Redirect->manageRedirect(
                    $params['url'],
                    $page['Redirect']['page_to']
                );
                $Dispatcher = new Dispatcher();
                $Dispatcher->dispatch($page['Redirect']['page_to']);
            }
            else{
                $this->Redirect->manageError($params['url']);
                $this->controller->set('url', $params['url']);
                $this->_outputMessage('error404', $params);
            }
        }
    }
?>

Agora os erros já estão sendo contados e redirecionados porém sua administração tem que ser feita diretamente no banco de dados. É interessante portanto também criar métodos para gerenciar estes erros e redirecionamentos. ficamos então com o controller: (/controllers/redirects_controller.php)

<?php
class RedirectsController extends AppController {
	var $name = 'Redirects';
	var $helpers = array('Html', 'Form');
    var $paginate = array(
        'limit' => 25,
        'order' => array(
            'num_errors' => 'desc'
        )
    );
	function index() {
		$this->Redirect->recursive = 0;
		$this->set('redirects', $this->paginate());
	}
	function create_redirect($id = null) {
		if (!$id && empty($this->data)) {
			$this->Session->setFlash(__('Redirecionamento Inválido', true));
			$this->redirect(array('action'=>'index'));
		}
		if (!empty($this->data)) {
			if ($this->Redirect->save($this->data)) {
				$this->Session->setFlash(__('O Redirecionamento foi salvo.', true));
				$this->redirect(array('action'=>'index'));
			} else {
				$this->Session->setFlash(__('O Redirecionamento não pode ser salvo. Por favor, tente novamente.', true));
			}
		}
		if (empty($this->data)) {
			$this->data = $this->Redirect->read(null, $id);
		}
	}
	function delete($id = null) {
		if (!$id) {
			$this->Session->setFlash(__('Redirecionamento com id inválido', true));
			$this->redirect(array('action'=>'index'));
		}
		if ($this->Redirect->del($id)) {
			$this->Session->setFlash(__('Redirecionamento excluído', true));
			$this->redirect(array('action'=>'index'));
		}
	}
}
?>

Observem que este controller não está utilizando nenhum método de autenticação. Fica em aberto o método a ser utilizado sendo que todas as páginas devem ficar invisíveis ao usuário comum. As views necessárias para este controller são:
create_redirect (/views/redirects/create_redirect.ctp):

<div class="redirects form">
<?php echo $form->create('Redirect', array('action' => 'create_redirect'));?>
	<fieldset>
 		<legend><?php __('Criar Redirecionamento');?></legend>
	<?php
		echo $form->input('id');
		echo $form->input('page_from', array('label'=>'URL do 404', 'disabled' => true));
		echo $form->input('page_to', array('label' => 'Redirecionar para'));
	?>
	</fieldset>
<?php echo $form->end('Criar');?>
</div>
<div class="actions">
	<ul>
		<li>
			<?php
				echo $html->link(
					__('Excluir', true),
					array(
						'action' => 'delete',
						$form->value('Redirect.id')
					),
					null,
					sprintf(
						__('Você tem certeza que deseja excluir # %s?', true),
						$form->value('Redirect.id')
					)
				);
			?>
		</li>
		<li>
			<?php
				echo $html->link(
					__('Listar 404s', true),
					array('action' => 'index')
				);
			?>
		</li>
	</ul>
</div>

e index (/views/redirects/index.ctp):

<div class="redirects index">
<h2><?php __('Redirecionamentos');?></h2>
<table cellpadding="0" cellspacing="0">
<tr>
    <th><?php echo $paginator->sort('URL do 404', 'page_from');?></th>
	<th><?php echo $paginator->sort('número de erros', 'num_errors');?></th>
	<th><?php echo $paginator->sort('URL a redirecionar', 'page_to');?></th>
	<th><?php echo $paginator->sort('número de redirecionamentos', 'num_redirects');?></th>
	<th class="actions"><?php __('Ações');?></th>
</tr>
<?php
foreach ($redirects as $redirect):
?>
	<tr>
		<td>
			<?php echo $redirect['Redirect']['page_from']; ?>
		</td>
		<td>
			<?php echo $redirect['Redirect']['num_errors']; ?>
		</td>
		<td>
			<?php echo $redirect['Redirect']['page_to']; ?>
		</td>
		<td>
			<?php echo $redirect['Redirect']['num_redirects']; ?>
		</td>
		<td class="actions">
			<?php
				echo $html->link(
					__('Criar Redirecionamento', true),
					array(
						'action' => 'create_redirect',
						$redirect['Redirect']['id']
					)
				);
			?>
            <?php
				echo $html->link(
					__('Excluir', true),
					array(
						'action' => 'delete',
						$redirect['Redirect']['id']
					)
				);
			?>
		</td>
	</tr>
<?php endforeach; ?>
</table>
</div>
<div class="paging">
	<?php echo $paginator->prev('<< '.__('anterior', true), array(), null, array('class'=>'disabled'));?>
 | 	<?php echo $paginator->numbers();?>
	<?php echo $paginator->next(__('próxima', true).' >>', array(), null, array('class' => 'disabled'));?>
</div>

Pronto, agora temos uma ferramenta para gerenciar os erros 404 do site. Tomara que algum dia a frustração de pensar que um link solucionará meus problemas e tomar um 404 na lata diminua

Criei um projeto no github pra facilitar a baixar os arquivos. Está tudo neste link

Tags: , , , , ,
Topo

8 Comentários

Migrando para o CakePHP 1.3

postado por Fabrício Ferracioli em 22/02/2010 13:39:06
Tags:

A maioria já deve saber que o CakePHP está com a versão 1.3 do framework em estado beta. Me recordo que a versão 1.2 do framework já era bem agradável quando estava em fase beta, e portanto decidi testar o que o CakePHP 1.3 tinha de novo. Fiz o download e fui direto para a página que contém a descrição das principais mudanças da versão 1.2 para a 1.3. Quando vi que ela tinha um tamanho um pouco grande decidi testar logo alguma aplicação que eu tinha funcionando na versão 1.2.

Para minha surpresa ela não exibiu nem a página inicial, e ao invés disso várias mensagens de erro foram mostradas. Então vi que era realmente necessário ler a extensa página. Durante a leitura percebi que diversas mudanças importantes ocorreram, e logo vi o porque da minha aplicação sequer funcionar. Como a leitura é um pouco extensa, decidi resumir as principais mudanças aqui. As novidades vão ficar para depois, porque também são várias.

Principais mudanças do CakePHP 1.3

  • Adicionadas configurações específicas do config/core.php para quem utiliza o PHP 5.3
  • O arquivo webroot/index.php foi alterado, e deve ser substituído em sua aplicação
  • Recomenda-se que todos os métodos e classes deprecados não sejam mais utilizados
  • Admin routes foram removidas por uma configuração mais geral, chamada routes prefix. O route prefix admin pode ser definido com a linha
    Configure::write('Routing.prefixes', array('admin'));
  • O método de remoção no modelo agora é único,
    Model::delete()
  • O Model teve os métodos métodos findAll(), findCount() e findNeighbours() removidos
  • Os diretórios css, js e img foram removidos dos diretórios app/vendors e plugin/vendors e substituídos com os diretórios plugin e theme no webroot
  • Somente a variável
    $title_for_layout

    poderá ser definida para o título da página, tanto no Controller quanto na View

  • Deve-se selecionar o ponto da aplicação desejado para dump de sql quando o debug está definido para 2, utilizando a linha de código
    echo $this->element('sql_dump');

    em qualquer ponto da aplicação

  • SessionHelper e SessionComponent não são mais carregados por padrão. Agora devem ser declarados como qualquer outro helper ou component. Para manter o comportamento antigo, adicione a declaração de helpers e components em seu AppController
  • A função
    SessionComponent::setFlash()

    teve seu segundo parâmetro alterado para usar um element e não um layout. Para alterar sua aplicação primeiro mova seus layouts para a pasta de elements e renomeie a variável

    $content_for_layout

    para

    $message
  • Não existe mais o nível 3 de debug
  • PaginatorHelper agora produz a saída dos métodos
    prev(); next(); first(); last();

    englobada por

    <span>

    e não

    <div>

    para tornar a estilização mais fácil

  • Os métodos
    dateTime(); year(); month(); day(); minute(); meridian(); select();

    do FormHelper não possuem mais o parâmetro

    $showEmpty

    , e agora utilizam

    $attributes['empty']
  • FormHelper::submit()

    agora pode criar outros inputs além de type=submit. Para isso utilize a option type

  • FormHelper::button()

    agora cria elementos button ao invés de inputs reset. Para criar inputs de reset, utilize

    FormHelper::submit()

    com o parâmetro option definindo ‘type’ => ‘reset’

  • O método
    FormHelper::create()

    não cria mais elementos fieldset escondidos, agora substituídos por divs escondidas, o que ajuda na validação de HTML 4

  • No HtmlHelper os métodos
    link(); para(); div(); tag()

    não pussuem mais o parâmetro

    $escape

    , que foi substituído por

    $options['escape']

    . Similarmente os métodos

    meta(); css()

    tiveram o parâmetro

    $inline

    substituído por

    $options['inline']
  • Agora as chamadas a
    $session->flash()

    não são mais auto-exibidas, sendo necessário adicionar

    echo

    antes da chamada a função, como em qualquer método de helper

  • JavascriptHelper e AjaxHelper estão deprecados, e agora deve-se utilizar o JsHelper em conjunto com o HtmlHelper.
    $javascript->link()

    agora é

    $javascript->codeBlock()

    é

    $html->scriptBlock()

Já deu para perceber que vai dar trabalho mudar algumas coisas. E essas são apenas as alterações que considerei mais importantes, na página de migração do 1.2 para o 1.3 está a lista completa. Verifique nessa se alguma mudança afeta as suas aplicações.

Pelas alterações deu para perceber que o framework está mais preocupado com a padronização do seu comportamento, de código, utilização de Web Standards e desempenho, questões que são sempre importantes.
Bom, é isso, espero ter ajudado. Na próxima vamos dar uma olhada nas novidades do Cake 1.3.

Tags:
Topo

3 Comentários

Gerando HTML 4.01 com o HTML Helper do CakePHP

postado por Fabrício Ferracioli em 15/12/2009 14:21:18

Apesar das melhorias introduzidas no HTMLHelper do CakePHP 1.2, sempre achei ridículo ele gerar marcação apenas em XHTML. Sempre preferi HTML, e me via engessado pelo CakePHP nesse aspecto.

Acredito que muitos passam por essa dificuldade quando estão trabalhando com o HTMLHelper e FormHelper do CakePHP. Uma das principais consequências disso, é que o código gerado na maioria das vezes não é válido, pois mistura tags HTML e XHTML.

Mas sempre imaginei que deveria haver um meio de contornar isso. Certa vez verifiquei que existia um método chamado docType no HTMLHelper, e que era possível definir HTML 4.01 com ele. Mas minha alegria logo acabou quando percebi que ele não alterava o comportamento do Helper para gerar tags HTML.

Finalmente, hoje acabei me deparando com uma alternativa. Cansado de mensagens de código inválido fiz uma leitura mais cuidadosa do manual do CakePHP e encontrei a página que diz como gerar tags HTML 4.01. A solução não é muito elegante, mas é uma alternativa presente no próprio framework.
Espero que a solução sirva para vocês também!

Update 1:Para quem estiver com preguiça de fazer o arquivo do zero, fiz um repositório no Github com o arquivo e algumas tags. Conforme for sentindo necessidade vou aumentar o conteúdo dele. Quem quiser colaborar, está convidado!

Update 2: Apliquei as sugestões do Juan Basso, agora um Helper está disponível para manter as tags HTML 4.01. Vejam a discussão abaixo. O link do Github contínua o mesmo. Mantive o arquivo anterior para quem preferir o método da documentação do Cake.

Tags: , , , , ,
Topo

4 Comentários

Creative Commons License
Sou Ágil: KISS em http://kiss.souagil.com.br está licenciado sobre
Creative Commons Attribution-Share Alike 2.5 Brazil License.

souÁgil