ilariopro / zenigata
Requires
- php: ^8.2 || ^8.3 || ^8.4
- league/route: ^6.2
- php-http/discovery: ^1.20
- psr/container: ^2.0
- psr/http-factory: ^1.1
- psr/http-factory-implementation: *
- psr/http-server-middleware: ^1.0
- psr/log: ^3.0
Requires (Dev)
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5 || ^12.0
Suggests
- guzzlehttp/psr7: PSR-7 and PSR-17 message implementation.
- laminas/laminas-diactoros: PSR-7 and PSR-17 message implementation.
- nyholm/psr7: Lightweight PSR-7 and PSR-17 implementation.
README
⚠️ This project is in an early development stage. Feedback and contributions are welcome!
A lightweight, PSR-compliant HTTP framework for PHP 8.2+ built for flexibility and simplicity.
Built around standard interfaces and a composable architecture, it gives you full control over routing, middleware, and error responses — powered by League Route under the hood, with sensible defaults that work out of the box.
Requirements
Installation
composer require ilariopro/zenigata
Overview
Application
Application is the main entry point of the framework. It orchestrates the full HTTP request lifecycle and provides a centralized API to interact with all internal components: registering routes and middleware, setting the dispatch strategy, propagating shared state such as a PSR-11 container or debug mode, and running or emitting responses.
Application implements RouteCollectionInterface and StrategyAwareInterface from League Route, so all routing methods are available directly on the instance.
Routing & Middleware
Routing and middleware dispatching are powered by League Route. Incoming requests are matched to registered routes and processed through the middleware stack before the handler is invoked. Middleware can be applied globally or scoped to individual routes and groups.
BodyParserMiddleware parses the incoming request body based on the Content-Type header and attaches the parsed data to the request. Ships with built-in parsers for JSON, XML, and URL-encoded bodies, all replaceable or extendable.
Strategies
League Route's strategy system controls how route handlers are invoked and how their return values are converted into PSR-7 responses. ApplicationStrategy is used by default, meaning handlers must return a ResponseInterface. Other strategies such as JsonStrategy allow returning plain arrays or other values that are automatically converted.
Error
ErrorHandler catches any Throwable thrown during the request lifecycle and converts it into a PSR-7 error response via an error processor selected by the Accept header. Supports an optional PSR-3 logger and debug mode for full exception details. Ships with strategies for HTML (used by default), JSON, XML, and plain text.
Runtime
RequestInitializerbuilds a PSR-7ServerRequestInterfacefrom PHP superglobals using Nyholm PSR-7 Server, normalizing headers, uploaded files, and body content. PSR-17 factories are auto-discovered via php-http/discovery if not explicitly provided.ResponseEmittersends the final PSR-7 response to the client, automatically switching to chunked streaming for file downloads and range responses to minimize memory usage.
Usage
Minimal Setup
use Laminas\Diactoros\Response\TextResponse; use Zenigata\Http\Application; $app = new Application(); $app->get('/hello', function (ServerRequestInterface $request): ResponseInterface { return new TextResponse('Hello, world!'); }); $app->run();
Routes
$app->get('/users', [UserController::class, 'index']); $app->post('/users', [UserController::class, 'create']); $app->put('/users/{id}', [UserController::class, 'update']); $app->delete('/users/{id}', [UserController::class, 'delete']); // Multiple methods on the same path $app->map(['GET', 'POST'], '/contact', ContactController::class); // Route groups with a shared prefix $app->group('/api', function ($router) { $router->get('/users', [UserController::class, 'index']); $router->post('/users', [UserController::class, 'create']); });
Handlers
Handlers can be defined in several ways:
// Closure $app->get('/hello', function (ServerRequestInterface $request): ResponseInterface { ... }); // Invokable class $app->get('/hello', InvokableHandler::class); // [Class, method] pair $app->get('/users', [UserController::class, 'index']); // PSR-15 RequestHandlerInterface $app->get('/users', Psr15Handler::class);
When defined as strings or [Class, method] pairs, handlers are resolved from the container if one is configured, or instantiated automatically if the class has no mandatory constructor parameters.
Middleware
use Zenigata\Http\Middleware\BodyParserMiddleware; // Global middleware — applied to every request, in registration order $app->middleware(new BodyParserMiddleware()); $app->middleware(AuthMiddleware::class); // resolved from container // Multiple at once $app->middlewares([ new BodyParserMiddleware(), AuthMiddleware::class, ]); // Route-level middleware $app->get('/admin', AdminController::class) ->middleware(new AuthMiddleware()); // Group-level middleware $app->group('/admin', function ($router) { $router->get('/dashboard', [AdminController::class, 'dashboard']); })->middleware(new AuthMiddleware());
Strategies
The default ApplicationStrategy expects handlers to return a ResponseInterface. Switch to JsonStrategy to return plain arrays or objects that are automatically encoded:
use League\Route\Strategy\JsonStrategy; $strategy = new JsonStrategy(new ResponseFactory()); $app->setStrategy($strategy); $app->get('/users', function (): array { return [ 'id' => 1, 'name' => 'Alice' ]; });
Strategies can also be set per route or per group:
$app->get('/users', [UserController::class, 'index']) ->setStrategy(new JsonStrategy(new ResponseFactory()));
Error Handling
Any uncaught exception is passed to ErrorHandler, which selects the right strategy based on the Accept header. In debug mode, responses include the full exception details:
$app->setDebug(true);
Attach a PSR-3 logger to record errors alongside request context:
$app->setLogger($logger);
Container Integration
Pass any PSR-11 container to resolve middleware, handlers, and error processors by service ID:
$app->setContainer($container); $app->middleware('app.middleware.auth'); $app->get('/users', 'app.handler.users');
The container is automatically propagated to the active strategy and error handler.
Extensibility
Zenigata is designed for flexibility. Every internal component can be replaced by passing a custom implementation to the constructor:
$app = new Application( router: new Router(), errorHandler: new MyErrorHandler(), requestInitializer: new MyRequestInitializer(), responseEmitter: new MyResponseEmitter(), );
Custom error processors can be registered at any time:
$app->errorProcessor(new SentryErrorProcessor()); $app->errorProcessor(MyCustomProcessor::class);
You can also extend Application directly and override any protected method to customize specific behaviors without replacing entire components.
Contributing
Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
Keep the implementation minimal, focused, and well-documented, making sure to update tests accordingly.
See CONTRIBUTING for more information.
License
This library is licensed under the MIT license. See LICENSE for more information.