On Github Ocramius / zf2-dpc-tutorial-slides
if $wifi:
curl -s https://getcomposer.org/installer | php -- php composer.phar create-project -sdev zendframework/skeleton-application .
else:
$pendrive;
php -S 127.0.0.1:80 path/to/install/public
\o/
Cross-Cutting Concerns
Explain what CCC is about and why it is bad for applications.A simple user class
class UserService { // ... public function logIn(User $user) { $this->authService->setIdentity($user); } }A simple user registration class
We also need to log authentication...
class UserService { // ... public function logIn(User $user) { $this->authService->setIdentity($user); $this->logger->log('User ' . $user->getId() . ' logged in'); } }
... and we get more feature requests over time
public function logIn(User $user) { $this->authService->setIdentity($user); $this->logger->log('User ' . $user->getId() . ' logged in'); if ($this->userDb->getLastLogin($user) < new DateTime('-1 week')) { $this->mailer->sendNews($user); } $this->userDb->incrementActiveUsers(); $this->flashMessenger->addMessage('Welcome, ' . $user->getName()); $this->notificationService->notifyFriendsOfLogin($user); }
Let's simplify it!
class LoginLoggerListener { // ... public function logLogin($event) { $user = $event->getParam('user'); $this->logger->log('User ' . $user->getId() . ' logged in'); } }
class LoginNewsMailerListener { // ... public function sendNewsOnLogin($event) { $user = $event->getParam('user'); if ($this->userDb->getLastLogin($user) < new DateTime('-1 week')) { $this->mailer->sendNews($user); } } }
And so on...
Introducing the event manager...
public function logIn(User $user) { $this->authService->setIdentity($user); $this->eventManager->trigger('login', $this, ['user' => $user]); }
... and wiring it together
$eventManager = new Zend\EventManager\EventManager(); $loggerListener = new LoginLoggerListener(/* ... */); $newsMailerListener = new LoginNewsMailerListener(/* ... */); $eventManager->attach('login', [$loggerListener, 'logLogin']); $eventManager->attach('login', [$newsMailerListener, 'sendNewsOnLogin']); $userService = new UserService(/* ... */, $eventManager);
Et voilà!
interface EventManagerInterface { /** @return ResponseCollection */ public function trigger( $event, $target = null, $args = [], $callback = null ); public function attach($listener, $callback = null, $priority = 1); public function setIdentifiers($identifiers); // more stuff ... }
interface EventInterface { /** @return string */ public function getName(); /** @return mixed */ public function getTarget(); /** @return array */ public function getParams(); public function stopPropagation($flag = true); public function propagationIsStopped(); // more stuff ... }
interface SharedEventManager { public function attach($identifier, $event, $callback, $priority = 1); // more stuff ... }
Listeners are triggered in the order they are registered if no priority is set:
$eventManager = new EventManager(); $eventManager->attach('event-name', function () { echo 'Hello '; }); $eventManager->attach('event-name', function () { echo 'World! '; }); $eventManager->trigger('event-name');
Listeners are triggered by priority
$eventManager = new EventManager(); $eventManager->attach('event-name', function () { echo 'Hello '; }, -100); $eventManager->attach('event-name', function () { echo 'World! '; }, 100); $eventManager->trigger('event-name');
Higher priority = before
Default priority = 1
Event propagation can be stopped:
$eventManager = new EventManager(); $eventManager->attach('event-name', function ($event) { echo 'Hello '; $event->stopPropagation(true); }); $eventManager->attach('event-name', function () { echo 'World! '; }); $eventManager->trigger('event-name');
Event propagation can be stopped also from a callback on the trigger side:
$eventManager = new EventManager(); $eventManager->attach('event-name', function () { return 'Hello '; }); $eventManager->attach('event-name', function () { return 'World! ' . PHP_EOL; }); // just run it as usual $eventManager->trigger('event-name', null, [], function ($result) { echo $result; }); // stop after the first listener $eventManager->trigger('event-name', null, [], function ($result) { echo $result; return true; });
Also called short-circuiting
More resources:
Where is Zend\EventManager used in Zend\Mvc?
Where is Zend\EventManager used in Zend\Mvc?
Pretty much everywhere where we may want to apply CCC!
3 levels of "maturity"
Direct instantiation Service Location Dependency Injection Just explain these 3 approaches to fetching dependencies briefly.Direct instantiation:
class ElePHPant { private $drunk = 0; public function drinkUntilDrunk() { $beerTruck = new BeerTruck(); while ($this->drunk < 1) { $beer = $beerTruck->deliverBeer(); $this->drunk += $beer->getLiters() * $beer->getGrade(); } return 'HIC!'; } }
Service Location:
class ElePHPant { private $drunk = 0; public function drinkUntilDrunk() { $truck = Registry::get('BeerTruck'); while ($this->drunk < 1) { $beer = $beerTruck->deliverBeer(); $this->drunk += $beer->getLiters() * $beer->getGrade(); } return 'HIC!'; } }
Dependency Injection:
class ElePHPant { private $truck; private $drunk = 0; public function __construct(BeerTruck $truck) { $this->truck = $truck; } public function drinkUntilDrunk() { while ($this->drunk < 1) { $beer = $this->truck->deliverBeer(); $this->drunk += $beer->getLiters() * $beer->getGrade(); } return 'HIC!'; } }
Ok, seriously...
Usually, never...
... or when you want get it shipped "quick and dirty" first
Service Location has edge use cases, but in general, it's technical debt
Configuring the Service Manager programmatically:
$serviceManager = new Zend\ServiceManager\ServiceManager(); $serviceManager->setService(...); $serviceManager->setInvokableClass(...); $serviceManager->setFactory(...); $serviceManager->addAbstractFactory(...); $serviceManager->addInitializer(...); $serviceManager->addDelegator(...); $serviceManager->setShared(...);
Configuring the Service Manager with arrays:
$config = [ 'services' => [...], 'invokables' => [...], 'factories' => [...], 'abstract_factories' => [...], 'initializers' => [...], 'delegators' => [...], 'shared' => [...], ]; $serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config($config); );
Fetching a service:
$service = $serviceManager->get('service-name');
Service names are Normalized
$service = $serviceManager->get('service-name');
Is the same as:
$service = $serviceManager->get('Service\\Name');
And the same as:
$service = $serviceManager->get('servicename');
Please don't rely on this, it was arguably a bad idea in first place, and it will be removed :-(
Services are Shared by default
$serviceManager->get('service-name') === $serviceManager->get('service-name');
$truck = new GuinnessTruck(); $serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config([ 'services' => [ 'BeerTruck' => $truck, ], ]) ); var_dump($truck === $serviceManager->get('BeerTruck'));
$serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config([ 'invokables' => [ 'BeerTruck' => 'GuinnessTruck, ], ]) ); var_dump($serviceManager->get('BeerTruck'));
$serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config([ 'invokables' => [ 'BreweryBrand' => 'Guinness', ], 'factories' => [ 'BeerTruck' => function ($serviceManager) { return new BeerTruck($serviceManager->get('BreweryBrand')); }, ], ]) ); var_dump($serviceManager->get('BeerTruck'));
Suited for anything that can't be instantiated directly
A factory can either be a callable or the class name of a Zend\ServiceManager\FactoryInterface
class MyAbstractFactory implements AbstractFactoryInterface { public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName) { return fnmatch('*Truck', $requestedName); } public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName) { return new $requestedName; // overly simplified! } }
$serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config([ 'abstract_factories' => ['MyAbstractFactory'], ]) ); var_dump($serviceManager->get('BeerTruck'));
Useful for mapping multiple services in one shot!
$serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config([ 'invokables' => [ 'BeerTruck' => 'SimpleBeerTruck', ], 'shared' => [ 'BeerTruck' => false, ], ]) ); $truck1 = $serviceManager->get('BeerTruck'); $truck2 = $serviceManager->get('BeerTruck'); var_dump($truck1 !== $truck2);
Allows you to use the ServiceManager as a factory
$serviceManager = new Zend\ServiceManager\ServiceManager( new Zend\ServiceManager\Config([ 'invokables' => [ 'BeerTruck' => 'SimpleBeerTruck', ], 'initializers' => [ function ($object) { if ($object instanceof SimpleBeerTruck) { $truck->setPaint('blue'); } }, ], ]) ); var_dump($serviceManager->get('BeerTruck')->getPaint());
Allows you to apply initialization logic to every object PRODUCED by the ServiceManager
Use carefully!
Like initializers, but more flexible and targeted to single services
My advice: use initializers and delegators only when you know the ServiceManager well enough.
Where is Zend\ServiceManager used in Zend\Mvc?
Where is Zend\ServiceManager used in Zend\Mvc?
Wherever you need to "get something by name".
The name may just be in your code, or in a configuration file/format
Allows re-using of code including
Basically everything.
namespace MyModule; class Module { public function getConfig() {} public function getControllerConfig() {} public function getServiceConfig() {} public function getAutoloaderConfig() {} public function onBootstrap() {} public function init() {} }
return [ 'router' => [ 'routes' => [ ... ], ], 'service_manager' = [ 'factories' => [ ... ], ], 'controllers' => [ ... ];
1-3 can be cached by the Module Manager easily
Tells the whole application how to behave and which modules to load
return [ 'modules' => [ 'Application', 'ZfcBase', 'ZfcUser', ], 'module_listener_options' => [ 'module_paths' => [ './module', './vendor', ], 'config_glob_paths' => [ 'config/autoload/{,*.}{global,local}.php', ], ], ... ];
Translates the URI from the HTTP request into a class and method that should process it (typically a controller and action)
/hello/world => My\Controller\Hello::worldAction()
Configured by any router key in the merged config
[ 'router' => [ 'routes' => [ 'home' => [ 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => [ 'route' => '/', 'defaults' => [ 'controller' => 'Application\Controller\Index', 'action' => 'index', ] ] ] ] ] ]/ => Application\Controller\Index::indexAction()
To add to the confusion...
Application\Controller\Index is a key in the ControllerManager service locator.
function getControllerConfig() { return [ 'invokables' => [ 'Application\Controller\Index' => 'Application\Controller\IndexController' ] ]; }
There are different types of routes:
Matches to the exact string of the URI path
'route' => '/ghostbuster/egon'
(note lack of trailing slash)
Matches any segment of the URI path.
Parameters are donated with a colon and are passed through to the controller
Square brackets donate optional parameters
'route' => '/ghostbuster/:name[/]'
name parameter will be set to egon
Uses a regular expression to match against the URI
Matched parts can be converted to named parameters using a spec
'regex' => '/ghostbuster/(?<name>[a-zA-Z0-9_-]+)(\.(?<type>(field|clerical|friend)))?', 'spec' => '/ghostbuster/%name%.%type%'
name parameter will be set to janine
type parameter will be set to clerical
Matches only given protocol
'scheme' => 'https'
Matches only given HTTP method verb(s)
'verb' => 'get,post'
Simply iterates through route definitions in a LIFO order unit it gets a match.
Allows complex definitions of routes in a tree structure. Uses B-tree algorithm to match the routes.
Use may_terminate to tell the router no other routes will follow
Use child_routes to define any children of an existing route
'base' => [ 'type' => 'literal', 'options' => [ 'route' => '/ghostbuster', 'defaults' => [ 'controller' => 'Ghostbuster', 'action' => 'base', ] ], 'may_terminate' => true, 'child_routes' => [ 'people' => [ 'type' => 'segment', 'options' => [ 'route' => '/[:name]', 'constraints' => [ 'name' => '[a-zA-Z0-9_-]+' ], 'defaults' => [ 'action' => 'people', ], ], ], ], ],
Composes an uri from route name and parameters
<?= $this->url('base/people', ['name' => 'ray']); ?>
gives...
<a href="/ghostbuster/ray"></a>
Add a new Module to your skeleton app called Blog
The module should initially route /blog to a brand new controller and action in that module
30 minutes
Once finished feel free to go to lunch
namespace Blog; class Module { public function getConfig() { return include __DIR__ . '/config/module.config.php'; } public function getAutoloaderConfig() { return [ 'Zend\Loader\StandardAutoloader' => ['namespaces' => [ __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, ]], ]; } public function getControllerConfig() { return [ 'invokables' => [ 'Blog\Controller\Blog' => 'Blog\Controller\BlogController', ], ]; } }
return [ 'router' => [ 'routes' => [ 'blog' => [ 'type' => 'Zend\Mvc\Router\Http\Literal', 'options' => [ 'route' => '/blog', 'defaults' => [ 'controller' => 'Blog\Controller\Blog', 'action' => 'index', ], ], ], ], ], 'view_manager' => [ 'display_not_found_reason' => true, 'display_exceptions' => true, 'doctype' => 'HTML5', 'template_map' => [], 'template_path_stack' => [ __DIR__ . '/../view', ], ], ];
A Database Abstraction Layer that allows easy and safe connection to many database platforms, includes:
Provides a connection to the database platform, usually via configuration through the merged config
config/db.local.config.phpreturn [ 'db' => [ 'driver' => 'Pdo', 'dsn' => 'mysql:mydb=blog;host=127.0.01', 'username' => 'keymaster', 'password' => 'gatekeeper', ], 'service_manager' => [ 'factories' => [ 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory', ], ], ];
DO NOT STORE IN YOUR VERSION CONTROL!!!
We're safely connected!
(We do not recommend the crossing of streams)
Allows pain-free view into a single table including all the usual suspects:
Creating:
return [ 'factories' => [ 'GhostTableGateway' => function (ServiceManager $serviceManager) { $adapter = $serviceManager->get('Zend\Db\Adapter\Adapter'); $tableGateway = new TableGateway('ghost', $adapter); } ], ];
Find all the clerical staff:
$clericalStaff = $tableGateway->select(['type' => 'clerical']);
Fire all the clerical staff:
$tableGateway->delete(['type' => 'clerical']);
Give all the Ghostbusters a capture:
$tableGateway->update( ['captures' => new Expression('captures + 1')], ['type' => 'ghostbuster'] );
Using:
'factories' => [ 'Ghostbuster\Controller\Ghost' => function(ControllerManager $cm) { $ghostTable = $cm->getServiceLocator()->get('GhostTable'); return new GhostController($ghostTable); }, ],GhostController
public function __construct(TableGateway $ghostTable) { $this->ghostTable = $ghostTable; } public function viewAction() { $this->ghostTable->select(['id' => 666]); }
Use a Service (Mapper) Layer:
Controller Config'GhostService' => function(ServiceManager $sm) { $ghostTable = $sm->get('GhostTableGateway'); return new GhostService($postTable); },Service Config
'Ghostbuster\Controller\Ghost' => function(ControllerManager $cm) { $ghostService = $cm->getServiceLocator()->get('GhostService'); return new GhostController($ghostService); },GhostController
public function __construct(GhostService $ghostService) { $this->ghostService = $ghostService; } public function viewAction() { $this->ghostService->findGhostById(666); }
The act of turning an array into an object (and vice-versa)...
because it's much easier to pass around objects than arrays.
$hydrator = new \Zend\Stdlib\Hydrator\ClassMethods(); $data = ['name' => 'Egon', 'type' => 'Ghostbuster', 'captures' => 39]; /** @var Ghostbuster $ghostbuster */ $ghostbuster = $hydrator->hydrate($data, new Ghostbuster());
Entity:
class Ghostbuster { protected $name; protected $type; protected $captures; public function setCaptures($captures) { $this->captures = $captures; } public function getCaptures() { return $this->captures; } ...
Get automagically hydrated objects back from your DbTable queries
MEGA WIN!!!
Just update your TableGateway definition in Service Manager:
'PostTableGateway' => function (ServiceManager $serviceManager) { $adapter = $serviceManager->get('Zend\Db\Adapter\Adapter'); $hydrator = new ClassMethods(true); $rowObjectPrototype = new Ghostbuster(); // Entity to hydrate $resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype); $tableGateway = new TableGateway('people', $adapter, null, $resultSet); return $tableGateway; }
30 minutes
Once finished feel free to go to break
return [ 'db' => [ 'driver' => 'Pdo', 'dsn' => 'mysql:dbname=blog;host=127.0.01', 'driver_options' => [ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'' ], 'username' => 'blog', 'password' => 'rayparkerjr', ], 'service_manager' => [ 'factories' => [ 'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory', ], ], ];
namespace Blog\Entity; class Post { protected $id; protected $slug; protected $writtenOn; protected $title; protected $preview; protected $views; protected $body; /** * @return mixed */ public function getBody() { return $this->body; } /** * @param mixed $body */ public function setBody($body) { $this->body = $body; } /** * @return mixed */ public function getId() { return $this->id; } /** * @param mixed $id */ public function setId($id) { $this->id = $id; } /** * @return mixed */ public function getPreview() { return $this->preview; } /** * @param mixed $preview */ public function setPreview($preview) { $this->preview = $preview; } /** * @return mixed */ public function getSlug() { return $this->slug; } /** * @param mixed $slug */ public function setSlug($slug) { $this->slug = $slug; } /** * @return mixed */ public function getTitle() { return $this->title; } /** * @param mixed $title */ public function setTitle($title) { $this->title = $title; } /** * @return mixed */ public function getViews() { return $this->views; } /** * @param mixed $views */ public function setViews($views) { $this->views = $views; } /** * @return mixed */ public function getWrittenOn() { return $this->writtenOn; } /** * @param mixed $writtenOn */ public function setWrittenOn($writtenOn) { $this->writtenOn = $writtenOn; } }
namespace Blog\Service; use Zend\Db\TableGateway\TableGateway; class PostService { /** * @var TableGateway */ protected $postTable; function __construct(TableGateway $postTable) { $this->postTable = $postTable; } }
namespace Blog\Service; use Zend\Db\TableGateway\TableGateway; class PostService { /** * @var TableGateway */ protected $postTable; function __construct(TableGateway $postTable) { $this->postTable = $postTable; } }
public function getServiceConfig() { return [ 'factories' => [ 'Blog\Service\PostService' => function(ServiceManager $serviceManager) { $postTable = $serviceManager->get('PostTableGateway'); return new PostService($postTable); } ], ]; }
public function getServiceConfig() { return [ 'factories' => [ 'PostTableGateway' => function (ServiceManager $serviceManager) { $adapter = $serviceManager->get('Zend\Db\Adapter\Adapter'); $hydrator = new ClassMethods(true); $rowObjectPrototype = new Post(); $resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype); $tableGateway = new TableGateway('post', $adapter, null, $resultSet); return $tableGateway; }, 'Blog\Service\PostService' => function(ServiceManager $serviceManager) { $postTable = $serviceManager->get('PostTableGateway'); return new PostService($postTable); } ], ]; }
public function getControllerConfig() { return [ 'factories' => [ 'Blog\Controller\Blog' => function(ControllerManager $controllerManager) { $postService = $controllerManager->getServiceLocator()->get('Blog\Service\PostService'); return new BlogController($postService); }, ]; }
namespace Blog\Controller; use Blog\Service\PostService; use Zend\Mvc\Controller\AbstractActionController; use Zend\View\Model\ViewModel; class BlogController extends AbstractActionController { /** * @var PostService */ protected $postService; function __construct(PostService $postService) { $this->postService = $postService; } }
Deals with rendering view scripts (PHP/Twig/Markdown/etc) into whatever format you want
A view script is just a file.
In Zend\Mvc, view scripts are .phtml files.
View scripts are usually in your modules, in a directory called view/
Zend\Mvc won't look for your view scripts automagically.
View script paths are "resolved" by a Zend\View\Resolver\ResolverInterface.
For simplicity, we will just see how to configure the view layer for our needs.
In order to get Zend\Mvc to find our scripts, we have to tell it where to find them in our config/module.config.php:
'view_manager' => array( 'template_path_stack' => array( __DIR__ . '/../view', ), ],
By default, view names are built from the dispatched controller name:
<root-namespace>/<controller-class-name>/<action>
So if Blog\Controller\BlogController#postAction() is executed the view name will be ...
blog/blog/post
... which in our case translates to view script ...
module/Blog/view/blog/blog/post.phtml
Create a file module/Blog/view/blog/blog/post.phtml and put following contents in it
<?php echo "Hello World!";
And that's it! Just normal PHP, nothing to see here
A view script is "filled" with variables from the ViewModel that is returned by the controller.
public function indexAction() { return new ViewModel(['foo' => 'bar']); }
<?php echo $foo; echo $this->foo;
Both approaches work!
View helpers are utility methods that are available in view scripts:
<?php <a href="<?= $this->escapeHtmlAttr($userDefinedUrl); ?>"> <?= $this->escapeHtml($userDefinedText); ?> </a>
In this example escapeHtml and escapeHtmlAttr are helpers.
View helpers are defined in a specific ServiceManager (PluginManager) that is pre-configured.
See the HelperPluginManager for a list of pre-defined helpers.
You can register your own helpers: it's just about configuring a specific service manager.
Write a view that does:
in
module/Blog/view/blog/blog/index.phtml
<?php foreach ($posts as $post): ?> <hr/> <a href="<?php echo $this->url('post', ['slug' => $post->getSlug()]); ?>"> <h1><?php echo $this->escapeHtml($post->getTitle()); ?></h1> </a> <h3><?php echo $this->escapeHtml($post->getWrittenOn()); ?></h3> <p><?php echo $this->escapeHtml($post->getPreview()); ?></p> <?php endforeach; ?>
Forms are hard work, who wants to be writing HTML forms and validating input by hand?
Create The Form
use Zend\Form\Form class MyForm extends Form { }
Add Some Elements
public function __construct() { $this->add( [ 'name' => 'name', // for added confusion 'type' => 'text', 'options' => [ 'label' => 'Name' ], ], ); $this->add( [ 'name' => 'submit', 'type' => 'submit', ] ); }
Render in the view!
Controller
return new ViewModel( ['form' => new MyForm()] );
View
echo $this->form()->openTag($form); echo $this->formCollection($form); echo $this->form()->closeTag();
Add Some Validation
protected $inputFilter; ... public function getInputFilter() { if (!$this->inputFilter) { $this->inputFilter = new InputFilter(); $this->inputFilter->add([ 'name' => 'name', // same as name of element 'required' => 'true', 'filters' => [['name' => 'StringTrim']], 'validators' => [[ 'name' => 'StringLength', 'options' => [ 'min' => 3, 'max' => 64 ] ]], ]); } return $this->inputFilter; }
public function addAction() { $form = new PostForm(); $ghostbuster = new Ghostbuster(); $form->setHydrator(new ClassMethods())->setObject($ghostbuster); //hydration rocks! if ($this->getRequest()->isPost()) { // form has been posted $form->setData($this->getRequest()->getPost()->toArray()); if($form->isValid()) { // form validates so save it! $this->ghostbusterService->savePost($form->getObject()); $this->redirect()->toRoute('view'); } } else { $form->bind($ghostbuster); } return new ViewModel(['form' => $form]); }
namespace Blog\Form; use Zend\Form\Form; use Zend\InputFilter\InputFilter; class PostForm extends Form { protected $inputFilter; public function __construct() { parent::__construct('post'); $this->add( [ 'name' => 'id', 'type' => 'hidden' ] ); $this->add( [ 'name' => 'slug', 'type' => 'text', 'options' => [ 'label' => 'Slug' ], 'attributes' => [ 'class' => 'form-control' ], ] ); $this->add( [ 'name' => 'written_on', 'type' => 'text', 'options' => [ 'label' => 'Written On' ], 'attributes' => [ ' class' => 'form-control', 'readonly' => true ], ] ); $this->add( [ 'name' => 'title', 'type' => 'text', 'options' => [ 'label' => 'Title' ], 'attributes' => [ 'class' => 'form-control' ], ] ); $this->add( [ 'name' => 'preview', 'type' => 'textarea', 'options' => [ 'label' => 'Preview' ], 'attributes' => [ 'class' => 'form-control' ], ] ); $this->add( [ 'name' => 'body', 'type' => 'textarea', 'options' => [ 'label' => 'Body' ], 'attributes' => [ 'class' => 'form-control', 'rows' => 20 ], ] ); $this->add( [ 'name' => 'views', 'type' => 'hidden', 'value' => '0', ] ); $this->add( [ 'name' => 'submit', 'type' => 'submit', 'attributes' => [ 'class' => 'btn btn-default margin-top' ], ] ); } public function getInputFilter() { if (!$this->inputFilter) { $this->inputFilter = new InputFilter(); $this->inputFilter->add( [ 'name' => 'slug', 'required' => 'true', 'filters' => [ ['name' => 'StringTrim'], ], 'validators' => [ [ 'name' => 'Regex', 'options' => [ 'pattern' => '/^[a-z0-9-]+$/' ] ], [ 'name' => 'StringLength', 'options' => [ 'min' => 3, 'max' => 64 ] ] ] ] ); $this->inputFilter->add( [ 'name' => 'title', 'required' => true, 'filters' => [ [ 'name' => 'StringTrim', 'name' => 'StripTags', ] ], 'validators' => [ [ 'name' => 'StringLength', 'options' => [ 'min' => 3, 'max' => 64 ] ] ] ] ); $this->inputFilter->add( [ 'name' => 'views', 'required' => false ] ); $this->inputFilter->add( [ 'name' => 'preview', 'required' => true, ] ); $this->inputFilter->add( [ 'name' => 'body', 'required' => true, ] ); } return $this->inputFilter; } }
<!--?php /** @var \Blog\Entity\Post $post */ ?--> <h3>Add Post</h3> <?php $form->setAttribute('action', $this->url()); echo $this->form()->openTag($form); echo $this->formCollection($form); echo $this->form()->closeTag(); ?>
public function addAction() { $form = new PostForm(); $post = new Post(); $form->setHydrator(new ClassMethods())->setObject($post); if ($this->getRequest()->isPost()) { $form->setData($this->getRequest()->getPost()->toArray()); if($form->isValid()) { $this->postService->savePost($form->getObject()); $this->redirect()->toRoute('blog'); } } else { $form->bind($post); } $form->get('submit')->setValue('Add'); return new ViewModel(['post' => $post, 'form' => $form]); }