terça-feira, 17 de março de 2009

Brincando com o PHP 5.3 beta 2

Bom, pensando um pouco sobre a arquitetura da Framework que eu pretendo criar, eu decidi dar uma pesquisada sobre as novas versões do PHP para ver oque vem por ai. Fiquei feliz em saber que o PHP 5.3 deve ter um release oficial dentro dos próximos 3 mêses.

Como minha Framework deve demorar muito mais que isso para ter um release oficial, então eu resolvi instalar o PHP 5.3 Beta aqui na minha máquina para ver como está ficando. Para quem quizer instalar, eu encontrei um tutorial muito legal de como instalar ele na porta 81 a partir de uma instalação XAMPP padrão (não se preocupe, ele é igualmente útil mesmo que você não utilize o XAMPP).

Depois de instalado, vamos aos testes :)

Late Static Binding


Esse é um recurso que já me fez perder bastante a paciência (na verdade a ausência dele...). O problema do que o Late Static Binding resolve é mais fácil de ser descrito com exemplos, então vamos a um exemplo:

class A
{
public static function metodo()
{
return self::outro_metodo();
}

public static function outro_metodo()
{
return "classe A chamando";
}
}

class B
{
public static function outro_metodo()
{
return "classe B sobreescreveu";
}
}

echo B::metodo();


Dado o código acima, o natural (na minha opinião) seria que a saída fosse: "classe B sobreescreveu"

Mas esse não é o comportamento que o PHP adota, para esse caso o retorno seria: "classe A chamando"

Isso ocorre pelo local da chamada no PHP, ele sabe que o método "metodo" foi definido na classe A, então mesmo chamando a partir de B, ele vai ser executado como um membro de A.

Isso pode gerar grandes problemas quando se quer fazer com métodos estáticos com um certo nível de complexidade, no meu caso a classe do ActiveRecord.

Com o Late Static Binding esse problema é resolvido da seguinte forma:

class A
{
public static function metodo()
{
return static::outro_metodo();
}

public static function outro_metodo()
{
return "classe A chamando";
}
}

class B
{
public static function outro_metodo()
{
return "classe B sobreescreveu";
}
}

echo B::metodo();


O código está quase totalmente igual, a diferença é na hora da chamada do "outro_metodo" que antes era "self::outro_metodo()" e agora é "static::outro_metodo()". Com isso o PHP consegue manter a compatibilidade com códigos antigos.

Essa é uma grande adição e vai fazer com que possamos criar classes como o ActiveRecord com uma interface mais limpa :)

So pra finalizar, uma dica que também veio junto com esse recurso, que é o novo método: get_called_class()

esse método retorna a classe atual usando o mesmo esquema do static no exemplo anterior.

Namespaces


Esse aqui, não sei como o PHP passou tanto tempo sem ter... De qualquer forma, para os "Javistas" agora temos pacotes, para "Rubistas" temos módulos, e por ai vai.

Os namespaces não são nada além de uma forma para organizar suas classes (e como consequência, ajudando a evitar conflito de nomes).

A verdade é que não fiquei muito satisfeito como o PHP está usando isso no momento (pode mudar, espero que mudem...).

Vou citar alguns pontos, a começar pela definição/uso:

namespace MeuNamespace\SubNamespace\Aninhando;

sinceramente... quem foi que teve a idéia de usar a barra invertida para separar os namespaces? achei feio... mas dos males esse é o menor (para o uso as barras são usadas da mesma forma)

Quem conhece Java/.NET (ou outros, vocês entenderam...), sabe que ao importarmos um pacote, as classes ficam diretamente acessiveis; o PHP comeca falhando nesse ponto... não é possível importar um pacote e ter as classes diretamente acessiveis... se você quizer ter acesso direto a classe (como se ela estivesse no pacote atual) você deve importar as classes uma a uma, exemplo:

use MeuNamespace\A;
use MeuNamespace\B;

$a = new A();
$b = new B();


tendo em mente o mesmo caso, o código abaixo NÃO funciona:

use MeuNamespace;

$a = new A();
$b = new B();


Além de receber um erro de ClassNotFound você ainda ganha de presente um Warning: The use statement with non-compound name 'MeuNamespace' has no effect

Para resumir um pouco o resto dos recursos de use (para importar) vou citar alguns exemplos (esses funcionam):

use MeuNamespace\SubNamespace;
use MeuNamespace as MN;

$a = new MN\A();
$b = new MN\B();
$c = SubNamespace\C();


Outra coisa que imcomoda é quando se está desenvolvendo dentro de um namespace... Entendam que o PHP considera os namespaces como pastas do seu HD, então, se você estiver trabalhando em um namespace, e precisar acessar o namespace global, você precisa avisar isso, exemplo:

namespace MeuNamespace; //informa o namespace do arquivo

