Você está na página 1de 166

Existe uma coisa que um programador PHP

não pode ter...

MEDO!
Advertência

 Alguns exemplos de código são exemplos de


como implementar construções com Zend
Framework 2 e não do Zend Framework 2.
 Alguns exemplos fazem uso de construções
disponíveis apenas no PHP 5.4.
Advertência

 Esta apresentação não visa saciar sua sede de


conhecimento, mas deixar você sedento por
ele.
O que esperar do Zend Framework 2

Flávio Gomes da
Silva Lisboa

www.fgsl.eti.br

@fgsl
Inspirador Inspirado
Desde 2008 capacitando
profissionais em Zend
Framework

@eminetto
@fgsl
ESGOTADO

ESGOTADO
Uma Breve História do ZF

Flávio Gomes da Silva Lisboa


A Gênese

 Outubro de 2005: o projeto é anunciado


 Março de 2006: primeiro release público,
0.1.0
 Final de 2006: Reescrita do MVC
1.0.0, julho de 2007

 Primeiro release estável


 Sistema básico de MVC,
com plugins, action helpers,
renderização automatizada,
etc.
 Muitos consumidores de
APIs de web services
 Classes servidoras para
XML-RPC e REST.
1.5.0 – Março de 2008

 Primeiro release menor


 Zend_Form
 Zend_Layout
 Sistema de view helper ciente
do layout
Layout content

View content
1.6.0 – Setembro de 2008

 Integração com Dojo


 Zend_Test: extensão PHPUnit
para controladores
 Action helper ContextSwitch

HTML JSON

XML
1.7.0 – Novembro de 2008

 Suporte à AMF
 Melhorias de performance
1.8.0 – Abril de 2009

 Zend_Tool
 Zend_Application

AMPLAMENTE
USADO!

Matthew O'Phinney, ZF Leader e autor do conteúdo


no qual esta apresentação se baseia
1.9.0 – Agosto de 2009

 Zend_Feed_Reader
 Suporte/compatibilidade
com PHP 5.3
 Adições levadas pela
comunidade
 Início da caça mensal a
bugs
1.10.0 – Janeiro de 2009

 Integração de
ControllerTestCase com
Zend_Application
 Adição de Zend_Feed_Writer
 Mudanças na documentação:
adoção do PhD para renderizar
o manual do usuário, introdução
do sistema de comentário e a
seção “Learning Zend
Framework”
1.11.0 – Novembro de 2010

 Suporte mobile via


Zend_Http_UserAgent
 API SimpleCloud via
Zend_Cloud
Arquitetura
Arquitetura
Arquitetura
Flexibilidade
Liberdade de Escolha
E daqui vamos para onde?
Revolução
Inevitável
Junte-se ou morra!
Evolução
“A mutação é a chave para a nossa evolução.
Ela nos permitiu evoluir de um organismo
unicelular à espécie dominante do planeta. O
processo é lento, normalmente leva milhares e
milhares de anos. Mas em algumas centenas de
milênios a evolução dá um salto.”
Evolução
O foco do Zend Framework 2.0 é na melhoria da
consistência e performance.
Código Explícito

 Não é isto:
class SoraniknatuController
extends Zend_Controller_Action
{
public function useTheRingAction()
{
$this->view->object = 'imagination';
}
}

Onde isso Quando


E os
está ocorre a
layouts?
definido? renderização?
Mágica
ou
Bruxaria?
Código Explícito

 Código explícito é fácil de entender.


 Código explícito é fácil de analisar.
 Código explícito é fácil de manter.
Melhorias incrementais
Passos de Bebê

 Conversão de código dos prefixos de


fornecedor (por exemplo “Zend_Phaser”) para
namespaces do PHP 5.3
 Refatoração das exceções
 Somente autoload
 Melhoria e padronização do sistema de plugins
Reescrever somente onde faz
sentido
Mudanças na Infraestrutura
Namespaces
O problema

 Nomes de classes muito grandes


 Dificuldade de refatorar
 Dificuldade de reter semântica com nomes mais
curtos

Zend_Form_Decorator_Marker_File_Interface
A solução

 Cada arquivo de classe declara uma


namespace
 Um namespace por arquivo
 Qualquer classe usada que não estiver no
namespace atual (ou em um subnamespace) é
importada e tipicamente apelidada
 A resolução global é desencorajada, exceto no
caso de classes referenciadas em strings.
Exemplo de Namespace

namespace Zend\EventManager;

use Zend\Stdlib\CallbackHandler;

class EventManager implements EventCollection


{
/* ... */
}
Usando Apelidos

namespace Zend\Mvc;

use Zend\Stdlib\Dispatchable,
Zend\Di\ServiceLocator as Locator;

class FrontController implements Dispatchable


{
public function __construct(Locator $locator)
{
$this->serviceLocator = $locator;
}
}
Recomendação para Migração

Importe classes com o comando


use em vez de fazer chamadas
com require_once em seu
código!
Importando Classes
use Zend_Controller_Action as Controller;

class PowerController extends Controller


{ ZF1
}

Como ficará:
use Zend\Controller\Action as Controller;

class PowerController extends Controller


{ ZF2
}
Nomeação

 Todo código no projeto está no namespace


“Zend”
 Cada componente define um namespace único
 Classes dentro de um componente residem
naquele namespace ou em um subnamespace
 Tipicamente, uma classe nomeada de acordo
com o componente é a classe gateway.
Exemplos de Nomeação

namespace Zend\EventManager;

class EventManager implements EventCollection


{

}
Interfaces
Interfaces

 Interfaces são nomeadas de acordo com


nomes e adjetivos, e descrevem o que elas
provêem
 Na maioria dos casos, implementações
concretas interfaces residem em um
subnamespace nomeado de acordo com a
interface
 Um paradigma Orientado a Contrato mais
forte
Exemplo de Interfaces

namespace Zend\Session;

use Traversable, ArrayAccess, Serializable, Countable;

interface Storage extends Traversable, ArrayAccess, Serializable,


Countable
{

}
Implementação Concreta

namespace Zend\Session\Storage;

use ArrayObject,
Zend\Session\Storage,
Zend\Session\Exception;

class ArrayStorage extends ArrayObject implements Storage


{
/* ... */
}
Exceções
O problema

 Todas as exceções derivavam de uma classe


comum
 Incapacidade de estender os tipos de exceção
semânticas oferecidas na SPL
 Estratégias de captura limitadas
 Forte dependência para cada e todos os
componenentes
Abordagem ZF2

 Zend_Exception foi eliminado


 Cada componente define uma interface
Exception marcadora
 Exceções concretas residem em um
subnamespace Exception, e estendem
exceções SPL
O que a solução provê

 Captura tipos de exceções específicas


 Captura tipos de exceções SPL
 Captura exceções no nível do componente
 Captura baseada no tipo de exceção global
Definições de Exceção
namespace Zend\EventManager;

interface Exception
{

namespace Zend\EventManager\Exception;

use Zend\EventManager\Exception;

class InvalidArgumentException extends \InvalidArgumentException


implements Exception
{

}
Capturando Exceções

namespace Zend\EventManager\Exception;

use Zend\EventManager\Exception;

try {
$events->trigger('dom.quixote', $object);
} catch (InvalidArgumentException $e) {
} catch (Exception $e) {
} catch (\InvalidArgumentException $e) {
} catch (\Exception $e) {
}
Autocarregamento
O problema

 Problemas de performance
 Muitas classes são usadas apenas no momento
adequado e não devem ser carregadas até que
seja necessário.
 A falta de chamadas require_once leva a erros.
Abordagem ZF2

 Chega de chamadas require_once!


 Múltiplas abordagens de autocarregamento
 Autocarregador via include_path estilo ZF1
 Autocarregamento pelo namespace / prefixo do
fornecedor
 Autocarregamento por Mapa de Classes
Autocarregamento estilo ZF1

require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new Zend\Loader\StandardAutoloader(array(
'fallback_autoloader' => true,
));
$loader->register();

EX
EM
PL
O
Autocarregamento
Namespace/Prefixo ZF2

require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new Zend\Loader\StandardAutoloader();
$loader->registerNamespace('My', __DIR__ . '/../library/My')
->registerPrefix('Fgsl_', __DIR__ . '/../library/Fgsl');
$loader->register();

EX
EM
PL
O
Autocarregamento com Mapas de
Classes

return array(
'Green\Lantern\Hal' => __DIR__ . '/Lantern/Hal.php',
);

require_once 'Zend/Loader/ClassMapAutoloader.php';
$loader = new Zend\Loader\ClassMapAutoloader();
$loader->registerAutoloadMap(__DIR__ .
'/../library/.classmap.php');
$loader->register();
Mapas de Classes? Mas não dá
trabalho pra fazer?
 Sim, dá trabalho. Mas nós temos uma
ferramenta, bin/classmap_generator.php
 E o uso é trivial:
prompt>
 cd your/library
prompt> php /path/to/classmap_generator.php -w

 A execução desse script cria o Mapa de


Classes em .classmap.php
Por que?

