tobyzerner / json-api-php

JSON-API (http://jsonapi.org) responses in PHP.
MIT License
436 stars 79 forks source link

Add resource polymorphism #143

Open antonkomarev opened 5 years ago

antonkomarev commented 5 years ago

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

  1. Create VehicleSerializerRegistry which will describe mappings between serializable object & serializer.
class VehicleSerializerRegistry extends \Tobscure\JsonApi\AbstractSerializerRegistry
{
    protected $serializers = [
        Car::class => CarSerializer::class,
        Bike::class => BikeSerializer::class,
    ];
}
  1. In GarageSerializer use PolymorphicCollection instead of Collection and pass VehicleSerializerRegistry to 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

  1. Create OwnerSerializerRegistry which will describe mappings between serializable object & serializer.
class OwnerSerializerRegistry extends \Tobscure\JsonApi\AbstractSerializerRegistry
{
    protected $serializers = [
        Person::class => PersonSerializer::class,
        Organization::class => OrganizationSerializer::class,
    ];
}
  1. In GarageSerializer use PolymorphicResource instead of Resource and pass OwnerSerializerRegistry to 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.

antonkomarev commented 5 years ago

@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.