SkillAgentSearch skills...

Psr15factory

Powerful and flexible factory for creating PSR-15 compatible middleware with automatic dependency injection and request data mapping

Install / Use

/learn @bermudaphp/Psr15factory

README

Bermuda PSR-15 Factory

Languages: English | Русский

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
View on GitHub
GitHub Stars6
CategoryDevelopment
Updated6mo ago
Forks0

Languages

PHP

Security Score

87/100

Audited on Oct 6, 2025

No findings