« Programmation PHP avec Symfony/Introduction » : différence entre les versions

Un livre de Wikilivres.
Contenu supprimé Contenu ajouté
Yjp (discussion | contributions)
→‎Symfony 4 (sorti en 2017) : mise à part du passage sur le passage de Symfony 4 (version "dépassée")
Yjp (discussion | contributions)
→‎Lancer le projet : explication sur ce que nous entendons par là...
Ligne 37 : Ligne 37 :


== Lancer le projet ==
== Lancer le projet ==
On entend par cette expression le lancement d'un serveur web local pour le développement et le choix d'un hébergeur pour la déployer (autrement dit "la mettre en production").

=== Mode dev ===
=== Mode dev ===
==== Serveur Web intégré ====
==== Serveur Web intégré ====

Version du 20 mars 2021 à 18:28

Présentation

Symfony (parfois abrégé SF) est un cadriciel MVC libre écrit en PHP (> 5). En tant que framework, il facilite et accélère le développement de sites et d'applications Internet et Intranet. Il propose en particulier :

  • Une séparation du code en trois couches, selon le modèle MVC, pour une plus grande maintenabilité et évolutivité.
  • Un templating simple, basé sur PHP et des jeux de "helpers", ou fonctions additionnelles pour les gabarits... Comme alternative au PHP, on peut aussi utiliser le moteur de templates Twig dont la syntaxe est plus simples.
  • Des performances optimisées et un système de cache pour garantir des temps de réponse optimums.
  • Le support de l'Ajax.
  • Une gestion des URL parlantes (liens permanents), qui permet de formater l'URL d'une page indépendamment de sa position dans l'arborescence fonctionnelle.
  • Un système de configuration en cascade qui utilise de façon extensive le langage YAML.
  • Un générateur de back-office et un "démarreur de module" (scaffolding).
  • Un support de l'I18N - Symfony est nativement multi-langue.
  • Une couche de mapping objet-relationnel (ORM) et une couche d'abstraction de données (cf. Doctrine et son langage DQL[1]).
  • Une architecture extensible, permettant la création et l'utilisation de plugins.
  • Assetic : gestionnaire de fichiers .css et .js.
  • Swift Mailer : messagerie électronique.

La présente page se veut une prise en main la plus rapide possible du framework qui peut paraître peu intuitif au début.

Créer un projet

Page d'accueil par défaut.

Symfony 5 (sorti en 2019)

Pour créer un nouveau projet sous Symfony 5, tapez la commande :

$ symfony new MonPremierProjetSF5

Cette commande a pour effet la création d'un dossier MonPremierProjetSF5 contenant les bases du site web à développer.

PS : Au besoin, voici comment installer la commande symfony utilisée ci-dessus.

$ wget https://get.symfony.com/cli/installer -O - | bash

Symfony 4 (sorti en 2017)

Pour mémoire : Symfony 4

Symfony 3 (sorti en 2015)

pour mémoire : symfony_3

Lancer le projet

On entend par cette expression le lancement d'un serveur web local pour le développement et le choix d'un hébergeur pour la déployer (autrement dit "la mettre en production").

Mode dev

Serveur Web intégré

Il existe un composant Symfony pour lancer le projet sans configurer son serveur Web (Apache, Nginx ou autre) :

composer require symfony/web-server-bundle

Une fois le nouveau projet créé, il suffit de suivre les consignes affichées dans la console :

   5.15 MB/5.15 MB ============================================================  100%

Preparing project...

OK  Symfony 3.1.4 was successfully installed. Now you can:

   * Change your current directory to C:\Program Files (x86)\EasyPHP\eds-www\MonProject1

   * Configure your application in app/config/parameters.yml file.

   * Run your application:
       1. Execute the php bin/console server:run command.
       2. Browse to the http://localhost:8000 URL.

L'URL permet d'accéder à toutes les pages du site grâce au serveur Web intégré à Symfony, tant que la console shell est active.

Sous Windows, pour lancer le projet rapidement et facilement, on peut créer un fichier MonProjet1.cmd :

cd "C:\Program Files (x86)\EasyPHP\eds-www\MonProject1"
start http://localhost:8000/
php bin\console server:run
pause

La page de bienvenue doit s'ouvrir ensuite dans le navigateur Web par défaut.

Pour rendre le site web accessible à d'autres machines, le lancer avec son IP, par exemple :

php bin/console server:start 192.168.1.8:80

Cela nécessite d'arrêter le serveur Web qui prenait le port 80.

Mode prod