 Mapas de Classes mostram 25% de melhoria


no carregador do ZF1 quando não é usada
aceleração.
 E 60-85% quando um cache de opcode está em
uso.
 O emparelhamento namespaces/prefixos com
caminhos especificados mostra um ganho de
10% sem aceleração.
 E 40% de melhoria quando uma cache de opcode é
usado.
Fábrica de Autocarregadores

 Com múltiplas estratégias vem a necessidade


por uma fábrica.
 Escolha diversas estratégias:
 Mapa de Classes para pesquisa mais rápida
 Caminhos namespace/prefixo para código comum
 Autocarregador de reserva estilo ZF1/PSR-0 para
desenvolvimento

PSR: PHP Specification Request


Exemplo de Fábrica de
Autocarregadores
require_once 'Zend/Loader/AutoloaderFactory.php';
use Zend\Loader\AutoloaderFactory;
AutoloaderFactory::factory(array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/../library/.classmap.php',
__DIR__ . '/../application/.classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
'Zend' => __DIR__ . '/../library/Zend',
),
'fallback_autoloader' => true,
),
));
Quando posso migrar?

 Você pode usar os autocarregadores e as


facilidades de geração dos mapas de classe do
ZF2... hoje! Pode começar a migração!
Carregamento de Plugins
Terminologia

 Para nossos propósitos, um “plugin” é qualquer


classe que é determinada em tempo de
execução.
 Auxiliares de Controle e Visão
 Adaptadores
 Filtros e Validadores
Plugins
O Problema

 Variar abordagens para descobrir classes


plugin
 Caminhos relativos para as classes chamadas
 Pilhas prexifo-caminho (mais comum)
 Modificadores para indicar classes
 A abordagem mais comum é terrível
 Má performance
 Difícil de depurar
 Sem caching de plugins descobertos
Abordagem ZF2: o Agente de
Plugins
 Interface de Localização de Plugins
 Permite variar a implementação de pesquisa de
plugins
 Interface de Agente de Plugins
 Compõe um Localizador de Plugins
Interface de Localização de
Plugins

namespace Zend\Loader;
interface ShortNameLocator
{
public function isLoaded($name);
public function getClassName($name);
public function load($name);
}
Interface de Agente de Plugins

namespace Zend\Loader;
interface Broker
{
public function load($plugin, array $options = null);
public function getPlugins();
public function isLoaded($name);
public function register($name, $plugin);
public function unregister($name);
public function setClassLoader(ShortNameLocator $loader);
public function getClassLoader();
}
Como usar?

 Crie um carregador de plugins padrão


 Crie um agente de plugins padrão
 Componha um agente dentro de sua classe
 Opcionalmente, defina configuração estática
 Opcionalmente, passe a configuração de
agente e carregador
 Opcionalmente, registre plugins com o
localizador ou o agente
Implementação do Localizador de
Plugins
namespace Zend\View;
use Zend\Loader\PluginClassLoader;
class HelperLoader extends PluginClassLoader
{
/**
* @var array Pre-aliased view helpers
*/
protected $plugins = array(
'action'
=> 'Zend\View\Helper\Action',
'base_url' => 'Zend\View\Helper\BaseUrl',
/* ... */
);
}
Implementação do Agente de
Plugins
class HelperBroker extends PluginBroker
{
protected $defaultClassLoader = 'Zend\View\HelperLoader';
public function load($plugin, array $options = null)
{
$helper = parent::load($plugin, $options);
if (null !== ($view = $this->getView())) {
$helper->setView($view);
}
return $helper;
}
protected function validatePlugin($plugin)
{
if (! $plugin instanceof Helper) {
throw new InvalidHelperException();
}
return true;
}
}
Compondo um Agente
use Zend\View\HelperLoader;
class Sinestro
{
protected $broker;
public function broker($spec = null, array $options = array())
{
if ($spec instanceof Broker) {
$this->broker = $spec;
return $spec;
} elseif (null === $this->broker) {
$this->broker = new PluginBroker();
}
if (null === $spec) {
return $this->broker;
} elseif (!is_string($spec)) {
throw new \Exception();
}
return $this->broker->load($spec, $options);
}
}
Precedência dos Localizadores

(Do menos para o mais específico)


 Mapa definido no carregador de plugins
concreto
 Mapas estáticos ( o registro mais recente tem
precedência)
 Mapeamento passado via instanciação
 Mapeamento explícito provido
programaticamente
Definindo Mapas Estáticos

use Zend\View\HelperLoader;
HelperLoader::addStaticMap(array(
'url'
=> 'Kilowog\Helper\Url',
'base_url' => 'Project\Helper\BaseUrl',
));
$loader = new HelperLoader();
$class = $loader->load('url'); // "Kilowog\Helper\Url"
Passando Mapas via
Configuração

