Search by

gijsbos / apiserver

Gijsbos

API Server

Package info

github.com/Gijsbos/apiserver

pkg:composer/gijsbos/apiserver

Statistics

Installs: 329

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0


README

PHP Version License Build Status Issues Last Commit

Introduction

Lightweight, attribute-driven PHP API Server that simplifies route definition, request validation, and response filtering. It aims to make building structured REST APIs clean, fast, and type-safe, leveraging PHP 8+ attributes.

Requirements

Before running the application, make sure you have the following installed:

  • Web Server: Apache or Nginx
  • PHP: >= 8.2
  • Composer: for dependency management

Installation

composer require gijsbos/apiserver

Setup

Controllers

Extend your controllers with the RouteController class and create a new route by defining attributes:

  • Route(method, path) or short GetRoute, PostRoute, PutRoute, DeleteRoute, PatchRoute, OptionsRoute.
  • ReturnFilter(array) - assoc array containing keys to filter out.
  • RequiresAuthorization() - extends the ExecuteBeforeRoute, provides simple token checking and extraction.

Route Parameters

Route parameters allow you to inject parameters into your controller method.

  • PathVariable - Extracts parameters from path that have been defined using curly brackets.
  • RequestParam - Extracts parameters from global variables.
  • RequestHeader - Extracts headers from server headers.

Parameter behaviour can be controlled by both defining union types and optional parameters.

  • RequestParam|string $id - Expects a string value, when not defined defaults to empty string.
  • RequestParam|int $id - Expects an int value, cast to int or defaults to empty string.
  • RequestParam|float $id - Expects an float value, cast to float or defaults to empty string.
  • RequestParam|double $id - Expects an int value, cast to double or defaults to empty string.
  • RequestParam|bool $id - Expects an int value, cast to bool or defaults to empty string.

Allowing null values requires you to add the null union type:

  • RequestParam|string|null $id - Expects a string value, when not defined defaults to null.

Route parameter options can be defined by defining the object inside the parameter definition:

PathVariable|string $id = new PathVariable(["min" => 0, "max" => 999, "required" => true, "pattern" => "/\d+/", "default" => 1])

The following options can be used:

  • min int - minimum value for int values, minimum length for string values.
  • max int - maximum value for int values, maximum length for string values.
  • required bool - when true, throws missing error when parameter is not defined (has no effect on PathVariable).
  • pattern string - regexp pattern uses to check the value.
  • default mixed - fallback value when value is empty.
  • values array - permitted values or throws error.

Example

class UserController extends RouteController
{
    #[GetRoute('/user/{id}/')]
    #[ReturnFilter(['name','id'])]
    public function getUser(
        PathVariable|string $id = new PathVariable(["min" => 0, "max" => 999, "required" => false]),
        RequestParam|string $name = new RequestParam(["min" => 0, "max" => 10, "pattern" => "/^[a-z]+$/", "required" => false, "default" => "john"]),
    )
    {
        return [
            "id" => "<$id>",
            "name" => $name,
        ];
    }
}

Server

Create a server by defining the following in your index.php. Every request must be rewritten to the index.php file.

try
{
    $server = new Server([
        "requireHttps" => false,        // Must use HTTPS or receive error
        "pathPrefix" => "",             // Used for subpaths e.g. localhost/mysubpath/
        "addServerTime" => false,       // Adds code execution time
        "addRequestTime" => false,      // Adds total server response time
        "trustedProxies" => [],         // IPs / CIDR ranges of proxies allowed to set X-Forwarded-For and X-Forwarded-Proto
    ]);

    $server->listen();
}
catch(RuntimeException | Exception | TypeError | Throwable $ex)
{
    print($ex->getMessage());
}

Request and correlation ids

Every request gets two ids, both returned as response headers:

Header Source
$server->getRequestId() X-Request-Id Always generated by the server (UUID v4), never taken from the client
$server->getCorrelationId() X-Correlation-ID The trace id of an incoming traceparent (W3C Trace Context), else a valid X-Correlation-ID or X-Request-Id of the caller, else the request id

Incoming ids are client supplied: values with other characters than A-Z a-z 0-9 . _ : - or longer than 128 characters are ignored. Add both ids to log entries and pass the correlation id on to calls a request makes (within your own services).

Response headers

JSON responses are data and are not HTML escaped, clients encode values where they show them. Every response carries:

  • X-Content-Type-Options: nosniff, Cache-Control: no-store, Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
  • Strict-Transport-Security over HTTPS
  • X-Request-Id, X-Correlation-ID

A route that renders HTML sends its own Content-Security-Policy and Cache-Control. The format follows the Accept header: XML when application/xml is preferred, JSON otherwise (a browser navigation, which lists text/html, gets JSON). #[ResponseFormat("json")] fixes the format of a route, e.g. for documents a standard defines as JSON.

Caching Routes

The route cache is built from the loaded controllers: set CLI_SCAN_FOLDERS (e.g. src/Controllers) so they are found. When no routes are found the cache is not written and an error names the cause, instead of every request answering 404.

Routes paths are cached in the cache folder, you can use the bin/api binary to cache routes. When there is no routes file, a cache will be created upon first use.

./bin/api cache routes -(v)erbose (--autoload <file>)

Enable APCU in your PHP build for faster route resolving. Build Docker images with the following command:

RUN pecl install apcu && docker-php-ext-enable apcu

Contributions

Contributions are welcome!
Please open an issue or submit a pull request following our contribution guidelines.