Top Features of

Zend Framework 2

Ilko Kacharov

Ilko Kacharov

Some bits about me

Started with web development in 2003

Started with PHP at 2006

Currently Lead Dev in Mailjet / Proxiad

@kachar136

kachar136@gmail.com

Started with programming in 2000

Not for every project

Not for every developer

Use what you need

Use at will

Zend Framework 2

Framework !== MVC

No MVC today

Why Zend Framework 2

Event Driven Architecture

Service Oriented Architecture

Library with interfaces and concrete implementations

Composition over inheritance principles

Enterprise level, not personal home page

Configurations

Entry script

<?php
/**
 * This makes our life easier when dealing with paths. 
 * Everything is relative to the application root now.
 */
chdir(dirname(__DIR__));

// Setup autoloading
require 'init_autoloader.php';

// Run the application!
Zend\Mvc\Application::init(require 'config/application.config.php')->run();

public/index.php

Current directory is the project root.

Everything is relative.

(to /project/, not /project/public/)

application.config.php

<?php
return [
    // This should be an array of module namespaces used in the application.
    'modules' => [
        'Application',
        'ThirdParty/Module',
        'MyModule',
    ],
    'module_listener_options' => [
        // This should be an array of paths in which modules reside.
        // If a string key is provided, the listener will consider that a module
        // namespace, the value of that key the specific path to that module's
        // Module class.
        'module_paths' => [
            './module',
            './vendor',
        ],
        // An array of paths from which to glob configuration files after
        // modules are loaded. These effectively override configuration
        // provided by modules themselves. Paths may use GLOB_BRACE notation.
        'config_glob_paths' => [
            'autoload' => __DIR__ . '/autoload/{,*.}{global,local}.php',
            'autoload-env' => __DIR__ . '/autoload/env/' . $env . '{,*.local}.php',
        ],
    ],
];

config.global.php

<?php

return [
    'db' => [
        'driver' => 'PdoMysql',
        'hostname' => 'localhost',
        'database' => '',
        'username' => '',
        'password' => '',
        'driver_options' => [
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'",
        ],
    ],
    'view_manager' => [
        'display_not_found_reason' => true,
        'display_exceptions' => false,
        'doctype' => 'HTML5',
        'not_found_template' => 'error/404',
        'exception_template' => 'error/index',
    ],
];
<?php

return [
    'view_manager' => [
        'display_exceptions' => true,
    ],
];

config.local.php

development.php

<?php

return [
    'db' => [
        'hostname' => 'dev-db-host',
        'database' => 'dev-db-name',
        'username' => 'dev-db-user',
        'password' => 'dev-db-password',
    ],
];

What's so cool about it?

You define the order configs are merged

You can define config per environment

Config file per module

You can cache all that and boost performance

Modules

Autoloader

Zend\Loader\StandardAutoloader is designed as a PSR-0-compliant autoloader.

 

It assumes a 1:1 mapping of the namespace+classname to the filesystem

 

Namespace separators and underscores are translated to directory separators

Structure of a Module

<?php
namespace MyModule;

use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $event)
    {
        $app = $event->getApplication();
        // Do cool stuff here
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return [
            'Zend\Loader\StandardAutoloader' => [
                'namespaces' => [
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ],
            ],
        ];
    }
}

What's so cool about it?

You can have the sub modules in separate repo

You can de-couple your application

Modules can have other modules as dependencies

You can overwrite the services of 3rd party modules

Routing

Router Types

Routing is the act(art) of matching 

a request to a given controller.

Two main router types:

Literal

Segment

Routing type Literal

<?php

return [
    'router' => [
        'routes' => [
            'home' => [ // This is the route alias
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => [
                    'route' => '/something', // This is the route
                    'defaults' => [
                        'controller' => 'Application\Controller\Index',
                        'action' => 'index',
                    ],
                ],
            ],
        ],
    ],
];

Routing type Segment

