
O Singleton é um dos padrões de design (design pattern) mais conhecidos e mais fáceis a serem implantados. Mas também, é um dos mais mal utilizados, pois ele é muitas vezes mas escolhido para desenvolver certas práticas não compatíveis. De um modo geral, com um Singleton, é bem fácil recuperar a instância a partir de qualquer ponto do aplicativo criando dependências transversais que logo se mostrarão difíceis de serem mantidas e desenvolvidas. Em outras palavras, com o singleton, você pode, como diz o ditado popular, "Pegar o bonde errado"!

Na maioria das vezes, ele se baseia em dois pontos importantes para ser desenvolvido em PHP: uma alteração da visibilidade do fabricante para impedir o distanciamento fora da classe (+ proibição de clonagem) e a criação de um método estático para fornecer a instância:
php><?php
class Singleton {
private static $instance = null;
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
$singleton = Singleton::getInstance();
// Gera um erro fatal. Catchable avec PHP 7
// "Call to private Singleton::__construct() from invalid context"
$singleton = new Singleton();
O principal problema desta implementação é o uso de um método estático que permite que qualquer desenvolvedor recupere a instância, seja qual for o contexto. Por exemplo, uma exibição encarregada de mostrar uma lista de opções, que chama diretamente o banco de dados para executar uma consulta SQL e recuperar seu conteúdo. Não é que devamos fazer absolutamente o MVC, mas a separação das responsabilidades fornece um ótimo nível de manutenção. Se, o desenvolvedor estiver apressado, ele vai pegar o caminho mais curto, e com a primeira marcha engatada, ele se deixará envolver demais pela velocidade.
Na verdade, se o problema for o método estático, é melhor optar por uma fábrica ("factory"), cujo papel é o de nos fazer voltar uma instância da classe e, se o chamarmos várias vezes, retornamos à instância já criada. Portanto, não temos mais uma única classe, mas duas. O problema desta solução é que o fabricante se torna público possibilitando criar outras instâncias da classe de singleton. Para evitar isso, desde o PHP 7, é possível criar uma classe anônima dentro da fábrica:
<?php
class Factory {
private $instance = null;
public function getInstance()
{
if ($this->instance === null) {
$this->instance = new class() {
private $var;
public function get() {
return $this->var;
}
public function set($value) {
$this->var = $value;
}
};
}
return $this->instance;
}
}
$factory = new Factory();
$o = $factory->getInstance();
$o->set(5);
$o2 = $factory->getInstance();
// Exibe "int(5)"
var_dump($o2->get());
O problema desta solução é que, não é possível fazer uma configuração típica (tipo de sugestão) porque a classe retornada é anônima "object(class@anonymous)". Este é um falso problema, já que a configuração típica deveria basear-se nas interfaces, e, neste caso, será proposta uma classe anônima que implemente uma interface:
<?php
interface MyInterface {
public function get(): int;
public function set(int $value);
}
class Factory {
private $instance = null;
public function getInstance(): MyInterface
{
if ($this->instance === null) {
$this->instance = new class() implements MyInterface {
private $var;
public function get(): int {
return $this->var;
}
public function set(int $value) {
$this->var = $value;
}
};
}
return $this->instance;
}
}
function display(MyInterface $object)
{
var_dump($object->get());
}
$factory = new Factory();
$o = $factory->getInstance();
$o->set(5);
$o2 = $factory->getInstance();
display($o2);
O problema agora é que é possível instanciar várias fábricas e, por conseguinte, ter várias instâncias do nosso singleton. Poderíamos declarar a propriedade "$ instance" estática mas, neste caso, voltamos ao problema da criação de uma instância da fábrica em qualquer lugar do código, para dispor do nosso singleton. Na realidade, o ideal é impedir que se instancie várias vezes a nossa classe e manter a nossa única instância na nossa fábrica, ou seja, uma Classe Musket (Musket Class). Porém, vamos encontrar um problema de classes anônimas, a classe é diferentes de uma instância para a outra e o uso de uma propriedade estática não identifica se esta é uma nova instância:
<?php
class Factory {
private $instance = null;
public function getInstance()
{
if ($this->instance === null) {
$this->instance = new class() {
private static $instanciated = false;
public function __construct()
{
if (self::$instanciated) {
throw new RuntimeException('Could not instanciate class');
}
self::$instanciated = true;
}
private $var;
public function get(): int {
return $this->var;
}
public function set(int $value) {
$this->var = $value;
}
};
}
return $this->instance;
}
}
$factory = new Factory();
$o = $factory->getInstance();
$o->set(5);
$factory2 = new Factory();
// Esperaríamos uma exceção aqui ... mas não
$o2 = $factory->getInstance();
var_dump($o2->get());
Mas, como não podemos instanciar a nossa classe várias vezes, não somos mais obrigados a usar uma classe anônima, então, o código ficaria assim:
<?php
class Singleton {
private static $instanciated = false;
private $var;
public function __construct()
{
if (self::$instanciated === true) {
throw new RuntimeException('Could not instanciate class');
}
self::$instanciated = true;
}
// Proibição de Clonagem
private function __clone()
{
}
public function get(): int {
return $this->var;
}
public function set(int $value) {
$this->var = $value;
}
}
class Factory {
private $instance = null;
public function getInstance()
{
if ($this->instance === null) {
$this->instance = new Singleton();
}
return $this->instance;
}
}
$factory = new Factory();
$o = $factory->getInstance();
$o->set(5);
$factory2 = new Factory();
// Gera uma exceção "Could not instanciate class"
$o2 = $factory2->getInstance();
Neste caso, não temos um singleton, mas uma Classe Musket (Musket Class). Aliás, podemos aplicar este mesmo princípio na própria fábrica.
Uma desvantagem da Classe Musket é a sua capacidade de teste, porque uma vez instanciada, não podemos mais fazê-lo, ou seja, não podemos testar vários casos. Este problema também existe com o singleton e não é raro adicionar um método estático "resetInstance ()". No caso da Classe Musket, também podemos adicionar um método do tipo "rearmar()" para ajudar a criar uma nova instância. Mas, "Qual seria o interesse de tudo isso?" O singleton é usado, na maior parte dos casos, pelo desempenho e pela sua simplicidade. Além disso, podemos ter um bom desenvolvimento e criar um código que não seja muito simples. Apesar de concordar com o desempenho e a simplicidade, não acredito no seu uso rigoroso. Se você estiver sozinho em seu projeto e não tiver nenhuma data de entrega a ser respeitada, aí sim, talvez seja possível. Em todos os outros casos, a minha experiência tem demonstrado que ele não aguenta muito tempo: Deadline is a bitch!