Supprimer un élément avec CollectionType

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

Bonjour,

Je dispose d'une entité Commande ayant une relation OneToMany avec une entité Detail. Lorsque je POST une commande je souhaite pouvoir directement ajouter ou supprimer des détails d'une Commande.

J'ai suivit les instruction disponible sur le site de Symfony à propos des CollectionType mais je n'arrive pas au résultat escompté.

A noté que dés l'introduction, l'explicatif indique une relation ManyToMany, hors dans mon cas ce n'est qu'une relation OneToMany. J'ignore si cela joue… le problème c'est que j'ai besoin de cette relation OneToMany. Dans l'absolu c'est bel et bien une relation ManyToMany avec mon entité Produit, cependant mon entité Detail stocke pas mal d'informations autre que de la pure liaison d'entité, d'où mon besoin d'une entité intermédiaire.

Lorsque je pose mon objet Commande en ajoutant un nouveau Detail tout va bien, celui-ci est bien persisté en base de données et si je récupère ma Commande je récupère bien le nouveau Detail associé.
En revanche lorsque je tente de retirer un élément existant (en récupérant d'abord ma commande puis en la postant en supprimant un détail de ma collection) je me chope une exception… Que je ne comprend même pas :(

Warning: Illegal offset type in isset or empty
500 Internal Server Error - ContextErrorException

Voici la stacktrace:

Symfony\Component\Debug\Exception\ContextErrorException: Warning: Illegal offset type in isset or empty at n/a in C:\wamp64\www\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php line 106

 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
at Symfony\Component\Debug\ErrorHandler->handleError('2', 'Illegal offset type in isset or empty', 'C:\wamp64\www\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php', '106', array('key' => object(Detail)))
    in  line

at call_user_func(array(object(ErrorHandler), 'handleError'), '2', 'Illegal offset type in isset or empty', 'C:\wamp64\www\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php', '106', array('key' => object(Detail)))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php line 242

at Symfony\Component\PropertyAccess\PropertyAccessor::handleError('2', 'Illegal offset type in isset or empty', 'C:\wamp64\www\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php', '106', array('key' => object(Detail)))
    in C:\wamp64\www\vendor\doctrine\collections\lib\Doctrine\Common\Collections\ArrayCollection.php line 106

at Doctrine\Common\Collections\ArrayCollection->remove(object(Detail))
    in C:\wamp64\www\vendor\doctrine\collections\lib\Doctrine\Common\Collections\AbstractLazyCollection.php line 95

at Doctrine\Common\Collections\AbstractLazyCollection->remove(object(Detail))
    in C:\wamp64\www\vendor\doctrine\orm\lib\Doctrine\ORM\PersistentCollection.php line 348

at Doctrine\ORM\PersistentCollection->remove(object(Detail))
    in C:\wamp64\www\src\AppBundle\Entity\Commande.php line 205

at AppBundle\Entity\Commande->removeDetail(object(Detail))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php line 648

at Symfony\Component\PropertyAccess\PropertyAccessor->writeCollection(array(object(Commande), object(Commande)), 'details', object(PersistentCollection), 'addDetail', 'removeDetail')
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php line 607

at Symfony\Component\PropertyAccess\PropertyAccessor->writeProperty(array(object(Commande), object(Commande)), 'details', object(PersistentCollection))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php line 200

at Symfony\Component\PropertyAccess\PropertyAccessor->setValue(object(Commande), object(PropertyPath), object(PersistentCollection))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper.php line 93

at Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper->mapFormsToData(object(RecursiveIteratorIterator), object(Commande))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 618

at Symfony\Component\Form\Form->submit(array('id' => '4', 'event' => '1', 'numero' => '1', 'nb_couverts' => '4', 'details' => array(array('id' => '4', 'quantity' => '3', 'prix_unitaire' => '2', 'produit' => '37')), 'payements' => array()), true)
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler.php line 116

at Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler->handleRequest(object(Form), object(Request))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 488

at Symfony\Component\Form\Form->handleRequest(object(Request))
    in C:\wamp64\www\src\AppBundle\Controller\RestController.php line 100

at AppBundle\Controller\RestController->putAction(object(Request), '4')
    in  line

at call_user_func_array(array(object(CommandesController), 'putAction'), array(object(Request), '4'))
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\HttpKernel.php line 139

at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), '1')
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\HttpKernel.php line 62

at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), '1', true)
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Kernel.php line 169

at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
    in C:\wamp64\www\web\app_dev.php line 30

at require('C:\wamp64\www\web\app_dev.php')
    in C:\wamp64\www\vendor\symfony\symfony\src\Symfony\Bundle\FrameworkBundle\Resources\config\router_dev.php line 40

Donc voic la commande lorsque je fais un GET

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "id": 4,
    "event": 1,
    "numero": 1,
    "nb_couverts": 4,
    "details": [
        {
            "id": 4,
            "quantity": 3,
            "prix_unitaire": 2,
            "produit": 37
        },
        {
            "id": 5,
            "quantity": 1,
            "prix_unitaire": 2,
            "produit": 37
        }
    ],
    "payements": []
}

J'enlève tout simplement le deuxième élément de details et je me retrouve avec l'erreur concernée.

Voici la description du formulaire:

 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
<?php

# Commande
$builder
    ->add('id', NumberType::class, array(
        'mapped' => false
    ))
    ->add('dateCloture', DateTimeType::class, array(
        'mapped' => false
    ))
    ->add('event', NumberType::class)
    ->add('numero', NumberType::class)
    ->add('nb_couverts', NumberType::class)
    ->add('details', CollectionType::class, array(
        'entry_type' => DetailType::class,
        'allow_add' => true,
        'allow_delete' => true,
        'by_reference' => false,
        'delete_empty' => true
    ))
    ->add('payements', CollectionType::class, array(
        'entry_type' => PayementType::class,
        'allow_add' => true,
        'by_reference' => false
    ))

#Detail
$builder
    ->add('id', NumberType::class, array(
        'mapped' => false
    ))
    ->add('quantity', NumberType::class)
    ->add('prix_unitaire', MoneyType::class)
    ->add('date', DateTimeType::class, array(
        'mapped' => false
    ))
    ->add('produit', EntityType::class, array(
        'class' => 'AppBundle:Produit'
    ))
    ->add('commande', EntityType::class, array(
        'class' => 'AppBundle:Commande',
        'mapped' => false
    ))

Pouvez-vous me venir en aide ?

Merci d'avance
Cordialement, La source.

+0 -0

Hello,

Je pense que le problème vient de ton entity.

<petit-moment-devin>

Tu dois avoir la méthode suivante:

1
2
3
4
5
<?php
public function removeDetail(Detail $detail)
{
    $this->details->remove($detail);
}

</petit-moment-devin>

Et celle-ci est fausse car il faut que tu utilises la méthode removeElement($detail).

1
2
3
4
5
<?php
public function removeDetail(Detail $detail)
{
    $this->details->removeElement($detail);
}
+1 -0

Je crois que tu devrai allé joué au loto ou un truc du genre car en effet tu a fait de la divination ^^

Sa marche mieux en effet… sauf que je me chope quand même une exception :(

An exception occurred while executing 'UPDATE detail SET commande_id = ? WHERE id = ?' with params [null, 7]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Le champ 'commande_id' ne peut être vide (null)

Comme il est là Symfony veut dire à mon détail d'arrêter de faire partie de ma Commande. Hors un Detail n'a aucun sens s'il n'a pas de commande. Je souhaite réellement le supprimer de la base de données.

Edit: Dans mon entité commande j'ai bien dit que les détails pouvaient être supprimé:

1
2
3
4
5
<?php
/**
 * @ORM\OneToMany(targetEntity="Detail", mappedBy="commande", cascade={"persist", "remove", "merge"})
 */
private $details;
+0 -0

C'est très très chelou que le fait de supprimer un élément fasse cet update…

… Mais en même temps c'est très très chelou d'avoir des détails dans une commande et en même temps une commande dans un détail…

Tout ça pour dire que les forms s'embourbent peut être un peu entre eux (et moi aussi).

Eu… nan ^^ c'est simplement une relation inverse.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php

#Commande
/**
 * @ORM\OneToMany(targetEntity="Detail", mappedBy="commande", cascade={"persist", "remove", "merge"})
 */
private $details;

#Detail
/**
 * @ORM\ManyToOne(targetEntity="Commande", inversedBy="details", cascade={"persist", "merge"})
 * @ORM\JoinColumn(nullable=false)
 */
private $commande;

C'est donc Detail qui dans la base de données tien la relation, mais concrètement on récupère les détails via la commandes.

L'entité Detail et son formulaire ne seront jamais attaqué directement, si un formulaire pour Detail a été créé c'est pour quand même validé ces details qu'on POST avec la commande.

+0 -0

Salut !

Est-ce que par hasard le souci ne viendrait pas de ce que cascade={"persist"} soit présent des deux côtés ? D'autant plus que tu nous dis que « L'entité Detail et son formulaire ne seront jamais attaqué directement », pourquoi demander à Detail d'enregistrer en cascade l'objet Commande ?

+0 -0

C'est vrai que dans le cas qui m'occupe ce cascade n'est pas nécessaire, mais ce n'est pas lui qui gène, ce genre de construction est tout à fait autorisé c'est même indiqué dans les exemples de bases de Doctrine.

Cela dit, j'ai finalement trouvé le problème, il vient bien de Doctrine, il fallait ajouté dans mon OneToMany l'option orphanRemoval pour qu'il efface automatiquement les enregistrements.

Merci à vous d'avoir pris le temps de chercher d'où venais le problème ;)

+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