IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo

FAQ SymfonyConsultez toutes les FAQ

Nombre d'auteurs : 17, nombre de questions : 81, dernière mise à jour : 18 juin 2017  Ajouter une question

 

Les questions redondantes se multipliant sur le forum Symfony, nous avons choisi de créer cette FAQ afin de regrouper les questions qui sont posées fréquemment.

Vos remarques et idées sont les bienvenues. Et pourquoi pas votre aide ! S'il vous prend l'envie de tailler votre plus belle plume et de retrousser vos manches, il y a de quoi faire !

SommaireSymfony 2Généralités sur Symfony 2 (5)
précédent sommaire suivant
 

Une entité est un type d'objets qui sera manipulé par l'application Web Symfony.
Les entités sont converties en tables de la base de données, à partir du schéma.

Mis à jour le 16 novembre 2015 Siguillaume

Dans l'univers de Symfony, un morceau d'application est appelé Bundle. En fait, cela correspond à une brique logicielle, c'est-à-dire un ensemble cohérent pour une fonctionnalité donnée. Par exemple, pour un site internet, on peut imaginer plusieurs bundles : un bundle actualité, un bundle forum, un bundle newsletter, un bundle utilisateurs, etc. Chaque bundle peut être indépendant, ou bien fonctionner avec d'autres bundles (par exemple le forum et ses utilisateurs qui vont poster des messages).

Mis à jour le 14 novembre 2015 djayp Siguillaume

Les services sont des classes pour le code métier (Business Logic) ou tout autre traitement que l'on souhaite rendre accessible à un contrôleur ou un autre service.

Le Logger fourni avec Symfony2 est un service, tout comme le Registry de Doctrine qui donne notamment accès à l'EntityManager.

L'exemple ci-dessous montre comment créer un service, lui passer en paramètres deux autres services qu'il pourra utiliser.

Étape 1 : il faut commencer par définir le nouveau service dans le fichier de configuration des services du bundle (AcmeDemoBundle) localisés dans src/Acme/DemoBundle/Resources/config
/services.xml :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
<?xml version="1.0" ?> 
  
<container xmlns="http://symfony.com/schema/dic/services" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> 
  
    <services> 
        <service id="twig.extension.acme.demo" class="Acme\DemoBundle\Twig\Extension\DemoExtension" public="false"> 
          <!-- ... --> 
        </service> 
  
        <service id="acme.demo.listener" class="Acme\DemoBundle\ControllerListener"> 
          <!-- ... --> 
        </service> 
  
        <!-- Start of my_service definition --> 
        <service id="acme.demo.my_service" class="Acme\DemoBundle\Service\MyService"> 
            <argument type="service" id="doctrine" /> 
            <argument type="service" id="logger" /> 
            <tag name="monolog.logger" channel="my_service" /> 
        </service> 
        <!-- End of my_service definition --> 
  
</container>
Dans ce script XML, un nœud <service> a été ajouté pour le nouveau service. Le nom de ce service est défini, dans l'attribut id, et le nom de la classe PHP associé dans l'attribut class.
Deux arguments de type service ont été ajoutés en paramètres : le service de registre Doctrine (id="doctrine") et le logger (id="logger").
Le paramètre <tag> sera expliqué dans les paragraphes suivants.


Étape 2 : il faut maintenant créer la classe PHP MyService contenant le code du service nouveau my_service.

Le chemin complet du fichier est : src/Acme/DemoBundle/Service/MyService.php :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 
<?php 
  
namespace Acme\DemoBundle\Service; 
  
use Symfony\Bundle\DoctrineBundle\Registry; 
use Symfony\Component\HttpKernel\Log\LoggerInterface; 
  
class MyService 
{ 
  
   //default EntityManager 
   protected $em; 
   //Logger 
   protected $logger; 
  
   public function __construct(Registry $doctrine, LoggerInterface $logger) 
   { 
      $this->em = $doctrine->getEntityManager(); 
      $this->logger = $logger; 
   } 
  
   public function insertTime($message = 'Default message :)') 
   { 
      $time = new \DateTime; 
  
      $myRecord = new \Acme\DemoBundle\Entity\MyServiceTable(); 
      $myRecord->setMessage($message); 
      $myRecord->setCallTimer($time); 
  
      $this->em->persist($myRecord); 
      $this->em->flush(); 
  
      $this->logger->info('Insert one record in my_service_table  (' . $time->format('Y-m-d H:i:s') . ') !'); 
   } 
  
   public function getTimeSinceLastCall() 
   { 
      $calls = $this->em->getRepository('AcmeDemoBundle:MyServiceTable') 
         ->findBy(array (), array ('callTimer' => 'DESC'), 2); 
  
      if (count($calls) == 2) 
      { 
         $diff = $calls[0]->getCallTimer()->getTimestamp() - $calls[1]->getCallTimer()->getTimestamp(); 
      } 
      else 
      { 
         $diff = 'N/A'; 
      } 
  
      return $diff; 
   } 
  
}

Dans ce script PHP, on a d'abord écrit le constructeur qui prend en paramètres les arguments que l'on a définis dans le fichier services.xml à l'étape 1. Le paramètre EntityManager est associé à la propriété $em, et le Logger à $logger.

Ensuite on a créé deux méthodes publiques :
  • insertTime qui écrit un enregistrement en base à chaque appel, avec un timestamp (my_service_table.call_timer) ;
  • getTimeSinceLastCall qui retourne la durée (en secondes) qui s'est écoulée depuis le dernier appel.


Voici le code de l'entité MyServiceTable utilisée pour l'occasion (src/Acme/DemoBundle/Entity/MyServiceTable.php) :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
 
<?php 
  
namespace Acme\DemoBundle\Entity; 
  
use Doctrine\ORM\Mapping as ORM; 
  
/** 
 * Acme\DemoBundle\Entity\MyServiceTable 
 * 
 * @ORM\Table(name="my_service_table") 
 * @ORM\Entity 
 */ 
class MyServiceTable 
{ 
    /** 
     * @var integer $id 
     * 
     * @ORM\Column(name="id", type="integer", nullable=false) 
     * @ORM\Id 
     * @ORM\GeneratedValue(strategy="IDENTITY") 
     */ 
    private $id; 
  
    /** 
     * @var datetime $callTimer 
     * 
     * @ORM\Column(name="call_timer", type="datetime", nullable=false) 
     */ 
    private $callTimer; 
  
    /** 
     * @var string $message 
     * 
     * @ORM\Column(name="message", type="string", length=255, nullable=false) 
     */ 
    private $message; 
  
  
  
    /** 
     * Get id 
     * 
     * @return integer  
     */ 
    public function getId() 
    { 
        return $this->id; 
    } 
  
    /** 
     * Set callTimer 
     * 
     * @param datetime $callTimer 
     */ 
    public function setCallTimer($callTimer) 
    { 
        $this->callTimer = $callTimer; 
    } 
  
    /** 
     * Get callTimer 
     * 
     * @return datetime  
     */ 
    public function getCallTimer() 
    { 
        return $this->callTimer; 
    } 
  
    /** 
     * Set message 
     * 
     * @param string $message 
     */ 
    public function setMessage($message) 
    { 
        $this->message = $message; 
    } 
  
    /** 
     * Get message 
     * 
     * @return string  
     */ 
    public function getMessage() 
    { 
        return $this->message; 
    } 
}


Script SQL de la table my_service_table

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 
-- phpMyAdmin SQL Dump 
-- version 3.4.5deb1 
-- http://www.phpmyadmin.net 
-- 
-- Client: localhost 
-- Généré le : Mer 30 Novembre 2011 à 01:41 
-- Version du serveur: 5.1.58 
-- Version de PHP: 5.3.6-13ubuntu3.2 
 
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; 
SET time_zone = "+00:00"; 
 
 
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 
/*!40101 SET NAMES utf8 */; 
 
-- 
-- Base de données: `sf2_bench` 
-- 
 
-- -------------------------------------------------------- 
 
-- 
-- Structure de la table `my_service_table` 
-- 
 
