Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Модули в Zend Framework 2

fwdays
November 24, 2012

Модули в Zend Framework 2

— Разработка, структура, конфигурация и интеграция модулей
— Событийная модель для модулей. Управление зависимостями.
— Вопросы распространения, composer, modules.zendframework.com
— Тестирование, Travis-CI

fwdays

November 24, 2012
Tweet

More Decks by fwdays

Other Decks in Programming

Transcript

  1. Init ZF2 application ├── README.md ├── composer.json ├── config │

    ├── application.config.php │ └── autoload │ └── global.php │ └── local.php ├── data ├── init_autoloader.php ├── module │ └── Application ├── public └── vendor └── ZF2
  2. Init ZF2 module ├── Module.php ├── README.md ├── autoload_classmap.php /

    autoload_function.php / autoload_register.php ├── config │ └── module.config.php ├── src │ └── ZendSkeletonModule ├── tests │ ├── TestConfiguration.php.dist │ ├── ZendSkeletonModule │ ├── bootstrap.php │ └── phpunit.xml └── view └── zend-skeleton-module
  3. Init ZF2 module namespace ZendSkeletonModule; use Zend\ModuleManager\Feature\AutoloaderProviderInterface; use Zend\Mvc\ModuleRouteListener; class

    Module implements AutoloaderProviderInterface { public function getAutoloaderConfig() public function getConfig() public function onBootstrap($e); }
  4. Init ZF2 module - simple. namespace ZendSkeletonModule; class Module {

    public function getConfig() { echo "I was there"; exit; } } Works!
  5. Config :: Why? public function onLoadModule(ModuleEvent $e) { $module =

    $e->getModule(); if (!$module instanceof ConfigProviderInterface && !is_callable(array($module, 'getConfig')) ) { return $this; } $config = $module->getConfig(); $this->addConfig($e->getModuleName(), $config); return $this; } Zend\ModuleManager\Listener\ConfigListener
  6. Config :: interfaces Zend\ModuleManager\Feature ├── AutoloaderProviderInterface => getAutoloaderConfig ├── BootstrapListenerInterface

    => onBoostrap ├── ConfigProviderInterface => getConfig ├── ConsoleBannerProviderInterface => getConsoleBanner (in console) ├── ConsoleUsageProviderInterface => getConsoleUsage (in console) ├── ControllerPluginProviderInterface => getControllerPluginConfig() ├── ControllerProviderInterface => getControllerConfig() ├── InitProviderInterface => init(ModuleManagerInterface $manager) ├── LocatorRegisteredInterface => LOAD by DI ├── ServiceProviderInterface => getServiceConfig() └── ViewHelperProviderInterface => getViewHelperConfig()
  7. Config :: merging application.config.php -> foreach modules configs -> Application

    module -> Api module -> ... -> global.php -> local.php
  8. Config :: merging 'TweeCdn\View\Helper\Cdn\AbstractCdn' => array( 'parameters' => array( 'type'

    => 'release', 'options' => array( 'hostnames' => array('http://cdn.com'), 'release' => trim(file_get_contents(__DIR__ . '/../../REVISION')), ), ), ), global.php
  9. Config :: merging 'TweeCdn\View\Helper\Cdn\AbstractCdn' => array( 'parameters' => array( 'type'

    => 'release', 'options' => array( 'hostnames' => array(''), 'release' => '000000000', ), ), ), local.php
  10. Config :: merging :: Why ? $a = array(1, 2);

    $b = array(2, 3); var_dump(array_merge($a, $b)); array(4) { [0] => int(1) [1] => int(2) [2] => int(2) [3] => int(3) } use ONLY string KEYS !
  11. Config :: recommendation module config: - default base configuration. Minimum

    config to work (routes, views) global config: - global things, DI definition, production settings local config: - local, environment dependable (db host)
  12. DI :: configuration return array( 'router' => array( 'routes' =>

    array( 'controllers' => array( 'invokables' => array( 'twitter' => 'Twitter\Controller\IndexController', 'di' => array( 'definition' => array( 'class' => array( ... ) 'instance' => array( 'alias' => array( ... )
  13. DI :: configuration :: application key <?php namespace Twitter\Controller; use

    Zend\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { pirvate $key = ''; public function setApplicationKey($key) { $this->key = $key; } public function getApplicatonKey() { return $key; } public function indexAction() {} }
  14. DI :: configuration :: application key 'di' => array( 'instance'

    => array( 'Twitter\Controller\IndexController' => array( 'parameters' => array( 'key' => 'my-secret-application-key' ), ), ), ), Let's add options to though DI config Works ??? NO.
  15. DI :: Why ? protected function createFromInvokable($canonicalName, $requestedName) { $invokable

    = $this->invokableClasses[$canonicalName]; $instance = new $invokable(); return $instance; } You have Invokables CONTROLLERS !!! Zend\ServiceManager\AbstractPluginManager
  16. DI :: configuration / Works return array( 'router' => array(

    'routes' => array( 'controllers' => array( 'invokables' => array( // 'twitter' => 'Twitter\Controller\IndexController', Remove class from invokables and replace to factories!
  17. DI :: Factories / Works namespace Twitter; class Module implements

    Feature\ControllerProviderInterface { public function getControllerConfig() { return array('factories' => array( 'Twitter\Controller\TwitterController' => function ($cm) { $sm = $cm->getServiceLocator(); $config = $sm->get('config'); $key = $config['di']['instance']['TwitterController']['parameters']['key']; $controller = new Controller\IndexController(); $controller->setApplicationKey($key); return $controller; } )); }
  18. DI :: factories :: interfaces namespace Application\Controller; interface TokenInterface {

    public function setToken($token); } namespace Application\Controller; class IndexController implements TokenInterface { private $token = ''; public function setToken($token) { $this->token = $token; } }
  19. DI :: factories :: interfaces class Module implements Feature\ControllerProviderInterface public

    function getControllerConfig() { return array('factories' => array( 'Twitter\Controller\TokenInteface' => function ($cm, $cmKey, $name) { // $cm - Zend\Mvc\Controller\ControllerManager // $cmKey - internal key for service locator // $name - class name $sm = $cm->getServiceLocator(); $config = $sm->get('config'); $token = $config['di']['instance'][$name]['parameters']['token']; $controller = new $name(); $controller->setToken($token); return $controller;
  20. DI :: configuration :: factories return array( 'controllers' => array(

    'invokables' => array( // 'twitter' => 'Twitter\Controller\IndexController', 'factories' => array( 'Index' => 'Twitter\Controller\Factory\Token' namespace Twitter\Controller\Factory; use \Zend\ServiceManager\FactoryInterface; use \Zend\ServiceManager\ServiceLocatorInterface; class Token implements FactoryInterface { public function createService(ServiceLocatorInterface $serviceLocator) {} }
  21. Events namespace Zend\Mvc; class MvcEvent extends Event { const EVENT_BOOTSTRAP

    = 'bootstrap'; const EVENT_DISPATCH = 'dispatch'; const EVENT_DISPATCH_ERROR = 'dispatch.error'; const EVENT_FINISH = 'finish'; const EVENT_RENDER = 'render'; const EVENT_ROUTE = 'route'; }
  22. Events :: Loading namespace Zend\ModuleManager; class ModuleEvent extends Event {

    CONST EVENT_LOAD_MODULES = 'loadModules'; CONST EVENT_LOAD_MODULE_RESOLVE = 'loadModule.resolve'; CONST EVENT_LOAD_MODULE = 'loadModule'; CONST EVENT_LOAD_MODULES_POST = 'loadModules.post'; }
  23. Events :: Bootstrap <?php namespace Api\Controller; use Zend\Mvc\Controller\AbstractActionController, Zend\View\Model\JsonModel; class

    IndexController extends AbstractActionController { public function indexAction() { return new JsonModel(array( 'message' => 'Congratulations!' )); } }
  24. Events :: Bootstrap namespace Api; use Zend\ModuleManager\Feature, Zend\EventManager\EventInterface, Zend\Mvc\MvcEvent; class

    Module implements Feature\BootstrapListenerInterface { public function onBootstrap(EventInterface $e) { $app = $e->getApplication(); $em = $app->getEventManager()->getSharedManager(); $sm = $app->getServiceManager(); $em->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, function($e) use ($sm) { $strategy = $sm->get('ViewJsonStrategy'); $view = $sm->get('ViewManager')->getView(); $strategy->attach($view->getEventManager()); }); }
  25. Events :: Bootstrap :: in config return array( 'view_manager' =>

    array( 'strategies' => array( 'ViewJsonStrategy', ), ), ); All modules affected!
  26. AbstractController abstract class AbstractController { public function getEventManager(); // EventManager

    public function getServiceLocator(); // ServiceManager public function plugin($name, $options=null) public function getRequest(); public function getResponse(); }
  27. AbstractActionController class IndexController extends AbstractActionController { public function indexAction() {

    $name = $this->getRequest() ->getQuery()->get('name'); $book = $this->getServiceLocator() ->get('Repository/Book') ->find($name); return new ViewModel(array( 'book' => $book )); } }
  28. AbstractActionController class IndexController extends AbstractActionController { public function pictureAction() {

    $response = new Response(); $response->setStatusCode(Response::STATUS_CODE_200); $response->getHeaders() ->addHeaderLine('Content-Type: image/png') ->addHeaderLine('Content-Length: ' . (string)filesize($file)); $response->setContent(file_get_ocntents($file)); return $response; } } It ignores everything! and returns response.
  29. AbstractRestfulController abstract class AbstractRestfulController extends AbstractController { abstract public function

    getList(); abstract public function get($id); abstract public function create($data); abstract public function update($id, $data); abstract public function delete($id); }
  30. AbstractRestfulController class IndexController extends AbstractRestfulController { public function getList() {

    $response = $this->getResponse(); $response->setStatusCode(200); $response->setContent(new ModelJson(array( array('name' => 'jo'), array('name' => 'peter'), ))); return $response; } } Sample
  31. Rest :: ZF 2.0.4 (broken DI) class SomeController extends AbstractActionController

    { protected $acceptCriteria = array( 'Zend\View\Model\JsonModel' => array( 'application/json', ), 'Zend\View\Model\FeedModel' => array( 'application/rss+xml', ), ); public function apiAction() { $viewModel = $this->acceptableViewModelSelector($this->acceptCriteria); // Potentially vary execution based on model returned if ($viewModel instanceof JsonModel) { // ... } } }
  32. Testing :: tests ├── TestConfiguration.php.dist ├── ZendSkeletonModule │ ├── Framework

    │ │ └── TestCase.php │ └── SampleTest.php ├── bootstrap.php └── phpunit.xml
  33. Testing :: Works (but slow) namespace Application\Controller; class IndexControllerTest extends

    Framework\ControllerTestCase { protected function setUp() { $bootstrap = \Zend\Mvc\Application::init(include 'application.php'); $this->controller = new IndexController(); $this->request = new Request(); $this->routeMatch = new RouteMatch(array('controller' => 'index')); $this->event = $bootstrap->getMvcEvent(); $this->event->setRouteMatch($this->routeMatch); $this->controller->setEvent($this->event); $this->controller->setEventManager($bootstrap->getEventManager()); $this->controller->setServiceLocator($bootstrap->getServiceManager()); } }
  34. Tests :: be simpler namespace Application\Controller; class IndexControllerTest extends Framework\TestCase

    { protected function setUp() { $this->controller = new IndexController(); $this->controller->setServiceLocator(new ServiceManager()); } public function testloadForm() { $response = $this->controller->indexAction(); $this->assertIntanceOf('Form', $response->getVariable(form'')); } }
  35. Tests :: be simpler <?php namespace Application\Form\Connect; class GooglePlusTest extends

    Framework\FormTestCase { public function testForm() { $form = new FormConnect(); $form->setData(array( 'name' => 'https://plus.google.com/108793379252097909860/posts' )); $this->assertTrue($form->isValid()); } }
  36. Tests :: structure ├── module | ├── Api | |

    └── tests | ├── Application | | └── tests | └── Registration | └── tests └── phpunit.xml
  37. Tests :: phpunit.xml <?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="false" bootstrap="tests/bootstrap.php"> <testsuites>

    <testsuite name="Api"> <directory>module/Api/tests</directory> </testsuite> <testsuite name="Application"> <directory>module/Application/tests</directory> </testsuite> </testsuites> </phpunit>
  38. Composer { "name": "necromant2005/cdn", "type": "library", "description": "my super project",

    "keywords": ["my super project"], "homepage": "https://github.com/necromant2005/cdn.git", "authors": [] "repositories": [], "require": {}, "autoload": { "psr-0": {}, "classmap": {} } }
  39. Composer :: author "authors": [ { "name": "Rostislav Mykhajliw", "email":

    "[email protected]", "homepage": "https://github.com/necromant2005", "role": "Developer" } ],
  40. Composer :: repositories "repositories": [ { "type": "composer", "url": "http://packages.zendframework.com/"

    }, { "type": "vcs", "url": "https://github.com/necromant2005/cdn" }, { "type": "vcs", "url": "https://github.com/zendframework/ZendService_Twitter" } ],
  41. Composer :: require "require": { "php": ">=5.3.0", "necromant2005/cdn": "@dev", "zendframework/zendframework":

    "2.0.3", "zendframework/zendservice-twitter": "dev- master#1708df156b64cff0c79e3db783476b7900714db5", "zendframework/zendgdata": "2.*", "zendframework/zendrest": "2.0.*", },
  42. Travis-ci + free (but also paid solution for ) +

    works with github (only) + clear environment every run + multiple php version + easy integration and notification + all configuration in one file .travis.yml But only execution limit - 35 min
  43. Travis-ci language: php php: - 5.4 before_script: - sudo apt-get

    update -qq - sudo apt-get install -qq memcached memcachedb tidy - printf "\n"| sudo pecl install memcache - composer install notifications: email: - [email protected] https://github.com/necromant2005/test/blob/master/.travis.yml
  44. Casper.js + Full JS/Ajax supports + Phantomjs as backend +

    A lots of helpers and tools + Do NOT open browser windows + Simple readable code and runner + alert/js error handling http://casperjs.org/index.html
  45. Casperjs var casper = require('casper').create(); casper.start('http://www.google.com/', function() { this.test.assertExists('form[action="/search"]', 'main

    form is found'); this.fill('form[action="/search"]', { q: 'foo' }, true); }); casper.then(function() { this.test.assertTitle('foo - Recherche Google', 'google title is ok'); }); casper.run(function() { this.test.renderResults(true); });
  46. Casperjs :: tips this.capture("true-form.png"); // xpath var xpath = require('casper').selectXPath;

    this.test.assertSelectorHasText(xpath("//table[@class='table table- striped']/tbody/tr[1]/td[4]"), "20"); // wait ajax for 10 seconds this.waitWhileSelector('div[name="cacheGooglePlus"]', function() { this.echo('div[name="cacheGooglePlus"] is no more!'); }, 10); // click the link this.click('a[href="/metric"]'); // phantomjs object access and get base64 resource attr logo = this.evaluate(function() { return __utils__.getBase64(document.querySelector('img.logo').getAttribute('src')); });