REST-like PHP micro-framework.

Overview

Phprest

Build Status Code Coverage Quality Score Software License

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

Skills

  • Dependency injection
  • Routing
  • Error handling
  • Serialization
  • Deserialization
  • HATEOAS
  • API versioning
  • Pagination
  • Logging

ToC

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 registerMiddlewarefunction.

$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 optional
  • until 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

id = $id; $this->value = $value; $this->created = $created; } }">
 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>

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.

Deserialization example

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())]);
        }
    }
# ...

Pagination


# ...
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

Responses

There are several responses you can use by default, one of them is the Ok response.

1xx, 2xx, 3xx status codes

These are simple Response objects.

Example


# ...
$app->get('/', function (Request $request) {
    return new Response\Ok('Hello World!');
});
# ...

Types

Responses
Accepted
Created
NoContent
NotModified
Ok

4xx, 5xx status codes

These are Exceptions.

Example


# ...
$app->get('/', function (Request $request) {
    # ...
    
    throw new \Phprest\Exception\BadRequest();
    
    # ...
});
# ...

Types

Exceptions
BadRequest
Conflict
Forbidden
Gone
InternalServerError
MethodNotAllowed
NotAcceptable
NotFound
TooManyRequests
PreconditionFailed
TooManyRequests
Unauthorized
UnprocessableEntity
UnsupportedMediaType

Dependency Injection Container

See Proton's doc and for more information please visit League/Container.

CLI

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:

  • Put your app instance to a specific file: app/app.php
  • You have to return with the bootstrapped app instance in the proper file
  • Put the path of the app instance in the paths.php file
  • You have to return with an array from the paths.php file with the app file path under the app array key

Error handler

Phprest handles error with League\BooBoo. The default formatter is Json and Xml Formatter.

On a single exception


# ...
$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>9code>
    <message>
        Code Red!]]>
    message>
result>

Authentication

Basic Authentication

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'
    ]
]);

JWT Authentication

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'
    ]
]);

API testing

There are a couple of great tools out there for testing your API.

  • Postman and Newman
  • Tip: Create collections in Postman and then run these in Newman
  • Frisby
  • Frisby is a REST API testing framework built on node.js and Jasmine that makes testing API endpoints easy, fast, and fun.
  • Runscope
  • For Api Monitoring and Testing

API documentation

Just a few recommendations:

  • API Blueprint
    • API Blueprint is a documentation-oriented API description language. A couple of semantic assumptions over the plain Markdown.
  • Swagger
    • With a Swagger-enabled API, you get interactive documentation, client SDK generation and discoverability.
Comments
  • Problem with the ErrorHandlers

    Problem with the ErrorHandlers

    In one of my endpoints, I validate a date param with the following code:

    public function index(Request $request)
    {
        if ($date = $request->query->get('date')) {
            try {
                $date = new \DateTime($date);
            } catch (\Exception $e) {
                throw new Exception\BadRequest();
            }
        }
    
        // ...
    }
    

    If I make a request to /endpoint?date=foobar I get the following response:

    {"code":0,"message":"Bad Request","details":[]}{"code":0,"message":"DateTime::__construct(): Failed to parse time string (foobar) at position 0 (f): The timezone could not be found in the database","details":[]}
    

    There's the BadRequest exception I thrown followed by the one I'm supposed to have caught.

    Am I doing it wrong?

    opened by marcaube 10
  • Update Proton

    Update Proton

    Proton (and the underlying libraries) changed a lot in the near past. Orno DI and Route are moved to the League, the container now supports ServiceProviders (which you called Serviceable I think), Proton has a simple configuration interface.

    I am happy to help in upgrading as this seems to be a good initiative.

    opened by sagikazarmark 8
  • Question: stable version, tags and releases

    Question: stable version, tags and releases

    Hi @adammbalogh,

    we are thinking about using this project to build a new API, but the lack of a stable version (or at least tagged versions) could be a deal-breaker. I realize that all the dependencies used in the project are stable, but being able to put a version contraint on phprest/phprest would make the choice easier for us.

    What do you think of tagging a 0.1.0 release and adding a 1.0.x-dev branch alias to the repository?

    opened by marcaube 6
  • Is there a way to replace the error formatter ?

    Is there a way to replace the error formatter ?

    I'm working on a project that has to adhere to the JSON API response format, and one on the things prescribed in the spec is the format and content of errors (see error examples).

    As far as I can tell, there is no way to replace the error formatter with my own in order to format the errors properly.

    opened by marcaube 4
  • Delete composer.lock

    Delete composer.lock

    It's normal for libraries to not ship with a lock file. Among other things, it makes it hard to contribute because people have to run composer to change dependencies.

    opened by GrahamCampbell 3
  • Annotations

    Annotations

    It might a good thing to create a mini section for annotation concepts in a Php manner. (?)

    A lot of people hate annotations in Php because "those are just comments". @rdohms has the most awesome presentations in this subject. I'd like to put a reference to this prezi in the annotation section.

    Should we do this or just leave annotation haters alone in the dark? :smiling_imp:

    question 
    opened by adammbalogh 3
  • Configuration

    Configuration

    The current configuration is messy.

    I don't want to be too strict, but I "have to" because I'd like to use some helper cli scripts:

    • routes:get
    • orm migrations
    • orm fixtures

    Now the scripts have concrete paths to their configurations. If this path does not exist then we turn to the paths.php file (which have to be in the root dir). This paths.php returns with an array with the actual config paths.

    We should find a better way to do this. (Or an improvement for this paths.php version) Maybe environment variables, but I would not go that way.

    enhancement help wanted 
    opened by adammbalogh 3
  • Prevent request from being overwritten

    Prevent request from being overwritten

    The request can be modified by middlewares before being dispatched, to add informations like GeoIP, Oauth scopes, etc. Proton\Application stores the request in the container during the handle() method, this is the request that should be dispatched to the controllers.

    The fix is simply to retrieve the request from the container instead of creating a brand new request from globals in Phprest\Router\Strategy.

    opened by marcaube 1
  • Update php version and dependencies

    Update php version and dependencies

    Hello, I have been using this package a long time for production projects and it is great.

    Will someone continue to maintain it? Maybe not new features but making a release with upgraded php version to 7 or 7.1 and upgrading the composer dependencies since the last time they were updated was 2 years ago.... Do you still accept PRs?

    Thanks :)

    opened by vladetd 3
Owner
Phprest
REST-like PHP micro-framework.
Phprest
A resource-oriented micro PHP framework

Bullet Bullet is a resource-oriented micro PHP framework built around HTTP URIs. Bullet takes a unique functional-style approach to URL routing by par

Vance Lucas 415 Dec 27, 2022
Lemon is php micro framework built for simple applications.

Lemon is simple micro framework that provides routing, etc.

Lemon 20 Dec 16, 2022
TidyPHP is a micro php framework to build web applications

TidyPHP is a micro MVC PHP Framework made to understand how PHP Frameworks work behind the scense and build fast and tidy php web applications.

Amin 15 Jul 28, 2022
Frankie - A frankenstein micro-framework for PHP

Frankie - A frankenstein micro-framework for PHP Features Frankie is a micro-framework focused on annotation. The goal is to use annotation in order t

null 19 Dec 10, 2020
ExEngine is an ultra lightweight micro-services framework for PHP 5.6+

ExEngine is an ultra lightweight micro-services framework for PHP 5.6+. Documentation Checkout the Wiki. Examples Click here to browse examples, also

linkfast.io 1 Nov 23, 2020
Silly CLI micro-framework based on Symfony Console

currentMenu home Silly CLI micro-framework based on Symfony Console. Professional support for Silly is available via Tidelift Video introduction in fr

Matthieu Napoli 862 Dec 23, 2022
Blink is a micro web framework for building long-running and high performance services

Blink is a micro web framework for building long-running and high performance services, the design heavily inspired by Yii2 and Laravel. Blink aims to provide the most expressive and elegant API and try to make the experience of web development as pleasant as possible.

Jin Hu 837 Dec 18, 2022
StackSync is a simple, lightweight and native fullstack PHP mini-framework.

StackSync is a fullstack PHP mini framework, with an MVC structure, custom API system with a Middleware and JWT authentication, components based views, flexible routing, PSR4 autoloading. Essential files generation (migrations, seeders, controllers and models) and other operations can be executed through custom commands.

Khomsi Adam 3 Jul 24, 2022
PHP微服务框架即Micro Service Framework For PHP

Micro Service Framework For PHP PHP微服务框架即“Micro Service Framework For PHP”,是Camera360社区服务器端团队基于Swoole自主研发现代化的PHP协程服务框架,简称msf或者php-msf,是Swoole的工程级企业应用框

Camera360 1.8k Jan 5, 2023
The Laravel Lumen Framework.

Lumen PHP Framework Laravel Lumen is a stunningly fast PHP micro-framework for building web applications with expressive, elegant syntax. We believe d

The Laravel Framework 7.6k Jan 7, 2023
Slim Framework 4 Skeleton Application

Slim Framework 4 Skeleton Application Use this skeleton application to quickly setup and start working on a new Slim Framework 4 application. This app

Slim Framework 1.5k Dec 29, 2022
🐺 Lightweight and easy to use framework for building web apps.

Wolff Web development made just right. Wolff is a ridiculously small and lightweight PHP framework, intended for those who want to build web applicati

Alejandro 216 Dec 8, 2022
Larasymf - mini framework for medium sized projects based on laravel and symfony packages

Larasymf, PHP Framework Larasymf, as its says is a mini framework for medium sized projects based on laravel and symfony packages We have not yet writ

Claude Fassinou 6 Jul 3, 2022
⚡ Flat-files and plain-old PHP functions rockin'on as a set of general purpose high-level abstractions.

Siler is a set of general purpose high-level abstractions aiming an API for declarative programming in PHP. ?? Files and functions as first-class citi

Leo Cavalcante 1.1k Dec 30, 2022
VELOX - The fastest way to build simple websites using PHP!

VELOX The fastest way to build simple websites using PHP! Table of Contents Installation About VELOX Architecture Config Classes Functions Themes Chan

Marwan Al-Soltany 53 Sep 13, 2022
A PHP client for (Spring Cloud) Netflix Eureka service registration and discovery.

PHP Netflix Eureka Client A PHP client for (Spring Cloud) Netflix Eureka service registration and discovery. Installation You can install this package

Hamid Mohayeji 72 Aug 21, 2022
Yet another PHP Microframework.

ρ Yet another PHP Microframework. The premise of this framework is to be backwards-compatible (PHP <= 5.6) with powerful utilities (Like caching and l

null 0 Apr 6, 2022
A PHP project/micro-package generator for PDS compliant projects or micro-packages.

Construct A PHP project/micro-package generator for PDS compliant projects or micro-packages. Installation Construct should be installed globally thro

Jonathan Torres 263 Sep 28, 2022
Rest API boilerplate for Lumen micro-framework.

REST API with Lumen 5.5 A RESTful API boilerplate for Lumen micro-framework. Features included: Users Resource OAuth2 Authentication using Laravel Pas

Hasan Hasibul 484 Sep 16, 2022
A micro web application providing a REST API on top of any relational database, using Silex and Doctrine DBAL

Microrest is a Silex provider to setting up a REST API on top of a relational database, based on a YAML (RAML) configuration file.

marmelab 187 Nov 17, 2022