0

I have a self referencing entity with parent and children. The strange thing is, when I add the form elements (DoctrineModule ObjectSelect) for parent and children to the form, some other fields of the parent entity doesn't update, when I persist the entity. The child entities are updating fine.

In the update query of the parent aren't these fields which I want to update. It's like doctrine doesn't recognize the changes for the parent (owning side) anymore. Before the persist I get the right entity with the actual changes from the form, but than doctrine doesn't update the changed fields in the query.

When I delete the form elements for parent and children in the form, everything works fine and the parent entity update/persist all fields.

/**
 * @var string
 *
 * @Gedmo\Translatable
 * @ORM\Column(type="text")
 */
private $teaser;

/**
 * @var string
 *
 * @Gedmo\Translatable
 * @ORM\Column(type="text")
 */

private $description;

/**
 * One Category has Many Categories.
 * @var Collection
 * @ORM\OneToMany(targetEntity="Rental\Entity\Rental", mappedBy="parent")
 */
private $children;

/**
 * Many Categories have One Category.
 * @ORM\ManyToOne(targetEntity="Rental\Entity\Rental", inversedBy="children")
 * @ORM\JoinColumn(name="parent_id", referencedColumnName="houses_id", nullable=true, onDelete="SET NULL")
 */
private $parent;

public function __construct()
{
    $this->children = new ArrayCollection();
}

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

/**
 * Set teaser
 *
 * @param string $teaser
 */
public function setTeaser($teaser)
{
    $this->teaser = $teaser;
}

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

/**
 * Set description
 *
 * @param string $description
 */
public function setDescription($description)
{
    $this->description = $description;
}

/**
 * Add $child
 * @param Collection $children
 */
public function addChildren(Collection $children)
{
    foreach ($children as $child) {
        $this->addChild($child);
    }
}


/**
 * @param Rental $child
 * @return void
 */
public function addChild(Rental $child)
{
    if ($this->children->contains($child)) {
        return;
    }
    $child->setParent($this);
    $this->children[] = $child;
}

/**
 * Remove children
 * @param Rental $children
 */
public function removeChildren(Collection $children)
{
    foreach ($children as $child) {
        $this->removeChild($child);
    }
}

/**
 * Remove child.
 *
 * @param \Rental\Entity\Rental $child
 *
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
 */
public function removeChild(Rental $child)
{
    return $this->children->removeElement($child);
}

/**
 * Get children.
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getChildren()
{
    return $this->children;
}

/**
 * Set parent.
 *
 * @param \Rental\Entity\Rental|null $parent
 */
public function setParent(Rental $parent = null)
{
    $this->parent = $parent;
}

/**
 * Get parent.
 *
 * @return \Rental\Entity\Rental|null
 */
