Soucis de persistance en cascade

Et j'ai suivi mes conseils

a marqué ce sujet comme résolu.

Bonjour à tous !

Aujourd'hui, je suis confronté à un comportement que je n'attendais certainement pas, et que d'habitude, j'arrive à résoudre, mais là, je ne comprends pas tout.

J'ai une entité Voucher dont l'ID est un code que je peux modifier à la création (donc une chaîne pseudo-aléatoire). Ce bon possède un intitulé qui doit être traduit. Du coup, j'ai une collection de VoucherTranslation pour gérer ça.

Cependant, et c'est bien la première fois, l'enregistrement en cascade ne fonctionne pas, pour une raison qui m'échappe : l'ID du bon, que les traductions doivent avoir pour être enregistrées, n'est apparemment jamais renseignée dans l'objet.

J'ai suivi mes propres conseils de la FAQ Symfony 2, j'ai aussi joué avec by_reference, mais rien n'y fait. Au final, j'ai trouvé une solution que je trouve hideuse, et qui plus est peu logique : j'ajoute à nouveau mes traductions dans mon objet avant de le persister, ce qui fait qu'ils se retrouvent à double dans la collection, mais sont cependant bien enregistrés, et une seule fois chacun.

Le plus étrange est que j'ai remarqué aujourd'hui que dans le même projet, j'ai une autre entité similaire dans sa structure pour laquelle j'ai aussi le problème, actuellement réglé par un collègue avec une solution plus symfoniesque, mais tout aussi illogiquement nécessaire à mon goût, et cette entité n'a pas d'ID qui soit généré côté PHP. Et, toujours dans le même projet, j'ai des entités "normales" pour lesquelles je n'ai aucun souci de persistance pour les traductions.

Est-ce que quelqu'un a déjà eu ce genre de comportement ?

Merci d'avance  :)

Edit

Voilà, un peu de temps pour fournir le code.
L'entité parent :

  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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
<?php

namespace MyWonderfulBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Voucher
 *
 * @ORM\Table(name="voucher")
 * @ORM\Entity(repositoryClass="MyWonderfulBundle\Entity\Repository\VoucherRepository")
 */
class Voucher
{
    /**
     * The code to identify the voucher
     * @var string
     *
     * @ORM\Column(name="id", type="string", length=7)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="NONE")
     */
    private $id;

    /**
     * The title of the voucher, might be the reason why it was issued
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255, nullable=false)
     */
    private $title;

    /**
     * The amount the voucher represents
     * @var string
     *
     * @ORM\Column(name="amount", type="decimal", precision=5, scale=2, nullable=false)
     */
    private $amount;

    /**
     * The number of times the voucher can be used in total
     * @var integer
     *
     * @ORM\Column(name="quantity", type="smallint", nullable=true)
     */
    private $quantity;

    /**
     * The last date when the voucher can be used
     * @var \DateTime
     *
     * @ORM\Column(name="until", type="datetime", nullable=true)
     */
    private $until;

    /**
     * The persons who used this voucher
     * @var \MyWonderfulBundle\Entity\Person
     *
     * @ORM\ManyToMany(targetEntity="MyWonderfulBundle\Entity\Person")
     * @ORM\JoinTable(
     *      name="voucher_person",
     *      joinColumns={@ORM\JoinColumn(name="voucher_code", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="person_id", referencedColumnName="id", unique=true)}
     * )
     */
    private $persons;

    /**
     * @var \MyWonderfulBundle\Entity\VoucherTranslation
     *
     * @ORM\OneToMany(targetEntity="MyWonderfulBundle\Entity\VoucherTranslation", mappedBy="voucher", cascade={"persist"})
     */
    private $voucherTranslations;

    /**
     * @ORM\OneToMany(targetEntity="MyWonderfulBundle\Entity\Wishlist", mappedBy="voucher", orphanRemoval=true)
     */
    protected $wishlists;

    public function __construct() {
        if ($this->id == null) {
            $this->id = base_convert(substr(uniqid(), 2, 9), 16, 36);
        }
        $this->persons = new \Doctrine\Common\Collections\ArrayCollection();
        $this->voucherTranslations = new \Doctrine\Common\Collections\ArrayCollection();
        $this->wishlists = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Set id
     *
     * @param string $id
     */
    public function setId($id)
    {
        $this->id == $id;

        return $this;
    }

    /**
     * Get id
     *
     * @return string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     * @return Voucher
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set amount
     *
     * @param string $amount
     * @return Voucher
     */
    public function setAmount($amount)
    {
        $this->amount = $amount;

        return $this;
    }

    /**
     * Get amount
     *
     * @return string
     */
    public function getAmount()
    {
        return $this->amount;
    }

    /**
     * Set quantity
     *
     * @param integer $quantity
     * @return Voucher
     */
    public function setQuantity($quantity)
    {
        $this->quantity = $quantity;

        return $this;
    }

    /**
     * Get quantity
     *
     * @return integer
     */
    public function getQuantity()
    {
        return $this->quantity;
    }

    /**
     * Set until
     *
     * @param \DateTime $until
     * @return Voucher
     */
    public function setUntil($until = null)
    {
        $this->until = $until;

        return $this;
    }

    /**
     * Get until
     *
     * @return \DateTime
     */
    public function getUntil()
    {
        return $this->until;
    }

    /**
     * Add person
     *
     * @param \MyWonderfulBundle\Entity\Person $person
     * @return Voucher
     */
    public function addPerson(\MyWonderfulBundle\Entity\Person $person)
    {
        $this->persons->add($person);

        return $this;
    }

    /**
     * Remove person
     *
     * @param \MyWonderfulBundle\Entity\Person $person
     */
    public function removePerson(\MyWonderfulBundle\Entity\Person $person)
    {
        $this->persons->removeElement($person);
    }

    /**
     * Add voucherTranslation
     *
     * @param \MyWonderfulBundle\Entity\VoucherTranslation $voucherTranslation
     * @return Voucher
     */
    public function addVoucherTranslation(\MyWonderfulBundle\Entity\VoucherTranslation $voucherTranslation)
    {
        $voucherTranslation->setVoucher($this);
        $this->voucherTranslations->add($voucherTranslation);

        return $this;
    }

    /**
     * Remove voucherTranslation
     *
     * @param \MyWonderfulBundle\Entity\VoucherTranslation $voucherTranslation
     */
    public function removeVoucherTranslation(\MyWonderfulBundle\Entity\VoucherTranslation $voucherTranslation)
    {
        $this->voucherTranslations->removeElement($voucherTranslation);
    }

    /**
     * Get voucherTranslations
     *
     * @return \MyWonderfulBundle\Entity\VoucherTranslation
     */
    public function getVoucherTranslations()
    {
        return $this->voucherTranslations;
    }

    /**
     * Get voucher translation
     *
     * @param string $locale
     * @return \MyWonderfulBundle\Entity\VoucherTranslation
     */
    public function getTranslation($locale)
    {
        foreach ($this->voucherTranslations as &$translation) {
            if ($translation->getLanguage()->getCode() == $locale) {
                return $translation;
            }
        }
    }

    /**
     * Has voucher translation
     *
     * @param string $locale
     * @return boolean
     */
    public function hasTranslation($locale)
    {
        foreach ($this->voucherTranslations as &$translation) {
            if ($translation->getLanguage()->getCode() == $locale) {
                return true;
            }
        }
        return false;;
    }

    public function checkPersonInCollection(\MyWonderfulBundle\Entity\Person $person) {
        return $this->persons->contains($person);
    }

    public function getPersons() {
        return $this->persons;
    }

    public function getWishlists() {
        return $this->wishlists;
    }

    public function setWishlists($wishlists) {
        $this->wishlists = $wishlists;
    }
}

Et l'entité enfant :

  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
121
122
123
124
125
126
127
128
129
130
131
132
133
<?php

namespace MyWonderfulBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * VoucherTranslation
 *
 * @ORM\Table(name="voucher_translation", indexes={@ORM\Index(name="fk_voucher_has_language_language1_idx", columns={"language_code"}), @ORM\Index(name="fk_voucher_has_language_voucher1_idx", columns={"voucher_id"})})
 * @ORM\Entity
 */
class VoucherTranslation
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(name="title", type="string", length=45, nullable=false)
     */
    private $title;

    /**
     * @var \MyWonderfulBundle\Entity\Voucher
     *
     * @ORM\ManyToOne(targetEntity="MyWonderfulBundle\Entity\Voucher", inversedBy="voucherTranslations")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="voucher_id", referencedColumnName="id", nullable=false)
     * })
     */
    private $voucher;

    /**
     * @var \Language
     * @ORM\ManyToOne(targetEntity="Language", cascade={"persist"})
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="language_code", referencedColumnName="code", nullable=false)
     * })
     */
    private $language;


    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     * @return VoucherTranslation
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set voucher
     *
     * @param \MyWonderfulBundle\Entity\Voucher $voucher
     * @return VoucherTranslation
     */
    public function setVoucher(\MyWonderfulBundle\Entity\Voucher $voucher = null)
    {
        $this->voucher = $voucher;

        return $this;
    }

    /**
     * Get voucher
     *
     * @return \MyWonderfulBundle\Entity\Voucher
     */
    public function getVoucher()
    {
        return $this->voucher;
    }

    /**
     * Set language
     *
     * @param \MyWonderfulBundle\Entity\Language $language
     * @return VoucherTranslation
     */
    public function setLanguage(\MyWonderfulBundle\Entity\Language $language = null)
    {
        $this->language = $language;

        return $this;
    }

    /**
     * Get language
     *
     * @return \MyWonderfulBundle\Entity\Language
     */
    public function getLanguage()
    {
        return $this->language;
    }

    public function getLocale()
    {
        return $this->language->getCode();
    }
}

+0 -0

Salut,

Ne serait-ce pas Doctrine qui, ne gérant pas lui-même l'ID, fait mal son boulot ? J'ai récemment eu un problème sur les ID : je les settais manuellement, mais Doctrine réécrivait par dessus mes ID. Je sais que ce n'est pas le même problème, mais je n'ai pas d'autre piste à donner. :s

+0 -0

J'y ai pensé aussi, mais cela n'explique pas que ça pose problème pour une autre entité très similaire, mais avec des IDs gérés par Doctrine… Non, je pense qu'il y a une subtilité qui doit m'échapper à un moment, le souci n'est pas directement lié à mon ID particulier.

+0 -0

Voilà le contrôleur.

  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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<?php

namespace MyWonderfulBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use MyWonderfulBundle\Entity\Voucher;
use MyWonderfulBundle\Form\VoucherType;
use MyWonderfulBundle\Entity\VoucherTranslation;
use \DateTime;

/**
 * Voucher controller.
 *
 */
class VoucherController extends Controller
{
    /**
     * Lists all Voucher entities.
     *
     */
    public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();

        $entities = $em->getRepository('MyWonderfulBundle:Voucher')->findAll();

