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

HUGE [BUG]: Custom subscribing handlers get overwritten in JMS\SerializerBundle\DependencyInjection\Compiler\CustomHandlersPass::sortAndFlattenHandlersList() and thus never called! #853

Open
yyaremenko opened this issue May 11, 2021 · 4 comments

Comments

@yyaremenko
Copy link

yyaremenko commented May 11, 2021

Q A
Bug report? yes
Feature request? no
BC Break report? don't know
RFC? no

Steps required to reproduce the problem

(Symfony 5.x)

  1. Create a new custom subscribing handler for DateTime, which only serializes and does that in json:
<?php

namespace App\Handler\Serializer;

use DateTime;
use JMS\Serializer\Context;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\JsonSerializationVisitor;

class DateTimeSerializationHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods(): array
    {
        return [
            [
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => DateTime::class,
                'method' => 'dateTimeToJson',
            ],
        ];
    }

    public function dateTimeToJson(
        JsonSerializationVisitor $visitor,
        DateTime $date,
        array $type,
        Context $context
    ) {
        die('HERE');
    }
}

  1. Register this handler: to services.yaml add
    App\Handler\Serializer\DateTimeSerializationHandler:
        tags:
            - { name: jms_serializer.subscribing_handler }
  1. Create an Entity which has a DateTime field, expose that field for JMS Serializer
  2. Serialize such Entity with JMS Serializer

Expected Result

The code dies with 'HERE';

Actual Result

Nothing happens.

Reason.

  1. Go to
    JMS\SerializerBundle\DependencyInjection\Compiler\CustomHandlersPass::sortAndFlattenHandlersList()
  2. Dump the incoming handlers which are the subject of our interest:
  • direction: serialization
  • type: dateTime
  • format: json

For this, add the following code to the very beginning of the sortAndFlattenHandlersList() method

foreach ($allHandlers as $hData) {
    if (GraphNavigatorInterface::DIRECTION_SERIALIZATION !== $hData[0]) {
        continue;
    }

    if (\DateTime::class !== $hData[1]) {
        continue;
    }

    if ('json' !== $hData[2]) {
        continue;
    }

    var_dump($hData);
}

Most probably, you'll get

array(6) {
  [0] =>
  int(1)
  [1] =>
  string(8) "DateTime"
  [2] =>
  string(4) "json"
  [3] =>
  int(0)
  [4] =>
  string(51) "App\Handler\Serializer\DateTimeSerializationHandler" <-- THAT'S COOL, OUR CUSTOM HANDLER IS HERE
  [5] =>
  string(14) "dateTimeToJson"
}
array(6) {
  [0] =>
  int(1)
  [1] =>
  string(8) "DateTime"
  [2] =>
  string(4) "json"
  [3] =>
  int(0)
  [4] =>
  string(31) "jms_serializer.datetime_handler"
  [5] =>
  string(17) "serializeDateTime"
}

As you can see, our custom handler is there. Fine.
4. In the same method, right before the return statement, dump the handlers once again to check if our custom handler is still there

var_dump($handlers[GraphNavigatorInterface::DIRECTION_SERIALIZATION][\DateTime::class]['json']);

And what you get is

array(2) {
  [0] =>
  string(31) "jms_serializer.datetime_handler"
  [1] =>
  string(17) "serializeDateTime"
}

OUR CUSTOM HANDLER IS GONE.
Why? Obviously because during 'flattering' in the sortAndFlattenHandlersList() method what happens is

$handlers[$direction][$type][$format] = [$service, $method];

So if we have two - three - whatever number of handlers for a specific direction + type + format (in our case, 'serialize' + 'DateTime' + 'json'), onlly the last handler in the list gets registered, each previous handler gets rewritten by the next one in the list (and, in our case, our custom handler is rewritten by jms_serializer.datetime_handler).

@nnmer
Copy link

nnmer commented May 25, 2021

I guess I'm facing the same issue.

UPD,

@yyaremenko But there is a workaround, which was not obvious:

public static function getSubscribingMethods(): array
  {
      return [
          [
              'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
              'format' => 'json',
              'type' => 'DateTime',
              'method' => 'serializeDateTimeToJson',
              'priority'=>-1                             <<<<========= Add this
          ],
          [
              'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
              'format' => 'json',
              'type' => 'DateTime',
              'method' => 'deserializeDateTimeToJson',
              'priority'=>-1                             <<<<========= Add this
          ],
      ];
  }

@yyaremenko
Copy link
Author

@nnmer Yes, I thought of this workaround, thank you. I still believe though the bundle must work without any workarounds out of the box, or at least have corresponding notice in the documentation. Also, if you have several custom subscribing methods for the same specific direction + type + format, with different priorities --> only one of such handlers will be taken into consideration.. You'll see no warnings / notices and will waste a ton of time figuring out why only one of your handlers works.

@yyaremenko yyaremenko changed the title HUGE BUG: Custom subscribing handlers get overwritten in JMS\SerializerBundle\DependencyInjection\Compiler\CustomHandlersPass::sortAndFlattenHandlersList() and thus never called! HUGE [BUG]: Custom subscribing handlers get overwritten in JMS\SerializerBundle\DependencyInjection\Compiler\CustomHandlersPass::sortAndFlattenHandlersList() and thus never called! May 30, 2021
@yyaremenko
Copy link
Author

@goetas may I have your opinion on this one please?

@goetas
Copy link
Collaborator

goetas commented Jul 26, 2021

I think that the best thing to do here is to have built-in handlers to run with a lower priority than the user defined one, in this way there is no need to use the workaround.

@yyaremenko fee free to send a pull request for such change

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

3 participants