ZF2 Workshop

Presenter Notes

Our goal

Better consistency and performance.

Presenter Notes

How we'll get there

  1. Community

Presenter Notes

Community has helped us identify the problems of ZF1, and also proposed and provided solutions we're now using and incorporating into ZF2. We've had close to 300 people make direct commits to ZF1, and close to 100 collaborators on ZF2 already. Our direction is now being driven by community proposals, IRC meetings, and voting.

How we'll get there

  1. Community
  2. Emphasis on SOLID principles

Presenter Notes

  • S ingle-responsibility principle
  • O pen/closed principle,
  • L iskov substitution principle,
  • I nterface segregation principle,
  • D ependency inversion principle.

How we'll get there

  1. Community
  2. Emphasis on SOLID principles
  3. Embrace the new features of PHP 5.3+

Presenter Notes

We're using namespaces, autoloading, closures, late static binding, functors, all the yumminess of SPL, even goto

How we'll get there

  1. Community
  2. Emphasis on SOLID principles
  3. Utilize PHP 5.3+ to full advantage
  4. Fully utilize HTTP

Presenter Notes

The HTTP specification is full of goodies, and we leverage it and code to it with a redesigned HTTP component. This includes full encapsulation of HTTP methods, headers, cookies, and more, and becomes the core of our new MVC layer.

A New Core

Presenter Notes

The ZF1 Way

Singletons, Registries, and Hard-Coded Dependencies

Presenter Notes

The main entry point to a ZF1 app was a singleton, Zend_Controller_Front. Registries have long been promoted as a way to access dependencies, and are the primary way i18n/l10n work. Controllers typically instantiated objects they needed directly. It's a mess.

The ZF2 Way

Aspect Oriented Design and Dependency Injection

Presenter Notes

The two biggest questions as we've worked on ZF2 have centered around how controllers could get their dependencies, and how to make systems flexible. Tasks like logging, caching, and authorization should be simple to initiate.

Dependency Injection

Presenter Notes

Basic Dependency Injection

Dependency Injection is not magic.

 1 <?php
 2 class Bar {}
 3 
 4 class Foo
 5 {
 6     protected $bar;
 7 
 8     public function __construct(Bar $bar)
 9     {
10         $this->bar = $bar;
11     }
12 }

Presenter Notes

Setter Injection

Sometimes we use setters.

 1 <?php
 2 class Foo
 3 {
 4     protected $bar;
 5 
 6     public function setBar(Bar $bar)
 7     {
 8         $this->bar = $bar;
 9     }
10 }

Presenter Notes

It solves a problem

  • How do we enforce a separation of concerns?

Presenter Notes

It creates a new problem

  • How and when do we create and inject dependencies?

Presenter Notes

DI Containers

  • Store object definitions, including dependency information
  • Store environment-specific configuration of the dependencies
  • Handle the creation of objects and their dependencies

Presenter Notes

Sample Definition

 1 <?php
 2 $definition = array(
 3     'Foo' => array(
 4         'setBar' => array(
 5             'bar' => array(
 6                 'type'     => 'Bar',
 7                 'required' => true,
 8             ),
 9         ),
10     ),
11 );

Presenter Notes

Using the DI Container

 1 <?php
 2 use Zend\Di\Di,
 3     Zend\Di\Configuration;
 4 
 5 $di     = new Di;
 6 $config = new Configuration(array(
 7     'definition' => array('class' => $definition)
 8 ));
 9 $config->configure($di);
10 
11 $foo = $di->get('Foo'); // contains Bar!

Presenter Notes

Typically configuration-driven

 1 <?php
 2 return array('di' => array('instance' => array(
 3     'Cache\Adapter\Memcached' => array('parameters' => array(
 4         'options' => 'Cache\Adapter\MemcachedOptions',
 5     )),
 6 
 7     'Cache\Adapter\MemcachedOptions' => array('parameters' => array(
 8         'options' => array(
 9             'namespace'       => 'cache_listener',
10             'ttl'             => 60 * 60 * 24 * 7, // 1 week
11             'server'          => '127.0.0.1',
12             'port'            => 11211,
13             'compression'     => true,
14             'binary_protocol' => true,
15             'no_block'        => true,
16             'connect_timeout' => 100,
17             'serializer'      => 3, // JSON
18         ),
19     )),
20 )));

Presenter Notes

Multiple DI strategies

  • Runtime (using Reflection)
  • Compiled (pre-compile to classes)
  • Builder (programmatically create)
  • Annotation (hint to Runtime or Compiled strategies)

Presenter Notes

Annotation support is primarily to make interface injection possible.

Aspects, Signals and Events, oh my!

Presenter Notes

All similar concepts, with different nuances.

Terminology

  • An Event is an action
  • A Listener is a callback that responds to an event
  • An EventManager is a collection of listeners and triggers events

Presenter Notes

In a Nutshell

 1 <?php
 2 use Zend\EventManager\EventManager;
 3 
 4 $events = new EventManager();
 5 
 6 $events->attach('do', function($e) {
 7     $event  = $e->getName();
 8     $params = $e->getParams();
 9     printf(
10         'Handled event "%s", with parameters %s',
11         $event,
12         json_encode($params)
13     );
14 });
15 
16 $params = array('foo' => 'bar', 'baz' => 'bat');
17 $events->trigger('do', null, $params);

Presenter Notes

Attach

  • An event name
  • The callback that will respond
  • Optionally, the priority at which to listen

Presenter Notes

Priority allows doing pre/post style hooks

1 <?php
2 $events->attach('do', $callback);       // base priority
3 $events->attach('do', $callback, 100);  // high priority
4 $events->attach('do', $callback, -100); // low priority

Presenter Notes

Trigger

Typically:

  • Event name
  • Target (object or function triggering the event)
  • Arguments

Presenter Notes

1 <?php
2 // instance method
3 public function foo($bar, $baz)
4 {
5     $params = compact('bar', 'baz');
6     $this->events()->trigger('do', $this, $params);
7 }

Presenter Notes

Event

 1 <?php
 2 interface EventDescription
 3 {
 4     public function setName($name);
 5     public function setTarget($target);
 6     public function setParams($params);
 7     public function getName();
 8     public function getTarget();
 9     public function getParam($name, $default = null);
10     public function getParams();
11 }

Presenter Notes

Listeners

Listeners receive an Event as their sole argument.

Listeners can be any valid PHP callback.

Presenter Notes

 1 <?php
 2 function ($e) {
 3     $event  = $e->getName();
 4     $target = $e->getTarget();
 5     $target = (is_object($target) 
 6             ? get_class($target) 
 7             : gettype($target))
 8     $params = $e->getParams();
 9     printf(
10         'Event "%s", params %s, target "%s"',
11         $event,
12         json_encode($params),
13         $target
14     );
15 }

Presenter Notes

Digression: Custom Events

Presenter Notes

Semantics matter.

Presenter Notes

An Example of Bad Semantics

1 <?php
2 $callback = function($e) {
3     $result  = $e->getParam('__RESULT__');
4     $matches = $e->getParam('route-match');
5     // ...
6 };

Presenter Notes

Better Semantics

1 <?php
2 $callback = function($e) {
3     $result  = $e->getResult();
4     $matches = $e->getRouteMatch();
5     // ...
6 };

Presenter Notes

Creating Custom Events

Easiest is to simply extend Zend\EventManager\Event:

 1 <?php
 2 use Zend\EventManager\Event;
 3 
 4 class MyEvent extends Event
 5 {
 6     protected $result;
 7 
 8     public function setResult($result)
 9     {
10         $this->setParam('result', $result);
11     }
12 
13     public function getResult()
14     {
15         return $this->getParam('result');
16     }
17 }

Presenter Notes

Using built-in parameters makes martialling easier.

Using Custom Events: part 1

Pass as sole parameter:

1 <?php
2 $event = new MyEvent();
3 $event->setName('foo');
4 $event->setTarget($this);
5 
6 $events->trigger($event);

Presenter Notes

Using Custom Events: part 2

Pass as second parameter:

1 <?php
2 $event = new MyEvent();
3 $event->setTarget($this);
4 
5 $events->trigger('foo', $event);

Presenter Notes

Using Custom Events: part 3

Pass as third parameter:

1 <?php
2 $event = new MyEvent();
3 
4 $events->trigger('foo', $this, $event);

Presenter Notes

Recommended Usage

Presenter Notes

Compose an EventManager

 1 <?php
 2 use Zend\EventManager\EventCollection,
 3     Zend\EventManager\EventManager;
 4 class Foo
 5 {
 6     protected $events;
 7     public function setEventManager(EventCollection $events)
 8     {
 9         $this->events = $events;
10     }
11     public function events()
12     {
13         if (!$this->events) {
14             $this->setEventManager(new EventManager(array(
15                 __CLASS__, get_called_class(),
16             ));
17         }
18         return $this->events;
19     }
20 }

Presenter Notes

This would make a great Trait

Trigger Events

 1 <?php
 2 class Foo
 3 {
 4     // ...
 5 
 6     public function bar($baz, $bat = null)
 7     {
 8         $params = compact($baz, $bat);
 9         $this->events()->trigger(__FUNCTION__, $this, $params);
10     }
11 }

Presenter Notes

Notes on Triggering

  • Use the method name as the event.
    • If triggering more than one event, use dot-notation (e.g., "dispatch.pre", "dispatch.post").
  • Pass all input to the method as parameters.

Presenter Notes

Aggregate Listeners

Presenter Notes

Sometimes you'll want stateful listeners.

Presenter Notes

Examples might be composing object collaborators

Sometimes listeners fall under the same area of responsibility.

Presenter Notes

Example includes view listener in ZF2 - views, layouts, error pages

Aggregate Listeners

  • Allow attaching and detaching many listeners at once
  • Pass an aggregate to the event manager, or vice versa

Presenter Notes

Example

 1 <?php
 2 class LogEvents implements ListenerAggregate
 3 {
 4     protected $handlers = array();
 5     protected $log;
 6 
 7     public function __construct(Logger $log)
 8     {
 9         $this->log = $log;
10     }
11 
12     public function log(Event $e)
13     {
14         $event  = $e->getName();
15         $params = $e->getParams();
16         $log->info(sprintf('%s: %s', $event, json_encode($params)));
17     }
18 
19     // ...
20 }

Presenter Notes

Aggregate Functionality

 1 <?php
 2 public function attach(EventCollection $events)
 3 {
 4     $this->handlers[] = $events->attach('do', array($this, 'log'));
 5     $this->handlers[] = $events->attach('doSomethingElse', array($this, 'log'));
 6 }
 7 
 8 public function detach(EventCollection $events)
 9 {
10     foreach ($this->handlers as $key => $handler) {
11         $events->detach($handler);
12         unset($this->handlers[$key];
13     }
14     $this->handlers = array();
15 }

Presenter Notes

Using Aggregates

1 <?php
2 $logEvents = new LogEvents($logger);
3 $events->attach($logEvents);
4 
5 // or
6 $logEvents->attach($events);

Presenter Notes

Attaching Listeners Globally

Presenter Notes

Why?

  • Listeners are often used for cross-cutting concerns.
  • Often, you'll want to setup listeners early, before you have access to specific event manager instances.
  • E.g., logging, debugging, caching.

Presenter Notes

How?

Use the StaticEventManager.

1 <?php
2 use Zend\EventManager\StaticEventManager;
3 $events = StaticEventManager::getInstance();
4 $events->attach($context, $event, $callback);

Presenter Notes

What is the "context"?

  • Each EventManager instance can listen on one or more contexts
    • Provided to the constructor
    • Alternately added using setIdentifier()
  • When triggering events, checks to see if any listeners were registered for its contexts with the StaticEventManager
  • You can disable this at any time!

Presenter Notes

Example

1 <?php
2 use Zend\EventManager\StaticEventManager;
3 $events = StaticEventManager::getInstance();
4 $events->attach('Foo', 'bar', function ($e) {
5     $event  = $e->getName();
6     $target = $e->getTarget();
7     $params = json_encode($e->getParams());
8     sprintf('%s::%s: %s', $event, $target, $params);
9 });

Presenter Notes

Advanced Topics

Presenter Notes

Short Circuiting

Presenter Notes

What?

  • Sometimes you may want to return early based on what a listener does
  • Sometimes a listener may want to prevent other listeners from processing

Presenter Notes

How?

Method 1: Attach a callback when triggering

1 <?php
2 $results = $events->trigger($event, function($r) {
3     return ($r instanceof Response);
4 });

Presenter Notes

How?

Method 2: Signal from a listener

1 <?php
2 $listener = function ($e) {
3     $e->stopPropagation(true);
4 };

Presenter Notes

This is like JavaScript

Testing for stopped propagation

1 <?php
2 $results = $events->trigger(/* ... */);
3 if ($results->stopped()) {
4     return $results->last();
5 }

Presenter Notes

Introspecting results

Presenter Notes

Iterate:

1 <?php
2 foreach ($results as $result) {
3     // ...
4 }

Presenter Notes

First result:

1 <?php
2 $first = $results->first();

Presenter Notes

Last result:

1 <?php
2 $first = $results->last();

Presenter Notes

Test for specific value:

1 <?php
2 if ($results->contains('foo')) {
3     // found!
4 }

Presenter Notes

Prioritized Events

Presenter Notes

Why?

Sometimes events need to execute in a given order.

Presenter Notes

Examples: ACLs, caching

How?

  • Optional final argument to attach() (even statically)
  • Default priority is 1
  • Higher values == higher priority
  • Negative values == lower priority

Presenter Notes

Examples

1 <?php
2 // High priority:
3 $events->attach('foo', $listener, 100);
4 
5 // Low priority:
6 $events->attach('foo', $listener, -100);

Presenter Notes

Putting it Together

Presenter Notes

Our target

 1 <?php
 2 public function someExpensiveCall($criteria1, $criteria2)
 3 {
 4     $params  = compact('criteria1', 'criteria2');
 5     $results = $this->events()->trigger(
 6         __FUNCTION__ . '.pre', $this, $params, function ($r) {
 7         return ($r instanceof SomeResultClass);
 8     });
 9     if ($results->stopped()) {
10         return $results->last();
11     }
12 
13     // ... do some work ...
14 
15     /* $params['__RESULT__'] = $calculatedResult; */
16     $this->events()->trigger(
17         __FUNCTION__ . '.post', $this, $params);
18     return $calculatedResult;
19 }

Presenter Notes

Caching: check for match

1 <?php
2 $events->attach('someExpensiveCall.pre', function($e) use ($cache) {
3     $params = $e->getParams();
4     $key    = md5(json_encode($params));
5     if (false !== ($hit = $cache->load($key)) {
6         return $hit;
7     }
8 }, 100);

Presenter Notes

Caching: cache a value

1 <?php
2 $events->attach('someExpensiveCall.post', function($e) use ($cache) {
3     $params = $e->getParams();
4     $result = $params['__RESULT__'];
5     unset($params['__RESULT__']);
6     $key    = md5(json_encode($params));
7     $cache->save($result, $key);
8 }, -100);

Presenter Notes

Summary

Use the EventManager. It's powerful. It's the basis of our Application architecture.

Presenter Notes

MVC

Presenter Notes

I'm going to show you the MVC from the inside out, using a flow diagram to detail how it works.

In a nutshell...

ZF2 MVC flow

Presenter Notes

From the inside out

  • Dispatchable interface
  • Request and Response objects
  • View Models
  • Routing
  • Route types
  • Tree routes
  • Modules
  • Bootstrapping

Presenter Notes

Controllers and HTTP

Presenter Notes

  • We'll now detail the dispatchable interface, basic controllers, the request/response/header objects, and view models

Zend\Stdlib\Dispatchable

1 <?php
2 namespace Zend\Stdlib;
3 
4 interface Dispatchable
5 {
6     public function dispatch(
7         RequestDescription $request, 
8         ResponseDescription $response = null);
9 }

Presenter Notes

It's so simple and obvious. It can be used by servers to accept requests and return responses, or by clients, to send requests, and parse responses.

Use Cases for Dispatchables

  • Strategy/Command patterns
  • Server classes
  • HTTP clients
  • MVC applications

Presenter Notes

One thing interesting about the MVC is that because the same base is used for server classes and general Strategies or Commands, these, too, can be attached to the MVC directly, with little or no extra overhead.

Most basic controller

 1 <?php
 2 namespace Foo\Controller;
 3 
 4 use Zend\Stdlib\Dispatchable,
 5     Zend\Stdlib\RequestDescription as Request,
 6     Zend\Stdlib\ResponseDescription as Response;
 7 
 8 class MyController implements Dispatchable
 9 {
10     public function dispatch(
11         Request $request, Response $response = null
12     ) {
13         // do something and return a Response object
14     }
15 }

Presenter Notes

Typical Controller

 1 <?php
 2 namespace Foo\Controller;
 3 
 4 use Zend\Mvc\Controller\ActionController;
 5 
 6 class HelloController extends ActionController
 7 {
 8     public function worldAction()
 9     {
10         $routeMatch = $this->getEvent()->getRouteMatch()
11         $post       = $this->request->post();
12         return array(
13             'target'  => $routeMatch->getParam('target'),
14             'message' => $post->get('message', 'Nobody'),
15         );
16     }
17 }

Presenter Notes

  • Looks a lot like ZF1
  • Has an explicit return -- easier to test
  • Inject dependencies via setters or constructor params
  • We also have a "RestfulController"

Proposed View Layer

 1 <?php
 2 namespace Foo\Controller;
 3 
 4 use Zend\Mvc\Controller\ActionController,
 5     Zend\View\Model\ViewModel;
 6 
 7 class HelloController extends ActionController
 8 {
 9     public function worldAction()
10     {
11         $routeMatch = $this->getEvent()->getRouteMatch()
12         $post       = $this->request->post();
13         return new ViewModel(array(
14             'target'  => $routeMatch->getParam('target'),
15             'message' => $post->get('message', 'Nobody'),
16         ), array(
17             'template' => 'foo/hello',
18         );
19     }
20 }

Presenter Notes

  • Returning an explicit View Model

Request Object

1 <?php
2 $request->query();   // $_GET
3 $request->post();    // $_POST
4 $request->env();     // $_ENV
5 $request->server();  // $_SERVER
6 $request->file();    // $_FILES
7 $request->cookie();  // $_COOKIE
8 $request->uri();     // URI object
9 $request->headers(); // headers

Presenter Notes

  • Has test methods for request method
  • Everything is mockable

Response Object

1 <?php
2 $response->setStatusCode($code); // Status
3 $response->headers();            // headers
4 $response->setContent($content); // body

Presenter Notes

  • Allows easy manipulation of all aspects of response

Headers

1 <?php
2 $headers->addHeaderLine($header, $value); // set
3 $headers->has($header);                   // test
4 $headers->get($header);                   // get
5 $header->getFieldValue();                 // get value

Presenter Notes

  • Many header types have specific APIs -- e.g., Accept header

View Models

  • Aggregate variables to expose
  • Aggregate renderer options

Presenter Notes

View Model: Example

1 <?php
2 $model = new ViewModel();
3 $model->setVariable($name, $value);
4 $model->setVariables($array);
5 $model->getVariables();
6 $model->setOption($name, $value);
7 $model->setOptions($array);
8 $model->getOptions();

Presenter Notes

Context-Specific View Models

  • JsonViewModel
  • FeedViewModel

Presenter Notes

  • These will typically not do much, but they will allow you to typehint and develop rendering strategies.

Routing Requests

Presenter Notes

Routing

The act of matching a Request

to a Controller.

Presenter Notes

Types of routes

  • Literal: "/contact"
  • Segment: "/article/:id"
  • Regex: "/tag/(?<tag>[^/]+)"
  • Part/TreeRouteStack: tree of routes
  • Wildcard: "/*"
  • Hostname
  • Scheme

Presenter Notes

TreeRouteStack Example

/blog -- Literal 
    .xml -- Literal ("/blog.xml")
    /(?<id>[^/]+) -- Regex ("/blog/foo")
    /tag/(?<tag>[^/.]+) -- Regex ("/blog/tag/foo")
        .xml -- Literal ("/blog/tag/foo.xml")
    /year/:year -- Segment ("/blog/year/2011")
    /month/:year/:month -- Segment ("/blog/month/2011/12")

Presenter Notes

  • Any given route can indicate if it can terminate, or if additional segments are required
  • Any given route may have child routes
  • Each route gets full configuration for its type

Routing

A matched route MUST return a controller

1 <?php
2 use Zend\Mvc\Router\Http\Regex as RegexRoute;
3 $route = new RegexRoute(
4     '/blog/(?<id>[/.]+)', 
5     '/blog/%id%', 
6     array(
7         'controller' => 'Blog\Controller\EntryController'
8     )
9 );

Presenter Notes

  • Controller is what the Application is trying to dispatch
  • Can be the class name (recommended) or an alias
  • Anything else returned is up to the developer -- "action" is not necessary, as your controller defines what to do based on the Request and RouteMatch

A note on RouteMatch-es

Routes return a RouteMatch object on success

1 <?php
2 namespace Zend\Mvc\Router;
3 
4 class RouteMatch
5 {
6     public function getMatchedRouteName();
7     public function getParams();
8     public function getParam($name, $default = null);
9 }

Presenter Notes

  • This allows you to pull out anything matched by the route.
  • Routes part of a TreeRoute will contain all matched parameters.
  • Note that tree routes are designated using "/" separator between segments

Modular MVC Applications

Presenter Notes

What provides us with controllers and routes?

The answer is: modules

What is a module?

Presenter Notes

  • Problem with ZF1: no re-use and sharing, due to architecture
  • ZF2's goal: share and collaborate
  • A module is a complete solution to a discrete problem or set of related problems

Modules in ZF2

  • A namespace,
  • containing a single classfile, a Module

Presenter Notes

  • Structure is left to the developer, though we have a recommended structure
  • We have a few interfaces you can implement that hint to the module manager how a module can be used

Modules can contain...

  • PHP code, including MVC code
  • Assets, such as CSS, JS, and images
  • Unit tests
  • Documentation
  • Anything

Presenter Notes

I've created a module that simply provided SkeletonCSS in a way that I could easily drop it into a project. I have modules that are basically libraries, and contain source code, unit tests, and documentation. The point is, they can provide just about anything, because the only requirement is the Module class as an entry point.

At the most basic...

module/
    Foo/
        Module.php

Presenter Notes

The module class...

Which contains:

1 <?php
2 namespace Foo;
3 
4 class Module { }

Presenter Notes

  • That's really it. It's a single entry point, and completely opt-in.

Recommended module structure

ModuleName/
    autoload_classmap.php
    Module.php
    config/
        module.config.php
    public/
        css/
        images/
        js/
    src/
        ModuleName/
            ... code ...
    test/
        ModuleName/
            ... code ...
    view/
        ... templates, etc. ...

Presenter Notes

  • I mentioned before they can contain anything. We have a recommended structure to answer the question of "where do I put X, Y, or Z?"
  • Discuss some of the reasons behind the decisions

Modules typically provide...

  • Autoloading artifacts
  • Basic configuration
  • Event listener registration

Presenter Notes

Commonly, we will provide DI and routing configuration.

Providing autoloading hints

 1 <?php
 2 namespace Foo;
 3 use Zend\Module\Consumer\AutoloaderProvider;
 4 class Module implements AutoloaderProvider
 5 {
 6     public function getAutoloaderConfig()
 7     {
 8       return array(
 9         'Zend\Loader\ClassMapAutoloader' => array(
10           include __DIR__ . '/autoload_classmap.php',
11         ),
12         'Zend\Loader\StandardAutoloader' => array(
13           'namespaces' => array(
14             'Foo' => __DIR__ . '/src/Foo',
15           ),
16         ),
17       );
18     }
19 }

Presenter Notes

  • Basically, returning a configuration array to pass to the AutoloaderFactory. This ensures re-use of the same autoloader objects, but also means that the AutoloaderListener will be able to cache this configuration for later use.

Providing configuration

 1 <?php
 2 namespace Foo;
 3 
 4 class Module
 5 {
 6     public function getConfig()
 7     {
 8         return include __DIR__ 
 9                 . '/config/module.config.php';
10     }
11 }

Presenter Notes

  • The config listener merges this configuration with the config it stores internally.

Example module configuration

1 <?php
2 return array(
3     'routes' => array( /* ... */ ),
4     'di'     => array( /* ... */ ),
5 );

Presenter Notes

  • Note lack of environments; we do envs via glob merging

Event registration (post-init tasks)

 1 <?php
 2 namespace Foo;
 3 use Zend\EventManager\StaticEventManager,
 4     Zend\Module\Manager as ModuleManager;
 5 class Module
 6 {
 7     public function init(ModuleManager $manager)
 8     {
 9         $events = StaticEventManager::getInstance();
10         $events->attach(
11             'bootstrap', 'bootstrap', 
12             array($this, 'bootstrap')
13         );
14     }
15 
16     public function bootstrap($e)
17     {
18         $app    = $e->getParam('application');
19         // do some stuff...
20     }
21 }

Presenter Notes

  • a special "InitTrigger" listener will call init() if it exists
  • this is a good place to register events
  • shows execution order: module manager aggregates items and triggers events; we then execute the bootstrap, which triggers other events; and then we run the application
  • Event at bootstrap knows about the application, config, and module manager

Bootstrapping and Application Running

Presenter Notes

We've reached the skin of the onion finally -- we'll now put it together.

Bootstrapping

  • An Application:
  • composes configuration, a router, a locator, and an event manager.
  • triggers events (route, dispatch, and finish).
  • Modules provide:
  • configuration, which includes routes and locator information
  • event listeners.
  • Bootstrapping
  • aggregates information from modules
  • provides aggregated information to the application

Presenter Notes

The second point is incredibly important. It means that if you want to provide alternate strategies for routing or dispatch, all you need to do is replace the listeners on those events.

Putting it together: index.php

 1 <?php
 2 $appConfig = include __DIR__ . '/../config/application.config.php';
 3 
 4 $moduleLoader = new ModuleAutoloader($appConfig['module_paths']);
 5 $moduleLoader->register();
 6 
 7 $moduleManager = new Manager($appConfig['modules']);
 8 $moduleManager
 9     ->getConfigListener()
10     ->addConfigGlobPath(__DIR__ . '/config/autoload/*.{global,local}.config.php');
11 $moduleManager->loadModules();
12 $config = $moduleManager->getMergedConfig();
13 
14 // Create application, bootstrap, and run
15 $bootstrap   = new Bootstrap($config);
16 $application = new Application;
17 $bootstrap->bootstrap($application);
18 $application->run()->send();

Presenter Notes

  • Loops through modules, grabbing configuration
  • Merges in local configuration (addConfigGlobPath())
  • Bootstraps from configuration
  • Executes application

What about the V, View?

When is view rendering happening? via an Event Listener (as of beta2):

 1 <?php
 2 use Zend\EventManager\EventCollection as Events,
 3     Zend\EventManager\ListenerAggregate;
 4 
 5 class ViewListener implements ListenerAggregate
 6 {
 7     /* ... */
 8 
 9     public function attach(Events $events)
10     {
11         $events->attach('dispatch', 
12             array($this, 'renderView', -100);
13         $events->attach('dispatch', 
14             array($this, 'renderLayout', -1000);
15     }
16 
17     /* ... */
18 }

Presenter Notes

NOTE: Update this to be a preview of the new View system Low priority listeners, layout happening magnitudes later than view We'll be formalizing this later * Discuss the concept of a "ViewResult" type of object

Getting Your Hands Dirty: Contact!

Presenter Notes

The idea now is to take the information we've got, and build a new module for the skeleton app I've handed around. We'll configure a mail transport and mail message, pass them into a controller, and do something with it all.

The goal:

  • Display a contact form
  • Validate the contact form, and re-display if necessary
  • Send an email via a configured mail transport, and display a "thank you" page

Presenter Notes

To begin:

1 cd ZendSkeletonApplication
2 mkdir module/PhpbnlContact
3 cd module/PhpbnlContact
4 mkdir -p config src/PhpbnlContact view/contact

Presenter Notes

This sets up the basic structure we need for the files we'll be creating.

Routes: Base route

 1 <?php
 2 // module/PhpbnlContact/config/module.config.php
 3 return array(
 4   'routes' => array(
 5     'contact' => array(
 6       'type' => 'Literal',
 7       'options' => array(
 8         'route' => '/contact',
 9         'defaults' => array(
10           'controller' => 'contact',
11           'action'     => 'index',
12         ),
13       ),
14       'may_terminate' => true,
15       'child_routes' => array(/* ... */),
16     ),
17   ),
18 );

Presenter Notes

Routes: Process form

 1 <?php
 2 /* this is a child route */
 3 'process' => array(
 4   'type' => 'Literal',
 5   'options' => array(
 6     'route' => '/process',
 7     'defaults' => array(
 8       'controller' => 'contact',
 9       'action'     => 'process',
10     ),
11   ),
12 ),

Presenter Notes

Routes: Thank you page

 1 <?php
 2 /* this is a child route */
 3 'thank-you' => array(
 4   'type' => 'Literal',
 5   'options' => array(
 6     'route' => '/thank-you',
 7     'defaults' => array(
 8       'controller' => 'contact',
 9       'action'     => 'thank-you',
10     ),
11   ),
12 ),

Presenter Notes

Defining the form

  • Email
  • Subject
  • Body
  • Captcha
  • CSRF-protection
  • Submit button

Presenter Notes

Contact Form

 1 <?php
 2 // module/PhpbnlContact/src/PhpbnlContact/ContactForm.php
 3 namespace PhpbnlContact;
 4 
 5 use Zend\Captcha\Adapter as CaptchaAdapter,
 6     Zend\Form\Form,
 7     Zend\Validator\Hostname as HostnameValidator;
 8 
 9 class ContactForm extends Form
10 {
11     protected $captchaAdapter;
12 
13     public function __construct($captchaAdapter = null)
14     {
15         if ($options instanceof CaptchaAdapter) {
16             $this->setCaptchaAdapter($captchaAdapter);
17             parent::__construct(null);
18             return;
19         };
20 
21         parent::__construct($captchaAdapterj);
22     }
23     /\* ... \*/

Presenter Notes

Contact Form

 1 <?php
 2 namespace PhpbnlContact;
 3 
 4 use Zend\Captcha\Adapter as CaptchaAdapter,
 5     Zend\Form\Form,
 6     Zend\Validator\Hostname as HostnameValidator;
 7 
 8 class ContactForm extends Form
 9 {
10     /\* ... \*/
11     public function setCaptchaAdapter(CaptchaAdapter $captcha)
12     {
13         $this->captchaAdapter = $captcha;
14     }
15     /\* ... \*/

Presenter Notes

Contact Form

 1 <?php
 2 namespace PhpbnlContact;
 3 
 4 use Zend\Captcha\ReCaptcha,
 5     Zend\Form\Form,
 6     Zend\Validator\Hostname as HostnameValidator;
 7 
 8 class ContactForm extends Form
 9 {
10     public function init()
11     {
12         /\* setup all elements \*/
13         $this->addElement('captcha', 'captcha', array(
14             'required'       => true,
15             'captcha'        => $this->captchaAdapter,
16         ));
17         /\* finish setting up \*/
18     }

Presenter Notes

Controller: Dependencies

 1 <?php
 2 namespace PhpbnlContact;
 3 
 4 use Zend\Mail\Transport,
 5     Zend\Mail\Message as Message,
 6     Zend\Mvc\Controller\ActionController;
 7 
 8 class ContactController extends ActionController
 9 {
10     protected $form;
11     protected $message;
12     protected $transport;
13 
14     public function setMessage(Message $message)
15     {
16         $this->message = $message;
17     }
18 
19     public function setMailTransport(Transport $transport)
20     {
21         $this->transport = $transport;
22     }
23 
24     public function setContactForm(ContactForm $form)
25     {
26         $this->form = $form;
27     }

Presenter Notes

Controller: Display form

1 <?php
2 public function indexAction()
3 {
4     return array('form' => $this->form);
5 }

Presenter Notes

Controller: Process form

 1 <?php
 2 public function processAction()
 3 {
 4     if (!$this->request->isPost()) {
 5         $this->response->setStatusCode(302);
 6         $this->response->headers()
 7              ->addHeaderLine('Location', '/contact');
 8     }
 9     $post = $this->request->post()->toArray();
10     $form = $this->form;
11     if (!$form->isValid($post)) {
12         $this->getEvent()->getRouteMatch()
13                          ->setParam('action', 'index');
14         return array(
15             'error' => true,
16             'form'  => $form
17         );
18     }
19 
20     // send email...
21     $this->sendEmail($form->getValues());
22 
23     return $this->redirect()->toRoute('contact/thank-you');
24 }

Presenter Notes

Controller: Send email

 1 <?php
 2 protected function sendEmail(array $data)
 3 {
 4     $from    = $data['from'];
 5     $subject = '[Contact Form] ' . $data['subject'];
 6     $body    = $data['body'];
 7 
 8     $this->message->addFrom($from)
 9                   ->addReplyTo($from)
10                   ->setSubject($subject)
11                   ->setBody($body);
12     $this->transport->send($this->message);
13 }

Presenter Notes

Controller: Thank you page

 1 <?php
 2 public function thankYouAction()
 3 {
 4     $headers = $this->request->headers();
 5     if (!$headers->has('Referer')
 6         || !preg_match('#/contact/process$#',
 7               $headers->get('Referer')->getFieldValue())
 8     ) {
 9         $this->response->setStatusCode(302);
10         $this->response->headers()
11              ->addHeaderLine('Location', '/contact');
12     }
13 
14     // do nothing...
15 }

Presenter Notes

Add view scripts: Form

 1 <?php
 2 // module/PhpbnlContact/view/contact/index.phtml
 3 $this->headTitle()->prepend('Contact'); ?>
 4 <section class="contact">
 5 <h2>Contact Me</h2>
 6 
 7 <?php if ($this->error): ?>
 8 <p class="error">
 9     There were one or more isues with your submission. Please correct them
10     as indicated below.
11 </p>
12 <?php endif ?>
13 
14 <?php 
15 $form = $this->form;
16 $form->setAction($this->url('contact/process'));
17 $form->setMethod('post');
18 echo $form->render($this);

Presenter Notes

Add view scripts: Thank You

 1 <?php
 2 // module/PhpbnlContact/view/contact/thank-you.phtml
 3 $this->headTitle()->prepend('Contact');
 4 $this->headTitle()->prepend('Thank You!');
 5 ?>
 6 <section class="contact">
 7 <h2>Thank you!</h2>
 8 
 9 <p>
10     Your message has been sent!
11 </p>
12 </section>

Presenter Notes

DI Definitions: Controller/Form

 1 <?php
 2 /* inside module/PhpbnlContact/config/module.config.php */
 3 'di' => array(
 4     'definition' => array('class' => array(
 5         'PhpbnlContact\ContactController' => array(
 6             'setContactForm' => array(
 7                 'form' => array(
 8                     'required' => true,
 9                     'type'     => 'PhpbnlContact\ContactForm',
10                 ),
11             ),
12         ),
13         'PhpbnlContact\ContactForm' => array(
14             '__construct' => array(
15                 'captchaAdapter' => array(
16                     'required' => true,
17                     'type'     => 'Zend\Captcha\Adapter',
18                 ),
19             ),
20         ),
21     )),
22 ),

Presenter Notes

DI Definitions: Message

 1 <?php
 2 /* inside module/PhpbnlContact/config/module.config.php,
 3    in the di -> definition array */
 4 'Zend\Mail\Message' => array(
 5     'addTo' => array(
 6         'emailOrAddressList' => array(
 7             'type' => false, 'required' => true),
 8         'name' => array('type' => false, 'required' => false),
 9     ),
10     'addFrom' => array(
11         'emailOrAddressList' => array(
12             'type' => false, 'required' => true),
13         'name' => array('type' => false, 'required' => false),
14     ),
15     'setSender' => array(
16         'emailOrAddressList' => array(
17             'type' => false, 'required' => true),
18         'name' => array('type' => false, 'required' => false),
19     ),
20 ),

Presenter Notes

DI Preferences and aliases

 1 <?php
 2 /* inside module/PhpbnlContact/config/module.config.php,
 3    in the "di" array */
 4   'preferences' => array(
 5     'Zend\Captcha\Adapter' => 'Zend\Captcha\Dumb',
 6     'Zend\Mail\Transport'  => 'Zend\Mail\Transport\Smtp',
 7   ),
 8   'instance' => array(
 9     'alias' => array(
10         'contact' => 'PhpbnlContact\ContactController',
11     ),
12   ),
13 );

Presenter Notes

  • We're telling DI that when a Transport is requested, use an SMTP transport
  • We're aliasing "contact" to the contact controller

DI Configuration: Controller/Form

1 <?php
2 /* inside module/PhpbnlContact/config/module.config.php,
3    in the "di -> instance" array */
4 'contact' => array('parameters' => array(
5     'form' => 'PhpbnlContact\ContactForm',
6 )),

Presenter Notes

  • Alias is used for controller name
  • If the controller is pulled, so are its dependencies.
  • We tell it the preferred Transport
  • and how to configure it

DI Configuration: View scripts

 1 <?php
 2 return array('di' => array(
 3 /* inside module/PhpbnlContact/config/module.config.php,
 4    in the "di -> instance" array */
 5 'Zend\View\PhpRenderer' => array('parameters' => array(
 6     'options' => array(
 7         'script_paths' => array(
 8             'contact' => __DIR__ . '/../view',
 9         ),
10     ),
11 )),

Presenter Notes

Create an autoloader map

1 cd module/PhpbnlContact
2 php ../../vendor/ZendFramework/bin/classmap_generator.php \
3 -l ./src \
4 -o ./autoload_classmap.php

Presenter Notes

Creates "autoload_classmap.php" file in module root

Module class: autoloading

 1 <?php
 2 // module/PhpbnlContact/Module.php
 3 namespace PhpbnlContact;
 4 
 5 use Zend\Module\Consumer\AutoloaderProvider;
 6 
 7 class Module implements AutoloaderProvider
 8 {
 9   public function getAutoloaderConfig()
10   {
11     return array(
12       'Zend\Loader\ClassMapAutoloader' => array(
13         __DIR__ . '/autoload_classmap.php'
14       ),
15       'Zend\Loader\StandardAutoloader' => array(
16         'namespaces' => array(
17           'PhpbnlContact' => __DIR__ . '/src/PhpbnlContact',
18         ),
19       ),
20     );
21   }
22   /* ... */

Presenter Notes

Module class: configuration

 1 <?php
 2 namespace PhpbnlContact;
 3 
 4 use Zend\Module\Consumer\AutoloaderProvider;
 5 
 6 class Module implements AutoloaderProvider
 7 {
 8   /* ... */
 9 
10   public function getConfig()
11   {
12     return include __DIR__ . '/config/module.config.php';
13   }
14 }

Presenter Notes

Add application configuration

 1 <?php
 2 // config/autoload/module.phpbnl-contact.config.php
 3 return array('di' => array(
 4   'preferences' => array(
 5     'Zend\Mail\Transport' => 'Zend\Mail\Transport\File',
 6   )
 7   'instance' => array(
 8     'contact' => array('parameters' => array(
 9       'transport' => 'Zend\Mail\Transport\File',
10     ))
11     'PhpbnlContact\ContactForm' => array('parameters' => array(
12       'captchaAdapter'  => 'Zend\Captcha\Dumb',
13     ),
14     'Zend\Mail\Transport\FileOptions' => array('parameters' => array(
15       'path' => sys_get_temp_dir(),
16     )),
17     'Zend\Mail\Message' => array('parameters' => array(
18       'Zend\Mail\Message::addTo:emailOrAddressList' => '[email protected]',
19       'Zend\Mail\Message::setSender:emailOrAddressList' => '[email protected]',
20     )),
21   )),
22 );

Presenter Notes

Add the module to the app

1 <?php
2 // config/application.config.php
3 /* ... */
4 'modules' => array(
5     'Application',
6     'PhpbnlContact', // <- add this
7 ),
8 'module_listener_options' => array( /* ... */)
9 /* ... */

Presenter Notes

Visit the page

http://phpbnl.dev/contact

Contact Form

Presenter Notes

Review

  • We created routes
  • We created a form (kind of!)
  • We created a controller, with end points for landing, processing, and displaying a thank you page
  • We created the necessary views
  • We setup DI configuration for our form
  • We setup DI configuration for the mail transport and message
  • We created an autoloader classmap
  • We created a Module class for our module
  • We added the module to the application

Presenter Notes

Wrapping Up

Presenter Notes

When?

  • Beta 1 and Beta 2:
    • http://packages.zendframework.com/
  • Beta 3:
    • End of February
  • New Betas around every six weeks, until we're done.
  • Stable:
    • ? (likely Summer 2012)

Presenter Notes

  • We're doing gmail style betas. We want you to play with the code. We think it's compelling, and going to change the way you develop applications.
  • This also means that our betas are going to introduce changes. We'll work hard to minimize them, but be prepared. But build with it!
  • Beta3 is focussing on the view layer, DB, and console tooling

Resources

  • Bundle o' links!
    • http://bit.ly/rzOOge
  • The ZF2 subsite
    • http://framework.zend.com/zf2
  • My "mwop.net" site repository
    • http://git.mwop.net/?a=summary&p=zf2sandbox

Presenter Notes

Helping out

  • http://framework.zend.com/zf2
  • https://github.com/zendframework
  • Bi-weekly IRC meetings (#zf2-meeting on Freenode IRC)
  • #zftalk.2 on Freenode IRC

Presenter Notes

  • Read our Git guide, NO CLA REQUIRED, blah, blah, blah

Thank You!

  • Feedback?
    • http://framework.zend.com/
    • http://twitter.com/weierophinney

Presenter Notes