        return $this->render('MyWonderfulBundle:Voucher:index.html.twig', array(
            'entities' => $entities,
        ));
    }

    /**
     * Creates a new Voucher entity.
     *
     */
    public function createAction(Request $request)
    {
        $entity = new Voucher();
        $form = $this->createCreateForm($entity);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $translations = $entity->getVoucherTranslations();
            foreach ($translations as $translation) {
                $entity->addVoucherTranslation($translation);
            }
            $em->persist($entity);
            $em->flush();

            return $this->redirect($this->generateUrl('voucher_show', array('id' => $entity->getId())));
        }

        return $this->render('MyWonderfulBundle:Voucher:new.html.twig', array(
            'entity' => $entity,
            'form' => $form->createView(),
        ));
    }

    /**
     * Creates a form to create a Voucher entity.
     *
     * @param Voucher $entity The entity
     *
     * @return \Symfony\Component\Form\Form The form
     */
    private function createCreateForm(Voucher $entity)
    {
        $form = $this->createForm(new VoucherType(), $entity, array(
            'action' => $this->generateUrl('voucher_create'),
            'method' => 'POST',
        ));

        $form->add('submit', 'submit', array('label' => 'Create'));

        return $form;
    }

    /**
     * Displays a form to create a new Voucher entity.
     *
     */
    public function newAction()
    {
        $entity = new Voucher();

        $languages = $this->getDoctrine()->getRepository('MyWonderfulBundle:Language')->findAllActivatedBut(
                $this->container->getParameter('locale')
        );
        foreach ($languages as $language) {
            $translation = new VoucherTranslation();
            $translation->setLanguage($language);
            $entity->addVoucherTranslation($translation);
        }
        $form = $this->createCreateForm($entity);

        return $this->render('MyWonderfulBundle:Voucher:new.html.twig', array(
            'entity' => $entity,
            'form' => $form->createView(),
        ));
    }

    /**
     * Finds and displays a Voucher entity.
     *
     */
    public function showAction($id)
    {
        $em = $this->getDoctrine()->getManager();

        $entity = $em->getRepository('MyWonderfulBundle:Voucher')->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Voucher entity.');
        }

        $languages = $this->getDoctrine()->getRepository('MyWonderfulBundle:Language')->findAllActivatedBut(
                $this->container->getParameter('locale')
        );
        foreach ($languages as $language) {
            if ($entity->hasTranslation($language->getCode())) {
                continue;
            }
            $translation = new VoucherTranslation();
            $translation->setLanguage($language);
            $entity->addVoucherTranslation($translation);
        }

        $deleteForm = $this->createDeleteForm($id);

        return $this->render('MyWonderfulBundle:Voucher:show.html.twig', array(
            'entity' => $entity,
            'delete_form' => $deleteForm->createView(),
        ));
    }

    /**
     * Displays a form to edit an existing Voucher entity.
     *
     */
    public function editAction($id)
    {
        $em = $this->getDoctrine()->getManager();

        $entity = $em->getRepository('MyWonderfulBundle:Voucher')->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Voucher entity.');
        }

        $editForm = $this->createEditForm($entity);
        $deleteForm = $this->createDeleteForm($id);

        return $this->render('MyWonderfulBundle:Voucher:edit.html.twig', array(
            'entity' => $entity,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }

    /**
     * Creates a form to edit a Voucher entity.
     *
     * @param Voucher $entity The entity
     *
     * @return \Symfony\Component\Form\Form The form
     */
    private function createEditForm(Voucher $entity)
    {
        $form = $this->createForm(new VoucherType(), $entity, array(
            'action' => $this->generateUrl('voucher_update', array('id' => $entity->getId())),
            'method' => 'PUT',
        ));

        $form->add('submit', 'submit', array('label' => 'Update'));

        return $form;
    }

    /**
     * Edits an existing Voucher entity.
     *
     */
    public function updateAction(Request $request, $id)
    {
        $em = $this->getDoctrine()->getManager();

        $entity = $em->getRepository('MyWonderfulBundle:Voucher')->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Voucher entity.');
        }

        $deleteForm = $this->createDeleteForm($id);
        $editForm = $this->createEditForm($entity);
        $editForm->handleRequest($request);

        if ($editForm->isValid()) {
            $em->flush();

            return $this->redirect($this->generateUrl('voucher_edit', array('id' => $id)));
        }

        return $this->render('MyWonderfulBundle:Voucher:edit.html.twig', array(
            'entity' => $entity,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }

    /**
     * Deletes a Voucher entity.
     *
     */
    public function deleteAction(Request $request, $id)
    {
        $form = $this->createDeleteForm($id);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $entity = $em->getRepository('MyWonderfulBundle:Voucher')->find($id);

            if (!$entity) {
                throw $this->createNotFoundException('Unable to find Voucher entity.');
            }

            $em->remove($entity);
            $em->flush();
        }

        return $this->redirect($this->generateUrl('voucher'));
    }

    /**
     * Creates a form to delete a Voucher entity by id.
     *
     * @param mixed $id The entity id
     *
     * @return \Symfony\Component\Form\Form The form
     */
    private function createDeleteForm($id)
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl('voucher_delete', array('id' => $id)))
            ->setMethod('DELETE')
            ->add('submit', 'submit', array('label' => 'Delete'))
            ->getForm()
        ;
    }
}

+0 -0

Oui, et avec les deux éléments dedans. Seulement, à cette étape, l'ID des vouchers n'est pas spécifiée dans les traductions.

Edit

En fait, c'est logique pour le cas de la création d'un Voucher et de ses traductions, mais ce qui l'est moins, c'est l'exception levée au moment du flush() comme quoi la colonne voucher_id ne peut être nulle. La contrainte à ce niveau est correcte à mon avis : on ne peut avoir une traduction sans l'objet à traduire…

+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