Criando uma aplicação MVC com Zend Framework
Por Fernando Chucre02/05/2008 – 02:08
Criando uma aplicação MVC com Zend Framework
Seguindo minha serie de artigos que mostraram como criar uma aplicação PHP usando REST e MVC no Zend Framework, hoje vou descrever como criar uma aplicação tradicional com MVC. Todos os conceitos necessários serão abordados, passando pelo mapeamento de objetos-relacional a construção do controle e view. Como sempre uma pequena introdução teórica.
O que é MVC?
Segundo a Wikipedia MVC é “um padrão de arquitetura de software. Com o aumento da complexidade das aplicações desenvolvidas torna-se fundamental a separação entre os dados (Model) e o layout (View). Desta forma, alterações feitas no layout não afectam a manipulação de dados, e estes poderão ser reorganizados sem alterar o layout. “
Por tanto as aplicações que usam MVC tem 3 camadas distintas, Modelo, Visão e Controle. O modelo é sua regra de negocio e persistência. Nessa camada você pode fazer como você quiser, não importa como, basta que você isole essas atividades do controle e visão. A visão é a forma que você vai apresentar ao usuário final, no caso de aplicações WEB será o HTML, mas pode ser outra coisa qualquer. O controle é quem vai organizar e chamar as outras duas camadas, dependendo da solicitação do usuário.
No caso da WEB o controle SEMPRE será a primeira camada a ser instanciada, pois a ação inicial sempre é escolhe de um usuário um endereço para visitar, assim essa chamada inicial é tratada por um controle. Esse controle ira determinar, dependendo da solicitação qual ou quais classes de modelo deve instanciar e qual será a visão utilizada para apresentar os dados ao usuário/sistema que solicitou a ação. A escolha da visão geralmente é chamada de renderizar.
O MVC não determina como deve ser implementado cada uma das camadas, mas somente como elas se comunicação. Mas é sempre bom usar um padrão de mapeamento objeto-relacional. O Zend Framework prove um pacote para essa atividade chamado Zend_Db.
Nossa aplicação de exemplo é composto de 2 tabelas, uma para armazenar as contas do usuário, outra para armazenar as lista de Feeds. A seguir serão listados as ações que serão implementadas.
- Criar usuário
- Editar usuário
- Adicionar Feed
- Remover Feed
No nosso exemplo precisaremos apenas de dois controles. Um chamado de ‘Index’, que ira receber as requisições padrões e outra chamada User.
Agora que temos uma visão teórica do MVC e uma ideia de como o sistema será, podemos nos aprofundar no Zend Framework. Para iniciar ou mostrar as principais classes que o ZF oferece para nos auxiliar na tarefa.
- Zend_Controller_Request_Http – Classe de manipulação da requisição
- Zend_Controller_Response_Http – Classe de manipulação da resposta
- Zend_Controller_Action – Classe base de todas as classes de controle da aplicação
- Zend_Controller_Front – Classe que implementa o FrontController e chama o despacho da requisição.
Para ter uma visão mais geral do processo segue o fluxo de uma requisição comum e onde iremos interferir no fluxo.

