PHP, Traits e Singleton
A minha maior motivação em participar do fórum iMasters, sempre foi ajudar, ensinar e aprender. O último, como sempre, é o maior ganho que se pode ter, aprender enquanto ajuda alguém com dificuldade. Dentro desse contexto, algumas situações acabam nos dando uma lição ainda mais surpreendente.
A partir de um questionamento (Qual a utilidade da traits), me vi intrigado em demonstrar exemplos práticos de quão poderosa as traits são. Além disso, como dizia tio Ben, “grandes poderes requerem grandes responsabilidades”.
Traits
O manual é bem claro quanto a motivação das traits:
Traits are a mechanism for code reuse in single inheritance languages such as PHP. A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies. The semantics of the combination of Traits and classes is defined in a way which reduces complexity, and avoids the typical problems associated with multiple inheritance and Mixins.
Parafraseando e simplificando, traits são um mecanismo de reutilização de código para linguagens de herança
simples/única. Dessa forma, é uma adição a herança tradicional habilitando a composição horizontal de
comportamento.
Uma trait pode agrupar um conjunto de funcionalidades e estas serem introduzidas em outras classes ou objetos. Assim,
esta classes irão encorporar o comportamento da trait, da mesma forma que se o código da trait. Isso se dá devido
ao funcionamento do interpretador para com a trait. Ele realmente copia e cola o código da trait dentro da classe
em que ela foi definida.
Singleton
Singleton é um padrão de projeto que define que uma classe deve possuir apenas uma instância
e um ponto de acesso global.
Normalmente, o padrão, é muito mal utilizado. Mas, sempre há a sua aplicabilidade. Um dos mais famosos uso é o
Registry.
Registry é um repositórios de objetos. Normalmente ele é aplicado como um padrão Singleton (mas não necessariamente
deve ser um).
Dessa forma, vamos a implementação de um Registry:
class Registry
{
/**
* @var $this
**/
protected static $instance;
/**
* @var mixed[]
**/
private $registry = array();
/**
* Retrieve a value by his key
* @param string $key.
* @return mixed.
* @throws \InvalidArgumentException If the key isn't registred
**/
public function get($key)
{
if (!isset($this->registry[$key])) {
throw new InvalidArgumentException(
sprintf("There\'s no value for key %s." , $key)
);
}
return $this->registry[$key];
}
/**
* Checks if a key is setted in registry
* @param string $key
* @return boolean
*/
public function has($key)
{
return isset($this->registry[$key]);
}
/**
* Define a pair key=value in registry
* @param string $key.
* @param mixed $value
**/
public function set($key , $value)
{
$this->registry[$key] = $value;
}
public static function getInstance()
{
return isset(static::$instance)
? static::$instance
: static::$instance = new static();
}
/**
* Final private constructor to prevent creating a new instance of the
* *Singleton* via the `new` operator from outside of this class
* and for not extend override the method
*/
private function __construct()
{
}
/**
* Private unserialize method to prevent unserializing of the *Singleton*
* instance.
*
* @return void
*/
private function __wakeup()
{
}
/**
* Private clone method to prevent cloning of the instance of the
* *Singleton* instance.
*
* @return void
*/
private function __clone()
{
}
}O Registry, nesse exemplo, é um Singleton. Os seguintes pontos são específicos do Singleton:
__construct:private- não permitir criar uma instância diretamente;__wakeup:private- não permitir unserialize;__clone:private- não permitir o clone da instância;getInstance:public- retornar a instância única;$instance:privateestatic- manter, a nível de classe, a instância única.
Por outro lado, os demais métodos são específicos do Registry:
get: retornar um registro;set: inserir um registro;has: verificar se um registro existe.
Suponha que precise de outra classe que implemente o Singleton (um manipulador para o DOM, por exemplo). Logo, eu
preciso implementar todos os métodos básicos novamente. Vamos ao molde da nossa próxima classe Singleton:
class SingletonClass
{
/**
* @var $this
**/
protected static $instance;
public static function getInstance()
{
return isset(static::$instance)
? static::$instance
: static::$instance = new static();
}
/**
* Final private constructor to prevent creating a new instance of the
* *Singleton* via the `new` operator from outside of this class
* and for not extend override the method
*/
private function __construct()
{
}
/**
* Private unserialize method to prevent unserializing of the *Singleton*
* instance.
*
* @return void
*/
private function __wakeup()
{
}
/**
* Private clone method to prevent cloning of the instance of the
* *Singleton* instance.
*
* @return void
*/
private function __clone()
{
}
}O código acima exemplifica o básico para uma classe Singleton. Qualquer classe que necessitar ser um
Singleton, deverá implementar o código acima.
Como é de praxe, sabe-se que copiar e colar não é uma boa alternativa e, nesse caso, devemos encontrar uma maneira para que o código seja reutilizado.
Outro ponto importante, é que uma classe Singleton não atua muito bem com herança (na realidade, ela inibe o uso da
herança por trazer para si o controle da instância). Dessa forma, não é interessante implementar o Singleton como uma
classe abstrata. Apesar de ser totalmente possível, apenas não é aconselhado.
Eis que surgem as traits. Nesse caso, é possível separar em uma trait, toda a implementação do Singleton.
namespace Harbinger\StandardLibrary\Traits;
/**
* Trait for singleton
* @package Harbinger
* @subpackage StandardLibrary\Trait
* @author Gabriel Heming <gabriel.heming@hotmail.com>
**/
trait Singleton
{
protected static $instance;
final public static function getInstance()
{
return isset(static::$instance) ? static::$instance : static::$instance = new static();
}
/**
* Final private constructor to prevent creating a new instance of the
* *Singleton* via the `new` operator from outside of this class
* and for not extend override the method
*/
final private function __construct()
{
$this->init();
}
/**
* this method must be used as constructor
*/
protected function init()
{
}
/**
* Private unserialize method to prevent unserializing of the *Singleton*
* instance.
*
* @return void
*/
final private function __wakeup()
{
}
/**
* Private clone method to prevent cloning of the instance of the
* *Singleton* instance.
*
* @return void
*/
final private function __clone()
{
}
}As únicas diferenças da implementação via trait são: a adição da keyword final, para a classe que implementar
o Singleton não sobrescreva o método implementado, e a implementação do método init, que pode ser usado como
construtor de uma classe.
Dessa forma, a classe Registry pode ser reescrita da seguinte maneira:
namespace Harbinger\StandardLibrary;
/**
* Implementation of a data registry.
* @package Harbinger
* @subpackage StandardLibrary
* @author Gabriel Heming <gabriel.heming@hotmail.com.br>
**/
class Registry
{
use \Harbinger\StandardLibrary\Traits\Singleton;
/**
* @var mixed[]
**/
private $registry = array();
/**
* Retrieve a value by his key
* @param string $key.
* @return mixed.
* @throws \InvalidArgumentException If the key isn't registred
**/
public function get($key)
{
if (!isset($this->registry[$key])) {
throw new InvalidArgumentException(
sprintf("There\'s no value for key %s." , $key)
);
}
return $this->registry[$key];
}
/**
* Checks if a key is setted in registry
* @param string $key
* @return boolean
*/
public function has($key)
{
return isset($this->registry[$key]);
}
/**
* Define a pair key=value in registry
* @param string $key.
* @param mixed $value
**/
public function set($key , $value)
{
$this->registry[$key] = $value;
}
}Assim, foi utilizada uma trait (que realiza o copy and paste) para evitar o “copia e cola” de códigos através de
vários arquivos.
De qualquer forma, esse é apenas um dos muitos exemplos da aplicabilidade de uma trait, algumas das quais irei trazer em breve.