Introduction à Symfony2
•  Hugo HAMON (@hhamon) •  Responsable des formations Sensio Labs •  Secrétaire Général de l’AFUP •  10 ans de développement web dont 8 avec PHP •  Coauteur d’ouvrages Eyrolles •  Apprendre-PHP.com / HugoHamon.com
Qu’est-ce que Symfony2 ?
Un framework web
PHP 5.3
Objectifs ?
•  Développer plus vite et mieux •  Faciliter le travail en équipe •  Pérenniser les applications •  Simpli er la maintenance et les évolutions •  Se concentrer sur la logique métier •  Ne pas réinventer la roue !
Symfony2 intègre les meilleurs outils Open-Source PHP
Symfony Components Dependency Injection Container Request Handler Event Dispatcher Console YAML …
Zend Framework PHPUnit Doctrine2 Swift Mailer Twig
Différences avec symfony 1.x ?
Même philosophie, Même outillage, Moins de concepts, Plus de exibilité Performances accrues 
Où en est-on aujourd’hui ?
•  Version ALPHA •  Briques logicielles manquantes •  Documentation incomplète •  L’API peut encore beaucoup évoluer •  Version stable repoussée à début Mars 2011
Je veux tester Symfony2 ! git clone http://github.com/symfony/symfony-sandbox.git http://www.symfony-reloaded.org
Je veux développer un projet client maintenant avec Symfony2 ? A ta place, je ne ferai pas ça…
Quel outillage ?
•  Sécurité •  Extensibilité •  Architecture MVC •  I18N & L10N •  URLs élégantes •  Authenti cation et ACLs •  DBAL & ORM •  Tests unitaires •  Outils de débogage •  Tests fonctionnels •  Formulaires •  Cache •  Con guration •  Admin Generator
Architecture d’un projet Symfony2
Un Projet Symfony2 est un répertoire qui se compose d’une Application, d’un jeu de Bundles et de librairies.
. |-- LICENSE |-- README |-- app/ | |-- AppCache.php | |-- AppKernel.php Répertoire  de  l’Applica0on   | |-- cache/ | |-- config/ | |-- console | |-- logs/ | |-- phpunit.xml.dist | `-- views/ |-- bin/ | |-- create_sandbox.sh | |-- install_vendors.sh | |-- prepare_vendors.sh | `-- update_vendors.sh |-- src/ | |-- Application/ | |-- Bundle/ Code  de  l’Applica0on  +     | |-- autoload.php Bundles  +     | `-- vendor/ Librairies  externes   `-- web/ |-- bundles/ |-- check.php |-- index.php Dossier  public   `-- index_dev.php
Une Application est un répertoire qui contient la con guration pour un jeu de Bundles donné.
Structure d’une application app/ |-- AppCache.php |-- AppKernel.php The  AppKernel  class  is  the   |-- cache/  main  class  of  the  applica0on   |-- config/ | |-- config.yml | |-- config_dev.yml | |-- config_test.yml | |-- routing.yml Configura0on  files   | `-- routing_dev.yml |-- console |-- logs/ | `-- dev.log |-- phpunit.xml.dist Logs  and  applica0on  templates   `-- views/ |-- layout.php
Un Bundle est un ensemble structuré et cohérent de chiers qui implémentent une fonctionnalité (un blog, un forum, …) et qui peut facilement être partagé avec d’autres développeurs.
symfony 1.x => Plugins Symfony 2.x => Bundles
+ BlogBundle/ Code  source  du  bundle  :  contrôleurs   |-- Controller/ | `-- BlogController.php modèles,  formulaires…   |-- Entity/ | `-- Post.php |-- Form/ | `-- PostForm.php Le  fichier  BlogBundle.php  est   |-- BlogBundle.php |-- Model/ obligatoire  et  con0ent  la  classe  qui   | `-- PostRepository.php déclare  le  bundle.   |-- Resources/ | |-- config/ | | `-- routing.yml | |-- views/ | | `-- Blog/ Un  bundle  peut  contenir  de  la   | | |-- showPost.php configura0on,  des  templates  et  des   | | `-- listPost.php ressources  web.   | `-- public/ | `-- css/ | `-- blog.css `-- Tests/ Un  bundle  peut  aussi  contenir   `-- Controller/ `-- BlogControllerTest.php des  scripts  de  tests  PHPUnit.  
Sécurité
XSS CSRF SQL Injections
Le dossier web/ du projet est le seul accessible depuis un navigateur web
Routage et URLs
Le système de routage a pour rôle de convertir une URL en une réponse web.
Elles sont propres et élégantes a n d’exposer des informations pertinentes et de masquer l’implémentation technique… http://www.domain.com/blog/2010/09/15/symfony2-rocks
Con guration des URLs en YAML # src/Bundle/BlogBundle/Resources/config/routing.yml post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost } Exemple d’URL générée http://www.domain.com/blog/2010/09/15/symfony2-rocks
Association URL et Code ?
# src/Application/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } }
# src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  du  Bundle   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller Un  dossier  /  namespace   { public function indexAction($name) { // ... } }
# src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  du  contrôleur   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller Une  classe   { public function indexAction($name) { // ... } }
# src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  de  l’ac0on   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } Une  méthode   }
# src/Application/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } }
Les paramètres peuvent être passés dans un ordre arbitraire post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost } namespace ApplicationBlogBundleController; class BlogController extends Controller { public function showPostAction($slug, $year) { // ... } }
Architecture MVC
•  Séparation du code en trois couches – Logique métier dans le Modèle – Logique applicative dans le Contrôleur – Affichage dans la Vue (templates) •  Modularité et découplage du code •  Maintenance simpli ée sur le code source •  Code testable unitairement et plus robuste
Les actions pour la logique applicative. Elles se situent dans les Contrôleurs.
๏  Une action est accessible depuis une URL # src/Application/BlogBundle/Resources/config/routing.yml post_show: pattern: /blog/article/:id/show defaults: { _controller: BlogBundle:Blog:show }
# src/Application/BlogBundle/Controller/BlogController.php namespace ApplicationBlogBundleController; use SymfonyBundleFrameworkBundleControllerController; class BlogController extends Controller { public function showAction($id) Paramètres  de  l’url   { // find the article by its id $post = ...; // render the view return $this->render('BlogBundle:Blog:show', array('post' => $post)); } } Template  à  rendre   Variables  du  template  
Les templates constituent la couche de présentation des données, la vue.
๏  Syntaxe alternative de PHP ๏  Quelques brèves instructions PHP (echo, if, foreach…) ๏  Echappement automatique des variables # src/Application/BlogBundle/Resources/views/Blog/show.php <?php $view->extend('::layout') ?> Layout  de  décora0on   <h2><?php echo $post->getTitle() ?></h2> <p> <?php echo $post->getContent() ?> </p> Variables  échappées  =>  pas  de  XSS  !!!  
Héritage de Vues
# src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('::layout') ?> Hello <?php echo $name ?>! étend # app/views/layout.php <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
Héritage de Vues layout.php _content Hello Hugo! index.php
# src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('HelloBundle::layout') ?> Hello <?php echo $name ?>! # src/Application/HelloBundle/Resources/views/layout.php <?php $view->extend('::layout') ?> <h1>Hello Application</h1> <div> <?php $view['slots']->output('_content') ?> </div> # app/views/layout.php <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
Héritage multiple ::layout.php HelloBundle::layout.php _content Hello Hugo! _content index.php
Les Slots sont des fragments dé nis dans un template et affichés dans un layout décorant ce dernier.
# src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['slots']->set('title', 'Hello World app') ?> # app/views/layout.php <html> <head> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
Symfony fournit des mécanismes simples pour évaluer et inclure des templates dans un autre # src/Application/HelloBundle/Resources/views/Hello/hello.php Hello <?php echo $name ?>! # Including another template in the current template <?php echo $view->render('HelloBundle:Hello:hello', array('name' => $name)) ?> src/Bundle/HelloBundle/Resources/views/Hello/hello.php  
Symfony offre également un moyen d’inclure le rendu d’une action depuis une vue… # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['actions']->output('HelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green’ )) ?>
# src/Application/HelloBundle/Controller/HelloController.php class HelloController extends Controller { public function fancyAction($name, $color) { // create some object, based on the $color variable $object = ...; return $this->render('HelloBundle:Hello:fancy', array( 'name' => $name, 'object' => $object )); } // ... }
Les aides de vue sont des objets accessibles depuis les templates et qui permettent de simpli er la logique d’affichage
Générer une URL avec le router helper <a href="<?php echo $view['router']->generate('hello', array( 'name' => 'Thomas')) ?>">Greet Thomas!</a> Inclure des feuilles de style <head> <!-- ... --> <?php $view['stylesheets']->add('css/styles.css') ?> <?php echo $view['stylesheets'] ?> </head>
Inclure des javascripts <head> <!-- ... --> <?php $view['javascripts']->add('js/libraries.js') ?> <?php echo $view['javascripts'] ?> </head> Manipuler des ressource web (images, ash…) <img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" src=""/> Traduire des chaînes de l’interface <?php echo $view['translator']->trans('Symfony is %what%!', array( '%what%' => 'awesome')) ?>
Con guration
3 formats de con guration PHP YAML XML
Quel format choisir ? Avantages Inconvénients XML Validation Verbeux Complétion dans les EDIs Long à écrire Facile à analyser YAML Concis Besoin du composant YAML Facile à lire Pas de validation Facile à modi er Pas d’autocomplétion PHP Flexible Pas de validation Plus facile à manipuler
Con guration en YML # app/config/routing.php homepage: pattern: / defaults: { _controller: FrameworkBundle:Default:index } hello: resource: HelloBundle/Resources/config/routing.yml Import  d’une  autre  configura0on  
Con guration en PHP # app/config/routing.php use SymfonyComponentRoutingRouteCollection; use SymfonyComponentRoutingRoute; $collection = new RouteCollection(); $collection->addRoute('homepage', new Route('/', array( '_controller' => 'FrameworkBundle:Default:index', ))); $collection->addCollection( $loader->import("HelloBundle/Resources/config/routing.php") ); Import  d’une  autre  configura0on   return $collection;
Con guration en XML # app/config/routing.xml <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://www.symfony-project.org/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.symfony-project.org/schema/routing http:// www.symfony-project.org/schema/routing/routing-1.0.xsd"> <route id="homepage" pattern="/"> <default key="_controller">FrameworkBundle:Default:index</default> </route> <import resource="HelloBundle/Resources/config/routing.xml" /> </routes> Import  d’une  autre  configura0on  
Import de chiers INI # app/config/config_dev.yml imports: - { resource: config.yml } - { resource: custom.ini} zend.logger: priority: debug path: %kernel.root_dir%/logs/%kernel.environment%.log # app/config/custom.ini [parameters] dice.min = 1 dice.max = 6
Accès à la con guration depuis le code public function diceAction() { // ... $min = (int) $this->container->getParameter('dice.min'); $max = (int) $this->container->getParameter('dice.max'); // ... }
Outils de Débogage
Parce qu’il est important pour un développeur d’identi er rapidement les bogues et les problèmes !!!
Web Debug Toolbar
Logs
Traces d’exception Trace  de  l’excep0on   courrante   Trace  pour  une   InvalidArgumentExcep0on   404  Status  Code  
Afficher  /  masquer  la  trace  d’une   excep0on   Logs  enregistrés   Lien  vers  le  profiler  
Pro ler
Trace  de  l’excep0on   Recherche  dans  les  logs  
Requêtes SQL
Extensibilité
http://www.symfony2bundles.org
Enregistrement de bundles # app/AppKernel.php class AppKernel extends Kernel { // ... public function registerBundles() { $bundles = array( // ... // Register third party bundles new BundleTwitterBundleTwitterBundle(), new BundleForumBundleForumBundle() ); // ... return $bundles; } }
DBAL & ORM Doctrine2
•  Abstraction de base de données relationnelles •  Performance •  Plus de magie •  Manipulation de vrais objets PHP (POPO) •  Génération de code •  Adapteur MongoDB disponible
๏  Con gurer la connexion BDD en YAML # app/config/config.yml doctrine.dbal: dbname: Blog user: root password: ~ doctrine.orm: ~ ๏  Con gurer la connexion BDD dans Apache // web/.htaccess or in the vhost configuration SetEnv SYMFONY__DOCTRINE__DBAL__USERNAME "root" SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "secret"
๏  Dé nition d’une entité (table) à l’aide d’une classe PHP namespace ApplicationBlogBundleEntity; /** * @Entity(repositoryClass="ApplicationBlogBundleModelBlogPostRepository") * @Table(name="blog_post") */ Annota0ons   class BlogPost { /** * @Id @Column(type="integer") * @GeneratedValue(strategy="IDENTITY") */ protected $id; /** @Column(length=100) */ protected $title; /** @Column(type="text") */ protected $content; }
๏  Génération de la base de données à partir des classes PHP $ php app/console doctrine:database:create $ php app/console doctrine:schema:create ๏  Chargement des données de test $ php app/console doctrine:data:load
๏  Les données de test sont écrites en pur PHP # src/Application/BlogBundle/Resources/data/fixtures/doctrine/fixtures.php use ApplicationBlogBundleEntityBlogPost; $post1 = new BlogPost(); $post1->setTitle('My first blog post'); $post1->setContent('Lorem ipsum dolor sit amet...'); $post2 = new BlogPost(); $post2->setTitle('My second blog post'); $post2->setContent('Lorem ipsum dolor sit amet...'); $post3 = new BlogPost(); $post3->setTitle('My third blog post'); $post3->setContent('Lorem ipsum dolor sit amet...');
๏  Ecrire des requêtes DQL dans un modèle Doctrine # src/Application/BlogBundle/Model/BlogPostRepository.php namespace ApplicationBlogBundleModel; use DoctrineORMEntityRepository; class BlogPostRepository extends EntityRepository { public function getHomepagePosts() { $query = $this->_em->createQuery(' SELECT u FROM BlogBundle:BlogPost u ORDER BY u.id DESC '); return $query->getResult(); } }
๏  Interroger la base de données à l’aide du Modèle Doctrine # src/Application/BlogBundle/Controller/BlogController.php // ... class BlogController extends Controller { public function indexAction() { $em = $this['doctrine.orm.entity_manager']; $posts = $em->getRepository('BlogBundle:BlogPost') ->getHomepagePosts(); return $this->render('BlogBundle:Blog:index', array( 'posts' => $posts )); } // ... }
Emails Swift Mailer
•  API Orientée Objet Open-Source •  Support des connexions SMTP •  Support des pièces jointes •  Support des formats de mails (text, html…) •  Gestion des les d’attente (spools) •  Facile à con gurer et à étendre avec des plugins
Con gurer Swift Mailer # app/config/config.yml swift.mailer: transport: smtp encryption: ssl auth_mode: login host: smtp.gmail.com username: your_username password: your_password
Envoyer un Email public function indexAction($name) { Récupéra0on  du  service  d’envoi  de  mails   $mailer = $this['mailer']; $message = Swift_Message::newInstance() ->setSubject('Hello Email') ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody($this->renderView('HelloBundle:Hello:email', array( 'name' => $name ))); Généra0on  du  corps  du  mail  à  l’aide   d’un  template  et  de  la  méthode   $mailer->send($message); renderView()   return $this->render(...); }
Tests Automatisés PHPUnit
•  Tests Unitaires et Couverture de Code •  Garantir la qualité du code •  Eviter les bugs et les régressions •  Documenter le code •  Industrialiser et professionnaliser les développements
๏  Exemple de script de tests unitaires dans Symfony2 # src/Application/BlogBundle/Tests/Entity/BlogPostTest.php namespace ApplicationBlogBundleTestsEntity; use ApplicationBlogBundleEntityBlogPost; class BlogPostTest extends PHPUnit_Framework_TestCase { public function testTitleIsSlugifiedOnce() { $slug = 'symfony2-rules-the-world'; $post = new BlogPost(); $post->setTitle('Symfony2 rules the world'); $this->assertEquals($slug, $post->getSlug()); // Slug doesn't change when it's already set $post->setTitle('An other title'); $this->assertEquals($slug, $post->getSlug()); } }
•  Tests fonctionnels •  Simuler des scénarios de navigation •  Simuler un client Web (navigateur) •  Véri er que l’application respecte le cahier des charges
๏  Exemple de script de tests fonctionnels dans Symfony2 class BlogControllerTest extends WebTestCase { // ... public function testAddComment() { $this->client->followRedirects(); $crawler = $this->client->request('GET', '/'); // Get the first link to a post $link = $crawler->filter('h2.post a')->first()->link(); // Click on the link and check there are two comments $crawler = $this->client->click($link); $this->assertTrue($crawler->filter('.comment')->count() == 2); } }
๏  Simuler des requêtes GET $crawler = $client->request('GET', '/hello/Fabien'); ๏  Simuler des requêtes POST $client->request('POST', '/submit', array('name' => 'Fabien') ๏  Simuler des uploads de chiers en POST $client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => '/path/to/photo') );
๏  Simuler une requête HTTP DELETE avec des entêtes $client->request('DELETE', '/post/12', array(), array(), array( 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word' )); ๏  Désactiver / activer les redirections HTTP $client->followRedirects(false); $client->followRedirect(); ๏  Insoler le client dans un processus séparé $client->insulate();
๏  Naviguer dans l’historique comme dans un navigateur web $client->back(); $client->forward(); $client->reload(); ๏  Réinitialiser le Client $client->restart();
๏  Parcourir le DOM avec le DOM Crawler // Nodes that match the CSS selector $crawler->filter('h1'); // Nodes that match the XPath expression $crawler->filterXpath('h1'); // Node for the specified index $crawler->eq(1); // First node $crawler->first(); // Last node $crawler->last();
// Siblings $crawler->siblings(); // All following siblings $crawler->nextAll(); // All preceding siblings $crawler->previousAll(); // Parent nodes $crawler->parents(); // Children $crawler->children(); // Nodes for which the callable, a lambda, returns true $crawler->reduce($lambda);
๏  Extraire des données sur des noeuds // Returns the attribute value for the first node $crawler->attr('class'); // Returns the node value for the first node $crawler->text(); // Extracts an array of attributes for all nodes // (_text returns the node value) $crawler->extract(array('_text', 'href')); // Executes a lambda for each node // and return an array of results $data = $crawler->each(function ($node, $i) { return $node->getAttribute('href'); });
๏  Simuler des clics sur des liens ou boutons $crawler->selectLink('Click here'); $link = $crawler->link(); $client->click($link); $links = $crawler->links(); ๏  Poster des formulaires // Select the submit button of a form $crawler->selectButton('submit'); // Get a form instance $form = $crawler->form(); // Override the default form values $form = $crawler->form(array( 'name' => 'Fabien', 'like_symfony' => true, ));
Performances
•  PHP 5.3.2 minimum •  “ Cachy framework “ •  Cache HTTP & Proxy cache (ESI) •  Faible consommation mémoire •  Tous les services sont chargés à la demande
// src/Application/BlogBundle/Resources/views/layout.php $view['actions']->output('BlogBundle:Blog:lastComments', array(), array( 'standalone' => false )); If  the  standalone  parameter  is  set   to  false,  Symfony2  will  render  the   HTML  content  
// src/Application/BlogBundle/Resources/views/layout.php $view['actions']->output('BlogBundle:Blog:lastComments', array(), array( 'standalone' => true )); If  the  standalone  parameter  is  set  to   true  and  if  there  is  a  compa0ble  proxy   <esi:include  src="..."  />   cache,  Symfony2  will  render  an  ESI  tag  
Edge Side Includes aka ESI...
Edge Side Includes
Questions ?
Trainings Business Unit trainings@sensio.com Sensio S.A. 92-98, boulevard Victor Hugo 92 115 Clichy Cedex FRANCE Tél. : +33 1 40 99 80 80 www.sensiolabs.com - www.symfony-project.org - trainings.sensiolabs.com

Introduction à Symfony2

  • 1.
  • 2.
    •  Hugo HAMON(@hhamon) •  Responsable des formations Sensio Labs •  Secrétaire Général de l’AFUP •  10 ans de développement web dont 8 avec PHP •  Coauteur d’ouvrages Eyrolles •  Apprendre-PHP.com / HugoHamon.com
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
    •  Développer plusvite et mieux •  Faciliter le travail en équipe •  Pérenniser les applications •  Simpli er la maintenance et les évolutions •  Se concentrer sur la logique métier •  Ne pas réinventer la roue !
  • 8.
    Symfony2 intègre les meilleursoutils Open-Source PHP
  • 9.
    Symfony Components Dependency InjectionContainer Request Handler Event Dispatcher Console YAML …
  • 10.
    Zend Framework PHPUnit Doctrine2 Swift Mailer Twig
  • 11.
  • 12.
    Même philosophie, Même outillage, Moins de concepts, Plus de exibilité Performances accrues 
  • 13.
  • 14.
    •  Version ALPHA • Briques logicielles manquantes •  Documentation incomplète •  L’API peut encore beaucoup évoluer •  Version stable repoussée à début Mars 2011
  • 15.
    Je veux testerSymfony2 ! git clone http://github.com/symfony/symfony-sandbox.git http://www.symfony-reloaded.org
  • 16.
    Je veux développerun projet client maintenant avec Symfony2 ? A ta place, je ne ferai pas ça…
  • 17.
  • 18.
    •  Sécurité •  Extensibilité •  Architecture MVC •  I18N & L10N •  URLs élégantes •  Authenti cation et ACLs •  DBAL & ORM •  Tests unitaires •  Outils de débogage •  Tests fonctionnels •  Formulaires •  Cache •  Con guration •  Admin Generator
  • 19.
  • 20.
    Un Projet Symfony2est un répertoire qui se compose d’une Application, d’un jeu de Bundles et de librairies.
  • 21.
    . |-- LICENSE |-- README |-- app/ | |-- AppCache.php | |-- AppKernel.php Répertoire  de  l’Applica0on   | |-- cache/ | |-- config/ | |-- console | |-- logs/ | |-- phpunit.xml.dist | `-- views/ |-- bin/ | |-- create_sandbox.sh | |-- install_vendors.sh | |-- prepare_vendors.sh | `-- update_vendors.sh |-- src/ | |-- Application/ | |-- Bundle/ Code  de  l’Applica0on  +     | |-- autoload.php Bundles  +     | `-- vendor/ Librairies  externes   `-- web/ |-- bundles/ |-- check.php |-- index.php Dossier  public   `-- index_dev.php
  • 22.
    Une Application estun répertoire qui contient la con guration pour un jeu de Bundles donné.
  • 23.
    Structure d’une application app/ |--AppCache.php |-- AppKernel.php The  AppKernel  class  is  the   |-- cache/  main  class  of  the  applica0on   |-- config/ | |-- config.yml | |-- config_dev.yml | |-- config_test.yml | |-- routing.yml Configura0on  files   | `-- routing_dev.yml |-- console |-- logs/ | `-- dev.log |-- phpunit.xml.dist Logs  and  applica0on  templates   `-- views/ |-- layout.php
  • 24.
    Un Bundle estun ensemble structuré et cohérent de chiers qui implémentent une fonctionnalité (un blog, un forum, …) et qui peut facilement être partagé avec d’autres développeurs.
  • 25.
    symfony 1.x =>Plugins Symfony 2.x => Bundles
  • 26.
    + BlogBundle/ Code  source  du  bundle  :  contrôleurs   |-- Controller/ | `-- BlogController.php modèles,  formulaires…   |-- Entity/ | `-- Post.php |-- Form/ | `-- PostForm.php Le  fichier  BlogBundle.php  est   |-- BlogBundle.php |-- Model/ obligatoire  et  con0ent  la  classe  qui   | `-- PostRepository.php déclare  le  bundle.   |-- Resources/ | |-- config/ | | `-- routing.yml | |-- views/ | | `-- Blog/ Un  bundle  peut  contenir  de  la   | | |-- showPost.php configura0on,  des  templates  et  des   | | `-- listPost.php ressources  web.   | `-- public/ | `-- css/ | `-- blog.css `-- Tests/ Un  bundle  peut  aussi  contenir   `-- Controller/ `-- BlogControllerTest.php des  scripts  de  tests  PHPUnit.  
  • 27.
  • 28.
    XSS CSRF SQL Injections
  • 29.
    Le dossier web/du projet est le seul accessible depuis un navigateur web
  • 30.
  • 31.
    Le système deroutage a pour rôle de convertir une URL en une réponse web.
  • 32.
    Elles sont propreset élégantes a n d’exposer des informations pertinentes et de masquer l’implémentation technique… http://www.domain.com/blog/2010/09/15/symfony2-rocks
  • 33.
    Con guration desURLs en YAML # src/Bundle/BlogBundle/Resources/config/routing.yml post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost } Exemple d’URL générée http://www.domain.com/blog/2010/09/15/symfony2-rocks
  • 34.
  • 35.
    # src/Application/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } }
  • 36.
    # src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  du  Bundle   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller Un  dossier  /  namespace   { public function indexAction($name) { // ... } }
  • 37.
    # src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  du  contrôleur   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller Une  classe   { public function indexAction($name) { // ... } }
  • 38.
    # src/Application/HelloBundle/Resources/config/routing.yml hello: Nom  de  l’ac0on   pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } Une  méthode   }
  • 39.
    # src/Application/HelloBundle/Resources/config/routing.yml hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index } # src/Application/HelloBundle/Controller/HelloController.php namespace ApplicationHelloBundleController; class HelloController extends Controller { public function indexAction($name) { // ... } }
  • 40.
    Les paramètres peuventêtre passés dans un ordre arbitraire post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost } namespace ApplicationBlogBundleController; class BlogController extends Controller { public function showPostAction($slug, $year) { // ... } }
  • 41.
  • 42.
    •  Séparation ducode en trois couches – Logique métier dans le Modèle – Logique applicative dans le Contrôleur – Affichage dans la Vue (templates) •  Modularité et découplage du code •  Maintenance simpli ée sur le code source •  Code testable unitairement et plus robuste
  • 43.
    Les actions pourla logique applicative. Elles se situent dans les Contrôleurs.
  • 44.
    ๏  Une actionest accessible depuis une URL # src/Application/BlogBundle/Resources/config/routing.yml post_show: pattern: /blog/article/:id/show defaults: { _controller: BlogBundle:Blog:show }
  • 45.
    # src/Application/BlogBundle/Controller/BlogController.php namespace ApplicationBlogBundleController; useSymfonyBundleFrameworkBundleControllerController; class BlogController extends Controller { public function showAction($id) Paramètres  de  l’url   { // find the article by its id $post = ...; // render the view return $this->render('BlogBundle:Blog:show', array('post' => $post)); } } Template  à  rendre   Variables  du  template  
  • 46.
    Les templates constituentla couche de présentation des données, la vue.
  • 47.
    ๏  Syntaxe alternativede PHP ๏  Quelques brèves instructions PHP (echo, if, foreach…) ๏  Echappement automatique des variables # src/Application/BlogBundle/Resources/views/Blog/show.php <?php $view->extend('::layout') ?> Layout  de  décora0on   <h2><?php echo $post->getTitle() ?></h2> <p> <?php echo $post->getContent() ?> </p> Variables  échappées  =>  pas  de  XSS  !!!  
  • 48.
  • 49.
    # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('::layout')?> Hello <?php echo $name ?>! étend # app/views/layout.php <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
  • 50.
    Héritage de Vues layout.php _content Hello Hugo! index.php
  • 51.
    # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('HelloBundle::layout')?> Hello <?php echo $name ?>! # src/Application/HelloBundle/Resources/views/layout.php <?php $view->extend('::layout') ?> <h1>Hello Application</h1> <div> <?php $view['slots']->output('_content') ?> </div> # app/views/layout.php <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
  • 52.
    Héritage multiple ::layout.php HelloBundle::layout.php _content Hello Hugo! _content index.php
  • 53.
    Les Slots sontdes fragments dé nis dans un template et affichés dans un layout décorant ce dernier.
  • 54.
    # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['slots']->set('title','Hello World app') ?> # app/views/layout.php <html> <head> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>
  • 55.
    Symfony fournit desmécanismes simples pour évaluer et inclure des templates dans un autre # src/Application/HelloBundle/Resources/views/Hello/hello.php Hello <?php echo $name ?>! # Including another template in the current template <?php echo $view->render('HelloBundle:Hello:hello', array('name' => $name)) ?> src/Bundle/HelloBundle/Resources/views/Hello/hello.php  
  • 56.
    Symfony offre égalementun moyen d’inclure le rendu d’une action depuis une vue… # src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['actions']->output('HelloBundle:Hello:fancy', array( 'name' => $name, 'color' => 'green’ )) ?>
  • 57.
    # src/Application/HelloBundle/Controller/HelloController.php class HelloControllerextends Controller { public function fancyAction($name, $color) { // create some object, based on the $color variable $object = ...; return $this->render('HelloBundle:Hello:fancy', array( 'name' => $name, 'object' => $object )); } // ... }
  • 58.
    Les aides devue sont des objets accessibles depuis les templates et qui permettent de simpli er la logique d’affichage
  • 59.
    Générer une URLavec le router helper <a href="<?php echo $view['router']->generate('hello', array( 'name' => 'Thomas')) ?>">Greet Thomas!</a> Inclure des feuilles de style <head> <!-- ... --> <?php $view['stylesheets']->add('css/styles.css') ?> <?php echo $view['stylesheets'] ?> </head>
  • 60.
    Inclure des javascripts <head> <!-- ... --> <?php $view['javascripts']->add('js/libraries.js') ?> <?php echo $view['javascripts'] ?> </head> Manipuler des ressource web (images, ash…) <img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" src=""/> Traduire des chaînes de l’interface <?php echo $view['translator']->trans('Symfony is %what%!', array( '%what%' => 'awesome')) ?>
  • 61.
  • 62.
    3 formats decon guration PHP YAML XML
  • 63.
    Quel format choisir? Avantages Inconvénients XML Validation Verbeux Complétion dans les EDIs Long à écrire Facile à analyser YAML Concis Besoin du composant YAML Facile à lire Pas de validation Facile à modi er Pas d’autocomplétion PHP Flexible Pas de validation Plus facile à manipuler
  • 64.
    Con guration enYML # app/config/routing.php homepage: pattern: / defaults: { _controller: FrameworkBundle:Default:index } hello: resource: HelloBundle/Resources/config/routing.yml Import  d’une  autre  configura0on  
  • 65.
    Con guration enPHP # app/config/routing.php use SymfonyComponentRoutingRouteCollection; use SymfonyComponentRoutingRoute; $collection = new RouteCollection(); $collection->addRoute('homepage', new Route('/', array( '_controller' => 'FrameworkBundle:Default:index', ))); $collection->addCollection( $loader->import("HelloBundle/Resources/config/routing.php") ); Import  d’une  autre  configura0on   return $collection;
  • 66.
    Con guration enXML # app/config/routing.xml <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://www.symfony-project.org/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.symfony-project.org/schema/routing http:// www.symfony-project.org/schema/routing/routing-1.0.xsd"> <route id="homepage" pattern="/"> <default key="_controller">FrameworkBundle:Default:index</default> </route> <import resource="HelloBundle/Resources/config/routing.xml" /> </routes> Import  d’une  autre  configura0on  
  • 67.
    Import de chiersINI # app/config/config_dev.yml imports: - { resource: config.yml } - { resource: custom.ini} zend.logger: priority: debug path: %kernel.root_dir%/logs/%kernel.environment%.log # app/config/custom.ini [parameters] dice.min = 1 dice.max = 6
  • 68.
    Accès à lacon guration depuis le code public function diceAction() { // ... $min = (int) $this->container->getParameter('dice.min'); $max = (int) $this->container->getParameter('dice.max'); // ... }
  • 69.
  • 70.
    Parce qu’il estimportant pour un développeur d’identi er rapidement les bogues et les problèmes !!!
  • 71.
  • 72.
  • 73.
    Traces d’exception Trace  de  l’excep0on   courrante   Trace  pour  une   InvalidArgumentExcep0on   404  Status  Code  
  • 74.
    Afficher  /  masquer  la  trace  d’une   excep0on   Logs  enregistrés   Lien  vers  le  profiler  
  • 75.
  • 76.
    Trace  de  l’excep0on   Recherche  dans  les  logs  
  • 77.
  • 78.
  • 79.
  • 80.
    Enregistrement de bundles #app/AppKernel.php class AppKernel extends Kernel { // ... public function registerBundles() { $bundles = array( // ... // Register third party bundles new BundleTwitterBundleTwitterBundle(), new BundleForumBundleForumBundle() ); // ... return $bundles; } }
  • 81.
    DBAL & ORM Doctrine2
  • 82.
    •  Abstraction debase de données relationnelles •  Performance •  Plus de magie •  Manipulation de vrais objets PHP (POPO) •  Génération de code •  Adapteur MongoDB disponible
  • 83.
    ๏  Con gurer la connexion BDD en YAML # app/config/config.yml doctrine.dbal: dbname: Blog user: root password: ~ doctrine.orm: ~ ๏  Con gurer la connexion BDD dans Apache // web/.htaccess or in the vhost configuration SetEnv SYMFONY__DOCTRINE__DBAL__USERNAME "root" SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "secret"
  • 84.
    ๏  Dé nition d’une entité (table) à l’aide d’une classe PHP namespace ApplicationBlogBundleEntity; /** * @Entity(repositoryClass="ApplicationBlogBundleModelBlogPostRepository") * @Table(name="blog_post") */ Annota0ons   class BlogPost { /** * @Id @Column(type="integer") * @GeneratedValue(strategy="IDENTITY") */ protected $id; /** @Column(length=100) */ protected $title; /** @Column(type="text") */ protected $content; }
  • 85.
    ๏  Génération dela base de données à partir des classes PHP $ php app/console doctrine:database:create $ php app/console doctrine:schema:create ๏  Chargement des données de test $ php app/console doctrine:data:load
  • 86.
    ๏  Les donnéesde test sont écrites en pur PHP # src/Application/BlogBundle/Resources/data/fixtures/doctrine/fixtures.php use ApplicationBlogBundleEntityBlogPost; $post1 = new BlogPost(); $post1->setTitle('My first blog post'); $post1->setContent('Lorem ipsum dolor sit amet...'); $post2 = new BlogPost(); $post2->setTitle('My second blog post'); $post2->setContent('Lorem ipsum dolor sit amet...'); $post3 = new BlogPost(); $post3->setTitle('My third blog post'); $post3->setContent('Lorem ipsum dolor sit amet...');
  • 87.
    ๏  Ecrire desrequêtes DQL dans un modèle Doctrine # src/Application/BlogBundle/Model/BlogPostRepository.php namespace ApplicationBlogBundleModel; use DoctrineORMEntityRepository; class BlogPostRepository extends EntityRepository { public function getHomepagePosts() { $query = $this->_em->createQuery(' SELECT u FROM BlogBundle:BlogPost u ORDER BY u.id DESC '); return $query->getResult(); } }
  • 88.
    ๏  Interroger labase de données à l’aide du Modèle Doctrine # src/Application/BlogBundle/Controller/BlogController.php // ... class BlogController extends Controller { public function indexAction() { $em = $this['doctrine.orm.entity_manager']; $posts = $em->getRepository('BlogBundle:BlogPost') ->getHomepagePosts(); return $this->render('BlogBundle:Blog:index', array( 'posts' => $posts )); } // ... }
  • 89.
  • 90.
    •  API OrientéeObjet Open-Source •  Support des connexions SMTP •  Support des pièces jointes •  Support des formats de mails (text, html…) •  Gestion des les d’attente (spools) •  Facile à con gurer et à étendre avec des plugins
  • 91.
    Con gurer SwiftMailer # app/config/config.yml swift.mailer: transport: smtp encryption: ssl auth_mode: login host: smtp.gmail.com username: your_username password: your_password
  • 92.
    Envoyer un Email publicfunction indexAction($name) { Récupéra0on  du  service  d’envoi  de  mails   $mailer = $this['mailer']; $message = Swift_Message::newInstance() ->setSubject('Hello Email') ->setFrom('send@example.com') ->setTo('recipient@example.com') ->setBody($this->renderView('HelloBundle:Hello:email', array( 'name' => $name ))); Généra0on  du  corps  du  mail  à  l’aide   d’un  template  et  de  la  méthode   $mailer->send($message); renderView()   return $this->render(...); }
  • 93.
  • 94.
    •  Tests Unitaireset Couverture de Code •  Garantir la qualité du code •  Eviter les bugs et les régressions •  Documenter le code •  Industrialiser et professionnaliser les développements
  • 95.
    ๏  Exemple descript de tests unitaires dans Symfony2 # src/Application/BlogBundle/Tests/Entity/BlogPostTest.php namespace ApplicationBlogBundleTestsEntity; use ApplicationBlogBundleEntityBlogPost; class BlogPostTest extends PHPUnit_Framework_TestCase { public function testTitleIsSlugifiedOnce() { $slug = 'symfony2-rules-the-world'; $post = new BlogPost(); $post->setTitle('Symfony2 rules the world'); $this->assertEquals($slug, $post->getSlug()); // Slug doesn't change when it's already set $post->setTitle('An other title'); $this->assertEquals($slug, $post->getSlug()); } }
  • 96.
    •  Tests fonctionnels • Simuler des scénarios de navigation •  Simuler un client Web (navigateur) •  Véri er que l’application respecte le cahier des charges
  • 97.
    ๏  Exemple descript de tests fonctionnels dans Symfony2 class BlogControllerTest extends WebTestCase { // ... public function testAddComment() { $this->client->followRedirects(); $crawler = $this->client->request('GET', '/'); // Get the first link to a post $link = $crawler->filter('h2.post a')->first()->link(); // Click on the link and check there are two comments $crawler = $this->client->click($link); $this->assertTrue($crawler->filter('.comment')->count() == 2); } }
  • 98.
    ๏  Simuler desrequêtes GET $crawler = $client->request('GET', '/hello/Fabien'); ๏  Simuler des requêtes POST $client->request('POST', '/submit', array('name' => 'Fabien') ๏  Simuler des uploads de chiers en POST $client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => '/path/to/photo') );
  • 99.
    ๏  Simuler unerequête HTTP DELETE avec des entêtes $client->request('DELETE', '/post/12', array(), array(), array( 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word' )); ๏  Désactiver / activer les redirections HTTP $client->followRedirects(false); $client->followRedirect(); ๏  Insoler le client dans un processus séparé $client->insulate();
  • 100.
    ๏  Naviguer dansl’historique comme dans un navigateur web $client->back(); $client->forward(); $client->reload(); ๏  Réinitialiser le Client $client->restart();
  • 101.
    ๏  Parcourir leDOM avec le DOM Crawler // Nodes that match the CSS selector $crawler->filter('h1'); // Nodes that match the XPath expression $crawler->filterXpath('h1'); // Node for the specified index $crawler->eq(1); // First node $crawler->first(); // Last node $crawler->last();
  • 102.
    // Siblings $crawler->siblings(); // Allfollowing siblings $crawler->nextAll(); // All preceding siblings $crawler->previousAll(); // Parent nodes $crawler->parents(); // Children $crawler->children(); // Nodes for which the callable, a lambda, returns true $crawler->reduce($lambda);
  • 103.
    ๏  Extraire desdonnées sur des noeuds // Returns the attribute value for the first node $crawler->attr('class'); // Returns the node value for the first node $crawler->text(); // Extracts an array of attributes for all nodes // (_text returns the node value) $crawler->extract(array('_text', 'href')); // Executes a lambda for each node // and return an array of results $data = $crawler->each(function ($node, $i) { return $node->getAttribute('href'); });
  • 104.
    ๏  Simuler desclics sur des liens ou boutons $crawler->selectLink('Click here'); $link = $crawler->link(); $client->click($link); $links = $crawler->links(); ๏  Poster des formulaires // Select the submit button of a form $crawler->selectButton('submit'); // Get a form instance $form = $crawler->form(); // Override the default form values $form = $crawler->form(array( 'name' => 'Fabien', 'like_symfony' => true, ));
  • 105.
  • 106.
    •  PHP 5.3.2minimum •  “ Cachy framework “ •  Cache HTTP & Proxy cache (ESI) •  Faible consommation mémoire •  Tous les services sont chargés à la demande
  • 107.
    // src/Application/BlogBundle/Resources/views/layout.php $view['actions']->output('BlogBundle:Blog:lastComments', array(),array( 'standalone' => false )); If  the  standalone  parameter  is  set   to  false,  Symfony2  will  render  the   HTML  content  
  • 108.
    // src/Application/BlogBundle/Resources/views/layout.php $view['actions']->output('BlogBundle:Blog:lastComments', array(),array( 'standalone' => true )); If  the  standalone  parameter  is  set  to   true  and  if  there  is  a  compa0ble  proxy   <esi:include  src="..."  />   cache,  Symfony2  will  render  an  ESI  tag  
  • 109.
  • 110.
  • 113.
  • 114.
    Trainings Business Unit trainings@sensio.com Sensio S.A. 92-98, boulevard Victor Hugo 92 115 Clichy Cedex FRANCE Tél. : +33 1 40 99 80 80 www.sensiolabs.com - www.symfony-project.org - trainings.sensiolabs.com