Criação de uma aplicação Zend Framework 2 com Zend Studio 13.5
Tutorial: Getting Started with Zend Framework 2
https://framework.zend.com/manual/2.4/en/user-guide/overview.html
1. Criar um Projeto Local
Zend Studio > File > New > Local Php Project
2. Executar a aplicação:
PHP Explorer > deployment.xml > Launch application
Finish
3. Adicionar o Módulo Album
Bt. dir. do rato em cima do nome do Projeto:
Albuns > New > Zend Framework Item >
Zend Module > Next
Erro:
Could not upload resource.
Could not create new file 'C:\Program Files
(x86)\Zend\ZendServer\data\apps\http\__default__\0\Albuns\1.0.0_90\module\Album\LICENSE.txt'
Zend Studio cria a seguinte estrutura de diretórios para o novo módulo:
/module
/Album
<- nome do Módulo
/config
/module.config.php
/src
/Album
<- nome do Módulo
/Controller
/AlbumController
<- nome do Controlador
/Form
/Model
/view
/album
<- nome do módulo
/album
<- nome do controlador
/index.phtml
<- vista
/autoload_classmap.php
/Module.php
Se tivéssemos adicionado um módulo com:
nome do Módulo:
Modulo
nome do controlador: Controlador
seria criada a seguinte estrutura:
Diretório src/Album – ficheiros PHP com classes pertencentes ao namespace Album.
Diretório view/album/album – scripts de vistas para o módulo.
Zend Framework 2 tem um gestor de módulos, ModuleManager, para carregar e configurar
cada módulo. Este gestor de módulos espera encontrar na raiz do módulo (module/Album) o
ficheiro Module.php com a classe Module (pertencente ao namespace Album).
ModuleManager carrega os módulos enumerados na secção “modules” do ficheiro de
configuração da aplicação config/application.config.php fornecido pela aplicação skeleton.
Verificar que este módulo foi acrescentado pelo Zend Studio:
<?php
return array(
// This should be an array of module namespaces used in the application. 'modules' => array(
'Application','Album', ),
. . .
Para o módulo Album ser reconhecido pelo ZF2 é necessário que esteja registado na secção
“modules” de config/application.config.php e que na raiz do módulo (module/Album) exista
uma classe Module.
Ficheiro Album/Module.php
<?php/**
* Zend Framework (http://framework.zend.com/) *
* @link http://github.com/zendframework/Album for the canonical source repository * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Album;
use Zend\ModuleManager\Feature\AutoloaderProviderInterface; use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;
class Module implements AutoloaderProviderInterface
{
public function getAutoloaderConfig() { return array( 'Zend\Loader\ClassMapAutoloader' => array( __DIR__ . '/autoload_classmap.php', ), 'Zend\Loader\StandardAutoloader' => array( 'namespaces' => array(
// if we're in a namespace deeper than one level we need to fix the \ in the path
__NAMESPACE__ => __DIR__ . '/src/' . str_replace('\\', '/' , __NAMESPACE__),
), ), ); }
public function getConfig() {
return include __DIR__ . '/config/module.config.php'; }
public function onBootstrap(MvcEvent $e) {
// You may not need to do this if you're doing it elsewhere in your
// application
$eventManager = $e->getApplication()->getEventManager(); $moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager); }
}
O ModuleManager executa automaticamente os métodos getAutoloaderConfig() e getConfig()
da classe Module.O método getConfig() carrega o ficheiro Album/config/module.config.php.
Ficheiro Album/config/module.config.php:
<?php return array( 'controllers' => array( 'invokables' => array( 'Album\Controller\Album' => 'Album\Controller\AlbumController', ), ), 'router' => array( 'routes' => array( 'album' => array( 'type' => 'Literal', 'options' => array(// Change this to something specific to your module 'route' => '/album',
'defaults' => array(
// Change this value to reflect the namespace in which // the controllers for your module are found
'__NAMESPACE__' => 'Album\Controller', 'controller' => 'Album', 'action' => 'index', ), ), 'may_terminate' => true, 'child_routes' => array(
// This route is a sane default when developing a module; // as you solidify the routes for your module, however, // you may want to remove it and replace it with more // specific routes. 'default' => array( 'type' => 'Segment', 'options' => array( 'route' => '/[:controller[/:action]]', 'constraints' => array( 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*', 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', ), 'defaults' => array( ), ), ), ), ), ), ), 'view_manager' => array( 'template_path_stack' => array( 'Album' => __DIR__ . '/../view', ),
), );
Zend Framework 2 tem um serviço, ServiceManager, que usa a informação contida neste
ficheiro module.config.php.
A secção ‘controllers’ lista todos os controladores fornecidos pelo módulo.
O nome completo da classe AlbumController é ‘Album/Controller/AlbumController’ e a chave
para referenciar esta classe é ‘Album/Controller/Album’ para ser um nome único em todos os
módulos.
A secção ‘view_manager’ contém o caminho para o diretório view que vai conter os scripts das
vistas do módulo Album.
Cada página da aplicação é designada por ação e as ações estão agrupadas em controladores
dentro de módulos. Cada controlador contém ações relacionadas.
A aplicação a construir vai ter 4 páginas sobre Albuns (agrupadas no controlador
AlbumController):
index – para listar álbuns
add – para adicionar um álbum
edit – para editar uma álbum
delete – para apagar um álbum
O Controlador é uma classe cujo nome tem de começar por letra maiúscula e tem o sufixo
Controller. Cada ação é um método público dentro da classe Controlador. O nome da ação
deve começar por letra minúscula.
namespace Album\Controller;
use Zend\Mvc\Controller\AbstractActionController; class AlbumController extends AbstractActionController
{
public function indexAction() {
return array(); }
public function addAction() {
return array(); }
public function editAction() {
return array(); }
public function deleteAction() {
return array(); }
Diretórios do Controlador e das Vistas:
module/Album/src/Album/Controller/AlbumController
module/Album/view/album/album/add.phtml
module/Album/view/album/album/delete.phtml
module/Album/view/album/album/edit.phtml
module/Album/view/album/album/index.phtml
Ficheiro add.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> add
Ficheiro delete.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> delete
Ficheiro edit.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> edit
Ficheiro index.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> index
Executar a aplicação.
Colocar o endereço
http://localhost/Albuns/album
mostra a página index
Colocar o endereço
http://localhost/Albuns/album/add
também mostra a página index
Colocar o endereço
http://localhost/Albuns/album/delete
também mostra a página index
Colocar o endereço
http://localhost/Albuns/album/edit
também mostra a página index
O mapeamento de um URL para uma ação particular é feito usando rotas que são definidas no
ficheiro module.config.php
Aleração do tipo da rota ‘album’ de ‘literal’ para ‘segment’
Ficheiro Album/config/module.config.php:
<?php return array( 'controllers' => array( 'invokables' => array( 'Album\Controller\Album' => 'Album\Controller\AlbumController', ), ), 'router' => array( 'routes' => array( 'album' => array( 'type' => 'segment', 'options' => array(// Change this to something specific to your module 'route' => '/album[/:action][/:id]', 'constraints' => array( 'action' => '[a-zA-Z][a-zA-Z0-9_-]*', 'id' => '[0-9]+', ), 'defaults' => array( 'controller' => 'Album\Controller\Album', 'action' => 'index', ), ), ), ), ), 'view_manager' => array( 'template_path_stack' => array( 'Album' => __DIR__ . '/../view', ),
), );
Um rota do tipo ‘segment’ permite especificar placeholders no padrão do URL (rota) que serão
mapeados em parâmetros.
Exemplo:
A rota /album[/:action][/:id] unificará com qualquer URL que comece por /album. O próximo
segmento será um nome de ação opcional e o segmento seguinte será mapeado num id
opcional.
Executar a aplicação
Colocar o endereço
http://localhost/Albuns/album
mostra a página index
Colocar o endereço
http://localhost/Albuns/album/add
mostra a página add
Colocar o endereço
http://localhost/Albuns/album/delete
mostra a página delete
Colocar o endereço
http://localhost/Albuns/album/edit
mostra a página edit
4. Criar a base de Dados
http://localhost/phpMyAdmin
New > Create database
Database name: zf2Albuns
Create
Criar um utilizador para esta base de dados:
Selecionar a base de dados criada
zf2albuns > Privileges >
Users having access to “zf2albuns” > Add user
User name:
zf2
Host:
Local ->
localhost
Password:
zf2
Re-type:
zf2
Grant all privileges on database “zf2albuns”
Go
Zf2albuns > Create table >
Name:
album
Number of columns:
3
Go
Name
Type
Length/Values
Null
Index
A_I
id
INT
10
PRIMARY
artist
VARCHAR
100
title
VARCHAR
100
Save
Selecionando a tabela album:
Insert
id
artist
title
1
The Military Wives
In My Dreams
Go
2
Adele
21
Go
3
Bruce Springsteen
Wrecking Ball (Deluxe)
Go
4
Lana Del Rey
Born To Die
Go
5. CRUD de albuns
Na pasta module/Album/src/Album/Model vamos criar as classes:
Album para representar um álbum,
AlbumTable para representar a tabela album da base de dados. Usa a classe
TableGateway (Zend\Db\TableGateway\TableGateway) para realizar CRUD na tabela
album.
Criação da classe Album
module/Album/src /Album > New > Folder
Folder name: Model
Model > New > Class
Class Name: Album
Finish
<?php namespace Model; class Album { public $id; public $artist; public $title;
public function exchangeArray($data) {
$this->id = (!empty($data['id'])) ? $data['id'] : null;
$this->artist = (!empty($data['artist'])) ? $data['artist'] : null; $this->title = (!empty($data['title'])) ? $data['title'] : null; }
}
Criação da classe AlbumTable
Model > New > Class
Class Name: AlbumTable
Finish
<?php namespace Model; use Zend\Db\TableGateway\TableGateway; class AlbumTable { protected $tableGateway;
public function __construct(TableGateway $tableGateway) { $this -> tableGateway = $tableGateway;
}
public function fetchAll() {
$resultSet = $this->tableGateway->select(); return $resultSet;
}
public function getAlbum($id) { $id = (int) $id;
$rowSet = $this->tableGateway->select(array('id' => $id)); $row -> $rowSet->current();
if (!$row) {
throw new \Exception("Could not find row $id"); }
return $row; }
public function saveAlbum(Album $album) { $data = array (
'artist' => $album -> artista, 'title' => $album -> title, );
$id = (int) $album->id; if ($id == 0) {
$this->tableGateway->insert($data); } else {
if $this->getAlbum($id)) {
$this->tableGateway->update($data, array('id' => $id)); } else {
throw new \Exception("Album id does not exist"); }
} }
public function deleteAlbum($id) {
$this->tableGateway->delete(array('id' => (int) $id)); }
}
A classe AlbumTable tem os seguintes métodos.
fetchAll() para retribuir todas as linhas da tabela como um ResultSet
getAlbum() para retribuir uma linha da tabela
saveAlbum() recebe um objeto Album; se foi criado em memória tem o Id = 0 e insere-o; se
este objeto Album veio da base de dados tem o ID ≠ 0 e atualiza-o.
deleteAlbum() para elininar uma linha da tabela
Criação de um objeto AlbumTable
No ficheiro Album\Module.php acrescentar o seguinte método getServiceConfig():
use Album\Model\Album; use Album\Model\AlbumTable; use Zend\Db\ResultSet\ResultSet; use Zend\Db\TableGateway\TableGateway; public function getServiceConfig() {
return array(
'factories' => array(
'Album\Model\AlbumTable' => function($sm) { $tableGateway = $sm->get('AlbumTableGateway'); $table = new AlbumTable($tableGateway);
return $table; },
'AlbumTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet(); $resultSetPrototype->setArrayObjectPrototype(new Album());
return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
}, ), ); }
Este método retorna um objeto AlbumTable passando ao seu construtor um objeto
TableGateway. Este objeto TableGateway é configurado para aceder à tabela ‘album’ e
também usa um objeto Album para criar um resultado para uma linha da tabela.
O objetivo do padrão TableGateway é encapsular a interação com uma tabela da base de dados.
A classe TableGateway contém todas as queries SQL necessárias para ler, inserir, atualizar ou
apagar linhas duma tabela, isto é, encapsula toda a lógica de acesso à fonte de dados.
Por outro lado o padrão Table Module (classe AlbumTable) realiza operações em memória sobre
as linhas de uma tabela obtidas pelo TableGateway. Normalmente uma classe Table Module
contém um objeto TableGateway.
Em ZF2 são nessários 3 passos para acesso a dados da entidade Album:
1. Criar a classe Album com o método exchangeArray
2. Configurar a classe TableGateway através do ServiceManager
3. Criar a classe AlbumTable (Table Module) que liga a classe TableGateway com a classe
Album
A classe Album representa um álbum, a classe TableGateway contém a lógica de acesso à
tabela da base de dados que contém registos de álbuns, e a classe AlbumTable (que usa a
classe TableGateway por composição) permite efetuar CRUD de objetos Album.
Em Zend a classe TableGateway estende a classe AbstractTableGateway.
A classe AbstractTableGateway implementa a interface TableGatewayInterface.
interface Zend\Db\TableGateway\TableGatewayInterface {
public function getTable();
public function select($where = null); public function insert($set);
public function update($set, $where = null); public function delete($where);
}
class TableGateway extends AbstractTableGateway {
public $lastInsertValue; public $table;
public $adapter;
public function __construct(
$table,
Adapter $adapter,
$features = null,
ResultSet $resultSetPrototype = null, Sql $sql = null)
/** Inherited from AbstractTableGateway */ . . .
}
Para criar um objeto TableGateway é necessário fornecer-lhe pelo menos o nome de uma tabela
e uma instância de um Adapter.
Configuração da ligação à base de dados
config/autoload/global.php:
<?php return array( 'db' => array( 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=zf2albuns;host=localhost;port=3306', 'driver_options' => array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ), ), 'service_manager' => array( 'factories' => array( 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory', ), ), );
config/autoload/local.php:
<?php return array( 'db' => array( 'username' => 'zf2', 'password' => 'zf2', ), );Criação do formulário para adicionar e editar álbuns
module/Album/src /Album > New > Folder
Folder name: Form
Form > New > Class
Class Name: AlbumForm
Finish
module/Album/src /Album/Form/AlbumForm.php:
<?phpnamespace Album\Form; use Zend\Form\Form;
class AlbumForm extends Form {
public function __construct($name = null) { // we want to ignore the name passed parent::__construct('album'); $this->add(array( 'name' => 'id', 'type' => 'Hidden', )); $this->add(array( 'name' => 'title', 'type' => 'Text', 'options' => array( 'label' => 'Title', ), )); $this->add(array( 'name' => 'artist', 'type' => 'Text', 'options' => array( 'label' => 'Artist', ), )); $this->add(array( 'name' => 'submit', 'type' => 'Submit', 'attributes' => array( 'value' => 'Go', 'id' => 'submitbutton', ), )); } }
Alteração da classe Album para adicionar um InputFilter para validação do formulário
module/Album/src /Album/Model/Album.php:
<?php namespace Album\Model; use Zend\InputFilter\InputFilter; use Zend\InputFilter\InputFilterAwareInterface; use Zend\InputFilter\InputFilterInterface;class Album implements InputFilterAwareInterface
{
public $id; public $artist; public $title;
protected $inputFilter; // New variable
public function exchangeArray($data) {
$this->id = (!empty($data['id'])) ? $data['id'] : null;
$this->artist = (!empty($data['artist'])) ? $data['artist'] : null; $this->title = (!empty($data['title'])) ? $data['title'] : null; }
public function getArrayCopy() {
return get_object_vars($this); }
public function setInputFilter(InputFilterInterface $inputFilter) {
throw new \Exception("Not used"); }
public function getInputFilter() {
if (!$this->inputFilter) {
$inputFilter = new InputFilter(); $inputFilter->add(array(
'name' => 'id', 'required' => true, 'filters' => array(
array('name' => 'Int'), ), )); $inputFilter->add(array( 'name' => 'artist', 'required' => true, 'filters' => array(
array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'StringLength', 'options' => array( 'encoding' => 'UTF-8', 'min' => 1, 'max' => 100, ), ), ), ));
$inputFilter->add(array( 'name' => 'title', 'required' => true, 'filters' => array(
array('name' => 'StripTags'), array('name' => 'StringTrim'), ), 'validators' => array( array( 'name' => 'StringLength', 'options' => array( 'encoding' => 'UTF-8', 'min' => 1, 'max' => 100, ), ), ), ));
$this->inputFilter = $inputFilter; }
return $this->inputFilter; } }
Classe AlbumController
module/Album/src /Album/Controller/AlbumController.php:
<?php /*** Zend Framework (http://framework.zend.com/) */ namespace Album\Controller; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; use Album\Model\Album; use Album\Form\AlbumForm;
class AlbumController extends AbstractActionController
{
protected $albumTable;
public function indexAction() {
return new ViewModel(array(
'albuns' => $this->getAlbumTable()->fetchAll(), ));
}
public function addAction() {
$form = new AlbumForm();
$form->get('submit')->setValue('Add');
$request = $this->getRequest(); if ($request->isPost()) { $album = new Album();
$form->setInputFilter($album->getInputFilter()); $form->setData($request->getPost()); if ($form->isValid()) { $album->exchangeArray($form->getData()); $this->getAlbumTable()->saveAlbum($album);
// Redirect to list of albums
return $this->redirect()->toRoute('album'); }
}
return array('form' => $form); }
public function editAction() {
$id = (int) $this->params()->fromRoute('id', 0); if (!$id) {
return $this->redirect()->toRoute('album', array( 'action' => 'index'
)); }
// Get the Album with the specified id. An exception is thrown // if it cannot be found, in which case go to the index page. try {
$album = $this->getAlbumTable()->getAlbum($id); }
catch (\Exception $ex) {
return $this->redirect()->toRoute('album', array( 'action' => 'index'
)); }
$form = new AlbumForm(); $form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit'); $request = $this->getRequest(); if ($request->isPost()) { $form->setInputFilter($album->getInputFilter()); $form->setData($request->getPost()); if ($form->isValid()) { $this->getAlbumTable()->saveAlbum($album); // Redirect to list of albums
return $this->redirect()->toRoute('album'); } } return array( 'id' => $id, 'form' => $form, ); }
public function deleteAction() {
$id = (int) $this->params()->fromRoute('id', 0); if (!$id) {
return $this->redirect()->toRoute('album'); }
$request = $this->getRequest(); if ($request->isPost()) {
$del = $request->getPost('del', 'No'); if ($del == 'Yes') {
$id = (int) $request->getPost('id'); $this->getAlbumTable()->deleteAlbum($id); }
// Redirect to list of albums
return $this->redirect()->toRoute('album'); } return array( 'id' => $id, 'album' => $this->getAlbumTable()->getAlbum($id) ); }
public function getAlbumTable() { if (!$this->albumTable) {
$sm = $this->getServiceLocator();
$this->albumTable = $sm->get('Album\Model\AlbumTable'); }
return $this->albumTable; }
Vistas para as ações de AlbumController
module/Album/view/album/album/index.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> index
<?php
// module/Album/view/album/album/index.phtml: $title = 'My albums';
$this->headTitle($title); ?>
<h1><?php echo $this->escapeHtml($title); ?></h1> <p>
<a href="<?php echo $this->url('album', array('action'=>'add'));?>">Add new album</a>
</p>
<table class="table"> <tr>
<th>Title</th> <th>Artist</th> <th> </th> </tr>
<?php foreach ($albuns as $album) : ?> <tr>
<td><?php echo $this->escapeHtml($album->title);?></td> <td><?php echo $this->escapeHtml($album->artist);?></td> <td>
<a href="<?php echo $this->url('album',
array('action'=>'edit', 'id' => $album->id));?>">Edit</a> <a href="<?php echo $this->url('album',
array('action'=>'delete', 'id' => $album->id));?>">Delete</a> </td>
</tr>
<?php endforeach; ?> </table>
module/Album/view/album/album/add.phtml:
<
strong
>Module:</
strong
> Album »
<
strong
>Controller:</
strong
> Album »
<
strong
>Action:</
strong
> add
<?php
$title = 'Add new album'; $this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1> <?php
$form->setAttribute('action', $this->url('album', array('action' => 'add'))); $form->prepare();
echo $this->form()->openTag($form); echo $this->formHidden($form->get('id')); echo $this->formRow($form->get('title')); echo $this->formRow($form->get('artist')); echo $this->formSubmit($form->get('submit')); echo $this->form()->closeTag();
module/Album/view/album/album/edit.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> edit
<?php
$title = 'Edit album'; $this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1> <?php
$form = $this->form;
$form->setAttribute('action', $this->url( 'album', array( 'action' => 'edit', 'id' => $this->id))); $form->prepare(); echo $this->form()->openTag($form); echo $this->formHidden($form->get('id')); echo $this->formRow($form->get('title')); echo $this->formRow($form->get('artist')); echo $this->formSubmit($form->get('submit')); echo $this->form()->closeTag();
module/Album/view/album/album/delete.phtml:
<strong>Module:</strong> Album » <strong>Controller:</strong> Album » <strong>Action:</strong> delete
<?php
$title = 'Delete album'; $this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1> <p>Are you sure that you want to delete
'<?php echo $this->escapeHtml($album->title); ?>' by '<?php echo $this->escapeHtml($album->artist); ?>'? </p>
<?php
$url = $this->url('album', array( 'action' => 'delete',
'id' => $this->id, ));
?>
<form action="<?php echo $url; ?>" method="post"> <div>
<input type="hidden" name="id" value="<?php echo (int) $album->id; ?>" /> <input type="submit" name="del" value="Yes" />
<input type="submit" name="del" value="No" /> </div>