40

JSON-API: How to create an API in PHP? | TSH.io

 4 years ago
source link: https://tsh.io/blog/json-api-how-to-create-api-in-php/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JSON-API: How to create an API in PHP?

Dear developers, have you ever worked on a new API? Those of you who have probably know that before writing the first endpoint, you need to determine the format in which the request parameters will be passed and the response returned. For inexperienced developers that might be a bother but I just might have the solution to this annoying problem. It’s JSON-API – a set of ready-made rules and recommendations, specifying all elements of the endpoint interface. So if you want to learn how to create an API in PHP with JSON API, follow me!

Why do I need JSON-API?

Since there is no single right solution for the API parameter schema and responses, any approach that coherently and consistently formulates all input and output data is sufficient. Meeting these conditions will ensure easy integration of customer applications and reduce the number of errors during development.

Having said that, if you decide to use your own structure, you should think about a few things. In terms of requests, it will be filters, pagination and sorting. As for the responses – formatting returned objects fields, nesting of related objects, metadata (e.g. pagination) and error messages.

It’s common practice to start implementation without comprehensive interface planning and then adding further elements as the work progresses and new needs arise. However, this strategy can lead to losing consistency and creating a pattern too difficult to automatically process in the client application.

If you don’t have your own proven and refined approach to this issue, it is worth applying a ready-made solution. I suggest looking for ideas used by a wider group of programmers – this way you will surely avoid all the aforementioned problems, save time and focus on the implementation of business logic and other priority tasks. And that’s where JSON API comes in.

What is JSON-API?

Citing the standard documentation, JSON API is a specification of how to send requests to read or modify a resource, and how the server responds to those requests. The data transport format is JSON, and requests and responses are sent as:

Content-type: application/vnd.api+json

💡 Read more: How to build an API? A Developer’s Guide to API Platform

Request to the server according to JSON-API

So let’s start by building requests, focusing on resource collection (GET requests). In this case, all relevant data is transmitted in the URL. The JSON API standard specifies how to name query string parameters. The parameters are divided into five groups:

  1. Pagination
  2. Sorting
  3. Filters
  4. Range of returned fields
  5. Including related objects

For the purposes of this article, we will use a hypothetical endpoint to download a list of products: 

GET /products

1. Pagination

Pagination is the simplest of the above-mentioned parameters. It typically consists of two values, specifying the number of elements per one “page” and which “page” is to be returned. There are various strategies based on page number and size, or limit and offset. In terms of pagination, JSON API only specifies that the parameter should be an array called page, leaving you the freedom to choose the strategy. Here’s the JSON API example:

GET /products?page[limit]=20&page[offset]=60 # return products 61-80

GET /products?page[size]=20&page[number]=2   # return products 41-60

2. Sorting

Sorting results in accordance with the JSON API recommendations should be carried out using the sort parameter. The parameter values should refer to the field names of returned objects (although this is not a strict requirement, you’re allowed to bend this rule a little). Example – sort by title:

GET /products?sort=name

It is possible to sort by more than one attribute. In this case, the subsequent attributes should be separated by a comma. On the server’s side, sorting should be carried out in order from the first attribute to the last – in the example below you should sort first by the date of publication and then by title.

GET /products?sort=publish_date,name

The default sorting direction is ascending (ASC). In order to change the direction to descending (DESC), any of the attributes should be preceded by a minus sign, as presented below:

GET /products?sort=-publish_date,name

The specification also provides sorting by fields from related objects. In this case, you should provide a path consisting of attribute names that lead to the desired field, separated by periods. Example – sorting by product category name:

GET /products?sort=category.name

3. Filters

Filtering results is probably the most complex issue here. But fear not – JSON API leaves a lot of freedom, requiring only that all filtering data be sent in an array parameter called (unsurprisingly) a filter. For example, filters can be passed in the request as follows:

GET /products?filter[name]=SSD drive

Similarly to sorting, filtering can apply to the associated object attribute. In a situation like that, you can use the same syntax:

GET /products?filter[category.name]=Storage

We can assume that the basic filters are strict filters, i.e. they require a perfect match of the filter value to the attribute. If you need to filter the range of values (e.g. minimum and/or maximum value for a numeric attribute) you can add suffix __min or __max (double underscore is necessary so that the server is able to distinguish the range filter from the field ending with the word min or max). Let’s look at an example:

GET /products?filter[price__min]=100&filter[price__max]=500