Pour que le site soit accessible en production, il faut qu'il tourne sur un serveur Web installé sur le système d'exploitation.

Par exemple avec Apache, il faut ajouter un vhost et un nom de domaine dédiés au site Symfony[2][3]. Pour le test, le domaine peut juste figurer dans /etc/hosts.

Logo

Le nom de domaine du site doit absolument rediriger vers le dossier /public. En effet, si on cherche à utiliser le site Symfony dans le sous-répertoire "public" d'un autre site, la page d'accueil s'affichera mais le routing ne fonctionnera pas.

Configurer le projet

Paramètres dev et prod

Les différences de configuration entre le site de développement et celui de production (par exemple les mots de passe) peuvent être définies de deux façons :

  • Dans le dossier config/packages. config.yml contient la configuration commune aux sites, config_dev.yml celle de développement et config_prod.yml celle de production.
  • Via le composant Symfony/Dotenv (abordé au chapitre suivant).

Par exemple, on constate l'absence de la barre de débogage (web_profiler) par défaut en prod. Une bonne pratique serait d'ajouter au config_dev.yml :

web_profiler:
    toolbar: true
    intercept_redirects: false

twig:
    cache: false

# Pour voir tous les logs dans la console shell (sans paramètre -vvv)
monolog:
    handlers:
        console:
            type: console
            process_psr_3_messages: false
            channels: ['!event', '!doctrine', '!console']
            verbosity_levels:
                VERBOSITY_NORMAL: DEBUG

Les fichiers .yml contenant les variables globales sont dans app\config\.

Par exemple en SF2 et 3, le mot de passe et l'adresse de la base de données sont modifiables en éditant parameters.yml (non versionné et créé à partir du parameters.yml.dist). L'environnement de test passe par web/app_dev.php, et le mode debug y est alors activé par la ligne Debug::enable(); (testable avec %kernel.debug% = 1).

Depuis SF4, il faut utiliser un fichier .env non versionné à la racine du projet, dont les lignes sont injectées ensuite dans les .yaml avec la syntaxe : '%env(APP_SECRET)%'. Le mode debug est activé avec APP_DEBUG=1 dans ce fichier .env.

 En YAML, on préfèrera déclarer les services avec des simples quotes car les doubles nécessitent d'échapper les antislashs.

Commandes

Les commandes sont, avec les contrôleurs, les seuls points d'entrée permettant de lancer le programme.

La liste des commandes disponibles en console est visible avec :

php bin\console

Logo

Dans Symfony 2 c'était php app\console.

Parmi les principales commandes natives au framework et à ses bundles, on trouve :

  • php bin/console list : liste toutes les commandes du projet.
  • php bin/console debug:router : liste toutes les routes (URL) du site.
  • php bin/console debug:container : liste tous les services avec leurs alias (qui sont des instanciations des classes).
  • php bin/console debug:autowiring --all : liste tous les services automatiquement déclarés.
  • php bin/console cache:clear : vide la mémoire cache du framework.
  • php bin/console generate:bundle : crée un bunble (surtout pour SF2).
  • php bin/console generate:controller : crée un contrôleur (en SF2).
  • php bin/console doctrine:migrations:generate; chown 1001:1001 -R app/DoctrineMigrations : génère un fichier vide de migration SQL ou DQL.

Le mode le plus verbeux est obtenu en ajoutant "- vvv" après la commande.

Créer une commande

Lors du lancement d'une commande, on distingue deux types de paramètres[4] :

  1. Les arguments : non nommés
  2. Les options : nommées.

Exemple :

php bin/console app:ma_commande argument1 --option1=test
class HelloWorldCommand extends Command
{
    /**
     * {@inheritDoc}
     */
    protected function configure()
    {
        $this
            ->addArgument(
                'argument1',
                InputArgument::OPTIONAL,
                'Argument de test'
            )
            ->addOption(
                'option1',
                null,
                InputOption::VALUE_OPTIONAL,
                'Option de test'
            )
        ;
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        echo 'Hello World! '.$input->getOption('option1').' '.$input->getArgument('argument1');
    }
}

Contrôleurs

Les contrôleurs Symfony sont les classes qui définissent les opérations à réaliser quand on visite les pages du sites[5] : elles transforment une requête HTTP en réponse (JSON ou XML).

Par convention, leurs noms se terminent par Controller, les noms de leurs méthodes se terminent par "Action", et les URL qui provoquent leurs exécutions sont définies dans leurs annotations. L'exemple suivant affiche un texte quand on visite l'adresse "/" ou "/helloWorld" :

