Symfony2 en pièces détachées

Post on 24-May-2015

20.708 views 7 download

description

Paradoxalement, Symfony2 n'est pas qu'un framework "full-stack". Il s'agit avant tout d'une parfaite synergie de briques logicielles autonomes qui travaillent de concert sous la baguette d'un seul chef d'orchestre : le conteneur d'injection de dépendances. Mais savez-vous que vous pouvez aussi les utiliser sans le framework ? Tous ces composants indépendants sont distribués sous licence MIT et offrent aux développeurs la liberté de les utiliser dans leurs projets PHP. Au cours de cette session, nous mettrons en lumière les fonctionnalités offertes par les principaux composants de Symfony2 tels que DependencyInjection, Console, Finder, EventDispatcher, Translation et bien d'autres encore. Vous découvrirez comment les intégrer et les utiliser dans vos projets PHP, et ainsi devenir le prochain Maestro du web.

Transcript of Symfony2 en pièces détachées

Symfony2 en pièces détachées

Symfony Live 2011 - 4 mars 2011 - Paris

Hugo Hamon aka @hhamon

²  Responsable des formations Sensio Labs

²  Ancien membre du Bureau de l’AFUP ²  Auteur d’ouvrage PHP / Symfony chez Eyrolles

²  http://www.hugohamon.com

Avant d’oublier…

http://joind.in/talk/view/2766

Quelques questions…

Qui développe encore des applications sans framework ou bibliothèques ?

Quelques questions…

Qui réutilise des composants éprouvés ? Zend Framework, PEAR, EZ Components…

Quelques questions…

Qui réinvente à la roue ?

Symfony2 est bâti autour d’une bibliothèque de composants indépendants

Les composants

§  BrowserKit §  ClassLoader §  Console §  CSS Selector §  Dependency Injection §  Dom Crawler §  Event Dispatcher §  Finder §  Form §  HTTP Foundation

§  HTTP Kernel §  Locale §  Process §  Routing §  Security §  Serializer §  Templating §  Translation §  Validator §  YAML

ClassLoader

ClassLoader

ClassLoader

UniversalClassLoader

ClassLoader UniversalClassLoader

use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony' => __DIR__ .'/symfony/src', 'Doctrine' => __DIR__ .'/doctrine/lib', 'Zend' => __DIR__ .'/zend/library', )); $loader->register();

PHP 5.3 Interoperability

standard

ClassLoader UniversalClassLoader

use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Sensio' => array(__DIR__ .'/src', __DIR__.'/sensio-extra') 'Symfony' => __DIR__ .'/symfony/src', 'Doctrine' => __DIR__ .'/doctrine/lib', 'Zend' => __DIR__ .'/zend/library', )); $loader->register(); Fallback

ClassLoader UniversalClassLoader

use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerPrefixes(array( 'Swift_' => __DIR__ .'/swiftmailer/lib/classes', 'Twig_' => __DIR__ .'/twig/lib/classes', )); $loader->register();

PEAR style

HttpFoundation

HttpFoundation

HttpFoundation

Request

HttpFoundation

Classe Dé!nition

Request Gestion des entêtes et paramètres GET, POST, Cookie, Server…

Response Gestion des entêtes et contenu de la réponse

Session Gestion de la session PHP

Cookie Génère un nouveau cookie

File Gestion des !chiers

HttpFoundation Request

require_once __DIR__ .'/../src/autoload.php'; use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $name = $request->query->get('name'); // Get query string variable $name = $request->request->get('name'); // Get post variable $name = $request->cookies->get('name'); // Get cookie value $file = $request->files->get('avatar'); // Get uploaded file $method = $request->getMethod(); // Get the request method $ip = $request->getClientIp(); // Get remote address $https = $request->isSecure(); // True if HTTPS $ajax = $request->isXmlHttpRequest(); // True if ajax request

HttpFoundation

Session

HttpFoundation Session

§  Lecture et écriture de variables de session §  Lecture et écriture de messages "ash §  Invalidation et nettoyage de la session §  Choix du moteur de stockage

§ ArraySessionStorage : session non persistente § NativeSessionStorage : session PHP native § PdoSessionStorage : session en base de données

HttpFoundation Session native de PHP

use Symfony\Component\HttpFoundation\Session; use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage; $session = new Session(new NativeSessionStorage()); $session->start(); // Read the session id $id = $session->getId(); // Set a session variable $session->set('username', 'hhamon'); // Set a flash message $session->setFlash('notice', 'You win!');

HttpFoundation Session native de PHP

use Symfony\Component\HttpFoundation\Session; use Symfony\Component\HttpFoundation\SessionStorage\NativeSessionStorage; $session = new Session(new NativeSessionStorage()); $session->start(); // Read the session variable echo $session->get('username'); // Read a flash message if ($session->hasFlash('notice')) { echo $session->getFlash('notice'); } // Remove a session variable $session->remove('username'); $session->clear();

HttpFoundation Session en base de données

§  Utilisation des objets PdoSessionStorage et PDO

require_once __DIR__ .'/../src/autoload.php'; use Symfony\Component\HttpFoundation\Session; use Symfony\Component\HttpFoundation\SessionStorage\PdoSessionStorage; $pdo = new \PDO('mysql:host=localhost;dbname=sflive2011', 'root', '', array( \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION )); $storage = new PdoSessionStorage($pdo, array('db_table' => 'php_session')); $session = new Session($storage, array('default_locale' => 'fr')); $session->start(); $session->set('username', 'hhamon');

HttpFoundation Session en base de données

§  Création de la table dans la base de données

§  Contrôle des données de session dans la table

CREATE TABLE `php_session` ( `sess_id` varchar(40) COLLATE utf8_unicode_ci NOT NULL, `sess_data` text COLLATE utf8_unicode_ci, `sess_time` INTEGER(10) UNSIGNED NOT NULL, PRIMARY KEY (`sess_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

HttpFoundation

Response

HttpFoundation Response §  Support des entêtes HTTP

§  Content-Type §  Status-Code

§  Support du cache HTTP §  Expires §  Etag §  Cache-Control (max-age, s-maxage…)

§  Support des cookies

HttpFoundation Response - Headers

HTTP/1.0 200 OK cache-control: s-maxage=3600 content-type: text/html; charset=utf-8 etag: "article-1337" <html>...</html>

use Symfony\Component\HttpFoundation\Response; $response = Response('<html>...</html>', 200); $response->headers->set('Content-Type', 'text/html; charset=utf-8'); $response->setEtag('article-1337'); $response->setSharedMaxAge(3600); $response->send();

HttpFoundation

Cookie

HttpFoundation Response - Cookie

HTTP/1.0 200 OK content-type: text/html; charset=utf-8 set-cookie: last-viewed=1337; expires=Mon, 10-Jan-2011 00:52:24 GMT; path=/; httponly <html>...</html>

require_once __DIR__ .'/../src/autoload.php'; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Cookie; $cookie = new Cookie('last-viewed', '1337', time()+3600*24); $response = new Response('<html>...</html>', 200); $response->headers->setCookie($cookie); $response->send();

HttpFoundation

File

HttpFoundation Fichiers et uploads

use Symfony\Component\HttpFoundation\File\File; // Set the document root File::setDocumentRoot(__DIR__); $file = new File(__DIR__.'/../tmp/avatar.png'); // Change the file location & rename it on the fly $file->move(__DIR__.'/images', 'hhamon.png'); $ext = $file->getExtension(); // Get the file extension $size = $file->getMimeType(); // Get the file type $path = $file->getPath(); // Get the file path echo '<img src="', $file->getWebPath() ,'" alt="" />';

HttpFoundation Fichier et uploads

§  Uploader un !chier avec Request et UploadedFile

<form action="upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="avatar"/> <button type="submit">upload</button> </form>

require __DIR__ .'/../src/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\File\UploadedFile; $request = Request::createFromGlobals(); $avatar = $request->files->get('avatar'); if ($avatar instanceOf UploadedFile && UPLOAD_ERR_OK == $avatar->getError()) { $avatar->move(__DIR__.'/images'); }

Event Dispatcher

Event Dispatcher

Event Dispatcher Introduction

§  Implémentation du pattern « Observateur »

§  2 classes et 2 interfaces

§  Faciliter le découplage entre les classes

§  Améliorer la "exibilité / réutilisabilité du code

§  Simpli!er l’intégration de plugins tiers

Event Dispatcher La classe EventDispatcher

§  Connecte des écouteurs (« callables ») à des événements

§  Les sujets « noti!ent » des événements aux écouteurs

$dispatcher = new EventDispatcher(); $dispatcher->connect('the.event.name', $callable);

class Subject { public $dispatcher; public function doThings() { $event = new Event($this, 'the.event.name'); $this->dispatcher->notify($event); } }

Event Dispatcher

« Forcer le nettoyage d’un cache lorsque certains événements se

produisent »

Event Dispatcher Vider un cache

class WikiPage { public $id; public $title; public $content; public function save(\PDO $conn) { if (!$this->id) { $conn->query('INSERT INTO ...'); } else { $conn->exec('UPDATE ...'); } } }

En cas de modi!cation, la page html statique du cache doit être régénérée…

namespace My\Domain; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\Event; class WikiPage { // ... private $dispatcher; public function __construct(EventDispatcher $dispatcher) { $this->dispatcher = $dispatcher; } public function save(\PDO $conn) { if (!$this->id) { // ... } else { $conn->exec('UPDATE ...'); $args = array('type' => 'wiki', 'id' => $this->id); $event = new Event($this, 'cache.flush', $args); $this->dispatcher->notify($event); } } }

Un événement est noti!é pour demander aux écouteurs de vider le cache de l’objet.

namespace My\Cache; use Symfony\Component\EventDispatcher\Event; class CacheManager { private $cacheDir; public function __construct($cacheDir) { $this->cacheDir = $cacheDir; } public function listenToCacheFlushEvent(Event $event) { // The WikiPage object $object = $event->getSubject(); // Retrieve all extra arguments $args = $event->all(); $path = $this->cacheDir .'/'. $args['type'] .'/'. $args['id'] .'.html'; if (file_exists($path)) { @unlink($path); } } }

Ecouteur invoqué à la noti!cation de l’événement.

use Symfony\Component\EventDispatcher\EventDispatcher; use My\Domain\WikiPage; use My\Cache\CacheManager; $pdo = new \PDO('...'); $cache = new CacheManager('/path/to/cache'); # Register a new listener. $dispatcher = new EventDispatcher(); $dispatcher->connect('cache.flush', array($cache, 'listenToCacheFlushEvent')); # Find the wiki page object with its id. $page = WikiPage::findById($_GET['id']); $page->setEventDispatcher($dispatcher); # Page properties are edited. $page->title = 'New title'; $page->content = 'New content'; # The CacheManager::listenToCacheFlushEvent method is called. $page->save($pdo);

Event Dispatcher

« Filtrer / transformer une valeur »

Event Dispatcher Filtrer une donnée

class WikiPage { private $rawContent; private $parsedContent; public function setRawContent($rawContent) { $this->rawContent = $rawContent; $parser = new WikiParser(); $this->parsedContent = $parser->parse($rawContent); } }

Event Dispatcher Problème ?

§  Les objets « WikiPage » et « WikiParser » sont fortement couplés

§  Changer le parser implique de modi!er la classe « WikiPage »

§  Idem, si l’on veut ajouter des !ltres supplémentaires

§  Testabilité du code réduite due à la dépendance

Event Dispatcher Solution?

class WikiPage { private $rawContent; private $parsedContent; public function setRawContent($rawContent) { $$this->rawContent = $rawContent; $event = new Event('wiki.filter_content', $rawContent); $this->parsedContent = $this->dispatcher->filter($event); } }

§  Filtrer la valeur en appelant une série de !ltres (« écouteurs »).

use Symfony\Component\EventDispatcher\EventDispatcher; use My\Domain\WikiPage; use My\Filter\WikiSyntaxParser; $dispatcher = new EventDispatcher(); # Register a first filter $dispatcher->connect('wiki.filter_content', function(Event $e, $value) { return striptags($value, '<code>') }); $wikiParser = new WikiSyntaxParser(); # Register a second filter $dispatcher->connect('wiki.filter_content', array($wikiParser, 'parse')); $wikiPage = new WikiPage(); $wikiPage->setEventDispatcher($dispatcher); # Set and filter the raw content value $wiki->setRawContent('Some **wiki** content with <script>alert("foo")</script> <code>$c = $a + $b</code>.');

Event Dispatcher Filtrer une valeur

§  La propriété « parsedContent » aura la valeur suivante.

Some <strong>wiki</strong> content with <code>$c = $a + $b</code>.

§  La balise <script> a été !ltrée

§  La balise <code> a été conservée

§  La syntaxe Wiki a été transformée en HTML

Dependency Injection

Dependency Injection

Dependency Injection Introduction

§  « Inversion de contrôle »

§  Composant au cœur de Symfony2

§  Instanciation et initialisation de services à la demande

§  Supporte les dépendances entre les différents services

§  Dé!nition des services en PHP, XML ou YAML

Dependency Injection Dé!nition d’un service SOAP

# config/services.xml <?xml version="1.0" ?> <container xmlns="http://www.symfony-project.org/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd"> <parameters> <parameter key="soap_server.wsdl.uri"> http://www.components.local/soap-server.php?wsdl </parameter> <parameter key="soap_server.response.return">true</parameter> <parameter key="soap_server.options.encoding">utf-8</parameter> <parameter key="calculator_service.class">Application\Calculator</parameter> </parameters> <services> <!-- ... --> </services> </container>

Paramètres  de  configura1on  des  services.  

Dependency Injection Dé!nition d’un service SOAP

<?xml version="1.0" ?> <container ... > <!-- ... --> <services> <!-- SOAP Server --> <service id="soap_server" class="Zend\Soap\Server"> <argument>%soap_server.wsdl.uri%</argument> <argument type="collection"> <argument key="encoding">%soap_server.options.encoding%</argument> </argument> <call method="setReturnResponse"> <argument>%soap_server.response.return%</argument> </call> <call method="setObject"><argument type="service" id="calculator_service" /></call> </service> <!-- SOAP Autodiscovery --> <service id="soap_autodiscover" class="Zend\Soap\Autodiscover"> <call method="setClass"><argument type="string">%calculator_service.class%</argument></call> </service> <!-- Calculator service used by the SOAP server --> <service id="calculator_service" class="%calculator_service.class%"/> </services> </container>

Défini1on  d’un  serveur  Zend  Soap  comme  étant  un  service  

Dependency Injection Dé!nition d’un service SOAP

<!-- Identification du service : identifiant + type --> <service id="soap_server" class="Zend\Soap\Server"> <!-- 1er argument du constructeur : valeur d’un paramètre défini précédemment --> <argument>%soap_server.wsdl.uri%</argument> <!-- 2nd argument du constructeur : tableau associatif d’options --> <argument type="collection"> <argument key="encoding">utf-8</argument> </argument> <!– 1ère méthode à appeler juste après l’instanciation du service --> <call method="setReturnResponse"> <argument>true</argument> </call> <!– 2nd méthode à appeler avec une instance du service calculator_service en argument --> <call method="setObject"> <argument type="service" id="calculator_service" /> </call> </service>

Dependency Injection Dé!nition du service Calculator

namespace Application; class Calculator { /** * Returns the sum of two numbers. * * @param float $a The first operand * @param float $b The second operand * @return float The result * @throws SoapFault */ public function sum($a, $b) { if (!is_numeric($a) || !is_numeric($b)) { throw new SoapFault('The sum() operation only accepts numeric arguments.'); } return $a + $b; } }

Documenta1on  avec  de  la  PHPDoc  afin  de  générer  le  WSDL  correspondant  grâce  à  

Zend_Soap_Autodiscover  

Dependency Injection Obtenir le conteneur de services

# web/soap-server.php use Symfony\Component\Config\ContainerBuilder;

use Symfony\Component\DependencyInjection\ContainerBuilder;

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;

use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

$container = new ContainerBuilder(new ParameterBag());

$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../config'));

$loader->load('services.xml');

if (isset($_GET['wsdl'])) {

$autodiscover = $container->get('soap_autodiscover');

$response = $autodiscover->toXml();

} else {

$soapServer = $container->get('soap_server');

$response = $soapServer->handle();

}

header('Content-Type: text/xml');

echo $response;

Lecture  du  fichier  services.xml  et  chargement  de  la  défini1on  dans  le  conteneur  d’injec1on  de  dépendances  

Récupéra1on  du  serveur  SOAP  Zend  correctement  instancié  et  ini1alisé.  

Finder

Finder

Finder Rechercher des !chiers

§  Rechercher des !chiers dans une arborescence §  Filtres sur des critères

§  Type (!chiers ou répertoires) § Nom et extension (patterns) §  Taille § Date § …

§  Tri les résultats sur des attributs de !chiers

Finder Rechercher des !chiers

use Symfony\Component\Finder\Finder;

$finder = new Finder();

// All files in the current folder

$files = $finder->files()->in(__DIR__);

// All directories in the current folder

$files = $finder->directories()->in(__DIR__);

// All PHP files in the current folder only

$files = $finder->files()->name('/\.php$/')->depth(0)->in(__DIR__);

// All files whose size is between 1K and 2K inclusive

$files = $finder->files()->size('>= 1K')->size('<= 2K')->in(__DIR__);

Finder Rechercher des !chiers

// Search files in several folders

$files = $finder->files()->in(array(__DIR__, __DIR__.'/../src'));

// Sort file by name

$files = $finder->files()->name('/\.php$/')->in(__DIR__)->sortByName();

// Sort files by type

$files = $finder->files()->name('/\.php$/')->in(__DIR__)->sortByType();

// Filter by date

$files = $finder->files()->name('/\.(png|jpg|jpeg|gif)$/i')->

date('>= 2011-01-09')->

date('<= 2011-02-03')->in(__DIR__);

$files = $finder->files()->name('/\.(png|jpg|jpeg|gif)$/i')->

date('since 2011-01-09')->

date('before 2011-02-03')->in(__DIR__);

Finder Filtres personnalisés

# Create a lambda function that acts as a filter.

$onlyWritableFiles = function(\SplFileInfo $file) {

return $file->isWritable();

};

Un !ltre personnalisé est une fonction anonyme qui reçoit une instance de \SplFileInfo et retourne une valeur booléenne. # Find writeable TXT files

$finder = new Finder(); $files = $finder->files()-> name('/\.txt$/')-> filter($onlyWritableFiles)-> in(__DIR__.'/../cache');

foreach (iterator_to_array($files) as $path => $file) { echo $file->getFilename(); }

Templating

Templating

Templating Introduction §  Système de templating simple et efficace §  Supporte des moteurs de stockage différents

§  Fichiers §  Chaînes de caractères

§  Supporte des fonctionnalités « modernes » § Héritage de templates §  Slots § Aide de vue (« helpers »)

§  Extensible

Templating Génération d’un template simple

§  Chargement et rendu d’un template simple

Mo1fs  de  chemins  de  fichiers  de  templates  

Variables  du  template  

require_once __DIR__ .'/../src/autoload.php'; use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\TemplateNameParser; use Symfony\Component\Templating\Loader\FilesystemLoader; $loader = new FilesystemLoader(array( __DIR__.'/../src/tpl/%name%.php', __DIR__.'/../src/tpl/hello/%name%.php', )); $engine = new PhpEngine(new TemplateNameParser(), $loader); echo $engine->render('index', array('name' => 'Hugo'));

Templating Génération d’un template simple

§  La variable $view correspond à l’objet $engine précédent.

§  L’échappement des variables est assuré par la méthode escape() de l’objet $view.

# src/tpl/index.php <p> Hello <?php echo $view->escape($name) ?>! </p>

use Symfony\Component\Templating\PhpEngine; use Symfony\Component\Templating\TemplateNameParser; use Symfony\Component\Templating\Loader\FilesystemLoader; use Symfony\Component\Templating\Helper\SlotsHelper; $loader = new FilesystemLoader(array( __DIR__.'/../src/tpl/%name%.php', __DIR__.'/../src/tpl/hello/%name%.php', )); $engine = new PhpEngine(new TemplateNameParser(), $loader, array( new SlotsHelper() )); $content = $engine->render('index', array('name' => 'Hugo')); echo $content;

Templating Génération d’un template avancé

Support des aides de vue et de l’héritage de templates.

# src/tpl/layout.php <html> <head> <title> <?php $view['slots']->output('title', 'Templating component') ?> </title> </head> <body> <h1>Welcome!</h1> <?php $view['slots']->output('_content') ?> </body> </html>

# src/tpl/index.php <?php $view->extend('layout') ?> <?php $view['slots']->set('title', 'Hello Application') ?> <p> Hello <?php echo $view->escape($name) ?>! </p>

Templating Génération d’un template avancé

Extension du template de base

Dé!nition de la valeur d’un slot

Contenu évalué de index.php

Affichage du slot ou une valeur par défaut si non dé!ni

Templating Génération d’un template avancé

<!DOCTYPE html> <html> <head> <title>Hello Application</title> </head> <body> <h1>Welcome!</h1> <p> Hello Hugo! </p> </body> </html>

§  Le rendu !nal est le suivant :

Translation

Translation

Translation Introduction §  Gestionnaire de dictionnaires de traduction §  Supporte différents formats de stockage

§ Array §  PHP §  YAML §  XLIFF §  CSV

§  Supporte les chaînes dynamiques §  Embarque des algorithmes de pluralisation

Translation Dé!nition d’un dictionnaire XLIFF

# i18n/fr/messages.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd"> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="messages.xml"> <header/> <body> <trans-unit id="1"> <source>Hello</source> <target>Salut</target> </trans-unit> <trans-unit id="2"> <source>Hello %name%</source> <target>Bonjour %name%</target> </trans-unit> </body> </file> </xliff>

require __DIR__ .'/../src/autoload.php'; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\Translation\Loader\XliffFileLoader; // Création d’un objet Translator $translator = new Translator('fr', new MessageSelector()); // Chargement du dictionnaire XLIFF $translator->addLoader('xliff', new XliffFileLoader()); $translator->addResource('xliff', __DIR__.'/../i18n/fr/messages.xml', 'fr'); // Returns "Salut" echo $translator->trans('Hello'); // Returns "Bonjour Hugo" echo $translator->trans('Hello %name%', array('%name%' => 'Hugo'));

Translation Traduire des chaînes d’un catalogue

Translation Utiliser une locale par défaut

§  Dé!nition d’un second dictionnaire pour l’allemand # i18n/de/messages.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd"> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="messages.xml"> <header/> <body> <trans-unit id="1"> <source>Welcome in %city%</source> <target>Wilkommen in %city%</target> </trans-unit> </body> </file> </xliff>

Translation Utiliser une locale par défaut

§  Le gestionnaire de traduction peut charger plusieurs catalogues de traduction et forcer une locale par défaut.

$trans = new Translator('fr', new MessageSelector()); $trans->addLoader('xliff', new XliffFileLoader()); // Load two distinct dictionaries $trans->addResource('xliff', __DIR__.'/../i18n/de/messages.xml', 'de'); $trans->addResource('xliff', __DIR__.'/../i18n/fr/messages.xml', 'fr'); // Set the default fallback locale $trans->setFallbackLocale('de'); // Translation only exists in german catalog even if the locale is fr echo $trans->trans('Welcome in %city%', array('%city%' => 'Paris'));

Translation Pluralisation des chaînes

# i18n/fr/messages.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd"> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="messages.xml"> <header/> <body> <!-- ... --> <trans-unit id="3"> <source>{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples</source> <target>{0} Il n'y a pas de pomme|{1} Il y a une pomme|]1,Inf] Il y a %count% pommes</target> </trans-unit> </body> </file> </xliff>

Translation Pluralisation des chaînes

§  La méthode transChoice() facilite la pluralisation des chaînes $count = 5; $translator = new Translator('fr', new MessageSelector()); $translator->addLoader('xliff', new XliffFileLoader()); $translator->addResource('xliff', __DIR__.'/../i18n/fr/messages.xml', 'fr'); // Returns "Il y a <strong>5</strong> pommes" echo $translator->transChoice( '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', $count, array('%count%' => '<strong>' . $count . '</strong>') );

Locale

Locale

Locale Introduction

§  Surcharge la classe Locale de PHP 5.3

§  Supporte les formatages des noms de pays

§  Supporte les formatages des noms de langues

§  L’API se compose uniquement de méthodes statiques

Locale Obtenir la listes des pays

Array ( [AF] => Afghanistan [ZA] => Afrique du Sud [AL] => Albanie ... )

Array ( [AF] => Afghanistan [AL] => Albania [ZA] => South Africa ... )

Array ( [AF] => Afghanistan [AL] => Albanien [ZA] => Südafrika ... )

§  Les pays sont retournés dans des tableaux associatifs triés sur les valeurs

use Symfony\Component\Locale\Locale; print_r(Locale::getDisplayCountries('fr')); print_r(Locale::getDisplayCountries('en')); print_r(Locale::getDisplayCountries('de'));

Locale Codes ISO des pays

Array ( [0] => AF [1] => AX [2] => AL [3] => DZ [4] => AS [5] => AD [6] => AO [7] => AI [8] => AQ [9] => AG [10] => AR [11] => AM [12] => AW [13] => AC [14] => AU [15] => AT [16] => AZ [17] => BS [18] => BH [19] => BD [20] => BB [21] => BY [22] => BE [23] => BZ [24] => BJ [25] => BM [26] => BT [27] => BO [28] => BA [29] => BW [30] => BV [31] => BR [32] => IO [33] => VG [34] => BN [35] => BG [36] => BF [37] => BI [38] => KH [39] => CM [40] => CA [41] => IC [42] => CV [43] => KY [44] => CF [45] => EA [46] => TD [47] => CL [48] => CN [49] => CX [50] => CP [51] => CC [52] => CO [53] => KM [54] => CG [55] => CD [56] => CK [57] => CR [58] => CI [59] => HR [60] => CU [61] => CY [62] => CZ [63] => DK [64] => DG [65] => DJ [66] => DM [67] => DO [68] => EC [69] => EG [70] => SV [71] => GQ [72] => ER [73] => EE ...)

use Symfony\Component\Locale\Locale; print_r(Locale::getCountries());

Locale Obtenir la listes des locales

Array ( [fr_BE] => français (Belgique) [fr_CA] => français (Canada) [fr_DJ] => français (Djibouti) [fr_LU] => français (Luxembourg) [fr_CH] => français (Suisse) ... )

§  Les locales sont retournées dans des tableaux associatifs triés sur les valeurs

Array ( [fr_BE] => French (Belgium) [fr_CA] => French (Canada) [fr_DJ] => French (Djibouti) [fr_LU] => French (Luxembourg) [fr_CH] => French (Switzerland) ... )

use Symfony\Component\Locale\Locale; print_r(Locale::getDisplayLocales('fr')); print_r(Locale::getDisplayLocales('en'));

Locale Codes ISO des locales

Array ( [0] => af [1] => af_NA [2] => ak [3] => sq [4] => am [5] => am_ET [6] => ar [7] => ar_DZ [8] => ar_BH [9] => ar_EG [10] => ar_IQ [11] => ar_JO [12] => ar_KW [13] => ar_LB [14] => ar_LY [15] => ar_MA [16] => ar_OM [17] => ar_QA [18] => ar_SA [19] => ar_SD [20] => ar_SY [21] => ar_TN [22] => ar_AE [23] => ar_YE [24] => hy [25] => as [26] => as_IN [27] => asa [28] => az [29] => az_Cyrl [30] => az_Cyrl_AZ [31] => az_Latn [32] => az_Latn_AZ [33] => bm [34] => eu [35] => be [36] => bem [37] => bez [38] => bn [39] => bn_IN [40] => bs [41] => bg [42] => my [43] => my_MM [44] => ca [45] => tzm [46] => tzm_Latn [47] => tzm_Latn_MA [48] => chr [49] => chr_US [50] => cgg [51] => zh [52] => kw [53] => hr [54] => cs [55] => da [56] => nl [57] => nl_BE [58] => ebu [59] => ebu_KE [60] => en [61] => en_AS [62] => en_AU [63] => en_BE [64] => en_BZ [65] => en_BW [66] => en_CA [67] => en_GU [68] => en_HK [69] => en_IN ...)

use Symfony\Component\Locale\Locale; print_r(Locale::getLocales());

Locale Obtenir la listes des langues

Array ( [en] => anglais [en_US] => anglais américain [en_AU] => anglais australien [en_GB] => anglais britannique [en_CA] => anglais canadien ... )

§  Les langues sont retournées dans des tableaux associatifs triés sur les valeurs

Array ( [en] => English [en_AU] => Australian English [en_GB] => British English [en_CA] => Canadian English [en_US] => U.S. English ... )

use Symfony\Component\Locale\Locale; print_r(Locale::getDisplayLanguages('fr')); print_r(Locale::getDisplayLanguages('en'));

Locale Codes ISO des langues

Array ( [0] => ab [1] => ace [2] => ach [3] => ada [4] => ady [5] => aa [6] => afh [7] => af [8] => afa [9] => ain [10] => ak [11] => akk [12] => sq [13] => ale [14] => alg [15] => tut [16] => am [17] => egy [18] => grc [19] => anp [20] => apa [21] => ar [22] => an [23] => arc [24] => arp [25] => arn [26] => arw [27] => hy [28] => rup [29] => art [30] => as [31] => ast [32] => asa [33] => ath [34] => cch [35] => en_AU [36] => aus [37] => de_AT [38] => map [39] => av [40] => ae [41] => awa [42] => ay [43] => az [44] => ban [45] => bat [46] => bal [47] => bm [48] => bai [49] => bad [50] => bnt [51] => bas [52] => ba [53] => eu [54] => btk [55] => bej [56] => be [57] => bem [58] => bez [59] => bn [60] => ber [61] => bho [62] => bh [63] => bik [64] => bin [65] => bi [66] => byn [67] => zbl [68] => brx [69] => bs [70] => bra [71] => pt_BR [72] => br [73] => en_GB [74] => bug [75] => bg [76] => bua [77] => my [78] => cad [79] => en_CA [80] => fr_CA [81] => yue [82] => car [83] ...)

use Symfony\Component\Locale\Locale; print_r(Locale::getLanguages());

Console

Console

Console Introduction

§  Automatiser des tâches pénibles (génération de code…)

§  Exécuter des tâches qui prennent du temps

§  Béné!cier des ressources du serveur

§  Plani!er des tâches dans une crontab

Console Introduction

§  Arguments et options

§  Codes de sortie

§  Shell

§  Coloration de la sortie

§  Testabilité

§  Messages d’erreur

§  …

Console Amorcer le CLI

#!/usr/bin/php<?phprequire __DIR__ .'/src/autoload.php';use Symfony\Component\Console\Application;$application = new Application('Console', '1.0');$application->run();  

§  Création d’un !chier « console.php » exécutable (chmod +x)

Console Amorcer le CLI

namespace Application\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Application\Service\GoogleWeather; class WeatherCommand extends Command { protected function configure() { $this-> addArgument('city', InputArgument::REQUIRED, 'The city')-> addOption('lang', null, InputOption::VALUE_REQUIRED, 'The lang', 'en')-> setName('google:weather')-> setDescription('Fetches weather information.')-> setHelp(<<<EOF The <info>google:weather</info> command fetches weather data for a given city: <info>google:weather paris --lang fr</info> EOF ); }}

Dé!nition des meta données de la commande : nom, manuel, arguments, options…

Console Dé!nir la logique de la commande

class WeatherCommand extends Command { # ... protected function execute(InputInterface $input, OutputInterface $output) { $city = $input->getArgument('city'); $lang = $input->getOption('lang'); $weather = new GoogleWeather($city, $lang); $output->writeln(sprintf('<info>City</info>: %s', $weather->getCity())); $output->writeln(sprintf('<info>Temperature</info>: %s', $weather->getTemperature())); $output->writeln(sprintf('<info>Latitude</info>: %s', $weather->getLatitude())); $output->writeln(sprintf('<info>Longitude</info>: %s', $weather->getLongitude())); $output->writeln(sprintf('<info>Condition</info>: %s', $weather->getCondition())); $output->writeln(sprintf('<info>Wind</info>: %s', $weather->getWindCondition())); } }

$input : arguments + options $output : sortie de la console

Console Enregistrer la commande

#!/usr/bin/php<?phprequire __DIR__ .'/src/autoload.php';use Symfony\Component\Console\Application;use Application\Command\WeatherCommand;$application = new Application('Console', '1.0');$application->add(new WeatherCommand());$application->run();  

Console Consulter la liste des commandes

Console Obtenir de l’aide de la commande

./console help google:weather  

Console Exécuter la commande

./console google:weather Berlin --lang fr  

Process

Process

Process Introduction

§  Exécuter des lignes de commande depuis un script PHP

§  Récupérer la sortie générée par la console

§  Récupérer le statut de l’exécution de la console

Process Mise en oeuvre

use Symfony\Component\Process\Process; $process = new Process('ls -lah', __DIR__); $process->run(); if (!$process->isSuccessful()) { $output = $process->getErrorOutput(); } else { $output = $process->getOutput(); } echo $output;

Process Mise en oeuvre total 176 drwxr-xr-x 25 Hugo staff 850B Feb 23 22:53 . drwxr-xr-x 9 Hugo staff 306B Feb 13 19:39 .. -rw-r--r--@ 1 Hugo staff 607B Feb 19 12:48 crawler.php -rw-r--r--@ 1 Hugo staff 1.9K Feb 13 12:08 event-dispatcher.php -rw-r--r--@ 1 Hugo staff 914B Feb 13 13:43 event-dispatcher2.php -rw-r--r--@ 1 Hugo staff 678B Jan 31 21:55 file.php -rw-r--r--@ 1 Hugo staff 1.7K Feb 12 14:40 finder.php drwxrwxrwx 3 Hugo staff 102B Jan 31 21:55 images -rw-r--r--@ 1 Hugo staff 276B Jan 9 23:39 locale.php -rw-r--r--@ 1 Hugo staff 454B Feb 23 22:53 process.php -rw-r--r--@ 1 Hugo staff 746B Jan 31 22:26 read-request.php -rw-r--r--@ 1 Hugo staff 545B Jan 31 22:41 response.php -rw-r--r--@ 1 Hugo staff 1.3K Feb 9 23:58 routing.php -rw-r--r--@ 1 Hugo staff 718B Feb 1 23:38 serializer.php -rw-r--r--@ 1 Hugo staff 502B Jan 9 00:37 session1.php -rw-r--r--@ 1 Hugo staff 682B Jan 9 00:37 session2.php -rw-r--r--@ 1 Hugo staff 520B Jan 9 01:15 session3.php -rw-r--r--@ 1 Hugo staff 241B Feb 6 11:28 soap-client.php -rw-r--r--@ 1 Hugo staff 737B Feb 19 10:45 soap-server.php -rw-r--r--@ 1 Hugo staff 558B Feb 19 10:56 templating-advanced.php -rw-r--r--@ 1 Hugo staff 476B Feb 19 10:56 templating-basic.php

PhpProcess Exécuter un script PHP

use Symfony\Component\Process\PhpProcess; $process = new PhpProcess('<?php echo "Hello World!" ?>'); $process->run(); if (!$process->isSuccessful()) { $output = $process->getErrorOutput(); } else { $output = $process->getOutput(); } echo $output;

Serializer

Serializer

Serializer Introduction

Le composant « Serializer » convertit des objets PHP en

représentations XML ou JSON par introspection.

Serializer Architecture

Serializer Architecture

§  L’objet « Serializer » transforme un objet PHP en représentation XML (ou JSON) et inversement.

§  Les objets « Normalizer » transforment un objet en tableau

PHP. §  Les objets « Encoder » transforment un tableau PHP en

représentations XML ou JSON.

class Author { private $firstName; private $lastName; public function __construct($firstName, $lastName) { $this->firstName = $firstName; $this->lastName = $lastName; } public function getFirstName() { return $this->firstName; } public function getLastName() { return $this->lastName; } public function getFullName() { return $this->firstName .' '. $this->lastName; } }

class Book { private $title; private $author; public function setTitle($title) { $this->title = $title; } public function getTitle() { return $this->title; } public function setAuthor(Author $author) { $this->author = $author; } public function getAuthor() { return $this->author; } }

Serializer Sérialiser un objet en XML

use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\XmlEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; $serializer = new Serializer(); # Register the XML encoder $serializer->setEncoder('xml', new XmlEncoder()); # Register a getter / setter normalizer $serializer->addNormalizer(new GetSetMethodNormalizer()); $book = new Book(); $book->setAuthor(new Author('Agatha', 'Christie')); $book->setTitle('Ten Little Niggers'); header('Content-Type: text/xml; charset=utf-8'); echo $serializer->serialize($book, 'xml');

Serializer Sérialiser un objet en XML

Serializer Sérialiser un objet en JSON

use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; $serializer = new Serializer(); # Register the JSON encoder $serializer->setEncoder('json', new JsonEncoder()); # Register a getter / setter normalizer $serializer->addNormalizer(new GetSetMethodNormalizer()); $book = new Book(); $book->setAuthor(new Author('Agatha', 'Christie')); $book->setTitle('Ten Little Niggers'); header('Content-Type: application/json; charset=utf-8'); echo $serializer->serialize($book, 'json');

Serializer Sérialiser un objet en JSON

{ "title":"Ten Little Niggers", "author": { "firstname":"Agatha", "lastname":"Christie", "fullname":"Agatha Christie" } }

use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; $serializer = new Serializer(); $serializer->setEncoder('json', new JsonEncoder()); $serializer->addNormalizer(new GetSetMethodNormalizer()); $jsonEncoded = '{ "title":"Ten Little Niggers", "author": { "firstname":"Agatha", "lastname":"Christie", "fullname":"Agatha Christie" } }'; header('Content-Type: text/plain; charset=utf-8'); print_r($serializer->decode($jsonEncoded, 'json'));

Serializer Désérialiser un objet JSON

Array ( [title] => Ten Little Niggers [author] => Array ( [firstname] => Agatha [lastname] => Christie [fullname] => Agatha Christie ) )

YAML

YAML

YAML Introduction

YAML signi!e « Ain't Markup Language ». C’est un format

standard de sérialisation de données pour tous les

langages de programmation.

YAML Parser un !chier YAML

parameters: # Twitter module twitter.timeline.cache: true twitter.auth.username: hhamon twitter.auth.password: s3cr3t # Blog module blog.post.max_per_page: 10 app: available_cultures: [fr, en, de] services: db_connection: class: \PDO dsn: mysq:host=localhost;dbname=foo username: root password: ~

YAML Parser un !chier YAML

use Symfony\Component\Yaml\Parser;$content = file_get_contents(__DIR__ . '/../config/config.yml');$yaml = new Parser();$result = $yaml->parse($content);echo '<pre>’;print_r($result);echo '</pre>';

§  La classe « Parser » transforme une chaîne YAML en un tableau associatif PHP.

Array ( [parameters] => Array ( [twitter.timeline.cache] => 1 [twitter.auth.username] => hhamon [twitter.auth.password] => s3cr3t [blog.post.max_per_page] => 10 ) [app] => Array ( [available_cultures] => Array ( [0] => fr [1] => en [2] => de ) ) [services] => Array ( [db_connection] => Array ( [class] => \PDO [dsn] => mysq:host=localhost;dbname=foo [username] => root [password] => ) ) )

YAML Transformer un tableau en YAML

use Symfony\Component\Yaml\Parser; use Symfony\Component\Yaml\Dumper; $yaml = new Parser(); $result = $yaml->parse(file_get_contents(__DIR__ . '/../config/config.yml')); $result['parameters']['twitter.timeline.cache'] = false; $result['services']['db_connection']['class'] = '\PdoMock'; $dumper = new Dumper(); $data = $dumper->dump($result, 2); file_put_contents(__DIR__ . '/../config/config_test.yml', $data);

§  La classe « Dumper » transforme un tableau PHP en YAML

YAML Transformer un tableau en YAML

parameters: twitter.timeline.cache: false twitter.auth.username: hhamon twitter.auth.password: s3cr3t blog.post.max_per_page: 10 app: available_cultures: [fr, en, de] services: db_connection: { class: \PdoMock, dsn: 'mysq:host=localhost;dbname=foo', username: root, password: null }

CSS Selector

CSS Selector

CSS Selector CSS3 vers XPath

use Symfony\Component\CssSelector\Parser; $doc = new \DOMDocument(); $doc->loadHTMLFile(__DIR__.'/../src/tpl/page.html'); $xpath = new \DOMXPath($doc); $expr = Parser::cssToXpath('ul#menu > li a:contains("Home")'); $xpath->query($expr); foreach ($xpath->query($expr) as $link) { printf('%s > %s', $link->nodeValue, $link->getAttribute('href')); }

CSS Selector CSS3 vers XPath

Parser::cssToXpath('ul#menu > li a:contains("Home")');

descendant-or-self::ul[@id = 'menu']/li/descendant::a[contains(string(.), 'Home')]

DomCrawler

DomCrawler

DomCrawler Introduction

Le composant DomCrawler offre une API simple pour analyser

un document SGML grâce au support intégral des sélecteurs

CSS3 et XPath.

DomCrawler Introduction §  Analyser et extraire des informations d’un document SGML

§  Supporte différentes sources de données

Ø  Chaîne HTML ou XML

Ø  Objet DOMNode ou DOMNodeList

Ø  Objet DOMDocument

§  Supporte les sélecteurs CSS3 et Xpath

§  Crawler s’appuie sur le composant CssSelector

DomCrawler Instancier un Crawler

use Symfony\Component\DomCrawler\Crawler; // from an HTML string $crawler = new Crawler(file_get_contents('http://www.google.com')); // from a DomDocument object $doc = new \DOMDocument(); $doc->loadHTML(file_get_contents('http://www.google.com')); $crawler = new Crawler($doc, 'http://www.google.com'); // from a DomNodeList object $list = $doc->getElementsByTagName('ul'); $crawler = new Crawler($list); // from a DomNode object $logo = $doc->getElementById('logo'); $crawler = new Crawler($logo);

DomCrawler Traverser un document /** * <div id="sidebar"> * <h2>Sidebar</h2> * <ul> * <li><a href="home.html">Home</a></li> * <li><a href="about.html">About</a></li> * <li> * <a href="contact.html">Contact</a> * </li> * </ul> * </div> */ $label = $crawler->filter('#sidebar ul li')-> last()->children()-> eq(0)->text();

DomCrawler Traverser un document

/** * <div id="sidebar"> * <h2>Sidebar</h2> * <ul> * <li><a href="home.html">Home</a></li> * <li><a href="about.html">About</a></li> * <li> * <a href="contact.html">Contact</a> * </li> * </ul> * </div> */ $links = $crawler->filter('#sidebar ul li a')->links();

The End…

Questions ?

http://joind.in/talk/view/2766

hugo.hamon@sensio.com @hhamon