REST-like PHP micro-framework.
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.
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.
There are a couple of services which can help you to solve some general problems:
These are separate repositories.
<?php
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();
You should check the Config class.
<?php
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());
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'
]
]);
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:
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
For more information please visit League/Route.
<?php
$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!');
});
\d\.\d
regular expression)'/3.6/hello'
<?php
$app->get('/2.4/hello/{name:word}', function (Request $request, $name) {
return new Response\Ok('Hello ' . $name);
});
<?php
# index.php
# calls index method on HomeController class
$app->get('/{version:\d\.\d}/', '\Foo\Bar\HomeController::index');
<?php 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!');
}
}
<?php
$app['HomeController'] = function () {
return new \Foo\Bar\HomeController();
};
$app->get('/{version:\d\.\d}/', 'HomeController::index');
You have to register your controller.
<?php
$app->registerController('\Foo\Bar\Controller\Home');
<?php 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 optionalTo create a Phprest Controller simply extends your class from \Phprest\Util\Controller
.
<?php namespace App\Module\Controller;
class Index extends \Phprest\Util\Controller
{
public function index(Request $request)
{
# ...
}
}
Except*:
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
<?php 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:
<?php
$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>1</id>
<value>32</value>
<link rel="self" href="/temperatures/1"/>
</result>
Properties will be translated from camel-case to a lower-cased underscored name, e.g. camelCase -> camel_case by default. If you want to use a custom serialized name you have to use the @SerializedName option on your attribute.
You have to use the HATEOAS Util trait in your controller to do deserialization.
# ...
use JMS\Serializer\Exception\RuntimeException;
# ...
public function post(Request $request)
{
try {
/** @var \Foo\Entity\Temperature $temperature */
$temperature = $this->deserialize('\Foo\Entity\Temperature', $request);
} catch (RuntimeException $e) {
throw new Exception\UnprocessableEntity(0, [new Service\Validator\Entity\Error('', $e->getMessage())]);
}
}
# ...
<?php
# ...
use Hateoas\Representation\PaginatedRepresentation;
use Hateoas\Representation\CollectionRepresentation;
# ...
$paginatedCollection = new PaginatedRepresentation(
new CollectionRepresentation([$user1, $user2, ...]),
'/users', # route
[], # route parameters, should be $request->query->all()
1, # page, should be (int)$request->query->get('page')
10, # limit, should be (int)$request->query->get('limit')
5, # total pages
'page', # page route parameter name, optional, defaults to 'page'
'limit', # limit route parameter name, optional, defaults to 'limit'
true, # absolute URIs
47 # total number of rows
);
# ...
return new Response\Ok($paginatedCollection);
For more informations please visit the HATEOAS docs
There are several responses you can use by default, one of them is the Ok response.
These are simple Response objects.
<?php
# ...
$app->get('/', function (Request $request) {
return new Response\Ok('Hello World!');
});
# ...
Responses |
---|
Accepted |
Created |
NoContent |
NotModified |
Ok |
These are Exceptions.
<?php
# ...
$app->get('/', function (Request $request) {
# ...
throw new \Phprest\Exception\BadRequest();
# ...
});
# ...
Exceptions |
---|
BadRequest |
Conflict |
Forbidden |
Gone |
InternalServerError |
MethodNotAllowed |
NotAcceptable |
NotFound |
TooManyRequests |
PreconditionFailed |
TooManyRequests |
Unauthorized |
UnprocessableEntity |
UnsupportedMediaType |
See Proton’s doc and for more information please visit League/Container.
You can use a helper script if you want after a composer install (vendor/bin/phprest
).
You have to provide the (bootstrapped) app instance for the script. You have two options for this:
app/app.php
paths.php
filepaths.php
file with the app file path under the app
array keyPhprest handles error with League\BooBoo. The default formatter is Json and Xml Formatter.
<?php
# ...
$app->get('/{version:\d\.\d}/', function (Request $request, $version) {
throw new \Phprest\Exception\Exception('Code Red!', 9, 503);
});
# ...
The response is content negotiationed (xml/json), the status code is 503.
{
"code": 9,
"message": "Code Red!",
"details": []
}
<result>
<code>9</code>
<message>
<![CDATA[Code Red!]]>
</message>
</result>
You’ll need this package:
$app->registerMiddleware('Dflydev\Stack\BasicAuthentication', [
[
'firewall' => [
['path' => '/', 'anonymous' => false],
['path' => '/temperatures', 'method' => 'GET', 'anonymous' => true]
],
'authenticator' => function ($username, $password) {
if ('admin' === $username && 'admin' === $password) {
# Basic YWRtaW46YWRtaW4=
return 'success';
}
},
'realm' => 'The Glowing Territories'
]
]);
You’ll need this package:
$app->registerMiddleware('Jsor\Stack\JWT', [
[
'firewall' => [
['path' => '/', 'anonymous' => false],
['path' => '/tokens', 'anonymous' => true]
],
'key_provider' => function() {
return 'secret-key';
},
'realm' => 'The Glowing Territories'
]
]);
There are a couple of great tools out there for testing your API.
Just a few recommendations: