2017 © Pedro PelĂĄez
 

library phrest

A REST API lib with swagger and hateoas support.

image

donurks/phrest

A REST API lib with swagger and hateoas support.

  • Wednesday, November 8, 2017
  • by donurks
  • Repository
  • 4 Watchers
  • 4 Stars
  • 130 Installations
  • PHP
  • 1 Dependents
  • 0 Suggesters
  • 1 Forks
  • 0 Open issues
  • 1 Versions
  • 0 % Grown

The README.md

Build Status Scrutinizer Code Quality Code Coverage Latest Stable Version Total Downloads License, (*1)

phrest

A PHP framework for building RESTful APIs with JSON and Swagger support. Phrest will automatically scan your code for swagger or HATEOAS annotations. If desired phrest will use the scanned swagger annotations for request data validation (see AbstractSwaggerValidatorAction)., (*2)

Features

Requirements

  • PHP 7.1
  • Understanding zircote/swagger-php annotations
  • Understanding willdurand/Hateoas annotations

Installation (with Composer)

Command line

composer require donurks/phrest

public/index.php

<?php
chdir(dirname(__DIR__));
require_once "vendor/autoload.php";

\Phrest\Application::run('phrest-example');

Quickstart (with donurks/phrest-skeleton)

composer create-project donurks/phrest-skeleton

\Phrest\Application::run parameters

Name Type Default Description
applicationName string phrest-application The name of your application. Used for Logging.
configDirectoryPattern string config/{{,*.}global,{,*.}local}.php The glob pattern used for loading and merging your config files.
request \Psr\Http\Message\ServerRequestInterface ServerRequestFactory::fromGlobals() If you want to provide your own request object instead of using the global variables. Useful for unit testing.

Configuration

By default phrest will look at your config/ directory and will load and merge all config files in the following order: - global.php - *.global.php - local.php - *.local.php, (*3)

<?php
// Config files should return arrays
return [
    'my-config-entry' => 'my-config-value'
];

You can use your own config file load pattern (glob) by providing a second parameter to \Phrest\Application::run, (*4)

<?php
\Phrest\Application::run('phrest-example', 'my_own_config_dir/just_one_config_file.php');

Config

For phrest configuration there are predefined class constants on \Phrest\Application which you can use as config entries in your config array., (*5)

<?php
return [
    \Phrest\Application::CONFIG_ENABLE_CACHE  => true
];
\Phrest\Application constant Type Description
CONFIG_ENABLE_CACHE boolean If true, phrest will cache swagger, HATEOAS and configurations. If true CONFIG_CACHE_DIRECTORY is required!
CONFIG_CACHE_DIRECTORY string The directory where phrest will cache. Make sure this directory is phrest exclusive to avoid conflicts (cache/phrest/ is a good choice).
CONFIG_SWAGGER_SCAN_DIRECTORY string Tells phrest where to look for your swagger annotations. Usually this is your src/ directory.
CONFIG_DEPENDENCIES array Phrest uses the zend-servicemanager. Place your zend-servicemanager config here.
CONFIG_ROUTES array['path' => string, 'action' => string] Bring path and action together. Each action must be a string refer to a service defined in CONFIG_DEPENDENCIES. Must implement \Interop\Http\ServerMiddleware\MiddlewareInterface - or just use phrest Abstract actions. See also Routing.
CONFIG_MONOLOG_HANDLER string[] You can register one or more Monolog handlers. Each string must refer to a service defined in CONFIG_DEPENDENCIES.
CONFIG_MONOLOG_PROCESSOR string[] You can register one or more Monolog processors. Each string must refer to a service defined in CONFIG_DEPENDENCIES.
CONFIG_ERROR_CODES string Tells phrest to use your error codes class. String must refer to a service defined in CONFIG_DEPENDENCIES. Must extends \Phrest\API\ErrorCodes. See Using your own error codes.
CONFIG_PRE_ROUTING_MIDDLEWARE string[] Register your own middleware called before routing. Each string must refer to a service defined in CONFIG_DEPENDENCIES. Must implements \Interop\Http\ServerMiddleware\MiddlewareInterface.
CONFIG_PRE_DISPATCHING_MIDDLEWARE string[] Register your own middleware called before dispatching the action. Each string must refer to a service defined in CONFIG_DEPENDENCIES. Must implements \Interop\Http\ServerMiddleware\MiddlewareInterface.

User config

Your whole configuration is accessible in the container with the \Phrest\Application::USER_CONFIG constant., (*6)

<?php
return [
    'my-own-config' => 'some-value',

    \Phrest\Application::CONFIG_DEPENDENCIES => [
        'factories' => [
            \Application\Action\SomeAction::class => function (\Interop\Container\ContainerInterface $container) {
                $userConfig = $container->get(\Phrest\Application::USER_CONFIG);
                $myOwnConfigValue = $userConfig['my-own-config'];

                // ...
            },
        ]
    ],
];

Services

Phrest provides several services for you. You can access them in your zend-servicemanager factory container., (*7)

<?php
return [
    \Phrest\Application::CONFIG_DEPENDENCIES => [
        'factories' => [
            \Application\Action\SomeAction::class => function (\Interop\Container\ContainerInterface $container) {
                return new \Application\Action\SomeAction(
                    $container->get(\Phrest\Application::SERVICE_LOGGER)
                );
            },
        ]
    ],
];
\Phrest\Application constant Interface Description
SERVICE_LOGGER \Psr\Log\LoggerInterface Writes log entries to all registered \Phrest\Application::CONFIG_MONOLOG_HANDLER
SERVICE_ROUTER \Zend\Expressive\Router\RouterInterface The router used to determine the action.
SERVICE_SWAGGER \Phrest\Swagger The phrest swagger abstraction.
SERVICE_HATEOAS \Hateoas\Hateoas The HATEOAS serializer / deserializer.
SERVICE_HATEOAS_RESPONSE_GENERATOR \Phrest\API\HateoasResponseGenerator Can be used to generate json response with the help of willdurand/Hateoas. See HATEOAS response generator
SERVICE_REQUEST_SWAGGER_VALIDATOR \Phrest\API\RequestSwaggerValidator Can be used to validate request data against swagger schema. See Request swagger validator

Actions

Phrest provides several actions for you. You can use them by simple bound them to paths., (*8)

<?php
return [
    \Phrest\Application::CONFIG_ROUTES => [
        // call http://your-host/swagger to see your swagger file
        'swagger' => \Phrest\Application::createRoute(
            '/swagger', 
            \Phrest\Application::ACTION_SWAGGER
        ),
    ],
];
\Phrest\Application constant Description
ACTION_SWAGGER Provides the swagger file in json format.
ACTION_ERROR_CODES Provides all possible error codes in json format. See Error Codes.

Routing

A route is connection between a path and an action. Use the \Phrest\Application::CONFIG_ROUTES configuration to add routes. There is also a static method \Phrest\Application::createRoute() which creates a route entry., (*9)

The array keys are used to name the route. The route names are used in the HATEOAS response generator for link generation., (*10)

You can also provide a mapping for operation ids (see AbstractSwaggerValidatorAction)., (*11)

```php#abstractswaggervalidatoraction <?php // your config file return [ \Phrest\Application::CONFIG_ROUTES => [ 'the-name-of-your-route' => \Phrest\Application::createRoute( '/the-path-of-your-route',, (*12)

        // Your action - must be refer to a service in your CONFIG_DEPENDENCIES
        \YourAction::class,
        [
            // AbstractSwaggerValidatorAction will now use "someOperationId" instead of "get.the-name-of-your-route"
            'get' => 'someOperationId'    
        ]
    ),
],

];, (*13)


## Abstract actions You can write your own actions by implementing the ```\Interop\Http\ServerMiddleware\MiddlewareInterface```. Or you can use the abstract actions provided by phrest. ### AbstractAction Use this abstract action if you just want to map the HTTP methods to action methods. Extend the ```\Phrest\API\AbstractAction``` class and overwrite the methods as needed. If phrest receives an request with a method not provided by your action, phrest will handle the error response automatically. Method | Parameter | Return type | Description ---|---|---|--- ```get```, ```put```, ```post```, ```patch```, ```delete``` | ```\Psr\Http\Message\ServerRequestInterface``` | ```\Psr\Http\Message\ResponseInterface``` or null | If your method method returns ```null```, phrest will generate an empty response with http status code 204. ```options``` | - | - | You can't overwrite the ```options``` method. Phrest will automatically generate a response with all allowed (=implemented) methods. ```php <?php class Test extends \Phrest\API\AbstractAction { public function get(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface { return new \Zend\Diactoros\Response\JsonResponse(['name' => 'some name']); } }

AbstractSwaggerValidatorAction

Use this abstract action if you want phrest to validate your request based on swagger annotations., (*14)

Phrest will use the current route name to validate all request parameters defined in your swagger annotations. By default, phrest will use a operationId with the pattern: "method.route-name" ("get.name-of-your-route")., (*15)

You can overwrite the operationIds for each method (see Routing)., (*16)

The operationId defined in the route have to match with the operationId in the swagger annotations., (*17)

If validation failed, phrest will handle the error response automatically., (*18)

Extend the \Phrest\API\AbstractSwaggerValidatorAction class and overwrite the methods as needed. If phrest receives a request with a method not provided by your actions, a \Phrest\Http\Exception will be thrown resulting in a http status 405 with error model body response (see Exceptions)., (*19)

Method Parameter Return type Description
get, put, post, patch, delete \Psr\API\RequestSwaggerData \Psr\Http\Message\ResponseInterface or null If your method method returns null, phrest will generate an empty response with http status code 204.
options - - You can't overwrite the options method. Phrest will automatically generate a response with all allowed (=implemented) methods.

Use the \Phrest\API\RequestSwaggerData object to access your request parameters. See Request swagger validator for details., (*20)

<?php
// your config file
return [
    \Phrest\Application::CONFIG_ROUTES => [
        // The route name "someRouteName" matches operationId in the swagger annotations
        'someRouteName' => \Phrest\Application::createRoute(
            '/some-path', 
            \SomeAction::class
        ),
    ],
];
<?php
// your action
class SomeAction extends \Phrest\API\AbstractSwaggerValidatorAction
{
    /**
     * @SWG\Get(
     *     path="/some-path",
     *     operationId="someRouteName",
     *     @SWG\Parameter(
     *          name="id",
     *          in="query",
     *          type="number"
     *     ),
     *     @SWG\Response(response="200", description="Success")
     * )
     *
     * @param \Phrest\API\RequestSwaggerData $data
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function get(\Phrest\API\RequestSwaggerData $data): \Psr\Http\Message\ResponseInterface
    {
        return new \Zend\Diactoros\Response\JsonResponse(
            ['id' => $data->getQueryValues()['id']]
        );
    }
}

Request swagger validator

You can use the request swagger validator to validate your request parameters against your swagger operations. Just provide your request object and swagger operationId. Phrest will use all parameter definitions provided in the operation linked by the operation Id., (*21)

You can use the service \Phrest\Application::SERVICE_REQUEST_SWAGGER_VALIDATOR to inject the request swagger validator to your classes., (*22)

<?php
// your config file
return [
    \Phrest\Application::CONFIG_DEPENDENCIES => [
        'factories' => [
            \SomeAction::class => function (\Interop\Container\ContainerInterface $container) {
                return new \SomeAction(
                    $container->get(\Phrest\Application::SERVICE_REQUEST_SWAGGER_VALIDATOR)
                );
            },
        ]
    ],
];

You can also implement the \Phrest\API\RequestSwaggerValidatorAwareInterface and use the \Phrest\API\RequestSwaggerValidatorAwareTrait. Phrest will automatically inject the request swagger validator to your class und populate a requestSwaggerValidator property., (*23)

<?php
// your action
class SomeAction extends \Phrest\API\AbstractAction
    implements \Phrest\API\RequestSwaggerValidatorAwareInterface
{
    use \Phrest\API\RequestSwaggerValidatorAwareTrait;

    public function get(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
    {
        $data = $this->requestSwaggerValidator->validate($request, 'someOperationId');
        return new \Zend\Diactoros\Response\EmptyResponse();
    }
}

Or just use the AbstractSwaggerValidatorAction and phrest will take care of fetching the right operationId., (*24)

The validate method takes two parameters: the request object and the operationId., (*25)

Phrest will look in your swagger for the given operationId. If there is no operation for this id, phrest will throw an \Phrest\Exception resulting in a http status 500 response if not catched. If the validation failed, phrest will throw an \Phrest\Http\Exception resulting in a http status 400 response with error model if not catched., (*26)

If validation succeed, the validate method will return an \Phrest\API\RequestSwaggerData object., (*27)

The \Phrest\API\RequestSwaggerData object contains all parameters validated, filled with default values (if defined) and correct data types (as definded in swagger annotations)., (*28)

\Phrest\API\RequestSwaggerData method Description Request example Value
getBodyValue() Returns the parsed json object. {"name": "Batman"} {stdClass json object}
getQueryValues() Returns all query params as key value pairs. your-url?var1=value1 ['var1' => 'value1']
getPathValues() Returns all path params as key value pairs. your-url/somePath/{pathVar} ['pathVar' => '{value}']
getHeaderValues() Returns all header params as key value pairs. SOME_HEADER=some-value ['SOME_HEADER' => 'some-value']

For swagger-php details see zircote/swagger-php., (*29)

For swagger details see OpenAPI Spec 2.0., (*30)

HATEOAS response generator

The HATEOAS response generator will generate a JSON response from your objects with the help of annotations (willdurand/Hateoas)., (*31)

You can use the service \Phrest\Application::SERVICE_HATEOAS_RESPONSE_GENERATOR to inject the hateoas response generator to your classes., (*32)

<?php
// your config file
return [
    \Phrest\Application::CONFIG_DEPENDENCIES => [
        'factories' => [
            \SomeAction::class => function (\Interop\Container\ContainerInterface $container) {
                return new \SomeAction(
                    $container->get(\Phrest\Application::SERVICE_HATEOAS_RESPONSE_GENERATOR)
                );
            },
        ]
    ],
];

You can also implement the \Phrest\API\HateoasResponseGeneratorAwareInterface and use the \Phrest\API\HateoasResponseGeneratorAwareTrait. Phrest will automatically inject the response generator to your class und populate a generateHateoasResponse method., (*33)

<?php
// your action
class SomeAction extends \Phrest\API\AbstractAction
    implements \Phrest\API\HateoasResponseGeneratorAwareInterface
{
    use \Phrest\API\HateoasResponseGeneratorAwareTrait;

    public function get(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
    {
        $user = new User(
            453, 
            'Bruce',
            'Wayne'
        );
        return $this->generateHateoasResponse($user);
    }
}

The generateHateoasResponse method takes your object and optionally a http status code and a headers array., (*34)

You can use the HATEOAS route in relation annotations to generate links. Just pass the name of the route (see Routing). You can also pass named path parameters for url generation., (*35)

<?php
// your config file
return [
    \Phrest\Application::CONFIG_ROUTES => [
        'your-route' => \Phrest\Application::createRoute(
            '/users/{userId}', 
            \YourAction::class
        ),
    ],
];
<?php
/**
 * @Hateoas\Configuration\Annotation\Relation(
 *      "self",
 *      href = @Hateoas\Configuration\Annotation\Route(
 *          "your-route",
 *          parameters = { "userId" = "expr(object.getId())" },
 *          absolute = true
 *      )
 * )
 */
class User
{
    private $id;
    private $first_name;
    private $last_name;

    public function __construct($id, $first_name, $last_name)
    {
        $this->id = $id;
        $this->first_name = $first_name;
        $this->last_name = $last_name;
    }

    public function getId() {
        return $this->id;
    }
}

The resulting output should now look like this:, (*36)

{
  "id": 453,
  "first_name": "Bruce",
  "last_name": "Wayne",
  "_links": {
    "self": {
      "href": "http://localhost/users/453"
    }
  }
}

For willdurand/Hateoas details see willdurand/Hateoas., (*37)

Logging

You can use the service \Phrest\Application::SERVICE_LOGGER to inject the logger to your classes., (*38)

<?php
// your config file
return [
    \Phrest\Application::CONFIG_DEPENDENCIES => [
        'factories' => [
            \SomeAction::class => function (\Interop\Container\ContainerInterface $container) {
                return new \SomeAction(
                    $container->get(\Phrest\Application::SERVICE_LOGGER)
                );
            },
        ]
    ],
];

You can also implement the \Psr\Log\LoggerAwareInterface and use the \Psr\Log\LoggerAwareTrait. Phrest will automatically inject the logger to your class und populate a logger property., (*39)

<?php
// your action
class SomeAction extends \Phrest\API\AbstractAction
    implements \Psr\Log\LoggerAwareInterface
{
    use \Psr\Log\LoggerAwareTrait;

    public function get(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
    {
        $this->logger->info('a log message');
        return new \Zend\Diactoros\Response\EmptyResponse();
    }
}

For handler and processor details see Monolog., (*40)

Exceptions

Phrest will generate a response with http status code 500 for every unhandled exception., (*41)

Except for the \Phrest\Http\Exception. If not catched, phrest will generate a response with correct http status code and error model body., (*42)

Every \Phrest\Http\Exception needs a http status code and an error model with error entries., (*43)

The error codes on \Phrest\API\Error and \Phrest\API\ErrorEntry should be used from \Phrest\API\ErrorCodes or your own error codes class (see Error Codes)., (*44)

<?php
// all alone
throw new \Phrest\Http\Exception(
    400,
    new \Phrest\API\Error(
        1,
        'Request parameter validation error',
        new \Phrest\API\ErrorEntry(
            2,
            '{query}/id',
            'Value must be a number, string given',
            'is_number'
        )
    )
);

// with short hand method
throw \Phrest\Http\Exception::Unauthorized(
    new \Phrest\API\Error(
        1,
        'Request parameter validation error',
        new \Phrest\API\ErrorEntry(
            2,
            '{query}/id',
            'Value must be a number, string given',
            'is_number'
        )
    )
);

// with error codes class
throw \Phrest\Http\Exception::Unauthorized(
    new \Phrest\API\Error(
        \Phrest\API\ErrorCodes::REQUEST_PARAMETER_VALIDATION,
        'Request parameter validation error',
        new \Phrest\API\ErrorEntry(
            \Phrest\API\ErrorCodes::REQUEST_VALIDATION_TYPE,
            '{query}/id',
            'Value must be a number, string given',
            'is_number'
        )
    )
);

Error codes

You can use the phrest error codes action to publish your error codes for your API consumers., (*45)

<?php
// your configuration file
return [
    \Phrest\Application::CONFIG_ROUTES => [
        'error_codes' => \Phrest\Application::createRoute(
            '/your/path/to/error_codes', 
            \Phrest\Application::ACTION_ERROR_CODES
        ), 
    ],
];

Now call http://localhost/your/path/to/error_codes to see your error codes., (*46)

Using your own error codes

You can tell phrest what error codes class to use. Just register your ErrorCodes class under \Phrest\Application::CONFIG_ERROR_CODES. Phrest uses error codes from 0 to 1000. To avoid conflicts you should use LAST_PHREST_ERROR_CODE as base for your own error codes., (*47)

<?php
namespace Application;
class ErrorCodes extends \Phrest\API\ErrorCodes
{
    const MY_OWN_ERROR = self::LAST_PHREST_ERROR_CODE + 1;
    const MY_OWN_ERROR_2 = self::LAST_PHREST_ERROR_CODE + 2;
}
<?php
// your configuration file
return [
    \Phrest\Application::CONFIG_ERROR_CODES => \Application\ErrorCodes::class,

    \Phrest\Application::CONFIG_DEPENDENCIES => [
        'invokables' => [
            \Application\ErrorCodes::class => \Application\ErrorCodes::class,
        ]
    ],
];

Now call http://localhost/your/path/to/error_codes and you should see the phrest error codes and your own error codes., (*48)

Todos

  • CallableUrlGenerator
    • url generation with missing params lead to invalid url (hateoas link params)
  • validate header against swagger consumes value(s)
  • ReadMe (some links doesnt work)
  • UnitTests
  • add OpenAPI Spec 3.0 support (as soon as zircote/swagger-php 3.0 is released)
  • HAL Links (part of OpenAPI Spec 3.0)
  • solve todos in code
  • check cache speed and need (filesystem access cost vs cachable process cost)
    • granular user config for caching? (cache swagger: yes, cache error codes: no, ...)
  • injectable PSR-16 (\Psr\SimpleCache\CacheInterface) cache-adapter when zend-cache 2.8.0 released 2.8.0

The Versions