<?php
return [
    'router' => [
        'routes' => [
            'application' => [
                'type' => 'Literal',
                'options' => [
                    'route' => '/',
                    'defaults' => [
                        '__NAMESPACE__' => 'Application\Controller',
                    ],
                ],
                'may_terminate' => true,
                'child_routes' => [
                    'dashboard' => [
                        'type' => 'Segment',
                        'options' => [
                            // http://hostname.com/dashboard
                            // http://hostname.com/dashboard/stats
                            // http://hostname.com/dashboard/orders
                            'route' => 'dashboard[/:action]',
                            'constraints' => [
                                'action' => '[a-z0-9_-]*',
                            ],
                            'defaults' => [
                                'controller' => 'DashboardController',
                                'action' => 'stats',
                            ],
                        ],
                        // ...

Plugins: Params, Url, Redirect

// Using controller plugin `params`
public function testAction()
{
    $id = $this->params()->fromRoute('id');

    $params = $this->params()->fromRoute();
}


// Using controller plugin `redirect`
public function testAction()
{
    return $this->redirect()->toRoute('application/dashboard', [
        'action' => 'stats',
    ], [
        'query-param' => 'here',
    ]);
}

// Using controller plugin `url`
public function testAction()
{
    $url = $this->url('application/route-name', [
        'id'   => $this->params()->fromRoute('id'),
        'type' => 'read',
        'sort' => 'desc',
    ]);
}

What's so cool about it?

Named routes for dispatching and url building

Nested child routes

Validate input parameters

Exceptions when route is  not found or used with bad params

Pass extra information about a route

(ACL, Unauthorized redirects, Caching is allowed)

Service Locator

What is a Service?

A Service is an object that executes

complex application logic.

 

Part of the application that wires all difficult stuff together and gives you easy to understand results.

 

Services are created on demand and their instance is kept in the memory until needed.

Service Locator

The Service Locator is a manager

that finds and retrieves object instances - services.

Service Types

Invokable services

Factories

Abstract Factories

Shared

Aliases

Invokable services

<?php

return [
    'service_manager' => [
        'invokables' => [
            'SomeService'  => 'MyModule\Service\SomeService',
            'AuthListener' => 'MyModule\Listener\AuthListener',
        ],
    ],
];

No constructor dependencies.

Zend will automatically set ServiceLocator

if they implement the ServiceLocatorAwareInterface.

Zend will throw exception if config is not correct.

Factories

<?php
return [
    'service_manager' => [
        'factories' => [
            'OtherService' => 'User\Factory\OtherServiceFactory',
            'AuthListener' => function (ServiceLocatorInterface $sm) {
                $dbAdapter = $sm->get('db');
                return new AuthListener($dbAdapter);
            },
        ],
    ],
    'some_service_specific' => ['title' => 'Top ZF2 features'],
];
<?php
namespace User\Factory;

use User\Service\OtherService;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class OtherServiceFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $sm)
    {
        $dbAdapter = $sm->get('db');
        $config = $sm->get('Config')['some_service_specific'];
        $service = new OtherService($dbAdapter, $config);
        return $service;
    }
}

Abstract Factories

<?php
return [
    'service_manager' => [
        'abstract_factories' => [
            'SomeModule\Factory\CommonControllerAbstractFactory',
        ],
    ],
];
<?php

class CommonControllerAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName($serviceLocator, $name, $requestedName)
    {
        if (class_exists($requestedName . 'Controller')) {
            return true;
        }
        return false;
    }
    public function createServiceWithName($serviceLocator, $name, $requestedName)
    {
        $class = $requestedName . 'Controller';
        return new $class;
    }
}

Shared

Indicating whether or not a service should be shared.

 

By default all services are shared - one instance.

 

If you specify a boolean false value

 a new instance will be returned every time.

<?php
return [
    'service_manager' => [
        'shared' => [
            'OtherService' => false,
        ],
    ],
];

Aliases

<?php
return [
    'service_manager' => [
        'aliases' => [
            // Another alias
            'translator' => 'MvcTranslator',
            'db' => 'Zend\Db\Adapter\Adapter',
            // Namespace + Class
            'OtherService' => 'MyModule\Super\Long\Factory\Name\OtherService',
            // Override zend service
            'Zend\Authentication\AuthenticationService' => 'zfcuser_auth_service',
        ],
    ],
];

What's so cool about it?

Manages constructor injections

Easy for refactoring

Consistent way of handling dependencies

You can replace services from the framework with your own

Events and Event Manager

Events and Event Manager

An Event is a named action.

 

Listener is any valid PHP callback that reacts to an event.

 

An EventManager aggregates listeners for one or more named events and triggers events.

The MvcEvent

The MvcEvent gives access to the following:

 

  • Application object.

  • Request object.

  • Response object.

  • Router object.

  • RouteMatch object.

  • Result - the result of dispatching a controller.

  • ViewModel object, representing the layout.

The MvcEvent

Bootstrap the application, merge module configs..

Perform all the route work (matching...).

Dispatch the matched route to a controller/action.

Event triggered in case of a problem during dispatch

Prepare the data and delegate the rendering to the view.

Event triggered in case of a problem during the rendering

Perform any task once everything is done.

The MvcEvent

MvcEvent::EVENT_BOOTSTRAP

MvcEvent::EVENT_ROUTE

MvcEvent::EVENT_DISPATCH

MvcEvent::EVENT_DISPATCH_ERROR

MvcEvent::EVENT_RENDER

MvcEvent::EVENT_RENDER_ERROR

MvcEvent::EVENT_FINISH

class SomeListener implements ListenerAggregateInterface
{
    public function attach(EventManagerInterface $em)
    {
        $this->listeners[] = $em->attach(MvcEvent::EVENT_ROUTE, [$this, 'trackTheBegining'], 1000);
        $this->listeners[] = $em->attach(MvcEvent::EVENT_ROUTE, [$this, 'justAfterRouting'], 1);
        $this->listeners[] = $em->attach(MvcEvent::EVENT_ROUTE, [$this, 'trackTheEnd'], -1000);
        $this->listeners[] = $em->attach(MvcEvent::EVENT_DISPATCH, [$this, 'beforeController'], 100);
    }
    public function trackTheBegining(EventInterface $e)
    {
        // You can redirect here if something is wrong
        // You can load route from cache here
    }
    public function justAfterRouting(EventInterface $e)
    {
        // Routing has just been done
    }
    public function trackTheEnd(EventInterface $e)
    {
        // This runs before the end of routing cycle
        // You can store route in cache here
    }
    public function beforeTheController(EventInterface $e)
    {
        // This runs just before the controller action
    }
}

Attach MvcEvent Listeners

What's so cool about it?

You can exit the application cycle early

Every module can interfere with the whole application

Have many independent callbacks being called on the same event

Look and feel of PSR-7 Middleware

"Z" End

¿Questions?

https://slides.com/kachar/zf2-top-features

Slides at:

Thanks to:

@kachar136

kachar136@gmail.com

i.kacharov@proxiad.com

Made with Slides.com