CREATE TABLE IF NOT EXISTS `my_service_table` ( 
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `call_timer` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
  `message` varchar(255) NOT NULL, 
  PRIMARY KEY (`id`) 
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Table de test pour MyService' AUTO_INCREMENT=10 ; 
 
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;


Étape 3: on teste le service en l'appelant depuis le contrôleur de l'AcmeDemoBundle


Fichier du contrôleur ; src/Acme/DemoBundle/Controller/DemoController.php. On l'édite selon le code suivant :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 
<?php 
  
namespace Acme\DemoBundle\Controller; 
  
use Symfony\Bundle\FrameworkBundle\Controller\Controller; 
use Symfony\Component\HttpFoundation\RedirectResponse; 
use Acme\DemoBundle\Form\ContactType; 
  
// these import the "@Route" and "@Template" annotations 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; 
  
class DemoController extends Controller 
{ 
    /** 
     * @Route("/my_service", name="_demo_my_service") 
     * @Template() 
     */ 
    public function myServiceAction() 
    { 
       //Call the CallTimer insertion from MyService 
       $this->get('acme.demo.my_service')->insertTime('Call depuis le controlleur...'); 
       //Call the last 2 calls interval from MyService 
       $diff = $this->get('acme.demo.my_service')->getTimeSinceLastCall(); 
  
       //Send time interval to the view 
       return array('diff' => $diff); 
    }  
}
À chaque appel de la route /demo/my_service, on appelle la méthode insertTime du nouveau service, qui écrit un enregistrement dans la table my_service_table et logge un message.
Puis on appelle la méthode getTimeSinceLastCall qui retourne le nombre de secondes depuis le dernier appel de cette route, et on retourne ce résultat à la vue.


Contenu du fichier src/Acme/DemoBundle/Resources/view/Demo/myService.html.twig
:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
{% extends "AcmeDemoBundle::layout.html.twig" %} 
  
{% block title "My Service" %} 
  
{% block content %} 
    <h1>My Service !</h1> 
  
    <div> 
       <span>Last Call was {{ diff }} seconds ago ... 
    </div> 
{% endblock %} 
  
{% set code = code(_self) %}
Étape 4 : Définition du paramètre tag, présenté à l'étape 1

Code : Sélectionner tout
1
2
3
4
5
6
7
8
 
        <!-- my_service definition --> 
        <service id="acme.demo.my_service" class="Acme\DemoBundle\Service\MyService"> 
            <argument type="service" id="doctrine" /> 
            <argument type="service" id="logger" /> 
            <tag name="monolog.logger" channel="my_service" /> 
        </service> 
        <!-- /MyService definition -->


Ce paramètre définit le channel utilisé par le Logger de Monolog (Logger par défaut de Symfony2) lorsqu'il écrit dans les fichiers de log, comme montré ci-dessous :

[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []
[2011-11-30 02:06:39] request.INFO: Matched route "_demo_my_service" (parameters: "_controller": "Acme\DemoBundle\Controller\DemoController::myServiceAction", "_route": "_demo_my_service") [] []
[2011-11-30 02:06:39] my_service.INFO: Insert one record in my_service_table (2011-11-30 02:06:39) ! [] []


Résultat :



Mis à jour le 14 novembre 2015 pc.bertineau Siguillaume

En cas d'erreur « Unable to write cache file », il faut donner les droits d'écriture à Apache pour le dossier cache de Symfony2. Il devra en être de même pour le dossier de log.

Il faut d'abord vider les différents répertoires :

$ rm -rf app/cache/*
$ rm -rf app/log/*

Ensuite, en fonction de la plateforme, il faut modifier les droits avec la commande setfacl ou chmod +a :

$ sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
$ sudo chmod +a "USERNAME allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

$ sudo setfacl -R -m u:www-data:rwx -m u:USERNAME:rwx app/cache app/logs
$ sudo setfacl -dR -m u:www-data:rwx -m u:USERNAME:rwx app/cache app/logs

Mis à jour le 14 novembre 2015 Siguillaume vinmar

En général, les gestionnaires de session des principaux frameworks verrouillent, d'office, l'écriture en session à chaque requête. C'est nécessaire pour éviter les accès concurrents au fichier de session. Mais cela peut souvent être ennuyeux.

Le problème, c'est que par défaut, la session n'est libérée en écriture qu'à la fin du traitement, au moment d'envoyer la réponse au client. Ce qui cause un sérieux problème avec les calls AJAX asynchrones. Ils ne peuvent plus vraiment s'exécuter en parallèle, car par défaut la session démarre automatiquement (Symfony 2.0.x). Il faut donc libérer l'écriture sur la session dès qu'on n'en a plus besoin, et permettre aux calls asynchrones de se paralléliser.

Pour cela il suffit de mettre les lignes de code suivantes, après toutes les modifications apportées aux données de session (en tout début de contrôleur si on ne modifie pas les données de session :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
myControllerAction() 
{ 
    //Traitements qui modifient la session 
    // ... 
  
    //On libère la session d'entrée car on n'écrit pas dedans dans la suite du traitement ! 
    $session = $this->get('session'); 
    $session->save(); 
    session_write_close(); 
  
    //Traitements qui ne modifient plus la session 
    // ... 
  
    $this->return('...'); 
}

Mis à jour le 14 novembre 2015 Siguillaume

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2021 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.