Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event properties matching ShouldBeStored methods are not serialized correctly #475

Open
ganyicz opened this issue Aug 14, 2024 · 1 comment

Comments

@ganyicz
Copy link

ganyicz commented Aug 14, 2024

This is due to how Symfony serializer works. When the serialized object (event) contains a method with the name of the attribute, return value of that method is preferred and the value of the attribute is ignored.

The simplest example of this is declaring a Carbon property named createdAt. When an event with this property is stored with a value different from now, the provided value is ignored and instead the event is saved in the database with the current time being the value of this property (which is the return value of the createdAt method on ShouldBeStored)

@ganyicz
Copy link
Author

ganyicz commented Aug 15, 2024

As a quick fix, we wrote a custom ObjectNormalizer and replaced it in event_normalizers array in config, this fixes the issue

namespace App\Support;

use Spatie\EventSourcing\Support\ObjectNormalizer as BaseObjectNormalizer;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfo;

class ObjectNormalizer extends BaseObjectNormalizer
{
    public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, ?PropertyAccessorInterface $propertyAccessor = null, ?PropertyTypeExtractorInterface $propertyTypeExtractor = null, ?ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, ?callable $objectClassResolver = null, array $defaultContext = [])
    {
        // We need to customize how properties are accessed during normalization
        // to prevent methods from being preferred over properties. This causes
        // issues with events that have property named eg. `createdAt` because
        // method with the same name already exists on ShouldBeStored class
        $readInfoExtractor = new class implements PropertyReadInfoExtractorInterface {
            protected ReflectionExtractor $reflectionExtractor;

            public function __construct()
            {
                $this->reflectionExtractor = new ReflectionExtractor([], null, null, false);
            }

            public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo
            {
                $context['enable_getter_setter_extraction'] = false;

                return $this->reflectionExtractor->getReadInfo($class, $property, $context);
            }
        };

        $propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()
            ->setReadInfoExtractor($readInfoExtractor)
            ->getPropertyAccessor();

        parent::__construct(
            $classMetadataFactory,
            $nameConverter,
            $propertyAccessor,
            $propertyTypeExtractor,
            $classDiscriminatorResolver,
            $objectClassResolver,
            $defaultContext,
        );
    }
}

Then, in config/event-sourcing.php:

    'event_normalizers' => [
        Spatie\EventSourcing\Support\CarbonNormalizer::class,
        Spatie\EventSourcing\Support\ModelIdentifierNormalizer::class,
        Symfony\Component\Serializer\Normalizer\DateTimeNormalizer::class,
        Symfony\Component\Serializer\Normalizer\ArrayDenormalizer::class,
        Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer::class,
        App\Support\ObjectNormalizer::class,
    ],

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant