Zf2 Tutorial Dev Allen
Zf2 Tutorial Dev Allen
Zend Framework 2
Rob Allen
The experiment
Agenda
The state of ZF2 ZF2 foundations A look at a ZF2 MVC application
The primary thrust of ZF 2.0 is to make a more consistent, well-documented product, improving developer productivity and runtime performance.
Matthew Weier OPhinney
PHP 5.3 & namespaces Interfaces over abstract classes Faster autoloader Consistent plugin loading DI & service locator Event manager New MVC system
Pace of development
When?
ZF2 foundations
Namespaces
Namespaces allow us to
combine libraries with the same class names avoid very long class names organise our code easily
Dening a namespace
namespace My\Db\Statement; class Sqlsrv { }
Namespace constant:
namespace My\Db\Statement; echo __NAMESPACE__;
Namespaces
Within namespace:
namespace My\Db\Statement; function testSqlsrv() { ! $stmt = new Sqlsrv (); }
Fully qualied:
$stmt = new \My\Db\Statement\Sqlsrv();
Import
Specic class:
use My\Db\Statement\Sqlsrv; $stmt = new Statement\Sqlsrv();
Multiple namespaces:
use My\Db\Statement; use My\Db\Adapter; $stmt = new Statement\Sqlsrv(); $adapter = new Adapter\Sqlsrv();
Import
You cant do this!
use Zend\Db\Statement\Sqlsrv; use Zend\Db\Adapter\Sqlsrv; $stmt = new Sqlsrv (); $adapter = new Sqlsrv ();
Aliases
Solve with aliases:
use My\Db\Statement\Sqlsrv as DbStatement; use My\Db\Adapter\Sqlsrv as DbAdapter; $stmt = new DbStatement (); $adapter = new DbAdapter();
Namespace resolution
An unqualied function name is resolved in this order:
The current namespace is prepended to the function name. If the function names doesn't exists in the current namespace, then a global function name is used if it exists.
$date = date('Y-m-d');
Namespace resolution
An unqualied class name is resolved in this order:
If there is an import statement that aliases another name to this class name, then the alias is applied. Otherwise the current namespace is applied.
Autoloader
Zend\Loader\Autoloader
Class map autoloading, including class map generation PSR-0-compliant per-prex or namespace autoloading Fallback PSR-0-compliant include_path autoloading Autoloader factory for loading several autoloader strategies at once
PSR-0?
A standard that describes how to name a class so that the autoloader can nd it. Essentially each namespace maps directly to a folder on disk: i.e. \Zend\Config\Ini should be found in /path/to/Zend/Config/ini.php
StandardAutoloader
Inspects classname and nds le on disk Load via namespace => directory pairs Load via prex => directory pairs Fallback search of include_path
StandardAutoloader
require_once ZF2_PATH.'/Loader/StandardAutoloader.php'; $loader = new Zend\Loader\StandardAutoloader(array( 'prefixes' => array( 'MyVendor' => __DIR__ . '/MyVendor', ), 'namespaces' => array( 'MyNamespace' => __DIR__ . '/MyNamespace', ), 'fallback_autoloader' => true, )); // register our loader with the SPL autoloader $loader->register();
StandardAutoloader
require_once ZF2_PATH.'/Loader/StandardAutoloader.php'; $loader = new Zend\Loader\StandardAutoloader(); $loader->registerPrefix('MyVendor', __DIR__.'/MyVendor') ->registerNamespace('MyNamespace', __DIR__.'/MyNamespace') ->setFallbackAutoloader(true); // register our loader with the SPL autoloader $loader->register();
ClassMapAutoloader
Array of class name to le on disk High performance!
A class map
autoload_classmap.php:
use My\Db\Statement\Sqlsrv; $stmt = new Statement\Sqlsrv();
Using:
require_once ZF2_PATH.'/Loader/ClassMapAutoloader.php'; $autoLoader = new \Zend\Loader\ClassMapAutoloader( array(__DIR__ . '/autoload_classmap.php')); // register with the SPL autoloader $autoLoader->register();
OR: $loader = new \Zend\Loader\ClassMapAutoloader(); $loader->registerAutoloadMap(array( __DIR__ . '/../library/autoload_classmap.php', __DIR__ . '/../application/autoload_classmap.php', ));
Creating classmaps
prompt> php path/to/zf2/bin/classmap_generator.php -w Creating class file map for library in '/var/www/ project/library'... Wrote classmap file to '/var/www/project/library/ autoload_classmap.php'
Combining autoloaders
Use class maps and the prexes! Very useful in development Use ZF2s AutoloaderFactory class
require_once ZF2_PATH . '/Loader/AutoloaderFactory.php'; Zend\Loader\AutoloaderFactory::factory(array( 'Zend\Loader\ClassMapAutoloader' => array( __DIR__ . '/../library/Zend/autoload_classmap.php', ), 'Zend\Loader\StandardAutoloader' => array( 'prefixes' => array( 'MyVendor' => __DIR__ . '/MyVendor', ), 'namespaces' => array( 'MyNamespace' => __DIR__ . '/MyNamespace', ), 'fallback_autoloader' => true, ), ));
Autoloader summary
Use the class map one in preference as its really really fast! Prex => directory pairs is surprisingly fast though compared to ZF1
Exercise
Update the ZF1 Tutorial to use the Autoloader from ZF2: 1. Using StandardAutoloader 2. Using a classmap for library code and a StandardAutoloader for the application code.
Dependency Injection
Dependency injection means giving an object its instance variables. Really. That's it.
James Shore
Contrived example
class Album { protected $artist; public function getArtistName() { return $artist->getName(); } }
Direct instantiation
class Album { protected $artist; public function __construct() { $this->artist = new Artist(); } // etc }
Constructor injection
class Album { protected $artist; public function __construct(Artist $artist) { $this->artist = $artist; } // etc }
Calling code:
$album = new Album($artist);
Constructor injection
class Album { protected $artist; public function __construct(Artist $artist) { $this->artist = $artist; } // etc }
Calling code:
$album = new Album($artist);
Setter injection
class Album { protected $artist; public function setArtist(Artist $artist) { $this->artist = $artist; } // etc }
Calling code:
$album = new Album(); $album->setArtist($artist);
Advantages
Can use different objects (e.g. SoloArtist) Conguration is natural Testing is simplied Do not need to change the Album class
Disadvantages
Instantiation and conguration of a class dependencies are the responsibility of the calling code.
Calling code:
$album = AlbumContainer::createAlbum();
A dependency injection container is a component that holds dependency denitions and instantiates them for you.
Zend\Di
Supports constructor and setter injection Congured in code or via cong le Type hinting makes life easier
Constructor injection
namespace My; class Artist { } class Album { protected $artist = null; public function __construct (Artist $artist) { $this->artist = $artist; } }
To use an $album:
$di = new Zend\Di\DependencyInjector(); $album = $di->get('My\Album');
Also:
$album2 = $di->newInstance('My\Album');
Parameters in dependencies
class Artist { protected $name; public function __construct($name) { $this->name = $name; } }
Use setParameters:
$di = new Zend\Di\DependencyInjector(); $di->getInstanceManager() ! ->setParameters('Artist', array('name' => 'Queen'));
Parameters in dependencies
Usage:
$album = $di->get('My\Album');
Setter injection
namespace My; class Artist { protected $name; public function __construct($name) { $this->name = $name; } } class Album { protected $artist = null; public function setArtist(Artist $artist) { $this->artist = $artist; } }
To use an $album:
Enable:
$di->getDefinition()->getIntrospectionRuleset() ->addSetterRule('paramCanBeOptional', false);
Use:
$album = $di->get('My\Album', array('name'=>'Train'));
Denitions
RuntimeDenition
BuilderDenition
use Zend\Di\DependencyInjector, Zend\Di\Definition, Zend\Di\Definition\Builder; // Builder definition for My\Album $method = new Builder\InjectionMethod(); $method->setName('setArtist'); $method->addParameter('artist', 'My\Artist'); $class = new Builder\PhpClass(); $class->setName('My\Album'); $class->addInjectionMethod($method); $builderDef = new Definition\BuilderDefinition(); $builderDef->addClass($class); $di = new DependencyInjector(); $di->setDefinition($builderDef);
Compilation to ArrayDenition
use Zend\Di, ! Zend\Code\Scanner\ScannerDirectory; $compiler = new Di\Definition\Compiler(); $compiler->addCodeScannerDirectory( new ScannerDirectory('path/to/library/My/') ); $definition = $compiler->compile(); $di = new Di\DependencyInjector(); $di->setDefinition($definition); $album = $di->get('My\Album', array('Oasis'));
Persistance
file_put_contents( __DIR__ . '/di-definition.php', '<?php return ' . var_export($definition->toArray(), true) . ';' );
Load:
$definition = new Zend\Di\Definition\ArrayDefinition( include __DIR__ . '/di-definition.php' );
Aliases
Makes it easier to specify dependencies Can retrieve from the injector by alias too.
$di = new Zend\Di\DependencyInjector(); $im = $di->getInstanceManager(); $im->addAlias('artist', 'My\Artist'); $im->addAlias('album', 'My\Album'); $im->setParameters('artist', array('name' => 'Blur')); $artist = $di->get("artist"); $album = $di->get("album", array("name" => "Queen"));
Conguration
Cong le:
[production] di.instance.alias.album = 'My\Album' di.instance.alias.artist = 'My\Artist' di.instance.artist.parameters.name = 'Marillion'
PHP le:
use Zend\Config, Zend\Di; $config = new Config\Ini('application.ini', 'dev'); $diConfig = new Di\Configuration($config->di); $di = new Di\DependencyInjector($diConfig); $artist = $di->get("artist"); $album = $di->get("album", array("name" => "Queen"));
Aggregation
use Zend\Di\Definition; $arrayDef = new Definition\ArrayDefinition( include __DIR__ . '/di-definition.php' ); $runtimeDef = new Definition\RuntimeDefinition(); // Aggregate the two definitions $aggregateDef = new Definition\AggregateDefinition(); $aggregateDef->addDefinition($arrayDef); $aggregateDef->addDefinition($runtimeDef); $di = new \Zend\Di\DependencyInjector(); $di->setDefinition($aggregateDef);
DI summary
DI allows dening dependencies independent of the calling code You still need to wire things up - its just all in one place with DIC. This is used extensively in the MVC system
Event Manager
Event Manager allows a class to publish events which other components can listen for and act when that event occurs.
Me!
Terminology
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. A Target is an object that creates events
Simple example
use Zend\EventManager\EventManager, Zend\EventManager\Event; $callback = function($event) { echo "An event has happened!\n"; var_dump($event->getName()); var_dump($event->getParams()); }; $eventManager = new EventManager(); $eventManager->attach('eventName', $callback); echo "\nRaise an event\n"; $eventManager->trigger('eventName', null, array('one'=>1, 'two'=>2));
Target
use Zend\EventManager\EventManager, Zend\EventManager\Event; class MyTarget { public $eventManager; public function __construct() { $this->eventManager = new EventManager(); } public function doIt() { $event = new Event(); $event->setTarget($this); $event->setParam('one', 1); $event->setParam('two', 2); $this->eventManager->trigger('doIt.pre', $event); } }
Target
$callback = function ($event) { echo "Responding to doIt.pre!\n"; var_dump(get_class($event->getTarget())); var_dump($event->getName()); var_dump($event->getParams()); };
StaticEventManager
class MyTarget { public $eventManager; public function __construct() { $this->eventManager = new EventManager(__CLASS__); } // etc } // attach a listener use Zend\EventManager\StaticEventManager; $events = StaticEventManager::getInstance(); $events->attach('MyTarget', 'doIt.pre', $callback); // cause event to trigger $target->doIt();
StaticEventManager
Statically attached listeners are called after the directly attached ones. The identier can be an array:
$this->eventManager = new EventManager( array(__CLASS__, get_called_class()));
Re-enable:
$this->eventManager->setStaticConnections( StaticEventManager::getInstance());
Listener aggregates
HandlerAggregate interface provides for attaching and detaching event listeners Two methods:
Using HandlerAggregate
class MyListener implements HandlerAggregate { protected $eventHandlers = array(); public function attach(EventCollection $events) { $this->handlers[] = $events->attach('event1', array($this, 'doSomething')); $this->handlers[] = $events->attach('event2', array($this, 'doSomethingElse')); } public function detach(EventCollection $events) { foreach ($this->handlers as $key => $handler) { $events->detach($handler); unset($this->handlers[$key]); } $this->handlers = array(); }
Using HandlerAggregate
$target = new MyTarget(); // Attach the target's event manager to the listener $listener = new MyListener(); $listener->attach($target->getEventManager()); // Now we can trigger some events $target->doSomething(); $target->doSomethingElse(); // Detach $listener->detach($target->getEventManager()); // this trigger will not fire the listeners $target->doSomething();
$results is a ResponseCollection $results are in reverse order (latest triggered event is rst)
Short-circuiting
public function triggerEvent1() { $results = $this->eventManager->trigger('event1', $this); foreach ($results as $result) { var_dump($result); } }
$results is a ResponseCollection $results are in reverse order (latest triggered event is rst)
Short-circuiting
public function doIt() { $params = array('id' => 1); $results = $this->eventManager->trigger('doIt.pre', $this, $params, function ($result) { if($result == 'done') { return true; } return false; } ); if ($results->stopped()) { // We ended early } }
Priority
Control the order of execution of listeners $priority is last parameter to attach()
$eventManager->attach('doIt.pre', $callback, $priority);
Default is 1
EventManager summary
Decouples objects from one another Increases exibility Expect to use in MVC system
Exercise
Using the ZF2 EventManager, add logging to Application_Models_DbTable_Albums
A ZF2 Application
Exercise
Use the ZF2 Skeleton Application and Skeleton Module to create a website that displays a random quote on the home page.
Resources
http://framework.zend.com/zf2 https://github.com/akrabat/zf2-tutorial https://github.com/zendframework/ ZendSkeletonApplication https://github.com/zendframework/ ZendSkeletonModule https://github.com/weierophinney/zf-quickstart/ tree/features/zf2-mvc-module
Thank you
feedback: http://joind.in/3459 email: [email protected] twitter: @akrabat