Ilko Kacharov
Ilko Kacharov
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
Event Driven Architecture
Service Oriented Architecture
Library with interfaces and concrete implementations
Composition over inheritance principles
Enterprise level, not personal home page
<?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();
Current directory is the project root.
Everything is relative.
(to /project/, not /project/public/)
<?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',
],
],
];
<?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,
],
];
<?php
return [
'db' => [
'hostname' => 'dev-db-host',
'database' => 'dev-db-name',
'username' => 'dev-db-user',
'password' => 'dev-db-password',
],
];
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
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
<?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__,
],
],
];
}
}
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 is the act(art) of matching
a request to a given controller.
Two main router types:
Literal
Segment
<?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',
],
],
],
],
],
];
<?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',
],
],
// ...
// 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',
]);
}
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)
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.
The Service Locator is a manager
that finds and retrieves object instances - 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.
<?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;
}
}
<?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;
}
}
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,
],
],
];
<?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',
],
],
];
Manages constructor injections
Easy for refactoring
Consistent way of handling dependencies
You can replace services from the framework with your own
An Event is a named action.
A 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 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.
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.
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
}
}
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
https://slides.com/kachar/zf2-top-features
Slides at:
Thanks to:
@kachar136
kachar136@gmail.com
i.kacharov@proxiad.com