Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Evandro Oliveira

[DICA] Trait para Singleton

Recommended Posts

Algo me diz que esse tópico deveria ficar no Lab de scripts. Qualquer coisa fiquem à vontade para mover.

 

É isso aí, galera! Com o advento do PHP 5.4, ganhamos uma poderosa feature chamada Trait, que tem por objetivo servir de template para classes e que complementa a funcionalidade de classes Abstratas. De certa forma, ela provê ao PHP a funcionalidade de múltipla herança. Para maiores informações, consultem o manual.

 

Se você já ouviu falar em OOP e também já ouviu falar sobre Design Patterns, há uma enorme chance de conhecer o polêmico padrão Singleton. Amado pelos novatos, causa paúra nos mais experientes por ser, de longe, o Design Pattern mais incorretamente utilizado pelos programadores em geral! O @João Batista Neto escreveu uma aplicação prática sobre Singleton aqui.

 

Um problema chato com a implementação de Singleton é que ela inibe a herança, uma vez que a própria classe deve manipular sua(s) instância(s). Isso quer dizer que, toda vez que tivermos que escrever uma classe que seja um Singleton, o código a seguir deveria ser embarcado:

 

 

<?php

class MinhaClasseQualquer
{
   private static $_instance = NULL;
   // Outras propriedades de MinhaClasseQualquer

   final protected function __construct()
   {
       self::$_instance =& $this;
   }

   final private function __clone()
   {}

   final public static function getInstance()
   {
       return self::$_instance ? self::$instance : new self();
   }

   // Abaixo vem o resto da implementação de MinhaClasseQualquer
}

 

 

Independente de se tratar do mesmo domínio ou aplicação, invariavelmente chegará o dia em que precisaremos declarar uma segunda classe que também seja um Singleton. Solução? Ctrl + C --- Ctrl + V!! DRY!

 

A partir do PHP 5.4, podemos isolar a implementação de Singleton em uma Trait e aplicá-la quando precisarmos que alguma classe se comporte como tal:

 

Singleton.php

<?php

trait Singleton
{
   private static $_instance = NULL;

   final protected function __construct()
   {
       self::$_instance =& $this;
   }

   final private function __clone()
   {}

   final public static function getInstance()
   {
       return self::$_instance ? self::$_instance : new self();
   }
}

 

MeuSingleton.php

<?php

class MeuSingleton
{
   use Singleton;

   private $prop;

   public function setProp($value)
   {
       $this->prop = $value;
   }

   public function getProp()
   {
       return $this->prop;
   }
}

 

Qualquer classe posterior que precise implementar Singleton, basta adicionar a linha 5, "use Singleton;" e todo o comportamento será importado para dentro da nova classe.

 

MeuOutroSingleton.php

<?php

class MeuOutroSingleton
{
   use Singleton;

   private $otherProp;

   public function setOtherProp($value)
   {
       $this->otherProp = $value;
   }

   public function getOtherProp()
   {
       return $this->otherProp;
   }
}

 

Testando:

<?php

require 'Singleton.php';
require 'MeuSingleton.php';
require 'MeuOutroSingleton.php';

$exemploSingleton1 = MeuSingleton::getInstance();
$exemploSingleton2 = MeuSingleton::getInstance();

$testeDeEscopo     = MeuOutroSingleton::getInstance();

// Configurando nosso "testador"
// Quando um teste falhar, irá imprimir "Afirmação incorreta: __afirmação__"
assert_options(ASSERT_CALLBACK, function ($file, $line, $code) {
   echo "Afirmação incorreta {$code}." . PHP_EOL;
});

// Impedimos o PHP de lançar avisos quando afirmações falharem
assert_options(ASSERT_WARNING, FALSE);

// Testando
assert('is_null($exemploSingleton2->getProp())');

// Note que alteraremos o valor de $exemploSingleton1 e
// testaremos o valor de $exemploSingleton2
$exemploSingleton1->setProp(1);
assert('$exemploSingleton2->getProp() === 1');

// Golpe de misericórdia, MeuOutroSingleton não deve
// compartilhar a mesma interface de MeuSingleton
assert('method_exists($testeDeEscopo, "getProp"))');

 

Nota: Pra quem não está acostumado com assertions, a regra básica é que você faz uma afirmação. Se ela for verdadeira, o script passa sem erros, caso não, uma função pode ser lançada - como é o caso do exemplo - um aviso pode será lançado (por padrão) e pode, também, encerrar a execução do script em caso de falhas. Novamente, para maiores informações, consulte o manual.

 

Bom, é isso aí, agora com o PHP 5.4 é possível encher sua implementação de Singletons mal projetados reaproveitar a estrutura Singleton, diminuindo código e poupando tempo!

 

Até a próxima. :bye:

Compartilhar este post


Link para o post
Compartilhar em outros sites

bacana =)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Evandro, show de bola. Uma ótima dica!

Ainda não comecei a trabalhar com php 5.4, mas já tenho estudado tem um tempinho.

 

Só uma dúvida: No trait você registrou o seguinte:

final protected function __construct()

Considerando que o trait seja uma espécie de herança, isso não iria invalidar a possibilidade de registrar um __construct na classe MeuSingleton?

 

E uma dica simples, que talvez você não conheça:

Essa linha:

return self::$_instance ? self::$_instance : new self();

Poderia ser escrita com a variação compactada do operador ternário, dessa maneira:

return self::$_instance ?: new self();

 

[]s!

Compartilhar este post


Link para o post
Compartilhar em outros sites

E uma dica simples, que talvez você não conheça:

Essa linha:

return self::$_instance ? self::$_instance : new self();

Poderia ser escrita com a variação compactada do operador ternário, dessa maneira:

return self::$_instance ?: new self();

 

Primeiro, obrigado por me lembrar do ternário com segundo argumento vazio, tinha realmente me esquecido.

 

Só uma dúvida: No trait você registrou o seguinte:

final protected function __construct()

Considerando que o trait seja uma espécie de herança, isso não iria invalidar a possibilidade de registrar um __construct na classe MeuSingleton?

O comportamento padrão é que as declarações nas Traits possam ser sobrescritas.

 

Isso é válido:

<?php

trait Template
{
   public $value;

   public function __construct()
   {
       $this->value = 'Set via Trait';
   }
}

class MyClass
{
   use Template;

   public function __construct()
   {
       $this->value = 'set via Class';
   }
}

$object = new MyClass();
echo $object->value; // Set via Class

 

Observe que, mesmo que $value pertença ao escopo da Trait, foi perfeitamente trabalhada dentro da classe como uma herança comum.

Deve-se atentar apenas à ordem de inclusão das Traits. Se houver colisão de métodos, o conflito deverá ser resolvido com insteadof. Não há sobrescrita.

 

<?php

trait FirstTrait
{
   public function method()
   {
       return TRUE;
   }
}

trait SecondTrait
{
   public function method()
   {
       return FALSE;
   }
}

class MyClass
{
   use FirstTrait, SecondTrait; // Fatal Error
   use FirstTrait, SecondTrait {
       FirstTrait::method insteadof SecondTrait
   } // Correto
}

$object = new MyClass();
assert($object->method());

 

No nosso caso, quem vai, na verdade, bloquear a sobrescrita é a declaração "final" que impede que um método seja sobrescrito.

Isso te previne de cair na tentação de criar um construtor com argumentos.

 

Veja, a especificação de Singleton declara que a implementação deve permitir apenas um número conhecido de instâncias - em geral, uma - e uma forma de acessá-la(s).

 

Qualquer valor de inicialização e configuração não deve ficar a cargo da implementação de Singleton. Não é de responsabilidade dela.

Você poderia aliar o Singleton a um Abstract Factory ou, melhor ainda, um Prototype caso seja realmente necessário um inicializador.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olha, na minha opinião, Traits são apenas RCP (Reuse by Copy & Paste), a diferença é que a linguagem faz isso pra você. Literalmente, o código da trait é COPIADO para dentro da classe que a utiliza, o que eu acho ruim.

 

Eu utilizo apenas um singleton registry. Se eu preciso de uma só instância do objeto, seto a instância primeiramente e depois vou utilizando get sempre que preciso. Basta 1 linha pra verificar se já existe um objeto daquela classe no registro. O nome da classe passa a ser a chave, se eu tentar setar algo naquela chave, lança-se uma exceção. Agora sim, sem copy/paste, nem manual, nem da linguagem. :thumbsup:

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olha, na minha opinião, Traits são apenas RCP (Reuse by Copy & Paste), a diferença é que a linguagem faz isso pra você. Literalmente, o código da trait é COPIADO para dentro da classe que a utiliza, o que eu acho ruim.

 

Concordo em gênero, número e grau. Por isso a piadinha tachada no final do post. Apenas aliei a apresentação da feature a uma implementação conhecida e problemática, que agora possui uma solução. Como você mesmo disse, a partir do momento que o comportamento fica a cargo da linguagem, podemos nos beneficiar indiretamente de algumas outras features. Posso citar como exemplo o uso de autoloading e o compartilhamento de bytecodes, reduzindo consumo de memória e agilizando processamento. Isso cai, também, diretamente na quantidade de linhas de código e na facilidade de uma posterior manutenção - não exatamente no caso de Singletons.

 

Eu utilizo apenas um singleton registry. Se eu preciso de uma só instância do objeto, seto a instância primeiramente e depois vou utilizando get sempre que preciso. Basta 1 linha pra verificar se já existe um objeto daquela classe no registro. O nome da classe passa a ser a chave, se eu tentar setar algo naquela chave, lança-se uma exceção. Agora sim, sem copy/paste, nem manual, nem da linguagem. :thumbsup:

 

No seu caso, apenas o Registry é um Singleton. Se a instância for obtida através do Registry é OK. Mas isso não inibe que as classes registradas tenham instâncias bastardas paralelas correndo pelo código.

 

Você pode ter o seu registro como um Singleton, como uma instância de uma suposta PHPCore, responsável pelo boostraping, além de um gerenciador de Session no DB porque você usa PHP Clustering balanceado com Nginx. Que tal? Independente da forma de acesso, através ou não, de um Registry, há casos extremos onde seja realmente necessário manter uma única instância existindo. Assim como consigo lhe provar, por A + B, que um Registry não precisa ser, necessariamente, um Singleton.

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.