Quick start with
Zend Framework 2
Ilko Kacharov
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
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
<?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 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',
),
),
);
<?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
│ │ 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
└───...
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
├───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
<?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__,
),
),
);
}
}
<?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
<?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;
}
}
Routing is the act of matching a request
to a given controller.
Last rules are with highest priority.
<?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',
),
),
),
),
),
);
<?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(
),
),
'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
}
// 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',
]);
}
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.
One service may require other services to work normally.
There are several ways to handle dependencies:
$object = new B(new A());
$this->getServiceLocator()->get('ClassAlias');
$di = new Zend\Di\Di;
$b = $di->get('ClassAlias');
'UserInputFiler' => 'SomeModule\InputFilter\User',
Keys are the service names
Values are valid class names to instantiate.
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();
}
The Service Locator is a service/object locator, tasked with retrieving other objects.
<?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.
<?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;
}
}
<?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 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*/}
// ...
}
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,
),
),
);
<?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',
),
),
);
<?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;
}
}
<?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()
}
An Event is a named action.
A Listener is any PHP callback that reacts to an event.
An EventManager aggregates listeners for one or more named events, and triggers events.
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);
}
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);
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);
}
}
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...';
}
}
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.
MvcEvent::EVENT_BOOTSTRAP
MvcEvent::EVENT_ROUTE
MvcEvent::EVENT_DISPATCH
MvcEvent::EVENT_DISPATCH_ERROR
MvcEvent::EVENT_RENDER
MvcEvent::EVENT_RENDER_ERROR
MvcEvent::EVENT_FINISH
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.
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
SQLite
MySQL - Mysqli
MySQL - PDO
PostgreSQL
Oracle OCI8
SQLServer
IBM DB2
$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);
The Zend\Filter component provides
a set of commonly needed data filters.
Filter chaining mechanism
$filter = new Zend\Filter\HtmlEntities();
echo $filter->filter('&'); // &
echo $filter->filter('"'); // "
$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.
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.
ArrayObject
ArrayUtils
Hydrator
Extractor
PriorityQueue
Request
Response
https://slides.com/kachar/zf2-tech-exchange
Presentation at: