Design Patterns avec PHP 5.3, Symfony et Pimple

68
Simpliez-vous les Design Patterns avec PHP 5.3 Hugo Hamon – 12/07/12

description

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

Transcript of Design Patterns avec PHP 5.3, Symfony et Pimple

Page 1: Design Patterns avec PHP 5.3, Symfony et Pimple

Simpli!ez-vous les Design Patterns avec PHP 5.3

Hugo Hamon – 12/07/12

Page 2: Design Patterns avec PHP 5.3, Symfony et Pimple

Observateur

Dependency Injection

Inversion de Contrôle

Page 3: Design Patterns avec PHP 5.3, Symfony et Pimple

Obse

rver

Page 4: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 5: Design Patterns avec PHP 5.3, Symfony et Pimple

Event Dispatcher

Page 6: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 7: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 8: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 9: Design Patterns avec PHP 5.3, Symfony et Pimple

Mise

en Pr

atiqu

e

Page 10: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 11: Design Patterns avec PHP 5.3, Symfony et Pimple

<p> Some <strong>content</strong> </p>

Page 12: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 13: Design Patterns avec PHP 5.3, Symfony et Pimple

Le Sujet Observé

Page 14: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 15: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 16: Design Patterns avec PHP 5.3, Symfony et Pimple

Propager un Evénement

Page 17: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 18: Design Patterns avec PHP 5.3, Symfony et Pimple

Ajouter des écouteurs

Page 19: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 20: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 21: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 22: Design Patterns avec PHP 5.3, Symfony et Pimple

Enregistrer les écouteurs

Page 23: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 24: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 25: Design Patterns avec PHP 5.3, Symfony et Pimple

Dependency Injection

Page 26: Design Patterns avec PHP 5.3, Symfony et Pimple

Mauvaise Conception Initiale

Page 27: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 28: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 29: Design Patterns avec PHP 5.3, Symfony et Pimple

Ca fonctionne !

Page 30: Design Patterns avec PHP 5.3, Symfony et Pimple

Oui mais ?!!!

Page 31: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 32: Design Patterns avec PHP 5.3, Symfony et Pimple

Je veux utiliser un transport différent…

Page 33: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 34: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 35: Design Patterns avec PHP 5.3, Symfony et Pimple

Si le logger n’existe pas ?

Page 36: Design Patterns avec PHP 5.3, Symfony et Pimple

Si je veux changer la con!guration du logger?

Page 37: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 38: Design Patterns avec PHP 5.3, Symfony et Pimple

La Solution?

Page 39: Design Patterns avec PHP 5.3, Symfony et Pimple

Injecter ses dépendances au Mailer

Page 40: Design Patterns avec PHP 5.3, Symfony et Pimple

What ???

Page 41: Design Patterns avec PHP 5.3, Symfony et Pimple

Injection par les propriétés

Page 42: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 43: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 44: Design Patterns avec PHP 5.3, Symfony et Pimple

Injection par constructeur

Page 45: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 46: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 47: Design Patterns avec PHP 5.3, Symfony et Pimple

Injection par un mutateur

Page 48: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 49: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 50: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 51: Design Patterns avec PHP 5.3, Symfony et Pimple

Découplage avec les interfaces

Page 52: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 53: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 54: Design Patterns avec PHP 5.3, Symfony et Pimple

Béné!ces vs Pertes ?!

Page 55: Design Patterns avec PHP 5.3, Symfony et Pimple

Con!gurabilité Modularité Testabilité

Page 56: Design Patterns avec PHP 5.3, Symfony et Pimple

Construction un peu plus Complexe

Page 57: Design Patterns avec PHP 5.3, Symfony et Pimple

Inversion de Contrôle

Page 58: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 59: Design Patterns avec PHP 5.3, Symfony et Pimple

Global Con!guration + Lazy Services

= Container

Page 60: Design Patterns avec PHP 5.3, Symfony et Pimple

Paramètres Globaux de Con!guration

Page 61: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 62: Design Patterns avec PHP 5.3, Symfony et Pimple

Enregistrer des services

Page 63: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 64: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 65: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 66: Design Patterns avec PHP 5.3, Symfony et Pimple

Initialisation des services à la demande

Page 67: Design Patterns avec PHP 5.3, Symfony et Pimple

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

Page 68: Design Patterns avec PHP 5.3, Symfony et Pimple