Criando uma aplicação MVC com Zend Framework

Por Fernando Chucre
02/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.

Floxo ZendFramework MVC
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.
Lista de arquivos da aplicação

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:

Modelo de dados da aplicação

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

  1. 3 Responses to “Criando uma aplicação MVC com Zend Framework”

  2. 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

  3. 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

  1. 1 Trackback(s)

  2. Mai 5, 2008: Criando rotas MVC no Zend Framework (PHP) | Horizontes Digitais

You must be logged in to post a comment.

Creative Commons License
Esta obra está licenciada sob uma Licença Creative Commons.