json-api-php
json-api-php copied to clipboard
Add resource polymorphism
Implements #139
Problem
Sometimes resources require to have polymorphic relations.
Garage has many vehicles in it. Vehicle could has it's own type Car or Bike.
Garage has one owner. Owner could be of type Person or Organization.
{
"data": {
"type": "Garage",
"id": "garage-1",
"relationships": {
"owner": {
"data": {
"type": "Organization",
"id": "organization-1"
}
},
"vehicles": {
"data": [
{
"type": "Car",
"id": "car-1"
},
{
"type": "Bike",
"id": "bike-1"
}
]
}
}
}
}
Solution
Solution is pretty simple. We are providing SerializerRegistry instead of concrete Serializer to polymorphic collection or resource and it will try to find serializer which mapped to this serializable object.
Collections
- Create
VehicleSerializerRegistrywhich will describe mappings between serializable object & serializer.
class VehicleSerializerRegistry extends \Tobscure\JsonApi\AbstractSerializerRegistry
{
protected $serializers = [
Car::class => CarSerializer::class,
Bike::class => BikeSerializer::class,
];
}
- In
GarageSerializerusePolymorphicCollectioninstead ofCollectionand passVehicleSerializerRegistryto it.
class GarageSerializer extends \Tobscure\JsonApi\AbstractSerializer
{
public function vehicles(Garage $garage): Relationship
{
$element = new \Tobscure\JsonApi\PolymorphicCollection(
$garage->getVehicles(),
new VehicleSerializerRegistry()
);
return new Relationship($element);
}
}
Resource
- Create
OwnerSerializerRegistrywhich will describe mappings between serializable object & serializer.
class OwnerSerializerRegistry extends \Tobscure\JsonApi\AbstractSerializerRegistry
{
protected $serializers = [
Person::class => PersonSerializer::class,
Organization::class => OrganizationSerializer::class,
];
}
- In
GarageSerializerusePolymorphicResourceinstead ofResourceand passOwnerSerializerRegistryto it.
class GarageSerializer extends \Tobscure\JsonApi\AbstractSerializer
{
public function owner(Garage $garage): Relationship
{
$element = new \Tobscure\JsonApi\PolymorphicResource(
$garage->getOwner(),
new OwnerSerializerRegistry()
);
return new Relationship($element);
}
}
In closing
Be careful with polymorphism, because there are some edge cases which were revealed in a discussion with Michael Hibay on discuss.
@tobscure this code is working in 2 of my projects on production for a one year. It's not fully covered with tests, but I've added some major ones. I will be glad to have a feedback about it and ready for discussion about methods and class naming.