The efficient and elegant JSON:API 1.1 server library for PHP

Overview

Woohoo Labs. Yin

Latest Version on Packagist Software License Build Status Coverage Status Quality Score Total Downloads Gitter

Woohoo Labs. Yin is a PHP framework which helps you to build beautifully crafted JSON:APIs.

Table of Contents

Introduction

JSON:API specification reached 1.0 on 29th May 2015 and we also believe it is a big day for RESTful APIs as this specification can help you make APIs more robust and future-proof. Woohoo Labs. Yin (named after Yin-Yang) was born to bring efficiency and elegance to your JSON:API servers, while Woohoo Labs. Yang is its client-side counterpart.

Features

  • 100% PSR-7 compatibility
  • 99% JSON:API 1.1 compatibility (approximately)
  • Developed for efficiency and ease of use
  • Extensive documentation and examples
  • Provides Documents and Transformers to fetch resources
  • Provides Hydrators to create and update resources
  • Additional middleware for the easier kickstart and debugging

Why Yin?

Complete JSON:API framework

Woohoo Labs. Yin is a framework-agnostic library which supports the vast majority of the JSON:API 1.1 specification: it provides various capabilities including content negotiation, error handling and pagination, as well as fetching, creation, updating and deleting resources. Although Yin consists of many loosely coupled packages and classes which can be used separately, the framework is most powerful when used in its entirety.

Efficiency

We designed Yin to be as efficient as possible. That's why attributes and relationships are transformed only and if only they are requested. This feature is extremely advantageous when there are a lot of resources to transform or a rarely required transformation is very expensive. Furthermore, as transformers are stateless, the overhead of having a separate model object for each resource is avoided. Additionally, due to statelessness, the overall library works really well with dependency injection.

Supplementary middleware

There is some additional middleware for Woohoo Labs. Yin you might find useful. It can facilitate various tasks like error handling (via transformation of exceptions into JSON:API error responses), dispatching JSON:API-aware controllers or debugging (via syntax checking and validation of requests and responses).

Install

The only thing you need before getting started is Composer.

Install a PSR-7 implementation:

Because Yin requires a PSR-7 implementation (a package which provides the psr/http-message-implementation virtual package), you must install one first. You may use Zend Diactoros or any other library of your preference:

$ composer require zendframework/zend-diactoros

Install Yin:

To install the latest version of this library, run the command below:

$ composer require woohoolabs/yin

Note: The tests and examples won't be downloaded by default. You have to use composer require woohoolabs/yin --prefer-source or clone the repository if you need them.

The latest version of Yin requires PHP 7.1 at least but you can use Yin 2.0.6 for PHP 7.0.

Install the optional dependencies:

If you want to take advantage of request/response validation then you have to also ask for the following dependencies:

$ composer require justinrainbow/json-schema
$ composer require seld/jsonlint

Basic Usage

When using Woohoo Labs. Yin, you will create:

  • Documents and resources in order to map domain objects to JSON:API responses
  • Hydrators in order to transform resources in a POST or PATCH request to domain objects

Furthermore, a JsonApi class will be responsible for the instrumentation, while a PSR-7 compatible JsonApiRequest class provides functionalities you commonly need.

Documents

The following sections will guide you through creating documents for successful responses and creating or building error documents.

Documents for successful responses

For successful requests, you must return information about one or more resources. Woohoo Labs. Yin provides multiple abstract classes that help you to create your own documents for different use cases:

  • AbstractSuccessfulDocument: A generic base document for successful responses
  • AbstractSimpleResourceDocument: A base class for documents about a single, very simple top-level resource
  • AbstractSingleResourceDocument: A base class for documents about a single, more complex top-level resource
  • AbstractCollectionDocument: A base class for documents about a collection of top-level resources

As the AbstractSuccessfulDocument is only useful for special use-cases (e.g. when a document can contain resources of multiple types), we will not cover it here.

The difference between the AbstractSimpleResourceDocument and the AbstractSingleResourceDocument classes is that the first one doesn't need a resource object. For this reason, it is preferable to use the former for only really simple domain objects (like messages), while the latter works better for more complex domain objects (like users or addresses).

Let's first have a quick look at the AbstractSimpleResourceDocument: it has a getResource() abstract method which needs to be implemented when you extend this class. The getResource() method returns the whole transformed resource as an array including the type, id, attributes, and relationships like below:

protected function getResource(): array
{
    return [
        "type"       => "<type>",
        "id"         => "<ID>",
        "attributes" => [
            "key" => "value",
        ],
    ];
}

Please note that AbstractSimpleResourceDocument doesn't support some features out-of-the-box like sparse fieldsets, automatic inclusion of related resources etc. That's why this document type should only be considered as a quick-and-dirty solution, and generally you should choose another, more advanced document type introduced below in the majority of the use cases.

AbstractSingleResourceDocument and AbstractCollectionDocument both need a resource object in order to work, which is a concept introduced in the following sections. For now, it is enough to know that one must be passed for the documents during instantiation. This means that a minimal constructor of your documents should look like this:

public function __construct(MyResource $resource)
{
    parent::__construct($resource);
}

You can of course provide other dependencies for your constructor or completely omit it if you don't need it.

When you extend either AbstractSingleResourceDocument or AbstractCollectionDocument, they both require you to implement the following methods:

/**
 * Provides information about the "jsonapi" member of the current document.
 *
 * The method returns a new JsonApiObject object if this member should be present or null
 * if it should be omitted from the response.
 */
public function getJsonApi(): ?JsonApiObject
{
    return new JsonApiObject("1.1");
}

The description says it very clear: if you want a jsonapi member in your response, then create a new JsonApiObject. Its constructor expects the JSON:API version number and an optional meta object (as an array).

/**
 * Provides information about the "meta" member of the current document.
 *
 * The method returns an array of non-standard meta information about the document. If
 * this array is empty, the member won't appear in the response.
 */
public function getMeta(): array
{
    return [
        "page" => [
            "offset" => $this->object->getOffset(),
            "limit" => $this->object->getLimit(),
            "total" => $this->object->getCount(),
        ]
    ];
}

Documents may also have a "meta" member which can contain any non-standard information. The example above adds information about pagination to the document.

Note that the object property is a variable of any type (in this case it is a hypothetical collection), and this is the main "subject" of the document.

/**
 * Provides information about the "links" member of the current document.
 *
 * The method returns a new DocumentLinks object if you want to provide linkage data
 * for the document or null if the member should be omitted from the response.
 */
public function getLinks(): ?DocumentLinks
{
    return new DocumentLinks(
        "https://example.com/api",
        [
            "self" => new Link("/books/" . $this->getResourceId())
        ]
    );

    /* This is equivalent to the following:
    return DocumentLinks::createWithBaseUri(
        "https://example.com/api",
        [
            "self" => new Link("/books/" . $this->getResourceId())
        ]
    );
}

This time, we want a self link to appear in the document. For this purpose, we utilize the getResourceId() method, which is a shortcut of calling the resource (which is introduced below) to obtain the ID of the primary resource ($this->resource->getId($this->object)).

The only difference between the AbstractSingleResourceDocument and AbstractCollectionDocument is the way they regard the object. The first one regards it as a single domain object while the latter regards it as an iterable collection.

Usage

Documents can be transformed to HTTP responses. The easiest way to achieve this is to use the JsonApi class and choose the appropriate response type. Successful documents support three kinds of responses:

  • normal: All the top-level members can be present in the response (except for the "errors")
  • meta: Only the "jsonapi", "links" and meta top-level member can be present in the response
  • relationship: The specified relationship object will be the primary data of the response

Documents for error responses

An AbstractErrorDocument can be used to create reusable documents for error responses. It also requires the same abstract methods to be implemented as the successful ones, but additionally an addError() method can be used to include error items.

/** @var AbstractErrorDocument $errorDocument */
$errorDocument = new MyErrorDocument();
$errorDocument->addError(new MyError());

There is an ErrorDocument too, which makes it possible to build error responses on-the-fly:

/** @var ErrorDocument $errorDocument */
$errorDocument = new ErrorDocument();
$errorDocument->setJsonApi(new JsonApiObject("1.0"));
$errorDocument->setLinks(ErrorLinks::createWithoutBaseUri()->setAbout("https://example.com/api/errors/404")));
$errorDocument->addError(new MyError());

Resources

Documents for successful responses can contain one or more top-level resources and included resources. That's why resources are responsible for converting domain objects into JSON:API resources and resource identifiers.

Although you are encouraged to create one transformer for each resource type, you also have the ability to define "composite" resources following the Composite design pattern.

Resources must implement the ResourceInterface. In order to facilitate this job, you can also extend the AbstractResource class.

Children of the AbstractResource class need several abstract methods to be implemented - most of them are similar to the ones seen in the Document objects. The following example illustrates a resource dealing with a book domain object and its "authors" and "publisher" relationships.

class BookResource extends AbstractResource
{
    /**
     * @var AuthorResource
     */
    private $authorResource;

    /**
     * @var PublisherResource
     */
    private $publisherResource;

    /**
     * You can type-hint the object property this way.
     * @var array
     */
    protected $object;

