Quick start with

Zend Framework 2

Ilko Kacharov

Installation

Requirements

PHP 5.3.3

Apache/Nginx

Git

Composer

cd my/projects/dir
git clone git://github.com/zendframework/ZendSkeletonApplication.git
cd ZendSkeletonApplication
php composer.phar self-update
php composer.phar install

Installation with Composer

Install Skeleton Application

{
    "name": "project-name",
    "description": "Project Description",
    "require": {
        "php": ">=5.3.3",
        "zendframework/zendframework": "~2.4"
    }
}
php composer.phar self-update
php composer.phar install

composer.json

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 array(
    // This should be an array of module namespaces used in the application.
    'modules' => array(
        'Application',
        'Example',
    ),
    'module_listener_options' => array(
        // 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' => array(
            './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' => array(
            'config/autoload/{,*.}{global,local}.php',
        ),
    ),
);

config.global.php

<?php

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

return array(
    'db' => array(
        'hostname'  => '111.222.333.444',
        'database'  => 'db-name',
        'username'  => 'db-user',
        'password'  => 'db-password',
    ),
    'view_manager' => array(
        'display_exceptions' => true,
    ),
);

config.local.php

Modules

Structure of Skeleton App

├───config
│   │   application.config.php
│   │
│   └───autoload
│           global.php
│           local.php
.......
├───public
│   │   .htaccess
│   │   index.php
│   │
│   ├───css
│   │       bootstrap-theme.css
│   │       bootstrap.css
│   │       style.css
│   │
│   ├───fonts
│   │       glyphicons-halflings.ttf
│   │
│   ├───img
│   │       favicon.ico
│   │       zf2-logo.png
│   │
│   └───js
│           bootstrap.min.js
│           jquery.min.js
│           respond.min.js
└───vendor
    │
    ├───bin
    │       classmap_generator.php
    │       classmap_generator.php.bat
    │       pluginmap_generator.php
    │       pluginmap_generator.php.bat
    │       templatemap_generator.php
    │       templatemap_generator.php.bat
    │
    ├───composer
    │       autoload_classmap.php
    │       autoload_namespaces.php
    │       autoload_psr4.php
    │       autoload_real.php
    │       ClassLoader.php
    │       installed.json
    │
    └───zendframework
        ├───zendframework
        └───...

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 Skeleton App

├───config
│   └───autoload
├───data
│   └───cache
├───module
│   └───Application
│       ├───config
│       ├───language
│       ├───src
│       │   └───Application
│       │       └───Controller
│       └───view
│           ├───application
│           │   └───index
│           ├───error
│           └───layout
├───public
│   ├───css
│   ├───fonts
│   ├───img
│   └───js
└───vendor
    ├───bin
    ├───composer
    └───zendframework
        └───zendframework
            └───bin
├───module
│   └───Application           // namespace
│       │   Module.php
│       │
│       ├───config
│       │       module.config.php
│       │
│       ├───language
│       │       en_US.mo
│       │       en_US.po
│       │
│       ├───src
│       │   └───Application    // namespace
│       │       └───Controller
│       │               IndexController.php
│       │
│       └───view
│           ├───application
│           │   └───index
│           │           index.phtml
│           │
│           ├───error
│           │       404.phtml
│           │       index.phtml
│           │
│           └───layout
│                   layout.phtml

Structure of Skeleton App

<?php
namespace MyModule;

use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;

class Module
{
    public function onBootstrap(MvcEvent $e)
    {
        $app = $e->getApplication();
    }
    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }
    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
                ),
            ),
        );
    }
}

Routing and controllers 

Controller config

<?php

return array(
    'controllers' => array(
        'invokables' => array(
            'Account\Controller\Index'    => 'Account\Controller\IndexController',
            'Account\Controller\Profile'  => 'Account\Controller\ProfileController',
            'Account\Controller\Settings' => 'Account\Controller\SettingsController',
        ),
    ),
    'view_manager' => array(
        'template_path_stack' => array(
            __DIR__ . '/../view',
        ),
    ),
);

Templates folder is relative to config

/modules/MyModule/view/controller-name/view-name.php

Controller

<?php

namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\JsonModel;
use Zend\View\Model\ViewModel;

class IndexController extends AbstractActionController
{
    public function indexAction()
    {
        $view = new ViewModel();
        $view->title = 'index content';
        $view->tags = ['eat', 'sleep', 'code', 'reapat'];
        $view->setTemplate('controller-name/view-name');
        return $view;
    }
    public function ajaxAction()
    {
        $view = new JsonModel([
            'id'          => 12345,
            'title'       => 'content',
            'description' => 'more stuff',
        ]);
        return $view;
    }
}

Router Types

Routing is the act of matching a request

to a given controller.

Last rules are with highest priority.

Literal

Segment

Regex

Wildcard

Scheme

Routing type Literal

<?php

return array(
    'router' => array(
        'routes' => array(
            'home' => array(
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route'    => '/',
                    'defaults' => array(
                        'controller' => 'Application\Controller\Index',
                        'action'     => 'index',
                    ),
                ),
            ),
        ),
    ),
);

Routing type Segment

<?php
return array(
    'router' => array(
        'routes' => array(
            'application' => array(
                'type'    => 'Literal',
                'options' => array(
                    // http://hostname.com/application/:controller/:action
                    'route'    => '/application',
                    'defaults' => array(
                        '__NAMESPACE__' => 'Application\Controller',
                        'controller'    => 'Index',
                        'action'        => 'index',
                    ),
                ),
                'may_terminate' => true,
                'child_routes' => array(
                    'default' => array(
                        'type'    => 'Segment',
                        'options' => array(
                            'route'    => '/[:controller[/:action]]',
                            'constraints' => array(
                                'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
                                'action'     => '[a-zA-Z][a-zA-Z0-9_-]*',
                            ),
                            'defaults' => array(
                            ),
                        ),

RouteMatch

'route-name' => array(
    'type'    => 'Segment',
    'options' => array(
        'route'    => '/[:id[/:type[/:sort]]]',
        'constraints' => array(
            'controller' => 'TestController',
            'action'     => 'test',

            'id'         => '[0-9]*',
            'type'       => '[a-zA-Z0-9_-]*',
            'sort'       => '[asc|desc]',
        ),
        'defaults' => array(
            'sort' => 'asc',
        ),
    ),
),
public function testAction()
{
    $match = $this->getRouteMatch();
    $id = $match->getParam('id');

    $params = $match->getParams();
}

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

    $params = $this->params()->fromRoute();
}
if (!$event->getRouteMatch() instanceof RouteMatch) {
    // No route match, this is a 404 page
}

Controller Plugins

Redirect

Url

Params

Layout

Forward

Identity

FlashMessenger

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()
{
    $this->redirect()->toRoute('home');
}

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

Services

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 dependencies

One service may require other services to work normally.

 

There are several ways to handle dependencies:

 

Constructor injection

 

ServiceLocatorAwareInterface

 

Dependency injection (DI)

$object = new B(new A());
$this->getServiceLocator()->get('ClassAlias');
$di = new Zend\Di\Di;
$b = $di->get('ClassAlias');

Service definition

'UserInputFiler' => 'SomeModule\InputFilter\User',

Keys are the service names

Values are valid class names to instantiate.

Interfaces

Best practice is to use Interfaces for the services.

<?php

namespace Module\Service;

class DemoService implements DemoServiceInterface
{

    public function find($id);
    {
    }

    public function findAll()
    {
    }

}
<?php

namespace Module\Service;

interface DemoServiceInterface
{

    public function find($id);

    public function findAll();

}

Service Locator

Service Locator

The Service Locator is a service/object locator, tasked with retrieving other objects.

Service Types

Invocable services

Factories

Abstract Factories

Module Definitions

Shared

Aliases

Invocable services

<?php

return array(
    'service_manager' => array(
        'invokables' => array(
            'SomeService'  => 'MyModule\Service\SomeService',
            'AuthListener' => 'MyModule\Listener\AuthListener',
        ),
    ),
);

No constructor dependencies.

Zend will automatically set ServiceLocator

if they implement the AwareInterface.

Zend may throw exceptions if config is not correct.

Factories

<?php
return array(
    'service_manager' => array(
        'factories' => array(
            'OtherService' => 'MyModule\Factory\OtherServiceFactory',
            'AuthListener' => function ($sm) {
                return new AuthListener($sm->get('db'));
            },
        ),
    ),
    'some_service_specific' => array('title'=>'Intro to ZF2'),
);
<?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)
    {
        $db = $sm->get('db');
        $config = $sm->get('Config')['some_service_specific'];
        $service = new OtherService($db, $config);
        return $service;
    }
}

Abstract Factories

<?php
return array(
    'service_manager' => array(
        'abstract_factories' => array(
            // Valid values include names of classes implementing
            // AbstractFactoryInterface, instances of classes implementing
            // AbstractFactoryInterface, or any PHP callbacks
            'SomeModule\Factory\CommonControlAppAbstractFactory',
        ),
    ),
);
class CommonControlAppAbstractFactory 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;
    }
}

Modules as Service Providers

Modules may act as service configuration providers

<?php
namespace MyModule;

use Zend\Mvc\MvcEvent;

class Module
{
    public function getConfig()
    {
        return [
            'service_locator' => [/*...*/]
        ];
    }
    public function getAutoloaderConfig() {/*common code*/}
    public function getControllerConfig() {/*common code*/}
    public function getControllerPluginConfig() {/*common code*/}
    public function getFilterConfig() {/*common code*/}
    public function getRouteConfig() {/*common code*/}
    public function getServiceConfig() {/*common code*/}
    public function getViewHelperConfig() {/*common code*/}
    // ...
}

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 array(
    'service_manager' => array(
        'shared' => array(
            'OtherService' => false,
        ),
    ),
);

Aliases

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

ServiceLocatorAwareTrait

<?php

namespace Zend\ServiceManager;

trait ServiceLocatorAwareTrait
{
    /**
     * @var ServiceLocatorInterface
     */
    protected $serviceLocator = null;

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
        return $this;
    }

    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }
}

