Symfony2, creare bundle e valore per il cliente

download Symfony2, creare bundle e valore per il cliente

If you can't read please download the document

Transcript of Symfony2, creare bundle e valore per il cliente

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 Acme\CoreBundle\Model\TranslatingInterface;
use Acme\CoreBundle\Locale\LocaleInterface;

interface TranslatableInterface
{
function addTranslation(TranslatingInterface $translation);

function getTranslations();

function setLocale(LocaleInterface $locale);
}


use Acme\CoreBundle\Model\TranslatableInterface;

interface TranslatingInterface
{
function setTranslatable(TranslatableInterface $translatable);

function getLocale();

function setLocale($string);
}


class Article
{
/**
* @ORM\OneToMany
* (targetEntity="ArticleI18n", mappedBy="translatable")
*/
protected $translations;

public function getTitle()
{
return $this->getTranslation()->getTitle();
}
}


class Article
{
/**
* @ORM\OneToMany
* (targetEntity="ArticleI18n", mappedBy="translatable")
*/
protected $translations;

public function getTitle()
{
return $this->getTranslation()->getTitle();
}
}

class ArticleI18n
{
/**
* @ORM\ManyToOne(targetEntity="Article", inversedBy="translations"
* @ORM\JoinColumn(name="article_id", referencedColumnName="id")
*/ protected $translatable;

/**
* @ORM\Column(type="string", length=128)
*/
private $title;

public function getTitle()
{
return $this->title;
}
}

Symfony2
kernel.request

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

public function testOnKernelRequest()
{
//Symfony\Component\HttpKernel\Event\GetResponseEvent
$this->event->expects($this->once()
->method('getRequest')
->will($this->returnValue($this->request)); //Symfony\Component\HttpFoundation\Request
$this->request->expects($this->once())
->method('getLocale')
->will($this->returnValue('en')); //Acme\CoreBundle\Locale\LocaleInterface
$this->locale->expects($this->once())
->method('setLocale')
->with('en');

//Acme\CoreBundle\Listener\LocaleListener
$this->listener = new LocaleListener($this->locale);
$this->listener->onKernelRequest($this->event);
}

Doctrine2
postLoad

public function testPostLoad()
{ //Acme\CoreBundle\Locale\LocaleInterface
$this->locale; //Doctrine\ORM\Event\LifecycleEventArgs
$this->args->expects($this->once())
->method('getEntity')
->will($this->returnValue($this->entity)); //Acme\CoreBundle\Model\TranslatableInterface
$this->entity->expects($this->once())
->method('setLocale')
->with($this->locale);

//Acme\CoreBundle\Listener\EntityListener $this->listener = new TranslatableListener($this->locale);
$this->listener->postLoad($this->event);
}

public function testPostLoad()
{ //Acme\CoreBundle\Locale\LocaleInterface
$this->locale; //Doctrine\ORM\Event\LifecycleEventArgs
$this->args->expects($this->once())
->method('getEntity')
->will($this->returnValue($this->entity)); //Acme\CoreBundle\Model\TranslatableInterface
$this->entity->expects($this->once())
->method('setLocale')
->with($this->locale);

//Acme\CoreBundle\Listener\EntityListener $this->listener = new TranslatableListener($this->locale);
$this->listener->postLoad($this->event);
}

public function testPostLoad()
{ //Acme\CoreBundle\Locale\LocaleInterface
$this->locale; //Doctrine\ORM\Event\LifecycleEventArgs
$this->args->expects($this->once())
->method('getEntity')
->will($this->returnValue($this->entity)); //Acme\CoreBundle\Model\TranslatableInterface
$this->entity->expects($this->once())
->method('setLocale')
->with($this->locale);

//Acme\CoreBundle\Listener\EntityListener $this->listener = new TranslatableListener($this->locale);
$this->listener->postLoad($this->event);
}

public function testPostLoad()
{ //Acme\CoreBundle\Locale\LocaleInterface
$this->locale; //Doctrine\ORM\Event\LifecycleEventArgs
$this->args->expects($this->once())
->method('getEntity')
->will($this->returnValue($this->entity)); //Acme\CoreBundle\Model\TranslatableInterface
$this->entity->expects($this->once())
->method('setLocale')
->with($this->locale);

//Acme\CoreBundle\Listener\EntityListener $this->listener = new TranslatableListener($this->locale);
$this->listener->postLoad($this->event);
}

public function testPostLoad()
{ //Acme\CoreBundle\Locale\LocaleInterface
$this->locale; //Doctrine\ORM\Event\LifecycleEventArgs
$this->args->expects($this->once())
->method('getEntity')
->will($this->returnValue($this->entity)); //Acme\CoreBundle\Model\TranslatableInterface
$this->entity->expects($this->once())
->method('setLocale')
->with($this->locale);

//Acme\CoreBundle\Listener\EntityListener $this->listener = new TranslatableListener($this->locale);
$this->listener->postLoad($this->event);
}

public function testPostLoad()
{ //Acme\CoreBundle\Locale\LocaleInterface
$this->locale; //Doctrine\ORM\Event\LifecycleEventArgs
$this->args->expects($this->once())
->method('getEntity')
->will($this->returnValue($this->entity)); //Acme\CoreBundle\Model\TranslatableInterface
$this->entity->expects($this->once())
->method('setLocale')
->with($this->locale);

//Acme\CoreBundle\Listener\EntityListener $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: Acme\CoreBundle\Service\Locale arguments: [%locale%] acme_core.listener.locale: class: Acme\CoreBundle\Service\LocaleListener arguments: ["@acme.core.locale"] tags: - { name: kernel.event_listener, event: kernel.request } acme_core.listener.entity: class: Acme\CoreBundle\Service\EntityListener 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: Acme\CoreBundle\Service\Locale arguments: [%locale%] acme_core.listener.locale: class: Acme\CoreBundle\Service\LocaleListener arguments: ["@acme.core.locale"] tags: - { name: kernel.event_listener, event: kernel.request } acme_core.listener.entity: class: Acme\CoreBundle\Service\EntityListener arguments: ["@acme.core.locale"] tags: - { name: doctrine.event_listener, event: postLoad }


class Article extends Translatable
{
/**
* @ORM\OneToMany
* (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 PUGX\I18nBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use PUGX\I18nBundle\DependencyInjection\PUGXI18nExtension;

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

namespace PUGX\I18nBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

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

return $treeBuilder;
}
}

namespace PUGX\I18nBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

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":"[email protected]"
}
],
"minimum-stability": "dev",
"require": {
"php": ">=5.3.2",
"symfony/framework-bundle": "2.1.*",
"doctrine/orm": ">=2.2.3,