    public function __construct(
        AuthorResource $authorResource,
        PublisherResource $publisherResource
    ) {
        $this->authorResource = $authorResource;
        $this->publisherResource = $publisherResource;
    }

    /**
     * Provides information about the "type" member of the current resource.
     *
     * The method returns the type of the current resource.
     *
     * @param array $book
     */
    public function getType($book): string
    {
        return "book";
    }

    /**
     * Provides information about the "id" member of the current resource.
     *
     * The method returns the ID of the current resource which should be a UUID.
     *
     * @param array $book
     */
    public function getId($book): string
    {
        return $this->object["id"];

        // This is equivalent to the following (the $book parameter is used this time instead of $this->object):
        return $book["id"];
    }

    /**
     * Provides information about the "meta" member of the current resource.
     *
     * The method returns an array of non-standard meta information about the resource. If
     * this array is empty, the member won't appear in the response.
     *
     * @param array $book
     */
    public function getMeta($book): array
    {
        return [];
    }

    /**
     * Provides information about the "links" member of the current resource.
     *
     * The method returns a new ResourceLinks object if you want to provide linkage
     * data about the resource or null if it should be omitted from the response.
     *
     * @param array $book
     */
    public function getLinks($book): ?ResourceLinks
    {
        return new ResourceLinks::createWithoutBaseUri()->setSelf(new Link("/books/" . $this->getId($book)));

        // This is equivalent to the following:
        // return new ResourceLinks("", new Link("/books/" . $this->getResourceId()));
    }

    /**
     * Provides information about the "attributes" member of the current resource.
     *
     * The method returns an array where the keys signify the attribute names,
     * while the values are callables receiving the domain object as an argument,
     * and they should return the value of the corresponding attribute.
     *
     * @param array $book
     * @return callable[]
     */
    public function getAttributes($book): array
    {
        return [
            "title" => function () {
                return $this->object["title"];
            },
            "pages" => function () {
                return (int) $this->object["pages"];
            },
        ];

        // This is equivalent to the following (the $book parameter is used this time instead of $this->object):
        return [
            "title" => function (array $book) {
                return $book["title"];
            },
            "pages" => function (array $book) {
                return (int) $book["pages"];
            },
        ];
    }

    /**
     * Returns an array of relationship names which are included in the response by default.
     *
     * @param array $book
     */
    public function getDefaultIncludedRelationships($book): array
    {
        return ["authors"];
    }

    /**
     * Provides information about the "relationships" member of the current resource.
     *
     * The method returns an array where the keys signify the relationship names,
     * while the values are callables receiving the domain object as an argument,
     * and they should return a new relationship instance (to-one or to-many).
     *
     * @param array $book
     * @return callable[]
     */
    public function getRelationships($book): array
    {
        return [
            "authors" => function () {
                return ToManyRelationship::create()
                    ->setLinks(
                        RelationshipLinks::createWithoutBaseUri()->setSelf(new Link("/books/relationships/authors"))
                    )
                    ->setData($this->object["authors"], $this->authorTransformer);
            },
            "publisher" => function () {
                return ToOneRelationship::create()
                    ->setLinks(
                        RelationshipLinks::createWithoutBaseUri()->setSelf(new Link("/books/relationships/publisher"))
                    )
                    ->setData($this->object["publisher"], $this->publisherTransformer);
            },
        ];

        // This is equivalent to the following (the $book parameter is used this time instead of $this->object):

        return [
            "authors" => function (array $book) {
                return ToManyRelationship::create()
                    ->setLinks(
                        RelationshipLinks::createWithoutBaseUri()->setSelf(new Link("/books/relationships/authors"))
                    )
                    ->setData($book["authors"], $this->authorTransformer);
            },
            "publisher" => function ($book) {
                return ToOneRelationship::create()
                    ->setLinks(
                        RelationshipLinks::createWithoutBaseUri()->setSelf(new Link("/books/relationships/publisher"))
                    )
                    ->setData($book["publisher"], $this->publisherTransformer);
            },
        ];
    }
}

Generally, you don't use resources directly. Only documents need them to be able to fill the "data", the "included", and the "relationship" members in the responses.

Hydrators

Hydrators allow us to initialize the properties of a domain object as required by the current HTTP request. This means, when a client wants to create or update a resource, hydrators can help instantiate a domain object, which can then be validated, saved etc.

There are three abstract hydrator classes in Woohoo Labs. Yin:

  • AbstractCreateHydrator: It can be used for requests which create a new resource
  • AbstractUpdateHydrator: It can be used for requests which update an existing resource
  • AbstractHydrator: It can be used for both type of requests

For the sake of brevity, we only introduce the usage of the latter class as it is simply the union of AbstractCreateHydrator and AbstractUpdateHydrator. Let's have a look at an example hydrator:

class BookHydrator extends AbstractHydrator
{
    /**
     * Determines which resource types can be accepted by the hydrator.
     *
     * The method should return an array of acceptable resource types. When such a resource is received for hydration
     * which can't be accepted (its type doesn't match the acceptable types of the hydrator), a ResourceTypeUnacceptable
     * exception will be raised.
     *
     * @return string[]
     */
    protected function getAcceptedTypes(): array
    {
        return ["book"];
    }

    /**
     * Validates a client-generated ID.
     *
     * If the $clientGeneratedId is not a valid ID for the domain object, then
     * the appropriate exception should be thrown: if it is not well-formed then
     * a ClientGeneratedIdNotSupported exception can be raised, if the ID already
     * exists then a ClientGeneratedIdAlreadyExists exception can be thrown.
     *
     * @throws ClientGeneratedIdNotSupported
     * @throws ClientGeneratedIdAlreadyExists
     * @throws Exception
     */
    protected function validateClientGeneratedId(
        string $clientGeneratedId,
        JsonApiRequestInterface $request,
        ExceptionFactoryInterface $exceptionFactory
    ) {
        if ($clientGeneratedId !== null) {
            throw $exceptionFactory->createClientGeneratedIdNotSupportedException($request, $clientGeneratedId);
        }
    }

    /**
     * Produces a new ID for the domain objects.
     *
     * UUID-s are preferred according to the JSON:API specification.
     */
    protected function generateId(): string
    {
        return Uuid::generate();
    }

    /**
     * Sets the given ID for the domain object.
     *
     * The method mutates the domain object and sets the given ID for it.
     * If it is an immutable object or an array the whole, updated domain
     * object can be returned.
     *
     * @param array $book
     * @return mixed|void
     */
    protected function setId($book, string $id)
    {
        $book["id"] = $id;

        return $book;
    }

    /**
     * You can validate the request.
     *
     * @throws JsonApiExceptionInterface
     */
    protected function validateRequest(JsonApiRequestInterface $request): void
    {
        // WARNING! THIS CONDITION CONTRADICTS TO THE SPEC
        if ($request->getAttribute("title") === null) {
            throw new LogicException("The 'title' attribute is required!");
        }
    }

    /**
     * Provides the attribute hydrators.
     *
     * The method returns an array of attribute hydrators, where a hydrator is a key-value pair:
     * the key is the specific attribute name which comes from the request and the value is a
     * callable which hydrates the given attribute.
     * These callables receive the domain object (which will be hydrated), the value of the
     * currently processed attribute, the "data" part of the request and the name of the attribute
     * to be hydrated as their arguments, and they should mutate the state of the domain object.
     * If it is an immutable object or an array (and passing by reference isn't used),
     * the callable should return the domain object.
     *
     * @param array $book
     * @return callable[]
     */
    protected function getAttributeHydrator($book): array
    {
        return [
            "title" => function (array $book, $attribute, $data, $attributeName) {
                $book["title"] = $attribute;

                return $book;
            },
            "pages" => function (array &$book, $attribute, $data, $attributeName) {
                $book["pages"] = $attribute;
            },
        ];
    }

    /**
     * Provides the relationship hydrators.
     *
     * The method returns an array of relationship hydrators, where a hydrator is a key-value pair:
     * the key is the specific relationship name which comes from the request and the value is a
     * callable which hydrate the previous relationship.
     * These callables receive the domain object (which will be hydrated), an object representing the
     * currently processed relationship (it can be a ToOneRelationship or a ToManyRelationship
     * object), the "data" part of the request and the relationship name as their arguments, and
     * they should mutate the state of the domain object.
     * If it is an immutable object or an array (and passing by reference isn't used),
     * the callable should return the domain object.
     *
     * @param mixed $book
     * @return callable[]
     */
    protected function getRelationshipHydrator($book): array
    {
        return [
            "authors" => function (array $book, ToManyRelationship $authors, $data, string $relationshipName) {
                $book["authors"] = BookRepository::getAuthors($authors->getResourceIdentifierIds());

                return $book;
            },
            "publisher" => function (array &$book, ToOneRelationship $publisher, $data, string $relationshipName) {
                $book["publisher"] = BookRepository::getPublisher($publisher->getResourceIdentifier()->getId());
            },
        ];
    }

    /**
     * You can validate the domain object after it has been hydrated from the request.
     * @param mixed $book
     */
    protected function validateDomainObject($book): void
    {
        if (empty($book["authors"])) {
            throw new LogicException("The 'authors' relationship cannot be empty!");
        }
    }
}

