Mostrando postagens com marcador php. Mostrar todas as postagens
Mostrando postagens com marcador php. Mostrar todas as postagens

sexta-feira, 31 de julho de 2009

Roda: Eu também já recriei

Uma das discussões mais quentes do momento, e também uma das mais antigas, é sobre a mania que nós desenvolvedores temos de reinventar a roda. Eu mesmo já reinventei diversas vezes e hoje olho para trás e vejo quanto tempo poderia ter poupado ao usar alguma ferramenta já pronta, ou apenas ter adaptado uma que não se encaixava perfeitamente aos meus requisitos.

Há claro excessões, em que seu requisito é tão mínimo que reimplementar uma solução é mais rápido do que aprender uma já existente no mercado. Como foi o caso de um sistema que estávamos desenvolvendo em PHP e precisávamos de um framework apenas para separar a lógica da renderização de templates por tema. Validamos rapidamente diversos frameworks MVC e percebemos que era mais fácil criar em meia hora a seguinte classe:

class Renderizador {
function Renderizador($tema=null) {
$this->_dir = dirname(__FILE__) . "/templates/" . $this->tema;
}
public function put($nome,$obj) {
$this->$nome = $obj;
}
public function show($template) {
include( $this->_dir . "/" . $template . ".php");
}
}

A utilização dela era mais simples ainda. No exemplo abaixo as variáveis inseridas no renderizador ficam disponiveis para o template listagem.php através de $this, que nada mais é do que um arquivo php comum.

$renderizador = new Renderizador('cinza');
$renderizador->put('lista',[1,2,3,4]);
$renderizador->show('listagem');

Simples, sem precisar aprender nenhum framework ou linguagem de marcação nova. Em duas horas o time já estava trabalhando com a lógica separada da apresentação. Contudo essa é a exceção.

Em geral já recriei diversas rodas desnecessariamente desperdiçando muito tempo. Framework de mapeamento objeto relacional, CMS, blog e até mesmo o TimerTask do Java são alguns dos exemplos. Esse último me rendeu uma longa discussão com o Phillip Calçado na época em que ele trabalhava aqui. Felizmente ele foi persistente em me fazer desistir daquela implementação.

De qualquer forma, o importante é a lição aprendida. Hoje tento usar ao máximo ferramentas já disponíveis, e quando alguma não me atende perfeitamente, ao invés de começá-la do zero eu a altero e contribuo, como foi o caso de uma recente alteração que precisei fazer no fakeweb.

Outros membros da equipe também já vem de longe com esta mesma filosofia e estão constantemente contribuindo como Guilherme Cirne ao contribuir com o WillPaginate, o Anselmo também contribuindo com o Fakeweb e o Bruno Carvalho contribuindo com o TralhaController e o ExceptionNotifier.

O importante é aproveitar aquilo que a comunidade já lhe oferece pronto e testado. Economiza tempo seu, e do próximo desenvolvedor, que dará manutenção a algo que é padrão, com muita discussão em fóruns e muitas soluções documentadas.

terça-feira, 16 de setembro de 2008

On-demand javascript com PHP

A técnica de script on-demand serve para compartilhar dados entre domínios. A idéia dela é driblar as restrições da classe XMLHttpRequest, que só permite requisições no mesmo domínio, e a restrição do iframe que no máximo permite comunicação entre sub-domínios de um mesmo domínio.

Basicamente consiste em adicionar um script à página quando uma informação é necessária. Esse script proverá a informação necessária chamando uma função de callback ao solicitador. Uma explicação mais detalhada sobre a técnica pode ser encontrada em On-Demand_Javascript - AjaxPatterns.

Com PHP fica ainda mais fácil desenvolver o script provedor de dados. Digamos por exemplo que você queira obter a lista de livros de uma biblioteca, seu javascript ficaria assim:
var Livros = {
listar:function() {
var s = document.createElement('script');
s.type = 'text/javascript';
s.src = 'listaDeLivros.php?callback=Livros.onload_listar';
document.getElementsByTagName('head')[0].appendChild(s);
}
onload_listar:function(html) {
document.getElementsByTagName('lista').innerHTML = html;
}
}

A função listar insere um javascript à página informando a ele a função javascript de callback. No PHP informaremos o html gerado chamando a função enviada no parâmetro callback. Mais ou menos como mostrado abaixo:
<?php echo $_REQUEST['callback']; ?>("<h1>Lista de livros</h1>");

Mas é claro que normalmente o html gerado não será de apenas uma linha. Além disso ele poderá ter diversos dados com aspas e outros caracteres que poderiam causar erros de javascript. Ficar se preocupando com esse tipo de coisa na diagramação do html é trabalhoso e passível de muitos erros.

Para evitar isso podemos utilizar a função ob_start, que faz com que tudo o que for escrito no output seja guardado em buffer e ao final remetido a uma função de callback do PHP. Nesta função então pode-se tratar o html e enfim escrever no real output para o cliente. Veja como ficaria:
<?php 
function captura_buffer($html) {
$html = addslashes($html);
$html = str_replace("\n", '\n', $html);
$html = str_replace("\r", '\r', $html);
return $_REQUEST['callback'] . '("' . $html . '");';
}
ob_start('captura_buffer');

$livros = funcao_ficticia_para_obter_livros();

foreach($livros as $livro) {
?>

<p>
<?php echo $livro->id ?> -
<b><?php echo $livro->titulo ?></b> -
<?php echo $livro->autor ?>
</p>

<?php } ?>