use Zend\View\HelperLoader;
$config = array(
'url'
=> 'Kilowog\Helper\Url',
'base_url' => 'Project\Helper\BaseUrl',
);
$loader = new HelperLoader($config);
$class = $loader->load('url'); // "Kilowog\Helper\Url"
Passando Mapas para Mapas!

use Zend\View\HelperLoader,
Zend\Loader\PluginClassLoader;
class HelperMap extends PluginClassLoader
{
protected $plugins = array(
'url'
=> 'Kilowog\Helper\Url',
'base_url' => 'Project\Helper\BaseUrl',
);
}
$helpers = new HelperMap();
$loader = new HelperLoader($helpers);
$class
= $loader->load('url'); // "Kilowog\Helper\Url"
Estendendo Carregadores
use Zend\View\HelperLoader;
class HelperMap extends HelperLoader
{
public function __construct($options = null)
{
// Adiciona e/ou sobrescreve o mapa do
HelperLoader
$this->registerPlugins(array(
'url'
=> 'Kilowog\Helper\Url',
'base_url' => 'Project\Helper\BaseUrl',
));
parent::__construct($options);
}
}
$helpers = new HelperMap();
$class
= $loader->load('url'); // "Kilowog\Helper\Url"
Passando Mapas via Agente

use Zend\View\HelperBroker;
$broker = new HelperBroker(array(
'class_loader' => array(
'class'
=> 'HelperMap',
'options' => array(
'base_url' => 'App\Helper\BaseUrl',
),
),
));
$plugin = $broker->load('base_url'); // "App\Helper\BaseUrl"
Criando Mapas Manualmente

use Zend\View\HelperLoader;
$loader = new HelperLoader();
$loader->registerPlugin('url', 'Kilowog\Helper\Url')
->registerPlugins(array(
'base_url' => 'Project\Helper\BaseUrl',
));
$class = $loader->load('url'); // "Kilowog\Helper\Url"
Gerenciando Plugins via Agente

 Por padrão, o carregador é consultado para um


nome de classe, e instancia a classe com os
argumentos dados
 Opcionalmente, você pode alimentar o agente,
registrando objetos plugins manualmente sob
um dado nome
Registrando um Plugin com o
Agente

use Kilowog\Helper\Url;
// Assume:
// - $request == objeto Request
// - $router == objeto Router
// - $broker == HelperBroker

$url = new Url($request, $router);


$broker->registerPlugin('url', $url); // OU:
$broker->registerPlugins(array(
'url' => $url,
));
$url = $broker->load('url'); // === $url acima
E sobre o carregamento tardio?

 Frequentemente você precisa configurar


plugins
 Mas você quer uma instância só quando ela for
realmente requisitada
 É aí que entra
Zend\Loader\LazyLoadingBroker
LazyLoadingBroker Interface

namespace Zend\Loader;
interface LazyLoadingBroker extends Broker
{
public function registerSpec($name, array $spec = null);
public function registerSpecs($specs);
public function unregisterSpec($name);
public function getRegisteredPlugins();
public function hasPlugin($name);
}
Usando LazyLoadingBroker

 Registra “especificações” com o agente


 Quando o plugin é requisitado, as opções
fornecidas serão usadas a menos que novas
opções sejam passadas
 De todas as outras maneiras, ele comporta-se
como outros agentes, incluindo a permissão do
registro explícito de plugins
Usando LazyLoadingBroker

$broker->registerSpec('url', array($request, $router));


$broker->registerSpecs(array(
'url' => array($request, $router),
));
if (!$broker->hasPlugin('url')) {
// sem especificação!
}
$plugins = $broker->getRegisteredPlugins(); // array('url')
$url = $broker->load('url'); // Com $request, $router é
injetado
Usando LazyLoadingBroker via
Configuração

use Zend\View\HelperBroker;
$config = array(
'specs' => array(
'url' => array($request, $router),
),
);
$broker = new HelperBroker($config);
$url
= $broker->load('url'); // Com $request, $router é
injetado
E ainda tem mais!
Novos Componentes
E Componentes Poderosos!
Novos Componentes

 Zend\EventManager
 Zend\Di
EventManager
O Problema

 Como nós introduzimos pontos de log/debug


no código do framework?
 Como nós permitimos que os usuários
introduzam caching sem necessidade de
estender o código do framework?
 Como nós permitimos que os usuários
introduzam validação, filtragem, verificações de
controle de acesso, etc., sem necessariamente
estender o código do framework?
O Problema

 Como permitirmos que os usuários manipulem


