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

possible to register a handler that will compare against abstract classes #577

Closed
Zxurian opened this issue May 26, 2017 · 7 comments
Closed

Comments

@Zxurian
Copy link

Zxurian commented May 26, 2017

so I'm using MyClabs\Enum\Enum to store enumerators in my classes.

class ActionParse extends Enum
class ActionType extends Enum
class MethodAPI extends Enum
...

the problem I'm running into is that if I register a handler like so

        $serializer = SerializerBuilder::create()
            ->configureHandlers(function(HandlerRegistry $registry) {
                $registry->registerHandler('serialization', 'MyCLabs\Enum\Enum', 'json',
                    function($visitor, Enum $object, array $type) {
                        return $object->getValue();
                    }
                );
            })
            ->build()
        ;

serializer doesn't actually intercept them, since the full Class Name doesn't match, even though they all share the same abstract class. While I know I can add a separate handler for each individual enumerator, I'd rather not, as this adds extra maintenance on future scoping.

Is there another way to have serialization listen or interact with objects that have properties of an abstract class?

@goetas
Copy link
Collaborator

goetas commented May 26, 2017

Need to add an pre-serialize listener as:

$serializer = SerializerBuilder::create()
->configureListeners(function(JMS\Serializer\EventDispatcher\EventDispatcher $dispatcher) {
    $dispatcher->addListener('serializer.pre_serialize',
        function(JMS\Serializer\EventDispatcher\PreSerializeEvent $event) {
             if ($event->getObject() instanceof \MyCLabs\Enum\Enum){
                   $event->setType('MyCLabs\Enum\Enum');
             }
        }
    )
})
->configureHandlers(function(HandlerRegistry $registry) {
    $registry->registerHandler('serialization', 'MyCLabs\Enum\Enum', 'json',
        function($visitor, Enum $object, array $type) {
            return $object->getValue();
        }
    );
})
->build()

@goetas goetas closed this as completed May 26, 2017
@Zxurian
Copy link
Author

Zxurian commented May 26, 2017

thanks. Is there a way to match that with deserialization?

I don't see a "PreDeserialization" method in the documentation at http://jmsyst.com/libs/serializer/master/reference/annotations#preserialize

EDIT: found one by examining the JMS/Serializer/EventDispatcher/Events.php class directly. I'll make it work with that.

@Zxurian
Copy link
Author

Zxurian commented May 26, 2017

okay @goetas , thought I had it but could probably use some pointers.

When deserializing, I have

        $serializer = SerializerBuilder::create()
            ->configureListeners(function(EventDispatcher $dispatcher) {
                $dispatcher->addListener(Events::PRE_DESERIALIZE,
                    function(PreDeserializeEvent $event) {
                        if (is_a($event->getType()['name'], 'MyClabs\Enum\Enum', true)) {
                            $event->setType('MyClabs\Enum\Enum');
                        }
                    }
                );
            })
            ->configureHandlers(function(HandlerRegistry $registry) {
                $registry->registerHandler('deserialization', 'MyClabs\Enum\Enum', 'json',
                    function(VisitorInterface $visitor, $value, array $class) {
                        return new $class['name']($value);
                    }
                );
            })
            ->build()
        ;

however by Setting the type on the pre-deserialize event, it tries to instantiate a base class of Enum, when what I want is to instantiate a class of the actual enumerator.

How can I add a deserialization handler for instantiating enumerators, but use the same theory that I only need to apply it once on an abstract class?

@goetas
Copy link
Collaborator

goetas commented May 29, 2017

Different problems have different solutions, different drawbacks and different advantages.

For my current understanding of your usecase, you can do something as:

  1. suppose you have (your enum clas)
namespace App\Enums

use MyCLabs\Enum\Enum;

class UserType extends Enum
{
    // something....
}
  1. your domain class (that is using your enum class)
class User
{
    /** @Type("MyCLabsEnums<'App\Enums\UserType'>") */
    private $type;
}

I will configure the serializer in this way::

$serializer = SerializerBuilder::create()
->configureHandlers(function(HandlerRegistry $registry) {
    // this handles serialization
    $registry->registerHandler('serialization', 'MyCLabsEnums', 'json',
        function($visitor, Enum $object, array $type) {
            return $object->getValue();
        }
    );

    // this handles deserialization
    $registry->registerHandler('deserialization', 'MyCLabsEnums', 'json',
        function($visitor, $data, array $type) {
            $class = $type['params'][0];
            return new $class($data);
        }
    );
})
->build()

this approach is faster and simpler than having listeners (and works for serialization and deserialization), but requires you to set the type of properties for each domain class.

@Zxurian
Copy link
Author

Zxurian commented Jun 5, 2017

@goetas I forgot to come back and thank you.
I've updated my listeners & handlers to use the special ones, which work well.

One last question for you. While I can specify a custom handler for a given object type, is there a way I can specify an array of those?

something similar to

    /** @Type("array<MyCLabsEnums<'App\Enums\UserType'>>") */
    private $type;

I know I can create a listener for an array type, and use @Type("array<'App\Enums\UserType'>") and then individually compare the class within the handler, but I was hoping for a way to specify an array of handlers for an object type.

@goetas
Copy link
Collaborator

goetas commented Jun 6, 2017

I was hoping for a way to specify an array of handlers for an object type.

an array of "handlers" with the current featureset available in the serializer is not possible and makes no sense. the main reason is that you will need some logic to decide which handler needs to be triggered.

Currently there is no "skip-handler" feature, so the first handler has to "handle" the process (or the forward it to an another handler)

@Zxurian
Copy link
Author

Zxurian commented Jun 6, 2017

I might have mis-phrased it.

If you have a property that stores a single object, which you want to use a custom handler instead of the object itself, that can be defined as

/**
 * @var App\Enums\UserType
 * @Type("MyCLabsEnums<'App\Enums\UserType'>")
 */

which will use the MyCLabsEnums handler to process that object type, both in and out.

If you have a property that stores an array of those values, like the below, how would the type declaration work that each entity of the array is an object that is processed through the handler.

/*
 * @var App\Enums\UserType[]
 * @Type(?)
 */

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

2 participants