aaronbullard/dogpile

JSON:API helper library to cleanly import included relationships

v1.0 2019-06-28 14:49 UTC

This package is auto-updated.

Last update: 2022-07-27 04:46:28 UTC


README

Maintainability

JSON:API helper library to cleanly import included relationships

Installation

Library

git clone git@github.com:aaronbullard/dogpile.git

Composer

Install PHP Composer

composer require aaronbullard/dogpile

Testing

composer test

Usage

First, visit https://jsonapi.org for documentation on JSON:API standard. This library attempts to simplify and modularize included related resource objects in api calls.

Ex. GET /posts/1?include=author,comments,comments.author

This library will quickly query and include the related 'author' and 'comments' assosciated with the resource. To do so, requires a few interface implementations.

// Example contoller method for GET /posts/1?include=author,comments,comments.author
public function show(Request $httpRequest): JsonResponse
{
    $postId = $httpRequest->get('postId');
    $includes = explode(',', $httpRequest->get('include')); //['author', 'comments', 'comments.author'];

    // In this example, $post implements the Dogpile\Contracts\Resource interface;
    $post = $this->myPostRepo->find($postId);

    // Example of using Dogpile\ResourceManager::class
    $includedResources = $this->resourceManager->newQuery()
        ->setRelationships($post->relationships())
        ->include(...$includes)
        ->query();

    return new JsonResponse([
        'data' => $post->toJsonapi(), // or however you want to transform your model
        'included' => array_map(function($resource){
            return $resource->toJsonapi();
        }, $includedResources);
    ], 200);
}

How it works

The Dogpile\ResourceManager class contains several ResourceQuery objects for each resource type. When an array of included relationships are provided, the Dogpile\QueryBuilder class will use the ResourceIdentifiers from the RelationshipCollection and query the resources via the RepositoryQuery::findHavingIds() method. Each resource will only be queried once.

Setup

First

Dogpile requires a Dogpile\Contracts\ResourceQuery interface implementation for each resource. As in the above example, you would want an implementation for 'people' (to query authors); and one for 'comments'.

<?php

namespace Dogpile\Contracts;

use Dogpile\Collections\ResourceCollection;

interface ResourceQuery
{
    /**
     * Returns the type of Resource Objects
     *
     * @return void
     */
    public function resourceType(): string;

    /**
     * Queries the resource based on the ids provided
     * 
     * Must return an array of objects implementing the ResourceObject interface
     *
     * @param array $ids
     * @return array
     * @throws NotFoundException
     */
    public function findHavingIds(array $ids): ResourceCollection;
}

Second

Register your ResourceQuery handlers with your Dogpile\ResourceManager

<?php
// some bootstrap file

use Dogpile\ResourceManager;

$conn = $app['database_connection'];

$app[ResouceManager::class] = new ResourceManager(
    new AuthorsResourceQuery($conn),
    new CommentsResourceQuery($conn)
);

Third

Dogpile\Contracts\ResourceQuery::findHavingIds() must return a Dogpile\Collections\ResourceCollection class containing objects that implement the Dogpile\Contracts\Resource interface.

<?php

namespace Dogpile\Contracts;

use Dogpile\Collections\RelationshipCollection;

interface Resource
{
    /**
     * Resource type
     *
     * @return string
     */
    public function type(): string;

    /**
     * Resource id
     *
     * @return string
     */
    public function id(): string;

    /**
     * Returns an array of ResourceIdentifiers
     *
     * @return RelationshipCollection
     */
    public function relationships(): RelationshipCollection;
}

Fourth

As stated in step above, each Resource object must implement the Resource::realtionships() interface which returns a RelationshipCollection class. The Dogpile\Collections\RelationshipCollection class can be implemented as such below.

<?php

// Example data
$jsonapiData = [
    'type' => 'posts',
    'id' => '42',
    'attributes' => [
        'title' => 'Bridge of Death',
        'body' => 'What is the airspeed velocity of an unladed swallow?'
    ],
    'relationships' => [
        'author' => [
            'data' => ['type' => 'people', 'id' => '24']
        ],
        'comments' => [
            'data' => [
                ['type' => 'comments', 'id' => '1'],
                ['type' => 'comments', 'id' => '2'],
                ['type' => 'comments', 'id' => '3']
            ]
        ]
    ]
]

// Example model
use Dogpile\ResourceIdentifier;
use Dogpile\Contracts\Resource;
use Dogpile\Collections\RelationshipCollection;

class SomeObjectModel implements Resource
{
    protected $jsonapiData = [];

    public function __construct(array $jsonapiData)
    {
        $this->jsonapiData = $jsonapiData;
    }

    public function type(): string
    {
        return $this->jsonapiData['type'];
    }

    public function id(): string
    {
        return $this->jsonapiData['id'];
    }

    public function relationships(): RelationshipCollection
    {
        $collection = new RelationshipCollection();

        // add author
        $authorIdent = $this->jsonapiData['relationships']['author']['data'];
        $collection->addRelationships('author', ResourceIdentifier::create($authorIdent['type'], $authorIdent['id']));

        // add comments
        $commentIdentifiers = array_map(function($comment){
            return ResourceIdentifier::create($comment['type'], $comment['id']);
        }, $this->jsonapiData['relationships']['comments']['data']);


        $collection->addRelationships('comments', ...$commentIdentifiers);


        return $collection;
    }
}

For more examples, see the tests: tests\Functional\ResourceManagerTest.php