a ordem na qual plugins, filtros de
interceptação, eventos, etc., são disparados.
 Como nós podemos prover ferramentas para o
código do usuário trabalhe em prol das
questões anteriores?
Solução: Programação Orientada
a Aspectos
 O código define vários “aspectos” que podem
ser interessantes observar e/ou anexar a partir
de um consumidor.
 Basicamente, todas as soluções que
examinaremos podem ser usadas para
implementar POA em um código base.

www.fgsl.eti.br

Palestras
Requisitos

 Projeto que seja razoavelmente fácil de


entender.
 Permitir anexar manipuladores de forma
estática ou por instância, preferencialmente de
ambas as formas.
 Preferencialmente enquanto reter o estado
não-global ou permitir sobrescrita.
 Permitir interrupção da execução
 Permitir a priorização de manipuladores
Requisitos

 Projeto que seja razoavelmente fácil de


entender.
 Permitir anexar manipuladores de forma
estática ou por instância, preferencialmente de
ambas as formas.
 Preferencialmente enquanto reter o estado
não-global ou permitir sobrescrita.
 Permitir interrupção da execução
 Permitir a priorização de manipuladores
Requisitos

 Previsibilidade de argumentos passados para


manipuladores.
 Habilidade de anexar a muitos componentes
emissores de eventos de uma vez.
Solução: Observador de Sujeitos

 Prós
 Simples de entender
 Interfaces SPL são bem conhecidas (mas
limitadas)
 Contras
 Tipicamente, não pode interromper a execução de
observadores remanescentes
 Requer um sistema para cada componente e/ou
classe
 Tipicamente, sem habilidade para priorizar
manipuladores
Solução: Publicador/Sobrescritor
de Eventos
 Prós
 Sobrescrita de notificações arbitrárias
 Tipicamente por componente + uso global; em
muitas linguagens, um único agregador global
 Paradigma bem-conhecido na programação de
interfaces com o usuário (pense em Javascript)
 Tende a ser um Turing completo
Solução: Publicador/Sobrescritor
de Eventos (PubSub)
 Contras
 Frequentemente, precisa testar o evento fornecido
para garantir que você pode manipulá-lo.
 Uso global implica em agregação estática e/ou
dependências estáticas.
 … mas o uso por componente implica em um
boilerplate para compor em cada classe se ele for
usado.
 Tipicamente, sem habilidade para priorizar
manipuladores.
 Falaremos mais sobre isso mais tarde...
Pausa para esclarecimento

 boilerplate é o termo usado para descrever


seções de código que foram incluídas em
muitos lugares com pouca ou nenhuma
alteração.
Solução: SignalSlots

 Prós
 Conceito bem conhecido nos círculos de Ciência da
Computação
 O código emite sinais, que são interceptados por
slots (vulgos manipuladores)
 Tipicamente, compõe um gerenciador de sinais em
uma classe, mas pode ser integrado com um
gerenciador global também
 Geralmente tem algumas habilidades para priorizar
manipuladores
Solução: SignalSlots

 Contras
 Esse palavreado não é bem conhecido entre
programadores PHP.
 Argumentos irão variar entre sinais.
 Os mesmos problemas com composição por classe
e uso estático como vemos em sistemas de
eventos.
Filtros de Interceptação

 Prós
 Similar às soluções anteriores, exceto que cada
manipulador recebe a cadeia de filtros como um
argumento, e é responsável por chamar o próximo
na cadeia.
 Frequentemente, o “trabalho” inteiro de um método
é simplesmente um executar um filtro.
 Dependendo do projeto, pode permitir acesso
global/estático.
Filtros de Interceptação

 Contras
 Algumas vezes é difícil acompanhar fluxos de
trabalho complexos.
 Os mesmos problemas com composição por classe
e uso estático como vemos em sistemas de evento.
 É fácil esquecer de invocar o próximo filtro na
cadeia.
 Tipicamente, sem habilidade de priorizar filtros.
Mas
qual a
solução
afinal?
Nenhuma!
Todas!
Combinação

de Poderes
Linka
Wheeler

Gi Ma-Ti Kwame
ZF2: EventManager Component

 A cereja do bolo de cada solução, PubSub,


SignalSlot, e Filtros de Interceptação, para
prover uma solução compreensiva.
 Não pode resolver completamente os
problemas de composição/uso estático.
 Nós podemos resolver o problema da
composição no PHP 5.4 com Traits.
 Há formas elegantes de manipular o uso
estático.
Interface EventCollection

namespace Zend\EventManager;
use Zend\Stdlib\CallbackHandler;
interface EventCollection
{
public function trigger($event, $context, $argv = array());
public function triggerUntil($event, $context, $argv, $callback);
public function attach($event, $callback, $priority = 1);
public function detach(CallbackHandler $handle);
public function getEvents();
public function getHandlers($event);
public function clearHandlers($event);
}
Disparando Eventos

use Zend\EventManager\EventManager;
$events = new EventManager();
$events->trigger($eventName, $object, $params);
/* Onde:
* - $eventName é o nome do evento; geralmente o nome do evento
atual
*
* - $object é o objeto que está disparando o evento
* - $params são os parâmetros que o manipulador pode precisar
para ter acesso,
geralmente os argumentos do método
*
*/
CallbackHandler

$handler = $events->attach('algum-evento', function($e) use


($log) {
$event
= $e->getName();
$context = get_class($e->getTarget());
$params = json_encode($e->getParams());
$log->info(sprintf("%s: %s: %s", $event, $context, $params));
});
Callback Handler com Prioridade

$handler = $events->attach('algum-evento', function($e) use


($log) {
/* o mesmo que o anterior */
}, 100); // Priorize! (números altos ganham)
Interrompendo a Execução:
Testando Resultados

$results = $events->triggerUntil('algum-evento', $o,


$argv,
function($result) {
return ($result instanceof SomeType);
});
if ($results->stopped()) {
return $results->last();
}
Interrompendo a Execução: via
Manipuladores

$events->attach('algum-evento', function($e) {
$result = new Result;
$e->stopPropagation(true);
return $result;
});
$results = $events->trigger('algum-evento', $object,
$params);
if ($results->stopped()) {
return $results->last();
}
Compondo um EventManager
use Zend\EventManager\EventCollection as Events,
Zend\EventManager\EventManager;
class Arisia
{
protected $events;
public function events(Events $events = null)
{
if (null !== $events) {
$this->events = $events;
} elseif (null === $this->events) {
$this->events = new EventManager(__CLASS__);
}
return $this->events;
}
public function doSomething($param1, $param2)
{
$params = compact('param1', 'param2');
$this->events()->trigger(__FUNCTION__, $this, $params);
}
}
Usando um Trait!
use Zend\EventManager\EventCollection as Events,
Zend\EventManager\EventManager;
trait Eventful
{
public function events(Events $events = null)
{
if (null !== $events) {
$this->events = $events;
} elseif (null === $this->events) {
$this->events = new EventManager(__CLASS__);
}
return $this->events;
}
}
class Arisia
{
use Eventful;
protected $events;
}
Conectando Manipuladores
Estaticamente

use Zend\EventManager\StaticEventManager;
$events = StaticEventManager::getInstance();
$events->connect('Arisia', 'algum-evento', function
($e) {
/* ... */
});
Recomendações

 Nomeie seus eventos usando __FUNCTION__


 Se disparar múltiplos eventos no mesmo método,
sufixe com um “.(pre|pos|etc.)”
 Forneça para o construtor do EventManager
tanto o nome da classe quanto um ou mais
nomes de “serviços”, para fazer anexações
estáticas mais semânticas.
 Isso permite que um único callback ouça muitos
componentes!
Injeção de Dependência (DI)
O Que é Injeção de Dependência?

 Muito simples: definir modos de passar


dependências para dentro de um objeto.

namespace Tomarre\Helper;
class Url
{
public function __construct(Request $request)
{
$this->request = $request;
}
public function setRouter(Router $router)
{
$this->router = $router;
}
}
Então porque as pessoas tem
medo disso?
 Porque elas não fazem isso.
 Elas temem os Conteineres de Injeção de
Dependência.
O Que é um Conteiner de Injeção
de Dependência

Colocando de forma
simples:
Um grafo de objetos para
mapear relações de
dependência entre
objetos.
Grafos
Novamente, por que as pessoas
tem medo disso?

Porque parece mágica!


Objeto com Dependências

namespace Tomarre\Helper;
class Url
{
public function __construct(Request $request)
{
$this->request = $request;
}
public function setRouter(Router $router)
{
$this->router = $router;
}
}
Outro Objeto com Dependências

namespace mwop\Mvc;
class Router
{
public function addRoute(Route $route)
{
$this->routes->push($route);
}
}
Agarrando um Objeto e Usando-o

$urlHelper = $di->get('url-helper');
echo $url->generate('/css/site.css');
echo $url->generate(array('id' => $id), array('name' =>
'blog'));
As Questões

 Como eu posso estar certo se eu tenho minhas