Usage in services

<?php

namespace MyModule\Service;

use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;

class SampleService implements ServiceLocatorAwareInterface
{
    use ServiceLocatorAwareTrait;

    protected $anotherService;

    public function doSomething()
    {
        $this->anotherService = $this->getServiceLocator()->get('AnotherService');
        $this->anotherService->doMoreThings();
    }

    // No need to call setServiceLocator()
}

Events and Event Manager

Events and Event Manager

An Event is a named action.

 

Listener is any PHP callback that reacts to an event.

 

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

Event

interface EventInterface
{
    public function getName();
    public function getTarget();
    public function getParams();
    public function getParam($name, $default = null);
    public function setName($name);
    public function setTarget($target);
    public function setParams($params);
    public function setParam($name, $value);
    public function stopPropagation($flag = true);
    public function propagationIsStopped();
}
interface EventManagerInterface
{
    public function trigger($event, $target = null, $argv = array(), $callback = null);
    public function triggerUntil($event, $target, $argv = null, $callback = null);
    public function attach($event, $callback = null, $priority = 1);
    public function detach($listener);
    public function getEvents();
    public function getListeners($event);
    public function clearListeners($event);
    // ...
    public function attachAggregate(ListenerInterface $aggregate, $priority = 1);
    public function detachAggregate(ListenerInterface $aggregate);
}

Attach and Trigger events

use Zend\EventManager\EventManager;

$events = new EventManager();
$events->attach('event-name', function ($e) {
    $event = $e->getName();
    $params = $e->getParams();
    printf(
        'Handled event "%s", with parameters %s',
        $event,
        json_encode($params)
    );
});

$params = array('foo' => 'bar', 'baz' => 'bat');
$events->trigger('event-name', null, $params);

Trigger events

use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerAwareTrait;
 
class Baz implements EventManagerAwareInterface
{
    use EventManagerAwareTrait;

    public function get($id)
    {
        $params = ['id' => $id, 'title' => 'test 1234'];
        $results = $this->getEventManager()->trigger('Bar.pre', $this, $params);
        // If an event stopped propagation, return the value
        if ($results->stopped()) {
            return $results->last();
        }
        // do some work...
        // fetch data from database

        $this->getEventManager()->trigger('Bar.post', $this, $params);
    }
}

Attach listeners

use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\EventInterface;
 
class Bar implements ListenerAggregateInterface
{
    protected $listeners = array();
    public function attach(EventManagerInterface $e)
    {  
        $this->listeners[] = $e->attach('Bar.pre', array($this, 'load'));
        $this->listeners[] = $e->attach('Bar.post', array($this, 'save'));
    }

    public function load(EventInterface $e)
    {
        echo 'load...'; 
    }
    public function save(EventInterface $e)
    {
        echo 'save...'; 
    }
}

