This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
fishyboat21/simple-api is a minimal PHP 8.4+ library for building JSON HTTP APIs using a per-file endpoint pattern. Each API endpoint is its own PSR-4 class file — the file path maps to the URL route. A pre-built trie-based route cache provides O(depth) route matching with zero filesystem scanning at runtime.
No build step, no framework, no ORM — just Composer autoloading.
PSR-4 mapping: Fishyboat21\SimpleApi\ → src/
After adding new classes under src/, run:
composer dump-autoloadAfter adding or changing endpoint handlers, regenerate the route cache:
composer route:generateThe main application class. Replaces the deprecated BaseAPI.
- Loads route cache via
require(opcache-friendly) - Handles CORS preflight (configurable via
CorsConfig) - Matches request to handler class via
RouteCollection(trie) - Dispatches to handler, applies CORS headers, sends JSON response
Error handling:
HttpExceptionsubclasses (404, 405) → their message is safe to exposeThrowable→ generic "Internal Server Error" (real message logged server-side via optional PSR-3 logger, never leaked)
Endpoint classes live under src/Api/ (in the consuming project). The filesystem path is the URL path:
src/Api/Health/Index.php → GET /api/health
src/Api/Users/Index.php → GET,POST /api/users
src/Api/Users/_id/Index.php → GET,PUT,DELETE /api/users/{id}
src/Api/Users/_id/Posts.php → GET /api/users/{id}/posts
Convention rules:
- Strip
src/Api/prefix and.phpsuffix - Convert to lowercase,
/separators - Trailing
/Indexsegment is removed (resource root) - Segments starting with
_become path parameters:_id→{id} - HTTP method declared via
#[Route(Method::GET)]attribute
Each endpoint class implements ApiHandler (marker interface) and uses #[Route] attributes:
#[Route(Method::GET)]
class Index implements ApiHandler
{
public function handle(Request $request): Response
{
return Response::ok(['data' => ...]);
}
}Generated by composer route:generate. A nested-array trie loaded via require:
return [
'users' => [
'__handlers' => ['GET' => ['App\Api\Users\Index', 'list'], ...],
'*' => ['__name' => 'id', '__handlers' => ['GET' => ['...', 'show'], ...]],
],
];Do not edit by hand. Regenerate after adding/changing endpoint files.
RouteCollection— Trie matcher, O(depth), static segment checked first (fast path),*for paramsRouteScanner— Scanssrc/Api/forApiHandlerclasses, reads#[Route]via reflection, builds trieRouteMatch— DTO:handlerClass,handlerMethod,paramsRouteGenerateCommand— CLI entry forvendor/bin/simple-api route:generate
Request—fromGlobals()/fromParts(), lazy JSON body parsing, query/body/header accessors, immutable path params viawithAttribute()Response— PHP 8.4 property hooks, factory methods:ok(),created(),noContent(),notFound(),error(),withHeader()for CORS/custom headers
Configurable: allowed origins, methods, headers, credentials, max age. Preflight returns proper Access-Control-Allow-* headers. Response headers applied after dispatch.
HttpException (abstract)
├── NotFoundException → 404
└── MethodNotAllowedException → 405
#[Route(Method::GET)] — repeatable, usable on classes (targets handle()) or methods (targets that method).
| Old | Replacement |
|---|---|
BaseAPI |
SimpleApi |
addHandler() |
#[Route] + ApiHandler |
IResponse |
Response |
ResponseError |
Response::error() |
ResponseSuccess |
Response::ok() |
Requires PHP >=8.4. Uses PHP 8.4 property hooks and readonly classes.
- Route cache loaded via
require— opcache caches to shared memory - Trie matching — O(depth), typically 2-5 array lookups per request
- Lazy handler loading — PSR-4 autoloader fires only after route match
- Lazy body parsing —
php://inputread only when handler callsbody() - No middleware stack — fixed pipeline: CORS → route → dispatch → send
- Zero filesystem scanning at runtime — route cache is pre-built