Introduction à Symfony2

Preview:

DESCRIPTION

Cette nouvelle version du framework a été entièrement réécrite afin de tirer profit de PHP 5.3 d'une part mais également de corriger les erreurs du passé avec symfony 1.x.Cette nouvelle version regorge de fonctionnalités puissantes pour vous aider à bâtir des applications web maintenables, pérennes, performantes et évolutives.Cette présentation donne un aperçu des nouvelles fonctionnalités de Symfony2 comme l'architecture MVC, les tests automatisés ou bien encore l'envoi d'emails.

Citation preview

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é •  Architecture MVC •  URLs élégantes •  DBAL & ORM •  Outils de débogage •  Formulaires •  Con!guration

•  Extensibilité •  I18N & L10N •  Authenti!cation et ACLs •  Tests unitaires •  Tests fonctionnels •  Cache •  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 | |-- cache/ | |-- config/ | |-- console | |-- logs/ | |-- phpunit.xml.dist | `-- views/ |-- bin/ | |-- create_sandbox.sh | |-- install_vendors.sh | |-- prepare_vendors.sh | `-- update_vendors.sh |-- src/ | |-- Application/ | |-- Bundle/ | |-- autoload.php | `-- vendor/ `-- web/ |-- bundles/ |-- check.php |-- index.php `-- index_dev.php

Répertoire  de  l’Applica0on  

Code  de  l’Applica0on  +    Bundles  +    

Librairies  externes  

Dossier  public  

Une Application est un répertoire qui contient la con!guration pour un jeu de

Bundles donné.

app/ |-- AppCache.php |-- AppKernel.php |-- cache/ |-- config/ | |-- config.yml | |-- config_dev.yml | |-- config_test.yml | |-- routing.yml | `-- routing_dev.yml |-- console |-- logs/ | `-- dev.log |-- phpunit.xml.dist `-- views/ |-- layout.php

The  AppKernel  class  is  the    main  class  of  the  applica0on  

Configura0on  files  

Logs  and  applica0on  templates  

