Fernando Mantoan Desenvolvimento e tecnologia da informação no geral

Arquitetura de Software com CodeIgniter – Camada Facade e Padronização de Nomenclaturas

Postado em 16 de setembro de 2009. Categorias: CodeIgniter, PHP | 7 Comentários

Continuando com a série de artigos sobre uma proposta de arquitetura de software com o framework CodeIgniter, desta vez abordarei a criação da camada Facade na arquitetura e algumas mudanças na nomenclatura dos Controllers, fornecendo uma maior padronização.

Obs: Para acompanhar este artigo é muito interessante que você já tenha feito as outras etapas citadas nos artigos anteriores.

Facade Design Pattern

O pattern arquitetural MVC traz uma forma muito mais consistente de se desenvolver aplicações, dando aos desenvolvedores camadas lógicas destinadas a cada parte que compõe uma aplicação, estas sendo lógica de negócio, apresentação e classes de representação de dados. Porém, existem alguns conceitos no mesmo que podem confundir muitos desenvolvedores, um deles é o de onde colocar regras de negócio? Algumas pessoas fazem isso no Controller, deixando o Model apenas como representação do negócio (objetos com get e set) e outros fazem com que o Model seja representação do negócio unido com as regras da aplicação também (por exemplo, uma lógica de venda), e o Controller fica encarregado apenas de controlar o fluxo entre as camadas. Eu particularmente uso o último conceito, por já ter lido ele em vários livros e também por seguir o conceito thin controller and fat model.

Em uma arquitetura de software não pode existir mal-entendimento de conceitos, senão cada programador faria da maneira que acha mais sensata, fugindo da padronização. Para que isso possa ser evitado, essa arquitetura utiliza um design pattern chamado Facade. O conceito de um Facade é simples, esconder várias linhas de códigos em seus métodos, fornecendo métodos comuns a outras partes da aplicação. Ele será utilizado para conter as regras de negócio, fazendo com que o Controller cuide de controle de fluxo e com que o Model fique apenas encarregado da representação do domínio da aplicação. Mesmo que nessa arquitetura o Model represente os dados do banco de dados e o DAO cuide das operações com o banco, poderia existir problemas de padronização com relação a escolha da camada na hora de se codificar a regra de negócio, este problema pode, então, ser resolvido com a Facade conceituada acima. Com isso a arquitetura teria um conceito de “thin controller, thin model and fat Facade“.

Modificando o Core

A camada Facade é criada diretamente no core do CodeIgniter, esta foi uma abordagem simplificada para se criar esta camada, a melhor abordagem seria utilizar os hooks para que não seja necessário um retrabalho ao atualizar o core do framework, pois tendo codificado direto no core alguns arquivos podem acabar sendo substítuidos necessitando a recodificação da camada. Este aviso é necessário pelo simples fato de ser importante saber que existem melhores alternativas.

Tendo tudo explanado, é hora de criar um arquivo chamado Facade.php, dentro de /system/libraries/, e colocar o seguinte conteúdo nele:

<?php
abstract class Facade
{
	public $_parent_name = '';
	public function Facade()
	{
		$this->_parent_name = ucfirst(get_class($this));
		log_message('debug', "Facade Class Initialized");
	}
	public function _assign_libraries($use_reference = TRUE)
	{
		$CI =& get_instance();
		foreach (array_keys(get_object_vars($CI)) as $key)
		{
			if ( ! isset($this->$key) AND $key != $this->_parent_name)
			{
				// In some cases using references can cause
				// problems so we'll conditionally use them
				if ($use_reference == TRUE)
				{
					$this->$key = NULL; // Needed to prevent reference errors with some configurations
					$this->$key =& $CI->$key;
				}
				else
				{
					$this->$key = $CI->$key;
				}
			}
		}
	}
}

