Type étendu de formulaire personnalisé : template non trouvé

Unable to render the form as none of the following blocks exist

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous !  :)

Aujourd'hui, j'ai décidé de mettre en pratique quelques idées que j'avais eues pour pouvoir enregistrer des durées sous forme d'entiers en base de données — c'est plus pratique pour faire des sommes par la suite.  ;)

Dans l'idée, je vais fournir un type qui étend Time afin d'avoir les widgets proposés nativement, et j'ajoute un ViewTransformer.

Tout ça dans ma tête avait l'air très bien et relativement simple. Ajouter à cela la documentation, et je ponds mon DataTransformer, mon type, la déclaration de ce dernier comme service (pour pouvoir injecter des paramètres, sinon j'aurais utilisé un simple CallbackTransformer).

Maintenant, une partie de ce travail correspond à la création d'un type personnalisé, dont j'ai suivi les instructions aussi. Voici notamment ce que j'aimerais relever.

In this case, since the parent field is ChoiceType, you don't need to do any work as the custom field type will automatically be rendered like a ChoiceType. […]

Documentation officielle de Symfony

Great! This will act and render like a text field (getParent()) […]

Documentation officielle de Symfony

Si je comprends bien, du moment que j'ai la méthode getParent() { return TimeType::class; }, les champs de mon nouveau type devraient être rendus comme s'il s'agissait de champs TimeType.

Or ce n'est pas le cas : Symfony / Twig tente apparemment de trouver des blocs particuliers.

An exception has been thrown during the rendering of a template ("Unable to render the form as none of the following blocks exist: "_parent_time_row", "time_row", "time_row", "form_row".") in form_div_layout.html.twig at line 312.

Erreur "complète" telle que mentionnée par Symfony

Outre le fait qu'il cherche apparemment deux fois le même bloc (qui n'existe effectivement pas), il me cherche un bloc du nom du champ lié à l'endroit où j'utilise mon DurationType.

J'ai bien fait attention à ne pas surcharger plus de méthodes que nécessaire pour hériter du comportement de TimeType.

Est-ce que quelqu'un a déjà tenté quelque chose de similaire et avec succès, et serait prêt à me dire ce que je pourrais avoir oublié ?

Merci

+0 -0

Je pense avoir saisi hier soir ce qui posait problème. Avant, j'avais le code suivant.

 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
<?php
namespace My\WonderfulBundle\Form\Type;

use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\FormBuilderInterface;
use My\WonderfulBundle\Form\DataTransformer\IntToTimeTransformer;

class DurationType extends TimeType
{
    private $stepDuration;

    /**
     *
     * @param int $stepDuration The number of seconds a step durates
     */
    public function __construct($stepDuration)
    {
        $this->stepDuration = $stepDuration;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new IntToTimeTransformer($this->stepDuration);
        $builder->addViewTransformer($transformer, true);
    }

    public function getParent()
    {
        return TimeType::class;
    }
}

DurationType

Déjà, je me suis demandé s'il ne tentait pas de créer un nouveau fragment, puis je me suis souvenu que le champ pour lequel j'utilise mon nouveau type s'appelle time (un champ appelé time avec un widget de type time, logique, mais pour le coup…).

Dans le code précédent, je ne créé aucun champ, ce qui ne vas pas trop aider. Comme la logique de création est dans TimeType, j'ai donc ajouté parent::buildForm($builder, $options);, l'erreur a déjà changé.

Unable to transform value for property path "time": Expected a \DateTime or \DateTimeInterface.

Erreur II

Après du débogage dans mon ViewTransformer, j'ai constaté que ce n'était pas celui-ci en cause. Seulement, j'ai fini par repenser à ce que j'avais fait et ce que je voulais faire : je souhaite avoir un type qui se comporte comme TimeType, mais qui prend comme données de modèle un entier. Je n'ai que ça à changer, pourquoi spécifier un parent ? Qui plus est, le parent de TimeType, c'est quoi ?

Donc j'ai enlevé ma méthode getParent(), et oh ! On y est, ça s'affiche, plus d'erreur.

