Nos primeiros anos, porque não dizer até pouco tempo atrás, o desenvolvimento de software foi sempre focado no resultado final do projeto, ou seja, a aplicação que o usuário final ia utilizar. Realmente, naquele momento, se comparado às tecnologias disponibilizadas hoje, não era necessária a preocupação excessiva com a qualidade no desenvolvimento do software, mas sim na sua aceitação e usabilidade.

Entretanto, com o tempo novas técnicas e filosofias de programação surgiram, proporcionando grande evolução, não só na forma de programar, mas também no resultado final para o usuário. Antigamente era comum nas grandes empresas poucos programadores desenvolverem grandes e robustas aplicações sozinhos, enquanto hoje em dia, nas principais aplicações empresariais, é comum (e aconselhável) a utilização de grandes equipes as quais são especializadas na arquitetura, no desenvolvimento, no marketing, etc.

Com isso, o programador foi induzido à filosofia de desenvolver com melhor qualidade, ainda que este não fosse o principal objetivo dele. O paradigma da Orientação a Objetos (surgida apenas na versão 3 do PHP) é um excelente exemplo desta “evolução obrigatória” na qualidade do software desenvolvido.

Nota: Neste artigo será criada uma aplicação utilizando MVC com base no framework Yii em sua versão 1.1. Logo, conceitos como ActiveRecord e banco de dados relacionais serão requisitos para o entendimento deste texto. Alguns passos importantes, como por exemplo a criação automática de Modelos e Controladores, além da estrutura de diretórios criada pelo Yii podem ser encontradas no site da DevMedia.
Confira todos os cursos online de PHP da DevMedia e se aprenda ainda mais sobre uma das linguagens web mais populares do mundo.

A arquitetura MVC

A arquitetura MVC consiste na separação lógica das principais camadas da aplicação, desde a camada mais abstrata até a mais concreta. Esta proposta de separar as camadas parece muito fácil em sua teoria, entretanto, colocá-la em prática não é tão simples assim.

Sendo assim, os criadores do MVC padronizaram sua divisão basicamente em três camadas: modelo (model), visualização (view) e controlador (controller):

  • Camada de Modelo (Model): Este é o local onde ficam todas as regras de negócio, ou seja, onde é feita a abstração dos dados. Aqui é definido a forma e as restrições dos dados os quais estarão disponíveis na aplicação. O modelo interage diretamente com as views e os controladores (camadas de visualização e controle, respectivamente). O modelo comunica a estes outros dois componentes quaisquer alterações feitas, com isso os controladores manipulam os dados e as visualizações renderizam estes dados para o usuário final.
  • Camada de Controle (Controller): A camada de controle é responsável por manipular os dados vindos do modelo (model), além de enviá-los, possuindo alterações ou não de volta à camada de visualizações (view).
    Uma vez recebido o dado a ser manipulado, o controlador executa ações(actions) com estes dados. Na prática estas ações (actions) são métodos que, depois de executada sua função, irão chamar uma view passando os dados manipulados pelo controlador.
  • Camada de Visualização (View): Esta é a camada responsável por exibir os dados para o usuário final, ela possui comunicação direta com o controlador. As visualizações não possuem nenhuma ligação direta com a camada de Modelos.

MVC no PHP

Diferentemente de outras linguagens, como o C# através da plataforma .NET, o PHP possui algumas “dificuldades” para se adaptar ao MVC. “Dificuldades” pois, comparado às aplicações em C# que só rodam utilizando o framework .NET, o PHP não possui nenhum framework base para rodar suas aplicações. Além disso, há o fato de o PHP ser interpretado, e não compilado, como nas linguagens que utilizam o framework .NET.

Por isso, para se adequar ao MVC no PHP, é necessário criar suas estruturas de diretórios manualmente, além de criar adaptações para haver a interação entre as três principais camadas do MVC.

Diante da teoria vista anteriormente, vamos criar uma aplicação para colocar em prática os conceitos já vistos.

Nota: O Yii, framework utilizado como base neste artigo, utiliza o conceito de ActiveRecord para criar suas aplicações. Portanto, é indicada a leitura do artigo “Yii: Aplicando ActiveRecord na sua aplicação” antes de avançar neste artigo.

Conceitos na prática

Como em todos os outros artigos escritos por mim, o cenário desta aplicação será o portal fictício de notícias PortalNews, onde nele é possível cadastrar notícias, classificando-as em suas respectivas categorias.

Estrutura de diretórios

Confira na Figura 1 a ilustração com a estrutura em árvore dos diretórios da aplicação “PortalNews” e a seguir uma breve explicação sobre ela.

