API Bundle.

Add simple API request/response handling for a Symfony API, recommended for micro-services / internal services.

Article image for: API Bundle

Controller, Transformer and Request Helper.

Introduction
Controller, Transformer and Request Helper
Event Subscribers
Controller Argument Resolvers

Github Print

ApiController

An abstract ApiController can be inherited to provide a suitable base to work from. This extends the Symfony controller and adds wrappers to the various helpers including the response factory and argument helper.

The base methods are:

  • created() - return a 201 response with the specified binding
  • updated() - return a 200 response with the specified binding
  • deleted() - return a 204 response with a '{message: "..."}' payload
  • noContent() - returns a 204 with no content

The following pass through methods are available:

  • collection(CollectionType $type) - return a JSON response for a collection of objects
  • item(ObjectType $type) - return a JSON response for a single item
  • paginate(PagerfantaType $type) - return a JSON response with a paginated result set
  • includes(Request $request) - returns an array of all requested objects to be included
  • orderBy(Request $request) - returns an array of all requested fields to order results by
  • page(Request $request, int $default = 1) - returns the current page from the request
  • perPage(Request $request, int $default = null, int $max = null) - returns the number of results per page
  • limit(Request $request, int $default = null, int $max = null) - returns the limit for the results
  • offset(Request $request, int $limit = null) - returns the offset if not using pages
  • nullOrValue(ParameterBag $request, array $fields, string $class = null) - returns null or a value

Transforming Responses

A base ApiController is included that exposes Fractal and the various helpers of this bundle. To use Fractal to transform an object to an array, create an appropriate type using either one of the provided types, or implement your own. The available types are:

  • ObjectType - for single items
  • CollectionType - specifically for Somnambulist/Collection
  • IterableType - for other iterable collections of items
  • PagerfantaType - specifically for Pagerfanta paginators

There are helper methods for: collection, item and paginate that are type-hinted for specific types. The types act as a bridge to the Fractal resource types, allowing meta data, includes and other requirements to be passed through consistently. Due to the use of specific types, the required arguments are enforced. To use other types, directly access the converter: ->responseConverter()->toJson(<type>) and pass the type object for conversion to a JSON response.

<?php
use Somnambulist\Bundles\ApiBundle\Response\Types\ObjectType;
use Somnambulist\Bundles\ApiBundle\Tests\Support\Stubs\MyEntityTransformer;

class MyEntityController extends \Somnambulist\Bundles\ApiBundle\Controllers\ApiController
{

    public function __invoke()
    {
        $entity  = new stdClass(); // fetch an entity from somewhere
        $binding = new ObjectType($entity, MyEntityTransformer::class);

        return $this->item($binding);
    }
}

The type encapsulates the resource, the transformer to apply (class name or instance, classes will be resolved via the container, provided the transformers are public services) and assorted other meta data and any includes to process.

To add includes or meta data call the withXXX method:

<?php
use Somnambulist\Bundles\ApiBundle\Response\Types\ObjectType;
use Somnambulist\Bundles\ApiBundle\Tests\Support\Stubs\MyEntityTransformer;

(new ObjectType(new stdClass(), MyEntityTransformer::class))
    ->withIncludes('child', 'child.child', '...')
    ->withMeta(['array' => ['of' => 'meta data']])
;

meta data will be placed in an array key named meta. You should avoid exporting a similar key at the root level of your transformer.

By default only collections will be exported under a specific key in the JSON response (defaults to data). You can set this either at construction time, or by using withKey() to use some other word. Note: this should be a valid JSON object property.

For paginators the URL must be specified when creating the binding. It may be changed using withURL once the binding has been created. The provided URL will be used to generate the pagination links. In addition to the pagination meta data, various X-API-Pagination headers are added along with a Link header for the next / previous results.

The ResponseConverter can be accessed to generate an array instead of a JsonResponse object. This allows that array to be further transformed, instead of having to JSON decode/encode from the response.

The transformer can be as simple or complex as you like. See the example in the tests or the documentation for Fractal Just remember that transformers should be configured as public services so that they are available to SamJs wrapper. Several default transformers are provided for very simple types:

  • ArrayTransformer - previously called PassThroughTransformer, used for collections of arrays
  • StdClassTransformer - casts stdClass objects to arrays
  • ReadModelTransformer - if using the somnambulist/read-models library; calls toArray on the model

The serializer can be changed by either re-defining the ResponseConverter service or by calling setSerializer before creating a response. This allows alternative encoding strategies to be used e.g. JSON Data API.

Request Handler

The request handler settings, allow changing the default values used in the RequestArgumentHelper. These are used for limiting the maximum page size of paginated results, or setting a hard limit to avoid an API endpoint returning too many results.

The defaults can be overridden at runtime by specifying the default / max as needed. The one exception is page. This always returns 1 if not set or out of bounds.

The expected request vars are:

  • include
  • order
  • page
  • per_page
  • limit

limit is to fetch only that many results and not a paginated set. page and per_page are typically used together.

include is for requesting data to be included in the response. It should be a comma separated list of include options. These can then be passed to a view transformer / query command for loading additional data. Typically this would only be used on view / GET type requests.

order is for specifying how the results should be ordered. It is a comma separated string of valid field names. If a field is prefixed with a - (hyphen/minus sign) e.g. -id then the order is set to DESC.