dependências?
 Você as define explicitamente.
 Você recupera o objeto via conteiner, o que garante
que as definições são usadas.
 Onde eu defino essas coisas?
 Programaticamente, via configuração, ou usando
uma ferramenta.
As Questões

 Se eu chamar $object = new Salaak(), como eu


forço o uso de diferentes dependências?
 Chamar new não usa o conteiner. Na verdade,
nada força você a usá-lo!
Por que usar um?

 Se a instanciação de seus objetos não está


debaixo de seu controle direto (por exemplo,
controladores), como você retém controle
sobre suas dependências?
 Acesso a dados diferente baseado no ambiente da
aplicação.
 Substituição de implementações mock/stub durante
o teste.
Abordagem ZF2

 Padronizar em uma interface de localizador de


serviços.
 Prover uma solução DI performática, e integrá-
la dentro de um localizador de serviços.
 Prover ferramentas para auxiliar na criação de
definições de DI durante o desenvolvimento.
Interface para Localizador de
Serviços

namespace Zend\Di;
interface ServiceLocation
{
public function set($name, $service);
public function get($name, array $params = null);
}
Interface para Injetor de
Dependências

namespace Zend\Di;
interface DependencyInjection
{
public function get($name, array $params = null);
public function newInstance($name, array $params = null);
public function setDefinitions($definitions);
public function setDefinition(
DependencyDefinition $definition, $serviceName = null);
public function setAlias($alias, $serviceName);
public function getDefinitions();
public function getAliases();
}
Definições
namespace Zend\Di;
interface DependencyDefinition
{
public function __construct($className);
public function getClass();
public function setConstructorCallback($callback);
public function getConstructorCallback();
public function hasConstructorCallback();
public function setParam($name, $value);
public function setParams(array $params);
public function setParamMap(array $map);
public function getParams();
public function setShared($flag = true);
public function isShared();
public function addTag($tag);
public function addTags(array $tags);
public function getTags();
public function hasTag($tag);
public function addMethodCall($name, array $args);
public function getMethodCalls();
}
Referências

namespace Zend\Di;
interface DependencyReference
{
public function __construct($serviceName);
public function getServiceName();
}
Definição de Classe
use Zend\Di\Definition,
Zend\Di\Reference;
$mongo = new Definition('Mongo');
$mongoDB = new Definition('MongoDB');
$mongoDB->setParam('conn', new Reference('mongo'))
->setParam('name', 'test');
$coll = new Definition('MongoCollection');
$coll->setParam('db', new Reference('mongodb'))
->setParam('name', 'resource');
$di->setDefinitions(array(
'mongo'=> $mongo,
'mongodb' => $mongoDB,
'resource' => $coll,
));
$resource = $di->get('resource');
Injeção por Modificador

use Zend\Di\Definition,
Zend\Di\Reference;
$service = new Definition('mwop\Service\Resources');
$service->addMethod('setResource', array(
new Reference('resource')
));
$di->setDefinition('resources', $service);
$resources = $di->get('resources');
Fazendo mais Rápido

 Especificar mapas de parâmetros de construtor


em definições.
 Gerar localizadores de serviço a partir de um
conteiner DI.
Mapas de Parâmetros

$mongoDB->setParam('conn', new Reference('mongo'))


->setParam('name', 'test')
->setParamMap(array(
'conn' => 0,
'name' => 1,
));
// Garante que os parâmetros estão em ordem, sem precisar
// recorrer à API de reflexão
Gerando um Localizador de
Serviços a partir de DI

use Zend\Di\ContainerBuilder as DiBuilder;


$builder = new DiBuilder($injector);
$builder->setContainerClass('AppContext');
$container = $builder->getCodeGenerator(
__DIR__ . '/../application/AppContext.php'
); // Retorna uma instância de Zend\CodeGenerator\Php\PhpFile
$container->write(); // Grava no disco
Exemplo de um localizador
gerado
use Zend\Di\DependencyInjectionContainer;
class AppContext extends DependencyInjectionContainer
{
public function get($name, array $params = array())
{
switch ($name) {
case 'request':
case 'Zend\Http\Request':
return $this->getZendHttpRequest();
default:
return parent::get($name, $params);
}
}
public function getZendHttpRequest()
{
if (isset($this->services['Zend\Http\Request'])) {
return $this->services['Zend\Http\Request'];
}
$object = new \Zend\Http\Request();
$this->services['Zend\Http\Request'] = $object;
return $object;
}
}
Usando um localizador gerado

$context = new AppContext();


$request = $context->get('request');
// O mesmo que usar um localizador de serviços ou
um conteiner DI!
Fazendo mais simples ainda

 Use arquivos de configuração


 Você pode usar qualquer formato suportado
por Zend\Config:
 INI
 JSON
 XML
 YAML
Exemplo de configuração com
JSON
{
"production": { "definitions": [
{ "class": "Mongo" },
{ "class": "MongoDB",
"params": {
"conn": {"__reference": "mongocxn"},
"name": "mwoptest"
},
"param_map": { "conn": 0, "name": 1 }
},
{ "class": "MongoCollection",
"params": {
"db": {"__reference": "MongoDB"},
"name": "entries"
},
"param_map": { "db": 0, "name": 1 }
}
], "aliases": {
"mongocxn": "Mongo",
"mongo-collection-entries": "MongoCollection"
}
}
}
Quais são os casos de uso no
ZF2?
 Um grandão.

Tirar os controladores
MVC do conteiner
Um Controlador de Ação
namespace Blog\Controller;
class Entry implements Dispatchable
{
public function setResource(Resource $resource)
{
$this->resource = $resource;
}
public function dispatch(Request $request, Response $response =
null)
{
/* ... */
$entry = $this->resource->get($id);
/* ... */
}
}
O Controlador Frontal
class FrontController implements Dispatchable
{
public function __construct(DependencyInjection $di)
{
$this->di = $di;
}
public function dispatch(Request $request, Response $response =
null)
{
/* ... */
$controller = $this->di->get($controllerName);
$result = $controller->dispatch($request, $response);
/* ... */
}
}
Benefícios de usar DI deste modo

 Performance
 Desacoplamento de código
 Simplificação do código do controlador
E vem mais por aí

 Compilação na primeira execução.


 Ferramentas para vasculhar classes ou
namespaces para construir definições.
 Injeção de interface.
 … e mais coisas legais.
Padrões MVC
Padrões MVC
Os Problemas

 Como os controladores obtém dependências?


 Como nós acomodamos diferentes padrões de
controladores?
 E se se nós quisermos uma seleção de ações mais
apurada, baseada em outros dados no ambiente de
requisição?
 E se quisermos passar argumentos para nomes de
ações, ou argumentos pré-validados?
 E se nós não gostarmos do sufixo “Action” nos
métodos acionadores?
 E se...
Os Problemas

 Como nós podemos melhorar o uso de


componentes do servidor dentro do MVC?
 Como nós podemos fazer o MVC mais
performático?
A estrutura básica de uma
aplicação web é a de um
ciclo de vida
Requisição/Resposta
A interface Dispatchable

namespace Zend\Stdlib;
interface Dispatchable
{
public function dispatch(
Request $request, Response $response = null
);
}
Requisição e Resposta

 Tanto a Requisição quanto a Resposta


simplesmente agregam metadados e conteúdo.
 A Resposta também tem a habilidade de enviar
a si mesma.
 Variantes específicas de HTTP serão o núcleo
do MVC.
 Para prover conveniência em torno de variáveis
superglobais, cookies e tarefas comuns tais como
determinar os cabeçalhos Accept e Content-Type.
Qualquer Dispatchable pode
anexar ao MVC

Dispatchable é simplesmente
uma formalização do padrão de
projeto Command.
 Controladores
 Servidores
 Qualquer coisa que você sonhar! Apenas
implemente Dispatchable!
Um protótipo simples de um
Controlador Frontal
public function dispatch(Request $request, Response $response = null)
{
$params = compact('request', 'response');
$this->events()->trigger(__FUNCTION__ . '.route.pre', $this,
$params);
$result = $this->getRouter()->route($request);
if (!$result) {
$result = array('controller' => 'page', 'page' => 404);
}
$params['routing'] = (object) $result;
$this->events()->trigger(__FUNCTION__ . '.route.post', $params);
$controller = $this->di->get($params['routing']->controller);
if (!$controller instanceof Dispatchable) {
$controller = new NotFoundController();
}
$result = $controller->dispatch($request, $response);
$params['__RESULT__'] = $result;
$this->events()->trigger(__FUNCTION__ . '.dispatch.post',
$params);
return $response;
}
Contexto Temporal

 Quando esta apresentação foi finalizada, o


último release do Zend Framework 1 era o
1.11.11 e o Zend Framework 2 estava na
versão 2.0.0beta1.
Mais informações

 http://framework.zend.com
 https://github.com/zendframework/zf2
 www.fgsl.eti.br
 Aguarde... treinamentos de arquitetura, migração,
e desenvolvimento.
 Pra quem quer sair na frente, Mão na Massa MVC
Zend Framework 2!
 Coming soon 2012!

Você também pode gostar