timacdonald / json-api

A lightweight API resource for Laravel that helps you adhere to the JSON:API standard. Supports sparse fieldsets, compound documents, and more.
576 stars 36 forks source link

How to define a `morphTo` relationship? #53

Open nelson6e65 opened 3 months ago

nelson6e65 commented 3 months ago

As its polymorphic nature, I can't know in advance what resource type it will be.

class AddressResource extends JsonApiResource
{
    public $attributes = [
        'label',
        'geo_point',
    ];

    public $relationships = [
        'owner' => UserResource::class, // But also can be TeamResource, RestaurantResource, etc
    ];
}

Is there a way to check loaded $resource->owner->getTable() to guest the corresponding type? Or maybe a trait included in my model with the method getJsonApiResouce(): $this->resource->owner->getJsonApiResource()

class User extends Model IHasJsonApiResource
{
    use HasFactory;

    /**
     * @return class-string<JsonApiResource>
     */
    public function getJsonApiResouce(): string
    {
        return UserResource::class;
    }

}

Or a similar way of doing it, as User::factory() works.

nelson6e65 commented 3 months ago

I "solved" it by using this in my AppServiceProvider:

JsonApiResource::guessRelationshipResourceUsing(function (string $relationship, JsonApiResource $jsonApiResource) {
    if (!$jsonApiResource->resource instanceof EloquentModel) {
        return null;
    }

    // TODO: Parametrizar estos namespaces.
    $modelsNamespace    = 'App\\Models\\';
    $resourcesNamespace = 'App\\Http\\Resources\\JsonApi\\';

    // How can I detect the underlying model class of an Eloquent relationship? Is this OK?
    $relatedModelClass = get_class($jsonApiResource->resource->{$relationship}()->getModel());

    // This will work only for Models following that structure.
    $relatedResourceClass = str_replace($modelsNamespace, $resourcesNamespace, $relatedModelClass) . 'Resource';

    if (!class_exists($relatedResourceClass)) {
        throw new \BadMethodCallException(
            "Resource class `{$relatedResourceClass}` not found for relationship `{$relationship}`."
        );
    }

    return $relatedResourceClass;
});

With this resources' resolver, it will guest the resource class by relationship model's class instead of relationship name. But I'm not sure if is it consequent enough for all kind of relationships.

App\Models\User <=> App\Http\Resources\JsonApi\UserResource