Phprest
Description
REST-like PHP micro-framework.
It's based on the Proton (StackPhp compatible) micro-framework.
Phprest gives you only the very basics to build your own architecture within your own framework and assemble any folder structure you like. It is just a thin layer around your application with the help of some great libraries.
Components
- League\Container
- League\Route
- League\Event
- League\BooBoo
- Willdurand\Negotiation
- Willdurand\Hateoas
- Monolog\Monolog
- Stack\Builder
Skills
- Dependency injection
- Routing
- Error handling
- Serialization
- Deserialization
- HATEOAS
- API versioning
- Pagination
- Logging
ToC
- Installation
- Usage
- Error handler
- Authentication
- API testing
- API documentation
Installation
Install it through composer.
{
"require": {
"phprest/phprest": "@stable"
}
}
tip: you should browse the phprest/phprest
page to choose a stable version to use, avoid the @stable
meta constraint.
Usage
Services
There are a couple of services which can help you to solve some general problems:
These are separate repositories.
Setup
require __DIR__ . '/../vendor/autoload.php';
use Phprest\Config;
use Phprest\Response;
use Phprest\Application;
use Symfony\Component\HttpFoundation\Request;
# vendor name, current API version, debug
$config = new Config('vendor.name', '0.1', true);
$app = new Application($config);
$app->get('/{version:\d\.\d}/', function (Request $request) {
return new Response\Ok('Hello World!');
});
$app->run();
Configuration
You should check the Config class.
Logging
use Phprest\Service\Logger\Config as LoggerConfig;
use Phprest\Service\Logger\Service as LoggerService;
use Monolog\Handler\StreamHandler;
$config = new Config('vendor.name', '0.1');
$loggerHandlers[] = new StreamHandler('path_to_the_log_file', \Monolog\Logger::DEBUG);
$config->setLoggerConfig(new LoggerConfig('phprest', $loggerHandlers));
$config->setLoggerService(new LoggerService());
Usage with Stack
You can register middlewares trough the registerMiddleware
function.
$app->registerMiddleware('Jsor\Stack\JWT', [
[
'firewall' => [
['path' => '/', 'anonymous' => false],
['path' => '/tokens', 'anonymous' => true]
],
'key_provider' => function() {
return 'secret-key';
},
'realm' => 'The Glowing Territories'
]
]);
API Versioning
Phprest works with API versions by default. This means that the ApiVersion Middleware manipulates the incoming request. The version (based on the current Accept header) is added to the path.
What does it mean?
Accept header | Route | Result route* |
---|---|---|
application/vnd.phprest-v1+json | /temperatures | /1.0/temperatures |
application/vnd.phprest-v3.5+json | /temperatures | /3.5/temperatures |
*/* | /temperatures | /the version which you set in your Config/temperatures |
* It is not a redirect or a forward method, it is just an inner application routing through a middleware.
Accept/Content-Type header can be | Transfers to |
---|---|
application/vnd.Vendor-vVersion+json | itself |
application/vnd.Vendor+json; version=Version | itself |
application/vnd.Vendor-vVersion+xml | itself |
application/vnd.Vendor+xml; version=Version | itself |
application/json | application/vnd.Vendor-vVersion+json |
application/xml | application/vnd.Vendor-vVersion+xml |
*/* | application/vnd.Vendor-vVersion+json |
API Version only can be one of the following ranges:
- 0 - 9
- 0.0 - 9.9
-
If Accept header is not parsable
-
then Phprest throws a Not Acceptable exception
-
If you do a deserialization and Content-Type header is not parsable
-
then Phprest throws an Unsupported Media Type exception
Routing
For more information please visit League/Route.
Simple routing
$app->get('/{version:\d\.\d}/hello', function (Request $request, $version) {
# You can leave the $request and the $version variable
return new Response\Ok('Hello World!');
});
- The ApiVersion Middleware manipulates the inner routing every time, so you have to care about the first part of your route as a version number.
- This route is available in all API versions (see the
\d\.\d
regular expression) - You can set a fix API version number too e.g.
'/3.6/hello'
Routing with arguments
$app->get('/2.4/hello/{name:word}', function (Request $request, $name) {
return new Response\Ok('Hello ' . $name);
});
- This route is available only in API version 2.4
Routing through a controller
# index.php
# calls index method on HomeController class
$app->get('/{version:\d\.\d}/', '\Foo\Bar\HomeController::index');
namespace Foo\Bar;
# HomeController.php
use Symfony\Component\HttpFoundation\Request;
use Phprest\Response;
class HomeController
{
public function index(Request $request, $version)
{
return new Response\Ok('Hello World!');
}
}
Routing through a service controller
$app['HomeController'] = function () {
return new \Foo\Bar\HomeController();
};
$app->get('/{version:\d\.\d}/', 'HomeController::index');
Routing with annotations
You have to register your controller.
$app->registerController('\Foo\Bar\Controller\Home');
namespace Foo\Bar\Controller;
# Home.php
use Phprest\Util\Controller;
use Symfony\Component\HttpFoundation\Request;
use Phprest\Response;
use Phprest\Annotation as Phprest;
class Home extends Controller
{
/**
* @Phprest\Route(method="GET", path="/foobars/{id}", since=1.2, until=2.8)
*/
public function get(Request $request, $version, $id)
{
return new Response\Ok('Hello World!');
}
}
since
tag is optionaluntil
tag is optional
Controller
To create a Phprest Controller simply extends your class from \Phprest\Util\Controller
.
namespace App\Module\Controller;
class Index extends \Phprest\Util\Controller
{
public function index(Request $request)
{
# ...
}
}
Serialization, Deserialization, Hateoas
- Phprest will automatically serialize* your response based on the Accept header.
- Phprest can deserialize your content based on the Content-Type header.
Except*:
- If your response is not a Response instance (e.g. it a simple string)
- If your response is empty
Serialization example
Let's see a Temperature entity:
You do not have to use annotations! You can use configuration files! Browse in Jms\Serializer and Willdurand\Hateoas
namespace Foo\Entity;
use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;
/**
* @Serializer\XmlRoot("result")
*
* @Hateoas\Relation(
* "self",
* href = @Hateoas\Route("/temperatures", parameters = {"id" = "expr(object.id)"}, absolute = false)
* )
*/
class Temperature
{
/**
* @var integer
* @Serializer\Type("integer")
*/
public $id;
/**
* @var integer
* @Serializer\Type("integer")
*/
public $value;
/**
* @var \DateTime
* @Serializer\Type("DateTime")
* @Serializer\Since("2")
* @Serializer\Exclude
*/
public $created;
/**
* @param integer $id
* @param integer $value
* @param \DateTime $created
*/
public function __construct($id = null, $value = null, \DateTime $created = null)
{
$this->id = $id;
$this->value = $value;
$this->created = $created;
}
}
The router:
$app->post('/{version:\d\.\d}/temperatures', function () use ($app, $version) {
$temperature = new \Foo\Entity\Temperature(1, 32, new \DateTime());
return new Response\Created('/temperatures/1', $temperature);
});
Json response (Accept: application/vnd.vendor+json; version=1):
{
"id": 1,
"value": 32,
"_links": {
"self": {
"href": "\/temperatures\/1"
}
}
}
Xml response (Accept: application/vnd.vendor+xml; version=1):
"><result> <id>1id> <value>32value> <link rel="self" href="/temperatures/1"/> result>