Je n'ai plus qu'à comprendre les modifications de fuseau horaire effectuées par Symfony. Actuellement, si je fournis 15 quarts d'heure, le widget choice m'affiche bien 3:45. Le widget single_text, lui, me met bien un <input type="time" />, mais la valeur est de 4:45… malgré la spécification de view_timezone et model_timezone.

 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
<?php
namespace My\WonderfulBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;

class IntToTimeTransformer implements DataTransformerInterface
{
    private $stepDuration;

    /**
     *
     * @param int $stepDuration The number of seconds a step durates
     */
    public function __construct($stepDuration)
    {
        $this->stepDuration = $stepDuration;
    }

    public function transform($steps)
    {
        return new \DateTime('@' . ($steps * $this->stepDuration));
    }

    public function reverseTransform($time)
    {
        $seconds = $steps->getTimestamp();
        return (int)($seconds / $stepDuration);
    }
}

+0 -0

Hello,

Du coup tu ne dois pas hériter de TimeType mais de AbstractType. Et bien conserver le getParent(). Le composant form est designé pour fonctionner en « association » et pas par « héritage » (termes à vérifier, mais c'est pour l'idée).

Pour ta stepDuration elle doit être une option de ton type. Et pour cela tu as la méthode configureOptions de l'AbstractType que tu peux overrider. Note qu'en utilsant l'OptionResolver tu peux ainsi spécifier les caractéristiques suivantes de ton option:

  • Son type
  • Obligatoire ou non
  • Une valeur par défaut

Quand tu auras fait ces modifications tu devrais y voir plus clair sur tes erreurs, si il en reste. N'hésite pas à poster ici.

Du coup tu ne dois pas hériter de TimeType mais de AbstractType. Et bien conserver le getParent(). Le composant form est designé pour fonctionner en « association » et pas par « héritage » (termes à vérifier, mais c'est pour l'idée).

Nek

D'accord, donc si je comprends bien, un type qui reprend la logique d'un autre doit être complètement redéfini ailleurs, étendre AbstractType et spécifier uniquement son parent avec getParent() ? Je vais donc devoir copier-coller tout ce que je voulais éviter de dupliquer depuis TimeType ? Ça ne me paraît pas optimal… à moins que getParent() indique de reprendre les méthodes non-existantes depuis le parent ainsi déclaré ?

Pour ta stepDuration elle doit être une option de ton type. Et pour cela tu as la méthode configureOptions de l'AbstractType que tu peux overrider. Note qu'en utilsant l'OptionResolver tu peux ainsi spécifier les caractéristiques suivantes de ton option:

  • Son type
  • Obligatoire ou non
  • Une valeur par défaut

Quand tu auras fait ces modifications tu devrais y voir plus clair sur tes erreurs, si il en reste. N'hésite pas à poster ici.

Nek

En fait, ce paramètre relève plus du fonctionnement global à l'application qu'à un simple élément cosmétique pour le champ, et qui varierait suivant où je l'utiliserais. D'où l'idée de le mettre dans la classe et non de le passer en paramètre du champ.
Dans l'idéal, je le passerais directement à mon ViewTransformer, mais je n'ai pas trouvé comment faire d'autre — je pensais à un service, mais les DataTransformers ne semblent pas en être.

+0 -0

Le fait de simplement utiliser getParent() indique que créé un type qui doit avoir toutes les propriétés de son parent. Inutile de copier coller du code (sinon quel intérêt ?). Je t'invite à jeter un œil au TextType qui hérite bel et bien de toutes les options de FormType (via l'AbstractType).

Effectivement si ton paramètre est au niveau applicatif c'est logique de l'introduire dans le constructeur en définissant ton type en service :) .

Non rien à voir avec un trait, il s'agit bien d'héritage d'un point de vue théorique puisque c'est ajouter du comportement via les mêmes méthodes (un trait rajoute du comportement en ajoutant des méthodes).

En plus ça induit des comportements de type… le template du form ! (bref rien à voir avec le trait, encore une fois)

Mais bon, je pense que t'as compris le truc. Le protip avec les forms de Symfony c'est d'aller voir comment sont construits ceux de Symfony.

Et dans le contexte de Symfony pour un bel usage des traits t'as ça: https://github.com/KnpLabs/DoctrineBehaviors#translatable

+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