Criando uma aplicação MVC com Zend Framework

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

You can leave a response, or trackback from your own site.

4 Responses to “Criando uma aplicação MVC com Zend Framework”

  1. [...] meu ultimo artigo eu mostrei como criar uma aplicação MVC com o Zend Framework. No artigo eu mostrei como [...]

  2. felix disse:

    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 ;)

  3. Fernando Chucre disse:

    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

  4. sidneyr disse:

    Muito boa as dicas!
    Queria umas dicas suas, se possível.
    Estou começando um projeto de monografia (Ciências da Computação), que terá continuidade por outras pessoas nos próximos semestres. Você poderia me recomendar algumas coisas tipo:
    Eclipse ou Zend?
    Symfony, Zend ou Cake?
    Smarty ou Zend_View?
    Jquery ou Prototype?
    Mysql ou Postgres?

    Sei que são coisas que vão muito da preferência de cada um, mas tenho receio de escolher algo meio limitado que mais pra frente atrapalhe no desenvolvimento, pois o projeto poderá ser grande.
    Dando preferência para software livre!
    Obrigado!

Leave a Reply

You must be logged in to post a comment.

Subscribe to RSS Feed Me siga no Twitter!