Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,21 @@ provides:
psr/http-server-handler-implementation: ^1
dependencies:
required:
php: ^8
php: ^8.1
composer:
psr/http-server-middleware: ^1
psr/http-server-handler: ^1
horde/exception: ^3
horde/http: ^3
optional:
composer:
psr/container: ^2.02
horde/injector: '*'
ext:
mbstring: '*'
zlib: '*'
dev:
composer:
phpunit/phpunit: ^12
friendsofphp/php-cs-fixer: ^3
phpstan/phpstan: ^2
vendor: horde
keywords:
- middleware
- requesthandler
- psr15
15 changes: 6 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@
"role": "lead"
}
],
"time": "2026-03-07",
"time": "2026-04-14",
"repositories": [],
"require": {
"php": "^8",
"php": "^8.1",
"psr/container": "^1.1 || ^2.0",
"psr/http-server-middleware": "^1",
"psr/http-server-handler": "^1",
"horde/exception": "^3 || dev-FRAMEWORK_6_0",
"horde/http": "^3 || dev-FRAMEWORK_6_0"
},
"require-dev": {
"phpunit/phpunit": "^12",
"friendsofphp/php-cs-fixer": "^3",
"phpstan/phpstan": "^2"
},
"suggest": {
"ext-mbstring": "*",
"ext-zlib": "*"
Expand All @@ -50,5 +46,6 @@
"branch-alias": {
"dev-FRAMEWORK_6_0": "1.x-dev"
}
}
}
},
"minimum-stability": "dev"
}
3 changes: 2 additions & 1 deletion doc/example/example1.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Example 1:
*
Expand Down Expand Up @@ -40,4 +41,4 @@

$handler = new RampageRequestHandler($responseFactory, $streamFactory);
$runner = new Runner($handler, new ResponseWriterWeb());
$runner->run($request);
$runner->run($request);
5 changes: 3 additions & 2 deletions doc/example/example2.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Example 2:
*
Expand Down Expand Up @@ -40,8 +41,8 @@
$request = $requestBuilder->withGlobalVariables()->build();

$middlewares = [
new Responder($responseFactory, $streamFactory)
new Responder($responseFactory, $streamFactory),
];
$handler = new RampageRequestHandler($responseFactory, $streamFactory, $middlewares);
$runner = new Runner($handler, new ResponseWriterWeb());
$runner->run($request);
$runner->run($request);
8 changes: 6 additions & 2 deletions src/DefaultHandlerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Horde\Http\Server;

use Horde\Http\ResponseFactory;
use Horde\Http\StreamFactory;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\RequestInterface;
Expand All @@ -19,8 +21,10 @@ trait DefaultHandlerTrait
protected ResponseFactoryInterface $responseFactory;
protected StreamFactoryInterface $streamFactory;

public function __construct(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory)
{
public function __construct(
ResponseFactoryInterface $responseFactory = new ResponseFactory(),
StreamFactoryInterface $streamFactory = new StreamFactory(),
) {
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory;
}
Expand Down
8 changes: 5 additions & 3 deletions src/Exception.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

/**
* Copyright 2007 Maintainable Software, LLC
* Copyright 2008-2017 Horde LLC (http://www.horde.org/)
* Copyright 2007-2026 Maintainable Software, LLC
* Copyright 2008-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
Expand All @@ -17,6 +17,8 @@

namespace Horde\Http\Server;

use Horde_Exception_Wrapped;

/**
*
*
Expand All @@ -29,4 +31,4 @@
* @license http://www.horde.org/licenses/bsd
* @package Controller
*/
class Exception extends \Horde_Exception_Wrapped {}
class Exception extends Horde_Exception_Wrapped {}
10 changes: 6 additions & 4 deletions src/Middleware/Gzip.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2008-2017 Horde LLC (http://www.horde.org/)
* Copyright 2008-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
Expand All @@ -14,6 +14,7 @@

namespace Horde\Http\Server;

use Horde\Http\StreamFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
Expand All @@ -25,7 +26,7 @@
* Filter to gzip content before being served
*
* @author James Pepin <james@bluestatedigital.com>
* @author Ralf Lang <lang@b1-systems.de>
* @author Ralf Lang <ralf.lang@ralf-lang.de>
* @category Horde
* @copyright 2008-2021 Horde LLC
* @license http://www.horde.org/licenses/bsd BSD
Expand All @@ -35,8 +36,9 @@ class Gzip implements MiddlewareInterface
{
private StreamFactoryInterface $streamFactory;

public function __construct(StreamFactoryInterface $streamFactory)
{
public function __construct(
StreamFactoryInterface $streamFactory = new StreamFactory(),
) {
$this->streamFactory = $streamFactory;
}

Expand Down
8 changes: 6 additions & 2 deletions src/Middleware/Responder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Horde\Http\Server\Middleware;

use Horde\Http\ResponseFactory;
use Horde\Http\StreamFactory;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
Expand All @@ -22,8 +24,10 @@ class Responder implements MiddlewareInterface
protected ResponseFactoryInterface $responseFactory;
protected StreamFactoryInterface $streamFactory;

public function __construct(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory)
{
public function __construct(
ResponseFactoryInterface $responseFactory = new ResponseFactory(),
StreamFactoryInterface $streamFactory = new StreamFactory(),
) {
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory;
}
Expand Down
87 changes: 66 additions & 21 deletions src/RampageRequestHandler.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
<?php

declare(strict_types=1);

namespace Horde\Http\Server;

use Horde\Http\ResponseFactory;
use Horde\Http\StreamFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use RuntimeException;

/**
* The Rampage request handler.
Expand All @@ -27,63 +33,78 @@
* Once a response is returned, the response object passes back through
* all the previous layers and may be changed by them or cause side effects.
*
* The final response returned by RampageRequestHandler should
*
* Note that middlewares or the payload could themselves delegate their
* duties to other middlewares, handlers or other code
*/
class RampageRequestHandler implements RequestHandlerInterface
{
protected ResponseFactoryInterface $responseFactory;
protected StreamFactoryInterface $streamFactory;
/**
* @var MiddlewareInterface[]
*/
private ?ContainerInterface $container;
/** @var array<string|MiddlewareInterface> */
private array $middlewares = [];

private ?RequestHandlerInterface $payloadHandler;
private string|RequestHandlerInterface|null $payloadHandler;

/**
* Constructor
*
* @param ResponseFactoryInterface $responseFactory
* @param StreamFactoryInterface $streamFactory
* @param MiddlewareInterface[] $middlewares
* @param RequestHandlerInterface|null $payloadHandler
* @param iterable<string|MiddlewareInterface> $middlewares
* @param string|RequestHandlerInterface|null $payloadHandler
* @param ContainerInterface|null $container PSR-11 container for lazy middleware resolution
*/
public function __construct(
ResponseFactoryInterface $responseFactory,
StreamFactoryInterface $streamFactory,
ResponseFactoryInterface $responseFactory = new ResponseFactory(),
StreamFactoryInterface $streamFactory = new StreamFactory(),
iterable $middlewares = [],
?RequestHandlerInterface $payloadHandler = null
string|RequestHandlerInterface|null $payloadHandler = null,
?ContainerInterface $container = null,
) {
// Needed for the fallback response in case of no payload
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory;
// We accept any iterable but cast it to array
$this->middlewares = (array) $middlewares;
$this->middlewares = [...$middlewares];
$this->payloadHandler = $payloadHandler;
$this->container = $container;
}

/**
* Add another middleware to the queue just before the payload handler
*/
public function addMiddleware(MiddlewareInterface $middleware): void
public function addMiddleware(string|MiddlewareInterface $middleware): void
{
$this->middlewares[] = $middleware;
}

/**
* Configure the payload handler
*/
public function setPayloadHandler(RequestHandlerInterface $handler): void
public function setPayloadHandler(string|RequestHandlerInterface $handler): void
{
$this->payloadHandler = $handler;
}

public function nextMiddleware(): ?MiddlewareInterface
{
return array_shift($this->middlewares);
$entry = array_shift($this->middlewares);
if ($entry === null) {
return null;
}
if ($entry instanceof MiddlewareInterface) {
return $entry;
}
if ($this->container === null) {
throw new RuntimeException(
sprintf('Cannot resolve middleware "%s": no container provided.', $entry)
);
}
$resolved = $this->container->get($entry);
if (!$resolved instanceof MiddlewareInterface) {
throw new RuntimeException(
sprintf('Container returned non-middleware for "%s": got %s', $entry, get_debug_type($resolved))
);
}
return $resolved;
}

/**
Expand All @@ -95,16 +116,17 @@ public function nextMiddleware(): ?MiddlewareInterface
* If the middlewares created no response,
* the payload handler will.
*
* Finally the we will return a response ourselves.
* Finally we will return a response ourselves.
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$middleware = $this->nextMiddleware();
if ($middleware) {
return $middleware->process($request, $this);
}
if ($this->payloadHandler) {
return $this->payloadHandler->handle($request);
$payloadHandler = $this->resolvePayloadHandler();
if ($payloadHandler) {
return $payloadHandler->handle($request);
}
// Fallback response
$code = 404;
Expand All @@ -113,4 +135,27 @@ public function handle(ServerRequestInterface $request): ResponseInterface

return $this->responseFactory->createResponse($code, $reason)->withBody($body);
}

private function resolvePayloadHandler(): ?RequestHandlerInterface
{
if ($this->payloadHandler === null) {
return null;
}
if ($this->payloadHandler instanceof RequestHandlerInterface) {
return $this->payloadHandler;
}
if ($this->container === null) {
throw new RuntimeException(
sprintf('Cannot resolve payload handler "%s": no container provided.', $this->payloadHandler)
);
}
$resolved = $this->container->get($this->payloadHandler);
if (!$resolved instanceof RequestHandlerInterface) {
throw new RuntimeException(
sprintf('Container returned non-handler for "%s": got %s', $this->payloadHandler, get_debug_type($resolved))
);
}
$this->payloadHandler = $resolved;
return $resolved;
}
}
9 changes: 6 additions & 3 deletions src/RequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Horde\Http\Server;

use Horde\Http\RequestFactory;
use Horde\Http\StreamFactory;
use Horde\Http\UriFactory;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
Expand All @@ -23,9 +26,9 @@ class RequestBuilder
private UriFactoryInterface $uriFactory;

public function __construct(
ServerRequestFactoryInterface $requestFactory,
StreamFactoryInterface $streamFactory,
UriFactoryInterface $uriFactory
ServerRequestFactoryInterface $requestFactory = new RequestFactory(),
StreamFactoryInterface $streamFactory = new StreamFactory(),
UriFactoryInterface $uriFactory = new UriFactory(),
) {
$this->requestFactory = $requestFactory;
$this->streamFactory = $streamFactory;
Expand Down
Loading
Loading