Psr15factory
Powerful and flexible factory for creating PSR-15 compatible middleware with automatic dependency injection and request data mapping
Install / Use
/learn @bermudaphp/Psr15factoryREADME
Bermuda PSR-15 Factory
A powerful and flexible factory for creating PSR-15 compatible middleware in PHP. Supports various types of middleware definitions, automatic dependency injection, request data mapping, and multiple middleware patterns.
Requirements
- PHP 8.4+
- PSR-7 HTTP Message Interface
- PSR-11 Container Interface
- PSR-15 HTTP Server Request Handlers
Installation
composer require bermudaphp/psr15factory
Quick Start
Basic Setup
use Bermuda\MiddlewareFactory\MiddlewareFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
// Create factory
$factory = MiddlewareFactory::createFromContainer($container);
// Create middleware from various definitions
$middleware1 = $factory->makeMiddleware('App\\Middleware\\AuthMiddleware');
$middleware2 = $factory->makeMiddleware(function(ServerRequestInterface $request, callable $next): ResponseInterface {
// Single-pass middleware
return $next($request);
});
$middleware3 = $factory->makeMiddleware([$middlewareArray]);
Controller Example
use Bermuda\MiddlewareFactory\Attribute\MapQueryParameter;
use Bermuda\MiddlewareFactory\Attribute\MapRequestPayload;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class UserController
{
public function getUsers(
#[MapQueryParameter] int $page = 1,
#[MapQueryParameter] int $limit = 10,
#[MapQueryParameter('q')] string $search = ''
): ResponseInterface {
// Automatically extracts ?page=2&limit=20&q=john
// $page = 2, $limit = 20, $search = "john"
return new JsonResponse(['users' => $this->userService->find($search, $page, $limit)]);
}
public function createUser(
#[MapRequestPayload(['full_name' => 'name'])] CreateUserRequest $request
): ResponseInterface {
// Automatically maps JSON payload to DTO with field renaming
return new JsonResponse(['user' => $this->userService->create($request)]);
}
}
Middleware Types
1. Class Name Strategy
Resolves middleware by class name from the container:
// Register in container
$container->set(AuthMiddleware::class, new AuthMiddleware());
// Usage
$middleware = $factory->makeMiddleware(AuthMiddleware::class);
2. Callable Strategy
Automatically detects and adapts various callable patterns. Supports multiple callable formats through built-in CallableResolver:
Supported callable formats:
1. Closures
$middleware = $factory->makeMiddleware(function(ServerRequestInterface $request, callable $next): ResponseInterface {
return $next($request);
});
2. Standard PHP callables
$middleware = $factory->makeMiddleware([$object, 'methodName']);
$middleware = $factory->makeMiddleware('globalFunction');
3. String representations:
- Static class methods:
"Class::method"
$middleware = $factory->makeMiddleware('App\\Middleware\\AuthMiddleware::handle');
- Container service methods:
"serviceId::method"
$middleware = $factory->makeMiddleware('auth.service::authenticate');
- Global functions:
"functionName"
$middleware = $factory->makeMiddleware('myGlobalMiddlewareFunction');
- Callable services from container:
"serviceId"
// Service that is itself callable
$middleware = $factory->makeMiddleware('custom.middleware.service');
4. Arrays:
- Object and method:
[object, method]
$authService = new AuthService();
$middleware = $factory->makeMiddleware([$authService, 'authenticate']);
- Service ID and method:
[serviceId, method]
$middleware = $factory->makeMiddleware(['user.service', 'validateToken']);
Middleware patterns (automatic detection):
Single-pass middleware
$middleware = $factory->makeMiddleware(function(ServerRequestInterface $request, callable $next): ResponseInterface {
// Pre-processing
$request = $request->withAttribute('timestamp', time());
// Call next middleware
$response = $next($request);
// Post-processing
return $response->withHeader('X-Processing-Time', microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']);
});
// Or through service
$middleware = $factory->makeMiddleware('logging.middleware::process');
Double-pass middleware
$middleware = $factory->makeMiddleware(function(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
): ResponseInterface {
// Work with base response object
if ($request->getHeaderLine('Accept') === 'application/json') {
return $next($request);
}
return $response->withStatus(406);
});
// Or through class
$middleware = $factory->makeMiddleware('App\\Middleware\\LegacyMiddleware::handle');
Standard callable with DI
$middleware = $factory->makeMiddleware(function(
ServerRequestInterface $request,
#[Inject('user.service')] UserService $userService,
#[MapQueryParameter] string $token
): ResponseInterface {
$user = $userService->findByToken($token);
if (!$user) {
return new JsonResponse(['error' => 'Invalid token'], 401);
}
return new JsonResponse(['user' => $user]);
});
// Or through service with DI
$middleware = $factory->makeMiddleware('api.middleware::handleAuth');
Factory Callable
For dynamic middleware creation using the container:
// Factory callable - created only on first use
$middleware = $factory->makeMiddleware(static function(ContainerInterface $c) use ($uri, $permanent): RedirectMiddleware {
return new RedirectMiddleware($uri, $c->get(ResponseFactoryInterface::class), $permanent);
});
$middleware instanceof MiddlewareInterface; // true
$middleware instanceof RedirectMiddleware; // true
// Complex factory callable with configuration
$authMiddleware = $factory->makeMiddleware(static function(ContainerInterface $c): AuthMiddleware {
$config = $c->get('config');
$jwtSecret = $config['auth']['jwt_secret'];
$tokenTtl = $config['auth']['token_ttl'] ?? 3600;
return new AuthMiddleware(
$c->get(JwtService::class),
$c->get(UserRepository::class),
$jwtSecret,
$tokenTtl
);
});
// Conditional factory callable
$compressionMiddleware = $factory->makeMiddleware(static function(ContainerInterface $c): MiddlewareInterface {
$config = $c->get('config');
if ($config['compression']['enabled'] ?? false) {
return new CompressionMiddleware(
$config['compression']['level'] ?? 6,
$config['compression']['types'] ?? ['text/html', 'application/json']
);
}
// Return empty middleware if compression is disabled
return new class implements MiddlewareInterface {
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
return $handler->handle($request);
}
};
});
Examples with different formats:
// Container with various middleware services
$container->set('auth.middleware', new AuthenticationMiddleware());
$container->set('auth.service', new AuthService());
$container->set('rate.limiter', new RateLimitMiddleware());
// Various ways to create middleware:
// 1. Direct closure
$middleware1 = $factory->makeMiddleware(function($request, $next) {
return $next($request);
});
// 2. Service-middleware from container
$middleware2 = $factory->makeMiddleware('auth.middleware');
// 3. Service method from container
$middleware3 = $factory->makeMiddleware('auth.service::validateRequest');
// 4. Static class method
$middleware4 = $factory->makeMiddleware('App\\Utils\\SecurityUtils::checkOrigin');
// 5. Array with service and method
$middleware5 = $factory->makeMiddleware(['rate.limiter', 'handle']);
// 6. Global function
$middleware6 = $factory->makeMiddleware('customSecurityHandler');
// 7. Direct object and method
$corsHandler = new CorsHandler();
$middleware7 = $factory->makeMiddleware([$corsHandler, 'process']);
3. Pipeline Strategy and MiddlewareGroup
For combining multiple middleware into a single pipeline:
use Bermuda\MiddlewareFactory\MiddlewareGroup;
$group = new MiddlewareGroup([
'App\\Middleware\\AuthMiddleware',
['cors.service', 'handle'],
function($request, $next) { return $next($request); }
]);
$middleware = $factory->makeMiddleware($group);
// Adding middleware
$newGroup = $group->add('rate.limiter::check');
$newGroup = $group->addMany(['cache.middleware', 'response.formatter']);
// Checks
echo $group->count(); // Number of middleware
foreach ($group as $definition) {
echo get_debug_type($definition) . "\n";
}
Request Data Mapping
The factory supports automatic extraction and transformation of data from PSR-7 requests into method parameters using PHP 8+ attributes. This provides clean separation between HTTP data handling and business logic.
Query parameters
The #[MapQueryParameter] attribute extracts individual parameters from the query string:
use Bermuda\MiddlewareFactory\Attribute\MapQueryParameter;
public function search(
#[MapQueryParameter] string $query, // ?query=something
#[MapQueryParameter('p')] int $page = 1, // ?p=2 (extracts parameter 'p')
#[MapQueryParameter] ?string $category = null // ?category=books (optional)
): ResponseInterface {
// $query = "something", $page = 2, $category = "books"
$results = $this->searchService->search($query, $page, $category);
return new JsonResponse(['results' => $results]);
}
// Advanced usage with typing
public function filter(
#[MapQueryParameter] int $page = 1, // Automatic cast to int
#[MapQueryParameter] bool $active = true
