jacksonsr451/php-easy-http

This is a simple library by HTTP, with server and message and using PSRs.

Installs: 17

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 1

Forks: 0

Open Issues: 0

pkg:composer/jacksonsr451/php-easy-http

v2.4.1 2025-11-30 18:37 UTC

This package is not auto-updated.

Last update: 2025-12-28 19:00:17 UTC


README

Modern PSR-7/PSR-15 inspired toolkit for building lightweight HTTP services and FastAPI-like microservices in PHP. Ships with strict-typed message objects, a declarative router, middleware pipeline, and an ergonomic Application class for rapid development.

Requirements

  • PHP 8.2+ (tested through PHP 8.3)
  • Composer for dependency management

Installation

composer require jacksonsr451/php-easy-http

The package exposes all classes under the PhpEasyHttp\Http namespace through PSR-4 autoloading.

Key Features

  • PSR-7 message implementations (Message, Request, Response, ServerRequest, Stream, Uri, UploadFiles).
  • FastAPI-inspired Application with declarative routing helpers (get, post, put, patch, delete).
  • Dependency Injection Container for constructor-free service registration and parameter auto-wiring.
  • Middleware pipeline compatible with PSR-15 style process() methods plus helper registration (use, registerMiddleware).
  • Automatic response normalization: arrays/objects become JSON, scalars become JSON payloads, strings become text responses.
  • Route parameter binding & validation with typed handler signatures.

Project Structure

src/
├── Message/        # PSR-7 message implementations
├── Server/         # Application, Router, middleware, request handling
└── Server/Support/ # Container, ResponseFactory, callable handlers

Quick Start

<?php

use PhpEasyHttp\Http\Server\Application;

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

$app = new Application();

$app->get('/ping', fn () => ['message' => 'pong']);

$app->post('/items/{id}', function (int $id, array $body) {
	return [
		'id' => $id,
		'payload' => $body,
	];
});

$app->run();

Visit http://localhost:8000/ping to receive a JSON payload.

Declarative Routing

$app->put('/users/{userId}', function (int $userId, array $body) {
	return ['userId' => $userId, 'changes' => $body];
}, options: [
	'name' => 'users.update',
	'middleware' => ['auth'],
	'summary' => 'Update a user profile',
	'tags' => ['users'],
]);
  • Paths can contain {param} placeholders; values are injected by name.
  • Route metadata (name, summary, tags) is preserved for tooling or documentation generators.

FastAPI-Style Declarative Routes

Skip the manual route table by annotating controller methods with PHPDoc comments or native PHP attributes. Any public method that declares a route directive/attribute is automatically registered when you call registerControllers().

Using PHPDoc

use App\Controllers\UserController;
use PhpEasyHttp\Http\Server\Application;

$app = new Application();

/**
 * @RoutePrefix /api
 */
final class UserController
{
	/**
	 * @Route GET /users/{id}
	 * @Summary Fetch a single user
	 * @Tags users,read
	 * @Middleware auth
	 */
	public function show(int $id): array
	{
		return ['id' => $id];
	}
}

$app->registerControllers(UserController::class);

Using Native PHP Attributes

use App\Controllers\AdminController;
use PhpEasyHttp\Http\Server\Application;
use PhpEasyHttp\Http\Server\Support\Attributes\Route;
use PhpEasyHttp\Http\Server\Support\Attributes\RoutePrefix;

$app = new Application();

#[RoutePrefix('/admin')]
final class AdminController
{
	#[Route(method: ['GET', 'POST'], path: '/reports', middleware: ['auth'], summary: 'Reports', tags: ['reports'])]
	public function reports(): array
	{
		return ['reports' => []];
	}
}

$app->registerControllers(AdminController::class);
  • Use @Route or #[Route] to declare method + path; multiple HTTP verbs are allowed via arrays or GET|POST strings.
  • Optional metadata: Summary, Tags, Middleware, and Name map 1:1 between PHPDoc directives and attribute named parameters.
  • Add a class-level @RoutePrefix/@Prefix directive or #[RoutePrefix]/#[Prefix] attribute for shared path segments.
  • Controllers can be registered as class names or instances (resolved through the container when available).

API Gateway Style Dynamic Routes

The jacksonsr45/api-gateway package ships with a thin layer on top of Application that mimics APISIX-style route management. Routes can be loaded from JSON/YAML files, database records, or an admin API (via custom GatewayRouteSource implementations). Each GatewayRoute entry accepts:

Field Type Description
id string Unique identifier, becomes the route name.
methods string[] List of HTTP verbs (GET, POST, ...).
path string Same placeholder syntax as regular routes (/users/{id}).
handler string Optional local handler reference (Class@method or invokable class).
proxy.handler string Optional class implementing InternalProxyInterface for internal proxying.
middleware string[] Middleware names already registered in the Application.
summary/tags string / string[] Metadata for documentation.

