Simplifiez-vous les design patterns avec Symfony

E2ed7c278c8c49bb3e7fe0b7de039997?s=47 Hugo Hamon
November 30, 2012

Simplifiez-vous les design patterns avec Symfony

Au cours de cette conférence, nous aborderons l'implémentation pratique des patrons Observateur et Service Locator au travers de deux libraries : le composant EventDispatcher du framework Symfony et Pimple, le conteneur d'injection de dépendance du framework Silex.

E2ed7c278c8c49bb3e7fe0b7de039997?s=128

Hugo Hamon

November 30, 2012
Tweet

Transcript

  1. 2.

    Patron de Conception « En génie logiciel, un patron de

    conception est une solution générique d'implémentation répondant à un problème spécifique. » http://fr.wikibooks.org/wiki/Patrons_de_conception
  2. 6.

    class Order { public function confirm() { $this->status = 'confirmed';

    $this->save(); if ($this->logger) { $this->logger->log('New order...'); } $mail = new Email(); $mail->recipient = $this->customer->getEmail(); $mail->subject = 'Your order!'; $mail->message = 'Thanks for ordering...'; $this->mailer->send($mail); $mail = new Email(); $mail->recipient = 'sales@acme.com'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }
  3. 11.
  4. 12.
  5. 13.
  6. 16.

    class LoggerHandler implements ObserverInterface { public $logger; public function notify(ObservableInterface

    $subject) { $reference = $subject->getReference(); $this->logger->log('New order #'. $reference); } }
  7. 17.

    class CustomerNotifier implements ObserverInterface { public $mailer; public function notify(ObservableInterface

    $subject) { $mail = new Email(); $mail->recipient = $subject->customer->getEmail(); $mail->subject = 'Your order!'; $mail->message = 'Thanks for ordering...'; $this->mailer->send($mail); } }
  8. 18.

    class SalesNotifier implements ObserverInterface { public $mailer; public function notify(ObservableInterface

    $subject) { $mail = new Email(); $mail->recipient = 'sales@acme.com'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }
  9. 20.

    class Order implements ObservableInterface { // ... private $observers; public

    function attach(ObserverInterface $observer) { $this->observers[] = $observer; } public function notifyObservers() { foreach ($this->observers as $observer) { $observer->notify($this); } } }
  10. 21.

    class Order implements ObservableInterface { public function confirm() { $this->status

    = 'confirmed'; $this->save(); $this->notifyObservers(); } }
  11. 22.

    class OrderController { public function finishOrderAction() { $order = new

    Order(); $order->attach(new LoggerNotifier($logger)); $order->attach(new CustomerNotifier($mailer)); $order->attach(new SalesNotifier($mailer)); $order->customer = $customer; $order->amount = 150; // Exécution des observateurs $order->confirm(); } }
  12. 24.

    class SalesNotifier extends SplObserver { public $mailer; public function update(SplSubject

    $subject) { $mail = new Email(); $mail->recipient = 'sales@acme.com'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }
  13. 25.

    class Order extends SplSubject { // ... private $observers; public

    function attach(SplObserver $observer) { $this->observers[] = $observer; } public function notify() { foreach ($this->observers as $observer) { $observer->notify($this); } } }
  14. 28.

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

    le sujet observé et ses observateurs (écouteurs).
  15. 30.

    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());
  16. 34.

    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; } }
  17. 36.

    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; } }
  18. 37.

    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; } }
  19. 39.

    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; } }
  20. 41.

    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); } }
  21. 42.

    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(); // ... } }
  22. 43.

    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(); } }
  23. 45.

    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. 49.

    « Dependency Injection is where components are given their dependencies

    through their constructors, methods, or directly into fields. » http://picocontainer.codehaus.org/injection.html
  25. 51.

    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; } } }
  26. 64.
  27. 66.

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

    try { $this->transport->send($message); } catch (TransportException $e) { // ... } } }
  28. 67.
  29. 73.

    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; } } } }
  30. 74.

    $message = Message(); // ... $logger = new FileLogger('/to/dev.log'); $transport

    = new SMTPTransport('...'); $mailer = new Mailer($transport); $mailer->setLogger($logger); $mailer->send($message);
  31. 76.

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

    } function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
  32. 86.

    $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*';
  33. 88.

    $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; });
  34. 89.

    $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'] ); });
  35. 90.

    $pimple['mailer'] = $pimple->share(function ($c) { $mailer = new Mailer($c['mailer.transport']); if

    (isset($c['logger'])) { $mailer->setLogger($c['logger']); } return $mailer; });
  36. 92.

    $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);
  37. 95.