quinta-feira, 3 de abril de 2008

Definindo atributos somente leitura em uma classe PHP

O modificador private em PHP, assim como em quase todas as linguagens, impede que membros de uma classe sejam acessados para leitura e escrita. Mas e se você deseja que os atributos de uma classe sejam privados para escrita mas não para leitura? Uma solução simples seria criar métodos get para cada atributo que se deseja liberar a leitura. Mas essa solução é muito verbosa, e como costumo dizer, se você deseja fazer algo desse tipo, melhor então usar Java.

O PHP possui o que no site está descrito como métodos mágicos. No caso para liberarmos a leitura de atributos privados de uma classe, utilizaríamos o método mágico __get. Esse método é chamado sempre que um atributo da classe é acessado. Veja abaixo como ficaria a implementação desse método de forma a liberar todos os atributos da classe:
class Pessoa {

private $nome;
private $idade;

function Pessoa($nome,$idade) {
$this->nome = $nome;
$this->idade = $idade;
}

function __get($atributo) {
echo "<br/>Leu atributo $atributo";
return $this->$atributo;
}
}
Repare que interessante a sintaxe do corpo do método __get. Eu retorno um atributo do objeto atual que contenha o nome enviado por parâmetro. Para verificarmos o funcionamento dele podemos testar com o seguinte código:
$eu = new Pessoa("Tiago",25);
echo "<br/>" . $eu->nome . " tem " . $eu->idade . " anos";
echo "<br/>Atributo nao existente: " . $eu->atributoNaoExistente;
A saída esperada é:
Leu atributo nome
Leu atributo idade
Tiago tem 25 anos
Leu atributo atributoNaoExistente
Atributo nao existente:

terça-feira, 1 de abril de 2008

Mock de classe no PHPUnit executa o construtor

O mock de classes no PHPUnit não funciona muito bem, o ideal, claro é mocar interfaces, mas quando isso não é possível é preciso estar ciente de que o construtor da classe mocada será executado e portanto o teste ficará um tanto falho.

É possivel identificar facilmente esse problema com o código abaixo. No caso mocarei a classe ClasseUtilizada para ser utilizada na classe ClasseASerTestada. Ao executar o teste é possível verifique que o echo dentro do construtor será executado erroneamente.
class ClasseUtilizada {   
function ClasseUtilizada() {
echo "\n[passou no construtor de ClasseUtilizada]\n";
}
public function fazAlgumaCoisa() {
return "uhuuu";
}
}
class ClasseASerTestada {
function ClasseASerTestada($in) {
echo "\n[" . $in->fazAlgumaCoisa() . "]\n";
}
}
class MeuTeste extends PHPUnit_Framework_TestCase {
/**
* @Test
*/
public function teste_com_classe() {
$mock = $this->getMock("ClasseUtilizada");
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(2));
new ClasseASerTestada($mock);
}
}
Uma forma de contornar isso é se aproveitar das características dinâmicas do PHP para simplesmente criar um Mock de uma classe que não existe. Mas pra isso é preciso informar no método getMock não só o nome de uma classe inexistente, como também a lista de métodos que a classe teria. Veja como ficaria abaixo:
    /**
* @Test
*/
public function teste_com_classe_inexistente_indicando_metodos() {
$mock = $this->getMock("ClasseUtilizadaMock", Array('fazAlgumaCoisa'));
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(4));
new ClasseASerTestada($mock);
}
Se por um acaso você não indicar o segundo parâmetro, que é um array com os métodos que a classe possui, um erro parecido com este será exibido: "Fatal error: Call to undefined method Mock_ClasseUtilizadaMock_4322e2fd :: fazAlgumaCoisa()".

O problema dessa solução é o caso em que a classe a ser utilizada possui verificação de tipo no recebimento dos parâmetros. Como a classe mock criada dinamicamente não é do tipo esperado, ocorrerá um erro. Veja um exemplo de como isso aconteceria abaixo:
class ClasseUtilizada {   
function ClasseUtilizada() {
echo "\n---->passou no construtor de ClasseUtilizada<-----\n";
}
public function fazAlgumaCoisa() {
return "uhuuu";
}
}
class ClasseASerTestadaComVerificacaoDeTipo {
function ClasseASerTestada(ClasseUtilizada $in) {
echo "\n[" . $in->fazAlgumaCoisa() . "]\n";
}
}
class MeuTeste extends PHPUnit_Framework_TestCase {
/**
* @Test
*/
public function teste_com_classe_inexistente_na_classe_com_verificacao_de_tipo() {
$mock = $this->getMock("ClasseUtilizadaMock", Array('fazAlgumaCoisa'));
$mock->expects($this->once())
->method('fazAlgumaCoisa')
->will($this->returnValue(5));
new ClasseASerTestadaComVerificacaoDeTipo($mock);
}
}
Nesse caso o erro exibido será "Argument 1 passed to ClasseASerTestadaComVerificacaoDeParametro :: ClasseASerTestadaComVerificacaoDeParametro() must be an instance of ClasseUtilizada"

Para esses casos não tem jeito, é preciso alterar as classes para que exista uma interface a ser mocada. Mas eu sugiro que não utilize a verificação de tipo nos parâmetros, caso contrário, seria melhor deixar de lado uma linguagem dinâmica como PHP e usar uma mais estática como Java.