According to the book example, the following request:

POST /books HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json

{
  "data": {
    "type": "book",
    "attributes": {
      "title": "Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation",
      "pages": 512
    },
    "relationships": {
      "authors": {
        "data": [
            { "type": "author", "id": "100" },
            { "type": "author", "id": "101" }
        ]
      }
    }
  }
}

will result in the following Book domain object:

Array
(
    [id] => 1
    [title] => Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation
    [pages] => 512
    [authors] => Array
        (
            [0] => Array
                (
                    [id] => 100
                    [name] => Jez Humble
                )
            [1] => Array
                (
                    [id] => 101
                    [name] => David Farley
                )
        )
    [publisher] => Array
        (
            [id] => 12346
            [name] => Addison-Wesley Professional
        )
)

Exceptions

Woohoo Labs. Yin was designed to make error handling as easy and customizable as possible. That's why all the default exceptions extend the JsonApiException class and contain an error document with the appropriate error object(s). That's why if you want to respond with an error document in case of an exception you need to do the following:

try {
    // Do something which results in an exception
} catch (JsonApiExceptionInterface $e) {
    // Get the error document from the exception
    $errorDocument = $e->getErrorDocument();

    // Instantiate the responder - make sure to pass the correct dependencies to it
    $responder = Responder::create($request, $response, $exceptionFactory, $serializer);

    // Create a response from the error document
    $responder->genericError($errorDocument);

    // Emit the HTTP response
    sendResponse($response);
}

To guarantee total customizability, we introduced the concept of Exception Factories. These are classes which create all the exceptions thrown by Woohoo Labs. Yin. As an Exception Factory of your own choice is passed to every transformer and hydrator, you can completely customize what kind of exceptions are thrown.

The default Exception Factory creates children of JsonApiExceptions but you are free to create any JsonApiExceptionInterface exceptions. If you only want to customize the error document or the error objects of your exceptions, just extend the basic Exception class and create your createErrorDocument() or getErrors() methods.

JsonApi class

The JsonApi class is the orchestrator of the whole framework. It is highly recommended to utilize this class if you want to use the entire functionality of Woohoo Labs. Yin. You can find various examples about the usage of it in the examples section or example directory.

JsonApiRequest class

The JsonApiRequest class implements the WoohooLabs\Yin\JsonApi\Request\JsonApiRequestInterface which extends the PSR-7 ServerRequestInterface with some useful, JSON:API related methods. For further information about the available methods, please refer to the documentation of JsonApiRequestInterface.

Advanced Usage

This section guides you through the advanced features of Yin.

Pagination

Yin is able to help you paginate your collection of resources. First, it provides some shortcuts for querying the request query parameters when page-based, offset-based, or cursor-based pagination strategies are used.

Page-based pagination

Yin looks for the page[number] and the page[size] query parameters and parses their value. If any of them is missing then the default page number or size will be used ("1" and "10" in the following example).

$pagination = $jsonApi->getPaginationFactory()->createPageBasedPagination(1, 10);

Fixed page-based pagination

Yin looks for the page[number] query parameter and parses its value. If it is missing then the default page number will be used ("1" in the following example). This strategy can be useful if you do not want to expose the page size at all.

$pagination = $jsonApi->getPaginationFactory()->createFixedPageBasedPagination(1);

Offset-based pagination

Yin looks for the page[offset] and the page[limit] query parameters and parses their value. If any of them is missing then the default offset or limit will be used ("1" and "10" in the following example).

$pagination = $jsonApi->getPaginationFactory()->createOffsetBasedPagination(1, 10);

Cursor-based pagination

Yin looks for the page[cursor] and the page[size] query parameters and parses their value. If any of them is missing then the default cursor or size will be used ("2016-10-01" or 10 in the following example).

$pagination = $jsonApi->getPaginationFactory()->createCursorBasedPagination("2016-10-01", 10);

Fixed cursor-based pagination

Yin looks for the page[cursor] query parameter and parses its value. If it is missing then the default cursor will be used ("2016-10-01" in the following example).

$pagination = $jsonApi->getPaginationFactory()->createFixedCursorBasedPagination("2016-10-01");

Custom pagination

If you need a custom pagination strategy, you may use the JsonApiRequestInterface::getPagination() method which returns an array of pagination parameters.

$paginationParams = $jsonApi->getRequest()->getPagination();

$pagination = new CustomPagination($paginationParams["from"] ?? 1, $paginationParams["to"] ?? 1);

Usage

As soon as you have the appropriate pagination object, you may use them when you fetch your data from a data source:

$users = UserRepository::getUsers($pagination->getPage(), $pagination->getSize());

Pagination links

The JSON:API spec makes it available to provide pagination links for your resource collections. Yin is able to help you in this regard too. You have use the DocumentLinks::setPagination() method when you define links for your documents. It expects the paginated URI and an object implementing the PaginationLinkProviderInterface as seen in the following example:

public function getLinks(): ?DocumentLinks
{
    return DocumentLinks::createWithoutBaseUri()->setPagination("/users", $this->object);
}

To make things even easier, there are some LinkProvider traits in order to ease the development of PaginationLinkProviderInterface implementations of the built-in pagination strategies. For example a collection for the User objects can use the PageBasedPaginationLinkProviderTrait. This way, only three abstract methods has to be implemented:

class UserCollection implements PaginationLinkProviderInterface
{
    use PageBasedPaginationLinkProviderTrait;

    public function getTotalItems(): int
    {
        // ...
    }

    public function getPage(): int
    {
        // ...
    }

    public function getSize(): int
    {
        // ...
    }

    // ...
}

You can find the full example here.

Loading relationship data efficiently

Sometimes it can be beneficial or necessary to fine-tune data retrieval of relationshipS. A possible scenario might be when you have a "to-many" relationship containing gazillion items. If this relationship isn't always needed than you might only want to return a data key of a relationship when the relationship itself is included in the response. This optimization can save you bandwidth by omitting resource linkage.

An example is extracted from the UserResource example class:

public function getRelationships($user): array
{
    return [
        "contacts" => function (array $user) {
            return
                ToManyRelationship::create()
                    ->setData($user["contacts"], $this->contactTransformer)
                    ->omitDataWhenNotIncluded();
        },
    ];
}

By using the omitDataWhenNotIncluded() method, the relationship data will be omitted when the relationship is not included. However, sometimes this optimization is not enough on its own. Even though we can save bandwidth with the prior technique, the relationship still has to be loaded from the data source (probably from a database), because we pass it to the relationship object with the setData() method.

This problem can be mitigated by lazy-loading the relationship. To do so, you have to use setDataAsCallable() method instead of setData():

public function getRelationships($user): array
{
    return [
        "contacts" => function (array $user) {
            return
                ToManyRelationship::create()
                    ->setDataAsCallable(
                        function () use ($user) {
                            // Lazily load contacts from the data source
                            return $user->loadContactsFromDataSource();
                        },
                        $this->contactTransformer
                    )
                    ->omitDataWhenNotIncluded()
                ;
        },
    ];
}

This way, the contacts of a user will only be loaded when the given relationship's data key is present in the response, allowing your API to be as efficient as possible.

Injecting metadata into documents

Metadata can be injected into documents on-the-fly. This comes handy if you want to customize or decorate your responses. For example if you would like to inject a cache ID into the response document, you could use the following:

// Calculate the cache ID
$cacheId = calculateCacheId();

// Respond with "200 Ok" status code along with the book document containing the cache ID in the meta data
return $jsonApi->respond()->ok($document, $book, ["cache_id" => $cacheId]);

Usually, the last argument of each responder method can be used to add meta data to your documents.

Content negotiation

The JSON:API standard specifies some rules about content negotiation. Woohoo Labs. Yin tries to help you enforce them with the RequestValidator class. Let's first create a request validator to see it in action:

$requestValidator = new RequestValidator(new DefaultExceptionFactory(), $includeOriginalMessageInResponse);

In order to customize the exceptions which can be thrown, it is necessary to provide an Exception Factory. On the other hand, the $includeOriginalMessageInResponse argument can be useful in a development environment when you also want to return the original request body that triggered the exception in the error response.

In order to validate whether the current request's Accept and Content-Type headers conform to the JSON:API specification, use this method:

$requestValidator->negotiate($request);

Request/response validation

You can use the following method to check if the query parameters of the current request are in line with the naming rules:

$requestValidator->validateQueryParams($request);

Note: In order to apply the following validations, remember to install the optional dependencies of Yin.

Furthermore, the request body can be validated if it is a well-formed JSON document:

$requestValidator->validateJsonBody($request);

Similarly, responses can be validated too. Let's create a response validator first:

$responseValidator = new ResponseValidator(
    new JsonSerializer(),
    new DefaultExceptionFactory(),
    $includeOriginalMessageInResponse
);

To ensure that the response body is a well-formed JSON document, one can use the following method:

$responseValidator->validateJsonBody($response);

To ensure that the response body is a well-formed JSON:API document, one can use the following method:

$responseValidator->validateJsonApiBody($response);

Validating the responses can be useful in a development environment to find possible bugs early.

Custom serialization

You can configure Yin to serialize responses in a custom way instead of using the default serializer (JsonSerializer) that utilizes the json_encode() function to write JSON:API documents into the response body.

In the majority of the use-cases, the default serializer should be sufficient for your needs, but sometimes you might need more sophistication. Or sometimes you want to do nasty things like returning your JSON:API response as an array without any serialization in case your API endpoint was called "internally".

In order to use a custom serializer, create a class implementing SerializerInterface and setup your JsonApi instance accordingly (pay attention to the last argument):

$jsonApi = new JsonApi(new JsonApiRequest(), new Response(), new DefaultExceptionFactory(), new CustomSerializer());

Custom deserialization

You can configure Yin to deserialize requests in a custom way instead of using the default deserializer (JsonDeserializer) that utilizes the json_decode() function to parse the contents of the request body.

In the majority of the use-cases, the default deserializer should be sufficient for your needs, but sometimes you might need more sophistication. Or sometimes you want to do nasty things like calling your JSON:API endpoints "internally" without converting your request body to JSON format.

In order to use a custom deserializer, create a class implementing DeserializerInterface and setup your JsonApiRequest instance accordingly (pay attention to the last argument):

$request = new JsonApiRequest(ServerRequestFactory::fromGlobals(), new DefaultExceptionFactory(), new CustomDeserializer());

Middleware

If you use a middleware-oriented framework (like Woohoo Labs. Harmony, Zend-Stratigility, Zend-Expressive or Slim Framework 3), you will find the Yin-middleware library quite useful. Read the documentation to learn about its advantages!

Examples

Fetching a single resource

public function getBook(JsonApi $jsonApi): ResponseInterface
{
    // Getting the "id" of the currently requested book
    $id = $jsonApi->getRequest()->getAttribute("id");

    // Retrieving a book domain object with an ID of $id
    $book = BookRepository::getBook($id);

    // Instantiating a book document
    $document = new BookDocument(
        new BookResource(
            new AuthorResource(),
            new PublisherResource()
        )
    );

    // Responding with "200 Ok" status code along with the book document
    return $jsonApi->respond()->ok($document, $book);
}

Fetching a collection of resources

public function getUsers(JsonApi $jsonApi): ResponseInterface
{
    // Extracting pagination information from the request, page = 1, size = 10 if it is missing
    $pagination = $jsonApi->getPaginationFactory()->createPageBasedPagination(1, 10);

    // Fetching a paginated collection of user domain objects
    $users = UserRepository::getUsers($pagination->getPage(), $pagination->getSize());

    // Instantiating a users document
    $document = new UsersDocument(new UserResource(new ContactResource()));

    // Responding with "200 Ok" status code along with the users document
    return $jsonApi->respond()->ok($document, $users);
}

Fetching a relationship

public function getBookRelationships(JsonApi $jsonApi): ResponseInterface
{
    // Getting the "id" of the currently requested book
    $id = $jsonApi->getRequest()->getAttribute("id");

    // Getting the currently requested relationship's name
    $relationshipName = $jsonApi->getRequest()->getAttribute("rel");

    // Retrieving a book domain object with an ID of $id
    $book = BookRepository::getBook($id);

    // Instantiating a book document
    $document = new BookDocument(
        new BookResource(
            new AuthorResource(),
            new PublisherResource(
                new RepresentativeResource()
            )
        )
    );

    // Responding with "200 Ok" status code along with the requested relationship document
    return $jsonApi->respond()->okWithRelationship($relationshipName, $document, $book);
}

Creating a new resource

public function createBook(JsonApi $jsonApi): ResponseInterface
{
    // Hydrating a new book domain object from the request
    $book = $jsonApi->hydrate(new BookHydrator(), []);

    // Saving the newly created book
    // ...

    // Creating the book document to be sent as the response
    $document = new BookDocument(
        new BookResource(
            new AuthorResource(),
            new PublisherResource(
                new RepresentativeResource()
            )
        )
    );

    // Responding with "201 Created" status code along with the book document
    return $jsonApi->respond()->created($document, $book);
}

Updating a resource

public function updateBook(JsonApi $jsonApi): ResponseInterface
{
    // Retrieving a book domain object with an ID of $id
    $id = $jsonApi->getRequest()->getResourceId();
    $book = BookRepository::getBook($id);

    // Hydrating the retrieved book domain object from the request
    $book = $jsonApi->hydrate(new BookHydrator(), $book);

    // Updating the book
    // ...

    // Instantiating the book document
    $document = new BookDocument(
        new BookResource(
            new AuthorResource(),
            new PublisherResource(
                new RepresentativeResource()
            )
        )
    );

    // Responding with "200 Ok" status code along with the book document
    return $jsonApi->respond()->ok($document, $book);
}

Updating a relationship of a resource

public function updateBookRelationship(JsonApi $jsonApi): ResponseInterface
{
    // Checking the name of the currently requested relationship
    $relationshipName = $jsonApi->getRequest()->getAttribute("rel");

    // Retrieving a book domain object with an ID of $id
    $id = $jsonApi->getRequest()->getAttribute("id");
    $book = BookRepository::getBook($id);
    if ($book === null) {
        die("A book with an ID of '$id' can't be found!");
    }

    // Hydrating the retrieved book domain object from the request
    $book = $jsonApi->hydrateRelationship($relationshipName, new BookHydrator(), $book);

    // Instantiating a book document
    $document = new BookDocument(
        new BookResource(
            new AuthorResource(),
            new PublisherResource(
                new RepresentativeResource()
            )
        )
    );

    // Responding with "200 Ok" status code along with the book document
    return $jsonApi->respond()->ok($document, $book);
}

How to try it out

If you want to see how Yin works, have a look at the examples. If docker-compose and make is available on your system, then just run the following commands in order to try out the example API:

cp .env.dist .env      # You can now edit the settings in the .env file
make composer-install  # Install the Composer dependencies
make up                # Start the webserver

And finally, just visit the following URL: localhost:8080. You can even restrict the retrieved fields and relationships via the fields and include parameters as specified by JSON:API.

Example URIs for the book examples:

  • GET /books/1: Fetch a book
  • GET /books/1/relationships/authors: Fetch the authors relationship
  • GET /books/1/relationships/publisher: Fetch the publisher relationship
  • GET /books/1/authors: Fetch the authors of a book
  • POST /books: Create a new book
  • PATCH /books/1: Update a book
  • PATCH /books/1/relationships/author: Update the authors of the book
  • PATCH /books/1/relationships/publisher: Update the publisher of the book

Example URIs for the user examples:

  • GET /users: Fetch users
  • GET /users/1: Fetch a user
  • GET /users/1/relationships/contacts: Fetch the contacts relationship

When you finished your work, simply stop the webserver:

make down

If the prerequisites are not available for you, you have to set up a webserver, and install PHP on your host system as well as the dependencies via Composer.

Integrations

Versioning

This library follows SemVer v2.0.0.

Change Log

Please see CHANGELOG for more information on recent changes.

Testing

Woohoo Labs. Yin has a PHPUnit test suite. To run the tests, run the following command from the project folder:

$ phpunit

Additionally, you may run docker-compose up or make test in order to execute the tests.

Contributing

Please see CONTRIBUTING for details.

Support

Please see SUPPORT for details.

Credits

License

The MIT License (MIT). Please see the License File for more information.

Comments
  • Don't ignore attribute in hydrator if not present in the request

    Don't ignore attribute in hydrator if not present in the request

    The Hydrator ignores attribute if they are not present in the request, the callable is never call.

    This is annoying if an attribute is required for example. I think it should be great to execute all the callable and let the developer decide what it should do, ignore the attribute, set it to null, throw an exception, etc.

    I made a commit on my repo https://github.com/lunika/yin/commit/d6955813bc7bc1839794ff445a75d222902e94d1

    This is yet again a BC break. An example how ti use it :

        protected function getAttributeHydrator($user)
        {
            return [
                'firstname' => function ($user, $attributeValue, $data, $attribute, $isMissing) {
                    if (true === $isMissing) {
                        throw new MissingMandatoryAttributeException($attribute);
                    }
    
                    $user['firstname'] = $attributeValue;
    
                    return $user;
                },
                'lastname' => function ($user, $attributeValue, $data, $attribute, $isMissing) {
                    if (false === $isMissing) {
                        $user['lastname'] = $attributeValue;
                    }
    
                    return $user;
                }
            ];
        }
    
    opened by lunika 15
  • Fix output for ToOneRelationship

    Fix output for ToOneRelationship

    As per json:api docs resource linkage data must be either null in case of single relation or empty array in case of many relation. In case of ToManyRelatioship it was ok but in case of ToOneRelationship it was not so we fixed it This is how it outputs in the current version:

    "relationships": {
        "one": []
        "many: {
             "data": []
        }
    }
    

    And this is how it outputs in the fix:

    "relationships": {
        "one": {
             "data": null
        }
        "many: {
             "data": []
        }
    }
    

    We also added tests for ToManyRelationship in AbstractResourceTransformerTest file:

    1. case when many relationship is empty
    2. case when many relationship has one entry
    opened by brunozoric 13
  • Links for paginations should include filter and sorting parameters

    Links for paginations should include filter and sorting parameters

    Hi, this is basically an improvement. I think it would be really cool if the sorting and filter params would automatically be included in the links of the pagination. Basically a pagination is useless if those parameters are not included. Because those values are already available inside the lib, it should be possible. For sure one can solve this in the environment, but it would be a nice improvement of your lib.

    opened by gfemorris 12
  • Passing the DomainObject to the validateRequest() method of the hydrator class

    Passing the DomainObject to the validateRequest() method of the hydrator class

    I am about to create my own hydrator base class so I can get my hands on the current DomainObject when validating. I was wondering if this made sense, and if so, if you would except a merge request for it?

    So the change would mainly be made here: https://github.com/woohoolabs/yin/blob/master/src/JsonApi/Hydrator/UpdateHydratorTrait.php#L107-L108 I will pass the $domainObject-var to the $this->validateRequest($request); call and update the abstract methods signatures. The same changes would also be made for the create side/hydrator.

    The reason I would like to do this is to pass the current object that contains data that I would like to use when validating the incoming request.

    Any thoughts?

    opened by Ilyes512 10
  • Nullable attributes in database

    Nullable attributes in database

    Hi @kocsismate,

    I have some nullable fields in my database, then the value of $attribute is null in https://github.com/woohoolabs/yin/blob/159c654e1dcf933eb2ac1eea9082366737ec8171/src/JsonApi/Transformer/AbstractResourceTransformer.php#L126

    Is this legitimate to encapse this with a is_null or any check that could avoid Function name must be a string

    Regards,

    opened by waghanza 9
  • Example of how to do multi-level relationship?

    Example of how to do multi-level relationship?

    Do you have any examples of how to do a multi-level includes?

    For example include=comments,comments.authors? (and have it return the right things)

    The problem we're seeing is when you try to do multi-level documents it seems to break on validateRelationships -

    e.g.

    $article = new ArticleDocument(new ArticleResourceTransformer(new CommentResourceTransformer(new AuthorResourceTransformer())));
    

    When CommentResourceTransformer defines a getRelationships($comment) of type "author", it seems to also get validated looking for "comments" in this case and then throws an exception out of "validateRelationships".

    bug 
    opened by mmucklo 8
  • Missing data in response if using omitDataWhenNotIncluded

    Missing data in response if using omitDataWhenNotIncluded

    I have found a potential bug with omitDataWhenNotIncluded and relationships.

    In the examples there is a UserResource, see https://github.com/woohoolabs/yin/blob/4.2.1/examples/User/JsonApi/Resource/UserResource.php#L120

    The user resource has a contacts relationship that data is set with setDataAsCallable() and omitDataWhenNotIncluded().

    If I call the example url /users/1/relationships/contacts I get a response without any data. Only the links are there:

    {
      "links": {
        "self": "\/users\/1\/contacts",
        "related": "\/users\/1\/relationships\/contacts"
      }
    }
    

    If i remove the call of ->omitDataWhenNotIncluded() I get the correct response:

    {
      "links": {
        "self": "\/users\/1\/contacts",
        "related": "\/users\/1\/relationships\/contacts"
      },
      "data": [
        {
          "type": "contacts",
          "id": "100"
        },
        {
          "type": "contacts",
          "id": "101"
        },
        {
          "type": "contacts",
          "id": "102"
        }
      ]
    }
    

    I think omitDataWhenNotIncluded should be ignored if we want to respond with the relationship.

    opened by Art4 7
  • Content-Type and Accept headers not really under domain validation

    Content-Type and Accept headers not really under domain validation

    https://github.com/woohoolabs/yin/blob/eae175718f66ff1c1e77d69847449a310541bd8e/src/JsonApi/Request/JsonApiRequest.php#L136

    I've just found that this line above is not working as it should.

    According to the official documentation:

    preg_match() returns 1 if the pattern matches given subject, 0 if it does not, or FALSE if an error occurred.

    You are testing that the returned value is equal to 0 but it should be equal to 1.

    This mistake makes my API not returning HTTP code 415 and 406 on bad headers as the following images show you:

    image image

    On the first picture, $jsonApi is just a wrapper of your JsonApi object:

    image

    EDIT:

    Your regex is also broken because it should be able to capture application/vnd.api+json only but it does not. I have to edit it and set ^.*application\/vnd\.api\+json\s*(;\s*([A-Za-z0-9]+)\s*=.*?)?$ instead to make matching both application/vnd.api+json and application/vnd.api+json;something=someone

    question 
    opened by samijnih 7
  • Improve test coverage

    Improve test coverage

    Test coverage could be improved for some key classes. See the "Test coverage" column here: https://scrutinizer-ci.com/g/woohoolabs/yin/code-structure/master?elementType=class&orderField=test_coverage&order=asc&changesExpanded=0

    enhancement Hacktoberfest 
    opened by kocsismate 7
  • Injecting a SerializerInterface

    Injecting a SerializerInterface

    Hello Woohoolabs ! How are you ?

    How do you think about instead hard coded json_encode() using a SerializerInterface which could be injected in Document ?

    Through this we could be able to inject any Serializer (i'm thinking about JMSSerializer right now) and use it.

    Thoughts ?

    opened by qpautrat 7
  • Example how to grab a sub-resource.

    Example how to grab a sub-resource.

    It seems you have examples on how to do:

    /books/{id}

    and

    /books/{id}/relationships/authors

    But not:

    /books/{id}/authors

    Unless I'm missing something? Do you natively support this type of call?

    question 
    opened by mmucklo 7
  • Add support for PSR-17 Http factory

    Add support for PSR-17 Http factory

    Instead of passing in a response object the JsonApi class should use a HttpFactoryInterface to generate the response if and when it is needed.

    This will break backwards compatibility and will require minor code changes from library consumers.

    opened by SamMousa 2
  • Feature request: InfoDocument support

    Feature request: InfoDocument support

    I am looking for a way to define a pure "info" document. Easiest would be to create a base class that implements DocumentInterface, but all the WoohooLabs\Yin\JsonApi\Response\Responder::ok<...>() methods require a ResourceDocumentInterface.

    So this left me with two options: create an InfoDocument that implemented ResourceDocumentInterface and somehow work around the methods that you have to implement, or have a method in WoohooLabs\Yin\JsonApi\Response\Responder that accept an InfoDocument extends DocumentInterface, but that also gives of course problems... Because several methods in ResourceDocumentInterface are marked as @internal, this also gives warnings (at least in Symfony).

    So for now I just went for option 1 (create an InfoDocument implements ResourceDocumentInterface in the WoohooLabs\Yin\JsonApi\Schema\Document namespace) but maybe someone else has a better idea?

    I feel this is something that Yin should support to be honest since it's a very legit JSON:API document type.

    https://gist.github.com/Doqnach/b0a000e4f238e91a9085fc7ba57a2719

    enhancement 
    opened by Doqnach 1
  • Hydrating same type (child, parent) related entity

    Hydrating same type (child, parent) related entity

    Hi all,

    I need help with this one.

    When I have json:apiresponse data with relationship of same type (e.g. parent or child - relationship with it-self) and their id's are the same, Hydratoris not capable creating those relationships.

    Response example:

    Page with id:15 has parent with id:14, but its not present in included because it already exists in data array.

      "data": [
        {
          "type": "page",
          "id": "15",
          "attributes": {
            "internal_name": "Company",
            "active": 1,
            "parent_id": 14
          },
          "relationships": {
            "parent": {
              "data": {
                "type": "page",
                "id": "14"
              }
            }
          }
        },
        {
          "type": "page",
          "id": "14",
          "attributes": {
            "internal_name": "About us",
            "parent_id": null
          },
          "relationships": {
            "parent": {
              "data": null
            }
          }
        },
        {
          "type": "page",
          "id": "13",
          "attributes": {
            "internal_name": "Information",
            "parent_id": 1
          },
          "relationships": {
            "parent": {
              "data": {
                "type": "page",
                "id": "1"
              }
            }
          }
        }
      ],
      "included": [
        {
          "type": "page",
          "id": "1",
          "attributes": {
            "internal_name": "General",
            "parent_id": null
          }
        }
      ]
    }
    
    
    opened by Vivenco 2
  • Problem with the resource transformer design pattern and cross relations

    Problem with the resource transformer design pattern and cross relations

    When using cross relations between 2 resources the current design pattern used by the yin resource transformers has the issue of circular dependencies. I think it is necessary to cross relate objects. For example:

    • You have a book object and want to know the author(s)
    • You have an author object and want two know books written by the author

    I solved this in my case outside of the lib with injecting just a container into the transformers which can require the transformers on demand. But in my opinion this should be solved by the lib itself.

    The neomerx lib solves this by mapping the transformers "hard" with the models.. but i liked your modular "loose" approach here.. so maybe a factory patter where one needs to register all transformers and a factory where you can get them whenever you need them could solve the problem.

    opened by gfemorris 9