class A
{
public function __construct($a)
{
$a = \trim($a);
}
}


Ou seja, nada limpo pra se programar... espero que a turma do PHP também ache isso... senão vamos ter códigos PHP entupidos com essas barras invertidas no meio...

RFC: Namespace Separator
RFC: Namespace Resolution

Traits


Os traits no PHP, vamos resumir isso de forma simples, sabe aquelas interfaces que você implementa no objeto, você diz que o objeto vai usar a interface, e então você cria a implementação para os respectivos métodos. Os traits são como interfaces, mas com uma grande diferença: eles vem com implementação.

Isso resolve o problema de herança múltipla, para quem é do mundo Ruby, os Traits são os Mixins do PHP :P

Isso é legal para várias coisas, quem programa em Ruby sabe o poder que podemos ter com isso (falo da turma do Ruby porque para eles isso já é muito comum), um exemplo simples seria para quebrar a implementação de uma classe em várias partes (pretendo usar isso no ActiveRecord visto o tamanho da mesma...).

Eu achei estranho pois não consegui utilizar os traits, embora no RFC do PHP esteja dizendo que eles foram inclusos no PHP 5.3, quem sabe no proximo beta release?

RFC: Traits

Lambda e Closures


Com certeza esse é um dos recursos mais esperados por mim. Com isso será possível executar uma programação de high-order no PHP.

Nas versões atuais já é possível algo semelhante utilizando o método create_function() do PHP, mas além de acabar com a organização do código, não suporte sintaxe highlight, ainda é algo bastante limitado.

Agora com funções lambda e closures é bem mais prático criar uma programação high-order utilizando funções como blocos.

Eu fiz alguns testes aqui para uma criação de enumeraveis como em Javascript e Ruby:

class ArrayData implements IteratorAggregate
{
private $data;

public function __construct($data = array())
{
$this->data = $data;
}

public function getIterator()
{
return new ArrayIterator($this->data);
}

public function push()
{
$args = func_get_args();

foreach ($args as $arg) {
array_push($this->data, $arg);
}
}

public function each($block)
{
foreach ($this as $value) {
$block($value);
}
}

public function map($block)
{
return new static(array_map($block, $this->data));
}

public function inject($base, $block)
{
$this->each(function($value) use (&$base, $block) {
$base = $block($base, $value);
});

return $base;
}

public function reject($block)
{
$new_data = new static;

$this->each(function($value) use ($block, $new_data) {
if (!$block($value)) $new_data->push($value);
});

return $new_data;
}
}


Esse foi apenas um exemplo básico para fins de teste, com o código acima foi possível criar algo assim:

$data = new ArrayData(array(1, 2, 3));
$data->push(4, 5);

$data->map(function($value) {
return $value * 2;
})->reject(function($value) {
return $value > 5;
})->each(function($value) {
echo "$value
";
});

echo $data->inject(0, function($sum, $value) {
return $sum + $value;
});


Apesar de bastante legal, a performance não foi essas coisas, fiz o seguinte teste de beenchmark:

function beenchmark($block)
{
$start = microtime(true);
$block();
echo "
Executed in " . (microtime(true) - $start);
}

$a = range(0, 100000);
$b = new ArrayData($a);

beenchmark(function() use ($a) {
$s = 0;

foreach ($a as $value) {
$s += $value;
}
});

beenchmark(function() use ($b) {
$s = 0;

$b->each(function($value) use (&$s) {
$s += $value;
});
});

beenchmark(function() use ($b) {
$b->inject(0, function($s, $value) {
return $s + $value;
});
});


Que resultou respectivamente em:

Executed in 0.0579040050507
Executed in 0.131916046143
Executed in 0.254753112793

Como pode-se ver, uma operação de inject teve um desempenho 5 vezes pior que uma versão linear simples com arrays.

Obviamente a classe pode ter algumas coisas mais "raw" na implementação para melhorar esse cenário, estou apenas demostrando oque acontece no caso de uso excessivo de lambda, então devemos utilizar com cautela.

Fiz um teste onde o resultado saiu em desacordo com o RFC do PHP, usando o seguinte código:

class A
{
public function my_method()
{
return "ok, reach here";
}

public function inner_lambda()
{
$proc = function() {
return $this->my_method();
};

return $proc();
}
}

$a = new A();

echo $a->inner_lambda();

Isso me resultou em: Fatal error: Using $this when not in object context

Mas de acordo com o RFC do PHP (link abaixo) o uso de $this deveria ser possível para funções lambda criadas dentro de objetos, isso deve ser corrigido nos próximos releases.

RFC: closures

Conclusão


O PHP está melhorando bastante, isso vai suprir muitas coisas para poder criar uma framework mais ágil e prática de se usar, espero melhoras até o release final do PHP 5.3, mas acho que o PHP está indo no caminho certo.

Nenhum comentário: