zend framework 2

André Roaldseth - @androa

Basics

PHP 5.3+
Namespaced
Event driven
Supports Composer

Key features/Buzzwords

Modules
Service Managers
Event Managers

Dependency Injection!

Everything is a module!



module, noun

A portion of a program that carries out a specific function and may be used alone or combined with other modules of the same program.

What can a Module be?

Anything.
Everything.

One requirement

Module.php

Example Modules


Error handling/logging
User authentication
API authentication
Caching
Database access
Routing
Image uploader

All of the above combined.

All that freedom!

Blessing and a curse.

Module Features

BootstrapListenerInterface
ConfigProviderInterface
ControllerPluginProviderInterface
ControllerProviderInterface
InitProviderInterface
ServiceProviderInterface
ViewHelperProviderInterface

Bootstrap Listener

interface BootstrapListenerInterface
{
    /**
     * Listen to the bootstrap event
     *
     * @param EventInterface $e
     * @return array
     */
    public function onBootstrap(EventInterface $e);
}

Config Provider

interface ConfigProviderInterface
{
    /**
     * Returns configuration to merge with application configuration
     *
     * @return array|\Traversable
     */
    public function getConfig();
}

Controller Plugin Provider

interface ControllerPluginProviderInterface
{
    /**
     * Expected to return \Zend\ServiceManager\Config object or array 
     * to seed such an object.
     *
     * @return array|\Zend\ServiceManager\Config
     */
    public function getControllerPluginConfig();
}

Controller Provider

interface ControllerProviderInterface
{
    /**
     * Expected to return \Zend\ServiceManager\Config object or array
     * to seed such an object.
     *
     * @return array|\Zend\ServiceManager\Config
     */
    public function getControllerConfig();
}

Init Provider

interface InitProviderInterface
{
    /**
     * Initialize workflow
     *
     * @param  ModuleManagerInterface $manager
     * @return void
     */
    public function init(ModuleManagerInterface $manager);
}

Service Provider

interface ServiceProviderInterface
{
    /**
     * Expected to return \Zend\ServiceManager\Config object or array
     * to seed such an object.
     *
     * @return array|\Zend\ServiceManager\Config
     */
    public function getServiceConfig();
}

View Helper Provider

interface ViewHelperProviderInterface
{
    /**
     * Expected to return \Zend\ServiceManager\Config object or array
     * to seed such an object.
     *
     * @return array|\Zend\ServiceManager\Config
     */
    public function getViewHelperConfig();
}

Services


Services, services, services.
And more services!

But first..

Dependency Injection!


DI explained


A dependent consumer.
Declaration of dependencies.
A dependency provider.


A Dependent Consumer

Piece of code that requires a different 
piece of code in order to work.

Let's say we are making Breakfast.
Then  we need some Eggs.
And definitely Bacon!

Declaration of Dependencies

The contract which tells what the consumer needs.

interface Breakfast {
    /**
     * Every Breakfast requires Bacon!
     *
     * @param Bacon $bacon ALL the bacon you have!
     */
    function __construct(Bacon $bacon);
    
    /**
     * But Eggs can be optional.
     *
     * @param Eggs $eggs Some eggs if you have :)
     */
    function setEggs(Egg $eggs);        
}

A dependency Provider

In ZF2, this is a Service Manager.

$bacon = $serviceManager->get('Bacon');
$eggs = $serviceManager->get('Eggs');

$breakfast = new Breakfast($bacon);

// Did we have any eggs?
if ($eggs) {
    $breakfast->setEggs($eggs);
}

Types of Dependency Injection


Constructor Injection (Bacon)
Hard dependency, 
we simply cannot have Breakfast without Bacon.

Setter Injection (Eggs)
Soft dependency, 
we can deal with a Breakfast without Eggs.

Property Injection
Not suited for PHP with ts weak types.
Don't use it. Seriously.

Why DI rocks?

Testable code without dependencies!
Change behaviour through composition!

Testable code Without Dependencies!

class Users {
    public function __construct() {
        $this->pdo = new PDO('mysql:..');
    }
    
    public function hasValidMembership($userId) {
        $sql = 'SELECT 1 FROM users WHERE userId = :userId';
    }
}

TESTABLE CODE WITHOUT DEPENDENCIES!

class Users {
    public function __construct($pdo = null) {
        $this->pdo = $pdo;
    }
    
    public function getUser($userId) {
        // Get the user from MySQL
        return new User($data);
    }
    
    public function hasValidMembership(User $user) {
        return $user->membershipEndsDate > time();
    }
}

Change Behavior through Composition!

class Users {
    public function hasValidMembership(
        User $user, 
        DateTime $now = null) {
        
        if ($now === null) {
            $now = new DateTime();
        }
        
        return $user->membershipEndsDate > $now->getTimestamp();
    }
}

Back To Our Service Manager


The Service Manager manages 
instances and the creation of objects.

Creation Methods


Abstract Factories
Factories
Invokables
Services
Initializers

Bonus features:
Aliases
Shared

INvokeables


The simplest type, assosiciative array with:
'service name' => FQCN to instantiate

Example:
'invokables' => array(
    'Bacon' => 'Breakfast\Refrigerator\Bacon',
    'Eggs' => 'Breakfast\Refrigerator\Eggs',
);

Great for simple classes 
without constructor dependencies.

Factories

Can be any type of PHP callable, 
even anonymous functions!

Example:
'breakfast' => function($serviceManager) {
    $bacon = $serviceManager->get('Bacon');
    $eggs = $serviceManager->get('Eggs');

    $breakfast = new Breakfast($bacon);
    $breakfast->setEggs($eggs);
    
    return $breakfast;
}
Or class implementing the 
Zend\ServiceManager\FactoryInterface

Initializers

A callable which is called after every object creation.

Enables lots of cool stuff like *AwareInterface.

'initializers' => array(
        function($instance, $sm) {
            if ($instance instanceof LoggerAwareInterface) {
                $instance->setLogger($sm->get('logger'));
            }
        }
    ),
    
Or class implementing the
Zend\ServiceManager\InitializerInterface

Services are shared!


Services are shared by default!

Instances created by a 
service manager is cached and reused.

Unless you set:
'shared' => array(
    'breakfast' => false
);

Application flow

Load all modules (Module.php)
Merge entire set of config
Run init() on all modules
Application::run()

There are some skipped steps.

Application::RUN()

Completely event driven

bootstrap
route
dispatch
dispatch.error
render
finish

Cheat Sheet

Code Structure

No requirements, everything is permitted.

Application

Zend recommend application structure:
.
├── config
│   └── application.config.php
├── modules
│   └── SomeModule
├── public
│   ├── css
│   ├── index.php
│   └── js
├── tests
│   └── phpunit.xml.dist
└── vendor

Module

Zend recommended module structure:
.
├── config
│   └── module.config.php
├── Module.php
├── src
│   └── SomeModule
│       └── Controller
│           └── SomeController.php
└── tests
    └── phpunit.xml.dist

Dependency handling with Composer

Composer is a small tool which 
install dependencies 
and 
handle version resolution.

Composer.json

{
    "name": "vgno/article-export",
    "description": "A CLI tool for exporting articles from Edrum.",
    "license": "MIT",
    "authors": [
        {
            "name": "André Roaldseth",
            "email": "andrer@vg.no"
        }
    ],
    "require": {
        "php": ">=5.3",
        "symfony/console": "2.3.*",
        "symfony/process": "2.3.*",
        "ezyang/htmlpurifier": "4.5.*"
    },
    "require-dev": {
        "phpunit/phpunit": "3.7.*"
    },
    "autoload": {
        "psr-0": {
            "ArticleExport": "src"
        }
    }
}

Cool stuff

Early Return in Controllers


class IndexController extends AbstactActionController {
    public function indexAction() {
        if (!$this->hasAccess()) {
            return $this->getResponse()->setStatusCode(401);
        }
        
        // Tons of more logic to do if user has access
        
        return new ViewModel();
    }
}

EARLY RETURN IN CONTROLLERS, Part II

class Module implements BootstrapInterface {
    public function onBootstrap(EventInterface $event) {
        $application    = $event->getTarget();
        $serviceManager = $application->getServiceManager();
    
        $application->getEventManager()
            ->attach(function (MvcEvent $event) use ($serviceManager) {
            $request  = $event->getRequest();
            $response = $event->getResponse();
    
            if ($serviceManager->get('AuthHandler')
                ->hasAccess($request)) {
                return; // Carry on
            }
           
            $response->setStatusCode(401);
            
            // Stop the rest application to run
            $event->setResult($response); 
                    
            return false; // Stop other event listeners
        }, MvcEvent::EVENT_DISPATCH);
    }
}

EARLY RETURN IN CONTROLLERS, PART III

This stops the application 
before the controller is loaded.

Can also be used for:

Signing URLs with tokens
Handling DB driven URL rewrites
Prepare the request object with data

Prepared Requests

class Module implements BootstrapInterface {
    public function onBootstrap(EventInterface $event) {
        // ... Same as previous slide
        ->attach(function (MvcEvent $event) use ($serviceManager) {
            $response = $event->getResponse();
            $routeMatch = $event->getRouteMatch();
    
            if ($routeMatch
                ->getMatchedRouteName() === 'articles') {
                
                $articleId = $routeMatch->getParam('articleId');
                $article = $serviceManager->get('ArticleLoader')
                    ->fetchArticle($articleId));
                    
                if (!$article) {
                    $event->setResult($reponse->setStatusCode(404));
                    return false;
                }
                
                $routeMatch->setParam('article', $article);
            }
        }, MvcEvent::EVENT_DISPATCH);
    }
}

Prepared Requests, Part II

class SomeArticleController extends AbstractActionController {
    public function articleAction() {
        $article = $this->getParam('article');
        
        // Do article stuff
        
        // Give the article to the view layer.
        return new ViewModel('article', $article);
    }
}

Content-negotation

class SomeController extends AbstractActionController {
    public function getAction() {
        $something = $this->getSomeServiceLayer()
            ->get($this->getParam('id'));
        
        // We always want to deliver this as JSON, 
        // so we make JsonModel instead of ViewModel.
        return new JsonModel($something);
    }
}

Content-negotation, Part II

class SomeController extends AbstractActionController {
    protected $acceptCriteria = array(
      'Zend\View\Model\JsonModel' => array(
            'application/json', // <- content-type, supports *
      ),
      // ...
    );
    
    public function getAction() {
        $something = $this->getSomeServiceLayer()
            ->get($this->getParam('id'));
        
        // We want to do content negotiation so we use the,
        // acceptableViewModelSelector which looks at the
        // HTTP Accept header and returns a correct ViewModel.
        $viewModel = $this->acceptableViewModelSelector(
            $this->acceptCriteria
        );
        
        $viewModel->setVariables($something);
        
        return $viewModel;
    }
}

Thanks!


Questions?

Introduction to Zend Framework 2

By André Roaldseth

Introduction to Zend Framework 2

A short introduction to Zend Framework 2 intended for my co-workers at VG.

  • 1,765