foxdie / rest
PHP RESTful API microframework
Requires
- php: >=7.2.0
- ext-curl: *
- symfony/yaml: ^4.2@dev
README
Requirements
- PHP >= 7.2
- PHP cURL extension
- Apache HTTP Server >= 2.4
- Apache mod_rewrite enabled
Installation
composer require foxdie/rest
Table of content
Client
Creating a client
use Plankton\Client\Client; $client = new Client(API_ENDPOINT);
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/simple-client.php
GET example
$response = $client->get("/users");
using callback
$client->get("/users", function(Response $response){ echo $response; });
using magic
$response = $client->getUsers();
POST example
$response = $client->post("/users", ["email" => "foo@bar.com"]);
using callback
$client->post("/users", ["email" => "foo@bar.com"], function(Response $response){ echo $response->getLocation(); });
using magic
$response = $client->postUsers(["email" => "foo@bar.com"]);
PUT, PATCH and DELETE examples
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/simple-client.php
Magic calls
Spinal case
If you want to use magic calls, your routes must use the spinal case Example:
$client->getUserAccounts()
will match the following route:
GET /user-accounts
camel case and snake case are not supported
Examples
call | route |
---|---|
$client->getUsers(); | GET /users |
$client->groups(1)->getUsers(); | GET /groups/1/users |
$client->groups(1)->getUsers(2); | GET /groups/1/users/2 |
$client->postUsers([]); | POST /users |
$client->groups(1)->postUsers([]); | POST /groups/1/users |
$client->deleteUsers(1); | DELETE /users/1 |
$client->users(1)->delete(); | DELETE /users/1 |
$client->groups(1)->deleteUsers(2); | DELETE /groups/1/users/2 |
$client->groups(1)->users(2)->delete(); | DELETE /groups/1/users/2 |
$client->groups(1)->users()->delete(2); | DELETE /groups/1/users/2 |
Content types
When you are using magic calls (e.g. $client->postUsers([]);
) or one of the methods Client::post()
, Client::put()
, Client::patch()
, a Content-Type
header is automatically added to the request. The Content-Type
is automatically guessed according to the data you send to the server :
data type | Content-Type |
---|---|
array | application/x-www-form-urlencoded |
object | application/json |
valid json string | application/json |
valid xml string | application/xml |
string | text/plain |
However, you still can set the Content-Type
manually with a customized request
Custom request example
use Plankton\Client\Client; use Plankton\Request; $request = new Request(API_ENDPOINT . "/users"); $request ->setMethod(Request::METHOD_POST) ->setParameter("foo", "bar") ->setHeader("User-Agent", "Mozilla/5.0") ->setContentType(Request::CONTENT_TYPE_JSON) ->setData(["email" => "foo@bar.com"]); $client = new Client(API_ENDPOINT); $client->send($request, function(Response $response){ // ... });
Automatic data conversion
For readability reasons, you can use arrays
or objects
with the Request::setData()
method, regardless of the content-type you use. The data will be automatically converted according to the rules below :
Content-Type | Data type | Conversion |
---|---|---|
Request::CONTENT_TYPE_JSON | array | json string |
Request::CONTENT_TYPE_JSON | object | json string |
other | array | URL-encoded query string |
other | object | URL-encoded query string |
Authentication strategy
anonymous auth
$client = new Client(API_ENDPOINT);
basic auth
use Plankton\Client\Strategy\BasicAuthentication; $client = new Client(API_ENDPOINT, new BasicAuthentication(USER, PASSWORD));
client credentials
use Plankton\Client\Strategy\ClientCredentialsAuthentication; $client = new Client(API_ENDPOINT, new ClientCredentialsAuthentication( CLIENT_ID, CLIENT_SECRET, AUTHENTICATION_URL ));
The authorize and access/refresh token requests will be performed automatically. The 3rd parameter is optionnal, the default value is "/token"
Server
Creating a server
use Plankton\Server\Server; $server = new Server(); $server->run();
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/simple-server.php
Creating controllers
You must create at least one controller which extends the abstract class Plankton\Server\Controller
use Plankton\Server\Controller; class APIController extends Controller{ public function getUsers(int $id, Request $request): Response{ } public function postUsers(Request $request): Response{ } }
Your controller will contain one public method for each action of your API.
You can create routes in 2 different ways:
- using a config file
- using annotations
Using a config file
This will automatically disable the annotation parser. The routes are described in a YAML file
Example of config file
routes: get-user: path: /users/{id} method: GET controller: Test\Controller\APIController::getUser create-user: path: /users method: POST controller: Test\Controller\APIController::createUser
Full example here: https://github.com/foxdie/plankton/blob/master/Test/config/server.yml
Configuring the server
use Plankton\Server\{Server, Config}; $server = new Server(new Config(CONFIG_PATH));
Full example here: https://github.com/foxdie/plankton/blob/master/Test/public/config-server.php
Using annotations
use Plankton\Server\Controller; class APIController extends Controller{ /** * @Route(/users/{id}) * @Method(GET) */ public function getUser(int $id, Request $request): Response{ } /** * @Route(/users) * @Method(POST) */ public function createUser(Request $request): Response{ } }
The routes will be created automatically according to the annotations @Route and @Method.
Full example here : https://github.com/foxdie/rest/blob/master/Test/Controller/APIController.php
@Route annotation
- accepts regular expresssions
- accepts placeholders: they will be passed as argument in the same order as they appear
- the spinal case is strongly recommended
You can add a route prefix to your controller:
/** * @Route(/users) */ class APIController extends Controller{ /** * @Route(/{id}) * @Method(GET) */ public function getUser(int $id, Request $request): Response{ } /** * @Route(/) * @Method(POST) */ public function createUser(Request $request): Response{ } }
@Method annotation
Possible values are:
- GET
- POST
- PUT
- PATCH
- DELETE
@Exception annotation
class APIController extends Controller{ /** * This will catch any \CustomNameSpace\CustomException * @Exception(CustomNameSpace\CustomException) */ public function catchCustomException(Exception $e, Request $request): Response{ } /** * This will catch all other exceptions * @Exception(*) */ public function catchException(Exception $e, Request $request): Response{ } }
Registering controllers
use Plankton\Server\Server; $server = new Server(); $server ->registerController(new APIController()); ->registerController(...); ->run();
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/simple-server.php
Creating middlewares
(this is optionnal) You must implement the Plankton\Server\Middleware interface. The middlewares can handle both incoming requests and outgoing responses.
use Plankton\Server\{Request, Response}; use Plankton\Server\{Middleware, RequestDispatcher}; class BasicAuthenticationMiddleware implements Middleware{ public function process(Request $request, RequestDispatcher $dispatcher): Response{ // ... return $dispatcher->process($request); } }
Full example here: https://github.com/foxdie/rest/blob/master/Test/Middleware/BasicAuthenticationMiddleware.php
Registering the middlewares
use Plankton\Server\Server; $server = new Server(); $server ->addMiddleware(new BasicAuthenticationMiddleware()) ->addMiddleware(...) ->registerController(new APIController()) ->run();
OAuth2
Client Credentials Grant
Client
use Plankton\Client\Client; use Plankton\Client\Strategy\ClientCredentialsAuthentication; use Plankton\Response; $client = new Client(API_ENDPOINT, new ClientCredentialsAuthentication( CLIENT_ID, CLIENT_SECRET, AUTHENTICATION_URL ));
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/oauth2-client.php
Server
use Plankton\Server\Server; use OAuth2\Middleware\ClientCredentialsMiddleware; use OAuth2\Provider\MemoryProvider; use Test\Controller\APIController; // Access Token provider $provider = new MemoryProvider(); $provider->addClient(CLIENT_ID, CLIENT_SECRET); $server = new Server(); $server ->addMiddleware(new ClientCredentialsMiddleware($provider)) ->registerController(new APIController()) ->run();
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/oauth2-server.php
Creating your own Access Token Provider
All you have to do is to implement the AccessTokenProvider interface:
use Plankton\OAuth2\Provider\AccessTokenProvider; use Plankton\OAuth2\Token\{AccessToken, BearerToken}; class PDOProvider implements AccessTokenProvider{ /** * return a new/issued Access Token if you find a client matching the authentication parameters (id + secret) */ public function getAccessToken(string $client_id, string $client_secret): ?AccessToken{ } /** * return a new Access Token if the Refresh Token is valid */ public function refreshToken(string $refreshToken): ?AccessToken{ } /** * authorize or not the given Access Token */ public function isValidAccessToken(string $token): bool{ } }
Logging
Client side
Simple logger
use Plankton\Logging\SimpleLogger; $client->setLogger(new SimpleLogger()); // ... do some requests foreach ($client->getLogger()->getLogs() as $request) { $response = $client->getLogger()->getLogs()[$request]; }
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/simple-client.php
XML logger
use Plankton\Logging\XMLLogger; $client->setLogger(new XMLLogger()); // ... do some requests header("Content-type: text/xml"); echo $client->getLogger()->getLogs()->asXML();
Full example here: https://github.com/foxdie/rest/blob/master/Test/public/oauth2-client.php
Custom logger
You have to implement the Plankton\Request\Logger interface:
use Plankton\{Request,Response}; use Plankton\Request\Logger class CustomLogger implements Logger{ public function log(Request $request, Response $response = NULL): void{ } }
Server side
You can easily log requests and responses by adding a middleware:
use Plankton\{Request,Response}; use Plankton\Server\{Middleware, RequestDispatcher}; class LogMiddleware implements Middleware{ public function process(Request $request, RequestDispatcher $dispatcher): Response{ $response = $dispatcher->process($request); // log $request and $response here return $response } }
and then register the middleware(#registering-the-middlewares)
use Plankton\Server\Server; use Test\Controller\APIController; use Test\Middleware\LogMiddleware; $server = new Server(); $server ->addMiddleware(new LogMiddleware()) ->registerController(new APIController()) ->run();