Structure d’une application

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/ |-- Controller/ | `-- BlogController.php |-- Entity/ | `-- Post.php |-- Form/ | `-- PostForm.php |-- BlogBundle.php |-- Model/ | `-- PostRepository.php |-- Resources/ | |-- config/ | | `-- routing.yml | |-- views/ | | `-- Blog/ | | |-- showPost.php | | `-- listPost.php | `-- public/ | `-- css/ | `-- blog.css `-- Tests/ `-- Controller/ `-- BlogControllerTest.php

Le  fichier  BlogBundle.php  est  obligatoire  et  con0ent  la  classe  qui  

déclare  le  bundle.  

Un  bundle  peut  contenir  de  la  configura0on,  des  templates  et  des  

ressources  web.  

Un  bundle  peut  aussi  contenir  des  scripts  de  tests  PHPUnit.  

Code  source  du  bundle  :  contrôleurs  modèles,  formulaires…  

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 Application\HelloBundle\Controller;

class HelloController extends Controller { public function indexAction($name) { // ... } }

# src/Application/HelloBundle/Resources/config/routing.yml

hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index }

# src/Application/HelloBundle/Controller/HelloController.php

namespace Application\HelloBundle\Controller;

class HelloController extends Controller { public function indexAction($name) { // ... } }

Nom  du  Bundle  

Un  dossier  /  namespace  

# src/Application/HelloBundle/Resources/config/routing.yml

hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index }

# src/Application/HelloBundle/Controller/HelloController.php

namespace Application\HelloBundle\Controller;

class HelloController extends Controller { public function indexAction($name) { // ... } }

Nom  du  contrôleur  

Une  classe  

# src/Application/HelloBundle/Resources/config/routing.yml

hello: pattern: /hello/:name defaults: { _controller: HelloBundle:Hello:index }

# src/Application/HelloBundle/Controller/HelloController.php

namespace Application\HelloBundle\Controller;

class HelloController extends Controller { public function indexAction($name) { // ... } }

Nom  de  l’ac0on  

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 Application\HelloBundle\Controller;

class HelloController extends Controller { public function indexAction($name) { // ... } }

post_details: pattern: /blog/:year/:month/:day/:slug defaults: { _controller: BlogBundle:Blog:showPost }

namespace Application\BlogBundle\Controller;

class BlogController extends Controller { public function showPostAction($slug, $year) { // ... } }

Les paramètres peuvent être passés dans un ordre arbitraire

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.

# src/Application/BlogBundle/Resources/config/routing.yml

post_show: pattern: /blog/article/:id/show defaults: { _controller: BlogBundle:Blog:show }

๏ Une action est accessible depuis une URL

# src/Application/BlogBundle/Controller/BlogController.php

namespace Application\BlogBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller { public function showAction($id) { // find the article by its id $post = ...;

// render the view return $this->render('BlogBundle:Blog:show', array('post' => $post)); } }

Template  à  rendre   Variables  du  template  

Paramètres  de  l’url  

Les templates constituent la couche de présentation des données, la vue.

# src/Application/BlogBundle/Resources/views/Blog/show.php

<?php $view->extend('::layout') ?>

<h2><?php echo $post->getTitle() ?></h2>

<p> <?php echo $post->getContent() ?> </p>

๏  Syntaxe alternative de PHP ๏  Quelques brèves instructions PHP (echo, if, foreach…) ๏  Echappement automatique des variables

Layout  de  décora0on  

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 ?>!

# 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>

étend

Héritage de Vues

_content

layout.php

Hello Hugo!

index.php

# src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view->extend('HelloBundle::layout') ?> Hello <?php echo $name ?>!

# 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>

# src/Application/HelloBundle/Resources/views/layout.php <?php $view->extend('::layout') ?> <h1>Hello Application</h1> <div> <?php $view['slots']->output('_content') ?> </div>

Héritage multiple

_content

_content Hello Hugo!

index.php

HelloBundle::layout.php

::layout.php

Les Slots sont des fragments dé!nis dans un template et affichés dans un

layout décorant ce dernier.

# app/views/layout.php <html> <head> <title> <?php $view['slots']->output('title', 'Hello Application') ?> </title> </head> <body> <?php $view['slots']->output('_content') ?> </body> </html>

# src/Application/HelloBundle/Resources/views/Hello/index.php <?php $view['slots']->set('title', 'Hello World app') ?>

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 Complétion dans les EDIs Facile à analyser

Verbeux Long à écrire

YAML Concis Facile à lire Facile à modi!er

Besoin du composant YAML Pas de validation Pas d’autocomplétion

PHP Flexible Plus facile à manipuler

Pas de validation

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 Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route;

$collection = new RouteCollection();

$collection->addRoute('homepage', new Route('/', array( '_controller' => 'FrameworkBundle:Default:index', )));

$collection->addCollection( $loader->import("HelloBundle/Resources/config/routing.php") );

return $collection; Import  d’une  autre  configura0on  

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

public function diceAction() { // ...

$min = (int) $this->container->getParameter('dice.min'); $max = (int) $this->container->getParameter('dice.max');

// ... }

Accès à la con!guration depuis le code

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

Trace  de  l’excep0on  courrante  

Trace  pour  une  InvalidArgumentExcep0on  

404  Status  Code  

Traces d’exception

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

# app/AppKernel.php class AppKernel extends Kernel { // ... public function registerBundles() { $bundles = array( // ... // Register third party bundles new Bundle\TwitterBundle\TwitterBundle(), new Bundle\ForumBundle\ForumBundle() );

// ... return $bundles; } }

Enregistrement de 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: ~

// web/.htaccess or in the vhost configuration

SetEnv SYMFONY__DOCTRINE__DBAL__USERNAME "root" SetEnv SYMFONY__DOCTRINE__DBAL__PASSWORD "secret"

๏ Con!gurer la connexion BDD dans Apache

๏ Dé!nition d’une entité (table) à l’aide d’une classe PHP

namespace Application\BlogBundle\Entity;

/**

* @Entity(repositoryClass="Application\BlogBundle\Model\BlogPostRepository")

* @Table(name="blog_post")

*/

class BlogPost {

/**

* @Id @Column(type="integer")

* @GeneratedValue(strategy="IDENTITY")

*/

protected $id;

/** @Column(length=100) */

protected $title;

/** @Column(type="text") */

protected $content;

}

Annota0ons  

๏ Génération de la base de données à partir des classes PHP

๏ Chargement des données de test

$ php app/console doctrine:database:create $ php app/console doctrine:schema:create

$ php app/console doctrine:data:load

# src/Application/BlogBundle/Resources/data/fixtures/doctrine/fixtures.php

use Application\BlogBundle\Entity\BlogPost;

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

๏ Les données de test sont écrites en pur PHP

# src/Application/BlogBundle/Model/BlogPostRepository.php namespace Application\BlogBundle\Model; use Doctrine\ORM\EntityRepository;

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

๏ Ecrire des requêtes DQL dans un 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 )); }

// ... }

๏  Interroger la base de données à l’aide du Modèle Doctrine

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

$mailer->send($message);

return $this->render(...); }

Généra0on  du  corps  du  mail  à  l’aide  d’un  template  et  de  la  méthode  

renderView()  

Récupéra0on  du  service  d’envoi  de  mails  

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

# src/Application/BlogBundle/Tests/Entity/BlogPostTest.php namespace Application\BlogBundle\Tests\Entity; use Application\BlogBundle\Entity\BlogPost;

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

๏ Exemple de script de tests unitaires dans Symfony2

•  Tests fonctionnels

•  Simuler des scénarios de navigation •  Simuler un client Web (navigateur)

•  Véri!er que l’application respecte le cahier des charges

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

๏ Exemple de script de tests fonctionnels dans Symfony2

$crawler = $client->request('GET', '/hello/Fabien');

๏  Simuler des requêtes GET

๏  Simuler des requêtes POST $client->request('POST', '/submit', array('name' => 'Fabien')

$client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => '/path/to/photo') );

๏  Simuler des uploads de !chiers en POST

$client->request('DELETE', '/post/12', array(), array(), array( 'PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word' ));

๏  Simuler une requête HTTP DELETE avec des entêtes

๏  Désactiver / activer les redirections HTTP

$client->followRedirects(false);

$client->followRedirect();

$client->insulate();

๏  Insoler le client dans un processus séparé

$client->back();

$client->forward();

$client->reload();

๏  Naviguer dans l’historique comme dans un navigateur web

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

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

๏ Extraire des données sur des noeuds

๏  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

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' => false ));

<esi:include  src="..."  />  If  the  standalone  parameter  is  set  to  true  and  if  there  is  a  compa0ble  proxy  cache,  Symfony2  will  render  an  ESI  tag  

// src/Application/BlogBundle/Resources/views/layout.php

$view['actions']->output('BlogBundle:Blog:lastComments', array(), array( 'standalone' => true ));

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

Recommended