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

E2ed7c278c8c49bb3e7fe0b7de039997?s=128

Hugo Hamon

July 10, 2012
Tweet

Transcript

  1. Simpli ez-vous les Design Patterns avec PHP 5.3 Hugo Hamon

    – 12/07/12
  2. Observateur Dependency Injection Inversion de Contrôle

  3. Observer

  4. Un sujet, l’objet observable, émet un signal à des modules

    qui jouent le rôle d’observateurs.
  5. Event Dispatcher

  6. Le Dispatcheur est un objet qui gère les connexions entre

    le sujet observé et ses observateurs (écouteurs).
  7. # composer.json { "require": { "php": ">=5.3.3", "symfony/event-dispatcher": "2.1.*" }

    }
  8. 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());
  9. Mise en Pratique

  10. use AFUP\Model\Article; $article = new Article(); $article->setTitle('AFUP Design Patterns'); $article->setContent('Some

    **content**'); $article->save(); echo $article->getHtmlContent();
  11. <p> Some <strong>content</strong> </p>

  12. 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; } }
  13. Le Sujet Observé

  14. 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; } }
  15. 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; } }
  16. Propager un Evénement

  17. 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; } }
  18. Ajouter des écouteurs

  19. 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); } }
  20. 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(); // ... } }
  21. 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(); } }
  22. Enregistrer les écouteurs

  23. 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);
  24. $article = new Article(); $article->setDispatcher($dispatcher); $article->setTitle('AFUP Design Patterns'); $article->setMarkdownContent( 'Some

    **markdown** content' ); $article->save();
  25. Dependency Injection

  26. Mauvaise Conception Initiale

  27. 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; } } }
  28. $message = new Message(); $message->setFrom('me@example.com'); $message->setTo('you@example.com'); $message->setSubject('Bonjour ...'); $message->setBody('Hello ...');

    $mailer = new Mailer(); $mailer->send($message);
  29. Ca fonctionne !

  30. Oui mais ?!!!

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

  32. Je veux utiliser un transport différent…

  33. Je veux con gurer le SMTP en dev et en

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

  35. Si le logger n’existe pas ?

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

  37. Je veux tester mon code avec PHPUnit et je n’y

    arrive pas…
  38. La Solution?

  39. Injecter ses dépendances au Mailer

  40. What ???

  41. Injection par les propriétés

  42. class Mailer { public $transport; public function send(Message $message) {

    try { $this->transport->send($message); } catch (TransportException $e) { // ... } } }
  43. $message = Message(); $message->setFrom('me@example.com'); $message->setTo('you@example.com'); $message->setSubject('Bonjour ...'); $message->setBody('Hello ...'); $transport

    = new SMTPTransport('...'); $mailer = new Mailer(); $mailer->transport = $transport; $mailer->send($message);
  44. Injection par constructeur

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

    = $t; } }
  46. $message = Message(); $message->setFrom('me@example.com'); // ... $transport = new SMTPTransport('...');

    $mailer = new Mailer($transport); $mailer->send($message);
  47. Injection par un mutateur

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

    = $logger; } }
  49. 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; } } } }
  50. $message = Message(); // ... $logger = new FileLogger('/to/dev.log'); $transport

    = new SMTPTransport('...'); $mailer = new Mailer($transport); $mailer->setLogger($logger); $mailer->send($message);
  51. Découplage avec les interfaces

  52. class Mailer { function __construct(TransportInterface $t) { $this->transport = $t;

    } function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
  53. class SMTPTransport implements TransportInterface { } class MailTransport implements TransportInterface

    { } class NullTransport implements TransportInterface { }
  54. Béné ces vs Pertes ?!

  55. Con gurabilité Modularité Testabilité

  56. Construction un peu plus Complexe

  57. Inversion de Contrôle

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

  59. Global Con guration + Lazy Services = Container

  60. Paramètres Globaux de Con guration

  61. $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*';
  62. Enregistrer des services

  63. $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; });
  64. $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'] ); });
  65. $pimple['mailer'] = $pimple->share(function ($c) { $mailer = new Mailer($c['mailer.transport']); if

    (isset($c['logger'])) { $mailer->setLogger($c['logger']); } return $mailer; });
  66. Initialisation des services à la demande

  67. $pimple = new Pimple(); $pimple['logger.file'] = '/path/to/dev.log'; $pimple['logger.severity'] = 200;

    // ... $message = Message(); $message->setFrom('me@example.com'); // ... // Création à la demande du mailer $pimple['mailer']->send($message);
  68. None