-
-
Notifications
You must be signed in to change notification settings - Fork 83
SerializerInterface violates LSP #115
Comments
Interesting, thanks for pointing this out. I'm open to exploring this. I'll play around with it when I have some time. |
One issue (inconvenience?) I can see, at least in the context of Laravel and other environments with a similar dependency injection container: What if I want to inject dependencies into a serializer to aid with the serialization? Where previously something like this would be possible: class GroupSerializer extends AbstractSerializer {
protected $type = 'groups';
public function __construct(TranslatorInterface $translator) {
$this->translator = $translator;
}
public function getAttributes($model) {
return ['name' => $this->translator->trans($model->name)];
}
}
$resource = new Resource($group, $container->make('GroupSerializer')); With a ResourceInterface I guess you'd have to either inject deps alongside the model: class GroupResource implements ResourceInterface {
protected $type = 'groups';
public function __construct(Group $group, TranslatorInterface $translator) {
$this->group = $group;
$this->translator = $translator;
}
public function getAttributes() {
return ['name' => $this->translator->trans($this->group->name)];
}
}
$resource = $container->make('GroupResource', ['group' => $group]); or inject them statically (this is what Laravel's Eloquent ORM does for its models): class GroupResource implements ResourceInterface {
protected $type = 'groups';
public function __construct(Group $group) {
$this->group = $group;
}
public function getAttributes() {
return ['name' => static::$translator->trans($this->group->name)];
}
}
GroupResource::setTranslator($translator);
$resource = new GroupResource($group); |
I imagine a factory might mitigate the incovenience: class TranslatingGroupResourceFactory
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function createResourceFor(Group $group): ResourceInterface
{
return new TranslatingGroupResource($group, $this->translator);
}
} The factory itself is created in the DI, but the GroupResource instances are created at runtime. |
🤦♂️ I always forget about factories... that's a good solution. Well, I think I'm all for this then. Feel free to send a PR if you're up for it? Otherwise, I'll try and tackle it sometime in the next few weeks. |
@f3ath and let me know if you are, so we don't double up :) |
@tobscure i don't think i have enough free resources to do anything meaningful (at least on this week). So please go ahead. I would be happy to watch the progress though ;) |
Alright! I've got something working. I had to rewrite most of the document-construction logic — which was a bit of a mess anyway — and now it's a lot simpler (and hopefully more performant). Still want to spend a bit more time thinking to make sure I've got it right... but otherwise I'll clean up and push to a new branch soon so we can start testing. |
Aaaand just squeezed out an extra drop of performance and made the algorithm even simpler. Did some basic benchmarks. New version is 2x faster for a simple document, 3x faster for a more complex one. |
@tobscure Is any of this online? I'm quite interested - I have built a similar tool in Ruby at work in the last few months. ;) |
@franzliedke tomorrow! |
Hi @tobscure
First of all, thanks for your efforts making this library. I was looking at the php libraries implementing jsonapi in order to pick one for one of my projects. There are a few of them worth trying, including yours. Unfortunately, it looks like that all of them suffer from the same issue.
Issue
They all have the similar idea of serializers - the logic which converts a business entity (model) into a json data object. And these serializers look pretty much similarly: they have a number of methods like
getId($model)
,getAttributes($model)
which take the entity as a parameter. These methods know about their$model
s internal structure and they use this knowledge to extract the id, attributes or whatever is needed to build the json. Consider the example from readme:PostSerializer
can only deal with$post
models. ThegetAttributes()
call will fail on everything else except a properly structured post object. ButSerializerInterface
does not restrict the model to anything particular. WhatSerializerInterface
says is basically: you give me anything (@param mixed $model
) and I tell you its attributes. Therefore,PostSerializer
breaks the contract given bySerializerInterface
. It means violation of Liskov Substitution Principle.Solution (well, a kind of)
Instead of these pseudo-universal serializers, we should have something like
ResourceInterface
:And a use-case would be like
Since your library version is still 0.*, it is not too late to make backward-incompatible changes to fix this issue. Please let me know what you think.
The text was updated successfully, but these errors were encountered: