Visão geral
O i-Educar possui várias classes espalhadas, principalmente, no diretório intranet/include. Essas classes possuem, na maioria das vezes, a abstração das tabelas do banco de dados. Outrora, as regras de negócio estão espalhadas, nessas mesmas classes e em vários arquivos do diretório intranet/, que seguem (embora não formalmente), o padrão Page Controller. Estes últimos são também responsáveis da maior parte do código que lida com a interface do usuário.
Essa situação cria uma total bagunça na distribuição das responsabilidades (principalmente ao levar-se em consideração a separação proposta pelo design pattern MVC). Essa falta de separação torna as alterações mais dispendiosas e difíceis de testar.
Percebendo essa dificuldade, foi combinado (no chat para desenvolvedores realizado no início de Abril/2009) entre os interessados no desenvolvimento do software que, qualquer nova funcionalidade seria implementada através de uma nova e leve API, fora do namespace das classes atuais.
Essa API teria como objetivo evitar um maior inchaço da API atual, permitindo o desenvolvimento de código com testes unitários, pouca depedência, acoplamento, alta coesão e encapsulamento. A falta desses elementos no código atual é o que torna a sua manutenção difícil.
No entanto, é necessário observar que, mesmo criando uma nova API, a antiga deve ser aos poucos refatorada e documentada. Aplicar os mesmos bons princípios de desenvolvimento ao código existente é a única maneira de diminuir os custos de manutenção e a incidência de bugs.
CoreExt
CoreExt veio de Core Extended. Foi apenas uma maneira de referenciar que essa API estende (com mais possibilidades, não da maneira formal de herança) o código do core (núcleo) do i-Educar.
É nesse contexto que surge o CoreExt, uma API exposta por componentes individuais e independentes, reusáveis e projetados para uso específico no i-Educar. É importante observar que o desenvolvimento desses componentes tem foco específico e não genérico como os projetos de frameworks web.
As primeiras classes desenvolvidas no CoreExt foram a CoreExt_Config e CoreExt_Config_Ini, ao qual foram enormemente baseadas nas classes Zend_Config e Zend_Config_Ini do Zend Framework.
O principal motivo para essa inspiração é a organização dos componentes do Zend Framework que é um framework web full-stack. Nesse framework, todas as suas classes de seus componentes compartilham o namespace Zend (exemplos: Zend_Config, Zend_Log). A classe base desses componentes geralmente implementam uma API de uma interface ou de uma classe abstrata localizada no diretório que contém o nome do componente (por exemplo: Zend/Db/Adapter/Abstract.php).
Componentes mais especializados, que possuem uma API maior dividem-se em mais classes. O componente Zend_Db, por exemplo, abstrai a conexão com diversos RDBMS como o MySQL, PostgreSQL até o IBM DB2. Como esses RDBMS possuem diferenças na implementação do padrão SQL e na forma que lidam com funcionalidades como sequences/auto-increments, são necessárias mais classes para implementar toda a abstração. Temos, por exemplo, as classes Zend_Db_Adapter_Mysqli (Zend/Db/Adapter/Mysqli.php) e Zend_Db_Adapter_Pdo_Pgsql (Zend/Db/Adapter/Pdo/Pgsql.php).
A estrutura de diretórios dos componentes do Zend Framework segue o padrão:
Zend/
|
|`Config/
| |`Abstract.php
| `Ini.php
|
|`Db/
| `Adapter/
| |`Pdo/
| | |`Pgsql.php
| | |`Oracle.php
| | `Mysql.php
| |
| `Mysql.php
|
|`Config.php
`Db.php
Por que não utilizar o Zend Framework então?
O motivo principal de não ter sido usado o componente Zend_Config é simplesmente de decisão como comunidade. A decisão de qual framework usar foi postergada para as atividades de desenvolvimento de uma versão 2.0 (com base de código nova).
O Zend Framework, por apresentar essa estrutura de componentes fracamente acoplados torna-se mais adaptável para a implementação em projetos de software existentes, por não requerer o uso de todos os seus componentes de forma integrada (como acontece na maioria dos frameworks que implementam o padrão MVC, como, por exemplo, Symfony e CakePHP). Por isso é denominado de framework use at will.
Essa filosofia de desenvolvimento pode trazer inúmeros benefícios na nova API e principalmente facilidade de integração com o código da API existente.
Padrão de diretórios
Seguindo a padronização utilizada no Zend Framework, a API CoreExt foi definida assim na organização geral:
./
|
|`configuration/
|`includes/
| |`bootstrap.php
| `download.php
|
|`intranet/
|`lib/
|`CoreExt/
| |`Config/
| | `Ini.class.php
| |
| `Config.class.php
|
|`Utils/
|`FileStream.class.php
`Mimetype.class.php
O diretório principal aqui é o lib/CoreExt. Nesse diretório é que serão desenvolvidas as novas classes da nova API. Toda e qualquer necessidade nova (web services, comunicação entre processos, geração de relatórios gráficos, entre outros) deverá ser desenvolvida nesse namespace. Digamos, por exemplo, que criemos um componente de geração de relatórios com gráficos estatísticos. Ao invés de tentar alterar as classes de relatório já existentes, a melhor opção seria criar classes para lidar com essa nova responsabilidade. Vamos nomear esse componente de CoreExt_Report:
CoreExt/
|`Report/
| |`Graphic/
| | |`Abstract.class.php
| | |`Pie.class.php
| | `Bar.class.php
| |
| |`Layout/
| | |`Abstract.class.php
| | |`OneCollumn.class.php
| | |`TwoCollumn.class.php
| | |`StackedAndTwoCollum.class.php
| |
| |`Report/
| | |`Interface.php
| | |`Abstract.class.php
| |
| |`Sheet/
| | |`Abstract.class.php
| | `A4.class.php
| |
| |
| |`Graphic.class.php
| |`Layout.class.php
| `Sheet.class.php
|
`Report.class.php
O nosso componente CoreExt_Report iria conter uma série de classes, cada qual com a sua responsabilidade. CoreExt_Report_Sheet definiria o tamanho do papel, CoreExt_Report_Layout seria responsável pela view/template do relatório e por aí vai. Outra vantagem é a separação explícita do namespace e a padronização no nome das classes, o que diminue a possibilidade de colisão de nomes. A desvantagem é o comprimento dos nomes, algo que poderá ser resolvido futuramente ao passo que o código for sendo portado para o PHP 5.3, que tem um mecanismo de namespaces built-in.
Uso no código legado
Ainda é necessário discutir e refinar a forma que esta nova API será integrada no código existente. Integrada aqui com o sentido de uso (via criação de objeto) e extensão (via herança). O caminho nunca deverá ser inverso (uma classe CoreExt especializando uma classe legada).
Por conveniência, o include_path do PHP é altetado para incluir o diretório lib/, tornando mais simples a inclusão da classe necessária via require/include. O arquivo responsável por isso é o includes/bootstrap.php, que foi incluído em diversos arquivos PHP responsáveis pela inclusão de diversas classes do código legado. É necessário, no entanto, ter atenção especial com arquivos PHP que tratam requisições simples e que por ventura não façam include de um desses arquivos (geralmente possuem o nome de geral.php).
No caso de CoreExt_Config_Ini, por exemplo, o objeto é armazenada em um array global, o $coreExt. O seu uso deve ser read-only e pode ser recuperado no escopo de um método com o uso da palavra-chave global:
// em includes/bootstrap.php
$coreExt['Config'] = new CoreExt_Config_Ini();
// uma classe Foo qualquer em intranet/
class Foo
{
public function __construct()
{
global $coreExt;
$this->config = $coreExt['Config'];
}
protected function _doFoo()
{
$obj = new Bar($this->config->app->bar);
$this->bar = $obj;
return $this;
}
}
O uso desse array global pode ser substituído futuramente por uma implementação do design pattern Registry mas, conceitualmente, esta aproximação é semelhante.
Com a criação de novos componentes, será importante distinguir a necessidade de armazenar os objetos no array global $coreExt. No caso do objeto CoreExt_Config_Ini, seus atributos contém valores de configuração para uso durante o processamento da requisição. Uma vez instanciado com estes valores, seu uso passa a ser read-only, o que torna-o apto a armazenar no array $coreExt. Se não for mais modificado, qual a necessidade de passá-lo como parâmetro de chamadas de métodos?
Se o caso for diferente do anterior, a aproximação será a clássica inclusão do arquivo, com o uso de require_once:
// uma classe Foo qualquer em intranet/
class Foo
{
public function generateReport()
{
// basta fazer o require a partir de CoreExt/ pois o include_path
// já inclui o diretório lib/
require_once 'CoreExt/Report.class.php';
$report = new CoreExt_Report($this->getReportOptions(), $this->getReportData());
$report->generate();
$this->_report = $report;
return $this;
}
}
API de módulos
O CoreExt em si não é a API destinada a expor os hooks aos quais os futuros módulos farão a interação com o restante do i-Educar. Essa API poderá ser exposta via componente ou por uma série de classes que utilizam a API dos componentes de CoreExt.
O objetivo desses módulos seria de prover as funcionalidades do i-Educar de forma modular, diminuindo gradativamente a implantação de funcionalidades empilhadas em intranet/. Cada módulo faria o tratamento de sua regra de negócio, eliminando gradativamente a bagunça de código presente das funcionalidades atuais que muitas vezes espalham-se em diferentes arquivos (geralmente sufixados com cad, lst e det para distinguir as responsabilidades).
