Twitter

@felipernb: Eu queria estar no #brhackday agora. Não vou mentir...

(Updated 3 minutes ago)

Caching em PHP com uma técnica “obscura” mas muito eficiente

Posted: December 25th, 2008 | Author: Felipe Ribeiro | Filed under: apache, php, web 2.0 | Tags: , , | 14 Comments »

Caching é fundamental para a escalabilidade de aplicações Web. Existem diversas ferramentas que oferecem diferentes maneiras de se fazer isso, seja no Smarty, no APC, nos diversos frameworks, ou Memcached para os mais drásticos…

Mas essa é a “maneira Rasmus Lerdorf” de se fazer cache com PHP sem nenhuma ferramenta externa e com uma sacada fenomenal.

Para um servidor Web é muito mais rápido servir arquivos estáticos do que esperar que aquele arquivo seja interpretado por algum módulo ou que seja executado em CGI e é isso que esse método faz, gera arquivos estáticos sob demanda, utilizando os recursos que o Apache oferece.

Agora imagine que você tem um site de notícias e quer que elas sejam tratadas como arquivos estáticos, e os links para cada notícia seria algo como:

http://meusite.com.br/noticias/000001.html

1º passo: Setamos nas configurações do Apache para que a página de erro 404 seja um arquivo .php (isso pode ser feito no .htaccess da pasta ou nas configurações do Apache propriamente dito). No caso do .htaccess, basta colocar isso:


ErrorDocument 404 /noticias/gera_cache.php

2º passo: Criamos o arquivo gera_cache.php, que irá tratar as requisições que teriam como resposta o erro 404 (Not Found) com o seguinte codigo:


<?php
    $id = basename($_SERVER['REDIRECT_URL'], '.html');

    /* Acessa a página dinâmica */
    $html = file_get_contents(sprintf("http://meusite.com.br/noticias.php?id=%d",$id));
    /* O ideal é fazer algum tratamento de erros, para evitar a
     criação de arquivos para ids inválidos */

    /* Exibe o conteúdo */
    header(sprintf('%s 200', $_SERVER['SERVER_PROTOCOL']));
    echo $html;

    /* Salva o conteúdo em um arquivo .html */
    $fp = fopen(sprintf(dirname(__FILE__)."/%d.html", $id), "w");
    fputs($fp, $html);
    fclose($fp);
?>

Sendo assim, o que vai acontecer:

Quando o usuário acessar pela primeira vez o link http://meusite.com.br/noticias/000001.html, o arquivo /noticias/000001.html não existirá e o usuário será redirecionado para o gera_cache.php. O gera_cache.php acessa a página dinâmica que exibe o conteúdo da página com o id passado (000001) e salva em um html. Nos acessos consecutivos ao http://meusite.com.br/noticias/000001.html o arquivo existirá e não passará mais pelo PHP.

É uma técnica bem “tricky” e que precisa de cuidados, por exemplo, você precisa ter uma rotina que expira os arquivos em cache após algum tempo e quando houver alguma alteração em determinada informação que interfere na página que está em cache, para garantir consistência dos dados. Uma maneira muito simples de se fazer isso é usando a função filectime do PHP para checar a idade dos arquivos em uma rotina que rodaria em background e apagaria os que fossem mais velhos que o tempo desejado. Mas funciona muito bem!

P.S.: Um artigo que li dizia que apesar dessa técnica ter se tornado pública através do Rasmus, ela foi criada mesmo pelo Stig Bakken


Algoritmo de “Você quis dizer”

Posted: October 8th, 2008 | Author: Felipe Ribeiro | Filed under: apache, desenvolvimento de software, php, web 2.0 | Tags: , | 7 Comments »

Recentemente precisei implementar um sistema de correção ortográfica à la Google.

Pesquisei bastante sobre como fazer isso, e encontrei duas maneiras que explicarei agora, mas antes disso explicarei algo que é comum às duas: A criação do dicionário

Para criar o dicionário, você precisa ter uma base de palavras, que idealmente é um arquivo txt.

Você lê o arquivo e cria um array onde as palavras são as chaves e as frequências com que aparecem nos textos serão os valores.