Depending on your domain requirements, you can define additional suffixes and corresponding filtering methods, e.g. __in, __like etc. Remember to maintain consistency – a specific suffix (as well as its absence) must cause the same behaviour in every filter. For example, if it is assumed that filters without a suffix work on the exact match principle, there can be no case of a filter that works differently.

4. Range of returned fields

The range of returned fields can be used to reduce the amount of data in the server response. For example, when downloading a list of items, you may not always need to download the date when the item was added to the offer, or availability in various stores – you may only need the name and price. In such cases, you can explicitly provide in the request which fields are to be returned in the response. For this purpose, the JSON API reserves a parameter called fields. The syntax is as follows – fields should be listed after the decimal point. Fields from the object to which the request relates should be explicitly mentioned:

GET /products?fields=name,price,publish_date

Fields from related objects should be listed with the path leading to the object. The following example specifies that a product with id = 1 should be returned, together with the name of the category and the name and shipment price available for the packaging in which the product is delivered.

GET /products/1?fields[category]=name&fields[package.shipping]=name,price

5. Including related objects

I’ve already mentioned including related objects several times in this article (just checking if you were paying attention 😏). You need to explicitly indicate in the request which of the objects associated with the main object should be included in the response. A good example is returning products along with customer feedback. This allows you to reduce the number of queries to the server, which always positively influences the performance of the application. According to the JSON API rules, the request parameter for this purpose is called include. Its value is a list of paths to the desired associations, separated by a comma:

GET /products?include=reviews,category

Just like in previous cases, reference may be made to further associations:

GET /products?include=reviews.author

💡 Read more: How to create a framework-agnostic application in PHP 

how to create API in PHP JSON meme fridy the 13th character Jason Voorhees

oopsie…

Server responses according to JSON-API

The content of the GET request response must be saved in JSON format. The structure of the response is strictly defined. The first level of response must contain at least one of the keys: data, errors, meta. The meta key can be used to return any additional information. An example of its use can be information about the pagination of results:

{
"meta": {
"total": 200,
"limit": 20,
"offset": 60
}
}

The errors key should contain messages from potential errors. JSON API defines this key as an array of objects. Each of them may have several of the defined keys. The most important of them are status, code, title. Example:

{
"errors": [
{
"status": 404,
"code": 0,
"title": "Product not found"
}
]
}

The data key is intended for storing the main content of the response, i.e. in most cases the content of objects returned by an endpoint. This response section is most closely defined in the JSON API. The returned resource (single object or array of objects) must appear directly under the data key. Each resource must be returned in the form of an object containing type and id keys.

The first key must contain a unique, global name for the object type, which is immutable within the entire API for each object (e.g. product). The second key is the identifier – it can successfully be the primary key from the database. Other allowed keys are attributes, relationships, links, meta. From this set, attributes and relationships are the most commonly used for transporting other object fields and information about related objects. Below you can find an example using the most important of the keys I’ve just described:

GET /products

{
"data": {
"type": "product",
"id": 1,
"attributes": {
"name": "SSD drive",
"price": 199
},
"relationships": {
"category": {
"data": {
"type": "category",
"id": 1
}
},
"package": {
"data": {
"type": "package",
"id": 5
}
}
}
}
}

Please note that the relationships section doesn’t show related object fields, only types and identifiers. This information is sufficient for the client application to automatically read further objects.

Related objects can also be returned in the same request. For the server to add these objects to the response, they must be listed in the include parameter. As a result, the JSON document with the response will receive another key called included, which will contain the full data of the indicated objects (or only selected fields if they are specified by the fieldsparameter). Example:

GET /products?include=reviews,category

{
"data": {
"type": "product",
"id": 1,
"attributes": {
"name": "SSD drive",
"price": 199
},
"relationships": {
"category": {
"data": {
"type": "category",
"id": 1
}
},
"reviews": [
{
"data": {
"type": "review",
"id": 5
}
},
{
"data": {
"type": "review",
"id": 6
}
}
]
}
},
"included": [
{
"type": "category",
"id": 1,
"attributes": {
"name": "Storage"
}
},
{
"type": "review",
"id": 5,
"attributes": {
"comment": "This drive is awesome!",
"rank": 5
}
},
{
"type": "review",
"id": 6,
"attributes": {
"comment": "Could work faster",
"rank": 3
}
}
]
}

As the example shows, the included section contains data about the objects previously listed in the product relationships section.

💡 Read more: PHP version 7.4 is here! A quick overview of new features

when third party API sends xmlinstead of json meme guy with a facepalm

JSON-API in PHP applications

The tobyzerner/json-api-php project allows quick implementation of the JSON API standard in PHP applications. You can quickly build responses in accordance with all the rules described above. Installation with the help of a composer:

composer require tobscure/json-api

Request parameters

To parse the parameters contained in the request, you can use the Parameters class. It provides methods of access to the parameters (described in the first part) defined by the JSON API standard:

<?php
declare(strict_types=1);
use Tobscure\JsonApi\Parameters;
// ...
$parameters = new Parameters($_GET);
$filter = $parameters->getFilter();
$include = $parameters->getInclude($available);
$fields = $parameters->getFields();
$sort = $parameters->getSort($available);
$page['limit'] = $parameters->getLimit($max);
$page['offset'] = $parameters->getOffset($default);

Arrays with acceptable names should be provided for include and sort parameters (parameters from URL will be automatically validated for correctness). You can specify the maximum limit and default offset for pagination.

Response building

To build a response in the controller of a given endpoint, all you have to do is create a collection object or a single resource, specifying in the constructor objects for serialization (e.g. Doctrine entities) and a serializer able to serialize a given object:

<?php
declare(strict_types=1);
use Tobscure\JsonApi\Resource;
use Tobscure\JsonApi\Collection;
// ...
// build collection of resources
$products = $this->getProducts();
$collection = (new Collection($products, new ProductSerializer()));
// build single resource
$category = $this->getCategory($categoryId);
$resource = (new Resource($category, new CategorySerializer()));

At this stage, you can indicate which of the related objects should be added to the response…

<?php
declare(strict_types=1);
// ...
$collection ->with(['category', 'reviews']);

…and what fields of objects should be included in the response:

<?php
declare(strict_types=1);
// ...
$collection->fields(['product' => ['name', 'price']]);

Then build a document and serialize it:

<?php
declare(strict_types=1);
use Tobscure\JsonApi\Document;
// ...
// build documents with resources
$document = new Document($collection);
// return response
return new Response(json_encode($document));

As you can see, the Document object enriches the response with metadata:

<?php
declare(strict_types=1);
// ...
$document->addMeta('total', count($products));
$document->addMeta('limit', $page['limit']);
$document->addMeta('offset', $page['offset']);

Serialization

Resource and Collection objects require you to point a serializer for each resource that you upload. At the same time, an abstract class is available, which can be used as a base to create your own serializers. In the minimum version, it is only necessary to implement a method that returns the attributes of a serialized object:

<?php
declare(strict_types=1);
use Tobscure\JsonApi\AbstractSerializer;
class ProductSerializer extends AbstractSerializer
{
protected $type = 'product';
public function getAttributes(Product $product, array $fields = null)
{
return [
'name' => $product->getName(),
'price' => $product->getPrice(),
];
}
}

The $type property is used in the type key in the endpoint response. By default, the id field uses the $id of the serialized object, but you can add your own serializer method that will be used to retrieve the id:

<?php
declare(strict_types=1);
use Tobscure\JsonApi\AbstractSerializer;
class ProductSerializer extends AbstractSerializer
{
// ...
public function getId(Product $product): int
{
return $product->calcId();
}
}

For bundle mechanisms to be able to add related objects to responses, the serializer must have methods by which these objects can be retrieved and serialized. The names of these methods must be exactly the same as the names of the attached relations. For example, for product-related reviews and categories:

<?php
declare(strict_types=1);
use Tobscure\JsonApi\AbstractSerializer;
use Tobscure\JsonApi\Collection;
use Tobscure\JsonApi\Relationship;
use Tobscure\JsonApi\Resource;
class ProductSerializer extends AbstractSerializer
{
// ...
public function reviews(Product $product): Relationship
{
$collection = new Collection(
$product->getReviews(),
new ReviewSerializer()
);
return new Relationship($collection);
}
public function category(Product $product): Relationship
{
$resource = new Resource(
$product->getCategory(),
new CategorySerializer()
);
return new Relationship($resource);
}
}

The mechanism above may work recursively, for example by adding the user($review) method in the ReviewSerializerclass, you can attach reviews with user data to the product.

💡 Read more: A few tips on how to keep your PHP code style under control

Need more information on JSON-API standard?

The information I provided in this article only addresses some of the most important issues regarding the JSON API standard. So when it comes to basics, you are already a pro! If you are interested in creating API in PHP, documentation of JSON API and practical examples are available on the project home page and Symfony bundle can be found on JSON-API Github. Good luck! 👍

Interested in developing microservices? 🤔 Make sure to check out our State of Microservices 2020 report – based on opinions of 650+ microservice experts!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK