donurks / phrest
A REST API lib with swagger and hateoas support.
Requires
- php: ^7.1
- justinrainbow/json-schema: 5.2.1
- monolog/monolog: 1.23.0
- willdurand/hateoas: 2.11.0
- zendframework/zend-cache: 2.7.2
- zendframework/zend-config-aggregator: 1.0.0
- zendframework/zend-expressive: 2.0.3
- zendframework/zend-expressive-fastroute: 2.0.0
- zendframework/zend-expressive-helpers: 4.0.0
- zendframework/zend-servicemanager: 3.3.0
- zircote/swagger-php: 2.0.10
Requires (Dev)
- phpunit/phpunit: 6.3.0
This package is not auto-updated.
Last update: 2024-11-09 02:46:28 UTC
README
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).
Features
- Swagger 2.0 definitions in annotations (zircote/swagger-php)
- HATEOAS response definitions in annotations (willdurand/Hateoas)
- Request data validation against swagger data (justinrainbow/json-schema)
- PSR-3 Logging (Seldaek/monolog)
- PSR-7 HTTP Message
- PSR-11 Container
- PSR-15 Middleware (extendable)
- Error codes for API consumers
- Unified exception handling for HTTP status codes
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
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
<?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
<?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.
<?php return [ \Phrest\Application::CONFIG_ENABLE_CACHE => true ];
User config
Your whole configuration is accessible in the container with the \Phrest\Application::USER_CONFIG
constant.
<?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.
<?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) ); }, ] ], ];
Actions
Phrest provides several actions for you. You can use them by simple bound them to paths.
<?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 ), ], ];
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.
The array keys are used to name the route. The route names are used in the HATEOAS response generator for link generation.
You can also provide a mapping for operation ids (see AbstractSwaggerValidatorAction).
<?php
// your config file
return [
\Phrest\Application::CONFIG_ROUTES => [
'the-name-of-your-route' => \Phrest\Application::createRoute(
'/the-path-of-your-route',
// 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'
]
),
],
];
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.
<?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.
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").
You can overwrite the operationIds for each method (see Routing).
The operationId defined in the route have to match with the operationId in the swagger annotations.
If validation failed, phrest will handle the error response automatically.
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).
Use the \Phrest\API\RequestSwaggerData
object to access your request parameters. See Request swagger validator for details.
<?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.
You can use the service \Phrest\Application::SERVICE_REQUEST_SWAGGER_VALIDATOR
to inject the request swagger validator to your classes.
<?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.
<?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.
The validate method takes two parameters: the request object and the operationId.
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.
If validation succeed, the validate method will return an \Phrest\API\RequestSwaggerData
object.
The \Phrest\API\RequestSwaggerData
object contains all parameters validated, filled with default values (if defined) and correct data types (as definded in swagger annotations).
For swagger-php details see zircote/swagger-php.
For swagger details see OpenAPI Spec 2.0.
HATEOAS response generator
The HATEOAS response generator will generate a JSON response from your objects with the help of annotations (willdurand/Hateoas).
You can use the service \Phrest\Application::SERVICE_HATEOAS_RESPONSE_GENERATOR
to inject the hateoas response generator to your classes.
<?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.
<?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.
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.
<?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:
{ "id": 453, "first_name": "Bruce", "last_name": "Wayne", "_links": { "self": { "href": "http://localhost/users/453" } } }
For willdurand/Hateoas details see willdurand/Hateoas.
Logging
You can use the service \Phrest\Application::SERVICE_LOGGER
to inject the logger to your classes.
<?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.
<?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.
Exceptions
Phrest will generate a response with http status code 500 for every unhandled exception.
Except for the \Phrest\Http\Exception
. If not catched, phrest will generate a response with correct http status code and error model body.
Every \Phrest\Http\Exception
needs a http status code and an error model with error entries.
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).
<?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.
<?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.
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.
<?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.
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