[Symfony] Optionele embedded form resetten

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • egonolieux
  • Registratie: Mei 2009
  • Laatst online: 06-01-2024

egonolieux

Professionele prutser

Topicstarter
Ik heb een form met een optionele embedded form. Deze form (alsook de embedded form) is gemapt met mijn entities waarop validation constraints staan. De embedded form heeft een één op één relatie met de hoofdform:

code:
1
2
3
4
5
6
7
8
9
10
veld 1
veld 2
veld 3
+-----------+
| subveld 1 |
| subveld 2 |
| subveld 3 |
+-----------+
veld 4
veld 5


Als bij het invullen van de form geen enkele van de 3 embedded form velden (subvelden) worden ingevuld, blijft het resulterende object op `null` staan. Vanaf 1 van de 3 velden ingevuld is, wordt er een nieuw object aangemaakt.

Stel dat ik bestaande gegevens wil bewerken en de subform bevat reeds een aantal waarden, dan zou ik willen dat bij het leeglaten van de 3 subvelden, de het resulterende object terug op `null` gezet wordt. Ik dacht dit te kunnen bereiken met het `PRE_SUBMIT` form event:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function buildForm(FormBuilderInterface $builder, array $options)
{
    // ...

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event)
    {
        $subFormData = $event->getData();

        if (
            empty($subFormData['field1']) &&
            empty($subFormData['field2']) &&
            empty($subFormData['field3'])
        )
        {
            $event->setData(null);
        }
    });

    // ...
}


Maar dit blijkt niets van effect te hebben op de data die gesubmit wordt.

In de titel vergeten vermelden: het gaat hier om Symfony 3.2.

Acties:
  • 0 Henk 'm!

  • HollowGamer
  • Registratie: Februari 2009
  • Niet online
Ik begrijp het even niet? Als een object leeg is, dan is toch is deze toch leeg?
De view houdt gewoon waarde 'null' aan, en bij een submit gebruik je ' required' (of niet) om te checken of deze leeg is.

Acties:
  • 0 Henk 'm!

  • egonolieux
  • Registratie: Mei 2009
  • Laatst online: 06-01-2024

egonolieux

Professionele prutser

Topicstarter
HollowGamer schreef op zondag 30 april 2017 @ 14:59:
Ik begrijp het even niet? Als een object leeg is, dan is toch is deze toch leeg?
De view houdt gewoon waarde 'null' aan, en bij een submit gebruik je ' required' (of niet) om te checken of deze leeg is.
Ik zal het anders proberen zeggen. Stel dat de subform niet ingevuld wordt, blijft het resulterende object inderdaad op `null`. Indien de subform wel ingevuld wordt, wordt een nieuw object aangemaakt en in de database opgeslagen. Indien dit bestaande object in de subform geladen wordt en terug gereset wordt door de gebruiker (alle velden worden leeggemaakt) dan gaat symfony zien dat er reeds een object bestaat, en zal dit object proberen updaten/aanpassen ook al zijn alle waarden leeg.

Dit laatste wil ik dus vermijden; ik wil dat het object terug op `null` gezet wordt (uit de database verwijderd wordt). M.a.w., ik moet de mapping van form data naar entities zien te overriden zodat het object terug op `null` gezet wordt indien het reeds bestaat. Dit moet gebeuren voordat de validation component opgeroepen wordt (kan dus niet in de controller) omdat deze geen lege velden op het object toelaat, ook al is het object zelf als geheel optioneel.

Even de desbetreffende code er bijgehaald:

code:
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CardRepository")
 * @ORM\Table(name="card")
 */
class Card
{
    // ...

    /**
     * @Assert\Valid()
     * 
     * @ORM\OneToOne(
     *     targetEntity="\AppBundle\Entity\CardEffect",
     *     mappedBy="card",
     *     cascade="all",
     *     orphanRemoval=true
     * )
     */
    private $cardEffect;

    // ... Getters & Setters ...
}

/**
 * @ORM\Entity
 * @ORM\Table(name="card_effect")
 */
class CardEffect
{
    /**
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @Assert\NotBlank()
     * @Assert\Type("\AppBundle\Entity\Card")
     * @Assert\Valid()
     * 
     * @ORM\OneToOne(targetEntity="\AppBundle\Entity\Card", inversedBy="cardEffect")
     * @ORM\JoinColumn(name="card_id", referencedColumnName="id")
     */
    private $card;

    /**
     * @Assert\NotBlank()
     * @Assert\Type("\AppBundle\Entity\CardEffectType")
     * @Assert\Valid()
     * 
     * @ORM\ManyToOne(targetEntity="\AppBundle\Entity\CardEffectType")
     * @ORM\JoinColumn(name="card_effect_type_id", referencedColumnName="id")
     */
    private $cardEffectType;

    /**
     * @Assert\NotBlank()
     * @Assert\Type(type="string")
     * @Assert\Length(max=50)
     * 
     * @ORM\Column(name="name", type="string", length=50)
     */
    private $name;

    /**
     * @Assert\NotBlank()
     * @Assert\Type(type="string")
     * 
     * @ORM\Column(name="description", type="text")
     */
    private $description;

    // ... Getters & Setters ...
}

// Dit is de parent form van de Card entity.
class CardType extends AbstractType
{
    // ...

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('cardEffect', CardEffectType::class, array(
                'required' => false,
                'error_bubbling' => false
            ))
            // ...
    }

    // ...
}

// Dit is de embedded form van de CardEffect entity.
class CardEffectType extends AbstractType
{
    // ...

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('cardEffectType', EntityType::class, array(
                'class' => CardEffectTypeEntity::class,
                'choice_label' => 'name',
                'multiple' => false,
                'expanded' => false,
                'required' => false
            ))
            ->add('name', TextType::class, array(
                'required' => false
            ))
            ->add('description', TextareaType::class, array(
                'required' => false
            ));
    }

    // ...
}


Bij het submitten van de form waarbij alle 3 de velden van cardEffect leeg zijn (rauwe form data):

code:
1
2
3
4
5
  "cardEffect" => array:3 [▼
    "cardEffectType" => ""
    "name" => ""
    "description" => ""
  ]


Wordt dit gemapt naar `null`, maar indien er reeds een card effect bestaat, krijg ik dit:

code:
1
2
3
4
5
6
7
  -cardEffect: CardEffect {#933 ▼
    -id: 1829
    -card: Card {#542}
    -cardEffectType: null
    -name: null
    -description: null
  }

[ Voor 60% gewijzigd door egonolieux op 30-04-2017 17:35 ]


Acties:
  • 0 Henk 'm!

  • egonolieux
  • Registratie: Mei 2009
  • Laatst online: 06-01-2024

egonolieux

Professionele prutser

Topicstarter
Even vermelden dat ik het ondertussen opgelost heb door de `@Assert\NotNull` te verwijderen van de velden van CardEffect, en een custom validate methode gemaakt heb als callback:

code:
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
public function allFieldsSet()
{
    return
        $this->cardEffectType !== null &&
        $this->name !== null &&
        $this->description !== null;
}

public function allFieldsEmpty()
{
    return
        $this->cardEffectType === null &&
        $this->name === null &&
        $this->description === null;
}

/**
 * @Assert\Callback
 */
public function validate(ExecutionContextInterface $context, $payload)
{
    if (!($this->allFieldsSet() || $this->allFieldsEmpty()))
    {
        $context
            ->buildViolation('Either all or none of the card effect fields are required.')
            ->addViolation();
    }
}


Om er voor te zorgen dat `$card->cardEffect` terug op `null` gezet wordt bij het persisten/updaten naar de database, heb ik een `PreFlush` event aan Card toegevoegd:

code:
1
2
3
4
5
6
7
8
9
10
11
12
/** 
 *  @ORM\PreFlush
 */
public function onPreFlush()
{
    // Set card effect to `null` if all fields are empty
    // (required by `NOT NULL` constraints on cardEffect).
    if ($this->cardEffect !== null && $this->cardEffect->allFieldsEmpty())
    {
        $this->cardEffect = null;
    }
}

Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
egonolieux schreef op zondag 30 april 2017 @ 02:06:
Ik heb een form met een optionele embedded form.
Gelukkig draait dit niet in HTML.
Als iemand dit topic leest, sub-formulieren mag niet in HTML. Dus niet:
code:
1
<form> ... <form> ... </form> .... </form>

Maak je niet druk, dat doet de compressor maar


Acties:
  • 0 Henk 'm!

  • egonolieux
  • Registratie: Mei 2009
  • Laatst online: 06-01-2024

egonolieux

Professionele prutser

Topicstarter
DJMaze schreef op donderdag 4 mei 2017 @ 09:45:
[...]

Gelukkig draait dit niet in HTML.
Als iemand dit topic leest, sub-formulieren mag niet in HTML. Dus niet:
code:
1
<form> ... <form> ... </form> .... </form>
Het gaat hier dan ook duidelijk over Symfony: http://symfony.com/doc/current/form/embedded.html

Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
Ah, dat is dus eigenlijk een heerlijke WTF van Symfony.
Aangezien het eigenlijk een "merge multiple forms into one" is. Je krijgt immers maar 1 <form>, en de form elementen worden gegroepeerd. Iets als in:
HTML:
1
<select name="GROUPNAME[fieldname]">

Maak je niet druk, dat doet de compressor maar


Acties:
  • 0 Henk 'm!

  • egonolieux
  • Registratie: Mei 2009
  • Laatst online: 06-01-2024

egonolieux

Professionele prutser

Topicstarter
DJMaze schreef op donderdag 4 mei 2017 @ 19:40:
[...]

Ah, dat is dus eigenlijk een heerlijke WTF van Symfony.
Aangezien het eigenlijk een "merge multiple forms into one" is. Je krijgt immers maar 1 <form>, en de form elementen worden gegroepeerd. Iets als in:
HTML:
1
<select name="GROUPNAME[fieldname]">
"Embedded" heeft vooral betrekking op het objectmodel. Bij het renderen naar HTML worden deze "embedded forms" genamespaced binnen dezelfde form met bvb een underscore: card_cardEffect_cardEffectType.

Acties:
  • +2 Henk 'm!

  • phex
  • Registratie: Oktober 2002
  • Laatst online: 06-10 02:12
@DJMaze
In Symfony zijn alle elementen zoals checkboxes en selects ook FormTypes, hierdoor kun een hele boomstructuur bouwen van elementen.

De naamgeving is misschien verwarrend maar daadwerkelijke HTML output is uiteraard wel gewoon 1 form.

Het is een heerlijke functie die simpele html elementen intern omtovert naar flexibele datastructuren met events en validatie.

Misschien moet je eens met Symfony werken voordat je het direct afschrijft :)

Acties:
  • 0 Henk 'm!

  • DJMaze
  • Registratie: Juni 2002
  • Niet online
phex schreef op vrijdag 5 mei 2017 @ 21:50:
Misschien moet je eens met Symfony werken voordat je het direct afschrijft :)
Ik schrijf het niet direct af hoor.
Je verklaring is wel duidelijk.

Maak je niet druk, dat doet de compressor maar

Pagina: 1