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

Модули в Zend Framework 2

Dd3f18c87b851137000c7427d7bd5d32?s=47 fwdays
November 24, 2012

Модули в Zend Framework 2

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

Dd3f18c87b851137000c7427d7bd5d32?s=128

fwdays

November 24, 2012
Tweet

More Decks by fwdays

Other Decks in Programming

Transcript

  1. Модули в Zend Framework 2 Rostislav Mykhajliw oDesk/TrueSocialMetrics

  2. Modules Configuration DI Events Testing Distribution Trick Tips

  3. Init github: ZendSkeletonApplication github: ZendSkeletonModule

  4. 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
  5. 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
  6. Configuration

  7. 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); }
  8. Init ZF2 module - simple. namespace ZendSkeletonModule; class Module {

    public function getConfig() { echo "I was there"; exit; } } Works!
  9. 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
  10. 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()
  11. Config :: merging application.config.php -> foreach modules configs -> Application

    module -> Api module -> ... -> global.php -> local.php
  12. 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
  13. Config :: merging 'TweeCdn\View\Helper\Cdn\AbstractCdn' => array( 'parameters' => array( 'type'

    => 'release', 'options' => array( 'hostnames' => array(''), 'release' => '000000000', ), ), ), local.php
  14. Config :: merging <link href="http://cdn.com/css/e510f4ecb884512172b0620de682de1a/style.css" media="screen" rel="stylesheet" type="text/css"> <link href="/css/0000000000/bootstrap-responsive.min.css"

    media="screen" rel="stylesheet" type="text/css">
  15. Config :: merging

  16. 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 !
  17. 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)
  18. DI (Dependency Injection)

  19. 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( ... )
  20. DI :: configuration <?php namespace Twitter\Controller; use Zend\Mvc\Controller\AbstractActionController; class IndexController

    extends AbstractActionController { public function indexAction() { } }
  21. 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() {} }
  22. 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.
  23. DI :: Why ? protected function createFromInvokable($canonicalName, $requestedName) { $invokable

    = $this->invokableClasses[$canonicalName]; $instance = new $invokable(); return $instance; } You have Invokables CONTROLLERS !!! Zend\ServiceManager\AbstractPluginManager
  24. 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!
  25. 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; } )); }
  26. 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; } }
  27. 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;
  28. 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) {} }
  29. Events

  30. 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'; }
  31. 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'; }
  32. Events :: workflow

  33. 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!' )); } }
  34. 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()); }); }
  35. Events :: Bootstrap :: in config return array( 'view_manager' =>

    array( 'strategies' => array( 'ViewJsonStrategy', ), ), ); All modules affected!
  36. Controllers

  37. AbstractController abstract class AbstractController { public function getEventManager(); // EventManager

    public function getServiceLocator(); // ServiceManager public function plugin($name, $options=null) public function getRequest(); public function getResponse(); }
  38. 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 )); } }
  39. 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.
  40. 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); }
  41. 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
  42. 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) { // ... } } }
  43. Testing and Distribution

  44. Testing :: tests ├── TestConfiguration.php.dist ├── ZendSkeletonModule │ ├── Framework

    │ │ └── TestCase.php │ └── SampleTest.php ├── bootstrap.php └── phpunit.xml
  45. 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()); } }
  46. Testing

  47. 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'')); } }
  48. 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()); } }
  49. Tests :: structure ├── module | ├── Api | |

    └── tests | ├── Application | | └── tests | └── Registration | └── tests └── phpunit.xml
  50. 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>
  51. Tests :: bootstrap.php <?php include_once "module/Api/tests/bootstrap.php"; include_once "module/Application/tests/bootstrap.php"; include_once "module/Registration/tests/bootstrap.php";

  52. Distribution

  53. Composer # curl -s https://getcomposer.org/installer | php # composer.phar install

    # composer.phar update
  54. 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": {} } }
  55. Composer :: author "authors": [ { "name": "Rostislav Mykhajliw", "email":

    "necromant2005@gmail.com", "homepage": "https://github.com/necromant2005", "role": "Developer" } ],
  56. 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" } ],
  57. 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.*", },
  58. Distribution Composer: https://packagist.org/ ZF2 modules: http://modules.zendframework.com/ Private(internal company usage): https://github.com/composer/packagist

  59. CI (continuous integration)

  60. 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
  61. 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: - necromant2005@gmail.com https://github.com/necromant2005/test/blob/master/.travis.yml
  62. Acceptable tests :: Selenium?

  63. 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
  64. 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); });
  65. Casperjs http://casperjs.org/installation.html

  66. 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')); });
  67. Questions? http://framework.zend.com/manual/2.0/en/user-guide/unit-testing.html http://about.travis-ci.org/docs/user/languages/php/ http://casperjs.org/testing.html http://getcomposer.org/doc/00-intro.md