Estrutura de pastas PortalNews
Figura 1. Estrutura de pastas “PortalNews”
  • Pasta Assets: Esta é a pasta responsável por armazenar todos os recursos adicionais que a aplicação necessita. Aqui são guardados arquivos JavaScript (como jQuery), arquivos de folhas de estilo CSS para a estilização de Widgets do framework, além de algumas imagens que serão utilizadas no seu site.
  • Pasta CSS: Aqui ficarão guardadas todos os arquivos folhas de estilo do portal.
  • Pasta Images: Diretório onde se localizam todas as imagens utilizadas na aplicação.
  • Pasta Protected: Pasta responsável por configurar todo o lado servidor da aplicação.
    Nota: Em outra ocasião foi explicada a estrutura do diretório Protected pasta a pasta. Caso não esteja bem entendido, faz-se necessário uma breve passagem na sessão “Entendendo a Estrutura de Arquivos” do artigo PHP Yii Framework.
  • Pasta Themes: Arquivos que posteriormente serão utilizados como template da aplicação.

Criação das tabelas

Seguem nas Listagens 1, 2 e 3 as consultas SQL para criação das tabelas Categoria, Administrador e Noticia, respectivamente.


CREATE TABLE IF NOT EXISTS `Categoria` ( 
`id` int(11) NOT NULL, 
  `nome` varchar(200) NOT NULL 
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ; 
 
-- 
-- Dumping data for table `Categoria` 
-- 
 
INSERT INTO `Categoria` (`id`, `nome`) VALUES 
(1, 'Categoria 1'), 
(2, 'Categoria 2'), 
(3, 'Categoria 3'), 
(4, 'Categoria 4'); 
 
ALTER TABLE `Categoria` 
 ADD PRIMARY KEY (`id`); 
 
ALTER TABLE `Categoria` 
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5;
Listagem 1. Tabela Categoria

CREATE TABLE IF NOT EXISTS `Administrador` ( 
`id` int(11) NOT NULL, 
  `nivel` int(11) NOT NULL, 
  `nome` varchar(300) NOT NULL, 
  `email` varchar(300) NOT NULL, 
  `usuario` varchar(300) NOT NULL, 
  `senha` varchar(300) NOT NULL 
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ; 
 
 
INSERT INTO `Administrador` (`id`, `nivel`, `nome`, `email`, `usuario`, `senha`) VALUES 
(1, 1, 'teste', 'teste', 'teste', 'teste'), 
(2, 0, 'teste 2', 'teste 2', 'teste2', 'teste 2'), 
(3, 0, 'teste 3', 'teste 3', 'teste3', 'teste 3'); 
 
ALTER TABLE `Administrador` 
 ADD PRIMARY KEY (`id`); 
 
ALTER TABLE `Administrador` 
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=4;
Listagem 2. Tabela Administrador

CREATE TABLE IF NOT EXISTS `Noticia` ( 
`id` int(11) NOT NULL, 
  `idAutor` int(11) NOT NULL, 
  `idCategoria` int(11) NOT NULL, 
  `data` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
  `titulo` varchar(400) NOT NULL, 
  `conteudo` text NOT NULL 
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ; 
 
INSERT INTO `Noticia` (`id`, `idAutor`, `idCategoria`, `data`, `titulo`, `conteudo`) VALUES 
(1, 2, 3, '2014-08-02 16:34:18', 'Titulo da Noticia 1', 'Conteudo da noticia 1'), 
(2, 2, 2, '2014-08-02 16:33:51', 'Título da Noticia 2', 'Conteúdo da noticia 2'), 
(3, 1, 4, '2014-08-02 16:33:51', 'Título da Noticia 3', 'Conteudo da noticia 3'); 
 
ALTER TABLE `Noticia` 
 ADD PRIMARY KEY (`id`), ADD KEY `idAutor` (`idAutor`,`idCategoria`), 
 ADD KEY `idCategoria` (`idCategoria`); 
 
ALTER TABLE `Noticia` 
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=4; 
 
ALTER TABLE `Noticia` 
ADD CONSTRAINT `fk_Noticia_idAutor_Administrador_id` FOREIGN KEY (`idAutor`) 
REFERENCES `Administrador` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 
ADD CONSTRAINT `fk_Noticia_idCategoria_Categoria_id` FOREIGN KEY (`idCategoria`) 
REFERENCES `Categoria` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
Listagem 3. Noticia

Entidade Noticia

Nesta parte do artigo será projetado o modelo da entidade Noticia no PortalNews. Lembrando que, como dito anteriormente, no caso do Yii Framework, é utilizado o padrão ActiveRecord para realizar a persistência dos dados. Aqui é muito importante lembrar que a geração do modelo ocorre a partir da estrutura física já criada (banco de dados).

Antes de criar o model Noticia é preciso entender como esta entidade é formada, ou seja, quais são seus atributos, seus relacionamentos, etc. Seguindo o padrão MER (modelo entidade relacionamento), veja na Figura 2 a ilustração do modelo lógico.

Modelo lógico da entidade Notícia
Figura 2. Modelo lógico da entidade Notícia

Diante do modelo lógico, devemos seguir os passos abaixo para execução das camadas:

  1. Model Noticia: Diante do modelo criado acima e das tabelas no banco de dados já criadas, acesse localhost/SEU-DIRETORIO/index.php/gii e logue em sua conta.
    Navegue até “model generator” e gere o modelo automaticamente.
  2. Controller Noticia: De forma analoga a criação do model, acesse localhost/SEU-DIRETORIO/index.php/gii e logue em sua conta.
    Navegue até “controller generator” e gere o controlador automaticamente.

Parar gerar CRUD Noticia é necessário acessar localhost/SEU-DIRETORIO/index.php/gii, e logar-se. Em seguida, navegue até “Crud Generator” e crie o CRUD automaticamente.

Para a análise do código gerado, diferentemente de outros artigos, aqui será analisado o CRUD já criado, com o objetivo de exemplificar na prática como os conceitos da arquitetura MVC são aplicados em um projeto baseado no framework Yii. A entidade a ser analisada será Noticia.

Para o modelo Noticia X Entidade Noticia, confira na Listagem 4 o código fonte gerado. Para facilitar a visualização do mesmo foram omitidos os métodos search() e model(), pois eles não possuem importância neste momento.


<?php

/**
* This is the model class for table "Noticia".
*
* The followings are the available columns in table 'Noticia':
* @property integer $id
* @property integer $idAutor
* @property integer $idCategoria
* @property string $data
* @property string $titulo
* @property string $conteudo
*
* The followings are the available model relations:
* @property Categoria $idCategoria0
* @property Administrador $idAutor0
*/
class Noticia extends CActiveRecord
{
/**
 * @return string the associated database table name
 */
public function tableName()
{
            return 'Noticia';
}

/**
 * @return array validation rules for model attributes.
 */
public function rules()
{
  // NOTE: you should only define rules for those attributes that
  // will receive user inputs.
  return array(
     array('idAutor, idCategoria, data, titulo, conteudo', 'required'),
     array('idAutor, idCategoria', 'numerical', 'integerOnly'=>true),
     array('titulo', 'length', 'max'=>400),
     // The following rule is used by search().
     // @todo Please remove those attributes that should not be searched.
     array('id, idAutor, idCategoria, data, titulo, conteudo', 'safe', 'on'=>'search'),
   );
}

/**
 * @return array relational rules.
 */
public function relations()
{
   // NOTE: you may need to adjust the relation name and the related
   // class name for the relations automatically generated below.
   return array(
    'idCategoria0' => array(self::BELONGS_TO, 'Categoria', 'idCategoria'),
    'idAutor0' => array(self::BELONGS_TO, 'Administrador', 'idAutor'),
   );
}

/**
 * @return array customized attribute labels (name=>label)
 */
public function attributeLabels()
{
   return array(
    'id' => 'ID',
    'idAutor' => 'Id Autor',
    'idCategoria' => 'Id Categoria',
    'data' => 'Data',
    'titulo' => 'Titulo',
    'conteudo' => 'Conteudo',
  );
}
Listagem 4. Modelo Noticia

Nas primeiras linhas do código fonte do modelo Noticia é possível visualizar, em linhas comentadas, todas as propriedades (atributos) de uma “instância de Noticia”. Note que estes atributos são os mesmos definidos no modelo lógico ao qual esta entidade se baseia (caso necessário volte até a Figura 2).

Como é sabido, o ActiveRecord atua sempre baseado em tabelas armazenadas no banco de dados, ou seja, é necessário que haja o mapeamento das tabelas em entidades. O método tableName() é o responsável por retornar o nome da tabela ao qual se refere o código atual.

O método rules() é um dos principais métodos desta classe, pois é ele que valida os atributos da entidade (classe). Para o leitor mais familiarizado com Java/C#, esta seria uma forma de validação equivalente às DataAnnotations.

Já o método relations() tem como objetivo indicar para as classes que se “comunicarão” com Noticia com quem e quais são os relacionamentos ela possui. Lembre-se que estes relacionamentos são sempre mapeados junto ao banco de dados.

O método attributeLabels() é responsável por atribuir Alias (apelido) aos atributos da classe. No exemplo da Listagem 4 nota-se o fato do campo data ter o seu alias definido como Data.

Agora vamos conferir na Listagem 5 o código do controlador Controlador Noticia X Ações da Entidade Noticia. Os métodos filters(), accessRules() e performAjaxValidation() foram omitidos por não possuírem importância neste momento.


<?php
class NoticiaController extends Controller
{
public function actionView($id)
{
$this->render('view',array(
'model'=>$this->loadModel($id),
));
}

public function actionCreate()
{
$model=new Noticia;
$categorias = Categoria::model()->findAll();
$autores = Administrador::model()->findAll();
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);
// 
if(isset($_POST['Noticia']))
{
$model->attributes=$_POST['Noticia'];
if($model->save())
$this->redirect(array('view','id'=>$model->id));
}

$this->render('create',array(
'model'=>$model,
'categorias'=>$categorias,
'autores'=>$autores,
));
}

public function actionUpdate($id)
{
$model=$this->loadModel($id);

// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($model);

if(isset($_POST['Noticia']))
{
$model->attributes=$_POST['Noticia'];
if($model->save())
$this->redirect(array('view','id'=>$model->id));
}

$this->render('update',array(
'model'=>$model,
));
}

public function actionDelete($id)
{
$this->loadModel($id)->delete();
// if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
if(!isset($_GET['ajax']))
$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));
}

public function actionIndex()
{
$dataProvider=new CActiveDataProvider('Noticia');
$this->render('index',array(
  'dataProvider'=>$dataProvider,
));
}

public function actionAdmin()
{
 $model=new Noticia('search');
 $model->unsetAttributes();  // clear any default values
 if(isset($_GET['Noticia']))
  $model->attributes=$_GET['Noticia'];
  $this->render('admin',array(
   'model'=>$model,
 ));
}
public function loadModel($id)
{
   $model=Noticia::model()->findByPk($id);
   if($model===null)
    throw new CHttpException(404,'The requested page does not exist.');
    return $model;
}
}
Listagem 5. Código do controlador Noticia

Vamos as explicações de cada método e ação:

  • actionView(): Ação responsável por chamar a view correspondente ao ID passado por parâmetro.
  • actionCreate(): Ação cujo papel é persistir os dados advindos do formulário no banco de dados. Esta ação, antes de persistir os dados no banco, chama a view com o formulário a ser preenchido pelo usuário.
  • actionUpdate(): Ação responsável por atualizar um registro na tabela, o registro a ser alterado é definido na passagem de parâmetro(ID) que esta ação recebe. Assim como a actionCreate(), esta ação chama a view com o formulário de atualização do registro o qual o usuário irá preencher.
  • actionDelete(): Ação que apaga um registro da tabela no banco de dados. O registro a ser deletado é definido no parâmetro ID que a ação recebe.
  • actionIndex(): Ação que lista todos os registros na tabela. Registros estes que serão visualizados na view chamada posteriormente.
  • actionAdmin(): Ação a qual administra todos os registros da tabela.
  • loadModel(): Método que retorna a instância de um objeto (registro na tabela). Mais uma vez este registro é definido pelo parâmetro ID recebido por este método.

A seguir, na Listagem 6, segue o código fonte da Visualização dos Dados (view) _view a qual irá listar todos os registros da tabela.


<?php
/* @var $this NoticiaController */
/* @var $data Noticia */
?>

<div class="view">

  <b><?php echo CHtml::encode($data->getAttributeLabel('id')); ?>:</b>
  <?php echo CHtml::link(CHtml::encode($data->id), array('view', 'id'=>$data->id)); ?>
  <br />

  <b><?php echo CHtml::encode($data->getAttributeLabel('idAutor')); ?>:</b>
  <?php echo CHtml::encode($data->idAutor); ?>
  <br />

  <b><?php echo CHtml::encode($data->getAttributeLabel('idCategoria')); ?>:</b>
  <?php echo CHtml::encode($data->idCategoria); ?>
  <br />

  <b><?php echo CHtml::encode($data->getAttributeLabel('data')); ?>:</b>
  <?php echo CHtml::encode($data->data); ?>
  <br />

  <b><?php echo CHtml::encode($data->getAttributeLabel('titulo')); ?>:</b>
  <?php echo CHtml::encode($data->titulo); ?>
  <br />

  <b><?php echo CHtml::encode($data->getAttributeLabel('conteudo')); ?>:</b>
  <?php echo CHtml::encode($data->conteudo); ?>
  <br />


</div>
Listagem 6. View _view Noticia

Nas primeiras linhas do código gerado pelo Yii é indicado as variáveis $data e $this. Note que estas variáveis são aquelas vindas dos controladores. Depois, estes dados são exibidos dentro de tags HTML para o usuário final.

Há muito mais sobre MVC com PHP a ser explorado. O objetivo desse artigo foi dar uma prévia de como é simples trabalhar com MVC e como o Yii Framework pode nos ajudar nessa tarefa.