SlideShare a Scribd company logo
Symfony2, creare bundle e valore
         per il cliente

          Leonardo Proietti
              @_leopro_




             Symfony day
        Torino, 5 ottobre 2012
valore per il cliente = benefici / costi
collaborazione
collaborazione
responsabilità
collaborazione
     responsabilità
reciproca soddisfazione
Il processo di sviluppo basato sulla
collaborazione, sulla reciproca soddisfazione e
sul senso di responsabilità permette di ottenere
           un alto valore per il cliente.
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
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
bundle = una libreria sufficientemente astratta
da essere utilizzata in contesti diversi tra loro
la realizzazione di un bundle non è valore
               per il cliente
Richiesta
un sito web con contenuti aggiornabili
      disponibili in diverse lingue
Analisi
gli strumenti esistenti sono valutati come non
                     adatti
Idea
relazione uno-a-molti, DIC, eventi
Sarà una libreria riutilizzabile?
Sarà una libreria riutilizzabile?
Non è importante perchè l'obiettivo principale è
         produrre valore per il cliente
Ma non vogliamo spaghetti code
Test Driven Development
ovvero sviluppare guidati dai test
Test Driven Development
ovvero sviluppi meglio, vivi meglio
$article->getTitle();

     /it/{article}
  “Il mio articolo”
   /en/{article}
    “My article”
Acme
  CoreBundle
  Listener
  LocaleListener.php
  EntityListener.php
  AnotherListener.php
  Locale
  Locale.php
  LocaleInterface.php
  Model
  Translatable.php
  TranslatableInterface.php
  TranslatingInterface.php
  AnotherModelUtil.php
  Tests
  ...
Acme
  CoreBundle
  Listener
  LocaleListener.php
  EntityListener.php
  AnotherListener.php
  Locale
  Locale.php
  LocaleInterface.php
  Model
  Translatable.php
  TranslatableInterface.php
  TranslatingInterface.php
  AnotherModelUtil.php
  Tests
  ...
interface LocaleInterface
{
   function setLocale($locale);

    function getLocale();

    function getDefaultLocale();
}
use AcmeCoreBundleModelTranslatingInterface;
use AcmeCoreBundleLocaleLocaleInterface;

interface TranslatableInterface
{
   function addTranslation(TranslatingInterface $translation);

    function getTranslations();

    function setLocale(LocaleInterface $locale);
}
use AcmeCoreBundleModelTranslatableInterface;

interface TranslatingInterface
{
    function setTranslatable(TranslatableInterface $translatable);

    function getLocale();

    function setLocale($string);
}
class Article
{
   /**
   * @ORMOneToMany
   * (targetEntity="ArticleI18n", mappedBy="translatable")
   */
   protected $translations;

    public function getTitle()
    {
       return $this->getTranslation()->getTitle();
    }
}
class Article
{
   /**
   * @ORMOneToMany
   * (targetEntity="ArticleI18n", mappedBy="translatable")
   */
   protected $translations;

    public function getTitle()
    {
       return $this->getTranslation()->getTitle();
    }
}
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;
    }
}
Symfony2
kernel.request
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
Doctrine2
postLoad
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
//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'),
     )
}
//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'),
     )
}
//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'),
     )
}
//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'),
     )
}
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);
}
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);
}
Non resta che soddisfare i test scrivendo del
           codice funzionante :-)
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 }
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 }
class Article extends Translatable
{
   /**
   * @ORMOneToMany
   * (targetEntity="ArticleI18n", mappedBy="translatable")
   */
   protected $translations;

    public function getTitle()
    {
       return $this->getTranslation()->getTitle();
    }
}
Una volta testata anche l'integrazione e
  verificato che la richiesta del cliente è stata
soddisfatta, abbiamo prodotto il valore richiesto
             e ci possiamo fermare.
Nuovo cliente, stessa richiesta
Prendiamo il codice e lo isoliamo
       PUGX/I18nBundle
