-1

My question is more or less the same as this one:

How to force Doctrine to update array type fields?

but for some reason the solution didn't work for me, so there must be a detail I am missing and I would be happy if someone could point it out to me.

The context is a Symfony 5.2 app with Doctrine ^2.7 being used.

Entity-Class-Excerpt:

class MyEntity {
    // some properties

    /**
     * @var string[]
     * @Groups("read")
     * @ORM\Column(type="array")
     * @Assert\Valid
     */
    protected array $abbreviations = [];

    
    public function getAbbreviations(): ?array
    {
        return $this->abbreviations;
    }

    //this is pretty much the same set-function as in the question I referenced
    public function setAbbreviations(array $abbreviations)
    {
        if (!empty($abbreviations) && $abbreviations === $this->abbreviations) {
            reset($abbreviations);
            $abbreviations[key($abbreviations)] = clone current($abbreviations);
        }
        $this->abbreviations = $abbreviations;
    }

    public function addAbbreviation(LocalizableStringEmbeddable $abbreviation): self
    {
        foreach ($this->abbreviations as $existingAbbreviation) {
            if ($abbreviation->equals($abbreviation)) {
                return $this;
            }
        }

        $this->abbreviations[] = $abbreviation;
        return $this;
    }

    public function removeAbbreviation(LocalizableStringEmbeddable $abbreviation): self
    {
        foreach ($this->abbreviations as $i => $existingAbbreviation) {
            if ($abbreviation->equals($existingAbbreviation)) {
                array_splice($this->abbreviations, $i, 1);
                return $this;
            }
        }

        return $this;
    }
}

But none of these methods are ever being called (I also tried removing add-/removeAbbreviation only leaving get/set in place).

LocalizableStringEmbeddable being an Embeddable like this:

* @ORM\Embeddable
 */
class LocalizableStringEmbeddable
{
    /**
     * @var string|null
     * @Groups("read")
     * @ORM\Column(type="string", nullable=false)
     */
    private ?string $text;

    /**
     * @var string|null
     * @Groups("read")
     * @ORM\Column(type="string", nullable=true)
     * @Assert\Language
     */
    private ?string $language;

    //getters/setters/equals/@Assert\Callback
}

By using dd(...) I can furthermore say that in my controller on submit

$myEntity = $form->getData();

yields a correctly filled MyEntity with an updated array but my subsequent call to

$entityManager->persist($myEntity);
$entityManager->flush();

doesn't change the database.

What am I missing?

EDIT: I was asked to give information about the Type I use. It is a custom one that is based on this class. So technically at the base of things I am using a collection type.

abstract class AbstractLocalizableUnicodeStringArrayType extends AbstractType implements DataMapperInterface
{
    abstract public function getDataClassName(): string;

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('items', CollectionType::class, [
                'entry_type' => LocalizedStringEmbeddableType::class,
                'entry_options' => [
                    'data_class' => $this->getDataClassName(),
                    'attr' => ['class' => 'usa-item-list--item-box'],
                ],
                'allow_add' => true,
                'allow_delete' => true,
                'prototype' => true,
            ])
        ;

        $builder->setDataMapper($this);
    }

    public function mapDataToForms($viewData, iterable $forms): void
    {
        $forms = iterator_to_array($forms);
        if (null === $viewData) {
            return;
        }

        if (!is_array($viewData)) {
            throw new UnexpectedTypeException($viewData, "array");
        }

        $forms['items']->setData($viewData);
    }

    public function mapFormsToData(iterable $forms, &$viewData): void
    {
        $forms = iterator_to_array($forms);
        $viewData = $forms['items']->getData();
    }
}
Wolfone
  • 1,276
  • 3
  • 11
  • 31
  • So just to clarify, $abbreviations is a key/value array and your form updates a value but does nothing with the keys? And you are saying that setAbbreviations is never being called? setAbbreviations is where you should be able to clone your abbreviations array thus triggering an update. It looks like you are cloning an individual element of the array but what you really need to do is to clone the entire array each time it is modified. – Cerad Mar 18 '21 at 13:40
  • Hey, it is a key-value-array, the values being objects of type LocalizableStringEmbeddable. set/add/remove are never called. Changing for example the text of one of those yields a correctly filled entity in the controller but doesn't trigger flush to actually write the change into the DB from which I derive that Doctrine doesn't think something changed. Meanwhile I found out that a combination of two answers from the linked thread solve this but in a not very nice way in my opinion. (Meaning: PreFlush-hook + cloning and new setting of one of the elements of the array triggers an actual write). – Wolfone Mar 19 '21 at 06:50
  • You might try changing the column type to json. Array is strictly a Doctrine data type while most databases have native support for json and perhaps (this is just a guess) the update problem will go away. It does seem strange that the setters never seem to get called. I am assuming you are using a collection type? Maybe update your question with the relevant portions of your form type. – Cerad Mar 19 '21 at 11:35
  • Hi and thanks again, I added the type-info to the question. And you are right, under the hood I am technically using a collection type but wrapped in a custom one. Json is unfortunately not the best option as the documentation states that ordering of keys may change using this type (using MySQL which is the case). But don't be too occupied with this. You already invested enough time on this question that seems to be not clear/useful/showing research effort -.- Especially after I have a solution that is just not very elegant IMO. – Wolfone Mar 22 '21 at 05:22

1 Answers1

0

As I found out this can be solved (in an unelegant way in my opinion but until something better comes around I want to state this) by combining two answers from another post on SO:

https://stackoverflow.com/a/13231876/6294605

https://stackoverflow.com/a/59898632/6294605

This means combining a preflush-hook in your entity class with a "fake-change" of the array in question.

Remark that doing the fake-change in a setter/adder/remover didn't work for me as those are not being called when editing an existing entity. In this case only setters of the changed objects inside the array will be called thus making Doctrine not recognize there was a change to the array itself as no deep-check seems to be made.

Another thing that was not stated in the other thread I wanna point out:

don't forget to annotate your entity class with

@ORM\HasLifecycleCallbacks

or else your preflush-hook will not be executed.

Wolfone
  • 1,276
  • 3
  • 11
  • 31