Skip to content

[Serializer][ORM] enable_lazy_ghost_objects with Serializer doesn't work #53972

@Hanmac

Description

@Hanmac

Symfony version(s) affected

5.4.34

Description

enable_lazy_ghost_objects is set to true

have a Class with Complex Structure and Collections

use Doctrine\Common\Collections\Collection;
interface PageInterface
{
    /**
     * @return Collection<array-key, PageInterface>
     */
    public function getChildren(): Collection;
    /**
     * @return Collection<array-key, BlockInterface>
     */
    public function getBlocks(): Collection;
}
use Doctrine\Common\Collections\Collection;
interface BlockInterface
{
    /**
     * @return Collection<int, BlockInterface>
     */
    public function getChildren(): Collection;
}

How to reproduce

doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '13'
    orm:
        auto_generate_proxy_classes: true
        enable_lazy_ghost_objects: true

have entity classes that implement this interfaces:

use Doctrine\Common\Collections\Collection;
interface PageInterface
{
    /**
     * @return Collection<array-key, PageInterface>
     */
    public function getChildren(): Collection;
    /**
     * @return Collection<array-key, BlockInterface>
     */
    public function getBlocks(): Collection;
}
use Doctrine\Common\Collections\Collection;
interface BlockInterface
{
    /**
     * @return Collection<int, BlockInterface>
     */
    public function getChildren(): Collection;
}

now use Symfony Normalizer to try to normalize the Page object when the Collections aren't fully loaded yet

This ErrorException happened:

ErrorException:
User Notice: Undefined property: Proxies\__CG__\App\Entity\SonataPageBlock::$lazyObjectState in D:\*****\******\******\sonata\private\vendor\symfony\property-access\PropertyAccessor.php on line 488

  at D:\*****\******\******\sonata\private\vendor\symfony\var-exporter\LazyGhostTrait.php:193
  at Proxies\__CG__\App\Entity\SonataPageBlock->__get('lazyObjectState')
     (D:\*****\******\******\sonata\private\vendor\symfony\property-access\PropertyAccessor.php:488)
  at Symfony\Component\PropertyAccess\PropertyAccessor->readProperty(array(object(SonataPageBlock)), 'lazyObjectState', false)
     (D:\*****\******\******\sonata\private\vendor\symfony\property-access\PropertyAccessor.php:154)
  at Symfony\Component\PropertyAccess\PropertyAccessor->getValue(object(SonataPageBlock), 'lazyObjectState')
     (D:\*****\******\******\sonata\private\vendor\symfony\serializer\Normalizer\ObjectNormalizer.php:136)
  at Symfony\Component\Serializer\Normalizer\ObjectNormalizer->getAttributeValue(object(SonataPageBlock), 'lazyObjectState', null, array('datetime_format' => 'U', 'skip_null_values' => true, 'callbacks' => array('blocks' => object(Closure), 'parent' => object(Closure)), 'cache_key' => false, 'circular_reference_limit_counters' => array('00000000000005400000000000000000' => 1, '00000000000008a70000000000000000' => 1)))
     (D:\*****\******\******\sonata\private\vendor\symfony\serializer\Normalizer\AbstractObjectNormalizer.php:190)
  at Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->normalize(object(SonataPageBlock), null, array('datetime_format' => 'U', 'skip_null_values' => true, 'callbacks' => array('blocks' => object(Closure), 'parent' => object(Closure)), 'cache_key' => false, 'circular_reference_limit_counters' => array('00000000000005400000000000000000' => 1, '00000000000008a70000000000000000' => 1)))
     (D:\*****\******\******\sonata\private\vendor\symfony\serializer\Serializer.php:161)
  at Symfony\Component\Serializer\Serializer->normalize(object(SonataPageBlock), null, array('datetime_format' => 'U', 'skip_null_values' => true, 'callbacks' => array('blocks' => object(Closure), 'parent' => object(Closure)), 'cache_key' => false, 'circular_reference_limit_counters' => array('00000000000005400000000000000000' => 1)))
     (D:\*****\******\******\sonata\private\vendor\symfony\serializer\Serializer.php:179)
  at Symfony\Component\Serializer\Serializer->normalize(array(object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock), object(SonataPageBlock)), null, array('datetime_format' => 'U', 'skip_null_values' => true, 'callbacks' => array('blocks' => object(Closure), 'parent' => object(Closure)), 'cache_key' => false, 'circular_reference_limit_counters' => array('00000000000005400000000000000000' => 1)))
     (D:\*****\******\******\sonata\private\vendor\symfony\serializer\Normalizer\AbstractObjectNormalizer.php:224)
  at Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->normalize(object(SonataPagePage), null, array('datetime_format' => 'U', 'skip_null_values' => true, 'callbacks' => array('blocks' => object(Closure), 'parent' => object(Closure)), 'cache_key' => false, 'circular_reference_limit_counters' => array('00000000000005400000000000000000' => 1)))
     (D:\*****\******\******\sonata\private\vendor\symfony\serializer\Serializer.php:161)
  at Symfony\Component\Serializer\Serializer->normalize(object(SonataPagePage), null, array('datetime_format' => 'U', 'skip_null_values' => true, 'callbacks' => array('blocks' => object(Closure), 'parent' => object(Closure))))

Possible Solution

calling Collection->getValues() might not be enough, the 'blocks' Closure already does it

AbstractNormalizer::CALLBACKS => [
    'blocks' => static fn (Collection $collection, PageInterface $object, string $attribute, ?string $format = null, array $context = []) => $collection->filter(static fn (BlockInterface $block) => !$block->hasParent())->getValues(),
]

Additional Context

Related Sonata Issue:
sonata-project/SonataPageBundle#1747

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