AcmePlus
PUGX
  I18nBundle
  Listener
  LocaleListener.php
  EntityListener.php
  Locale
  Locale.php
  LocaleInterface.php
  Model
  Translatable.php
  TranslatableInterface.php
  TranslatingInterface.php
  Tests
Il grosso è fatto, ora dovremo:

- esporre una configurazione semantica

- gestire le dipendenze (composer) e la configurazione dei test

- aggiungere la documentazione, la licenza ecc.
AcmePlus
PUGX
  I18nBundle
  DependencyInjection
  Configuration.php
  PUGXI18nExtension.php
  […]
  Tests
  bootstrap.php
  PUGXI18nBundle.php
  composer.json
  phpunit.xml.dist
namespace PUGXI18nBundle;

use SymfonyComponentHttpKernelBundleBundle;
use PUGXI18nBundleDependencyInjectionPUGXI18nExtension;

class PUGXI18nBundle extends Bundle
{
    public function getContainerExtension()
    {
      return new PUGXI18nExtension();
    }
}
namespace PUGXI18nBundleDependencyInjection;

use SymfonyComponentConfigDefinitionBuilderTreeBuilder;
use SymfonyComponentConfigDefinitionConfigurationInterface;

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
      $treeBuilder = new TreeBuilder();
      $rootNode = $treeBuilder->root('pugx_i18n');

        return $treeBuilder;
    }
}
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';
    }
}
Composer
gestisce le dipendenze
//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"
//src/PUGX/I18nBundle/Tests/bootstrap.php

if (!is_file($autoloadFile = __DIR__.'/../vendor/autoload.php')) {
      throw new LogicException('Could not find autoload.php');
}

require $autoloadFile;
//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>
Servizio rivolto alla comunità open source per
la continuos integration di progetti pubblicati su
                       github

                 https://travis-ci.org/
//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
Ora basta creare un repository su github e
avremo dato il nostro piccolo contributo alla
         comunità open source.
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
GRAZIE :-)

More Related Content

What's hot

Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
Kacper Gunia
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
Nate Abele
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Kacper Gunia
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
Jorn Oomen
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
G Woo
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
Aleix Vergés
 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
Nate Abele
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
Konstantin Kudryashov
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Kacper Gunia
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
Samuel ROZE
 
The Origin of Lithium
The Origin of LithiumThe Origin of Lithium
The Origin of Lithium
Nate Abele
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Colin O'Dell
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201Fabien Potencier
 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Nate Abele
 

What's hot (20)

Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Min-Maxing Software Costs
Min-Maxing Software CostsMin-Maxing Software Costs
Min-Maxing Software Costs
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers Cracow
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
 
Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010Symfony2 - WebExpo 2010
Symfony2 - WebExpo 2010
 
The Origin of Lithium
The Origin of LithiumThe Origin of Lithium
The Origin of Lithium
 
Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016Hacking Your Way To Better Security - Dutch PHP Conference 2016
Hacking Your Way To Better Security - Dutch PHP Conference 2016
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
 
Dependency Injection IPC 201
Dependency Injection IPC 201Dependency Injection IPC 201
Dependency Injection IPC 201
 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
 

Similar to Symfony2, creare bundle e valore per il cliente

Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
Jakub Zalas
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsMichael Peacock
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitinternational PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitsmueller_sandsmedia
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
Bastian Feder
 
Symfony internals [english]
Symfony internals [english]Symfony internals [english]
Symfony internals [english]
Raul Fraile
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
Jeroen van Dijk
 
Filesystem abstractions and msg queue sergeev - symfony camp 2018
Filesystem abstractions and msg queue   sergeev - symfony camp 2018Filesystem abstractions and msg queue   sergeev - symfony camp 2018
Filesystem abstractions and msg queue sergeev - symfony camp 2018
Юлия Коваленко
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
Kris Wallsmith
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
Elena Kolevska
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown PartsPhpUnit - The most unknown Parts
PhpUnit - The most unknown Parts
Bastian Feder
 
Oops in php
Oops in phpOops in php
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
Nate Abele
 
SPL: The Missing Link in Development
SPL: The Missing Link in DevelopmentSPL: The Missing Link in Development
SPL: The Missing Link in Developmentjsmith92
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
Daniel Knell
 
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for PerformanceMeet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Ivan Chepurnyi
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
Kris Wallsmith
 
Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Fabien Potencier
 
News of the Symfony2 World
News of the Symfony2 WorldNews of the Symfony2 World
News of the Symfony2 WorldFabien Potencier
 
Event Sourcing with php
Event Sourcing with phpEvent Sourcing with php
Event Sourcing with php
Sébastien Houzé
 
Practical PHP 5.3
Practical PHP 5.3Practical PHP 5.3
Practical PHP 5.3
Nate Abele
 

Similar to Symfony2, creare bundle e valore per il cliente (20)

Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnitinternational PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
international PHP2011_Bastian Feder_The most unknown Parts of PHPUnit
 
Php unit the-mostunknownparts
Php unit the-mostunknownpartsPhp unit the-mostunknownparts
Php unit the-mostunknownparts
 
Symfony internals [english]
Symfony internals [english]Symfony internals [english]
Symfony internals [english]
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
 
Filesystem abstractions and msg queue sergeev - symfony camp 2018
Filesystem abstractions and msg queue   sergeev - symfony camp 2018Filesystem abstractions and msg queue   sergeev - symfony camp 2018
Filesystem abstractions and msg queue sergeev - symfony camp 2018
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5Bootstrat REST APIs with Laravel 5
Bootstrat REST APIs with Laravel 5
 
PhpUnit - The most unknown Parts
PhpUnit - The most unknown PartsPhpUnit - The most unknown Parts
PhpUnit - The most unknown Parts
 
Oops in php
Oops in phpOops in php
Oops in php
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
 
SPL: The Missing Link in Development
SPL: The Missing Link in DevelopmentSPL: The Missing Link in Development
SPL: The Missing Link in Development
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for PerformanceMeet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
Meet Magento Sweden - Magento 2 Layout and Code Compilation for Performance
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
 
Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)
 
News of the Symfony2 World
News of the Symfony2 WorldNews of the Symfony2 World
News of the Symfony2 World
 
Event Sourcing with php
Event Sourcing with phpEvent Sourcing with php
Event Sourcing with php
 
Practical PHP 5.3
Practical PHP 5.3Practical PHP 5.3
Practical PHP 5.3
 

Recently uploaded

Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Tobias Schneck
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Inflectra
 
Essentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with ParametersEssentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with Parameters
Safe Software
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
Sri Ambati
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
Product School
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Product School
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Product School
 
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
Product School
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
DianaGray10
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
Alan Dix
 
Search and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesSearch and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical Futures
Bhaskar Mitra
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
BookNet Canada
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
Prayukth K V
 
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
DanBrown980551
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
James Anderson
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
Product School
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
91mobiles
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
Product School
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
Product School
 

Recently uploaded (20)

Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
 
Essentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with ParametersEssentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with Parameters
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
 
Search and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical FuturesSearch and Society: Reimagining Information Access for Radical Futures
Search and Society: Reimagining Information Access for Radical Futures
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
 
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
 
