Getting Started
Get up and running with Waypoint in under 5 minutes.
Table of contents
Installation
Install Waypoint via Composer:
composer require ascetic-soft/waypoint
Requirements:
- PHP >= 8.4
- ext-mbstring
Optional PSR packages
The core matching engine works as pure PHP. For PSR-15 request handling (middleware, DI, Router::handle()), install the PSR packages:
composer require psr/http-message psr/http-server-handler psr/http-server-middleware psr/container
Quick Start
Waypoint separates route registration (RouteRegistrar) from request dispatching (Router).
Step 1: Register routes
use AsceticSoft\Waypoint\RouteRegistrar;
$registrar = new RouteRegistrar();
$registrar->get('/hello/{name}', function (string $name) use ($responseFactory) {
$response = $responseFactory->createResponse();
$response->getBody()->write("Hello, {$name}!");
return $response;
});
Step 2: Create the router
use AsceticSoft\Waypoint\Router;
$router = new Router($container, $registrar->getRouteCollection());
Step 3: Handle requests
use Nyholm\Psr7\ServerRequest;
$request = new ServerRequest('GET', '/hello/world');
$response = $router->handle($request);
Waypoint automatically extracts {name} from the URL and injects it into your handler. No manual parsing required.
Using Controllers
For real applications, use controller classes instead of closures:
$registrar = new RouteRegistrar();
$registrar->get('/users', [UserController::class, 'list']);
$registrar->post('/users', [UserController::class, 'create']);
$registrar->get('/users/{id:\d+}', [UserController::class, 'show']);
$registrar->put('/users/{id:\d+}', [UserController::class, 'update']);
$registrar->delete('/users/{id:\d+}', [UserController::class, 'destroy']);
Controller methods receive route parameters automatically with type coercion:
class UserController
{
public function show(int $id, UserRepository $repo): ResponseInterface
{
// $id is automatically cast to int
// $repo is resolved from the PSR-11 container
$user = $repo->find($id);
// ...
}
}
Route parameters are matched by name and automatically cast to the type declared in the method signature (int, float, bool). Container services are resolved by type-hint.
Using Attributes
Instead of manual registration, declare routes directly on your controllers:
use AsceticSoft\Waypoint\Attribute\Route;
#[Route('/api/users', middleware: [AuthMiddleware::class])]
class UserController
{
#[Route('/', methods: ['GET'], name: 'users.list')]
public function list(): ResponseInterface { /* ... */ }
#[Route('/{id:\d+}', methods: ['GET'], name: 'users.show')]
public function show(int $id): ResponseInterface { /* ... */ }
#[Route('/', methods: ['POST'], name: 'users.create')]
public function create(ServerRequestInterface $request): ResponseInterface { /* ... */ }
}
Then load them:
$registrar = new RouteRegistrar();
// Load specific classes
$registrar->loadAttributes(UserController::class, PostController::class);
// Or scan an entire directory
$registrar->scanDirectory(__DIR__ . '/Controllers', 'App\\Controllers');
// Optionally filter by filename pattern
$registrar->scanDirectory(__DIR__ . '/Controllers', 'App\\Controllers', '*Controller.php');
A Complete Bootstrap Example
<?php
// bootstrap.php
use AsceticSoft\Waypoint\Cache\RouteCompiler;
use AsceticSoft\Waypoint\RouteRegistrar;
use AsceticSoft\Waypoint\Router;
use AsceticSoft\Waypoint\Exception\RouteNotFoundException;
use AsceticSoft\Waypoint\Exception\MethodNotAllowedException;
require_once __DIR__ . '/vendor/autoload.php';
$cacheFile = __DIR__ . '/cache/routes.php';
$compiler = new RouteCompiler();
// In production: load from cache; otherwise register and compile
if ($compiler->isFresh($cacheFile)) {
$router = new Router($container);
$router->loadCache($cacheFile);
} else {
$registrar = new RouteRegistrar();
$registrar->scanDirectory(__DIR__ . '/Controllers', 'App\\Controllers');
// Compile for next request
$compiler->compile($registrar->getRouteCollection(), $cacheFile);
$router = new Router($container, $registrar->getRouteCollection());
}
// Global middleware
$router->addMiddleware(CorsMiddleware::class);
// Handle request
try {
$response = $router->handle($request);
} catch (RouteNotFoundException $e) {
// 404 Not Found
} catch (MethodNotAllowedException $e) {
// 405 Method Not Allowed
$allowed = $e->getAllowedMethods();
}
What’s Next?
- Routing — Route registration, parameters, groups, and attribute-based routing
- Middleware — Global and per-route PSR-15 middleware
- Advanced — Dependency injection, URL generation, caching, and diagnostics
- Internals — Algorithms, data structures, and architecture diagrams
- API Reference — Complete reference for all public classes and methods