zenstruck / messenger-monitor-bundle

Batteries included UI to monitor your Messenger workers, transports, schedules, and messages.
MIT License
153 stars 16 forks source link

`LogicException` on message serialization (`symfony/serializer > 7.1`) #89

Open stanislavzozulia opened 4 months ago

stanislavzozulia commented 4 months ago

After the recent update of symfony/serializer (tested with v7.1.2) its not possible to enqueue messages due to LogicException thrown in \Zenstruck\Messenger\Monitor\Stamp\MonitorStamp::transport

    public function transport(): string
    {
        return $this->transport ?? throw new \LogicException('Message not yet received.');
    }

As the serializer now checks isAllowedAttribute by using $this->propertyAccessor->isReadable(...) which basically gets the value, BUT the transport property is not yet assigned at that moment, so the exception occurs on serializing attempt of the MonitorStamp object.

Using of the Attribute #[DisableMonitoringStamp] does not help, as MonitorStamp is still being added to the message.

Is there any way to avoid this issue?

Chris53897 commented 3 months ago

Could you please provide more details to reproduce?

I use this with symfony/serializer 7.1.2 and do not see any errors.

stanislavzozulia commented 3 months ago

@Chris53897 thank you for the response.

I noticed that the issue can be reproduced with configured storage layer only (https://github.com/zenstruck/messenger-monitor-bundle?tab=readme-ov-file#storage)

Please find attached the output & stack trace dispatch_stack_trace.txt

I also created a small app which helps reproduce the issue - https://github.com/stanislavzozulia/messenger-example Command app:enqueue tries to enqueue the message and throws this exception when calling MonitorStamp::transport()

andersonamuller commented 3 months ago

I have the same issue

GregoireGiraud commented 3 months ago

Hey, I'm having the exact same issue.

Tried the bundle today but it never worked.

I'm using symfony/serializer with v7.1.3 image

Here is the stack trace if it helps you.

Thanks a lot !

andersonamuller commented 3 months ago

As a workaround I have registered a normalizer to deal with the MonitorStamp like this:

<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Messenger;

use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Zenstruck\Messenger\Monitor\Stamp\MonitorStamp;

final class MonitorStampNormalizer extends AbstractObjectNormalizer
{
    #[\Override]
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
    {
        return $data instanceof MonitorStamp;
    }

    #[\Override]
    public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
    {
        return false;
    }

    /**
     * @return array<string, ?bool>
     */
    #[\Override]
    public function getSupportedTypes(?string $format): array
    {
        return [
            MonitorStamp::class => true,
        ];
    }

    /**
     * @param array<array-key, mixed> $context
     * @return list<string>
     */
    protected function extractAttributes(object $object, ?string $format = null, array $context = []): array
    {
        return [
            'received',
            'finished',
            'runId',
            'dispatchedAt',
            'transport',
            'receivedAt',
            'finishedAt',
            'memoryUsage',
        ];
    }

    /**
     * @param array<array-key, mixed> $context
     */
    protected function getAttributeValue(object $object, string $attribute, ?string $format = null, array $context = []): mixed
    {
        return $this->readProperties($object)[$attribute] ?? null;
    }

    /**
     * @param array<array-key, mixed> $context
     */
    protected function setAttributeValue(object $object, string $attribute, mixed $value, ?string $format = null, array $context = []): void
    {
        // NO-OP
    }

    /**
     * @return array<string, mixed>
     */
    private function readProperties(object $object): array
    {
        $this->get ??= fn() => get_object_vars($this);

        return $this->get->call($object);
    }

    /** @var \Closure(): array<string, mixed> */
    private \Closure $get;
}