Este código utiliza como base o código do Model.php, classe nativa do CodeIgniter, onde no caso da Facade serão configuradas e carregadas as bibliotecas que existem no Controller para deixá-las disponíveis nesta camada, já que isso será útil, por exemplo, na hora de validar formulários. Como o Model já foi implementado para obter estas bibliotecas, utilizar sua implementação era a melhor abordagem para a Facade, mas outra forma seria herdar da classe Model.

Agora, já existe uma camada Facade como base para extender as Facades específicas, só que ainda é necessário implementar um loader para a Facade. O CodeIgniter fornece métodos para carregar o Model, View e as Libraries. Esse loader funciona da seguinte maneira, pelo comando $this->load->model(‘nomedomodel’), de dentro de um Controller, o Model fica disponível para o Controller. O responsável por esta funcionalidade é o Loader, que pode ser encontrado na pasta /system/libraries/Loader.php. Primeiramente é necessário setar um atributo que conterá as Facades carregadas pelo Loader. A definição dos atributos da classe é a seguinte:

var $_ci_ob_level;
var $_ci_view_path        = '';
var $_ci_is_php5        = FALSE;
var $_ci_is_instance     = FALSE; // Whether we should use $this or $CI =& get_instance()
var $_ci_cached_vars    = array();
var $_ci_classes        = array();
var $_ci_loaded_files    = array();
var $_ci_models            = array();
var $_ci_helpers        = array();
var $_ci_plugins        = array();
var $_ci_varmap            = array('unit_test' => 'unit', 'user_agent' => 'agent');

Adicionando a variável $_ci_facades, que será um array contendo as Facades carregadas, a definição então fica:

var $_ci_ob_level;
var $_ci_view_path		= '';
var $_ci_is_php5		= FALSE;
var $_ci_is_instance 	= FALSE; // Whether we should use $this or $CI =& get_instance()
var $_ci_cached_vars	= array();
var $_ci_classes		= array();
var $_ci_loaded_files	= array();
var $_ci_facades		= array(); // Facade pattern added to the CI
var $_ci_models		= array();
var $_ci_helpers		= array();
var $_ci_plugins		= array();
var $_ci_varmap		= array('unit_test' => 'unit', 'user_agent' => 'agent');

Agora, para implementar a funcionalidade de carregar as Facades, é necessário adicionar, neste mesmo arquivo Loader.php, o seguinte método, que pode ser colocado acima do método database():

function facade($facade, $name = '')
{
	if (is_array($facade))
	{
		foreach($facade as $babe)
		{
			$this->facade($babe);
		}
		return;
	}

	if ($facade == '')
	{
		return;
	}

	// Is the facade in a sub-folder? If so, parse out the filename and path.
	if (strpos($facade, '/') === FALSE)
	{
		$path = '';
	}
	else
	{
		$x = explode('/', $facade);
		$facade = end($x);
		unset($x[count($x)-1]);
		$path = implode('/', $x).'/';
	}

	if ($name == '')
	{
		$name = strtolower(str_replace('_', '', $facade));
	}

	if (in_array($name, $this->_ci_facades, TRUE))
	{
		return;
	}

	$CI =& get_instance();
	if (isset($CI->$name))
	{
		show_error('The facade name you are loading is the name of a resource that is already being used: '.$name);
	}

	$facade = strtolower($facade);

	if ( ! file_exists(APPPATH . 'facades/'. $path . $facade . EXT))
	{
		show_error('Unable to locate the facade you have specified: '.$facade);
	}

	if ( ! class_exists('Facade'))
	{
		load_class('Facade', FALSE);
	}

	require_once(APPPATH . 'facades/'.$path.$facade.EXT);

	$facade = ucfirst(str_replace('_', '', $facade));

	$CI->$name = new $facade();
	$CI->$name->_assign_libraries();

	$this->_ci_facades[] = $name;
}

Este código, basicamente, irá incluir o arquivo da Facade e irá deixá-la disponível através de um atributo do Controller em questão, permitindo desta forma que o Controller acesse os métodos desta Facade.

Testando

Agora, é necessário testar se a camada criada está funcionando corretamente. Primeiramente é necessária a criação da pasta facades dentro de /system/application/. Esta pasta armazenará as Facades que a aplicação necessitará. Apenas para testar a camada basta criar o arquivo /system/application/facades/index_facade.php, e colocar o seguinte conteúdo nele:

<?php
class IndexFacade extends Facade
{
	public function index() { echo 'camada facade acionada'; }
}
?>

Dentro do Controller welcome.php, criado no artigo anterior, basta mudar o construtor e o método index(), para que fique assim:

public function Welcome()
{
	parent::Controller();
	$this->load->library('smartyview');
	$this->load->facade('index_facade');
}
public function index()
{
	$this->indexfacade->index();
	$dao = new TarefasDAO();
	$tarefas = $dao->allData();
	$this->smartyview->assign_by_ref('tarefas', $tarefas);
	$this->smartyview->display('index.tpl');
}

Ao chamar no navegador, deverá exibir a mensagem “camada facade acionada“. Com isso, a camada Facade está corretamente integrada ao CodeIgniter, de uma forma que pode ser considerada nativa, pois ela pode ser carregada e acionada da mesma forma que os Models padrão do framework.

Nomenclatura dos Controllers

Agora, para finalizar a definição da arquitetura, é necessária uma modificação na nomenclatura dos Controllers. Isso é necessário pois o CodeIgniter não obriga o desenvolvedor a seguir uma forma de nomear seus Controllers e isso pode dar brechas para ocorrer erros, como o de definir o mesmo nome para o Controller e para o Model, ou ao se criar um nome próprio para os Controllers, ele ser necessário na URL do browser (por exemplo, se seu Controller é Pessoas_controller, a url ficaria: seuhost.com/index.php/pessoas_controller).

A nomenclatura escolhida para esta arquitetura é a mesma do framework CakePHP, onde os arquivos devem ter o sufixo _controller.php, por exemplo pessoas_controller.php, e o nome das classes deve conter o sufixo Controller, por exemplo PessoasController.

Iniciando as modificações necessárias para se estabelecer esta nomenclatura, é necessário modificar a biblioteca do CodeIgniter Router.php, localizada em /system/libraries/, a função _set_request, possui a seguinte linha:

$this->set_class($segments[0]);

Ela seta a classe para o que vier dos segmentos da URL. É necessário adicionar o sufixo “_controller”, ficando esta linha assim:

$this->set_class($segments[0] . '_controller');

A próxima alteração a ser feita é no método _validate_request, a função completa é a seguinte:

function _validate_request($segments)
{
	// Does the requested controller exist in the root folder?
	if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))
	{
		return $segments;
	}
	// Is the controller in a sub-folder?
	if (is_dir(APPPATH.'controllers/'.$segments[0]))
	{
		// Set the directory and remove it from the segment array
		$this->set_directory($segments[0]);
		$segments = array_slice($segments, 1);
		if (count($segments) > 0)
		{
			// Does the requested controller exist in the sub-folder?
			if ( ! file_exists(APPPATH . 'controllers/' . $this->fetch_directory() . $segments[0] . EXT))
			{
				show_404($this->fetch_directory().$segments[0]);
			}
		}
		else
		{
			$this->set_class($this->default_controller);
			$this->set_method('index');
			// Does the default controller exist in the sub-folder?
			if ( ! file_exists( APPPATH . 'controllers/' . $this->fetch_directory() . $this->default_controller . EXT))
			{
				$this->directory = '';
				return array();
			}
		}
		return $segments;
	}
	// Can't find the requested controller...
	show_404($segments[0]);
}

Alterando-a, ela fica:

