PHP code example of skedli / http-middleware

1. Go to this page and download the library: Download skedli/http-middleware library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

skedli / http-middleware example snippets




declare(strict_types=1);

use Skedli\HttpMiddleware\CorrelationId\CorrelationIdMiddleware;

$middleware = CorrelationIdMiddleware::create()->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\CorrelationId\CorrelationIdMiddleware;

$app->add(CorrelationIdMiddleware::create()->build());



declare(strict_types=1);

use Skedli\HttpMiddleware\CorrelationId\CorrelationId;

$correlationId = $request->getAttribute('correlationId');
# @var CorrelationId $correlationId
$correlationId->toString(); # "550e8400-e29b-41d4-a716-446655440000"



declare(strict_types=1);

use Skedli\HttpMiddleware\CorrelationId\CorrelationId;
use Skedli\HttpMiddleware\CorrelationId\CorrelationIdProvider;

final readonly class PrefixedCorrelationId implements CorrelationId
{
    public function __construct(private string $value)
    {
    }

    public function toString(): string
    {
        return $this->value;
    }
}

final readonly class PrefixedCorrelationIdProvider implements CorrelationIdProvider
{
    public function __construct(private string $prefix)
    {
    }

    public function generate(): CorrelationId
    {
        return new PrefixedCorrelationId(value: sprintf('%s-%s', $this->prefix, bin2hex(random_bytes(8))));
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\CorrelationId\CorrelationIdMiddleware;

$middleware = CorrelationIdMiddleware::create()
    ->withProvider(provider: new PrefixedCorrelationIdProvider(prefix: 'bff'))
    ->build();



declare(strict_types=1);

use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Skedli\HttpMiddleware\CorrelationId\CorrelationId;

final readonly class CreateUserHandler
{
    public function __construct(private LoggerInterface $logger)
    {
    }

    public function __invoke(ServerRequestInterface $request): void
    {
        # @var CorrelationId $correlationId
        $correlationId = $request->getAttribute('correlationId');

        $this->logger->info(
            'user.creating',
            ['correlation_id' => $correlationId->toString(), 'email' => '[email protected]']
        );
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\LogMiddleware;

$middleware = LogMiddleware::create()->withLogger(logger: $logger)->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\LogMiddleware;

$app->add(LogMiddleware::create()->withLogger(logger: $logger)->build());



declare(strict_types=1);

use Skedli\HttpMiddleware\CorrelationId\CorrelationIdMiddleware;
use Skedli\HttpMiddleware\LogMiddleware;

# In Slim 4, middleware executes in LIFO order (last added = first to run).
# CorrelationIdMiddleware must run before LogMiddleware.
$app->add(LogMiddleware::create()->withLogger(logger: $logger)->build());
$app->add(CorrelationIdMiddleware::create()->build());



declare(strict_types=1);

use Skedli\HttpMiddleware\Error\ErrorMiddleware;
use Skedli\HttpMiddleware\Error\ExceptionMapping;
use Skedli\HttpMiddleware\Error\MappedError;
use Throwable;

$mapping = new class implements ExceptionMapping {
    public function mapTo(Throwable $exception): ?MappedError
    {
        if ($exception instanceof NotFoundException) {
            return new MappedError(
                code: 'NOT_FOUND',
                status: 404,
                message: 'The requested resource was not found.'
            );
        }

        return null;
    }
};

$middleware = ErrorMiddleware::create()
    ->withMapping(mapping: $mapping)
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Error\ExceptionMapping;
use Skedli\HttpMiddleware\Error\MappedError;
use Throwable;

$mapping = new class implements ExceptionMapping {
    public function mapTo(Throwable $exception): ?MappedError
    {
        if ($exception instanceof LoginThrottled) {
            return new MappedError(
                code: 'LOGIN_THROTTLED',
                status: 429,
                message: 'Too many failed login attempts. Please try again later.',
                headers: ['Retry-After' => (string) $exception->retryAfter->toSeconds()]
            );
        }

        return null;
    }
};



declare(strict_types=1);

use Skedli\HttpMiddleware\Error\ErrorHandlingSettings;
use Skedli\HttpMiddleware\Error\ErrorMiddleware;

$middleware = ErrorMiddleware::create()
    ->withLogger(logger: $logger)
    ->withMapping(mapping: $mapping)
    ->withSettings(settings: ErrorHandlingSettings::from(
        logErrors: true,
        logErrorDetails: true,
        displayErrorDetails: true
    ))
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Error\ErrorHandlingSettings;
use Skedli\HttpMiddleware\Error\ErrorMiddleware;

$middleware = ErrorMiddleware::create()
    ->withLogger(logger: $logger)
    ->withMapping(mapping: $mapping)
    ->withSettings(settings: ErrorHandlingSettings::from(
        logErrors: true,
        logErrorDetails: true,
        displayErrorDetails: false
    ))
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Error\ErrorHandlingSettings;
use Skedli\HttpMiddleware\Error\ErrorMiddleware;

$middleware = ErrorMiddleware::create()
    ->withMapping(mapping: $mapping)
    ->withLogger(logger: $logger)
    ->withSettings(settings: ErrorHandlingSettings::from(
        logErrors: true,
        logErrorDetails: false,
        displayErrorDetails: false
    ))
    ->build();



declare(strict_types=1);

use RuntimeException;
use Skedli\HttpMiddleware\Error\ExceptionMapping;
use Skedli\HttpMiddleware\Error\ExceptionMappingTable;
use Skedli\HttpMiddleware\Error\MappedError;
use Throwable;

final readonly class PaymentExceptionMapping implements ExceptionMapping
{
    public function mapTo(Throwable $exception): ?MappedError
    {
        return ExceptionMappingTable::create()
            ->when(InvalidMetadata::class)
            ->mapsTo(code: 'INVALID_METADATA', status: 400, message: 'The metadata must be a valid JSON object.')
            ->whenAny([DomainTransactionNotFound::class, QueryTransactionNotFound::class])
            ->mapsTo(code: 'TRANSACTION_NOT_FOUND', status: 404, message: 'Transaction not found.')
            ->when(InvalidMoneyCurrency::class)
            ->resolvesWith(resolver: fn(InvalidMoneyCurrency $exception) => new MappedError(
                code: 'INVALID_MONEY_CURRENCY',
                status: 422,
                message: sprintf('The currency <%s> is not supported.', $exception->value),
            ))
            ->whenSubclassOf(RuntimeException::class)
            ->mapsTo(code: 'GATEWAY_UNAVAILABLE', status: 502, message: 'The upstream gateway is currently unavailable.')
            ->mapTo(exception: $exception);
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\Error\ErrorMiddleware;

# Inner scope: maps domain exceptions, rethrows anything unrecognized.
$inner = ErrorMiddleware::create()
    ->withMapping(mapping: $domainMapping)
    ->withFallbackOnUnmapped(fallbackOnUnmapped: false)
    ->build();

# Outer scope: catches everything that escaped the inner middleware.
$outer = ErrorMiddleware::create()
    ->withMapping(mapping: $globalMapping)
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\SigningAlgorithm;

$middleware = AuthenticationMiddleware::create()
    ->withAlgorithm(algorithm: SigningAlgorithm::RS256)
    ->withKeyMaterial(keyMaterial: $publicKey)
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\SigningAlgorithm;

$middleware = AuthenticationMiddleware::create()
    ->withAlgorithm(algorithm: SigningAlgorithm::HS256)
    ->withKeyMaterial(keyMaterial: 'your-shared-secret-key')
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\SigningAlgorithm;

$app->add(
    AuthenticationMiddleware::create()
        ->withAlgorithm(algorithm: SigningAlgorithm::RS256)
        ->withKeyMaterial(keyMaterial: $publicKey)
        ->build()
);



declare(strict_types=1);

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\HttpJwksProvider;

$middleware = AuthenticationMiddleware::create()
    ->withJwksProvider(jwksProvider: HttpJwksProvider::with(
        client: new Client(),
        factory: new HttpFactory(),
        jwksUrl: 'http://identity-web/v1/keys/public'
    ))
    ->build();



declare(strict_types=1);

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\HttpJwksProvider;

# $jwksUrl = your way of loading IDENTITY_JWKS_URL
$app->add(
    AuthenticationMiddleware::create()
        ->withJwksProvider(jwksProvider: HttpJwksProvider::with(
            client: new Client(),
            factory: new HttpFactory(),
            jwksUrl: $jwksUrl
        ))
        ->build()
);



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticatedUser;
use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;

# @var AuthenticatedUser $user
$user = $request->getAttribute(AuthenticationMiddleware::AUTHENTICATED_USER_ATTRIBUTE);

$user->userId();    # "e3b0c442-98fc-1c14-b39f-f32d831cb27a"
$user->issuedAt();  # 1740000000
$user->expiresAt(); # 1740003600



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticatedUser;
use Skedli\HttpMiddleware\Authentication\TokenDecoder;
use Skedli\HttpMiddleware\Authentication\TokenValidationFailed;

final readonly class OpaqueTokenDecoder implements TokenDecoder
{
    public function __construct(private TokenStore $tokenStore)
    {
    }

    public function decode(string $token): AuthenticatedUser
    {
        $claims = $this->tokenStore->lookup($token);

        if ($claims === null) {
            throw TokenValidationFailed::withReason(reason: 'Token not found.');
        }

        return MyAuthenticatedUser::from(claims: $claims);
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;

$middleware = AuthenticationMiddleware::create()
    ->withTokenDecoder(tokenDecoder: new OpaqueTokenDecoder($tokenStore))
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticatedUser;

final readonly class TenantAwareUser implements AuthenticatedUser
{
    public function __construct(
        private array $roles,
        private string $userId,
        private int $issuedAt,
        private int $expiresAt,
        private string $tenantId
    ) {
    }

    public function expiresAt(): int
    {
        return $this->expiresAt;
    }

    public function issuedAt(): int
    {
        return $this->issuedAt;
    }

    public function roles(): array
    {
        return $this->roles;
    }

    public function tenantId(): string
    {
        return $this->tenantId;
    }

    public function userId(): string
    {
        return $this->userId;
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;

# @var TenantAwareUser $user
$user = $request->getAttribute(AuthenticationMiddleware::AUTHENTICATED_USER_ATTRIBUTE);

$user->userId();   # "e3b0c442-98fc-1c14-b39f-f32d831cb27a"
$user->tenantId(); # "tenant-42"
$user->roles();    # ["admin", "billing"]



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\SigningAlgorithm;

# The custom decoder wins, JwksProvider, key material, and algorithm are ignored.
# $jwksProvider = HttpJwksProvider::with(...)
$middleware = AuthenticationMiddleware::create()
    ->withAlgorithm(algorithm: SigningAlgorithm::RS256)
    ->withKeyMaterial(keyMaterial: $publicKey)
    ->withJwksProvider(jwksProvider: $jwksProvider)
    ->withTokenDecoder(tokenDecoder: $customDecoder)
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Authentication\AuthenticatedUser;
use Skedli\HttpMiddleware\Authentication\AuthenticationMiddleware;
use Skedli\HttpMiddleware\Authentication\RequireAuthorization;
use Skedli\HttpMiddleware\Authentication\SigningAlgorithm;

$authentication = AuthenticationMiddleware::create()
    ->withAlgorithm(algorithm: SigningAlgorithm::RS256)
    ->withKeyMaterial(keyMaterial: $publicKey)
    ->build();

$authorization = RequireAuthorization::matching(
    predicate: fn(AuthenticatedUser $user): bool => $user->userId() !== '',
    description: 'You do not have permission to access this resource.'
);

# Register both in the pipeline: authentication runs first, authorization second.



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\LivenessHandler;

$handler = LivenessHandler::create()->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\LivenessHandler;

$app->get('/health/liveness', LivenessHandler::create()->build());



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\DoctrineHealthCheck;
use Skedli\HttpMiddleware\HealthCheck\ReadinessHandler;

$handler = ReadinessHandler::create()
    ->withCheck(check: DoctrineHealthCheck::create(connection: $connection)->build())
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\DoctrineHealthCheck;
use Skedli\HttpMiddleware\HealthCheck\ReadinessHandler;

$app->get('/health/readiness', ReadinessHandler::create()
    ->withCheck(check: DoctrineHealthCheck::create(connection: $connection)->build())
    ->build()
);



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\HealthCheck;
use Skedli\HttpMiddleware\HealthCheck\HealthCheckResult;
use Skedli\HttpMiddleware\HealthCheck\ReadinessHandler;

final readonly class RedisHealthCheck implements HealthCheck
{
    public function __construct(private Redis $redis)
    {
    }

    public function name(): ?string
    {
        return null;
    }

    public function check(): HealthCheckResult
    {
        try {
            $this->redis->ping();
            return HealthCheckResult::up();
        } catch (\Throwable $exception) {
            return HealthCheckResult::down(message: $exception->getMessage());
        }
    }

    public function component(): string
    {
        return 'cache';
    }
}

$handler = ReadinessHandler::create()
    ->withCheck(check: $databaseCheck)
    ->withCheck(check: new RedisHealthCheck(redis: $redis))
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\DoctrineHealthCheck;

$check = DoctrineHealthCheck::create(connection: $connection)->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\DoctrineHealthCheck;

# Override query and criticality; no label needed for a single connection.
$check = DoctrineHealthCheck::create(connection: $replicaConnection)
    ->withQuery(query: 'SELECT 1 FROM migrations LIMIT 1')
    ->withCritical(critical: false)
    ->build();

# Opt-in: explicitly expose the schema name as the discriminator label.
$check = DoctrineHealthCheck::create(connection: $connection)
    ->withName(name: $connection->getDatabase() ?? 'primary')
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\HealthCheck;
use Skedli\HttpMiddleware\HealthCheck\HealthCheckResult;

final readonly class CacheHealthCheck implements HealthCheck
{
    public function __construct(private Redis $redis)
    {
    }

    public function name(): ?string
    {
        return null;
    }

    public function check(): HealthCheckResult
    {
        try {
            $this->redis->ping();
            return HealthCheckResult::up(critical: false);
        } catch (\Throwable $exception) {
            return HealthCheckResult::down(message: $exception->getMessage(), critical: false);
        }
    }

    public function component(): string
    {
        return 'cache';
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\DoctrineHealthCheck;
use Skedli\HttpMiddleware\HealthCheck\ReadinessHandler;

$handler = ReadinessHandler::create()
    ->withCheck(check: DoctrineHealthCheck::create(connection: $primaryConnection)->withName(name: 'primary')->build())
    ->withCheck(
        check: DoctrineHealthCheck::create(connection: $replicaConnection)
            ->withName(name: 'replica')
            ->withCritical(critical: false)
            ->build()
    )
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\HealthCheck\DoctrineHealthCheck;
use Skedli\HttpMiddleware\HealthCheck\ReadinessHandler;

$handler = ReadinessHandler::create()
    ->withCheck(check: DoctrineHealthCheck::create(connection: $connection)->build())
    ->withDrainMarker(path: '/tmp/draining')
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Idempotency\DoctrineIdempotencyStore;
use Skedli\HttpMiddleware\Idempotency\IdempotencyMiddleware;

$store = DoctrineIdempotencyStore::create(connection: $connection)->build();

$middleware = IdempotencyMiddleware::create()
    ->withReader(reader: $store)
    ->withWriter(writer: $store)
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Idempotency\DoctrineIdempotencyStore;
use Skedli\HttpMiddleware\Idempotency\IdempotencyMiddleware;

$store = DoctrineIdempotencyStore::create(connection: $connection)->build();

$app->add(
    IdempotencyMiddleware::create()
        ->withReader(reader: $store)
        ->withWriter(writer: $store)
        ->build()
);



declare(strict_types=1);

use Skedli\HttpMiddleware\Idempotency\DoctrineIdempotencyStore;

$store = DoctrineIdempotencyStore::create(connection: $connection)
    ->withTable('idempotency_keys')
    ->build();



declare(strict_types=1);

use Psr\Http\Message\ServerRequestInterface;
use Skedli\HttpMiddleware\Idempotency\IdempotencyScopeProvider;

final readonly class TenantScopeProvider implements IdempotencyScopeProvider
{
    public function resolve(ServerRequestInterface $request): string
    {
        # @var \Skedli\HttpMiddleware\Authentication\AuthenticatedUser $user
        $user = $request->getAttribute('authenticatedUser');
        return $user->tenantId();
    }
}



declare(strict_types=1);

use Skedli\HttpMiddleware\Idempotency\IdempotencyMiddleware;

$middleware = IdempotencyMiddleware::create()
    ->withReader(reader: $store)
    ->withWriter(writer: $store)
    ->withScopeProvider(scopeProvider: new TenantScopeProvider())
    ->build();



declare(strict_types=1);

use Skedli\HttpMiddleware\Idempotency\IdempotencyLookup;
use Skedli\HttpMiddleware\Idempotency\IdempotencyReader;
use Skedli\HttpMiddleware\Idempotency\IdempotencyWriter;
use Skedli\HttpMiddleware\Idempotency\IdempotentResponse;

final class RedisIdempotencyStore implements IdempotencyReader, IdempotencyWriter
{
    public function __construct(private \Redis $redis)
    {
    }

    public function find(IdempotencyLookup $lookup): ?IdempotentResponse
    {
        $raw = $this->redis->get("{$lookup->namespace()}:{$lookup->key()}");

        if ($raw === false) {
            return null;
        }

        $data = json_decode($raw, true);

        return new RedisIdempotentResponse(
            key: $data['key'],
            namespace: $data['namespace'],
            statusCode: $data['status_code'],
            ttlSeconds: $data['ttl_seconds'],
            requestHash: $data['request_hash'],
            responseBody: $data['response_body'],
            responseContentType: $data['response_content_type']
        );
    }

    public function save(IdempotentResponse $response): IdempotentResponse
    {
        $raw = json_encode([
            'key'                   => $response->key(),
            'namespace'             => $response->namespace(),
            'status_code'           => $response->statusCode(),
            'ttl_seconds'           => $response->ttlSeconds(),
            'request_hash'          => $response->requestHash(),
            'response_body'         => $response->responseBody(),
            'response_content_type' => $response->responseContentType()
        ]);

        $this->redis->setex("{$response->namespace()}:{$response->key()}", $response->ttlSeconds(), $raw);

        return $response;
    }
}
json
{
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred."
}
json
{
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred.",
    "exception": "RuntimeException",
    "file": "/app/src/Application/Handlers/UserCreatingHandler.php",
    "line": 42,
    "trace": [
        "#0 /app/src/Driver/Http/Endpoints/User/CreateUser.php(25): ...",
        "#1 /app/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php(30): ..."
    ]
}
sh
#!/bin/sh
set -e

DRAIN_FILE="/tmp/draining"
PHP_FPM_PID=""

cleanup() {
    touch "$DRAIN_FILE"
    if [ -n "$PHP_FPM_PID" ]; then
        kill -TERM "$PHP_FPM_PID"
        wait "$PHP_FPM_PID"
    fi
    rm -f "$DRAIN_FILE"
}

trap cleanup TERM

php-fpm --nodaemonize &
PHP_FPM_PID=$!
wait "$PHP_FPM_PID"