Exactly one of handler or proxy must be provided. Proxy handlers receive the current ServerRequestInterface (with route params stored as request attributes) and return a PSR-7 response. Unlike APISIX, proxying is fully in-process (no external HTTP hop) so you can encapsulate orchestration logic inside PHP classes.

Loading Routes from a File

use Jacksonsr45\ApiGateway\Gateway;
use Jacksonsr45\ApiGateway\Loader\FileGatewayRouteSource;
use PhpEasyHttp\Http\Server\Application;

$app = new Application();
$gateway = new Gateway($app);

$gateway->addSource(new FileGatewayRouteSource(__DIR__ . '/routes/gateway.yaml'));
$gateway->boot();

$app->run();

gateway.yaml (JSON works the same):

routes:
	- id: healthcheck
		methods: [GET]
		path: /health
		handler: App\Http\HealthcheckHandler
	- id: users-service
		methods: [GET, POST]
		path: /users
		proxy:
			handler: App\Gateway\UsersProxy
		middleware: [auth]

Create additional GatewayRouteSource implementations (database, HTTP admin API, etc.) and feed them to Gateway::addSource() to merge multiple configuration backends. Because the router builds on top of the existing Application, you can reuse FastAPI-style handlers, middleware, and dependency injection with zero duplication.

Handler Parameter Binding

Handlers can type-hint any of the following and the application resolves them automatically:

Parameter type Injection source
ServerRequestInterface Full PSR-7 request instance
Scalar/int/float/bool Route parameter with implicit casting
array $body Parsed JSON or form body
array $query Query parameters
Any class name Service container entry or auto-instantiated class
ResponseFactory Convenience factory for JSON/text helpers

If a parameter cannot be resolved and lacks a default value, an exception is thrown to highlight configuration issues early.

Dependency Injection

use Psr\Log\LoggerInterface;

$app->register(LoggerInterface::class, fn () => new Monolog\Logger('api'));

$app->get('/secure', function (LoggerInterface $logger) {
	$logger->info('secure endpoint accessed');
	return ['ok' => true];
});
  • register(string $id, callable|object|string $concrete) binds services.
  • Singleton-style instantiation: the container caches resolved instances.
  • String bindings are treated as class names and instantiated lazily.

Middleware

Implement MiddlewareInterface (PSR-15 style) or reuse existing classes.

use PhpEasyHttp\Http\Server\Middleware;
use PhpEasyHttp\Http\Server\Interfaces\RequestHandlerInterface;
use PhpEasyHttp\Http\Message\Interfaces\ServerRequestInterface;

final class AuthMiddleware extends Middleware
{
	public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
	{
		if (!$request->getAttribute('user')) {
			return (new ResponseFactory())->json(['error' => 'Unauthorized'], 401);
		}

		return $handler->handle($request);
	}
}

$app->registerMiddleware('auth', AuthMiddleware::class);
$app->use('auth');                // global middleware
$app->get('/private', fn () => ['secret' => true]);
$app->get('/public', fn () => ['hello' => 'world'], options: ['middleware' => []]);
  • use() attaches middleware globally in the order registered.
  • Route-level middleware can be provided via the middleware option array.
  • When registering middleware by string name, Application looks it up in the middleware map before instantiating.

Response Handling

Your handler may return:

  • A ResponseInterface if you need full control.
  • An array/object → automatically encoded as JSON with application/json headers.
  • A string → emitted as text/plain; charset=utf-8.
  • Scalars/bools/null → wrapped in a JSON envelope ({"data": ...}).

You can create responses manually with ResponseFactory:

use PhpEasyHttp\Http\Server\Support\ResponseFactory;

$app->get('/download', function (ResponseFactory $responses) {
	return $responses->text('custom body', 202, ['X-Trace' => 'abc']);
});

Working with Requests

  • ServerRequest::getParsedBody() inspects content-type and will decode JSON or form data automatically.
  • ServerRequest::withUploadedFiles() accepts PSR-7 UploadFileInterface objects.
  • Helper methods such as inPost() or withAttribute() allow you to tag requests while processing middleware.

Running & Emitting Responses

$app->run() returns the generated response by default and also emits it (headers + body). To take control of the emission (useful in testing pipelines) set emit: false:

$response = $app->run(emit: false);

// Assert, inspect, or emit manually
$app->emit($response);

Testing

Create ServerRequest instances manually and pass them to run():

use PhpEasyHttp\Http\Message\ServerRequest;
use PhpEasyHttp\Http\Message\Uri;

$request = new ServerRequest('GET', new Uri('http://localhost/ping'));
$response = $app->run($request, emit: false);

$this->assertSame(200, $response->getStatusCode());
$this->assertSame('{"message":"pong"}', (string) $response->getBody());

Roadmap

  • Validation & schema-based request parsing
  • Automatic OpenAPI generation from route metadata
  • Async/worker adapters for popular PHP runtime servers

Contributing

  1. Fork the repository and create a topic branch.
  2. Run composer install followed by composer test (when available).
  3. Submit a pull request with a concise description of your changes and any relevant tests.

Please open an issue if you encounter bugs or have feature requests.