<?php
$text = file_get_contents('arquivo.txt');
preg_match_all("/[a-z]+/",strtolower($text),$matches);
$palavras = $matches[0];
$dicionario = array();
foreach($palavras as $palavra)
	$dicionario[$palavra] += 1;
sort($dicionario); /* Essa ordenação será útil para otimização de performance
                               no primeiro algoritmo */
file_put_contents('dicionario_serializado.dat',serialize($dicionario));
?>

1 – Algoritmo de Levenshtein

PHP implementa nativamente o algoritmo de Levenshtein (http://php.net/levenshtein), que calcula a distância de edição entre duas palavras, funcionando da seguinte forma:

  • suponha que eu tenha duas palavras: água e mágua – A distância de edição delas é de uma inserção de carácter
  • suponha que eu tenha as palavras: casa e caixa – A distância delas é de uma substituição (s por i) e uma inserção (x)
  • suponha que eu tenha as palavras: arrocho e arroto – A distância delas é de uma substituição e uma remoção.

A função levenshtein também permite que você dê pesos diferenciados para inserção/remoção/substituição, sendo que o valor default é 1 para todas operações. Usando essa função, fica simples implementar um corretor, supondo que você tem um dicionário que já citei, você só precisa fazer:

<?php
function voce_quis_dizer($palavra_procurada) {
	$dicionario = unserialize(file_get_contents(‘dicionario_serializado.dat’));
	$minima_distancia = -1;
	$palavra_procurada = strtolower($palavra_procurada);
	foreach($dicionario as $palavra_do_dicionario) {
		if($palavra_procurada == $palavra_do_dicionario) return $palavra;
		$distancia = levenshtein($palavra_procurada,$palavra_do_dicionario);
		if($distancia < $minima_distancia || $minima_distancia == -1) {
			$minima_distancia = $distancia;
			$sugestao = $palavra_do_dicionario;
		}
	}
return $sugestao;
}
?>

Análise de desempenho:

Considerando que temos k palavras no dicionário, a palavra que desejamos procurar tem m letras e cada palavra do dicionário tem em média n letras, o algoritmo de Levenshtein tem ordem de complexidade O(m*n) onde para k comparações (no caso da palavra não existir no dicionário, já que se ela existir não teremos nenhuma comparação) teremos O(k*m*n) (k*m*n operações onde k é muito maior que m e n).

2 - Algoritmo de Peter Norvig

Toda explicação probabilística desse algorítmo está presente no site do Norvig, um cara renomado na área de inteligência artificial: http://norvig.com/spell-correct.html. Mas basicamente esse algoritmo apesar de mais complicado é bem mais inteligente que o primeiro. O que ele faz é o seguinte: gera perturbações na sua palavra procurada e vê quais dessas perturbações é a mais relevante no dicionário. Evitando comparações desnecessárias, já que no primeiro algoritmo cada palavra que você colocar será comparada com todas as outras (por exemplo: se você digitar BOLA, e ela não existir no dicionário ela será comparada com palavras absurdas como: LIVRO, que obviamente não é o que você quis dizer).

Apesar do conceito do Norvig ser simples, o código é um pouquinho complicado. Então eu criei uma classe que implementa ele e disponibilizei sob Licença BSD no PHPClasses.org (http://www.phpclasses.org/browse/file/24605.html) e tenho recebido elogios e feedback muito positivo de quem está usando.

Análise de desempenho:

Considerando 26 letras do alfabeto (depois da reforma da língua portuguesa, agora também temos 26 letras! :D ) e considerando que sua palavra tem m letras, teremos mais ou menos 26*m iterações para gerar as possíveis perturbações na palavra (inserções, remoções e substituições possíveis), e para garantir um segundo nível de perturbações teremos aproximadamente 26*m para cada perturbação gerada, que dá um total de aproximadamente (26*m)*(26*m) = 676 m². Agora só precisamos consultar cada uma das perturbações para vermos qual a mais relevante no dicionário. Cada consulta no dicionário é O(1) então desprezaremos na análise de performance.

E podemos considerar esse algoritmo como sendo mais eficiente do que o primeiro pois o tempo de execução cresce de acordo com o quadrado do tamanho da palavra (que normalmente é bem menor do que o tamanho do dicionário). Enquanto o primeiro sofre interferência do tamanho do dicionário e do tamanho médio das palavras contidas lá, então esse é bem mais inteligente e eficiente.

Espero ter ajudado!


Analogias

Posted: July 16th, 2008 | Author: Felipe Ribeiro | Filed under: apache, funny, geeky | Tags: , , , | No Comments »

Só para tirar as teias de aranha enquanto não tenho tempo de escrever nada.


Cheat sheets

Posted: February 16th, 2008 | Author: Felipe Ribeiro | Filed under: apache, desenvolvimento de software, php, ruby | Tags: , , , | No Comments »

“Quem cola não sai da escola”, é o ditado que a gente escuta no colégio, mas Cheat sheets e guias de bolso realmente são muito úteis quando você quer o nome daquela funçãozinha que faz exatamente o que você quer e você não lembra no momento, para consultas rápidas e não pra quem quer realmente aprender algo.
Achei algumas bem interessantes e vou compartilhar aqui até para facilitar de me lembrar depois (uma Cheat sheet de cheat sheets :D ):


O Mantra das REGEX – As 8 Expressões Regulares que precisamos saber.

Posted: January 29th, 2008 | Author: Felipe Ribeiro | Filed under: apache, desenvolvimento de software, php, regex | Tags: , , , | No Comments »

RegexVocê tem medo de REGEX!? Eu também!

Nos últimos tempos tenho lidado bastante com REGEX editando regras do Apache mod_rewrite e com a classe que publiquei no PHPClasses, a SiteMapGenerator Tabajara, que se propõe a gerar um mapa do site em xml no padrão estabelecido pelo Google para melhorar a indexação.

Mas depois de tirar onda da galera do Perl no post anterior, vou exaltar a importância que expressões regulares têm nas nossas validações, e para isso vou citar as 8 expressões regulares que todo programador Web deveria saber (exemplos em PHP, mas são válidos para qualquer linguagem com suporte a PCRE).

Validação de nomes de usuário

4 a 28 caracteres alfanuméricos e underscores:


$string = "userNaME4234432_";
if (preg_match('/^[a-z\d_]{4,28}$/i', $string)) {
echo "exemplo 1 ok.";
}

Validação de números de telefone

(##) ####-#### ou ##-####-####

$string = "(32) 5555-5555";
if (preg_match('/^(\(?[0-9]{2}\)?|[-. ]?)[ ][0-9]{4}[-. ]?[0-9]{4}$/', $string)) {
echo "exemplo 2 ok.";
}


Endereços de e-mail

foo@bar.foo

$string = "first.last@domain.co.uk";
if (preg_match('/^[^0-9][a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)*[@][a-zA-Z0-9_]+([.]

[a-zA-Z0-9_]+)*[.][a-zA-Z]{2,4}$/',
$string)) {
echo "examplo 3 ok.";
}

CEPs

#####-### ou ########

$string = "55324-432";
if (preg_match('/^[0-9]{5,5}([- ]?[0-9]{4})?$/', $string)) {
echo "exemplo 4 ok.";
}

Endereços IP

255.255.255.0

$string = "255.255.255.0";
if (preg_match('^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$'

, $string)) {
echo "exemplo 5 ok.";
}


Código de cores RGB em Hexadecimal

#FFFFFF, #FFF, FFF, FFFFFF

$string = "#666666";
if (preg_match('/^#(?:(?:[a-f\d]{3}){1,2})$/i', $string)) {
echo "exemplo 6 ok.";
}

Comentários de múltiplas linhas

/* Lorem
Ipsun
dolor*/


$string = "/* commmmment */";
if (preg_match('/^[(/*)+.+(*/)]$/', $string)) {
echo "exemplo 7 ok.";
}

Datas

DD/MM/YYYY ou MM/DD/YYYY

$string = "30/01/2008";
if (preg_match('/^\d{1,2}\/\d{1,2}\/\d{4}$/', $string)) {
echo "exemplo 8 successful.";
}

Esses exemplos foram retirados do site Devolio[1] , porém algumas expressões eu alterei para ajustar aos padrões brasileiros de telefone e cep. Se por acaso fiz alguma besteira, peço que um REGEXPERT comente e corrija! E no segundo link[2] um validador de expressões regulares.

[1] 8 Practical PHP Regular Expressions – Devolio
[2] Perl Compatible Regular Expression Tester