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;
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 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\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\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;
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: $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;
}
}