symfony2


Présentation


  • SF1 : 2005 (mvc, ajax)
  • SF2 : 2011 (low-level)


 Twitter: @Rebolon
 GitHub: http://github.com/Rebolon

Notions


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

Librairie


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

ou une fonctionnalité particulière
(Smarty, PHPUnit)

CMS


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

NdA : Ca peut aller du blog au cms plus évolué

CMF


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

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)

Concepts SF2


  • 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...

Modularité


  • Les composants  permettent de packager des modules applicatif
  • 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)

Bundle


  • Conteneur des controllers
  • Défini des services
  • A souvent besoin de composant ou de service
  • Indépendant de l'application globale => ré-utilisable

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

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)
...

Structure


  • app
  • src
  • vendor
  • web

app


l'ensemble de la configuration du projet

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

src


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


vendor


les resources tiers : librairies, bundles, composants
installées via Composer


web


l'ensemble des assets (via php console assets:dump)

et surtout, l'accès au front controller via :
app.php (default, optional)
app_yourEnv.php

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


Pré-requis : avoir un serveur Web opérationnel
Définir : silex.local.dev comme domaine de l'appli
Installation : composer is your friend, so DL it !

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

Créer son controller


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

$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

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


Alors vous préférez quelle méthode pour Silex ?

Symfony 2, 

concrètement


  • Présentation de certains composants
  • Démo

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 www/sf 2.1.6
Attention 'sf' ne doit pas exister, il sera créé par Composer

read README.md

 and run 

php app/check.php

Les environnements

  • dev
  • test
  • prod
  • ...

Il suffit de changer l'url du controller :
http://sf2.local.dev/app_dev.php/demo/hello/Fabien

ou

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

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



AppKernel.php

Et surtout,


videz votre cache ;-)


php console clear:cache --env=prod

Conclusion


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

Détailler en montrant les fichiers correspondant

Demo 2

mon premier bundle

  1. creation en cmd
  2. creation de la route
  3. parametres des urls
  4. acces aux services

Création en cmd


php app/console generate:bundle


Peaks/DemoBundle


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

Et surtout,


videz votre cache ;-)

Tester l'url :


/peaks/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 :

$this->get('NomDuService');

Demo 3

créer mon service 

  • on part d'un composant existant
  • on le rend disponible via un bundle -> inconvénient de SF2 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(),


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ée 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émi, Ugo,
des remarques ?

Références


Made with Slides.com