Symfony2


Formation ALG


  • SF1 : 2005 (mvc, ajax)
  • SF2 : 2011 (bas-niveau)


 

Société: Peaks

Mais avant-tout,


Benjamin RICHARD @rebolon

  • 14 années d'expérience dans le web
  • +12 ans sur PHP
  • un peu de Java, de dotNet, de coldfusion, de nodejs
  • du framework depuis très longtemps : fusebox, copix, ZF, Sf2...
  • et maintenant, beaucoup de MeteorJs

Programme du jour


  1. PHP
  2. de symfony 1 à Symfony2
  3. Gestionnaire de paquet PHP
  4. Micro Framework
  5. Silex
  6. Symfony 2
  7. Quelques paquets utiles

Pourquoi faire ?


Vous permettre d'être autonome sur Symfony2



PHP

N'oubliez pas l'essentiel


  • il reste votre langage quelque soit le framework utilisé
  • il a 20 ans cette année (PHP5 a déjà 10 ans)
  • il a un vrai modèle objet, évolué
  • il vous permet d'industrialiser vos développements
  • il n'a pas à rougir face à Java ou .Net
  • il est vraiment adapté pour les applications web

PHP 5.3


  • namespaces
  • late static bindings
  • fonction anonyme (closure)

PHP 5.4


  • Traits
  • SAPI : cli-server

PHP 5.5


  • generators
  • finally

PHP 5.6


  • variadics
  • phpdbg, le debugueur interactif

Et surtout



A chaque montée de version, 

une amélioration des performances

Normes


Définies par le PHP FIG

  • PSR0: standard d'autoloading
  • PSR1: rêgle de codage basique
  • PSR2: style avancé du code
  • PSR3: interface d'un Logger
  • PSR4: autoloding avancé

Notions


  • librairie
  • framework 
  • framework bas niveau
  • micro-framework
  • cms
  • cmf

Librairie


propose un ensemble de fonctionnalités (geolocalisation, couche d'abstraction bdd, système de cache...)
ex. ZF1, Pear

ou une fonctionnalité particulière
ex. Smarty, PHPUnit

Framework


propose un ensemble de fonctionnalité avec une cohérence dans les interfaces et la logique de programmation
(symfony, PHPCake, Jelix, CodeIgniter, ...)

Framework bas-niveau


propose un ensemble de fonctionnalité permettant de développer son propre framework
(Symfony2, ZF2)

Micro framework


Tout est dans le titre !
se veut plus léger qu'un framework mais avec quasiment autant de fonctionnalité (facultatives)
(silex, lemonade, glue, photon)

CMS


propose un ensemble de fonctionnalité autour de la gestion du contenu
(Drupal, Joomla, Wordpress)

CMF


propose un ensemble de fonctionnalité autour de la gestion de contenu afin de permettre de développer son propre CMS
(PIMCore, Symfony2 CMF distribution)



Un peu d'histoire

symfony 1



  • de 2005 à 2006 : beta
  • 2007 : la v1

Principe




Framework orienté web 2.0

Problèmes



 
  • pas de namespace (lié aux limites de PHP)
  • fort couplage entre les classes
  • difficile à tester

Concepts SF2


  • Autoloading
  • Code less : shell is your friend (dump assets, cache...)
  • More configuration : yml / xml
  • Component and Bundles : on ne doit rien faire en dehors de ces concepts (enfin presque)


Et donc...
Les fichiers de configuration sont partout ! 
(dans le root app, dans les bundles...)

Mais aussi


  • Annotations (Doctrine/Annotations)
  • Inversion of Control (via injection de dépendance)
  • Aspect oriented programation (JMS-AOP Bundle)
  • Et beaucoup d'autres choses...



Symfony2 !== symfony 1

Modularité


  • Les composants  permettent de packager des modules applicatifs
  • Les bundles se chargent notamment des controllers, donc fort couplage SF2
  • Les services permettent de partager une logique métier pour la rendre disponible dans l'ensemble de votre application

Component


  • PHP pure
  • Peut avoir besoin d'autres composants
  • Logique métier
  • Normalement pas d'affichage : mais peut générer du html (pas de echo en interne, ce serait une erreur de conception)
  • Pas d'adhérence au framework
  • Ré-utilisable

Bundle


  • Conteneur des Controllers, Command, ...
  • Défini des Services
  • A souvent besoin de composants ou de services
  • Indépendant de l'application globale => ré-utilisable dans Symfony2

Service


  • Pattern d'injection de dépendance (cf. Pimple)
  • Auto instanciation des objets
  • Disponibilité universelle dans vos controllers

N'oubliez pas de passer le conteneur à vos objets métiers, enfin juste les objets dont vous avez besoin, c'est mieux

Avant Symfony2, le gestionnaire de paquet


Composer



Historiquement : PEAR



Composer - Packagist - Satis


  • Composer : gestionnaire de paquet
  • Packagist : dépôt centralisé public
  • Satis : dépôt central privé

Composer


  • Json
    • décrit un projet
    • défini ses dépendances (version de php, module php, composants)
  • Application
    • résous l'arbre de dépendance
    • télécharge les projets
    • génère l'autoloader

Exemple :


 {
    "name": "moi/monexemple",
    "license": "MIT",
    "type": "component",
    "description": "Exemple de fichier composer",
    "autoload": {
        "psr-0": { "Moi\\MonExemple": "" }
    },
    "require": {
        "php": ">=5.3.3",
        "guzzle/guzzle": "4.0"
    },
    "require-dev": {
        "atoum/atoum": "~1"
    }
}

installation dans : monApp/vendor/moi/monexemple/

Install !== Update


  • Composer install : 
    • lit le composer.lock
    • install les composants
  • Composer update : 
    • lit le composer.json
    • résoud les dépendances
    • télécharge les dépendances
    • génère le composer.lock

Donc pensez bien à commiter votre composer.lock
Et faites des "composer update" que si nécessaire

Autoload


l'un des problèmes de PHP enfin résolu

  • PSR0 : fait correspondre un namespace à un path physique 
  • PSR4 : permet de supprimer le lien au path physique et de réduire la profondeur de l'arborescence

Composer permet de gérer les 2 cas, mais également des spécificités (cf. atoum/atoum)

Git - Github - Gitlab


  • GIT: Gestionnaire de version de fichier
  • Github: Dépôt distant de projet git (mode privé payant)
  • Gitlab: Projet open-source de dépôt git

Packagist


  • Dépôt central par défaut de Composer
  • Packagist.org pour chercher des projets

Attention !

Ne fait que référencer les descriptions des projets (via le composer.json)

Il n'héberge pas le code source

Pourquoi Satis ?


  • Packagist : repository par défaut, et public
  • Vous avez des composants métiers, donc privé
  • Packagist ou Github peuvent tomber, vous serez bloqué

Comme Packagist, Satis est indépendant de Git, il peut référencer des projets sous Pear, ou des archives zip/tar...

Mise en place de satis : 
https://getcomposer.org/doc/articles/handling-private-packages-with-satis.md#setup

A vous :


Créez un nouveau projet "alg_composer"

  • qui a besoin de PHP 5.5
  • de l'extension pdo
  • de la librairie guzzle
  • et de la librairie atoum en dev uniquement

Installez votre projet

Regardons ensuite ce que nous avons dans le répertoire

Avant Symfony2, le micro-framework


Silex

Silex


  • Le micro-framework de SensioLabs
  • Se repose sur des briques de SF2

(cf. https://speakerdeck.com/hhamon/designing-rest-api-with-silex)

Demo 1 : Silex the simple way


  1. Pré-requis : avoir un serveur Web opérationnel
    (ou utiliser cli-server de php)

  2. Définir : silex.local.dev comme domaine de l'appli
  3. Installation : via composer

composer.json type :
{
    "require": {
        "silex/silex": "~1.2"
    },
    "minimum-stability": "dev"
}

Créer son controller


<?php // fichier web/demo1.php

$loader = require_once __DIR__.'/../vendor/autoload.php'; // thanks to composer

$app = new Silex\Application();

$app['debug'] = true;

// definitions
$app->get('/hello/{name}', function ($name) {
    return 'hello ' . $name;
})
->assert('name', '[a-z]*')
->value('name', 'world');

$app->run();

Et tester


http://silex.local.dev/web/demo1.php



A vous d'essayer !

Demo 2 : Mieux s'organiser


<?php // fichier web/demo1.php
$loader = require_once __DIR__.'/../bootstrap.php';

$app = new Silex\Application();

$app['debug'] = true;

// sample for using monolog (dont' forget to install file with composer)
$app->register(new Silex\Provider\MonologServiceProvider(), array(
    'monolog.logfile' => __DIR__.'/../logs/development.log',
));

// definitions
$app->mount('/hello', new PeaksTuto\HelloControllerProvider);

$app->run();

Votre bootstrap


<?php
$loader = require_once __DIR__.'/vendor/autoload.php';

// add your namespaces
$prefixList = require_once __DIR__.'/kernel.php';

// load your controller for each namespace loaded by kernel
if (count($prefixList) > 0) {
    foreach ($prefixList as $prefixName => $prefixSrc) {
        $loader->add($prefixName, $prefixSrc);
    }
    unset($prefixName, $prefixSrc);
}

$app = new Silex\Application();

Votre Kernel

(à la symfony2)

<?php
$prefixList = array(
    'PeaksTuto' => __DIR__.'/src',
);

return $prefixList;

Et vos classes Controller


<?php
namespace PeaksTuto;
use Silex\Application;
use Silex\ControllerProviderInterface;

class HelloControllerProvider implements ControllerProviderInterface {
    public function connect(Application $app) {
        $controllers = $app['controllers_factory'];
        // route will depend on mount name in index file : might be /hello/{name} but may also be /whatIWant/{name}
        $controllers->get('/{name}', function ($name) use ($app) {
            $app['monolog']->addInfo(sprintf("name selected is %s", $name));           
            return 'hello ' . $name;
        })
            ->assert('name', '[a-z]*')
            ->value('name', 'world');
        return $controllers;
    }
}

Et tester


silex.local.dev/web/demo2.php

Symfony 2, 

concrètement


  • Le framework
  • Démo
  • Présentation de certains composants

Pile Sf2


  • MVC : contrairement à ce qu'ils disent...
  • Log : Monolog (PSR3)
  • Moteur de template : Twig
  • ORM : Doctrine, Propel
  • Routage
  • Cache
  • Gestion des assets
  • Authentification
  • UniversalAutoloader (PSR0, PSR4)
...

Structure


  • app
  • bin
  • src
  • vendor
  • web
  • composer.*

app


l'ensemble de la configuration de l'application

  • Kernel
  • Config
  • Logs
  • Resources (layout)
  • Console
  • Check

bin


des utilitaires fourni par les librairies contenu dans /vendor

  • doctrine
  • security checker

src


Les sources du projet 
l'ensemble des bundles 
et
 des composants développés pour le projet


vendor


les resources tiers installées via Composer

  • librairies
  • bundles
  • composants



web


  • les assets (via php console assets:dump)
  • le front controller via :
    • app.php (default, optional)
    • app_yourEnv.php

Installation


Pré-requis : 

  • PHP >=5.3.3
  • exécutable php dans son PATH
  • Composer disponible (c'est plus pratique)
  • Git

Les distributions


Symfony se présente sous forme de distribution

  • symfony/framework-standard-edition
  • symfony-cmf/standard-edition
  • gimler/symfony-rest-edition

#composer create-project symfony/framework-standard-edition

Les fondamentaux du web




  • Request

  • Response

Vie d'une requête HTTP



Vie d'une action Symfony2



Demo 1 : Acme


  • Pré-requis : avoir un serveur Web opérationnel
  • Définir : sf2.local.dev comme domaine de l'appli
  • Installation : via composer  

php composer.phar create-project symfony/framework-standard-edition alg_sf 2.6
Attention 'alg_sf' ne doit pas exister, il sera créé par Composer

et acceptez l'installation du bundle Acme

read README.md

 and run 

php app/check.php

Les environnements


  • dev
  • test
  • prod
  • ...

Changez d'environnement, en modifiant l'url du controller :
http://sf2.local.dev/app_dev.php/demo/hello/Ben

ou

http://sf2.local.dev/app.php/demo/hello/Ben

Environnements en détails


  • le choix du front controller détermine l'environnement
    • app.php, app_dev.php, app_test.php
  • AppKernel défini les bundles chargé par environnement
    • cf. AppKernel.php et registerContainerConfiguration()
  • les éléments de configuration se font par environnement
    • config_dev.php, routing_dev.php

Mais avant, enregistrez votre bundle auprès de Symfony :



AppKernel.php

(AcmeBundle doit être inséré automatiquement,
mais pas les votre)

Et surtout,


videz votre cache ;-)


php console clear:cache --env=prod

Résultats


  • front controller : app_dev.php
  • routage : demo/hello/toto
  • controller : DemoController + HelloAction
  • template : hello.html.twig

    = un bundle : AcmeBundle

Contenu d'un Bundle


  • Controller
  • Resources
  • Command
  • DependancyInjection
  • EventListener
  • Form
  • Twig
  • ...

Les controllers


  • appartiennent à un Bundle
  • points d'entrée de votre application
  • ne devraient pas avoir de code métier
  • contiennent des "action" regroupées par fonctionnalités
  • doivent construire la Response

ex. Acme\DemoBundle\Controller\DemoController

Les services


Objectifs 
 
  • éviter la dépendance entre les classes
  • automatiser l'instanciation d'objets
  • mettre à disposition des fonctionnalités métiers
  • rendre votre code testable

La console de Symfony 2


  • gestion des assets
  • cache
  • liste des services
  • table de routage
  • scafolding
  • ...



Accélérez votre développement avec la console

Demo 2

mon premier bundle

  1. création en cmd
  2. création de la route
  3. paramètres des urls
  4. accès aux services

Création en cmd


php app/console generate:bundle 


ALG/DemoBundle


puis modifier le routing.yml pour changer le prefix : /alg

Et surtout,


videz votre cache ;-)

Tester l'url :


/alg/hello/world

Inclure d'autres projets


Modifier le composer.json de l'application
rajouter la section require :

    "require": {
        "php": ">=5.4.0",
        "rebolon/pager-bundle": "dev-master"
    } 

Appeler un service


depuis le controller :

$myLogger = $this->get('logger'); //récupération du service logger 

Pour lister les services disponibles :

php app/console container:debug 

Demo 3

créer mon service 

  • on part d'un composant existant
  • on le rend disponible via un bundle 

Attention, dans Symfony2 les services sont liés aux bundles


Exemple


// fichier services.xml de votre bundle
<parameters
    <parameter key="rebolon_pager.pager.class">Rebolon\Bundle\Pager\Pager</parameter>
</parameters>

<services>
    <service id="rebolon_pager.pager" class="%rebolon_pager.pager.class%">
        <call method="setContainer">
             <argument type="service" id="service_container" />
        </call>
        <call method="setSuffixName">
             <argument>%rebolon_pager.suffixname%</argument>
        </call>
        ...

Et n'oubliez pas :


Enregistrez votre bundle auprès du AppKernel

<?php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(... ,
             new Rebolon\Bundle\Pager\RebolonPagerBundle(),

            

Autres outils de Symfony2


  • Logs
  • Web Debug profiler

Les logs de Symfony 2



app/logs/[environnement].log

Web debug Profiler


  • Une debug bar en bas de page
    • Résumé d'informations pour la page
      • mémoire
      • temps d'éxécution
      • version de php
      • ...
  • Un profiler : http://host/app_dev.php/_profiler

Intégration des tests



  • unitaires (PHPUnit par défaut, Atoum envisageable)
  • fonctionnels (Behat)

Configuration dans Symfony2


  • au niveau application
  • au niveau des bundles

Configuration de l'app


  • config.yml
  • config_[env].yml
  • parameters.yml

config.yml


  • syntaxe YAML
  • possibilité d'inclure d'autres fichiers via "imports"
  • peut utiliser des paramètres via %nomDuParametre%

parameters.yml


détermine les variables d'accès aux resources externes, mot de passe, tokens, host, username, path...


Ne versionnez pas parameters.yml, mais uniquement un template parameters.yml.dist

ET

Utilisez "incenteev/composer-parameter-handler" pour définir les configurations à l'installation

Routage


  • au niveau application
  • au niveau des controllers

Au niveau de l'application


  • /app/config/routing.yml
  • /app/config/routing_[env].yml


alg_tuto:
    resource: "@AlgTutoBundle/Controller/"
    type:     annotation
    prefix:   /alg 

Au niveau des controllers


class DemoController extends Controller
{
    /**
     * @Route("/{name}", name="_demo", requirements={"id" = "*"}, defaults={"name" = "world"})
     * @Method({"GET", "POST"})
     */
    public function indexAction($name)
    {
        ....


Fin des démos,


Ne lâcher pas la symphonie

Les composants


  • EventDispatcher
  • StopWatch
  • Process
  • Finder
  • Console
  • Form

EventDispatcher


Vous avez besoin du pattern Observer ?

Ne ré-inventez pas la roue !

$dispatcher = new EventDispatcher();

$dispatcher->addListener('event.name', callable[, priority]);
$dispatcher->dispatch('event.name');

StopWatch


Vous avez besoin d'un timer ?

Ne ré-inventez pas la roue !

$stopwatch = new Stopwatch();

$stopwatch->start('eventName');

$event = $stopwatch->stop('eventName');

$event->getDuration();
$event->getMemory();
... Et plein d'autres choses

Process


Vous avez besoin d'utiliser des processus machines  ?

Ne ré-inventez pas la roue !

$process = new Process('ls -lsa');
$process->setTimeout(3600);
$process->run();
if (!$process->isSuccessful()) {
    throw new \RuntimeException($process->getErrorOutput());
}

print $process->getOutput();

Finder


Vous avez besoin de chercher des fichiers ou des répertoires  ?

DirectoryIterator c'est bien, Mais,
Ne ré-inventez pas la roue !

$finder = new Finder();
$finder->files()->in('src/Symfony/*/*/Resources')
    ->name('smiley*')->size('< 100K')->date('since 1 hour ago');

foreach ($finder->files() as $file) {
    print $file->getFilename()."\n"; // SplFileInfo $file
}

Console


Vous avez besoin de faire du script shell-like en PHP  ?

Ne ré-inventez pas la roue !
Mais là ça va être un peu plus complexe

// vous avez besoin d'une classeclass MyShellCommand extends Command

// et de 2 méthodes protected function configure() // pour setter les parametres, et le help protected function execute(InputInterface $input, OutputInterface $output)

Form

  • Créez vos entités
  • Créez le builder createFormBuilder proposé par la classe Controller
  • Définissez vos types avec le builder : add('property', 'typeField')
  • Générez la vue via la méthode createView de votre formBuilder
  • Reliez votre formulaire à la saisie utilisateur via bind du formBuilder
  • Validez la saisie avec la méthode isValid du formBuilder

Bilan


++ : composants indépendants
oblige le développeur à réfléchir
adapté à de nombreux besoins

-- : lourdeur (mise en place et mise en production)
courbe d'apprentissage longue
bonne connaissance objet et des pattern requise
conception en amont obligatoire

Références


En attendant la suite


  • lire les best-practices : http://symfony.com/pdf/Symfony_best_practices_2.6.pdf
  • étudier la gestion des utilisateurs avec le FOSUserBundle
  • étudier la sécurité avec JMSSecurityExtraBundle
  • étudier la gestion du cache
  • mettre en place un gestionnaire de paquet privé : satis

Symfony2 for ALG

By Retour d'expérience MeteorJS

Symfony2 for ALG

Quick learning of Symfony

  • 2,258