Symfony 4 & Doctrine 2 serialize after removing (f

2019-08-02 09:57发布

问题:

I'm having a lot of trouble with serializing collection from which I've removed first element.

I have CompaniesCollection entity with Many2Many relation to Company entity.

/**
 * @ORM\ManyToMany(targetEntity="App\Entity\Company")
 * @Groups({"get-by-collection-owner"})
 */
private $items;

When I fetch the object with that collection, I receive items as an array of two elements I've added in the first place. I serialize it

{
    "id": 19,
    "name": "collection dummy name",
    "items": [
        {
            "id": 431,
            "name": "Company 1"
        },
        {
            "id": 435,
            "name": "Company 2"
        }
    ],
    "createdAt": "2019-03-11T13:55:43+01:00",
    "updatedAt": "2019-03-11T15:48:57+01:00"
},

Then I remove FIRST item:

    $collection->removeItem($companyToRemove);
    $em = $this->getDoctrine()->getManager();
    $em->persist($collection);
    $em->persist($companyToRemove);
    $em->flush();

    $results = $companiesCollectionRepository->getCollections($companyLoader->getCurrentCompany());

Serialize the $results and get not an array of one element, but object with key of second element from the previous array of items:

{
    "id": 19,
    "name": "collection dummy name",
    "items": {
        "1": {
            "id": 435,
            "name": "Company 2"
        }
    },
    "createdAt": "2019-03-11T13:55:43+01:00",
    "updatedAt": "2019-03-11T15:52:48+01:00"
},

When I reload the page and fetch this object, the collection is again one element array, not an object. Apparently Doctrine doesn't get new results from database, but returns data which has already loaded in the memory.

And serializer most likely treats this "array" as an "object", because it doesn't start with 0 for a key of first array element, but with key 1.

Is there any way to make that query again, so I get freshly generated keys, or refresh these keys?

EDIT:

Actually I've finally found simple solution for this: refresh after flush

    $collection->removeItem($companyToRemove);
    $em = $this->getDoctrine()->getManager();
    $em->persist($collection);
    $em->persist($companyToRemove);
    $em->flush();
    $em->refresh($collection);

回答1:

I've faced the same problem. And here is another solution which seems working for me.

Custom normalizer, which will be used for collections with "holes" in they keys:

namespace App\Serializer\Normalizer;

use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerAwareTrait;

class DoctrineCollectionNormalizer implements NormalizerInterface, SerializerAwareInterface
{
    use SerializerAwareTrait;

    public function normalize($object, $format = null, array $context = array()): array
    {
        /* @var $object Collection */
        $values = $object->getValues();
        $object->clear();
        foreach ($values as $k => $value) {
            $object->set($k, $value);
        }

        return $this->serializer->normalize($object, $format, $context);
    }

    public function supportsNormalization($data, $format = null): bool
    {
        if ($data instanceof Collection) {
            $keys = $data->getKeys();
            $count = count($keys);
            $lastKey = (int) array_pop($keys);
            return $count && $lastKey !== $count-1;
        }
        return false;
    }
}