Foire aux questions Symfony

L'un des premiers endroits où passer quand vous avez un souci avec ce framework

a marqué ce sujet comme résolu.

Avant toute chose, vérifiez plusieurs points

Est-ce un problème purement Symfony ou de PHP plus généralement, voire de configuration serveur ?

Impossible d’installer Symfony parce qu’il manque OpenSSL ? Impossible d’utiliser correctement la ligne de commandes parce que date.timezone n’est pas défini, ou que cURL vous fait des misères ? Vous pourrez probablement trouver votre bonheur dans l’un ou l’autre tutoriel (notamment celui sur WAMP et la partie d’utilisation en ligne de commandes), voire dans la FAQ PHP

Avez-vous vérifié que le serveur qui pose problème est configuré correctement pour votre version de Symfony ?

Il suffit d’installer le paquetage symfony/requirements-checker, de suivre les instructions de la documentation, et il vous sera indiqué si certains paramètres ne sont pas corrects, s’il manque des extensions de PHP, etc. Sur votre propre serveur, vous pourrez très probablement régler ces soucis vous-même, tandis que chez un hébergeur, il vous faudra passer par le support et être persuasif.  ;)

Avez-vous la même configuration en ligne de commandes et avec votre serveur web ?

Si vous avez plusieurs versions de PHP sur votre machine, vous avez très probablement configuré l’une d’elles pour qu’elle soit accessible en ligne de commandes, et une autre avec votre serveur. Utiliser PHP 7 en ligne de commandes (et donc composer…) et PHP 5 avec le serveur peut faire installer des dépendances qui ne sont pas compatibles avec la version du serveur.
Vérifiez aussi les paramètres pour PHP en ligne de commandes et ceux pour PHP du serveur. Les fichiers php.ini n’étant pas les mêmes, vous pourriez passer du temps à regarder un fichier correct, mais utilisé que par une des deux versions… Plus d’informations dans la FAQ PHP

Avez-vous nettoyé le cache ?

Certains changements ne peuvent être pris en compte que si celui-ci est vidé. Citons entre autres la création d’un nouveau catalogue pour l’environnement de développement, et pour l’environnement de production, ce sont (en plus) tous les changements liés aux templates, aux mappings et aux assets.

Notez aussi que parfois il faut supprimer le cache à la main.

Avez-vous regardé s’il y avait des erreurs mentionnées dans les différents onglets du profileur fourni avec Symfony ?

Le profileur indique des soucis de mapping, par exemple, quand les relations ne sont pas correctes, mais pas au point de bloquer l’application. Particulièrement sournois, parce que si le problème vient d’une entité qui n’est pas directement utilisée, vous pouvez passer à côté un moment…

Avez-vous vérifié les logs de l’application ?

Les applications Symfony écrivent plein d’informations dans des fichiers dev.log, prod.log et prod.deprecations.log, tous dans le dossier /var/log du projet. Prenez le temps de vider ces fichiers de temps à autre, mais plus particulièrement quand vous avez une erreur, et donc vider juste avant de refaire ce qui pose problème, pour avoir des informations fraîches et ciblées.

Avez-vous lu et compris le message d’erreur ?

Les développeurs de Symfony ont fait de gros efforts pour que les messages d’erreur soient le plus parlant possible, et fournissent même avec eux des pistes. Prenez donc le temps de lire l’entier du message d’erreur et de le traduire au besoin. Ce n’est pas parce que les messages sont de plus en plus clairs qu’il ne faut plus faire d’effort de compréhension  :p

Avez-vous bien comparé le code de l’exemple que vous suiviez avec le vôtre ?

Beaucoup de fautes surviennent parce que le code a été recopié (quand ce n’est pas directement copié-collé…) sans faire attention aux noms de variables qui changent.

Avez-vous lu des informations pour votre version de Symfony ?

Depuis le temps que Symfony existe, il y a beaucoup de scripts "tout prêts" qui se trouvent sur Internet. Mais ils ne sont pas tous pour la version de Symfony que vous utilisez. A vous de faire attention  ;)

