Advertisement

Symfony2, creare bundle e valore per il cliente

Software Engineer at LoveCrafts
Oct. 8, 2012
Advertisement

More Related Content

Advertisement

Symfony2, creare bundle e valore per il cliente

  1. Symfony2, creare bundle e valore per il cliente Leonardo Proietti @_leopro_ Symfony day Torino, 5 ottobre 2012
  2. valore per il cliente = benefici / costi
  3. collaborazione
  4. collaborazione responsabilità
  5. collaborazione responsabilità reciproca soddisfazione
  6. Il processo di sviluppo basato sulla collaborazione, sulla reciproca soddisfazione e sul senso di responsabilità permette di ottenere un alto valore per il cliente.
  7. bundle = è una cartella con una struttura ben definita, che può ospitare qualsiasi cosa, dalle classi ai controllori e alle risorse web http://symfony.com/it/doc/current/cookbook/bundles/best_practices.html
  8. bundle = uno spazio dei nomi di PHP ovvero un namespace, con con una classe Bundle http://symfony.com/it/doc/current/cookbook/bundles/best_practices.html
  9. bundle = una libreria sufficientemente astratta da essere utilizzata in contesti diversi tra loro
  10. la realizzazione di un bundle non è valore per il cliente
  11. Richiesta un sito web con contenuti aggiornabili disponibili in diverse lingue
  12. Analisi gli strumenti esistenti sono valutati come non adatti
  13. Idea relazione uno-a-molti, DIC, eventi
  14. Sarà una libreria riutilizzabile?
  15. Sarà una libreria riutilizzabile? Non è importante perchè l'obiettivo principale è produrre valore per il cliente
  16. Ma non vogliamo spaghetti code
  17. Test Driven Development ovvero sviluppare guidati dai test
  18. Test Driven Development ovvero sviluppi meglio, vivi meglio
  19. $article->getTitle(); /it/{article} “Il mio articolo” /en/{article} “My article”
  20. Acme CoreBundle Listener LocaleListener.php EntityListener.php AnotherListener.php Locale Locale.php LocaleInterface.php Model Translatable.php TranslatableInterface.php TranslatingInterface.php AnotherModelUtil.php Tests ...
  21. Acme CoreBundle Listener LocaleListener.php EntityListener.php AnotherListener.php Locale Locale.php LocaleInterface.php Model Translatable.php TranslatableInterface.php TranslatingInterface.php AnotherModelUtil.php Tests ...
  22. interface LocaleInterface { function setLocale($locale); function getLocale(); function getDefaultLocale(); }
  23. use AcmeCoreBundleModelTranslatingInterface; use AcmeCoreBundleLocaleLocaleInterface; interface TranslatableInterface { function addTranslation(TranslatingInterface $translation); function getTranslations(); function setLocale(LocaleInterface $locale); }
  24. use AcmeCoreBundleModelTranslatableInterface; interface TranslatingInterface { function setTranslatable(TranslatableInterface $translatable); function getLocale(); function setLocale($string); }
  25. class Article { /** * @ORMOneToMany * (targetEntity="ArticleI18n", mappedBy="translatable") */ protected $translations; public function getTitle() { return $this->getTranslation()->getTitle(); } }
  26. class Article { /** * @ORMOneToMany * (targetEntity="ArticleI18n", mappedBy="translatable") */ protected $translations; public function getTitle() { return $this->getTranslation()->getTitle(); } }
  27. class ArticleI18n { /** * @ORMManyToOne(targetEntity="Article", inversedBy="translations" * @ORMJoinColumn(name="article_id", referencedColumnName="id") */ protected $translatable; /** * @ORMColumn(type="string", length=128) */ private $title; public function getTitle() { return $this->title; } }
  28. Symfony2 kernel.request
  29. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  30. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  31. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  32. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  33. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  34. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  35. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  36. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  37. public function testOnKernelRequest() { //SymfonyComponentHttpKernelEventGetResponseEvent $this->event->expects($this->once() ->method('getRequest') ->will($this->returnValue($this->request)); //SymfonyComponentHttpFoundationRequest $this->request->expects($this->once()) ->method('getLocale') ->will($this->returnValue('en')); //AcmeCoreBundleLocaleLocaleInterface $this->locale->expects($this->once()) ->method('setLocale') ->with('en'); //AcmeCoreBundleListenerLocaleListener $this->listener = new LocaleListener($this->locale); $this->listener->onKernelRequest($this->event); }
  38. Doctrine2 postLoad
  39. public function testPostLoad() { //AcmeCoreBundleLocaleLocaleInterface $this->locale; //DoctrineORMEventLifecycleEventArgs $this->args->expects($this->once()) ->method('getEntity') ->will($this->returnValue($this->entity)); //AcmeCoreBundleModelTranslatableInterface $this->entity->expects($this->once()) ->method('setLocale') ->with($this->locale); //AcmeCoreBundleListenerEntityListener $this->listener = new TranslatableListener($this->locale); $this->listener->postLoad($this->event); }
  40. public function testPostLoad() { //AcmeCoreBundleLocaleLocaleInterface $this->locale; //DoctrineORMEventLifecycleEventArgs $this->args->expects($this->once()) ->method('getEntity') ->will($this->returnValue($this->entity)); //AcmeCoreBundleModelTranslatableInterface $this->entity->expects($this->once()) ->method('setLocale') ->with($this->locale); //AcmeCoreBundleListenerEntityListener $this->listener = new TranslatableListener($this->locale); $this->listener->postLoad($this->event); }
  41. public function testPostLoad() { //AcmeCoreBundleLocaleLocaleInterface $this->locale; //DoctrineORMEventLifecycleEventArgs $this->args->expects($this->once()) ->method('getEntity') ->will($this->returnValue($this->entity)); //AcmeCoreBundleModelTranslatableInterface $this->entity->expects($this->once()) ->method('setLocale') ->with($this->locale); //AcmeCoreBundleListenerEntityListener $this->listener = new TranslatableListener($this->locale); $this->listener->postLoad($this->event); }
  42. public function testPostLoad() { //AcmeCoreBundleLocaleLocaleInterface $this->locale; //DoctrineORMEventLifecycleEventArgs $this->args->expects($this->once()) ->method('getEntity') ->will($this->returnValue($this->entity)); //AcmeCoreBundleModelTranslatableInterface $this->entity->expects($this->once()) ->method('setLocale') ->with($this->locale); //AcmeCoreBundleListenerEntityListener $this->listener = new TranslatableListener($this->locale); $this->listener->postLoad($this->event); }
  43. public function testPostLoad() { //AcmeCoreBundleLocaleLocaleInterface $this->locale; //DoctrineORMEventLifecycleEventArgs $this->args->expects($this->once()) ->method('getEntity') ->will($this->returnValue($this->entity)); //AcmeCoreBundleModelTranslatableInterface $this->entity->expects($this->once()) ->method('setLocale') ->with($this->locale); //AcmeCoreBundleListenerEntityListener $this->listener = new TranslatableListener($this->locale); $this->listener->postLoad($this->event); }
  44. public function testPostLoad() { //AcmeCoreBundleLocaleLocaleInterface $this->locale; //DoctrineORMEventLifecycleEventArgs $this->args->expects($this->once()) ->method('getEntity') ->will($this->returnValue($this->entity)); //AcmeCoreBundleModelTranslatableInterface $this->entity->expects($this->once()) ->method('setLocale') ->with($this->locale); //AcmeCoreBundleListenerEntityListener $this->listener = new TranslatableListener($this->locale); $this->listener->postLoad($this->event); }
  45. //tranlsations, defaultLocale, locale, tranlsationExpected public function getTranslationProvider() { return array( array( array('it' => 'translationIt', 'en' => 'translationEn'), 'en', 'en', 'translationEn'), array( array('en' => 'translationEn', 'it' => 'translationIt'), 'en', 'it', 'translationIt'), array( array('en' => 'translationEn'), 'en', 'it', 'translationEn'), ) }
  46. //tranlsations, defaultLocale, locale, tranlsationExpected public function getTranslationProvider() { return array( array( array('it' => 'translationIt', 'en' => 'translationEn'), 'en', 'en', 'translationEn'), array( array('en' => 'translationEn', 'it' => 'translationIt'), 'en', 'it', 'translationIt'), array( array('en' => 'translationEn'), 'en', 'it', 'translationEn'), ) }
  47. //tranlsations, defaultLocale, locale, tranlsationExpected public function getTranslationProvider() { return array( array( array('it' => 'translationIt', 'en' => 'translationEn'), 'en', 'en', 'translationEn'), array( array('en' => 'translationEn', 'it' => 'translationIt'), 'en', 'it', 'translationIt'), array( array('en' => 'translationEn'), 'en', 'it', 'translationEn'), ) }
  48. //tranlsations, defaultLocale, locale, tranlsationExpected public function getTranslationProvider() { return array( array( array('it' => 'translationIt', 'en' => 'translationEn'), 'en', 'en', 'translationEn'), array( array('en' => 'translationEn', 'it' => 'translationIt'), 'en', 'it', 'translationIt'), array( array('en' => 'translationEn'), 'en', 'it', 'translationEn'), ) }
  49. public function testGetTranslation($translations, $defaultLocale, $locale, $translationExpected) { $this->translatable = new Translatable(); $this->translatable->setLocale($this->locale); foreach ($translations as $language => $translation) { $this->$translation->expects($this->exactly(2))->method('getLocale') ->will($this->returnValue($language)); $this->translatable->addTranslation($this->$translation); } $this->locale->expects($this->exactly(1)) ->method('getDefaultLocale') ->will($this->returnValue($defaultLocale)); $this->locale->expects($this->exactly(1)) ->method('getLocale') ->will($this->returnValue($locale)); $result = $this->translatable->getTranslation(); $this->assertEquals($this->$translationExpected, $result); }
  50. public function testGetTranslation($translations, $defaultLocale, $locale, $translationExpected) { $this->translatable = new Translatable(); $this->translatable->setLocale($this->locale); foreach ($translations as $language => $translation) { $this->$translation->expects($this->exactly(2))->method('getLocale') ->will($this->returnValue($language)); $this->translatable->addTranslation($this->$translation); } $this->locale->expects($this->exactly(1)) ->method('getDefaultLocale') ->will($this->returnValue($defaultLocale)); $this->locale->expects($this->exactly(1)) ->method('getLocale') ->will($this->returnValue($locale)); $result = $this->translatable->getTranslation(); $this->assertEquals($this->$translationExpected, $result); }
  51. Non resta che soddisfare i test scrivendo del codice funzionante :-)
  52. configuriamo i servizi //src/Acme/CoreBundle/Resources/config/services.yml services: acme_core.locale: class: AcmeCoreBundleServiceLocale arguments: [%locale%] acme_core.listener.locale: class: AcmeCoreBundleServiceLocaleListener arguments: ["@acme.core.locale"] tags: - { name: kernel.event_listener, event: kernel.request } acme_core.listener.entity: class: AcmeCoreBundleServiceEntityListener arguments: ["@acme.core.locale"] tags: - { name: doctrine.event_listener, event: postLoad }
  53. configuriamo i servizi //src/Acme/CoreBundle/Resources/config/services.yml services: acme_core.locale: class: AcmeCoreBundleServiceLocale arguments: [%locale%] acme_core.listener.locale: class: AcmeCoreBundleServiceLocaleListener arguments: ["@acme.core.locale"] tags: - { name: kernel.event_listener, event: kernel.request } acme_core.listener.entity: class: AcmeCoreBundleServiceEntityListener arguments: ["@acme.core.locale"] tags: - { name: doctrine.event_listener, event: postLoad }
  54. class Article extends Translatable { /** * @ORMOneToMany * (targetEntity="ArticleI18n", mappedBy="translatable") */ protected $translations; public function getTitle() { return $this->getTranslation()->getTitle(); } }
  55. Una volta testata anche l'integrazione e verificato che la richiesta del cliente è stata soddisfatta, abbiamo prodotto il valore richiesto e ci possiamo fermare.
  56. Nuovo cliente, stessa richiesta
  57. Prendiamo il codice e lo isoliamo PUGX/I18nBundle
  58. AcmePlus PUGX I18nBundle Listener LocaleListener.php EntityListener.php Locale Locale.php LocaleInterface.php Model Translatable.php TranslatableInterface.php TranslatingInterface.php Tests
  59. Il grosso è fatto, ora dovremo: - esporre una configurazione semantica - gestire le dipendenze (composer) e la configurazione dei test - aggiungere la documentazione, la licenza ecc.
  60. AcmePlus PUGX I18nBundle DependencyInjection Configuration.php PUGXI18nExtension.php […] Tests bootstrap.php PUGXI18nBundle.php composer.json phpunit.xml.dist
  61. namespace PUGXI18nBundle; use SymfonyComponentHttpKernelBundleBundle; use PUGXI18nBundleDependencyInjectionPUGXI18nExtension; class PUGXI18nBundle extends Bundle { public function getContainerExtension() { return new PUGXI18nExtension(); } }
  62. namespace PUGXI18nBundleDependencyInjection; use SymfonyComponentConfigDefinitionBuilderTreeBuilder; use SymfonyComponentConfigDefinitionConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('pugx_i18n'); return $treeBuilder; } }
  63. namespace PUGXI18nBundleDependencyInjection; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentConfigFileLocator; use SymfonyComponentHttpKernelDependencyInjectionExtension; use SymfonyComponentDependencyInjectionLoaderYamlFileLoader; class PUGXI18nExtension implements Extension { public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $path = __DIR__.'/../Resources/config' $loader = new YamlFileLoader($container, new FileLocator($path)); $loader->load('services.yml'); } public function getAlias() { return 'pugx_i18n'; } }
  64. Composer gestisce le dipendenze
  65. //src/PUGX/I18nBundle/composer.json { "name": "pugx/i18n-bundle", "type": "symfony-bundle", "description": "Manage i18n", "keywords": ["symfony2, i18n, translation"], "license": "MIT", "authors": [ { "name":"Leonardo Proietti", "email":"leonardo.proietti@gmail.com" } ], "minimum-stability": "dev", "require": { "php": ">=5.3.2", "symfony/framework-bundle": "2.1.*", "doctrine/orm": ">=2.2.3,<2.4-dev", "symfony/yaml": "2.1.*" }, "autoload": { "psr-0": { "PUGXI18nBundle": "" } }, "target-dir": "PUGX/I18nBundle"
  66. //src/PUGX/I18nBundle/Tests/bootstrap.php if (!is_file($autoloadFile = __DIR__.'/../vendor/autoload.php')) { throw new LogicException('Could not find autoload.php'); } require $autoloadFile;
  67. //src/PUGX/I18nBundle/phpunit.xml.dist <?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="./Tests/bootstrap.php"> <testsuites> <testsuite name="PUGXI18nBundle test suite"> <directory suffix="Test.php">./Tests</directory> </testsuite> </testsuites> <filter> <whitelist> <directory>./</directory> <exclude> <directory>./Resources</directory> <directory>./Tests</directory> </exclude> </whitelist> </filter> </phpunit>
  68. Servizio rivolto alla comunità open source per la continuos integration di progetti pubblicati su github https://travis-ci.org/
  69. //src/PUGX/I18nBundle/.travis.yml language: php php: - 5.3 - 5.4 before_script: - curl -s http://getcomposer.org/installer | php - php composer.phar install –dev
  70. Ora basta creare un repository su github e avremo dato il nostro piccolo contributo alla comunità open source.
  71. Credits symfony.com/it/doc/2.0/book/internals.html symfony.com/it/doc/2.0/cookbook/bundles/best_practices.html symfony.com/it/doc/2.0/cookbook/bundles/extension.html sviluppoagile.it PUG Roma
  72. GRAZIE :-)
Advertisement