-
-
Notifications
You must be signed in to change notification settings - Fork 81
Polymorphic Serializer #139
Description
Hello, guys! I'm thinking about possibility to add Polymorphic Serializers in my application.
Preface
Imagine situation when you have activity
resource with actor
relationship which polymorphic and could be represented as user
or org
. Additionally activity
resource has polymorphic collection subjects
which could be represented as article
or comment
resources alltogether in one collection.
I've chosen only 2 resource types in each relationship only for the sake of example simplicity.
Hadcode Solution
$actor = $activity->actor;
if ($actor instanceof User) {
$serializer = new UserSerializer();
} elseif ($actor instanceof Org) {
$serializer = new OrgSerializer();
}
$element = new Resource($actor, $serializer);
It's not great way, but it works... for resource, but not for subjects
polymorphic collection. You don't have a way to analyze resources inside collection on the fly and choose what serializer should be used for the concrete resource.
Possible Solutions
1. Serializers Array
$element = new Collection($activity->subjects, [
Article::class => new ArticleSerializer(),
Comment::class => new CommentSerializer(),
]);
Benefits:
- easy and clean implementation.
Drawbacks:
- instantiates a lot of classes simultaneously even if they wouldn't be in use;
- not reusable.
2. Serializers Array with Closures
This point fixes 2nd one by using closures, but code becomes clumsy, especially if you need to pass some data to instantiate serializers.
$element = new Collection($activity->subjects, [
Article::class => function () use ($request) {
return new ArticleSerializer($request),
},
Comment::class => function () use ($request) {
return new CommentSerializer($request),
},
]);
Benefits:
- not so greedy as solution №1.
Drawbacks:
- clumsy code (IMHO);
- not reusable.
3. Serializer Registry Class
Create some kind of serializer registry (let's say SubjectSerializerRegistry
) which will be called for each resource while iterating over the collection to resolveSerializer
based on $model
class.
$element = new Collection($activity->subjects, new SubjectSerializerRegistry());
SubjectSerializerRegistry
could looks like something similar to this:
class SubjectSerializerRegistry extends AbstractSerializerRegistry
{
protected $serializers = [
Article::class => ArticleSerializer::class,
Comment::class => CommentSerializer::class,
];
}
AbstractSerializerRegistry
could be pretty easy too, but there could be issues with serializers instantiation. I don't have cases when different serializers has different arguments in constructors, but if there will be need of it - code will be bigger and will require each serializer instantiation process described in separate methods inside SubjectSerializerRegistry
.
class AbstractSerializerRegistry
{
public function serializer(string $name): string
{
return $this->serializers[$name];
}
public function resolveSerializer($model)
{
$class = get_class($model);
return new $this->serializer($class)();
}
}
If each model have only one serializer inside an application - this registry could include all possible models and their serializers.
Benefits:
- reusable.
Drawbacks:
- more classes;
- more complicated logic if serializers has complex instantiation process.
Conclusion
These are first sketches. I want to think about this issue a bit more in nearest future... but first of all it would be great to receive a feedback before starting PR implementation. Personally I like 3rd solution most of all.
What do you think about this requirement and possible ways to solve it?