Releases(4.3.0)
  • 4.3.0(Apr 20, 2021)

  • 4.2.1(Jan 28, 2021)

  • 4.2.0(Jan 24, 2021)

    ADDED:

    • #98: Support for validating top-level members in requests
    • #96: Support for providing a custom schema path to ResponseValidator

    FIXED:

    • #100: Error in createResourceIdInvalidException
    • #97: Incorrect encoding of (pagination) query parameters
    Source code(tar.gz)
    Source code(zip)
  • 4.1.2(Jun 23, 2020)

  • 4.1.1(Dec 30, 2019)

  • 4.1.0(Dec 28, 2019)

  • 4.0.1(Jun 5, 2019)

  • 4.0.0(Apr 19, 2019)

    This release is the same as 4.0.0-beta2. The full change set is the following:

    ADDED:

    • JSON:API 1.1 related features:
      • Partial support for Profiles
      • Support for type links in errors
    • Resources can also use the $object property to access the object which is being transformed
    • Separate classes for the different types of links instead of the generic Links class
      • DocumentLinks
      • ResourceLinks
      • RelationshipLinks
      • ErrorLinks
    • New capabilities related to pagination:
      • #70: Better support for query parameters in pagination links
      • PaginationFactory class to decouple Pagination class instantiation from the request
      • JsonApi::getPaginationFactory() method to make it easier to retrieve the Pagination Factory
      • FixedCursorBasedPagination class which preserves the original behaviour of the changed CursorBasedPagination
      • FixedCursorBasedPaginationProviderTrait for using it in connection with FixedCursorBasedPagination
      • FixedPageBasedPaginationLinkProviderTrait for using it in connection with FixedPageBasedPagination
    • New capabilities related to relationships:
      • ResponseInterface::hasToOneRelationship() to determine if a specific To-One relationship exists
      • ResponseInterface::hasToManyRelationship() to determine if a specific To-Many relationship exists

    CHANGED:

    • Updated justinrainbow/json-schema to v5.2
    • Documents should use the $object property instead of $domainObject (BREAKING CHANGE)
    • Links are used strictly according to the spec (BREAKING CHANGE):
      • AbstractSuccessfulDocument::getLinks() returns ?DocumentLinks instead of ?Links
      • AbstractErrorDocument::getLinks() returns ?DocumentLinks instead of ?Links
      • ErrorDocument::getLinks() returns ?DocumentLinks instead of ?Links
      • ErrorDocument::setLinks() expects a parameter of ?DocumentLinks type instead of ?Links
      • AbstractResource::getLinks() returns ?ResourceLinks instead of Links
      • AbstractRelationship::getLinks() returns ?RelationshipLinks instead of Links
      • AbstractRelationship::setLinks() expects a parameter of ?RelationshipLinks type instead of Links
      • Error::getLinks() returns ErrorLinks instead of Links
      • Error::setLinks() expects a parameter of ErrorLinks instead of Links
    • Improvements related to JsonApiExceptionInterface (BREAKING CHANGE):
      • JsonApiExceptionInterface now extends Throwable
      • JsonApiExceptionInterface::getErrorDocument() must return an ErrorDocumentInterface instead of an AbstractErrorDocument
    • Improvements related to pagination (BREAKING CHANGE):
      • A $defaultSize constructor parameter was added to CursorBasedPagination to define a default value for the page[size] query parameter
      • Properties and methods of FixedPageBasedPagination became non-nullable
      • Properties and methods of OffsetBasedPagination became non-nullable
      • Properties and methods of PageBasedPagination became non-nullable
      • Methods of PaginationLinkProviderInterface expect a second parameter with a $queryString name
    • Setting the status code and Content-Type header of the JSON:API response is done by the Responder by default instead of Serializers (BREAKING CHANGE):
      • The Responder class sets the status code and the Content-Type header of the response, while custom Serializers can override them optionally
      • SerializerInterface::serialize() only accepts two arguments instead of 3 as the $responseCode parameter was removed
      • JsonSerializer does not set the Content-Type header and the status code of the response anymore
    • Improvements related to relationships (BREAKING CHANGE):
      • ResponseInterface::getToOneRelationship() throws an exception instead of returning null if the relationship doesn't exist
      • ResponseInterface::getToManyRelationship() throws an exception instead of returning null if the relationship doesn't exist
    • The TransformerTrait::fromSqlToIso8601Time() method expects a ?DateTimeZone as its second argument instead of string (BREAKING CHANGE)

    REMOVED:

    • The generic Links class (BREAKING CHANGE)
    • Methods related to pagination class instantiation were removed from RequestInterface (BREAKING CHANGE):
      • RequestInterface::getFixedPageBasedPagination()
      • RequestInterface::getPageBasedPagination()
      • RequestInterface::getOffsetBasedPagination()
      • RequestInterface::getCursorBasedPagination()
    • Various deprecated classes (BREAKING CHANGE):
      • WoohooLabs\Yin\JsonApi\Document\AbstractCollectionDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractCollectionDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractErrorDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractErrorDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSimpleResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSimpleResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSingleResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSingleResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSuccessfulResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSuccessfulResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\ErrorDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\ErrorDocument instead
      • WoohooLabs\Yin\JsonApi\Transformer\AbstractResourceTransformer: use WoohooLabs\Yin\JsonApi\Schema\Resource\AbstractResource instead
      • WoohooLabs\Yin\JsonApi\Transformer\ResourceTransformerInterface: use WoohooLabs\Yin\JsonApi\Schema\Resource\ResourceInterface instead
      • WoohooLabs\Yin\JsonApi\Schema\Error: use WoohooLabs\Yin\JsonApi\Schema\Error\Error instead
      • WoohooLabs\Yin\JsonApi\Schema\ErrorSource: use WoohooLabs\Yin\JsonApi\Schema\Error\ErrorSource instead
      • WoohooLabs\Yin\JsonApi\Exception\JsonApiException: use WoohooLabs\Yin\JsonApi\Exception\AbstractJsonApiException instead
      • WoohooLabs\Yin\JsonApi\Request\Request: use WoohooLabs\Yin\JsonApi\Request\JsonApiRequest instead
      • WoohooLabs\Yin\JsonApi\Request\RequestInterface: use WoohooLabs\Yin\JsonApi\Request\JsonApiRequestInterface instead
      • WoohooLabs\Yin\JsonApi\Schema\Link: use WoohooLabs\Yin\JsonApi\Schema\Link\Link instead
      • WoohooLabs\Yin\JsonApi\Schema\LinkObject: use WoohooLabs\Yin\JsonApi\Schema\Link\LinkObject instead
    • Various deprecated methods (BREAKING CHANGE):
      • AbstractErrorDocument::getResponseCode() (use AbstractErrorDocument::getStatusCode() instead)
      • RequestValidator::lintBody() (use RequestValidator::validateJsonBody() instead)
      • ResponseValidator::lintBody() (use ResponseValidator::validateJsonBody() instead)
      • ResponseValidator::validateBody() (ResponseValidator::validateJsonApiBody())
    • The deprecated AbstractRelationship::omitWhenNotIncluded() method (BREAKING CHANGE): use AbstractRelationship::omitDataWhenNotIncluded()

    FIXED:

    • Issues with 0 and non-numeric values when using built-in pagination objects (PageBasedPagination, FixedPageBasedPagination, OffsetBasedPagination)
    • Various issues found by static analysis
    • Query parameters of pagination links were not encoded properly
    • The page and filter query parameters must have an array value as per the spec
    • Instead of returning null, an exception is thrown when a non-existent relationship is fetched
    Source code(tar.gz)
    Source code(zip)
  • 3.1.1(Apr 19, 2019)

    DEPRECATED:

    • ToOneRelationship::omitWhenNotIncluded(): Use ToOneRelationship::omitDataWhenNotIncluded()
    • ToManyRelationship::omitWhenNotIncluded(): Use ToManyRelationship::omitDataWhenNotIncluded()
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0-beta2(Apr 9, 2019)

    REMOVED:

    • The deprecated AbstractRelationship::omitWhenNotIncluded() method (BREAKING CHANGE): use AbstractRelationship::omitDataWhenNotIncluded()

    FIXED:

    • Regression with uninitialized Resource properties
    • Regression with the source pointer of the ResourceTypeUnacceptable error
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0-beta1(Jan 19, 2019)

    ADDED:

    • JSON:API 1.1 related features:
      • Partial support for Profiles
      • Support for type links in errors
    • Resources can also use the $object property to access the object which is being transformed
    • Separate classes for the different types of links instead of the generic Links class
      • DocumentLinks
      • ResourceLinks
      • RelationshipLinks
      • ErrorLinks
    • New capabilities related to pagination:
      • #70: Better support for query parameters in pagination links
      • PaginationFactory class to decouple Pagination class instantiation from the request
      • JsonApi::getPaginationFactory() method to make it easier to retrieve the Pagination Factory
      • FixedCursorBasedPagination class which preserves the original behaviour of the changed CursorBasedPagination
      • FixedCursorBasedPaginationProviderTrait for using it in connection with FixedCursorBasedPagination
      • FixedPageBasedPaginationLinkProviderTrait for using it in connection with FixedPageBasedPagination
    • New capabilities related to relationships:
      • ResponseInterface::hasToOneRelationship() to determine if a specific To-One relationship exists
      • ResponseInterface::hasToManyRelationship() to determine if a specific To-Many relationship exists

    CHANGED:

    • Updated justinrainbow/json-schema to v5.2
    • Documents should use the $object property instead of $domainObject (BREAKING CHANGE)
    • Links are used strictly according to the spec (BREAKING CHANGE):
      • AbstractSuccessfulDocument::getLinks() returns ?DocumentLinks instead of ?Links
      • AbstractErrorDocument::getLinks() returns ?DocumentLinks instead of ?Links
      • ErrorDocument::getLinks() returns ?DocumentLinks instead of ?Links
      • ErrorDocument::setLinks() expects a parameter of ?DocumentLinks type instead of ?Links
      • AbstractResource::getLinks() returns ?ResourceLinks instead of Links
      • AbstractRelationship::getLinks() returns ?RelationshipLinks instead of Links
      • AbstractRelationship::setLinks() expects a parameter of ?RelationshipLinks type instead of Links
      • Error::getLinks() returns ErrorLinks instead of Links
      • Error::setLinks() expects a parameter of ErrorLinks instead of Links
    • Improvements related to JsonApiExceptionInterface (BREAKING CHANGE):
      • JsonApiExceptionInterface now extends Throwable
      • JsonApiExceptionInterface::getErrorDocument() must return an ErrorDocumentInterface instead of an AbstractErrorDocument
    • Improvements related to pagination (BREAKING CHANGE):
      • A $defaultSize constructor parameter was added to CursorBasedPagination to define a default value for the page[size] query parameter
      • Properties and methods of FixedPageBasedPagination became non-nullable
      • Properties and methods of OffsetBasedPagination became non-nullable
      • Properties and methods of PageBasedPagination became non-nullable
      • Methods of PaginationLinkProviderInterface expect a second parameter with a $queryString name
    • Setting the status code and Content-Type header of the JSON:API response is done by the Responder by default instead of Serializers (BREAKING CHANGE):
      • The Responder class sets the status code and the Content-Type header of the response, while custom Serializers can override them optionally
      • SerializerInterface::serialize() only accepts two arguments instead of 3 as the $responseCode parameter was removed
      • JsonSerializer does not set the Content-Type header and the status code of the response anymore
    • Improvements related to relationships (BREAKING CHANGE):
      • ResponseInterface::getToOneRelationship() throws an exception instead of returning null if the relationship doesn't exist
      • ResponseInterface::getToManyRelationship() throws an exception instead of returning null if the relationship doesn't exist
    • The TransformerTrait::fromSqlToIso8601Time() method expects a ?DateTimeZone as its second argument instead of string (BREAKING CHANGE)

    REMOVED:

    • The generic Links class (BREAKING CHANGE)
    • Methods related to pagination class instantiation were removed from RequestInterface (BREAKING CHANGE):
      • RequestInterface::getFixedPageBasedPagination()
      • RequestInterface::getPageBasedPagination()
      • RequestInterface::getOffsetBasedPagination()
      • RequestInterface::getCursorBasedPagination()
    • Various deprecated classes (BREAKING CHANGE):
      • WoohooLabs\Yin\JsonApi\Document\AbstractCollectionDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractCollectionDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractErrorDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractErrorDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSimpleResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSimpleResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSingleResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSingleResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSuccessfulResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSuccessfulResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\ErrorDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\ErrorDocument instead
      • WoohooLabs\Yin\JsonApi\Transformer\AbstractResourceTransformer: use WoohooLabs\Yin\JsonApi\Schema\Resource\AbstractResource instead
      • WoohooLabs\Yin\JsonApi\Transformer\ResourceTransformerInterface: use WoohooLabs\Yin\JsonApi\Schema\Resource\ResourceInterface instead
      • WoohooLabs\Yin\JsonApi\Schema\Error: use WoohooLabs\Yin\JsonApi\Schema\Error\Error instead
      • WoohooLabs\Yin\JsonApi\Schema\ErrorSource: use WoohooLabs\Yin\JsonApi\Schema\Error\ErrorSource instead
      • WoohooLabs\Yin\JsonApi\Exception\JsonApiException: use WoohooLabs\Yin\JsonApi\Exception\AbstractJsonApiException instead
      • WoohooLabs\Yin\JsonApi\Request\Request: use WoohooLabs\Yin\JsonApi\Request\JsonApiRequest instead
      • WoohooLabs\Yin\JsonApi\Request\RequestInterface: use WoohooLabs\Yin\JsonApi\Request\JsonApiRequestInterface instead
      • WoohooLabs\Yin\JsonApi\Schema\Link: use WoohooLabs\Yin\JsonApi\Schema\Link\Link instead
      • WoohooLabs\Yin\JsonApi\Schema\LinkObject: use WoohooLabs\Yin\JsonApi\Schema\Link\LinkObject instead
    • Various deprecated methods (BREAKING CHANGE):
      • AbstractErrorDocument::getResponseCode() (use AbstractErrorDocument::getStatusCode() instead)
      • RequestValidator::lintBody() (use RequestValidator::validateJsonBody() instead)
      • ResponseValidator::lintBody() (use ResponseValidator::validateJsonBody() instead)
      • ResponseValidator::validateBody() (ResponseValidator::validateJsonApiBody())

    FIXED:

    • Issues with 0 and non-numeric values when using built-in pagination objects (PageBasedPagination, FixedPageBasedPagination, OffsetBasedPagination)
    • Various issues found by static analysis
    • Query parameters of pagination links were not encoded properly
    • The page and filter query parameters must have an array value as per the spec
    • Instead of returning null, an exception is thrown when a non-existent relationship is fetched
    Source code(tar.gz)
    Source code(zip)
  • 3.1.0(Jan 17, 2019)

    This is a release with several deprecations in order to ensure forward compatibility with Yin 4.0.

    DEPRECATED:

    • Classes related to Documents:
      • WoohooLabs\Yin\JsonApi\Document\AbstractCollectionDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractCollectionDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractErrorDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractErrorDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSimpleResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSimpleResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSingleResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSingleResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\AbstractSuccessfulResourceDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\AbstractSuccessfulResourceDocument instead
      • WoohooLabs\Yin\JsonApi\Document\ErrorDocument: use WoohooLabs\Yin\JsonApi\Schema\Document\ErrorDocument instead
    • Classes related to Resources:
      • WoohooLabs\Yin\JsonApi\Transformer\AbstractResourceTransformer: use WoohooLabs\Yin\JsonApi\Schema\Resource\AbstractResource instead
      • WoohooLabs\Yin\JsonApi\Transformer\ResourceTransformerInterface: use WoohooLabs\Yin\JsonApi\Schema\Resource\ResourceInterface instead
    • Classes related to Errors and Exceptions:
      • WoohooLabs\Yin\JsonApi\Schema\Error: use WoohooLabs\Yin\JsonApi\Schema\Error\Error instead
      • WoohooLabs\Yin\JsonApi\Schema\ErrorSource: use WoohooLabs\Yin\JsonApi\Schema\Error\ErrorSource instead
      • WoohooLabs\Yin\JsonApi\Exception\JsonApiException: use WoohooLabs\Yin\JsonApi\Exception\AbstractJsonApiException instead
    • Classes related to the Request:
      • WoohooLabs\Yin\JsonApi\Request\Request: use WoohooLabs\Yin\JsonApi\Request\JsonApiRequest instead
      • WoohooLabs\Yin\JsonApi\Request\RequestInterface: use WoohooLabs\Yin\JsonApi\Request\JsonApiRequestInterface instead
    • Classes related to Links:
      • WoohooLabs\Yin\JsonApi\Schema\Link: use WoohooLabs\Yin\JsonApi\Schema\Link\Link instead
      • WoohooLabs\Yin\JsonApi\Schema\LinkObject: use WoohooLabs\Yin\JsonApi\Schema\Link\LinkObject instead
    • The following methods:
      • AbstractErrorDocument::getResponseCode(): use AbstractErrorDocument::getStatusCode() instead
      • RequestValidator::lintBody(): use RequestValidator::validateJsonBody() instead
      • ResponseValidator::lintBody(): use ResponseValidator::validateJsonBody() instead
      • ResponseValidator::validateBody(): use ResponseValidator::validateJsonApiBody() instead
    Source code(tar.gz)
    Source code(zip)
  • 3.0.2(Feb 12, 2018)

  • 2.0.6(Feb 6, 2018)

  • 3.0.1(Feb 2, 2018)

    CHANGED:

    • Return the included array even when it is empty if the include parameter is supplied
    • PHPUnit 7.0 is minimally required to run tests

    FIXED:

    • #66: Bug in request header validation
    • #68: Fix fatal error when resource ID is not a string
    Source code(tar.gz)
    Source code(zip)
  • 2.0.5(Jan 31, 2018)

  • 3.0.0(Nov 21, 2017)

    CHANGED:

    • Increased minimum PHP version requirement to 7.1
    • ExceptionFactoryInterface methods must return JsonApiExceptionInterface (BREAKING)
    • AbstractDocument::getJsonApi() and AbstractDocument::getLinks() return types must be declared (BREAKING)
    • ResourceTransformerInterface::getLinks() return type must be declared (BREAKING)

    REMOVED:

    • TransformerTrait::toBool() and TransformerTrait::toInt() methods

    FIXED:

    • Some minor type declaration-related issues
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-beta1(Sep 14, 2017)

    CHANGED:

    • Increased minimum PHP version requirement to 7.1
    • ExceptionFactoryInterface methods must return JsonApiExceptionInterface (BREAKING)
    • AbstractDocument::getJsonApi() and AbstractDocument::getLinks() return types must be declared (BREAKING)
    • ResourceTransformerInterface::getLinks() return type must be declared (BREAKING)

    REMOVED:

    • TransformerTrait::toBool() and TransformerTrait::toInt() methods

    FIXED:

    • Some minor type declaration-related issues
    Source code(tar.gz)
    Source code(zip)
  • 2.0.4(Sep 13, 2017)

  • 2.0.3(Aug 24, 2017)

  • 2.0.2(Jun 13, 2017)

  • 2.0.1(Apr 18, 2017)

    ADDED:

    • Possibility to configure the displayed time zone when using TransformerTrait::toIso8601Date() and TransformerTrait::toIso8601DateTime()

    CHANGED:

    • Updated JSON:API schema to the latest version
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Mar 10, 2017)

    ADDED:

    • Responder::okWithRelationship() and Responder::createdWithRelationship()
    • #58: Allow to set options to the json_encode method
    • Support for custom deserializers
    • #57: Support for validating the request during hydration

    CHANGED:

    • Yin now requires PHP 7.0 at least
    • Documents, Transformers, Hydrators, Serializers and Exceptions must be type hinted strictly (BREAKING)
    • #51: Decouple AbstractSuccessfulDocument from Serializer and Response (BREAKING)
    • Renamed JsonApi schema object to JsonApiObject in order to avoid ambiguities (BREAKING)
    • Renamed DefaultSerializer to JsonSerializer (BREAKING)
    • Renamed some methods of ExceptionFactoryInterface which didn't end with Exception (e.g. createRelationshipNotExists() to createRelationshipNotExistsException()) (BREAKING)
    • Hydrators must implement the validateRequest() method (BREAKING)
    • HydratorTrait::getAcceptedType() was renamed to HydratorTrait::getAcceptedTypes() and it should always return an array even if the hydrator can only accept one resource type (BREAKING)

    REMOVED:

    • RelationshipResponder::okWithMeta() method (BREAKING)
    • JsonApi::respondWithRelationship() method (BREAKING)

    FIXED:

    • #59: Resource schema validating
    • Minor problems with request/response validators
    • Minor bug fixes
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0-rc1(Mar 6, 2017)

  • 1.0.6(Feb 28, 2017)

  • 2.0.0-beta2(Feb 15, 2017)

    ADDED:

    • Support for custom deserializers

    CHANGED:

    • Renamed DefaultSerializer to JsonSerializer (BREAKING)
    • Renamed some methods of ExceptionFactoryInterface which didn't end with Exception (e.g. createRelationshipNotExists() to createRelationshipNotExistsException()) (BREAKING)

    FIXED:

    • #59: Resource schema validating
    • Minor problems with request/response validators
    Source code(tar.gz)
    Source code(zip)
  • 1.0.5(Feb 11, 2017)

    ADDED:

    • Possibility to configure the DefaultSerializer

    FIXED:

    • AbstractSimpleResourceDocument::getRelationshipContent() didn't return any value
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0-beta1(Feb 9, 2017)

    ADDED:

    • Responder::okWithRelationship() and Responder::createdWithRelationship()
    • Possibility to configure the DefaultSerializer

    CHANGED:

    • Yin now requires PHP 7.0 at least
    • Documents, Transformers, Hydrators, Serializers and Exceptions must be type hinted strictly (BREAKING)
    • #51: Decouple AbstractSuccessfulDocument from Serializer and Response (BREAKING)
    • Renamed JsonApi to JsonApiObject in order to avoid ambiguities (BREAKING)

    REMOVED:

    • RelationshipResponder::okWithMeta() method (BREAKING)
    • JsonApi::respondWithRelationship() method (BREAKING)

    FIXED:

    • Minor bug fixes
    Source code(tar.gz)
    Source code(zip)
  • 1.0.4(Feb 2, 2017)

  • 1.0.3(Dec 21, 2016)

    ADDED:

    • Better support for "about" links

    FIXED:

    • Error status codes are now represented as string as per the spec
    • TransformerTrait() datetime transformer methods identify the ISO-8601 format correctly
    Source code(tar.gz)
    Source code(zip)
Owner
Woohoo Labs.
Woohoo Labs.
A simple PHP package for sending messages to Slack, with a focus on ease of use and elegant syntax.

Slack for PHP | A simple PHP package for sending messages to Slack with incoming webhooks, focused on ease-of-use and elegant syntax. supports: PHP 7.

null 128 Nov 28, 2022
Simple and effective multi-format Web API Server to host your PHP API as Pragmatic REST and / or RESTful API

Luracast Restler ![Gitter](https://badges.gitter.im/Join Chat.svg) Version 3.0 Release Candidate 5 Restler is a simple and effective multi-format Web

Luracast 1.4k Dec 14, 2022
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder

PoP PoP is a monorepo containing several projects. The GraphQL API for WordPress plugin GraphQL API for WordPress is a forward-looking and powerful Gr

Leonardo Losoviz 265 Jan 7, 2023
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder

PoP PoP is a monorepo containing several projects. The GraphQL API for WordPress plugin GraphQL API for WordPress is a forward-looking and powerful Gr

Leonardo Losoviz 265 Jan 7, 2023
Best resources restful api for developers (with JSON:API standar specification design)

List API Best resources restful api for developers (with JSON:API standar specification design). API Resource Endpoint Name Resource Description Al Qu

Noval 2 Jan 18, 2022
JSON:API serializer for PHP resources

kwai-jsonapi A JSON:API serializer for PHP classes using PHP attributes. Currently, this library has no support for links. Installation composer requi

Franky Braem 1 Jan 19, 2022
Laravel API 文档生成器,可以将基于 Laravel 项目的项目代码,自动生成 json 或 md 格式的描述文件。

Thresh Laravel API 文档生成器,可以将基于 Laravel 项目的项目代码,自动生成 json 或 md 格式的描述文件。 安装 $ composer require telstatic/thresh -vvv 功能 生成 Markdown 文档 生成 Postman 配置文件 生

静止 5 Jul 12, 2021
JSON API (jsonapi.org) package for Laravel applications.

cloudcreativity/laravel-json-api Status This package has now been rewritten, substantially improved and released as the laravel-json-api/laravel packa

Cloud Creativity 753 Dec 28, 2022
Read and write OpenAPI 3.0.x YAML and JSON files and make the content accessible in PHP objects.

php-openapi Read and write OpenAPI 3.0.x YAML and JSON files and make the content accessible in PHP objects. It also provides a CLI tool for validatin

Carsten Brandt 399 Dec 23, 2022
Quickly and easily expose Doctrine entities as REST resource endpoints with the use of simple configuration with annotations, yaml, json or a PHP array.

Drest Dress up doctrine entities and expose them as REST resources This library allows you to quickly annotate your doctrine entities into restful res

Lee Davis 88 Nov 5, 2022
微信支付 API v3 的 PHP Library,同时也支持 API v2

微信支付 WeChatPay OpenAPI SDK [A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP 概览 微信支付 APIv2&APIv3 的Guzzle HttpClient封装组合, APIv2已内置请求数据签名及XML转换器,应

null 275 Jan 5, 2023
The server component of API Platform: hypermedia and GraphQL APIs in minutes

API Platform Core API Platform Core is an easy to use and powerful system to create hypermedia-driven REST and GraphQL APIs. It is a component of the

API Platform 2.2k Dec 27, 2022
PHP implementation of JSON schema. Fork of the http://jsonschemaphpv.sourceforge.net/ project

JSON Schema for PHP A PHP Implementation for validating JSON Structures against a given Schema with support for Schemas of Draft-3 or Draft-4. Feature

Justin Rainbow 3.4k Dec 26, 2022
pedre-response is a standard structure of json response

PedreResponse It's very important to use same structure for responses in large projects that PedreResponse package can do it for you. PedreResponse is

Pedram Rezaei 2 Dec 22, 2021
A robust JSON decoder/encoder with support for schema validation.

A robust wrapper for json_encode()/json_decode() that normalizes their behavior across PHP versions, throws meaningful exceptions and supports schema validation by default.

Bernhard Schussek 356 Dec 21, 2022
Like FormRequests, but for validating against a json-schema

JSON Schema Request Laravels Form Request Validation for JSON Schema documents Installation composer require wt-health/laravel-json-schema-request Us

Webtools Health 1 Feb 3, 2022
ohmyga's API Server

ohmyga's API Server

O's API 9 Oct 8, 2022
Petstore is a sample API that simulates a pet shop management server🐶

Petstore is a sample API that simulates a pet shop management server. The API allows you to access Petstore data using a set of individual calls

Wilmer Rodríguez S 1 Jan 8, 2022
Syntax to query GraphQL through URL params, which grants a GraphQL API the capability to be cached on the server.

Field Query Syntax to query GraphQL through URL params, which grants a GraphQL API the capability to be cached on the server. Install Via Composer com

PoP 4 Jan 7, 2022