zenigata/http

Installs: 5

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/zenigata/http

0.1.9 2025-11-25 21:29 UTC

This package is auto-updated.

Last update: 2025-12-12 16:55:31 UTC


README

⚠️ This project is in an early development stage. Feedback and contributions are welcome!

Lightweight, PSR-15 compliant HTTP runner and middleware framework built for composability and simplicity.

Zenigata HTTP provides a clean abstraction for handling the full HTTP lifecycle: request initialization, middleware dispatching, routing, and response emission, while offering a modular architecture that allows you to freely combine components, and being fully Dependency Injection friendly

Zenigata HTTP draws inspiration from the modern PHP interoperability standards and aims to provide a cohesive, framework-agnostic HTTP kernel for PHP developers.

Requirements

Installation

composer require zenigata/http

Overview

HttpRunner.

  • Orchestrates the PSR-15 HTTP flow, serving as the "engine" of the HTTP application.
  • Combines:
    • A RequestHandlerInterface (e.g., Router or Dispatcher) that handles the main request logic and produces a PSR-7 response.
    • A ServerRequestInterface Initializer which builds the initial ServerRequestInterface from PHP globals or provided arrays.
    • A ResponseInterface Emitter responsible for sending headers and body to the client according to PSR-7.
    • An ErrorHandler that logs exceptions, formats error responses, and includes debug info when enabled.

Router

  • PSR-15 compatible handler built on top of on FastRoute.
  • Supports route groups, middleware stacks, and container-based resolution.
  • Uses a HandlerResolver to convert route definitions into executable PSR-15 handlers.
  • By default, accepts the following handler types:
    • String identifiers, resolved via container or reflection.
    • Callables, with signature function(ServerRequestInterface $request): ResponseInterface.
    • [Class, method] controller pairs
    • Instances of RequestHandlerInterface

Dispatcher

  • A PSR-15 compatible middleware dispatcher.
  • Executes middleware sequentially, passing the request through each layer until it reaches the final handler.
  • If no final handler is provided it throws an HttpError with status code 404 (Not Found).

RouterMiddleware

  • Middleware wrapper for the Router.
  • Allows routing to be part of a larger middleware stack.

ResponseBuilder

  • Automatically detect PSR-17 factories using the Factory utility from middleware/utils.
  • Provides convenience methods to build PSR-7 ResponseInterface instances (e.g. jsonResponse, htmlResponse, fileResponse, etc).
  • Can be reused through the ResponseBuilderTrait to share response-building logic across handlers.

ErrorHandler

  • Optionally accepts a Psr\Log\LoggerInterface to log thrown exceptions.
  • Supports custom error formatters to convert exceptions into response bodies.
  • When debug mode is enabled, the response will include stack trace and exception details.

HttpError

  • Represents an HTTP-specific exception that maps directly to a status code.
  • Validates that the code is within the 4xx–5xx range.
  • Automatically assigns the standard reason phrase if no message is provided.
  • Stores the original ServerRequestInterface that triggered the error, accessible via getRequest().

Usage

Example 1 — Using the Router as the main handler

use Laminas\Diactoros\Response\HtmlResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zenigata\Http\HttpRunner;
use Zenigata\Http\Router\Router;
use Zenigata\Http\Router\Route;

$router = new Router([
    Route::get('/', function (ServerRequestInterface $request): ResponseInterface {
        return new HtmlResponse('Hello World');
    }),
    Route::get('/hello/{name}', function (ServerRequestInterface $request): ResponseInterface {
        return new HtmlResponse("Hello {$request->getAttribute('name', 'World')}");
    }),
]);

$runner = new HttpRunner($router);
$runner->run();

This example registers two simple routes (/ and /hello/{name}), automatically initializes the request, handles it through the router, and emits the final response to the client.

Example 2 — Using the Middleware Dispatcher

use Zenigata\Http\HttpRunner;
use Zenigata\Http\Middleware\Dispatcher;
use Zenigata\Http\Middleware\JsonPayloadMiddleware;
use Zenigata\Http\Middleware\UrlEncodePayloadMiddleware;
use Zenigata\Http\Router\Router;

