apivalk/apivalk

A Lightweight, Framework-Agnostic REST API Ecosystem for PHP. Built for speed, precision, and type-safe development.

Maintainers

Package info

github.com/apivalk/apivalk

Homepage

pkg:composer/apivalk/apivalk

Statistics

Installs: 3 502

Dependents: 0

Suggesters: 0

Stars: 5

Open Issues: 15

v2.2.1 2026-05-17 18:06 UTC

This package is auto-updated.

Last update: 2026-05-17 18:09:19 UTC


README

Apivalk

Apivalk ๐Ÿฆ…

Packagist Version PHP License Docs

OpenAPI-first PHP framework for type-safe REST APIs. Framework-agnostic ยท PSR-7/15/11/3 ยท PHP 7.2+

The Problem

OpenAPI specs drift from code. $_POST['name'] has no type. Nobody knows which endpoints are secured. You maintain two things โ€” the code and the docs โ€” and they never quite agree.

Apivalk makes your PHP classes the single source of truth. Define a property once โ†’ automatic validation, type casting, OpenAPI 3.0 generation, and full IDE autocompletion.

Why Apivalk? ๐Ÿค”

  • ๐Ÿ“„ Code is the spec โ€” getDocumentation() on your request/response classes drives validation, type casting, and OpenAPI generation from one definition. No annotation parsing, no separate YAML.
  • ๐Ÿ” Zero route registration โ€” drop a controller into your directory. ClassLocator auto-discovers it and caches the route index. No config files to update.
  • โšก Resource CRUD โ€” one AbstractResource declaration generates five typed CRUD endpoints with full OpenAPI coverage. ~15 hand-authored classes collapse into one resource + five thin controllers.
  • ๐Ÿ”’ Security built in โ€” JWT (JWK-based), scope enforcement, and three route security levels out of the box.
  • ๐Ÿง  Typed everything โ€” by the time __invoke() runs, input is sanitized, validated, and cast. You get $request->body()->name, not $_POST['name'].
  • ๐Ÿ’ก Full IDE autocompletion โ€” DocBlockGenerator rewrites your request classes with typed @method annotations and generates Shape/ classes per bag. $request->body()->, $request->sorting()->, $request->filtering()-> all autocomplete with correct types in PhpStorm and VS Code โ€” zero hand-written boilerplate.

Installation

composer require apivalk/apivalk

PHP 7.2+, ext-json, ext-mbstring โ€” full installation guide โ†’

Quick Start

Bootstrap

<?php
declare(strict_types=1);

use apivalk\apivalk\Apivalk;
use apivalk\apivalk\ApivalkConfiguration;
use apivalk\apivalk\ApivalkExceptionHandler;
use apivalk\apivalk\Cache\FilesystemCache;
use apivalk\apivalk\Middleware\RequestValidationMiddleware;
use apivalk\apivalk\Middleware\SanitizeMiddleware;
use apivalk\apivalk\Router\Router;
use apivalk\apivalk\Util\ClassLocator;

require __DIR__ . '/vendor/autoload.php';

$classLocator = new ClassLocator(__DIR__ . '/src/Http/Controller', 'App\\Http\\Controller');
$router = new Router($classLocator, new FilesystemCache(__DIR__ . '/var/cache'));

$configuration = new ApivalkConfiguration(
    $router,
    null,                                        // default: JsonRenderer
    [ApivalkExceptionHandler::class, 'handle']
);

$configuration->getMiddlewareStack()->add(new SanitizeMiddleware());
$configuration->getMiddlewareStack()->add(new RequestValidationMiddleware());

$apivalk = new Apivalk($configuration);
$response = $apivalk->run();
$apivalk->getRenderer()->render($response);

Every controller in src/Http/Controller is auto-discovered on first boot and cached. No routes to register. โ†’ Configure Apivalk

Define an Endpoint

Every endpoint is a Controller + Request + Response triplet. The Request defines the shape โ€” it drives validation and OpenAPI. The Response defines the output schema.

// Controller โ€” owns the route and the business logic
final class ReadPetController extends AbstractApivalkController
{
    public static function getRoute(): Route
    {
        return Route::get('/v1/pets/{id}')->description('Get a pet by ID');
    }

    public static function getRequestClass(): string  { return ReadPetRequest::class; }
    public static function getResponseClasses(): array { return [ReadPetResponse::class, NotFoundApivalkResponse::class]; }

    public function __invoke(ApivalkRequestInterface $request): AbstractApivalkResponse
    {
        $pet = $this->petRepo->find($request->path()->id); // id is cast to int automatically
        return $pet ? new ReadPetResponse($pet) : new NotFoundApivalkResponse('Pet not found');
    }
}

// Request โ€” declares the input shape; drives validation + OpenAPI
class ReadPetRequest extends AbstractApivalkRequest
{
    public static function getDocumentation(): ApivalkRequestDocumentation
    {
        $doc = new ApivalkRequestDocumentation();
        $doc->addPathProperty(new IntegerProperty('id', 'Pet ID'));
        return $doc;
    }
}

// Response โ€” declares the output shape; drives OpenAPI schema
class ReadPetResponse extends AbstractApivalkResponse
{
    public static function getStatusCode(): int { return self::HTTP_200_OK; }

    public static function getDocumentation(): ApivalkResponseDocumentation
    {
        $doc = new ApivalkResponseDocumentation();
        $doc->addProperty(new IntegerProperty('id', 'Pet ID'));
        $doc->addProperty(new StringProperty('name', 'Pet name'));
        return $doc;
    }