function _validate_request($segments)
{
	// Does the requested controller exist in the root folder?
	if (file_exists(APPPATH.'controllers/'.$segments[0] . '_controller' .EXT))
	{
		return $segments;
	}
	// Is the controller in a sub-folder?
	if (is_dir(APPPATH.'controllers/'.$segments[0]))
	{
		// Set the directory and remove it from the segment array
		$this->set_directory($segments[0]);
		$segments = array_slice($segments, 1);
		if (count($segments) > 0)
		{
			// Does the requested controller exist in the sub-folder?
			if ( ! file_exists( APPPATH . 'controllers/' . $this->fetch_directory() . $segments[0] . '_controller' . EXT ))
			{
				show_404($this->fetch_directory().$segments[0]);
			}
		}
		else
		{
			$this->set_class($this->default_controller);
			$this->set_method('index');
			// Does the default controller exist in the sub-folder?
			if ( ! file_exists( APPPATH . 'controllers/' . $this->fetch_directory() . $this->default_controller . EXT ))
			{
				$this->directory = '';
				return array();
			}
		}
		return $segments;
	}
	// Can't find the requested controller...
	show_404($segments[0]);
}

O que é feito, é alterar a forma padrão de se adicionar os Controllers, para que eles contenham o sufixo “_controller” em seus nomes. Agora basta mudar a forma de instanciar os Controllers. No arquivo CodeIgniter.php, localizado em /system/codeigniter/, basta alterar a seguinte linha:

$class  = $RTR->fetch_class();

Para:

$class  = str_replace('_', '', $RTR->fetch_class());

Pronto, agora a nomenclatura dos Controllers está padronizada.

Para testar se as alterações estão funcionando, basta renomear o arquivo welcome.php dentro de /system/application/controllers/, para welcome_controller.php, e dentro dele, modificar a declaração da classe e seu construtor, de:

