Skip to content

[LiveComponent] Hydration fails on arrays of entities whose identifier is an UUID (object) #3410

@redclov3r

Description

@redclov3r

When using arrays of entities as a LiveProp in a component (as described here in the docs) the dehydration fails, if the object's identifiers are object values like some kind of Uuid/Symfony\Component\Uid\AbstractUid. The corresponding error is thrown here:

if (!$this->isValueValidDehydratedValue($value)) {
throw new \LogicException(throw new \LogicException(\sprintf('Unable to dehydrate value of type "%s" for property "%s" on component "%s". Change this to a simpler type of an object that can be dehydrated. Or set the hydrateWith/dehydrateWith options in LiveProp or set "useSerializerForHydration: true" on the LiveProp to use the serializer.', get_debug_type($value), $propMetadata->getName(), $parentObject::class)));
}

When I only refer to a single object with an Uuid everything works. The reason for that is seemingly, that LiveComponentHydrator::isValueValidDehydratedValue() is only checked for values in lower levels, i.e. in two places:

  1. When writable is explicitly set to an array of paths
  2. When dehydrating an array/iterable

Since objects fail the check in this function, this causes the aforementioned exception.

Expected behavior

Arrays of entities with UUID object identifiers should be hydrated into an array of scalar values that can later be hydrated back correctly.

Actual behavior

The object identifiers fail a validity check which breaks the hydration flow for LiveComponent when the entity ID is a UUID object.

How to reproduce

<?php

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity]
class Product
{
    #[ORM\Id]
    #[ORM\Column(type: UuidType::NAME, unique: true)]
    #[ORM\GeneratedValue(strategy: 'CUSTOM')]
    #[ORM\CustomIdGenerator(class: UuidGenerator::class)]
    private ?Uuid $id;

    public function getId(): ?Uuid
    {
        return $this->id;
    }

    // ...
}
<?php
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class ProductList
{
    use DefaultActionTrait;

    /**
     * @var Product[]
     */
    #[LiveProp]
    public array $products= [];

    // ...
}

Additional Notes
I worked around this now, by copying the DoctrineEntityHydrationExtension into my project and adding a conversion from AbstractUid to string in there, right below the recursive call for other entities:

// Dehydrate ID values in case they are other entities
$id = array_map(fn ($id) => \is_object($id) && $this->supports($id::class) ? $this->dehydrate($id) : $id, $id);
$id = array_map(fn ($id) => $id instanceof AbstractUid ? (string) $id : $id, $id); // <-- this is new

This works for now but I am not sure this is the best way to solve this in the long run. Another option would be to extend the check in LiveComponentHydrator::isValueValidDehydratedValue() to also allow for AbstractUid or even \Stringable.

If we settle on a viable solution, I can try to provide a corresponding PR.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions