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

Design Patterns avec PHP 5.3, Symfony et Pimple

Design Patterns avec PHP 5.3, Symfony et Pimple

Cette conférence présente deux grands motifs de conception : l'observateur et l'injection de dépendance. Ce sujet allie à la fois théorie et pratique. Le composant autonome EventDispatcher de Symfony ainsi que le conteneur d'injection de dépendance Pimple sont mis à l'honneur avec des exemples pratiques d'usage. Ces cas pratiques combinent du code de l'ORM Propel ainsi que le composant autonome Zend\Search\Lucene du Zend Framework 2

Hugo Hamon

July 10, 2012
Tweet

More Decks by Hugo Hamon

Other Decks in Technology

Transcript

  1. Simpli ez-vous les Design
    Patterns avec PHP 5.3
    Hugo Hamon – 12/07/12

    View full-size slide

  2. Observateur
    Dependency Injection
    Inversion de Contrôle

    View full-size slide

  3. Un sujet, l’objet
    observable, émet un signal
    à des modules qui jouent
    le rôle d’observateurs.

    View full-size slide

  4. Event Dispatcher

    View full-size slide

  5. Le Dispatcheur est un objet
    qui gère les connexions
    entre le sujet observé et ses
    observateurs (écouteurs).

    View full-size slide

  6. # composer.json
    {
    "require": {
    "php": ">=5.3.3",
    "symfony/event-dispatcher": "2.1.*"
    }
    }

    View full-size slide

  7. use Symfony\Component\EventDispatcher\Event;
    use Symfony\Component\EventDispatcher\EventDispatcher;
    use AFUP\ArticleListener;
    $dispatcher = new EventDispatcher();
    // Déclaration des écouteurs
    $listener1 = array(new ArticleListener(), 'onDelete');
    $listener2 = array(new ArticleListener(), 'onSave');
    // Enregistrement des écouteurs
    $dispatcher->addListener('article.delete', $listener1);
    $dispatcher->addListener('article.pre_save', $listener2);
    // Notification des écouteurs
    $dispatcher->dispatch('article.pre_save', new Event());

    View full-size slide

  8. Mise en Pratique

    View full-size slide

  9. use AFUP\Model\Article;
    $article = new Article();
    $article->setTitle('AFUP Design Patterns');
    $article->setContent('Some **content**');
    $article->save();
    echo $article->getHtmlContent();

    View full-size slide


  10. Some content

    View full-size slide

  11. namespace AFUP\Model;
    use Propel\Runtime\Connection\ConnectionInterface;
    use dflydev\markdown\MarkdownParser;
    class Article extends \AFUP\Model\Base\Article
    {
    public function save(ConnectionInterface $con = null)
    {
    $parser = new MarkdownParser();
    $html = $parser->transformMarkdown($this->getContent());
    $this->setHtmlContent($html);
    $ret = parent::save($con);
    $this->updateLuceneIndex();
    return $ret;
    }
    }

    View full-size slide

  12. Le Sujet
    Observé

    View full-size slide

  13. namespace AFUP\Model;
    use Symfony\Component\EventDispatcher\EventDispatcher;
    use AFUP\Model\Base\Article as BaseArticle;
    class Article extends BaseArticle
    {
    private $dispatcher;
    public function setDispatcher(EventDispatcher $dispatcher)
    {
    $this->dispatcher = $dispatcher;
    }
    }

    View full-size slide

  14. namespace AFUP\Model;
    // ...
    use Propel\Runtime\Connection\ConnectionInterface;
    use AFUP\Event\ArticleEvent;
    class Article extends BaseArticle
    {
    // ...
    public function save(ConnectionInterface $con = null)
    {
    $event = new ArticleEvent($this);
    $this->dispatcher->dispatch('article.pre_save', $event);
    $ret = parent::save($con);
    $this->dispatcher->dispatch('article.post_save', $event);
    return $ret;
    }
    }

    View full-size slide

  15. Propager un
    Evénement

    View full-size slide

  16. namespace AFUP\Event;
    use Symfony\Component\EventDispatcher\Event;
    use AFUP\Model\Article;
    class ArticleEvent extends Event
    {
    private $article;
    public function __construct(Article $article)
    {
    $this->article = $article;
    }
    public function getArticle()
    {
    return $this->article;
    }
    }

    View full-size slide

  17. Ajouter des
    écouteurs

    View full-size slide

  18. namespace AFUP\Listener;
    use AFUP\Event\ArticleEvent;
    use dflydev\markdown\MarkdownParser;
    class ArticleListener
    {
    public function onPreSave(ArticleEvent $event)
    {
    $article = $event->getArticle();
    $markdown = $article->getContent();
    $parser = new MarkdownParser();
    $html = $parser->transformMarkdown($markdown);
    $article->setHtmlContent($html);
    }
    }

    View full-size slide

  19. namespace AFUP\Listener;
    use Zend\Search\Lucene\Document;
    use Zend\Search\Lucene\Document\Field;
    use AFUP\Event\ArticleEvent;
    use AFUP\Model\ArticlePeer;
    class LuceneListener
    {
    public function onPostSave(ArticleEvent $event)
    {
    $article = $event->getArticle();
    // ...
    }
    }

    View full-size slide

  20. namespace AFUP\Listener;
    // ...
    class LuceneListener
    {
    public function onPostSave(ArticleEvent $event)
    {
    $article = $event->getArticle();
    $index = ArticlePeer::getLuceneIndex();
    // remove existing entries
    foreach ($index->find('pk:'.$article->getId()) as $hit) {
    $index->delete($hit->id);
    }
    $doc = new Document();
    $doc->addField(Field::Keyword('pk', $article->getId()));
    $doc->addField(Field::UnStored('title', $article->getTitle()));
    $doc->addField(Field::UnStored('content', $article->getContent()));
    $index->addDocument($doc);
    $index->commit();
    }
    }

    View full-size slide

  21. Enregistrer les
    écouteurs

    View full-size slide

  22. use Symfony\Component\EventDispatcher\EventDispatcher;
    use AFUP\Listener\ArticleListener;
    use AFUP\Listener\LuceneListener;
    use AFUP\Model\Article;
    // Déclaration des écouteurs
    $listener1 = array(new ArticleListener(), 'onPreSave');
    $listener2 = array(new LuceneListener(), 'onPostSave');
    // Enregistrement des écouteurs
    $dispatcher = new EventDispatcher();
    $dispatcher->addListener('article.pre_save', $listener1);
    $dispatcher->addListener('article.post_save', $listener2);

    View full-size slide

  23. $article = new Article();
    $article->setDispatcher($dispatcher);
    $article->setTitle('AFUP Design Patterns');
    $article->setMarkdownContent(
    'Some **markdown** content'
    );
    $article->save();

    View full-size slide

  24. Dependency Injection

    View full-size slide

  25. Mauvaise
    Conception
    Initiale

    View full-size slide

  26. class Mailer
    {
    public function send(Message $message)
    {
    try {
    $transport = new SMTPTransport(
    'smtp.foo.com', 1234, 'mailer', 'p$wD^'
    );
    return $transport->send($message);
    } catch (TransportException $e) {
    $logger = Logger::getInstance();
    $logger->log('Unable to send message to...');
    $logger->logException($e);
    throw $e;
    }
    }
    }

    View full-size slide

  27. $message = new Message();
    $message->setFrom('[email protected]');
    $message->setTo('[email protected]');
    $message->setSubject('Bonjour ...');
    $message->setBody('Hello ...');
    $mailer = new Mailer();
    $mailer->send($message);

    View full-size slide

  28. Ca fonctionne !

    View full-size slide

  29. Oui mais ?!!!

    View full-size slide

  30. $transport = new SMTPTransport(
    'smtp.foo.com',
    1234,
    'mailer',
    'p$wD^'
    );

    View full-size slide

  31. Je veux utiliser
    un transport
    différent…

    View full-size slide

  32. Je veux con gurer
    le SMTP en dev et
    en prod…

    View full-size slide

  33. $logger = Logger::getInstance();
    $logger->log('Unable to...');
    $logger->logException($e);

    View full-size slide

  34. Si le logger
    n’existe pas ?

    View full-size slide

  35. Si je veux changer
    la con guration
    du logger?

    View full-size slide

  36. Je veux tester mon
    code avec PHPUnit
    et je n’y arrive
    pas…

    View full-size slide

  37. La Solution?

    View full-size slide

  38. Injecter ses
    dépendances au
    Mailer

    View full-size slide

  39. Injection par
    les propriétés

    View full-size slide

  40. class Mailer
    {
    public $transport;
    public function send(Message $message)
    {
    try {
    $this->transport->send($message);
    } catch (TransportException $e) {
    // ...
    }
    }
    }

    View full-size slide

  41. $message = Message();
    $message->setFrom('[email protected]');
    $message->setTo('[email protected]');
    $message->setSubject('Bonjour ...');
    $message->setBody('Hello ...');
    $transport = new SMTPTransport('...');
    $mailer = new Mailer();
    $mailer->transport = $transport;
    $mailer->send($message);

    View full-size slide

  42. Injection par
    constructeur

    View full-size slide

  43. class Mailer
    {
    private $transport;
    function __construct(Transport $t)
    {
    $this->transport = $t;
    }
    }

    View full-size slide

  44. $message = Message();
    $message->setFrom('[email protected]');
    // ...
    $transport = new SMTPTransport('...');
    $mailer = new Mailer($transport);
    $mailer->send($message);

    View full-size slide

  45. Injection par
    un mutateur

    View full-size slide

  46. class Mailer
    {
    private $logger;
    function setLogger(Logger $logger)
    {
    $this->logger = $logger;
    }
    }

    View full-size slide

  47. class Mailer
    {
    // ...
    public function send(Message $message)
    {
    try {
    $this->transport->send($message);
    } catch (TransportException $e) {
    if (null !== $this->logger) {
    $this->logger->log('...');
    $this->logger->logException($e);
    throw $e;
    }
    }
    }
    }

    View full-size slide

  48. $message = Message();
    // ...
    $logger = new FileLogger('/to/dev.log');
    $transport = new SMTPTransport('...');
    $mailer = new Mailer($transport);
    $mailer->setLogger($logger);
    $mailer->send($message);

    View full-size slide

  49. Découplage
    avec les
    interfaces

    View full-size slide

  50. class Mailer
    {
    function __construct(TransportInterface $t)
    {
    $this->transport = $t;
    }
    function setLogger(LoggerInterface $logger)
    {
    $this->logger = $logger;
    }
    }

    View full-size slide

  51. class SMTPTransport implements TransportInterface
    {
    }
    class MailTransport implements TransportInterface
    {
    }
    class NullTransport implements TransportInterface
    {
    }

    View full-size slide

  52. Béné ces vs
    Pertes ?!

    View full-size slide

  53. Con gurabilité
    Modularité
    Testabilité

    View full-size slide

  54. Construction
    un peu plus
    Complexe

    View full-size slide

  55. Inversion de Contrôle

    View full-size slide

  56. # composer.json
    {
    "require": {
    "pimple/pimple": "1.0.*"
    }
    }

    View full-size slide

  57. Global Con guration
    + Lazy Services
    = Container

    View full-size slide

  58. Paramètres
    Globaux de
    Con guration

    View full-size slide

  59. $pimple = new Pimple();
    $pimple['logger.file'] = '/path/to/dev.log';
    $pimple['logger.severity'] = 200;
    $pimple['transport.smtp.host'] = 'smtp.foo.com';
    $pimple['transport.smtp.port'] = 1234;
    $pimple['transport.smtp.user'] = 'mailer';
    $pimple['transport.smtp.passwd'] = '^p4$$W0rD*';

    View full-size slide

  60. Enregistrer
    des services

    View full-size slide

  61. $pimple['logger'] = $pimple->share(function ($c) {
    if (!is_writable($c['logger.file'])) {
    throw new Exception('...');
    }
    $logger = new Logger($c['logger.file']);
    if (isset($c['logger.severity'])) {
    $logger->setSeverity($c['logger.severity']);
    }
    return $logger;
    });

    View full-size slide

  62. $pimple['mailer.transport'] = $pimple
    ->share(function ($c) {
    return new SMTPTransport(
    $c['transport.smtp.host'],
    $c['transport.smtp.port'],
    $c['transport.smtp.user'],
    $c['transport.smtp.passwd']
    );
    });

    View full-size slide

  63. $pimple['mailer'] = $pimple->share(function ($c) {
    $mailer = new Mailer($c['mailer.transport']);
    if (isset($c['logger'])) {
    $mailer->setLogger($c['logger']);
    }
    return $mailer;
    });

    View full-size slide

  64. Initialisation des
    services à la
    demande

    View full-size slide

  65. $pimple = new Pimple();
    $pimple['logger.file'] = '/path/to/dev.log';
    $pimple['logger.severity'] = 200;
    // ...
    $message = Message();
    $message->setFrom('[email protected]');
    // ...
    // Création à la demande du mailer
    $pimple['mailer']->send($message);

    View full-size slide