In Zend 2

Everything is an event!

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

MvcEvent::EVENT_BOOTSTRAP

MvcEvent::EVENT_ROUTE

MvcEvent::EVENT_DISPATCH

MvcEvent::EVENT_DISPATCH_ERROR

MvcEvent::EVENT_RENDER

MvcEvent::EVENT_RENDER_ERROR

MvcEvent::EVENT_FINISH

The MvcEvent

Bootstrap the application by creating the ViewManager.

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.

Database and models

Database adapter

Attributes of an adapter

Driver
Database
Username
Password
Hostname
Port
Charset

Mysqli, Sqlsrv, Pdo_Sqlite, Pdo_Mysql
the name of the database (schema)
the connection username
the connection password
the IP address or hostname to connect to
the port to connect to (if applicable)
the character set to use

Adapter types

SQLite
MySQL - Mysqli
MySQL - PDO
PostgreSQL
Oracle OCI8
SQLServer

IBM DB2

Query Execution

$adapter = $this->getServiceLocator()->get('db');

// Position params
$adapter->query('SELECT * FROM `records` WHERE `id` = ?', [5]);

// Named params
$result = $adapter->query('SELECT * FROM `records` WHERE `id` = :id', [':id' => 5]);

// Get current row
$row = $result->current();

// Get all rows
$row = $result->toArray();

use Zend\Db\Sql\Sql;
$sql = new Sql($adapter);
$select = $sql->select(['col_1','col_2','b.col_3']);
$select->from(['f' => 'foo']);
$select->join(['b' => 'bar'], 'f.foo_id = b.foo_id');
$select->where(['f.id' => 5]);

$selectString = $sql->getSqlStringForSqlObject($select);
$results = $adapter->query($selectString, $adapter::QUERY_MODE_EXECUTE);

Filters and Validators

Filters

The Zend\Filter component provides

a set of commonly needed data filters.

Filter chaining mechanism

$filter = new Zend\Filter\HtmlEntities();
echo $filter->filter('&'); // &amp;
echo $filter->filter('"'); // &quot;

$filter = new Zend\Filter\StripTags();
echo $filter->filter('<strong>My content</strong>'); // My content

$filter = new Zend\Filter\UriNormalize(['enforcedScheme' => 'https']);
echo $filter->filter('www.example.com'); // https://www.example.com
// Create a filter chain and add filters to the chain
$filterChain = new Zend\Filter\FilterChain();
$filterChain->attach(new Zend\I18n\Filter\Alpha())
            ->attach(new Zend\Filter\StringToLower());

// Filter the username
$username = $filterChain->filter($_POST['username']);

BaseName
Blacklist
Boolean
Callback
Compress
DataUnitFormatter
DateSelect
DateTimeFormatter
DateTimeSelect

Decompress
Decrypt

Digits
Dir
Encrypt
HtmlEntities
Inflector
Int
MonthSelect
Null

PregReplace

RealPath
StaticFilter

StringToLower
StringToUpper
StringTrim
StripNewlines
StripTags
ToInt
ToNull
UpperCaseWords
UriNormalize
Whitelist

Always check if a given filter exists.

Validators

The validator may provide information about which requirement(s) the input did not meet.

$validator = new Zend\Validator\EmailAddress();

if ($validator->isValid($email)) {
    // email appears to be valid
} else {
    // email is invalid; print the reasons
    foreach ($validator->getMessages() as $messageId => $message) {
        echo "Validation failure '$messageId': $message\n";
    }
}

A validator examines its input with respect to some requirements and produces a boolean result.

Alnum
Alpha
Barcode
Between
Callback
CreditCard
Date
Db\RecordExists
Db\NoRecordExists

Digits
EmailAddress
GreaterThan
Hex
Hostname
Iban
Identical
InArray
Ip
 

Isbn
LessThan
NotEmpty
PostCode
Regex
Sitemap
Step
StringLength

Always check if a given validator exists.

Standard classes

ArrayObject

ArrayUtils

Hydrator

Extractor

PriorityQueue

Request

Response

Standard classes

Namespace Zend\Stdlib

https://slides.com/kachar/zf2-tech-exchange

Presentation at:

Quick start with Zend Framework 2

By Ilko Kacharov

Quick start with Zend Framework 2

  • 951