8000 [Messenger] Symfony Serializer: Cannot create instance of BusNameStamp · Issue #31490 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Messenger] Symfony Serializer: Cannot create instance of BusNameStamp #31490

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

Closed
raress96 opened this issue May 13, 2019 · 11 comments
Closed

[Messenger] Symfony Serializer: Cannot create instance of BusNameStamp #31490

raress96 opened this issue May 13, 2019 · 11 comments

Comments

@raress96
Copy link

Symfony version(s) affected: 4.3-beta1

Description
The exact error that I receive from the console:
Cannot create an instance of Symfony\Component\Messenger\Stamp\BusNameStamp from serialized data because its constructor requires parameter "busName" to be present.

To me this error makes no sens, I have published a message using a SF 4.3 application and tried to consume it in another SF 4.3 application. For some reason the deserialization of the BusNameStamp fails in Symfony\Component\Messenger\Transport\Serialization\Serializer class in the decodeStamps function, which really makes no sense since the JSON that is used for the deserialization of this stamp is this: [{"busName":"messenger.bus.default"}]

How to reproduce
Publish a message from a SF 4.3 application using the messenger.transport.symfony_serializer service as the messenger default serializer and then try to consume it.

@raress96 raress96 changed the title [Messenger] Serializer: Cannot create instance of BusNameStamp [Messenger] Symfony Serializer: Cannot create instance of BusNameStamp May 13, 2019
@weaverryan
Copy link
Member

Hmm. Weren’t you having success serializing and deserializing messages for other issues you created? Indeed, I agree that this is odd. I thought that because the constructor arg is “$busName” and the key is “busName” in JSON, that it would know to construct it using that key. Many other stamps follow this convention and I believe are serializing and deserializing fine. I remember even testing the full process using the Symfony serializer a few days ago.

Is there any other special setup you have?

@raress96
Copy link
Author

No special setup, the same that I used for the other issues.
I have a publisher app which is written in SF 4.1, 4.2 and 4.3 to see if old versions of the Messenger are compatible with a second consumer app written in SF 4.3.
I tested before beta1 and the publishing and consuming worked fine with all 3 SF versions (aside from other issues I posted but that were solved in the mean time).
Now the publishing does not work from the SF 4.3 app (were the BusNameStmap was added).

@chalasr
Copy link
Member
chalasr commented May 25, 2019

The issue should be gone after running composer require symfony/property-access.
The reason is that ObjectNormalizer is required for normalizing the stamp object, and that normalizer is only wired when the property-access component is installed.

We could probably improve DX by detecting that it is not available before trying to serialize the stamp, and throw a meaningful exception (could even be thrown at compile-time if the symfony serializer is configured on a transport but property-access is not installed).

Or we could "hardcode" a special case for the BusNameStamp in the Serializer e.g:

foreach ($allStamps as $class => $stamps) {
    if (BusNameStamp::class === $class && $stamps) {
        $stamps = array_map(function (BusNameStamp $stamp): array { return ['busName' => $stamp->getBusName()]; }, $stamps);
        $headers[self::STAMP_HEADER_PREFIX.$class] = $this->serializer->encode([$stamps]);
        continue;
    }
    # ...
}

But I guess it wouldn't be very scalable.

@weaverryan
Copy link
Member

Ah! Wait, so if property access isn’t available, object normalizer isn’t available? What normalizer is used then? Or is it just that his normalizer is less powerful?

Anyways, if I understand correctly, pretty much none of the core stamps are serializable with the Symfony serializer unless property access is available as many have constructor arguments without setters, which I think is what works only with property access.

I think we should throw an exception in the Symfony serializer for messenger that property access is required if not installed.

@chalasr
Copy link
Member
chalasr commented May 26, 2019

Wait, so if property access isn’t available, object normalizer isn’t available

Yes!

if (!\class_exists(PropertyAccess::class)) {
throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.');
}

What normalizer is used then?

Here is the full list of default norsmalizers injected in the serializer service when you don't have property-access installed:

"Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer"
"Symfony\Component\Serializer\Normalizer\DateTimeNormalizer"
"Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer"
"Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer"
"Symfony\Component\Serializer\Normalizer\DataUriNormalizer"
"Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer"
"Symfony\Component\Serializer\Normalizer\DateTimeNormalizer"
"Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer"
"Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer"
"Symfony\Component\Serializer\Normalizer\DataUriNormalizer"

Which obviously don't support normalization for any other type than the one they are prefixed by.
So yes, it should definitely throw.

But actually, the error I get is not exactly the same as the one described here (which I can't manage to reproduce). Mine is:

Could not normalize object of type Symfony\Component\Messenger\Stamp\BusNameStamp, no supporting normalizer found.

So we still need @raresserban to confirm that my solution also fixes his issue, but I think it won't as the stamp is properly normalized but not denormalized.

@raresserban Can you please run the command with -vvv and paste the full stacktrace?

@raress96
Copy link
Author
raress96 commented May 27, 2019

Hello, I have the property-access component, so I don't think the problem is related to yours. Here is the stack trace:

In AbstractNormalizer.php line 510:
                                                                                 
  [Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException]  
  Cannot create an instance of Symfony\Component\Messenger\Stamp\BusNameStamp    
   from serialized data because its constructor requires parameter "busName"     
  to be present.                                                                 
                                                                                 

Exception trace:
 () at /app/vendor/symfony/serializer/Normalizer/AbstractNormalizer.php:510
 Symfony\Component\Serializer\Normalizer\AbstractNormalizer->instantiateObject() at /app/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:231
 Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->instantiateObject() at /app/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php:332
 Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize() at /app/vendor/symfony/serializer/Serializer.php:191
 Symfony\Component\Serializer\Serializer->denormalize() at /app/vendor/symfony/serializer/Normalizer/ArrayDenormalizer.php:60
 Symfony\Component\Serializer\Normalizer\ArrayDenormalizer->denormalize() at /app/vendor/symfony/serializer/Serializer.php:191
 Symfony\Component\Serializer\Serializer->denormalize() at /app/vendor/symfony/serializer/Serializer.php:142
 Symfony\Component\Serializer\Serializer->deserialize() at /app/vendor/symfony/messenger/Transport/Serialization/Serializer.php:118
 Symfony\Component\Messenger\Transport\Serialization\Serializer->decodeStamps() at /app/vendor/symfony/messenger/Transport/Serialization/Serializer.php:73
 Symfony\Component\Messenger\Transport\Serialization\Serializer->decode() at /app/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceiver.php:66
 Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver->getEnvelope() at /app/vendor/symfony/messenger/Transport/AmqpExt/AmqpReceiver.php:47
 Symfony\Component\Messenger\Transport\AmqpExt\AmqpReceiver->get() at /app/vendor/symfony/messenger/Worker.php:92
 Symfony\Component\Messenger\Worker->run() at /app/vendor/symfony/messenger/Worker/StopWhenRestartSignalIsReceived.php:54
 Symfony\Component\Messenger\Worker\StopWhenRestartSignalIsReceived->run() at /app/vendor/symfony/messenger/Command/ConsumeMessagesCommand.php:224
 Symfony\Component\Messenger\Command\ConsumeMessagesCommand->execute() at /app/vendor/symfony/console/Command/Command.php:255
 Symfony\Component\Console\Command\Command->run() at /app/vendor/symfony/console/Application.php:930
 Symfony\Component\Console\Application->doRunCommand() at /app/vendor/symfony/framework-bundle/Console/Application.php:87
 Symfony\Bundle\FrameworkBundle\Console\Application->doRunCommand() at /app/vendor/symfony/console/Application.php:273
 Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:73
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:149
 Symfony\Component\Console\Ap
8000
plication->run() at /app/bin/console:38

I also updated the apps to 4.3-beta2, but still the same problem...

@raress96
Copy link
Author
raress96 commented May 27, 2019

I think I found the problem, it is because the name of the constructor argument is not correct. The $key parameter that the name converter provides on line 479 from the AbstractNormalizer class is bus_name not busName, but the data is stored in the form of busName => "value", hence the error... Might be because the publishing app uses ApiPlatform, which uses camelCase, but the subscribing app does not use ApiPlatform, hence the other deserialization format...

And if publishing from the same ApiPlatform app with SF 4.2, it works because the constructor variable on line 468 of AbstractNormalizer is null.

Also, the nameConverter injected in the AbstractNormalizer class is Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter, but the fallbackNameConverter is Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter which seems to be used, so I am guessing there is a problem here.

After further debugging, I think the problem is in the MetadataAwareNameConverter class on line 78. The attributesMetadata there is empty, and I think the busName should have been included there. The metadataFactory is an instance of Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory

@raress96
Copy link
Author

Finally found the issue, it was because of a configuration option set in one of our internal bundles. There we set the serializer name converter to be the CamelCaseToSnakeCaseNameConverter for some weird reason, and that bundle was only used in the consuming application, so the publishing app was not affected by it.

Also publishing from SF 4.2 was not affected since there the BusNameStamp is not serialized in the envelope...

So finally fixed this by setting this in the framework config:

serializer:
        name_converter: false

Closing this ticket now.

OskarStark added a commit to symfony/symfony-docs that referenced this issue Jun 11, 2019
This PR was merged into the 4.2 branch.

Discussion
----------

[Serializer] Use pack to install

As [here](symfony/symfony#31490 (comment)), I also ran into issues using messenger with the Symfony Serializer, due to `symfony/property-access` missing and the `ObjectNormalizer` not being available due to that.

This change updates the docs to use the pack instead of the package to install the Serializer (which includes `symfony/property-access`).

~I also included a note for developers that already installed the Serializer directly and try to figure out why `ObjectNormalizer` wasn't available.~

Commits
-------

1ffc677 [Serializer] Use pack to install
@barell
Copy link
barell commented Dec 17, 2021

I know it is old issue but I encountered the same error message and thought it is worth to put my comment here for the future.

In my case I have installed Symfony Serializer compontent as composer require symfony/serializer but uninstalling and installing as composer require symfony/serializer-pack solved the issue.

@faizanakram99
Copy link
Contributor
faizanakram99 commented Feb 22, 2023

@chalasr This is still a problem,

The issue is TransportNamesStamp has $transports as ctor parameter but a getter with name getTransportNames which is serialized to transportNames.

When denormalizing it again, it is unable to map transportNames key to transports ctor parameter

The fix would be to rename the ctor parameter to $transportNames (to make serialization + deserialization interoperable)

@faizanakram99
Copy link
Contributor
8000

I'll try create another issue/PR with a reproducer. The fix is simple.

tucksaun added a commit to tucksaun/symfony that referenced this issue Feb 27, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | symfony#31490 (comment)
| License       | MIT
| Doc PR        | n/a

Currently, when ones use `TransportNameStamp` the following exception occurs:

```
In Serializer.php line 125:

  [Symfony\Component\Messenger\Exception\MessageDecodingFailedException]
  Could not decode stamp: Cannot create an instance of "Symfony\Component\Messenger\Stamp\TransportNamesStamp" from serialized data because its constructor requires parameter "transports" to be present.

In AbstractNormalizer.php line 384:

  [Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException]
  Cannot create an instance of "Symfony\Component\Messenger\Stamp\TransportNamesStamp" from serialized data because its constructor requires parameter "transports" to be present.

```

This PR renames `TransportNamesStamp` constructor argument in order to match the accesor method (`getTranspdortNames`) so that deserialization work.

I know this is technically a BC break but as far as I can tell the feature can not currently work this way and also named arguments are not covered by Symfony's BC if I remember correctly.
nicolas-grekas added a commit that referenced this issue Mar 6, 2023
…ksaun)

This PR was merged into the 6.2 branch.

Discussion
----------

[Messenger] Fix `TransportNamesStamp` deserialization

| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #49574, #31490 (comment)
| License       | MIT
| Doc PR        | n/a

Currently, when ones use `TransportNameStamp` the following exception can occur if they don't use native PHP serialization:

```
In Serializer.php line 125:

  [Symfony\Component\Messenger\Exception\MessageDecodingFailedException]
  Could not decode stamp: Cannot create an instance of "Symfony\Component\Messenger\Stamp\TransportNamesStamp" from serialized data because its constructor requires parameter "transports" to be present.

In AbstractNormalizer.php line 384:

  [Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException]
  Cannot create an instance of "Symfony\Component\Messenger\Stamp\TransportNamesStamp" from serialized data because its constructor requires parameter "transports" to be present.

```

This PR renames `TransportNamesStamp` constructor argument in order to match the accessor method (`getTransportNames`) so that deserialization works when using the Serializer.

I know this is technically a (small) BC break but Symfony's BC does not cover named arguments if I remember correctly.

Commits
-------

2c7eee0 [Messenger] Fix TransportNamesStamp deserialization
symfony-splitter pushed a commit to symfony/messenger that referenced this issue Mar 6, 2023
| Q             | A
| ------------- | ---
| Branch?       | 6.2
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | symfony/symfony#31490 (comment)
| License       | MIT
| Doc PR        | n/a

Currently, when ones use `TransportNameStamp` the following exception occurs:

```
In Serializer.php line 125:

  [Symfony\Component\Messenger\Exception\MessageDecodingFailedException]
  Could not decode stamp: Cannot create an instance of "Symfony\Component\Messenger\Stamp\TransportNamesStamp" from serialized data because its constructor requires parameter "transports" to be present.

In AbstractNormalizer.php line 384:

  [Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException]
  Cannot create an instance of "Symfony\Component\Messenger\Stamp\TransportNamesStamp" from serialized data because its constructor requires parameter "transports" to be present.

```

This PR renames `TransportNamesStamp` constructor argument in order to match the accesor method (`getTranspdortNames`) so that deserialization work.

I know this is technically a BC break but as far as I can tell the feature can not currently work this way and also named arguments are not covered by Symfony's BC if I remember correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants
0