    public function toArray(): array { return ['id' => $this->pet['id'], 'name' => $this->pet['name']]; }
}

RequestValidationMiddleware returns 422 with field-level errors automatically. โ†’ Controllers ยท Requests ยท Responses

Features

๐Ÿ›ฃ๏ธ Routing

Auto-discovery, filesystem route caching, fluent builder (Route::get/post/put/patch/delete), automatic 404/405 handling, path parameters via {name} syntax. โ†’ Routing docs

๐Ÿ’ก IDE Autocompletion via DocBlock Generator

Run DocBlockGenerator once (or as a CI step) and your request classes get full IDE support โ€” no hand-written boilerplate.

Before:

class ReadPetRequest extends AbstractApivalkRequest { /* empty */ }

After (auto-generated):

/**
 * @method ParameterBag|Shape\ReadPetPathShape path()
 * @method ParameterBag|Shape\ReadPetBodyShape body()
 * @method SortBag|Shape\ReadPetSortingShape sorting()
 * @method FilterBag|Shape\ReadPetFilteringShape filtering()
 * @method Paginator|null paginator()
 */
class ReadPetRequest extends AbstractApivalkRequest { /* still empty */ }

$request->path()->id, $request->body()->name, $request->sorting()->createdAt โ€” all autocomplete with their correct types. Works for resource controllers too: DocBlockGenerator emits @property annotations on AbstractResource subclasses and generates typed list request classes (AnimalListRequest) with fully wired sort/filter/paginator shapes.

$generator = new DocBlockGenerator();
$generator->run('/src/Http/Controller', 'App\\Http\\Controller');

โ†’ DocBlock generator docs ยท Generate how-to

๐Ÿง… Middleware Pipeline

Onion-style PSR-15 pipeline. Built in: SanitizeMiddleware, RequestValidationMiddleware, AuthenticationMiddleware, SecurityMiddleware, RateLimitMiddleware. Trivial to extend with your own. โ†’ Middleware docs ยท Custom middleware

๐Ÿ”’ Security & Authorization

Three route security levels โ€” public, authenticated-only, scoped. JwtAuthenticator supports JWK endpoints out of the box. Missing scope โ†’ 403 Forbidden. No token โ†’ 401 Unauthorized. Custom authenticators supported via AuthenticatorInterface. โ†’ Security docs ยท JWT how-to ยท API key how-to

๐Ÿ“„ OpenAPI 3.0 Generation

OpenAPIGenerator introspects every controller's request and response classes and emits a complete OpenAPI 3.0 spec โ€” including pagination envelopes, X-RateLimit-* headers, locale headers, and per-operation security requirements. No annotations. Run it as a bin/ script and drop the JSON behind Swagger UI. โ†’ OpenAPI generator ยท Generate how-to

๐Ÿ“ฆ Resource CRUD

Declare an AbstractResource once โ€” identifier, properties, filters, sortings โ€” and get five fully typed, validated, OpenAPI-documented CRUD endpoints with matching response envelopes. Only __invoke() is yours to write. โ†’ Resources ยท Resource CRUD how-to

๐Ÿ“ƒ Pagination

Three strategies per route: Pagination::page(), Pagination::offset(), Pagination::cursor(). Apivalk handles query param validation, paginator hydration, and JSON envelope (data + pagination). All shapes documented in OpenAPI automatically. โ†’ Pagination docs ยท Pagination how-to

๐Ÿ”ข Sorting & Filtering

Declare allowed sort fields and filter types on the route. Sorting defaults are applied when order_by is omitted โ€” $request->sorting() is always populated. Undeclared filter keys are silently ignored. โ†’ Sorting ยท Filtering

โฑ๏ธ Rate Limiting

Per-route IP-based rate limiting. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset on every response; Retry-After on 429. Documented in OpenAPI automatically. โ†’ Rate limiting

๐ŸŒ Localization

Locale resolved from Accept-Language on every request, Content-Language set on every response. Both headers documented in OpenAPI. Zero boilerplate in controllers. โ†’ Localization docs

โš™๏ธ Dependency Injection

Pass any PSR-11 container โ€” PHP-DI, Symfony DI, or your own. Apivalk uses it to resolve controllers, enabling full constructor injection. Without a container it falls back to new ControllerClass(). โ†’ Configuration

Built-in Error Responses

BadRequestApivalkResponse (400) ยท UnauthorizedApivalkResponse (401) ยท ForbiddenApivalkResponse (403) ยท NotFoundApivalkResponse (404) ยท MethodNotAllowedApivalkResponse (405) ยท BadValidationApivalkResponse (422) ยท TooManyRequestsApivalkResponse (429) ยท InternalServerErrorApivalkResponse (500)

Contributing & Local Development

docker compose build
docker compose run --rm php72 composer install
docker compose run --rm php72 composer test      # PHPUnit
docker compose run --rm php72 composer phpstan   # PHPStan level 6

Own PHP 7.2+ setup? Docker is optional โ€” DDEV, Lando, or native all work. PHPStan runs at level 6; new code must not add violations (a baseline covers pre-existing issues). โ†’ Contributing guide

๐Ÿ“š docs.apivalk.com ยท ๐ŸŒ apivalk.com ยท ๐Ÿ› Issues

ยฉ 2025 Apivalk. MIT License. Maintainer: Dominic Poppe.