class HelloWorldController extends AbstractController
{
    /**
     * @Route("/", name="helloWorld")
     * @Route("/helloWorld")
     *
     * @return Response
     */
    public function indexAction(Request $request)
    {
        return new Response('Hello World!');
    }
}

Retours

Ces méthodes peuvent déboucher sur plusieurs actions :

  • Response() : affiche un texte, et facultativement un code HTTP en deuxième paramètre (ex : erreur 404).
  • JsonResponse() : affiche du JSON.
  • $this->render() : affiche une page à partir d'un template, par exemple HTML ou Twig.
  • $this->redirect('mon_url') : redirige à une autre adresse.
  • $this->redirectToRoute('nom_de_la_route'); : redirige vers une route du site par son nom.
  • $this->generateUrl('app_mon_chemin', []); : redirige vers une URL relative (ajouter UrlGeneratorInterface::ABSOLUTE_URL en paramètre 3 pour l'absolue, car il est à UrlGeneratorInterface::ABSOLUTE_PATH par défaut dans SF3).
  • $this->container->get('router')->generate('app_mon_chemin', ['paramètre' => 'mon_paramètre']);.

Accès aux paramètres et services

Les contrôleurs étendent la classe abstraite Symfony\Bundle\FrameworkBundle\Controller\AbstractController. Cela leur permettait entre autres dans Symfony 2, de récupérer les services et paramètres ainsi :

dump($this->get('session'));
dump($this->getParameter('kernel.root_dir'));

Depuis Symfony 4, il faut injecter le service service_container pour accéder à la liste des services publiques (public: true en YAML).

Les paramètres sont ceux des fichiers .yml du dossier "config", mais plusieurs autres paramètres sont fournis par Symfony :

  • kernel.root_dir : chemin du site dans le système de fichier.
  • kernel.debug : renvoie vrai si le site est en préprod et faux en prod.

Routing

Par exemple pour créer une nouvelle page sur l'URL :

http://localhost:8000/test

Installer le routage :

composer require sensio/framework-extra-bundle
composer require symfony/routing

Par défaut, la page renvoie l'exception No route found for "GET /test". Pour la créer, il faut d'abord générer un fichier contrôleur (rôle MVC), qui fera le lien entre les URL, les données (modèle) et les pages (vue).

Les URL définies dans l"annotation "@Route" d'une méthode exécuteront cette dernière :

<?php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class TestController extends AbstractController
{
    /**
     * @Route("/test", name="test", methods={"GET|POST"})
     */
    public function HelloWorldAction($numero = 0)
    {
        return new Response('Hello World!');
    }
}
?>


Pour créer des alias, c'est-à-dire plusieurs autres URL pointant vers la page ci-dessus, on peut l'ajouter dans les annotations des contrôleurs, ou bien dans app\config\routing.yml :

test:
    path:      /test/{numero}
    defaults:  { _controller: AppBundle:Test:HelloWorld }

A présent http://localhost:8000/test/1 ou http://localhost:8000/test/2 affichent "Hello World!".

Logo

  • Une fois routing.yml sauvegardé, l'URL fournie en annotation (/test) ne fonctionne plus.
  • S'il y a des annotations précédant @Route dans le même bloc, cela peut inhiber son fonctionnement.


Redirection vers la dernière page visitée

Une astuce pour rediriger l'utilisateur vers la dernière page qu'il avait visité :

$router = $this->get('router');
$lastPage = $request->getSession()->get('last_view_page');
$parameterLastPage = $router->match($lastPage);
$routeLastPage = $parameterLastPage['_route'];
unset($parameterLastPage['_route']); // Pour ne pas la voir dans l'URL finale
return $this->redirect(
   $this->generateUrl($routeLastPage, $parameterLastPage)
);

Paramètres spéciaux

Il existe quatre paramètres spéciaux que l'on peut placer en argument des méthodes des contrôleurs[6] :

  • _controller : contrôleur appelé par le chemin.
  • _ format : format de requête (ex : html, xml).
  • _fragment : partie de l'URL après "#".
  • _locale : langue de la requête (code ISO, ex : fr, en).

Vue

Pour commencer à créer des pages plus complexes, il suffit de remplacer :

 return new Response('Hello World!');

par une vue Twig :

 return $this->render('helloWorld.html.twig');

Pour installer les bibliothèques JavaScript qui agiront sur ces pages, se positionner dans /public. Exemple :

cd public/
sudo apt-get install npm
npm install --save jquery
npm install --save bootstrap

Ensuite il suffit de les appeler dans /templates/helloWorld.html.twig pour pouvoir les utiliser :

<link rel="stylesheet" href="{{ asset('node_modules/bootstrap/dist/css/bootstrap.min.css') }}">

<script type="text/javascript" src="{{ asset('node_modules/jquery/dist/jquery.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('node_modules/bootstrap/dist/js/bootstrap.min.js') }}"></script>

Modèle

Le principe des services Symfony est d'éviter d'instancier la plupart des classes avec des "new" dispersés dans le code, pour les déclarer une seule fois, grâce au container. Ils sont alors instanciés uniquement s'ils sont utilisés (ex : sur la page web courante), grâce au lazy loading du container[7].

Cette déclaration peut se faire en PHP, en YAML ou en XML. On baptise alors le service (il peut y en avoir plusieurs par classe), et on appelle ses arguments par leur nom de service. Exemple :

services:
    app.my_namespace.my_service:
        class: App\myNamespace\myServiceClass
        arguments:
            - '%parameter%'
            - '@app.my_namespace.my_other_service'

Autowiring

Avant SF2.8, il était obligatoire de déclarer chaque service dans les fichiers de configuration .yml ou .yaml, en plus de leurs classes .php (qui peuvent se contredire), et de les mettre à jour à chaque changement de structure.

Depuis SF2.8, l'autowiring peut être définit à "true" pour injecter automatiquement les arguments connus (ex : une autre classe appelée par son espace de nom et son nom), donc sans déclaration manuelle[8].

Depuis SF4, cette déclaration est par défaut sans le fichier services.yaml, mais on peut la placer dans un autre fichier qui sera importé par le premier, par exemple avec :

imports:
    - { resource: services1.yaml }
    - { resource: services2.yaml }

ou :

imports:
    - { resource: services/* }

Logo

Cette séparation des services en plusieurs .yaml nécessite par contre d'exclure les dossiers de ces services de l'autowiring, et de reprendre la section _defaults dans le nouveau .yaml.

Exemple d'exclusion récursive de plusieurs dossiers de même nom, avec ** :

    App\:
        resource: '../src/*'
        exclude:
            - '../src/UnDossier'
            - '../src/**/Entity' # Tous les sous-dossiers "Entity"


Par défaut, l'autowiring ne fonctionne pas avec les classes avec des tags, ou ayant autre chose que des services dans leurs constructeurs[9]. Néanmoins pour injecter des scalaires automatiquement, il suffit que ces derniers soit déclarés aussi. Ex :

services:
    _defaults:
        bind:
            $salt: 'ma_chaine_de_caractères'
            $variableSymfony: '%kernel.project_dir%'
            $variableDEnvironnement: '%env(resolve:APP_DEBUG)%'

En SF2

Les contrôleurs ne sont pas des services mais peuvent les appeler avec la méthode :

$this->get('app.my_namespace.my_service')

Pour déterminer si un service existe depuis un contrôleur :

$this->getContainer->hasDefinition('app.my_namespace.my_service')

Pas de require_once()

Les classes natives de PHP doivent être introduites par leur namespace ou bien par l'espace de nom global. Ex :

use DateTime;
echo new DateTime();

ou

echo new \DateTime();


Paramètres

Chaque service doit donc être déclaré avec un paramètre "class", puis peut ensuite facultativement contenir les paramètres suivants :

Paramètres des services en YAML
Nom Rôle
class Nom de la classe instanciée par le service.
arguments Tableau des arguments du constructeur de la classe, services ou variables.
calls Tableau des méthodes de la classe à lancer après l'instanciation, généralement des setters.
factory Instancie la classe depuis une autre classe donnée. Méthode statique de la classe qui sera renvoyée par le service[10].
configurator Exécute un invocable donné après l'instanciation de la classe[11].
alias Crée un autre nom pour un service, qui peut alors être modifié par d'autres paramètres de déclaration (ex : créer une version publique d'un service privé).
parent Nom de la superclasse.
abstract Booléen indiquant si la méthode est abstraite.
public Booléen indiquant une portée publique du service.
shared Booléen indiquant un singleton.
tags Quand on doit injecter un nombre indéterminé de services dans un autre, il est possible de le définir avec chacun des services à injecter, en y ajoutant un tag avec le nom du service qui peut les appeler. Ce tag doit néanmoins être défini dans un CompilerPass[12].
autowire Booléen vrai par défaut, spécifiant si le framework doit injecter automatiquement les arguments du constructeur.
decorates Remplace un service par sa version décorée (mais l'ancien est toujours accessible an ajoutant le suffixe .inner au service décorateur)[13]

Références

Voir aussi