Arquitetura de Software com CodeIgniter – Camada Facade e Padronização de Nomenclaturas
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
. Bom, então é isso, até a próxima!
Arizão
Postado em 17/09/2009 às 08:03Muito bacana o artigo Fernando! Muito didático! Meus sinceros parabéns!