class Welcome extends Controller
{
	public function Welcome
	{

Para:

class WelcomeController extends Controller
{
	public function WelcomeController
	{

Ao acessar o projeto, se tudo aparecer corretamente, significa que a padronização da nomenclatura foi setada com sucesso. Agora, os controllers requisitados por uma URL, como por exemplo index.php/carros, serão referenciados no arquivo com o sufixo _controller e numa classe com o sufixo Controller, por exemplo carros_controller.php e CarrosController.

Concluindo

Assim concluí-se a definição da arquitetura utilizando o Framework CodeIgniter e alguns Design Patterns. Com isso, já é possível desenvolver as aplicações necessárias para você/sua empresa, de uma forma robusta, dando mais facilidade ao trabalho tanto dos designers quanto dos programadores. A arquitetura possui 5 camadas lógicas para desenvolvimento: Controller, Facade, Model, DAO, View; possui os frameworks: Smarty e Doctrine; e possui uma forma padronizada de se nomear os Controllers.

No próximo artigo demonstrarei os passos necessários para se desenvolver um sistema simples de controle de tarefas, para demonstrar o uso de alguns helpers e os exemplos de cada camada lógica desta arquitetura. Futuramente transformarei esta série de artigos referentes à arquitetura com o CodeIgniter em um ebook e publicarei sob a licença Creative Commons, aqui no meu site.

Ficarei agora um tempo sem postar artigos referentes a esta arquitetura, estou com vários projetos de freelance, além de estar trabalhando em tempo integral na B3, e também tenho que refazer boa parte da fundamentação teórica da minha monografia (pois é, agora que troquei de orientador, eu vi que eu estava no rumo errado) e fazer todo o capítulo de experimento dela (digamos, o capítulo principal da monografia), ou seja, estou sem tempo disponível para escrever o próximo artigo. Colocarei alguns posts rápidos aqui no site, mas diariamente estou no twitter, falando do dia-a-dia, reclamando e etc :D . Bom, então é isso, até a próxima!

7 Comentários +

Responder

Arizão

Postado em 17/09/2009 às 08:03

Muito bacana o artigo Fernando! Muito didático! Meus sinceros parabéns!

    Responder

    admin

    Postado em 17/09/2009 às 08:24

    Opa, obrigado ein cara! Abraços!

Responder

Felipe R.

Postado em 21/09/2009 às 11:01

Olá Fernando.
Em primeiro lugar quero lhe dar os parabéns pelo site e pelos artigos.
Estão ótimos.

Em segundo lugar, estou desenvolvendo um site em php com o Code Igniter e gostaria de tirar duas dúvidas em relação ao artigo sobre design patterns.

Aqui neste trecho onde você diz: “O model também pode ser abstraído em 3 partes, uma para DAO, outra para o Model em si (Regras de Negócio) e uma para o Bean (ou VO).”

Está errado se eu tiver o Model em apenas uma parte?
No caso, meu Model seria responsável pela regra de negócios, por fazer as consultas ao banco e por criar objetos dele mesmo com os valores selecionados e passar para o controller através de um array de objetos.

Outra dúvida que tenho é que se a classe Database do Code Igniter poderia ser interpretada como DAO.

Desde já agradeço sua atenção.
Abraço,
Felipe

    Responder

    admin

    Postado em 21/09/2009 às 11:09

    Olá Felipe.
    Bom, não está errado ter o model com as regras de negócio, fazer as consultas no banco e etc. Este é um pattern chamado Active Record, que define que uma determinada classe terá regras de negócio e persistência, e este é o pattern padrão do CodeIgniter, CakePHP etc. Na arquitetura ele foi dividido em 3 camadas devido ao intúito da arquitetura: Padronizar a programação e fornecer N-Camadas que são muito importantes em aplicações enterprise.

    A classe Database do Code Igniter pode sim ser considerada um DAO Genérico, já que ela é responsável por fazer os acessos ao banco de dados. DAO (Data Access Object) é um ou mais objetos que, ou cuidam da persistência de determinadas entidades ou cuidam de toda a persistência. Tendo em vista o foco de uma DAO, pode-se considerar que a classe Database é uma DAO.

    Agradeço o comentário, qualquer dúvida pode postar.

Responder

Felipe Ribeiro

Postado em 29/09/2009 às 18:50

Olá Fernando, obrigado pela resposta me ajudou bastante.
Porém, surgiram mais dúvidas aqui.

Atualmente a estrutura de minha Model é a seguinte:

Ela possui atributos de uma determinada tabela do banco de dados. Estes atributos são privados e possuem seus respectivos métodos get e set.
Outros métodos que a model possui são, por exemplo, listar todos os registros da tabela ou listar apenas um registro a partir de um ID, etc.
Utilizando o Active Record do CI, faço a consulta na minha base de dados e em seguida, crio um loop para percorrer o query->result(), e para cada iteração do resultado crio um novo objeto de minha Model, setando seus atributos com cada linha retornada do resultado. Em seguida insiro este objeto em um array de objetos e passo para a proxima iteração do resultado, até o final dos registros.
Após o final do loop, retorno este array de objetos para quem fez sua chamada, no caso algum Controller.
Aí é que está minha dúvida. No meu Controller dei um PRINT_R neste array de objetos retornado da Model e vi que ele gera muitas outras coisas que pertencem ao “Super Objeto” do CI.

Gostaria de saber se estou desenvolvendo minha Model corretamente.

Se você puder, me adicione no MSN (strfelipe@msn.com) aí lhe envio exemplos de como está meu código.

Muito obrigado novamente,

Felipe.

Responder

Conrado Carvalho

Postado em 26/11/2009 às 08:18

Fernando, parabéns pela série de artigos! É a primeira vez que leio artigos sobre PHP, Design Patterns e Framework com essa profundidade e propriedade!

Conrado

Responder

Rocha

Postado em 20/07/2010 às 16:51

Boa tarde.
Gostei muito do seu artigo.
Eu poderia usar essa arquitetura utilizando o ZendFramework?
Poderia explicar melhor a facade?
Brigadão

Deixe um Comentário