How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...How world-class product teams are winning in the AI era by CEO and Founder, P...
How world-class product teams are winning in the AI era by CEO and Founder, P...
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
 

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.
  • 3. valore per il cliente = benefici / costi
  • 6. collaborazione responsabilità reciproca soddisfazione
  • 7. Il processo di sviluppo basato sulla collaborazione, sulla reciproca soddisfazione e sul senso di responsabilità permette di ottenere un alto valore per il cliente.
  • 8. 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
  • 9. 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
  • 10. bundle = una libreria sufficientemente astratta da essere utilizzata in contesti diversi tra loro
  • 11. la realizzazione di un bundle non è valore per il cliente
  • 12. Richiesta un sito web con contenuti aggiornabili disponibili in diverse lingue
  • 13. Analisi gli strumenti esistenti sono valutati come non adatti
  • 15. Sarà una libreria riutilizzabile?
  • 16. Sarà una libreria riutilizzabile? Non è importante perchè l'obiettivo principale è produrre valore per il cliente
  • 17. Ma non vogliamo spaghetti code
  • 18. Test Driven Development ovvero sviluppare guidati dai test
  • 19. Test Driven Development ovvero sviluppi meglio, vivi meglio
  • 20. $article->getTitle(); /it/{article} “Il mio articolo” /en/{article} “My article”
  • 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. Acme CoreBundle Listener LocaleListener.php EntityListener.php AnotherListener.php Locale Locale.php LocaleInterface.php Model Translatable.php TranslatableInterface.php TranslatingInterface.php AnotherModelUtil.php Tests ...
  • 23. interface LocaleInterface { function setLocale($locale); function getLocale(); function getDefaultLocale(); }
  • 24. use AcmeCoreBundleModelTranslatingInterface; use AcmeCoreBundleLocaleLocaleInterface; interface TranslatableInterface { function addTranslation(TranslatingInterface $translation); function getTranslations(); function setLocale(LocaleInterface $locale); }
  • 25. use AcmeCoreBundleModelTranslatableInterface; interface TranslatingInterface { function setTranslatable(TranslatableInterface $translatable); function getLocale(); function setLocale($string); }
  • 26. class Article { /** * @ORMOneToMany * (targetEntity="ArticleI18n", mappedBy="translatable") */ protected $translations; public function getTitle() { return $this->getTranslation()->getTitle(); } }
  • 27. class Article { /** * @ORMOneToMany * (targetEntity="ArticleI18n", mappedBy="translatable") */ protected $translations; public function getTitle() { return $this->getTranslation()->getTitle(); } }
  • 28. 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; } }
  • 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. 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); }
  • 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. 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); }
  • 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. //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'), ) }
  • 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. 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); }
  • 52. Non resta che soddisfare i test scrivendo del codice funzionante :-)
  • 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. 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 }
  • 55. class Article extends Translatable { /** * @ORMOneToMany * (targetEntity="ArticleI18n", mappedBy="translatable") */ protected $translations; public function getTitle() { return $this->getTranslation()->getTitle(); } }
  • 56. Una volta testata anche l'integrazione e verificato che la richiesta del cliente è stata soddisfatta, abbiamo prodotto il valore richiesto e ci possiamo fermare.
  • 58. Prendiamo il codice e lo isoliamo PUGX/I18nBundle
  • 59. AcmePlus PUGX I18nBundle Listener LocaleListener.php EntityListener.php Locale Locale.php LocaleInterface.php Model Translatable.php TranslatableInterface.php TranslatingInterface.php Tests
  • 60. Il grosso è fatto, ora dovremo: - esporre una configurazione semantica - gestire le dipendenze (composer) e la configurazione dei test - aggiungere la documentazione, la licenza ecc.
  • 61. AcmePlus PUGX I18nBundle DependencyInjection Configuration.php PUGXI18nExtension.php […] Tests bootstrap.php PUGXI18nBundle.php composer.json phpunit.xml.dist
  • 62. namespace PUGXI18nBundle; use SymfonyComponentHttpKernelBundleBundle; use PUGXI18nBundleDependencyInjectionPUGXI18nExtension; class PUGXI18nBundle extends Bundle { public function getContainerExtension() { return new PUGXI18nExtension(); } }
  • 63. namespace PUGXI18nBundleDependencyInjection; use SymfonyComponentConfigDefinitionBuilderTreeBuilder; use SymfonyComponentConfigDefinitionConfigurationInterface; class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('pugx_i18n'); return $treeBuilder; } }
  • 64. 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'; } }
  • 66. //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"
  • 67. //src/PUGX/I18nBundle/Tests/bootstrap.php if (!is_file($autoloadFile = __DIR__.'/../vendor/autoload.php')) { throw new LogicException('Could not find autoload.php'); } require $autoloadFile;
  • 68. //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>
  • 69. Servizio rivolto alla comunità open source per la continuos integration di progetti pubblicati su github https://travis-ci.org/
  • 70. //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
  • 71. Ora basta creare un repository su github e avremo dato il nostro piccolo contributo alla comunità open source.