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.
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
7. • 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 !
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 tester Symfony2 !
git clone http://github.com/symfony/symfony-sandbox.git
http://www.symfony-reloaded.org
16. Je veux développer un projet client
maintenant avec Symfony2 ?
A ta place, je ne ferai pas ça…
22. Une Application est un 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 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.
31. Le système de routage a pour rôle de
convertir une URL en une réponse web.
32. 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
33. 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
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)
{
// ...
}
}
42. • 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
43. Les actions pour la logique applicative.
Elles se situent dans les Contrôleurs.
44. ๏ 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 }
45. # 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
55. 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
56. 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’
)) ?>
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 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
65. 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;
66. 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
68. 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');
// ...
}
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;
}
}
82. • 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
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 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
86. ๏ 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...');
87. ๏ 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();
}
}
88. ๏ 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
));
}
// ...
}
90. • 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
91. 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
92. 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(...);
}
94. • 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
95. ๏ 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());
}
}
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 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);
}
}
98. ๏ 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')
);
99. ๏ 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();
100. ๏ Naviguer dans l’historique comme dans un navigateur web
$client->back();
$client->forward();
$client->reload();
๏ Réinitialiser le Client
$client->restart();
101. ๏ 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();
102. // 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);
103. ๏ 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');
});
104. ๏ 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,
));