Toda a parte que esta dentro do retângulo em vermelho é feito pelo ZF. A parte em azul será onde colocaremos nosso controle e no retângulo verde é onde esta o objeto Response, que por padrão chamara a classe Zend_View. Essa classe chamara de um arquivo que este será nossa visão.
Como dito anteriormente, o MVC não exige uma implementação em padrões do modelo, assim a estrutura do ZF na parte dos Controllers é para encaminhar a requisição ao seu controle e a ação correta. Vamos entender como funciona isso, a seguir você verá uma estrutura padrão de uma aplicação desenhada para o ZF.
A pasta application é onde colocaremos nossos módulos do sistema. Nesse caso somente o ‘default’. Abaixo desse modulo será usado 3 pastas, isolando cada uma das camadas. A que merece uma atenção maior é a camada da visão, que neste caso separa os arquivos pelo mesmo nome do controle, e sua extensão por padrão é phtml.
A pasta que será publicada é a appmvc/html/. Nela há dois arquivos. Um que ira prepara o ambiente para a chamada dos controles, o index.php, e outra que ira redirecionar todas as requisições para o index.php, o ‘.htaccess’.
Uma olhada nesses arquivos.
AppMVC/.htaccess
RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
AppMVC/index.php
<?php/*
* Inclui a pasta AppMVC/library e AppMVC/application/default/models/ no include_path
*/
set_include_path(’..’ . PATH_SEPARATOR . ‘../library’ . PATH_SEPARATOR . ‘../application/default/models/’ . PATH_SEPARATOR . get_include_path());/*
* Aqui inclui varias classe do ZF para uso. Coloque o ZF na pasta AppMVC/library
*/require_once ‘Zend/Controller/Front.php’; /* Classe reponsavel pelo FrontController */
require_once ‘Zend/Db/Table.php’; /* Classe para setar instancia do banco de dados para todos os Models */
require_once ‘Zend/Db.php’; /* Classe para instanciar o banco do dados. */
require_once ‘Zend/Config.php’; /* Classe para ler as configurações do banco. *//*
* Dados de acesso ao seu servidor de dados
*
* adapter => tipo de conexão (MySQL, PostgreSQL, Oracle, etc).
*
*/$config = new Zend_Config(
array(
‘database’ => array(
‘adapter’ => ‘Mysqli’,
‘params’ => array(
‘dbname’ => ‘appmvc’,
‘username’ => ‘userDB’,
‘password’ => ‘XXX’,
‘host’ => ‘localhost’
)
)
)
);/*
* Aqui seta a instancia default para todas as classes de acesso a dados
*/Zend_Db_Table_Abstract::setDefaultAdapter(Zend_Db::factory($config->database));
/**
* Instancia o FrontController, configura o diretório padrão
*/
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory(’../application/default/controllers’);
$controller->throwExceptions(false); // should be turned on in development time// Executa do despacho da operação solicitada.
$controller->dispatch();
?>
Na camada de modelo temos 2 arquivos que vão mapear os objetos-relacional, em geral usamos um arquivo para cada tabela no banco. Antes de mostrar o conteúdo dos arquivos uma rápida olhada pelas principais classes que vão nos auxiliar nessa tarefa de mapeamento.
- Zend_Db_Table_Abstract
- fetchAll()
- find()
- delete()
- insert()
- update()
- fetchRow()
- getAdapter()
- Zend_Db_Table_Rowset_Abstract
- current()
- count()
- getTable()
- key()
- next()
- toArray()
- Zend_Db_Table_Row_Abstract
- delete()
- save()
- toArray()
Nosso modelo de dados é o seguinte:
A representação deste modelo na nossa aplicação será em duas classe: User e Feed.
AppMVC/application/default/models/User.php
<?php
require_once ‘Zend/Db/Table/Abstract.php’;
class User extends Zend_Db_Table_Abstract {
protected $_name = ‘User’;
protected $_primary = ‘idUser’;protected $_dependentTables = array(’Feed’);
}
?>
Bom neste arquivo criamos a classe User. Ela estende da classe Zend_Db_Table_Abstract e ela define 3 propriedades como preteridas, que são:
- $_name: nome da tabela no banco de dados.
- $_primary: colunas da chave primaria, pode ser um string ou um array de strings para tabelas com chaves compostas.
- $_dependentTables: um array de strings para dizer quais outras classes tem dependência com esta, para relacionamento.
Seguimos com o arquivo que mapeara a tabela Feed.
AppMVC/application/default/models/Feed.php
<?php
require_once ‘Zend/Db/Table/Abstract.php’;class Feed extends Zend_Db_Table_Abstract {
protected $_name = ‘Feed’;
protected $_primary = ‘idFeed’;protected $_referenceMap = array(
‘User’ => array(
‘columns’ => ‘idUser’,
‘refTableClass’ => ‘User’,
‘refColumns’ => ‘idUser’
));}
?>
Neste vemos a propriedade $_referenceMap, que é efetivamente onde criamos os relacionamentos entre as classes. A propriedade é uma matriz onde temos no primeiro nível o nome da regra, no nosso caso ‘User’. Ele sendo um array que tem a propriedade ‘columns’, pode ser uma string ou um array de string, que vai indicar os campos na tabela ‘Feed’ para relacionamento. A propriedade ‘refTableClass’ indica a CLASSE de referencia e não o nome da tabela e por fim a propriedade ‘refColumns’ indica os campos na tabela ‘User’.
Antes de mostrar o conteúdo dos controles vamos entender como o ZF funciona no despacho das requisições, como uma solicitação de uma URL chega ao controle da aplicação.
O ZF trabalha com 3 níveis na parte do controle:
- Module: O padrão é o default, por isso nossa aplicação tem essa pasta. O modulo é um conjunto de classes de controle.
- Controller: é a classe de controle, que manterá os métodos de acesse publico. O ZF determina que uma classe de controle seja nomecontrole+Controller, por exemplo, UserController.
- Action: método de chamada publica. Assim como o controle a ação deve ser seguida de Action.
Quando o despacho é realizado o ZF tenta identificar qual é o controle solicitado e a ação. Essa identificação é realizada usando o path solicitado. A primeira parte do path é entendido como o modulo, se a aplicação não setou nenhum modulo ou somente 1, o ZF considera que a primeira parte do path é o controle. Depois do controle esta a ação, se a ação ou o controle não são informados o ZF os nomeia como ‘index’. Veja os exemplos.
- http://appmvc/
- modulo: default
- controle: index
- ação: index
- http://appmvc/user
- modulo: default
- controle: user
- ação: index
- http://appmvc/user/userinfo/
- modulo: default
- controle: user
- ação: userinfo
- http://appmvc/user/userinfo/id/1
- modulo: default
- controle: user
- ação: userinfo
- parâmetros:
- id = 1
No ultimo exemplo foi adicionado ao path ‘/id/1′, isso foi colocado para mostrar que se desejado pode-se passar parâmetros ao controle via path. Assim a o parâmetro ‘id’ sera preenchido com o valor 1, isso tem o mesmo efeito que passar a valor via variável via chamada get (’?id=1′).
Nota: Quando um controle chamado não existe ou quando uma Exception não é tratada o ZF chama o controle Error e a ação Error.
Assim segue o conteúdo dos arquivos de controle.
AppMVC/application/default/controllers/ErrorController.php
<?php
require_once ‘Zend/Controller/Action.php’;
class ErrorController extends Zend_Controller_Action
{/**
* This action handles
* - Application errors
* - Errors in the controller chain arising from missing
* controller classes and/or action methods
*/
public function errorAction()
{
$errors = $this->_getParam(’error_handler’);
switch ($errors->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
/*404 error - controller or action not found */
$this->getResponse()->setRawHeader(’HTTP/1.1 404 Not Found’);
$this->view->title = ‘HTTP/1.1 404 Not Found’;
break;
default:
/* application error; display error page, but don’t change */
/* status code */
$this->view->title = ‘Application Error’;
break;
}$this->view->message = $errors->exception;
}
}
?>AppMVC/application/default/controllers/IndexController.php
<?php
require_once ‘Zend/Controller/Action.php’;class IndexController extends Zend_Controller_Action
{
/**
* The default action - show the home page
*/
public function indexAction()
{
$this->_forward(’index’,'user’);
}
}
?>
AppMVC/application/default/controllers/IndexController.php
<?phprequire_once ‘Zend/Controller/Action.php’;
require_once ‘application/default/models/User.php’;
require_once ‘application/default/models/Feed.php’;class UserController extends Zend_Controller_Action {
/*
* Método padrão de acesso
*/
public function indexAction()
{
/*lista todos os usuários*//*instancia da classe User (Modelo)*/
$users = new User();/*lista de todos os usuários*/
$userList = $users->fetchAll();/*pega a instancia da classe view */
$view = $this->initView();/*set a propriedade usersList da classe view com o array de todos os usuários */
$view->usersList = $userList->toArray();/* renderiza a view home.phtml */
$this->render(’home’);
}public function adduserAction()
{
/*inclui novo usuário e lista todos os usuários*/
$users = new User();
$view = $this->initView();/* pega as variáveis passadas na requisição, nome e about*/
$name = $this->getRequest()->getParam(’name’);
$about = $this->getRequest()->getParam(’about’);/*verifica se as variáveis foram passadas*/
if (!$name) $error = true;
if (!$about) $error = true;if ($error)
{
$view->errorMsg = ‘Faltaram dados.’;
}
else
{
$data = array(
‘name’=>$name,
‘about’=>$about
);
$users->insert($data);
}/* aqui depois de fazer as alterações repassa para a ação que mostrara as informações */
$this->_forward(’index’);
}public function infouserAction()
{
/*mostra informações de um usuário incluindo suas feed*/
$users = new User();
$view = $this->initView();
$id = $this->getRequest()->getParam(’id’);if (!$id)
{
$view->errorMsg = ‘Faltam dados’;
$this->render(’home’);
return;
}
else
{
/* pega a registro solicitado */
$user = $users->find($id)->current();
if ($user)
{
$feeds = $user->findDependentRowset(’Feed’);
$view->userInfo = $user->toArray();
$view->userFeeds = $feeds->toArray();
$this->render(’info-user’);
}
else
{
$view->errorMsg = ‘Usuario não existe’;
$this->render(’home’);
return;
}
}
}public function edituserAction()
{
$view = $this->initView();
$users = new User();$id = $this->getRequest()->getParam(’id’);
$name = $this->getRequest()->getParam(’name’);
$about = $this->getRequest()->getParam(’about’);if (!$id)
{
$view->errorMsg = ‘Usuario não informado’;
$this->_forward(’index’);
return;
}
else
{
$user = $users->find($id)->current();
if ($user)
{
if (!$name) $error = true;
if (!$about) $error = true;if ($error)
{
$view->errorMsg = ‘Informações faltando’;
}
else
{
$user->name = $name;
$user->about = $about;
$user->save();
}
$this->_forward(’infouser’);
return;
}
else
{
$view->errorMsg = ‘Usuario não encontrado’;
$this->_forward(’index’);
return;
}
}
}
public function addfeedAction()
{
/*adiciona uma feed a lista do usuário*/
$view = $this->initView();
$feeds = new Feed();$id = $this->getRequest()->getParam(’id’);
$description = $this->getRequest()->getParam(’description’);
$url = $this->getRequest()->getParam(’url’);if (!$id)
{
$view->errorMsg = ‘Usuario não encontrado’;
$this->_forward(’index’);
return;
}if (!$description) $error = true;
if (!$url) $error = true;if ($error)
{
$view->errorMsg = ‘Error add Feed: faltaram dados’;
}
else
{
$data = array(
‘idUser’=>$id,
‘url’=>$url,
‘description’=>$description
);
$feeds->insert($data);
}
$this->_forward(’infouser’);
}
public function delfeedAction()
{
/*delete uma feed da lista do usuário*/$view = $this->initView();
$feeds = new Feed();$idUser = $this->getRequest()->getParam(’id’);
$idFeed = $this->getRequest()->getParam(’idFeed’);if (!$idUser)
{
$view->errorMsg = ‘Usuario não encontrado’;
$this->_forward(’index’);
return;
}if (!$idFeed)
{
$view->errorMsg = ‘Feed não informada’;
}
else
{
$feed = $feeds->find($idFeed)->current();
if ($feed)
{
$feed->delete();
}
else
{
$view->errorMsg = ‘Feed não encontrada’;
}
}
$this->_forward(’infouser’);
}
}?>
Quando um ação não invoca o método $this->render() o ZF renderiza o arquivo como mesmo nome da ação. Em nossa aplicação usamos apenas 3 arquivos de visão, um para o controle de erro e outros dois para a o controle User.
AppMVC/application/default/views/scripts/error/error.phtml
<html>
<head>
<title>Error Page</title>
</head>
<body>
<h1><? echo $this->title ?></h1>
<? if ($this->message): ?>
<p>The following error occurred:</p>
<? echo $this->message ?>
<? endif; ?>
</body>
</html>
Nessa visão é utilizada as propriedades title e message. Essas foram populadas no método ErrorAction.
AppMVC/application/default/views/scripts/user/home.phtml
<html>
<head>
<title>appMVC Home</title>
</head>
<body>
<? if ($this->errorMsg): ?>
<h2><?=$this->errorMsg ?></h2>
<? endif; ?>
Inclua novo usuário<br>
<form action=”/user/adduser”>
nome: <input type=’text’ name=’name’ ><br>
sobre:
<textarea rows=”4″ cols=”15″ name=’about’></textarea><br>
<input type=”submit” value=”Criar”>
</form><hr>
Usuários cadastrados<br><table border=1>
<tbody>
<? foreach($this->usersList as $user) :?>
<tr>
<td><?=$user[’name’]?></td>
<td><a href=’/user/infouser/id/<?=$user[’idUser’]?>’>Editar</a></td>
</tr>
<? endforeach; ?>
</tbody>
</table>
</body>
</html>
Este arquivo pode chamar duas ações,’/user/adduser’ para incluir um novo usuário, pelo form ou ver as informações de um usuário já criado através do link ‘/user/infouser/id/:idUser’.
AppMVC/application/default/views/scripts/user/info-user.phtml
<html>
<head>
<title>appMVC Home</title>
</head>
<body>
<a href=”/”>Home</a>
<hr>
<? if ($this->errorMsg): ?>
<h2><?=$this->errorMsg ?></h2>
<? endif; ?>
<form action=”/user/edituser/id/<?=$this->userInfo[’idUser’] ?>”>
nome: <input type=’text’ name=’name’ value=’<?=$this->userInfo[’name’]?>’><br>
sobre:
<textarea rows=”4″ cols=”15″ name=’about’><?=$this->userInfo[’about’]?></textarea><br>
<input type=”submit” value=”Salvar”>
</form>
<hr>
Adicionar Feed
<form action=”/user/addfeed/id/<?=$this->userInfo[’idUser’] ?>”>
Descrição: <input name=’description’ size=’30′ ><br>
URL: <input name=’url’ size=’60′ ><br>
<input type=”submit” value=’incluir’>
</form>
<hr>
Feeds<br>
<table border=1>
<? foreach ($this->userFeeds as $feed) :?>
<tr>
<td><?=$feed[’description’]?></td>
<td><?=$feed[’url’]?></td>
<td><a href=’/user/delfeed/id/<?=$this->userInfo[’idUser’] ?>/idFeed/<?=$feed[’idFeed’]?>’>excluir</a></td>
</tr>
<? endforeach; ?>
</table>
</body>
</html>
Nesta visão o usuário pode invocar 3 ações: edituser, addfeed e delfeed.
Acredito que agora o leitor poderá criar suas próprias aplicações usando o ZendFramework. Espero logo estar postando novos artigos com explicações mais técnicas. O codigo fonte da aplicação pode ser obtido aqui.
Abraços


3 Responses to “Criando uma aplicação MVC com Zend Framework”
cara bem legal, estou lendo bastante sobre o zend. Eu estava trabalhando com o symfony em uns projetos pessoais, mas achei o symfony muito intrusivo. Agora o que não me agrada em ambos eh como eles tratam a view, acostumei a usar o smarty, daih fico sentido falta dele, jah que tanto o symfony quanto o zend usam o esquema de “php scriplet template”
abraços
By felix on Mai 6, 2008
Cara, você pode usar o Smarty na View. Acho que vou até fazer um artigo para isso :D, o que ocorre é que o ZF permite vc usar classes customizadas.
Valeu
By Fernando Chucre on Mai 6, 2008