Il est relativement facile de louper un paragraphe quand on lit un tutoriel et qu’on bidouille son code à côté. Reprenez calmement les différentes étapes expliquées sans vous préoccuper de votre code. Ce que vous avez oublié devrait vous apparaître comme « Tiens, je ne me souvenais pas de ça… ».

Avez-vous bien installé des dépendances qui vont avec votre version de Symfony ?

Là aussi, vous pouvez trouver des indications sur des dépendances qui ne sont pas compatibles avec votre version de Symfony. De manière générale, vérifiez sur le site originel si ce que vous souhaitez est encore maintenu et quelle version est nécessaire pour votre version de Symfony.

Avez-vous pris le temps de chercher ?

Vous êtes ici, c’est déjà bien !  :p
Mais si vous ne trouvez pas votre bonheur ici, cela ne veut pas dire que personne n’a eu le problème avant vous. Ce site n’est pas le centre d’Internet. Vos moteurs de recherche favoris peuvent vous aider, ainsi que ce tutoriel au besoin.

Si après avoir vérifié ces points et lu tout ce qui figure ici vous n’avez rien trouvé qui vous paraisse concluant, alors vous pouvez ouvrir un autre sujet.
En effet, les demandes d’aide qui seraient écrites dans ce sujet se verraient masquées, voire supprimées. Non seulement elles ne seront plus visibles, mais en plus elles pollueraient le sujet. Rappelez-vous : une FAQ est un endroit où certes il y a des questions, mais celles-ci surviennent régulièrement et figurent ici avec leur réponse.

Pour mieux vous aider (ainsi que ceux qui vous aident ou ceux qui auraient le même problème que vous), utilisez les tags symfony ET celui de la version, limitée à son nombre le plus significatif — premier pour des soucis "généraux" (symfony 3), second pour des soucis propres à une version mineure (symfony 3.2), ceci tant que quelque chose comme expliqué dans ce message soit mis en place.

Quelques exemples en live :

Trêve de blabla, voici ce que vous pourrez trouver ici  ^^

+1 -0

Général

Doctrine

Technique / programmation

Mappings

Traduction

Twig

Cette FAQ a été reprise d'OpenClassrooms. Un grand merci à tous les contributeurs !

+1 -0

J'ai étendu le bundle FOSUserBundle et je n'arrive pas à écraser les templates (=overriding), que faire ?

Vérifier que vous avez bien répliqué la structure des dossiers de FOSUserBunde. Par exemple pour écraser show.html.twig qui se situe dans /vendor/bundles/FOS/UserBundle/Resources/views/Profile/, il faut aussi que vous mettiez votre nouveau show.html.twig dans un dossier Profile : /src/Mon/UserBundle/Resources/views/Profile/

+0 -0

J'ai une erreur quand je tente de manipuler un fichier ! En substance

Trying to get property of non-object

Fatal error: Call to a member function move() on a non-object

The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string

Vous devez séparer la propriété qui contient le fichier uploadé (que vous ne persisterez pas) du nom du fichier (que vous persisterez). Actuellement, ces erreurs proviennent de ce que vous avez une chaîne dans une propriété qui devrait être de type UploadedFile. Soit le code suivant

1
2
3
4
5
<?php
$this->file = 'test.jpg';
echo ($this->file === null ? 'NULL' : $this->file); // Affiche "test.jpg"
$this->file->test; // Notice: Trying to get property of non-object in test.php on line 5
$this->file->test(); // Fatal error: Call to a member function test() on a non-object in test.php on line 6

Exemple de problèmes si on traite un nom de fichier comme le fichier lui-même

Ne stockez pas le nom du fichier dans l'attribut $file, $fichier, $picture, $image ou autre : cet attribut doit uniquement contenir l'objet UploadedFile que Symfony génère lors d'upload de fichiers

Enregistrez donc le nom du fichier dans un autre attribut $file_name, $nom_fichier, $picture_name, $nom_image, etc.

Lors de l'édition, recrééz l'objet UploadedFile à partir du nom et du chemin (sauvegardé avec le nom, ou défini quelque part dans votre application)

+0 -0

Je veux ajouter une contrainte d'unicité d'un champ d'une entité avec l'annotation @UniqueEntity, mais elle n'est pas prise en compte.

Ce problème peut survenir quand vous avez un formulaire avec des validation_groups. Il faut alors définir le validation_group dans l'annotation @UniqueEntity en haut de la classe de l'entité générale. Un exemple est plus parlant :

 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
<?php
namespace Sdz\BlogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
// Attention à ajouter ce use
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
 * Sdz\BlogBundle\Entity\Utilisateur
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\UtilisateurRepository")
 * @UniqueEntity(fields={"email"}, groups={"registration"}, message="Cet email est déjà utilisé") // Attention au validation_group
 */
class Utilisateur
{
   /**
    * @var integer $id
    *
    * @ORM\Column(name="id", type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    */
   private $id;
   // Vos autres propriété
   /**
    * @var string $email
    *
    * @Assert\Email(message = "L'adresse email {{ value }} est invalide", checkMX = true, groups={"registration"}) // Attention au validation_group
    * @ORM\Column(name="email", type="string", length=255, unique=true)
    */
   private $email;
   // La suite de votre entité

Exemple d'utilisation des groupes de validation avec @UniqueEntity

+0 -0

A la validation de mon formulaire j'ai l'erreur

Le jeton CSRF est invalide. Veuillez renvoyer le formulaire

Pourquoi ?

Vous avez probablement oublié de rendre au moins un champ dans la vue de votre formulaire, notamment celui qui permet une sécurité en utilisant un token afin de certifier que les données proviennent bien de votre site.
Pour éviter les oublis, vous pouvez terminer votre formulaire avec {{ form_end(form) }} ou, avant Symfony 2.3, utiliser {{ form_rest(form) }}. Ces deux fonctions Twig s'occuperont de générer pour vous les champs que vous n'avez pas encore rendus dans votre vue.

Ce code rend également les champs de type hidden

+0 -0

J’ai des pages statiques dans mon application, mais je n’aimerais pas avoir à changer partout où j’ai des liens qui mènent vers ces pages quand je change leur URL ! Y a-t-il une solution ?

Oui, mais ça demandera évidemment quelques changements.
Il est possible de rendre des templates sans aucun contrôleur, mais liés à une vue, ce qui nous va très bien pour des pages statiques. On va donc créer une route qui ne fera que rendre la page statique, qu’il vous faudra simplement renommer (si ce n’est pas déjà le cas) avec l’extension .html.twig. Ensuite de quoi, créez votre route comme suit :

ma_route_pour_ma_page_statique:
  path: mon/url
  controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
  defaults:
    template: 'chemin/vers/monTemplateDePageStatique.html.twig'
Exemple de route pour une page statique

Ne changez pas le paramètre controller, c’est un contrôleur particulier. Vous noterez qu’on n’y spécifie pas d’action.

+0 -0

Comment faire une redirection depuis une route ?

Il faut utiliser un controller déjà défini dans FrameworkBundle :

  • Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction si vous voulez faire une redirection à partir du path
  • Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction si vous voulez faire une redirection à partir d’une route.
    Si la route vers laquelle on est redirigé a des paramètres sans aucune valeur par défaut, il faut que la route de redirection possède ces paramètres

Plus d’informations sur cette page de documentation [en].
Par exemple, pour faire une redirection en gardant une locale par défaut :

mon_accueil:
  resource: \App\Controller\MonController:accueil
  type: annotation
  prefix:  /{_locale}
  defaults: {_locale: en}
  requirements:
    _locale: en|fr

ma_langue:
  path: /
  controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController::redirectAction
  defaults:
    route: mon_bundle_accueil
    permanent: true
Exemple de route pour la page d’accueil qui redirige avec une locale par défaut
+0 -0

J'ai supprimé un fichier lié à une annotation ainsi que l'annotation mais l'application en prod me dit qu'elle ne peut plus avoir accès au fichier (par exemple un repository) alors que je ne m'en sers plus. Pourtant j'ai vidé le cache et en dev tout fonctionne !

Vous avez probablement activé le cache apc pour les métadonnées de doctrine en prod. metadata_cache_driver: apc.
Ce qui n'est pas un mal au contraire mais cela a quelques inconvénients. Vous devez maintenant vider le cache apc à chaque fois que vous faites une modification sur les annotations en prod. Pour cela utiliser la commande doctrine doctrine:cache:clear-metadata. Si cela ne fonctionne toujours pas utiliser la manière forte : redémarrer Apache !

+0 -0

[JMSSecurityExtraBundle] J'ai envie de créer une expression de sécurité supplémentaire afin de ne pas dupliquer mon code. Je n'ai pas non plus envie d'utiliser le système d'annotation de JMSDiExtraBundle. Comment puis-je faire ?

Si vous ne voulez pas utiliser le système d'annotation de JMSDiExtraBundle, vous pouvez utiliser votre service comme n'importe quel autre service. Il suffit pour ça de le déclarer dans le DI. En ce qui concerne l'expression que vous voulez ajouter il faudra utiliser le tag security.expressions.function_evaluator pour chaque fonction que vous voulez déclarer en tant qu'expression.

1
2
3
4
5
6
7
8
9
        <service id="demo_bundle.security.expressions"
            class="Acme/DemoBundle/Security/Expressions"
        >
            <argument type="service" id="security.context" />
            <tag name="security.expressions.function_evaluator"
                function="canEditUser"
                method="canEdit"
            />
        </service>

Exemple de service pour une annotation de sécurité personnelle

On peut omettre l'attribut method si la méthode porte le même nom que la fonction. Ici, function représente l'expression qu'on veut utiliser et method représente la méthode de la classe qui va être appelée.

+0 -0

Pourquoi ai-je l'erreur

Call to undefined method FOS\UserBundle\Doctrine\UserManager::getRepository()

J'utilise FOSUserBundle qui fonctionne très bien avec toutes les autres fonctionnalités

Lorsque l'on utilise un entity manager personnel (même si l'entity user hérite de celle de FOSUB), il faut passer par les méthodes classiques d'accès au manager et au repository, tel que :

1
2
3
4
<?php
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('MonUserBundle:User')
             ->findContacts($contact);

Récupération du manager correct pour une entité héritant de FOSUserBundle

+0 -0

J’ai des entités embarquées dans une autre, mais elles ne sont pas validées !

Pour que la validation des entités embarquées soit aussi effectuée, il faut le spécifier dans les règles de validation. De manière simplifiée, cela se traduit par l’ajout de @Assert\Valid dans les annotations des propriétés impliquées dans les relations.

Hé, mais avec des groupes de validation, ça ne fonctionne pas :'(

Effectivement, si vous utilisez des groupes de validation, il faut paramétrer le formulaire parent à l’aide de 'cascade_validation' comme dans cet exemple :

<?php
$resolver->setDefaults(array(
    'data_class' => \App\Entity\MonEntite::class,
    'cascade_validation' => true
));
Définition du comportement de cascade pour la validation avec des groupes
+0 -0

Pourquoi mes formulaires sous OVH envoient des données vides ?

Cela peut venir de la configuration d'OVH. En effet, via votre FTP favori, vous devriez trouver à la racine de votre serveur, un petit fichier nommé ".ovhconfig" (valable uniquement depuis Hébergements Web 2014). Ce fichier permet de configurer certaines valeurs qui concernent le fonctionnement de votre serveur. Voici les valeurs par défaut :

1
2
3
4
app.engine=php
app.engine.version=5.4
http.firewall=none
environment=production

Exemple de configuration pour un hébergement chez OVH

Voici un lien vous permettant de consulter toutes les configurations possibles : www.ovh.com/fr/g1207.configurer-php-web

+0 -0

J’ai une erreur

Expected argument of type "Mon\Bundle\Entity\MonEntité", "array" given

