Design Patterns com PHP -Singleton
O padrão criacional chamado Singleton me garante que terei somente uma instância de uma classe, independente se é a instância da própria classe ou de uma classe dentro de outra, pode ter ficado um pouco confuso essa explicação, então vamos aos exemplos para deixar tudo claro.
Vou dar dois exemplos, o primeiro de uma classe de Log(com instância da própria classe) e outra de uma classe de conexão(com instância de outra classe).
Antes de dar esses exemplos acho importante você entender como funciona uma classe com métodos e atributos estáticos, para te mostrar como funciona vou criar uma classe simples chamada Counter.php
, veja a estrutura abaixo.
<?php
namespace app;
class Counter
{
protected static $counter = 0;
public static function addCounter($number)
{
return self::$counter+=$number;
}
public static function getCounter(): int
{
return self::$counter;
}
}
Criei dois métodos estáticos, um para adicionar um número ao contador e outra para pegar esse número, e o próprio contador é um atributo estático, e ele é estático porque os métodos são estáticos, e não posso chamar uma propriedade não estática dentro de um método estático.
No método addCounter
estou atribuindo a essa propriedade estática $counter
o valor dele mais o valor passado por parâmetro nesse método.
E claro o método getCounter
retorna esse counter.
Veja abaixo como usarei essa classe.
<?php
require "app/Counter.php";
use App\Counter;
Counter::addCounter(3);
Counter::addCounter(3);
var_dump(Counter::getCounter());
Cada vez que eu chamo o addCounter
estou adicionando para a propriedade estática $counter
o valor passado por parâmetro mais o valor que ele já tinha(essa propriedade estática continua com o mesmo valor de antes), então quando eu chamo o método addCounter o valor da propriedade estática $counter
continua o mesmo da última vez e adicionando mais o valor passado por parâmetro.
Agora se eu chamar esse método addCounter
em qualquer parte do meu sistema a propriedade $counter continuará a ter o mesmo valor de antes acrescentando o valor passado por parâmetro.
Sabendo disso posso agora criar uma classe de Log, e agora sim poder trabalhar com a instância única, que é o motivo de usar o Singleton.
Se eu quiser retornar a instância da própria classe dentro de um método estático eu posso fazer isso:
namespace app;
class Log
{
public static function getInstance()
{
return new self();
}
}
Mas só que não faz sentido nenhum, se eu quiser somente a instância da classe Log era somente instancia-la em outro lugar, assim:
$log = new Log
Lembre-se que nosso intuito é ter somente uma instância dessa classe, não importa o número de vezes que a chamemos, porque agora se eu em outro lugar instancia-la novamente terei obviamente outra instância dessa classe.
Mas agora se eu chamar o método estático getInstance
em vários lugares terei sempre a mesma instância ou terei outra?Porque se me lembro bem o intuito do Singleton é ter somente uma instância de uma classe, não importa o número de vezes que chamemos o método que cria essa instância.
<?php
require "app/Log.php";
use App\Log;
var_dump(Log::getInstance());
var_dump(Log::getInstance());
Você não concorda que cada vez que chamo o método getInstance
a instância(new self
) é retornada?.E com isso retorna tem uma nova instancia dessa classe, você pode acompanhar isso vendo o resultado da expressão abaixo:
var_dump(Log::getInstance() == Log::getInstance()); // true
var_dump(Log::getInstance() === Log::getInstance()); // false
Veja que quando comparei usando três sinais de igual retornou false
, e usando dois retornou true
, porque quando uso dois sinais de igual verifica simplesmente se são instâncias da mesma classe, e não se são a mesma instância, e quando uso três ai sim verifica se são da mesma instância.
Então como irei pegar a mesma instância sempre que chamo esse método getInstance
?
Agora sim vou entrar no papel da propriedade estática, porque a única coisa que fiz até agora foi retornar a instância do próprio objeto(new self
).
Lembra o que eu disse lá no começo desse artigo?.Que a propriedade estática, nesse caso $instance
, preserva o valor dado a ela anteriormente?.No caso do $counter
continua com o valor dado a ele na última vez que chamei o método addCounter
, com isso em mente, eu então posso usar o mesmo princípio para ter somente uma instância, verificando se já existe algo em $instance
, se tiver retorna ele, se não, faz uma nova instância da própria classe.
<?php
namespace app;
class Log
{
private static $instance = null;
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
O código acima faz a simples verificação se já existe algo em $instance
, se não existir faz uma nova instância e atribui a essa propriedade estática, se já existir retorna ela, simples assim, agora não importa onde eu chamar o método getInstance
, sempre terei uma única instância da classe.
Mas ainda faltam duas coisas para garantir que terei somente uma instância, primeiro proibir que se clone essa instância, porque se eu clonar terei outra instância, e com isso a idéia do Singleton vai pro brejo, veja como ficaria o clone.
<?php
require "app/Log.php";
use App\Log;
$log1 = Log::getInstance();
$log2 = clone $log1;
var_dump($log1 === $log2); // false não são a mesma instância
O que posso fazer para proibir isso é dentro da classe Log criar o método mágico __clone
como privado, fazendo isso ninguém mais clonará esse objeto.
<?php
namespace app;
class Log
{
private function __clone()
{
}
private static $instance = null;
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
Outra coisa é proibir a instância dessa classe através do new Log
, para isso usarei de novo a mesma tática que usei para proibir um clone, que é colocar o método mágico __construct
como privado.
<?php
namespace app;
class Log
{
private function __construct()
{
}
private function __clone()
{
}
private static $instance = null;
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
Quem quiser agora uma instância de Log
, tem que chamar o método getInstance
.
E para finalizar esse exemplo vou ter a certeza que nenhuma outra classe estenderá essa, colocando a palavra final
antes do classe.
<?php
namespace app;
final class Log
{
private function __construct()
{
}
private function __clone()
{
}
private static $instance = null;
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
Se alguma outra classe quiser estender essa não vai conseguir, justamente por ter essa palavra chave final, que proíbe qualquer estensão dela.
O exemplo que dei acima foi mostrando como ter somente uma instância da própria classe, que é o Log, mas e no caso de uma conexão com o banco de dados, onde quero somente uma instância do PDO, como faço?
Vou criar uma classe chamada Connection, mas veja bem, o que quero é somente uma instância do PDO e não da própria classe, veja uma exemplo de conexão sem usar o Singleton.
<?php
namespace app\\database;
class Connection
{
public static function getInstance()
{
return new \PDO("mysql:host=localhost;dbname=lumen", 'root', '', [
\PDO::ATTR_DEFAULT_FETCH_MODE => \\PDO::FETCH_OBJ
]);
}
}
Agora vou chamar esse método getInstance duas vezes e comparar se são a mesma instância, do mesmo jeito que fiz com o Log.
<?php
require "app/database/Connection.php";
use app\database\Connection;
$instance1 = Connection::getInstance();
$instance2 = Connection::getInstance();
var_dump($instance1 === $instance2); // false
O resultado, como esperado, é falso, não são e mesma instância.
Agora usarei o padrão Singleton, também da mesma maneira que fiz com o Log.
namespace app\\database; final class Connection { private static $instance = null; private function __construct() { } private function __clone() { } public static function getInstance() { if (self::$instance === null) { self::$instance = new \\PDO("mysql:host=localhost;dbname=lumen", 'root', '', [ \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_OBJ ]); } return self::$instance; } }
Se eu fizer a comparação agora terei a mesma instância sempre que chamar o getInstance
.
<?php
require "app/database/Connection.php";
use app\database\Connection;
$instance1 = Connection::getInstance();
$instance2 = Connection::getInstance();
var_dump($instance1 === $instance2); // true
Então é assim que se trabalha com o Singleton. simples não?
Se quiser conhecer meu trabalho mais a fundo, visite meu canal no YouTube e veja meus cursos completos clicando nos links abaixo.
🔥 Canal no YouTube: Ir para o canal no YouTube
🔥Veja meus cursos disponíveis: Ir para a lista de cursos
Olá Alexandre, estou iniciando no PHP e lendo livros e tutoriais de design patterns, nada é mais explicativo e fácil de aprender como teus posts. Está me ajudando e muito.
Obrigado!