Singleton PHP – Exemplos de Padrões de Projeto

Singleton PHP

Já passou por suas cabeças que em algumas situações, nós programadores criamos instâncias de classes com vida curta ou que gostariamos de ter uma mesma instância da classe em vários arquivos ou camadas do código? Ai vocês me perguntam “Como assim?”. Vou mostrar alguns exemplos de Anti-Padrões e como usar o Singleton PHP, assim fica mais claro.

Anti-Padrão Singleton PHP

Comumente vejo por ai algumas classes de configuração como a mostrado abaixo.

Arquivo Config.php

/**
* Classe abstrata, mãe de todas as classes de configuração
* @author DigitalDev
*/
abstract class Config
{
	private $v_params;

	public function __contruct()
	{
		$this->v_params = array();
	}

	protected function setParam($mx_param,$mx_value)
	{
		$this->v_params[$mx_param] = $mx_value;
		return $this;
	}

	protected function getParam($mx_param)
	{
		if(isset($this->v_params[$mx_param]))
			return $this->v_params[$mx_param];
		return null;
	}
}

Arquivo ConfigPrincipal.php

/**
* Classe de configuração principal do sistema
* @author DigitalDev
*/

//incluindo o arquivo da classe mãe
require_once 'Config.php';

class ConfigPrincipal extends Config
{
	public function __construct()
	{
		parent::__contruct();
		
		//definindo algumas configurações
		$this->setParam('sistema_nome', 'DigitalDev Site');
		$this->setParam('adm_nome','Tarcisio Ruas');
		$this->setParam('adm_email','contato@digitaldev.com.br');
	}
}

Imaginem que seja necessário acessar os dados da classe de configuração principal em alguns lugares do código, por exemplo o arquivo index.php, onde colocaremos as tags HTML head e title do sistema.

<?php
//incluindo o arquivo de configuração principal
require_once 'ConfigPrincipal.php'; 

//instanciando a classe de configuração
$o_config = new ConfigPrincipal();
?>
<html>
	<head>
		<title><?php echo $o_config->getParam('sistema_nome'); ?></title>
		...
	</head>
	<body>
		...
	</body>
</html>

Agora que seja necessário usar essa mesma classe em outra parte ou camada do código, como abaixo.

<?php
require_once 'ConfigPrincipal.php';

/**
* Controlador ou Controller padrão do sistema 
*/
class IndexController
{
	/**
	* Ação ou Action padrão do sistema
	*/
	public function IndexAction()
	{
		/**
		* Criando um array com dados
		* ficticios de clientes
		* @var Array
		*/
		$v_email_clientes = array
		(
			'Joao' => 'joao@domain.com.br',
			'Carlos' => 'carlos@domain.com.br', 
			'Pedro' => 'pedro@domain.com.br',
			'Marcos' => 'marcos@marcos.com.br'
		);
		
		
		/*
		* instanciando a classe de configuração para 
		* obter o e-mail do administrador
		*/
		$o_config = new ConfigPrincipal();
		$st_adm_email = $o_config->getParam('adm_email');
		
		foreach( $v_email_clientes AS $st_cliente => $st_email_cliente )
			$this->enviaEmail
			(
				$st_adm_email,
				$st_adm_email,
				'E-mail Teste',
				'Texto E-mail Teste'
			);
	}
	
	/**
	* Metodo de envio de e-mail
	*/
	private function enviaEmail( $st_de , $st_para , $st_titulo , $st_texto )
	{
		/**
		 * @todo Implementar código necessário 
		 * para o funcionamento do método
		 */
	}
}
?>

Perceberam que foram criadas duas instâncias da classe ConfigPrincipal? E se fosse necessário inserir mais parâmetros na classe de configuração e usar os tais em outros métodos, camadas e arquivos do software? Seria necessário instanciar a classe algumas outras vezes e as instâncias teriam uso curto e pontual, além disso mais memória seria alocada para cada uma delas.

E sobre o fato de inserir novos parâmetros na classe, em vários lugares do código, e usá-los em outros lugares? E para o caso de ser necessário existir apenas uma instância da classe para todo o código. Como você faria? Antes de responderem, vejam o que eu encontro com muita frequência por ai. Editei o arquivo index.php para demonstrar.

<?php
//incluindo o arquivo de configuração principal
require_once 'ConfigPrincipal.php'; 

/*
* Adicionando parametros
*/

$o_config = new ConfigPrincipal();

//Adiconando e-mail para envio de e-mail que não devem ser respondidos
$o_config->addParam('email_noreply','noreply@domain.com.br');

//Adiconando outros parametros aleatorios
$o_config->addParam('xx','xxxx');
$o_config->addParam('yy','xxyy');
$o_config->addParam('zz','zzzz');

?>
<html>
	<head>
		<title><?php echo $o_config->getParam('sistema_nome'); ?></title>
		...
	</head>
	<body>
		...
	</body>
</html>

Agora observem o código abaixo, onde fiz a chamada de uma variável global no meio de um método.

<?php
require_once 'ConfigPrincipal.php';

/**
* Controlador ou Controller padrão do sistema 
*/
class IndexController
{
	/**
	* Ação ou Action padrão do sistema
	*/
	public function IndexAction()
	{
		/**
		* Criando um array com dados
		* fictícios de clientes
		* @var Array
		*/
		$v_email_clientes = array
		(
			'Joao' => 'joao@domain.com.br',
			'Carlos' => 'carlos@domain.com.br', 
			'Pedro' => 'pedro@domain.com.br',
			'Marcos' => 'marcos@marcos.com.br'
		);
		
		
		/*
		* instanciando a classe de configuração para 
		* obter o e-mail do administrador
		*/
		global $o_config;
		$st_noreply_email = $o_config->getParam('email_noreply');
		
		foreach( $v_email_clientes AS $st_cliente => $st_email_cliente )
			$this->enviaEmail
			(
				$st_noreply_email ,
				$st_adm_email,
				'E-mail Teste',
				'Texto E-mail Teste'
			);
	}
	
	/**
	* Metodo de envio de e-mail
	*/
	private function enviaEmail( $st_de , $st_para , $st_titulo , $st_texto )
	{
		/**
		 * @todo Implementar código necessário 
		 * para o funcionamento do método
		 */
	}
}
?>

Se vocês já viram isso por ai ou já implementaram algo parecido, tenham consciência que o uso de variáveis globais é ultrapassado, em alguns casos causam falhas de segurança e contradiz totalmente o fator agregação, composição, herança e encapsulamento do paradigma de orientação a objeto. Mas então, como resolver tantos problemas mostrados acima?

O Padrão Singleton PHP

Como falei acima, a ideia do padrão de projeto Singleton é não permitir que em exista mais de uma instância de uma determinada classe para todo o código. As vantagens principais disso são – mínimo de alocação de memória, saída para o problema de criação de variáveis globais, um dado encapsulado em uma área de código persiste e pode ser acessado em vários outros lugares até que a execução do software chegue ao fim.

Vamos ao segredo da implementação do Singleton PHP. Para demonstrar o mesmo, reescrevi as classes e códigos já usadas acima, começando pela ConfigPrincipal.

/**
* Classe de configuração principal do sistema
* @author DigitalDev
*/
require_once 'Config.php';
class ConfigPrincipal extends Config
{
	/*
	* Variável que será usada para
	* guardar a única instância da classe
	* ConfigPrincipal
	*/
	static private $o_instance;
	
	/**
	* O construtor passou de public para private
	*/ 
	private function __construct()
	{
		parent::__contruct();
		
		//definindo algumas configurações
		$this->setParam('sistema_nome', 'DigitalDev Site');
		$this->setParam('adm_nome','Tarcisio Ruas');
		$this->setParam('adm_email','contato@digitaldev.com.br');
	}
	
	/**
	* Método responsável pela mágica da 
	* única instância da classe ConfigPrincipal
	* @return ConfigPrincipal
	*/
	static function getInstance()
	{
		/**
		* A classe já foi instanciada? Se não, instancie...
		*/
		if(!isset(self::$o_instance))
		{

			$st_class = __CLASS__;
			self::$o_instance = new $st_class();
		}
		
		//retorne a instância
		return self::$o_instance;
	}
}

A primeira mudança foi a troca no modo em que o método __construct da classe ConfigPrincipal é acessado, passando o mesmo de public para static. Isso evita que instâncias da classe sejam criadas usando a instrução new como no código mostrado abaixo. A tentativa de usar tal instrução resultará em um erro.

<?php

require_once 'ConfigPrincipal.php';

//isso não executará, retornando um erro no PHP
$o_config = new ConfigPrincipal();

?>

Agora, para conseguir acessar a instância única da classe ConfigPrincipal, é necessário usar seu método ConfigPrincipal::getInstance(), como mostrado no código abaixo.

<?php

require_once 'ConfigPrincipal.php';

//isso irá funcionar, retornado a instância única da classe
$o_config = ConfigPrincipal::getInstance(); 

?>

Vejam a como usar a classe ConfigPrincipal, agora implementada usando o padrão Singleton PHP nos outros arquivos e camadas do software.

<?php
//incluindo o arquivo de configuração principal
require_once 'ConfigPrincipal.php';

//recuperando a instância única da classe
$o_config = ConfigPrincipal::getInstance();
?>
<html>
	<head>
		<title><?php echo $o_config->getParam('sistema_nome'); ?></title>
		...
	</head>
	<body>
		...
	</body>
</html>

e na classe IndexControler

<?php
require_once 'ConfigPrincipal.php';

/**
* Controlador ou Controller padrão do sistema 
*/
class IndexController
{
	/**
	* Ação ou Action padrão do sistema
	*/
	public function IndexAction()
	{
		/**
		* Criando um array com dados
		* ficticios de clientes
		* @var Array
		*/
		$v_email_clientes = array
		(
			'Joao' => 'joao@domain.com.br',
			'Carlos' => 'carlos@domain.com.br', 
			'Pedro' => 'pedro@domain.com.br',
			'Marcos' => 'marcos@marcos.com.br'
		);
		
		

		//usando a instância única da classe ConfigPrincipal
		$o_config = ConfigPrincipal::getInstance();
		$st_noreply_email = $o_config->getParam('email_noreply');
		
		foreach( $v_email_clientes AS $st_cliente => $st_email_cliente )
			$this->enviaEmail
			(
				$st_noreply_email ,
				$st_adm_email,
				'E-mail Teste',
				'Texto E-mail Teste'
			);
	}
	
	/**
	* Metodo de envio de e-mail
	*/
	private function enviaEmail( $st_de , $st_para , $st_titulo , $st_texto )
	{
		/**
		 * @todo Implementar código necessário 
		 * para o funcionamento do método
		 */
	}
}
?>

E assim se dá a implementação do padrão de projeto Singleton, que aqui nesse post chamei várias vezes de Singleton PHP, pelo fato de ter usando a linguagem PHP para escrever o código. Dei um exemplo simples de como e onde usar o padrão com uma classe de configuração, mas se observarmos nossos programas com atenção, encontraremos várias outras aplicações para o Singleton PHP.

Se houver qualquer dúvida sobre esse post, sobre o blog ou sugestão, usem a área de perguntas e respostas abaixo. Obrigado.

Alguns links sobre o assunto:
Patterns – php.net
Singleton – Wikipedia

Deixe uma dúvida, resposta ou sugestão

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.