kleijnweb/php-api-descriptions

A PHP library for creating "contract-first" API applications.

v1.0.0-alpha5 2017-12-23 12:35 UTC

This package is not auto-updated.

Last update: 2020-04-03 18:00:48 UTC


README

Build Status Coverage Status Scrutinizer Code Quality Latest Stable Version

A PHP library for creating "contract-first" API applications.

Supported formats:

Limited:

* RAML is much more feature-rich and generally elaborate standard than OpenAPI, it will take some time to support the full set. Help is appreciated.

The aim is to provide full support and interchangeability.

Typical Usage

Validating Requests And Responses

Namespaces omitted for brevity:

$validator = new MessageValidator(
  (new Repository('some/path'))->get('some-service/v1.0.1/swagger.yml')
);
/** @var ServerRequestInterface $request */
$result = $validator->validateRequest($request, $path);

/** @var ResponseInterface $response */
$result = $validator->validateResponse($body, $request, $response, $path);

If you're feeling frisky and want to try RAML support:

$validator = new MessageValidator(
    (new Repository())
        ->setFactory(new DescriptionFactory(DescriptionFactory::BUILDER_RAML))
        ->get('tests/definitions/raml/mobile-order-api/api.raml')
);

(De-)Hydration

// $input is deserialized and validated using $inputSchema

$builder   = new ProcessorBuilder(new ClassNameResolver(['Some\Namespace']));
$processor = $builder->build($schema);
$hydrated  = $processor->hydrate($input, $inputSchema);

// Perform business logic, creating $appOutput

$output = $processor->dehydrate($appOutput, $outputSchema);

// Validate output using $outputSchema

NULLs, Undefined And Defaults

The processor will assume hydration input is pre-validated. This implies that when an input object contains a property with a NULL value, it will leave it as is, and it may be casted to something other than NULL if the input is invalid (otherwise it will be "hydrated" by NullProcessor). When dehydrating, the processors will intentionally not try to force validity of anything that may have been set to an invalid value by application processing.

The implied flow is thus: input > deserialization > validation > hydration > business logic > dehydration [> validation] > serialization > output .

When adhering to this flow, the behavior should be intuitive. There is a separate document detailing the implementation here.

DateTime

The expected in- and output format can be tweaked by configuring the DateTimeProcessor factory with a custom instance of DateTimeSerializer (via the builder):

$builder = new ProcessorBuilder($classNameResolver, new DateTimeSerializer(\DateTime::RFC850));

By default output is formatted as 'Y-m-d\TH:i:s.uP' (RFC3339 with microseconds). When passed, the first constructor argument will be used instead. Input parsing is attempted as follows:

  1. Arguments to the constructor
  2. RFC3339 with decreasing precision:
    1. RFC3339 with microseconds
    2. RFC3339 with milliseconds
    3. RFC3339
  3. ISO8601

NOTE: Formats not starting with Y-m-d do not work with Schema::FORMAT_DATE nor AnyProcessor.

Custom Processors

Class name resolution and DateTime handling can be tweaked by injecting custom instances into the builder, but pretty much all parts of the hydration and dehydration processes are customizable. You can inject custom processors by injecting factories for them into the "processor factory queue". All of the processors and their factories are open for extension. Use cases include:

  • Loading objects from a data store
  • Maintaining identity of objects that occur more than once in a structure
  • Custom typed object hydration (eg. using constructors, setters)
  • Custom object creation per type
  • Issuing domain events on object creation
  • Coercing scalar values (eg. interpreting 'false' as FALSE)
  • Pretty much anything else you can think of

Some examples can be found here.

Performance Expectations

On my old Xeon W3570, both hydration and deydration of a an array of 1000 realistic objects (nested objects, arrays) takes about 100ms; on average a little short of 1ms per root object.

Integration

If you want OpenAPI support in combination with Symfony, you should check out SwaggerBundle.

And there's PSR-7/PSR-15 Middleware, which behaves pretty much the same but is much more reusable.

Limitations

  • Very limited RAML support
  • Does not work with form data
  • Requires a router to determine the matching path
  • If the request has a body, it will have to be deserialized using objects, not as an associative array
  • Requires the response body to be passed unserialized
  • Response validation does not validate headers and content-types (yet)

Contributing

Pull requests are very welcome, but the code has to be PSR2 compliant, follow used conventions concerning parameter and return type declarations, and the coverage can not go down.

License

KleijnWeb\PhpApi\Descriptions is made available under the terms of the LGPL, version 3.0.