Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Observateur Dependency Injection Inversion de Contrôle

Slide 3

Slide 3 text

Observer

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Event Dispatcher

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 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 9

Slide 9 text

Mise en Pratique

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Some content

Slide 12

Slide 12 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 13

Slide 13 text

Le Sujet Observé

Slide 14

Slide 14 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 15

Slide 15 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 16

Slide 16 text

Propager un Evénement

Slide 17

Slide 17 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 18

Slide 18 text

Ajouter des écouteurs

Slide 19

Slide 19 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 20

Slide 20 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 21

Slide 21 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 22

Slide 22 text

Enregistrer les écouteurs

Slide 23

Slide 23 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 24

Slide 24 text

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

Slide 25

Slide 25 text

Dependency Injection

Slide 26

Slide 26 text

Mauvaise Conception Initiale

Slide 27

Slide 27 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 28

Slide 28 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 29

Slide 29 text

Ca fonctionne !

Slide 30

Slide 30 text

Oui mais ?!!!

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Je veux utiliser un transport différent…

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Si le logger n’existe pas ?

Slide 36

Slide 36 text

Si je veux changer la con guration du logger?

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

La Solution?

Slide 39

Slide 39 text

Injecter ses dépendances au Mailer

Slide 40

Slide 40 text

What ???

Slide 41

Slide 41 text

Injection par les propriétés

Slide 42

Slide 42 text

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

Slide 43

Slide 43 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 44

Slide 44 text

Injection par constructeur

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Injection par un mutateur

Slide 48

Slide 48 text

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

Slide 49

Slide 49 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 50

Slide 50 text

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

Slide 51

Slide 51 text

Découplage avec les interfaces

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Béné ces vs Pertes ?!

Slide 55

Slide 55 text

Con gurabilité Modularité Testabilité

Slide 56

Slide 56 text

Construction un peu plus Complexe

Slide 57

Slide 57 text

Inversion de Contrôle

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Global Con guration + Lazy Services = Container

Slide 60

Slide 60 text

Paramètres Globaux de Con guration

Slide 61

Slide 61 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 62

Slide 62 text

Enregistrer des services

Slide 63

Slide 63 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 64

Slide 64 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 65

Slide 65 text

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

Slide 66

Slide 66 text

Initialisation des services à la demande

Slide 67

Slide 67 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 68

Slide 68 text

No content