$dispatcher = new Dispatcher(
    middleware: [
        new JsonPayloadMiddleware(),
        new UrlEncodePayloadMiddleware(),
    ],
    handler: new Router($routes)
);

$runner = new HttpRunner($dispatcher);
$runner->run();

The Dispatcher executes middleware in registration order (FIFO), each having the opportunity to process or modify the request and response before passing control to the next one.

Once all middleware are processed, the final handler handles the request and produces a response (in this case, the Router).

Example 3 — Using the Router as Middleware

You can easily use the router as middleware using the built-in RouterMiddleware:

use Zenigata\Http\HttpRunner;
use Zenigata\Http\Middleware\Dispatcher;
use Zenigata\Http\Middleware\JsonPayloadMiddleware;
use Zenigata\Http\Middleware\UrlEncodePayloadMiddleware;
use Zenigata\Http\Router\Router;

$dispatcher = new Dispatcher(
    middleware: [
        new JsonPayloadMiddleware(),
        new Router($routes),
        new CustomPostRouterMiddleware(),
    ],
    // If no final handler is defined, the Dispatcher will throw an HttpError (404 Not Found).
);

$runner = new HttpRunner($dispatcher);
$runner->run();

The RouterMiddleware behaves exactly like the Router, but can be placed anywhere within a middleware stack. It supports the same constructor arguments and methods as the Router, including route registration, container-based resolution, and caching.

Example 4 — Using a PSR-11 Container

Zenigata HTTP can integrate with any PSR-11 container to lazily resolve middleware, handlers, or routes declared by class name or service ID.

In this example, the container will instantiate middleware and handlers automatically when needed.

use DI\Container;
use Psr\Container\ContainerInterface;
use Zenigata\Http\HttpRunner;
use Zenigata\Http\Middleware\Dispatcher;
use Zenigata\Http\Router\Route;
use Zenigata\Http\Router\Router;

// Example with PHP-DI
$container = new Container();

$container->set(JsonPayloadMiddleware::class, new JsonPayloadMiddleware());
$container->set(UrlEncodePayloadMiddleware::class, new UrlEncodePayloadMiddleware());

$container->set('routes', [
    Route::get('/', HomeHandler::class),
    Route::get('/hello/{name}', HelloHandler::class),
]);

$container->set(Router::class, function (ContainerInterface $container) {
    return new Router(
        routes:    $container->get('routes'),
        container: $container
    );
});

$dispatcher = new Dispatcher(
    middleware: [
        JsonPayloadMiddleware::class,
        UrlEncodePayloadMiddleware::class,
    ],
    handler:   Router::class,
    container: $container
);

$runner = new HttpRunner($dispatcher);
$runner->run();

When a middleware or handler is declared as a string (class name or service identifier), the dispatcher and router will ask the container to resolve it. If the container does not contain the entry, Zenigata HTTP falls back to reflection-based instantiation via ReflectionHelper. This mechanism only works for classes that have no required constructor dependencies.

This approach allows lazy loading, dependency injection, and testability while keeping middleware configuration declarative.

Error Handling

The runner delegates any uncaught exception or error to an ErrorHandlerInterface, which must return a valid ResponseInterface.

If no error handler is explicitly provided, a default ErrorHandler instance is automatically created.

$runner = new HttpRunner($router, debug: true);

When debug mode is enabled, the default error handler will include detailed exception information (stack traces, messages, etc.) in the response body, useful for development and testing.

You can provide your own implementation by passing it to the constructor:

$runner = new HttpRunner($router, errorHandler: new CustomErrorHandler());

Extensibility

Zenigata HTTP is designed for flexibility and extensibility:

  • Implement your own InitializerInterface to create PSR-7 requests from non-standard sources (e.g. CLI, tests, or custom environments).
  • Implement a custom EmitterInterface to emit responses differently (e.g. buffered output, async streaming).
  • Integrate any PSR-15 compatible middleware, router, or handler.
  • Plug in a PSR-11 container to lazily resolve handlers and middleware by service ID.
  • Extend or replace the ErrorHandlerInterface to customize error rendering, logging, or formatting.
  • Customize how the Router resolves handlers by providing your own implementations of HandlerResolverInterface.

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.