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

Simplifiez-vous les design patterns avec Symfony

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.

Hugo Hamon

November 30, 2012
Tweet

More Decks by Hugo Hamon

Other Decks in Technology

Transcript

  1. 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. 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 = '[email protected]'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }
  3. class LoggerHandler implements ObserverInterface { public $logger; public function notify(ObservableInterface

    $subject) { $reference = $subject->getReference(); $this->logger->log('New order #'. $reference); } }
  4. 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); } }
  5. class SalesNotifier implements ObserverInterface { public $mailer; public function notify(ObservableInterface

    $subject) { $mail = new Email(); $mail->recipient = '[email protected]'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }
  6. 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); } } }
  7. class Order implements ObservableInterface { public function confirm() { $this->status

    = 'confirmed'; $this->save(); $this->notifyObservers(); } }
  8. 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(); } }
  9. class SalesNotifier extends SplObserver { public $mailer; public function update(SplSubject

    $subject) { $mail = new Email(); $mail->recipient = '[email protected]'; $mail->subject = 'New order to ship!'; $mail->message = '...'; $this->mailer->send($mail); } }
  10. 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); } } }
  11. Le Dispatcheur est un objet qui gère les connexions entre

    le sujet observé et ses observateurs (écouteurs).
  12. 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());
  13. 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; } }
  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. 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; } }
  17. 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); } }
  18. 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(); // ... } }
  19. 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(); } }
  20. 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);
  21. « Dependency Injection is where components are given their dependencies

    through their constructors, methods, or directly into fields. » http://picocontainer.codehaus.org/injection.html
  22. 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; } } }
  23. class Mailer { public $transport; public function send(Message $message) {

    try { $this->transport->send($message); } catch (TransportException $e) { // ... } } }
  24. 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; } } } }
  25. $message = Message(); // ... $logger = new FileLogger('/to/dev.log'); $transport

    = new SMTPTransport('...'); $mailer = new Mailer($transport); $mailer->setLogger($logger); $mailer->send($message);
  26. class Mailer { function __construct(TransportInterface $t) { $this->transport = $t;

    } function setLogger(LoggerInterface $logger) { $this->logger = $logger; } }
  27. $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*';
  28. $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; });
  29. $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'] ); });
  30. $pimple['mailer'] = $pimple->share(function ($c) { $mailer = new Mailer($c['mailer.transport']); if

    (isset($c['logger'])) { $mailer->setLogger($c['logger']); } return $mailer; });
  31. $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);