quand je soumets un formulaire ! A l’aide !

Vous avez une relation entre deux entités, et vous affichez les champs de l’entité enfant dans le formulaire de l’entité parent. Seulement, vous avez probablement oublié d’ajouter data_class dans les options du formulaire. C’est l’option à ne pas oublier dès qu’on embarque des formulaires dans d’autres. Dans le cas d’une relation entre Article et Tag, par exemple, il ne faut donc pas oublier d’ajouter ceci dans TagType :

<?php
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => \App\Entity\Tag::class,
    );
}
Spécification de l’entité pour enregistrer les données du TagType
+0 -0

Quand je persiste une entité qui embarque d’autres entités, celles qui sont embarquées ne sont pas persistées, malgré cascade={"persist"}. Pourquoi ?

Quand je persiste une entité qui embarque d’autres entités, la liaison n’est pas enregistrée, malgré cascade={"persist"}, j’ai ma clé étrangère qui est vide. Pourquoi ?

Quand je supprime une entité qui embarque d’autres entités à supprimer aussi, celles qui étaient embarquées ne sont pas supprimées, malgré cascade={"remove"} ou orphanRemoval=true. Que se passe-t’il ?

Dans le cas de relations *ToMany, il faut rendre plus forte la liaison dans la classe parent, et ajouter un peu de traitement dans la méthode d’ajout (add*). Pour suivre l’exemple utilisé dans le message précédent, voici à quoi vont ressembler les mutateurs dans Article (le côté propriétaire d’une relation*ToOne) :

public function addTag(Tag $tag)
{
    if (!$this->tags->contains($tag)) {
        $this->tags[] = $tag;
        $tag->setArticle($this);
    }

    return $this;
}

public function removeTag(Tag $tag)
{
    if ($this->tags->contains($tag)) {
        $this->tags->removeElement($tag);
        // set the owning side to null (unless already changed)
        if ($tag->getArticle() === $this) {
            $thing->setArticle(null);
        }
    }

    return $this;
}
Modification des méthodes add* et remove* pour renforcer les relations *ToMany, tel que généré par la commande make:entity de symfony/maker-bundle 1.9.

Et pour pouvoir utiliser orphanRemoval=true du côté inverse d’une relation *ToOne,

// Notez la partie "= null"
public function setArticle(Article $article = null)
{
    $this->article = $article;
}
Modification de la méthode get* afin de permettre l’utilisation de orphanRemoval avec les relations *ToOne

Qui plus est, en cas de collection dans votre formulaire ou dans certains cas particuliers avec EntityType, il faut modifier le paramètre by_reference qui n’a par défaut pas la valeur souhaitée.

->add('tags', CollectionType::class, array(
    'entry_type'   => TagType::class,
    'by_reference' => false,
    // …
))
->add('category', EntityType::class, array(
    'class'        => Category::class,
    // Non documenté, mais parfois nécessaire
    'by_reference' => false,
    // …
))
Ajout de 'by_reference' => false dans les paramètres du champ collection

Si tout cela ne suffit pas, vérifiez que vous n’êtes pas dans le cas où vos mappings ne sont pas pris en compte

+0 -0

J'ai un message

No entity manager defined for class /Mon/BôBundle/Entity/MonEntité

Cela arrive quand vous n'avez pas mis entity_managers.default.mappings.auto_mapping: true dans votre configuration et que vous listez vos bundles. Dans ce cas, vous avez probablement oublié de renseiger votre bundle dans la liste ;)

+0 -0

J'ai des formulaires embarqués pour une collection, mais Symfony/Doctrine ne me persiste que le dernier objet que j'y ai mis !

Vous avez probablement un souci avec le JavaScript qui gère le prototype de votre collection : le remplacement des parties __name__ ne se fait pas correctement. Vous pouvez avoir oublié de le remplacer, ou d'incrémenter un compteur, par exemple.

+0 -0

Comment récupérer l’id d’un article que je viens tout juste de créer ?

C’est très simple et ne requiert pas de requête supplémentaire.

<?php
// Note: id est auto-incrémenté
$article = new Article();
$article->setTitre('Mon titre');
$article->setDescription('Ma description');
$em = $this->getDoctrine()->getManager();
$em->persist($article);
$em->flush();
echo $article->getId(); // Affichera bien l'id de l'article
Récupération d’un ID pour un article tout juste persisté

On créé un article, on le persiste et on flush, il suffit ensuite de récupérer la variable donnée à persist().
Dans ce cas, $article->getId() affichera bien l’id et $article contient bien l’intégralité de l’article qui vient d’être créé et inséré dans la base de données :)

+0 -0

Comment utiliser une méthode personnelle du repository dans un ParamConverter ?

Il peut-être intéressant d'utiliser une méthode personnelle lorsqu'on utilise un param converter, par exemple pour faire une jointure. Pour ça il suffit de le spécifier dans l'annotation.

1
2
3
4
5
6
7
8
<?php
/**
 * @Route("/blog/article/{id}")
 * @ParamConverter("article", class="SdzBlogBundle:Article", options={"repository_method" = "findWithComments"})
 */
public function showAction(Article $article)
{
   // …

Exemple d'annotation @ParamConverter utilisant une méthode personnelle

+0 -0

Comment organiser une requête sous forme de tableau que l'on nomme soit même et exécuter un setMaxResult() sur un élément en particulier, et non pas tout ce qu'elle retourne ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
public function getModsWeeklyMostViewed($nombre = null)
{
   $query = $this->_em->createQuery(
      "SELECT m, SUM(v.views) AS totalView
      FROM KynaModsBundle:Mods m
      INNER JOIN m.views v
      WHERE m.id = v.mod
      AND v.date > :dateLimit
      GROUP BY m.id"
   )->setParameter('dateLimit', new \dateTime('-7days'));
   if ($nombre) {
      $query->setMaxResults($nombre);
   }
   return $query->getResult();
}

Une requête avec jointure, requête dont on souhaite ne récupérer qu'un certain nombre de résultats

Ici, une clé "totalView" contenant la somme de notre nombre de vus sera créé, c'est le AS totalView qui permet ça, vous pouvez renommer facilement totalView par n'importe quoi pour avoir une clé portant le nom de votre choix et ainsi pouvoir organiser et nomer les éléments de votre tableau.
Il serait par exemple possible de faire SELECT m AS JeContientLesMods.
Ensuite, on va avoir problèmes avec setMaxResults() dans ce type de requête. Le setMaxResults(10) va prendre 10 résultats, mais non pas 10 mods : si on a 10 mods mais que le premier contient 8 vus, 8+1 = 9, la requête récupérera un mod, ses 8 views et un mod supplémentaire affin d'arriver à 10, ce qui pose un gros problème vu que cela fausse totalement la requête.

Pour pallier cela, il faut utiliser GROUP BY m.id, qui va grouper les mods ensemble dans la requête affin que le setMaxResults() compte bien le nombre de mods et non pas le nombre de mod mélangé aux vus.

+0 -0

Je n'arrive pas à utiliser les méthodes de mon repository ! J'ai l'erreur

Undefined method 'UneMéthodeDeMonRepository'. The method name must start with either findBy or findOneBy!

Vous avez très probablement

  • oublié de renseigner votre repository dans vos mappings
1
2
3
4
5
6
<?php
/**
 * @ORM\Entity(
 *   repositoryClass="[[ le nom de votre classe repository, avec le namespace ]]"
 * )
 */

Annotation pour préciser l'existence d'un repository

  • mis le mauvais nom/namespace pour le repository (attention à la casse et aux contre-obliques)
  • mis le fichier de votre repository dans un dossier ne correspondant pas au namespace dudit repository
  • mal nommé la classe

Je n'arrive toujours pas à utiliser les méthodes de mon repository, et j'ai ajouté l'annotation ci-dessus, avec le namespace, et je suis SÛR qu'il est correct aux trois points mentionnés ci-dessus !

Alors passez au message suivant ^^

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte