Slide 1

Slide 1 text

http://brightsideof.com/wp-content/uploads/2012/11/m4-1024x679.jpg Simplifiez-vous les design patterns avec Symfony2

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Disclaimer

Slide 4

Slide 4 text

Observateur Injection de Dépendance Service Locator

Slide 5

Slide 5 text

Observer

Slide 6

Slide 6 text

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); } }

Slide 7

Slide 7 text

Trop de responsabilités! => SOLID Principle

Slide 8

Slide 8 text

Couplage fort!

Slide 9

Slide 9 text

Evolutivité limitée!

Slide 10

Slide 10 text

Maintenance peu aisée!

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

interface ObserverInterface { function notify(ObservableInterface $subject); } interface ObservableInterface { function attach(ObserverInterface $observer); function notify(); }

Slide 15

Slide 15 text

Implémenter des Observateurs

Slide 16

Slide 16 text

class LoggerHandler implements ObserverInterface { public $logger; public function notify(ObservableInterface $subject) { $reference = $subject->getReference(); $this->logger->log('New order #'. $reference); } }

Slide 17

Slide 17 text

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); } }

Slide 18

Slide 18 text

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); } }

Slide 19

Slide 19 text

Notifier les Observateurs

Slide 20

Slide 20 text

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); } } }

Slide 21

Slide 21 text

class Order implements ObservableInterface { public function confirm() { $this->status = 'confirmed'; $this->save(); $this->notifyObservers(); } }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

SplObserver SplSubject

Slide 24

Slide 24 text

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); } }

Slide 25

Slide 25 text

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); } } }

Slide 26

Slide 26 text

Limitations?

Slide 27

Slide 27 text

EventDispatcher

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

"symfony/event-dispatcher": "2.1.*"

Slide 30

Slide 30 text

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());

Slide 31

Slide 31 text

Mise en Pratique

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Some content

Slide 34

Slide 34 text

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; } }

Slide 35

Slide 35 text

Le Sujet Observé

Slide 36

Slide 36 text

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; } }

Slide 37

Slide 37 text

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; } }

Slide 38

Slide 38 text

Propager un Evénement

Slide 39

Slide 39 text

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; } }

Slide 40

Slide 40 text

Ajouter des écouteurs

Slide 41

Slide 41 text

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); } }

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Enregistrer les écouteurs

Slide 45

Slide 45 text

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);

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Injection de dépendance

Slide 48

Slide 48 text

Single Responsability Open / Close principle Liskov substitution Interface segregation Dependency Injection

Slide 49

Slide 49 text

« Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields. » http://picocontainer.codehaus.org/injection.html

Slide 50

Slide 50 text

Mauvaise Conception Initiale

Slide 51

Slide 51 text

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; } } }

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

ça fonctionne !

Slide 54

Slide 54 text

Oui mais ?!!!

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Je veux utiliser un transport différent…

Slide 57

Slide 57 text

Je veux configurer le SMTP en dev et en prod…

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Si le logger n’existe pas ?

Slide 60

Slide 60 text

Si je veux changer la configuration du logger?

Slide 61

Slide 61 text

Je veux tester mon code et je n’y arrive pas…

Slide 62

Slide 62 text

La Solution?

Slide 63

Slide 63 text

Injecter ses dépendances au Mailer

Slide 64

Slide 64 text

What ???

Slide 65

Slide 65 text

Injection par les propriétés

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

$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);

Slide 68

Slide 68 text

Injection par constructeur

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Injection par un mutateur

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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; } } } }

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Découplage avec les interfaces

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Bénéfices vs Pertes ?!

Slide 79

Slide 79 text

Configurabilité Modularité Testabilité

Slide 80

Slide 80 text

Construction un peu plus Complexe

Slide 81

Slide 81 text

Service Locator

Slide 82

Slide 82 text

« Le patron service locator permet d’encapsuler les mécanismes d’obtention d’un service. »

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

Global Configuration + Lazy Services = Container

Slide 85

Slide 85 text

Paramètres Globaux de Configuration

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Enregistrer des services

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Initialisation des services à la demande

Slide 92

Slide 92 text

$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);

Slide 93

Slide 93 text

Pour aller plus loin!

Slide 94

Slide 94 text

DependencyInjection

Slide 95

Slide 95 text

No content