public function getParent()
{
    return $this->parent;
}
Oskar
  • 105
  • 1
  • 6
  • From what I see I think the handling by Doctrine is correct. You have configured the child/parent relation so that persisting changes to either the parent or the child from the Entity in the Form is not allowed. Only the currently add/edited Entity may be modified. If you want to change parent/child from whichever current Entity, you need to add `cascade={"persist"}` (to allow remove also add `,"remove"` within accolades). – rkeet May 02 '18 at 08:16
  • @rkeet Thx for your comment. I tried also with cascade={"persist"}, but the problem is, that Doctrine doesn't recognize all other fields apart from $children and $parent in the parent entity. These other fields are not persisted. – Oskar May 03 '18 at 12:06
  • For the sake of being thorough, can you copy in a few of the other properties? – rkeet May 03 '18 at 15:38
  • @rkeet I added two other fields. When I add to my form the form elements for $children and $parent, other fields aren't updated. When I delete the two form elements, everything is working fine. – Oskar May 03 '18 at 19:24
  • Looks absolutely fine. Problem must then be in your form setup. Could you share your Form, Fieldset, InputFilter and/or Factories and config as well? Becomes a lot, but an issue is an issue. To see if you can find it yourself, have a look at a [working example](https://stackoverflow.com/a/49938280/1155833) of Form + Fieldset + InputFilter (as separate classes) setup. Includes Factory classes, config and partial for front-end. – rkeet May 03 '18 at 21:17
  • My Form works fine and indeed it would be a lot. When I dump the data before the persist statement, all data from the form exists and is right. But persist execute only the SQL statement UPDATE rental SET type = ?, updated_at = ? WHERE houses_id = ? All updated fields aren't set. Only type and updated_at. I can't get the clue. – Oskar May 04 '18 at 12:37
  • Yea that's why I asked. Your description makes it sound as if you might be making a mistake or 2 with data binding and validation. To gain some more insight into the `isValid()` method, [please read this](https://stackoverflow.com/a/45613909/1155833). Next, make sure that *before* validation you do: `$form->bind(new Entity())` and `$form->setData($this->getRequest()->getData())`. Then *after* validation do: `$entity = $form->getObject(); $this->getObjectManager()->persist($entity); $this->getObjectManager()->flush($entity);`. ... Ok, I'll write it out in a small answer. – rkeet May 04 '18 at 14:51
  • If my answer answered your question, feel free to accept ;-) If you still have issues here, please let me know. – rkeet May 12 '18 at 09:50
  • I still have issues, but actually don't have time. I you don't mind, I will contact you when I have time. Thanks a lot in advance. – Oskar May 12 '18 at 13:41

1 Answers1

0

Answer based (and might still change) based on discussion in comments with OP under question.


Simple use case: self-referencing Doctrine Entity does not correctly update parent/child related Entity object properties on on-save action.


OP provided Entity.

I'm assuming you have a Form setup for this Entity. I'm also assuming it's correctly setup, as you did not provide it after being asked (because, rightly so, it would mean a lot of code). However, assuming stuff with code makes for a lot of mistakes, which is why I mention it here.

As such, I'm assuming the handling of your Form in the Controller might be faulty. To check, please use the following simplified addAction function and give it a shot (Factory code below).

/**
 * @var RentalForm
 */
protected $form;

/**
 * @var ObjectManager
 */
protected $objectManager;

public function __construct(ObjectManager $objectManager, RentalForm $form)
{
    $this->form = $form;
    $this->objectManager = $objectManager;
}

public function addAction()
{
    /** @var RentalForm $form */
    $form = $this->getForm();

    /** @var Request $request */
    $request = $this->getRequest();
    if ($request->isPost()) {
        $form->setData($request->getPost());

        if ($form->isValid()) {
            $entity = $form->getObject();

            $this->getObjectManager()->persist($entity);

            try {
                $this->getObjectManager()->flush();
            } catch (\Exception $e) {
                $message = sprintf(
                    'Was unable to save the data. Saving threw an error. <br />Code: %s. <br />Message: %s',
                    $e->getCode(),
                    $e->getMessage()
                );

                $this->flashMessenger()->addErrorMessage($message);

                return [
                    'form' => $form,
                    'validationMessages' => $form->getMessages() ?: '',
                ];
            }

            $this->flashMessenger()->addSuccessMessage(
                $this->getTranslator()->translate('Successfully created object.')
            );

            // TODO replace vars with your own: return $this->redirect()->route($route, $routeParams);
        }

        $this->flashMessenger()->addWarningMessage(
            'Your form contains errors. Please correct them and try again.'
        );
    }

    return [
        'form' => $form,
        'validationMessages' => $form->getMessages() ?: '',
    ];
}

Factory for class with the above, modify as needed for you situation

class RentalControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        /** @var ObjectManager $objectManager */
        $objectManager = $container->get(EntityManager::class);

        /** @var FormElementManagerV3Polyfill $formElementManager */
        $formElementManager = $container->get('FormElementManager');

        /** @var RentalForm $form */
        $form = $formElementManager->get(RentalForm::class);

        return new RentalController($objectManager, $form);
    }
}
rkeet
  • 3,406
  • 2
  • 23
  • 49