Creating Re-usable Modules for Zend Framework 2

Presenter Notes

Who am I?

Matthew Weier O'Phinney

Matthew Weier O'Phinney

  • Project Lead, Zend Framework
  • PHP Developer
  • http://mwop.net/
  • @weierophinney

Presenter Notes

Goals

  • Learn the basics of ZF2
  • Learn what modules are, and how to use them
  • Build a module

Presenter Notes

  • In the process, we'll learn about a bunch of ZF2 functionality

MVC Quick Start

Presenter Notes

Three core concepts

  • Decoupled and DI-driven (ServiceManager, Di)
  • Event-driven (EventManager)
  • HTTP handlers (DispatchableInterface)

Presenter Notes

Decoupling, DI, and Services

Presenter Notes

The problem

How do controllers get their dependencies?

Presenter Notes

  • Core problem in ZF1 is there is no clean way to do this

Decoupling

  • Contract Oriented Design
  • Favor composition over inheritance
    • ... and thus dependency injection

Presenter Notes

  • Zend\Di is robust and well-tested
  • But we won't talk about it. We're focussing on the SM now

Service Manager

Presenter Notes

Zend\ServiceManager

  • Very fast -- no magic or discovery
  • Code -- don't configure -- your injections
  • Explicit wiring -- it's all code -- makes debugging easy

Presenter Notes

Types of services

  • Explicit (name => object pairs)
  • Invokables (name => class to instantiate)
  • Factories (name => callable returning object)
  • Aliases (name => some other name)
  • Abstract Factories (unknown services)
  • Scoped Containers (limit what can be created)
  • Shared (or not; you decide)

Presenter Notes

Example: service

1 <?php
2 array('services' => array(
3     'foo' => new Some\Component\Foo(),
4 ))

Presenter Notes

Example: invokable

1 <?php
2 array('invokables' => array(
3     'foo' => 'Some\Component\Foo',
4 ))

Presenter Notes

Example: factories

1 <?php
2 array('factories' => array(
3     'foo' => function ($services) {
4         // lazy-loading, essentially:
5         return new Some\Component\Foo();
6     },
7     'bar' => 'Some\Static::method',
8     'baz' => 'Some\Class\Implementing\FactoryInterface',
9 ))

Presenter Notes

  • Basically, any PHP callable can be used
  • Or implement the FactoryInterface, which makes things more explicit

Example: aliases

1 <?php
2 array('aliases' => array(
3     'my_foo' => 'foo',        // alias services
4     'foo_master' => 'my_foo', // alias aliases
5 ))

Presenter Notes

Example: abstract factories

 1 <?php
 2 array('abstract_factories' => array(
 3     'Class\Implementing\AbstractFactoryInterface',
 4 ))
 5 
 6 class SampleAbstractFactory implements AbstractFactoryInterface
 7 {
 8     public function canCreateServiceWithName($name) {/* */}
 9     public function createServiceWithName(
10         ServiceLocatorInterface $locator, $name
11     ) { /* */ }
12 }

Presenter Notes

  • Makes it relatively easy to create a "catch-all" factory to provide instances
  • Good as a factory for context-specific types

Example: (un)shared service

1 <?php
2 array('shared' => array(
3     'EventManager' => false, // default is true
4 ))

Presenter Notes

Configure services anywhere

  • Application configuration
  • Module classes
  • Module configuration
  • Local override configuration

Presenter Notes

Why?

  • MVC uses the ServiceManager to wire the default workflow and event listeners
  • Developers provide additional services and service configuration

Presenter Notes

  • controllers are services!

Event Manager

Presenter Notes

Zend\EventManager in a slide

  • Trigger events
  • Listen and react to triggered events

Presenter Notes

Terminology

Event Listeners

  • An Event Manager is an object that aggregates listeners for one or more named events, and which triggers events.
  • A Listener is a callback that can react to an event.
  • An Event is an action.

Presenter Notes

Example

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

Presenter Notes

Everything is an event

MVC Events

Presenter Notes

MVC Events

  • Loading modules: loadModules.pre, loadModule, loadModules.post
  • bootstrap
  • route
  • dispatch and dispatch.error
  • render
  • finish

Presenter Notes

Dispatchables == Controllers

Presenter Notes

What is a web application?

Presenter Notes

HTTP negotiation.

Presenter Notes

DispatchableInterface

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

Presenter Notes

  • Implication is that you return a response
  • Because we pass one in, can return arbitrary results

Shipped contollers

  • Zend\MvcController\ActionController
  • Zend\MvcController\RestfulController

Presenter Notes

  • Compose an EM; dispatch actually simply triggers an event
  • Compose the SM; you can lazily retrieve dependencies, but it's mostly there to assist with a variety of plugins
  • Compose a PluginBroker, Event, and more

Most importantly...

Controllers are services

Presenter Notes

  • This allows them to be injected by the SM or DIC
  • It means we're mainly interested in configuring the SM and EM

Configuring the MVC

Presenter Notes

  • We've talked about the core concepts
  • How do we wire it together?

Things that must happen

  • Configure services
  • Wire event listeners

Presenter Notes

Core services

  • Router
  • View
  • Controllers

Presenter Notes

How do we provide configuration?

Presenter Notes

Modules

Presenter Notes

What?

A module is all related code and assets that solve a specific problem.

Presenter Notes

  • Don't put unrelated code in the same module
  • Assets related to the code should be in the module

Modules in ZF2

  • Modules provide the MVC with configuration:
    • Autoloading
    • Routing
    • Controllers
  • Modules provide and attach listeners

Presenter Notes

How?

  • Zend\ModuleManager loops through modules, and passes Module objects to listeners.
  • Listeners look for features modules provide.

Presenter Notes

Common Module Features

  • Autoloading configuration
  • Service configuration
  • General configuration
  • Bootstrap listeners

Presenter Notes

  • Each "feature" has an accompanying interface
  • Listeners look for either interface implementation OR duck-typing

Module classes

  • A namespace...
  • ...containing a single classfile, Module.php...
  • ...containing a single class, Module.

Presenter Notes

When you write ZF2 MVC applications, you will write Modules.

Presenter Notes

Skeleton Application

Presenter Notes

  • The ZF2 community built this to make the job of starting simpler
  • Provide basic application structure and configuration

Getting it

1 cd my/project/dir
2 git clone \
3     git://github.com/zendframework/ZendSkeletonApplication.git
4 cd ZendSkeletonApplication
5 php composer.phar install

Presenter Notes

Let's look through it

(Live coding here...)

Presenter Notes

Module Demonstration

Presenter Notes

PhlyContact

  • Simple, configurable contact form functionality
    • Configure a mail transport
    • Configure the captcha used in the form
    • Potentially configure and change the route

Presenter Notes

Install

Via composer:

  • Edit composer.json to add a new requirement
  • php composer.phar install

Presenter Notes

  • Alternately, the attendees can drop the "PhlyContact" file into the vendor subdir

Configure

  • Copy the module's "config/module.phly-contact.local.php" to the application's "configautoload" directory, and edit
  • Edit "config/application.config.ini" to add the module

Presenter Notes

  • Live coding time

Try it out!

Presenter Notes

Other things we could do

  • Alter the base route
  • Alter the captcha
  • Change what controller ultimately wasy called
  • Change where view scripts resolve

Presenter Notes

  • Local configuration allows overriding in a variety of ways

Break time!

Presenter Notes

Project Walkthrough

Presenter Notes

PhlyPeep: Dead Simple Twitter Clone

"peep": Make a cheeping or beeping sound
  • Show a timeline of all "peeps"
  • Show a timeline of individual user's "peeps"
  • Show individual "peeps"
  • Allow authenticated users to "peep"

Presenter Notes

Demo time

Presenter Notes

Dependencies

  • We don't want to write authentication ourselves
    • We'll install the "ZfcUser" module to save some work

Presenter Notes

Installing ZfcUser

(live coding)

Presenter Notes

Create the module

Presenter Notes

simple steps:

  • Create a directory, "vendor/PhlyPeep"
  • Create a "Module" class in "vendor/PhlyPeep/Module.php"
  • Create "config", "data", "src/PhlyPeep", and "view" subdirectories

Presenter Notes

Module.php

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

Presenter Notes

First things first: Modelling

Presenter Notes

Model the peep and create a schema

  • identifier (hash of username + timestamp + tweet text)
  • username
  • user_email (for gravatar)
  • user_fullname (?)
  • timestamp
  • peep_text

Presenter Notes

Entity

 1 <?php
 2 namespace PhlyPeep\Model;
 3 
 4 use Zend\InputFilter\InputFilterAwareInterface;
 5 use Zend\InputFilter\InputFilterInterface;
 6 use Zend\Stdlib\ArraySerializableInterface;
 7 
 8 class PeepEntity implements 
 9     ArraySerializableInterface,
10     InputFilterAwareInterface
11 {
12     protected $filter;
13 
14     protected $identifier;
15     protected $username;
16     protected $email;
17     protected $displayName;
18     protected $timestamp;
19     protected $peepText;
20 }

Presenter Notes

Schema

 1 CREATE TABLE peep
 2 (
 3     identifier    CHARACTER(8) PRIMARY KEY NOT NULL,
 4     username      VARCHAR(255) NOT NULL,
 5     email         VARCHAR(255) NOT NULL,
 6     display_name  VARCHAR(50) DEFAULT NULL,
 7     timestamp     INTEGER NOT NULL,
 8     peep_text     TEXT NOT NULL
 9 );
10 
11 CREATE INDEX peep_username ON peep(username);

Presenter Notes

Validation

Presenter Notes

Validate and normalize

  • Identifiers should be 8 characters
  • Username should ...
  • Email should be valid
  • Timestamp should be an integer
  • We need at least some text

Presenter Notes

Zend\InputFilter

  • Define "inputs", which know how to normalize and validate themselves, and define "meta-behavior" (required, allow empty, etc.).
  • Define "input filters", which aggregate inputs and other input filters, and are capable of validating the entire or partial sets.
  • Perfect fit for models
  • Perfect fit for forms

Presenter Notes

Persistence

Presenter Notes

Zend\Db

  • Robust and flexible database abstraction layer (DBAL)
  • Offers query abstraction
  • Offers Table and Row Data Gateway implementations

Presenter Notes

Service Layer (and Pagination)

Presenter Notes

Service Layer?

  • Abstract domain model operations from persistence operations
  • We'll use it to return paginators instead of result sets
  • We'll use it to provide the minimum useful interface to the domain

Presenter Notes

Zend\Paginator

  • Paginate arbitrary data sets
  • Adapters need only implement getItems($offset, $numPerPage) and count()

Presenter Notes

Form

Presenter Notes

Zend\Form

  • Bridges the domain model and view
  • Elements, fieldsets, and forms
    • Each defines a name and attributes
    • Fieldsets aggregate elements and fieldsets
    • Forms aggregate input filters and can bind to objects

Presenter Notes

Routes and Controller

Presenter Notes

config/module.config.php

(live coding time)

Presenter Notes

PeepController.php

(live coding time)

Presenter Notes

View scripts and helpers

Presenter Notes

What we need

View Scripts:

  • "index" -- show the public timeline
  • "form-error" -- display peep submission errors
  • "username" -- display user timelines
  • "401" -- unauthorized (peeping without authorization)

Presenter Notes

What we need

View Helpers

  • "PeepText" -- escape a peep, but also link any URLs
  • "PeepForm" -- show a form if user authorized, link to login/register otherwise

Presenter Notes

Service Factories

Presenter Notes

What we need

  • Controller factory
    • inject authentication service
    • inject peep service
  • Peep service factory
    • inject peep table object
  • Peep table factory
    • inject database adapter
    • inject configuration

Presenter Notes

Configuration

Presenter Notes

What we need to configure

  • services
  • routes (already done)
  • controllers
  • view information

Presenter Notes

Define Dependencies

Presenter Notes

composer.json

 1 {
 2     "name": "phly/phly-peep",
 3     "description": "Rudimentary twitter clone",
 4     "type": "library",
 5     "keywords": ["zf2", "zend", "module"],
 6     "homepage": "https://github.com/weierophinney/PhlyPeep",
 7     "authors": [ /* ... */ ],
 8     "require": {
 9         "php": ">=5.3.3",
10         "zendframework/zendframework": "dev-master",
11         "zf-commons/zfc-user": "dev-master"
12     },
13     "autoload": {
14         "psr-0": {
15             "PhlyPeep": "src/"
16         },
17         "classmap": [
18             "./"
19         ]
20     }
21 }

Presenter Notes

Add it to your Application

Presenter Notes

config/application.config.php

 1 <?php
 2 return array(
 3     'modules' => array(
 4         'Application',
 5         'PhlyContact',
 6         'PhlyPeep',    // HERE IT IS!
 7         'ZfcBase',
 8         'ZfcUser',
 9     ),
10     /* ... */
11 );

Presenter Notes

Time to test!

Presenter Notes

Bonus tasks

  • Add caching to the timelines
  • Add search capabilities
  • Allow using a queue/job service to insert peeps
    • Offload caching, search index updating
  • Add Atom/RSS feeds
  • Allow peep retrieval via XMLHttpRequest

Presenter Notes

Takeaways

Presenter Notes

Module naming

Prefix modules with your vendor name

  • PhlyBlog, ZfcUser, OcraComposer, etc.

Presenter Notes

Use service aliases

Define aliases, routes, etc. using a normalized module prefix; point them to class names or interfaces by default. (phly-peep-service, zfcuser_db_adapter, etc.)

1 <?php
2 return array(
3     'service_manager' => array('aliases' => array(
4         'phly-peep-db-adapter' => 'Zend\Db\Adapter\Adapter',
5     ))
6 );

Presenter Notes

Enable helpers via the service manager

Helpers and plugins can be looked up via the service manager. Leverage that.

 1 <?php
 2 return array(
 3     'service_manager' => array(
 4         'invokables' => array(
 5             'peeptext' => 'PhlyPeep\View\PeepText',
 6         ),
 7         'factories' => array(
 8             'peepform' => 'PhlyPeep\Service\PeepViewFormFactory',
 9         ),
10     )
11 );

Presenter Notes

Routing

Define a single, top-level route for the module, with children beneath

 1 <?php
 2 return array('router' => array('routes' => array(
 3     'phly-peep' => array(
 4         'type' => 'Literal',
 5         /* ... */
 6         'may_terminate' => true, // or not
 7         'child_routes' => array(
 8             /* all other routes for this module */
 9         ), 
10     )
11 )));

Presenter Notes

Be explicit about dependencies

Define and delineate dependencies, preferably via a form of package metadata.

  • Make it easy to install your module; developers like easy
  • Document installation and post-install steps
  • Look for existing modules when possible, and extend from them or use their functionality
  • Composer and Pyrus are awesome!

Presenter Notes

Thank You!

  • Feedback?
    • https://joind.in/6221
    • http://framework.zend.com/zf2
    • http://twitter.com/weierophinney

Presenter Notes