Compare commits

..

No commits in common. "6716ac6d269876eeaddb4bbc35da015f30daf9af" and "e473341f46ecf021e3e729a9fa5de0efa6f7068e" have entirely different histories.

15 changed files with 75 additions and 390 deletions

121
.gitattributes vendored
View File

@ -1,121 +0,0 @@
###
# https://github.com/gitattributes/gitattributes/blob/master/Common.gitattributes
###
# Auto detect text files and perform LF normalization
* text=auto
#
# The above will handle all files NOT found below
#
# Documents
*.bibtex text diff=bibtex
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text diff=markdown
*.mdx text diff=markdown
*.tex text diff=tex
*.adoc text
*.textile text
*.mustache text
*.csv text eol=crlf
*.tab text
*.tsv text
*.txt text
*.sql text
*.epub diff=astextplain
# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG treated as text by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.eps binary
# Scripts
*.bash text eol=lf
*.fish text eol=lf
*.ksh text eol=lf
*.sh text eol=lf
*.zsh text eol=lf
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Serialisation
*.json text
*.toml text
*.xml text
*.yaml text
*.yml text
# Archives
*.7z binary
*.gz binary
*.tar binary
*.tgz binary
*.zip binary
# Text files where line endings should be preserved
*.patch -text
#
# Exclude files from exporting
#
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore
###
# https://github.com/gitattributes/gitattributes/blob/master/PHP.gitattributes
###
# PHP files
*.php text eol=lf diff=php
*.phpt text eol=lf diff=php
*.phtml text eol=lf diff=html
*.twig text eol=lf
*.phar binary
# Configuration
phpcs.xml text eol=lf
phpunit.xml text eol=lf
phpstan.neon text eol=lf
psalm.xml text eol=lf
###
# Open Culture Consulting custom additions
###
# Configuration
.editorconfig text eol=lf
*.dist.xml text eol=lf
*.xml.dist text eol=lf
*.neon text eol=lf
# Generated documentation
doc/* linguist-generated=true
# Exclude files from exporting
.github/* export-ignore
.phpdoc/* export-ignore
phpdoc.dist.xml export-ignore

View File

@ -4,7 +4,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
versioning-strategy: "increase-if-necessary"
assignees: assignees:
- "sebastian-meyer" - "sebastian-meyer"
labels: [ ] labels: [ ]

View File

@ -1,35 +0,0 @@
name: PHP Code Sniffer
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
PHPCS:
name: PHPCS Scanner
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Setup Environment
uses: php-actions/composer@v6
with:
command: update
php_version: "8.1"
- name: Run PHPCS
uses: php-actions/phpcs@v1
with:
php_version: "8.1"
path: src/
standard: phpcs.xml.dist

View File

@ -25,7 +25,7 @@ jobs:
- name: Setup Environment - name: Setup Environment
uses: shivammathur/setup-php@v2 uses: shivammathur/setup-php@v2
with: with:
php-version: "8.1" php-version: "8.0"
coverage: none coverage: none
tools: phpmd tools: phpmd

View File

@ -26,7 +26,7 @@ jobs:
uses: php-actions/composer@v6 uses: php-actions/composer@v6
with: with:
command: update command: update
php_version: "8.1" php_version: "8.0"
- name: Run PHPStan - name: Run PHPStan
uses: php-actions/phpstan@v3 uses: php-actions/phpstan@v3

View File

@ -1,35 +0,0 @@
name: Psalm Static Analyzer
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
Psalm:
name: Psalm Scanner
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Run Psalm
uses: docker://ghcr.io/psalm/psalm-github-actions
with:
security_analysis: true
report_file: psalm-results.sarif
- name: Upload Analysis Results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: psalm-results.sarif
wait-for-processing: true

5
.gitignore vendored
View File

@ -1,10 +1,5 @@
/.phpdoc/cache/
/.vscode/ /.vscode/
/vendor/ /vendor/
.php-cs-fixer.php .php-cs-fixer.php
composer.lock composer.lock
phpcs.xml
phpdoc.xml
phpstan.neon phpstan.neon
psalm.xml
TODO

View File

@ -1,8 +1,8 @@
<?php <?php
/** /**
* PHP Basics * Queue-based PSR-15 HTTP Server Request Handler
* Copyright (C) 2024 Sebastian Meyer <sebastian.meyer@opencultureconsulting.com> * Copyright (C) 2023 Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -24,15 +24,15 @@ namespace PhpCsFixer;
/** /**
* Configuration for PHP-CS-Fixer. * Configuration for PHP-CS-Fixer.
*
* @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst * @see https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/blob/master/doc/config.rst
* *
* @return ConfigInterface * @return ConfigInterface
*/ */
$config = new Config(); return (new Config())
$finder = new Finder();
return $config
->setRiskyAllowed(true) ->setRiskyAllowed(true)
->setRules(['@PSR12' => true]) ->setRules([
->setFinder($finder->in([__DIR__ . '/src'])); '@PSR12' => true,
])
->setFinder(
(new Finder())->in(__DIR__)
);

View File

@ -8,7 +8,6 @@
"request", "request",
"handler", "handler",
"middleware", "middleware",
"queue",
"http-server-handler", "http-server-handler",
"http-server-middleware" "http-server-middleware"
], ],
@ -26,21 +25,19 @@
"support": { "support": {
"issues": "https://github.com/opencultureconsulting/psr-15/issues", "issues": "https://github.com/opencultureconsulting/psr-15/issues",
"source": "https://github.com/opencultureconsulting/psr-15", "source": "https://github.com/opencultureconsulting/psr-15",
"docs": "https://opencultureconsulting.github.io/psr-15/" "docs": "https://github.com/opencultureconsulting/psr-15/blob/main/README.md"
}, },
"require": { "require": {
"php": "^8.1", "php": "^8.0",
"guzzlehttp/psr7": "^2.6", "guzzlehttp/psr7": "^2.6",
"opencultureconsulting/basics": "^2.0", "opencultureconsulting/basics": "^1.1",
"psr/http-server-handler": "^1.0", "psr/http-server-handler": "^1.0",
"psr/http-server-middleware": "^1.0" "psr/http-server-middleware": "^1.0"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10",
"phpstan/phpstan-strict-rules": "^1.5", "phpstan/phpstan-strict-rules": "^1.5",
"friendsofphp/php-cs-fixer": "^3.52", "friendsofphp/php-cs-fixer": "^3.48"
"squizlabs/php_codesniffer": "^3.9",
"vimeo/psalm": "^5.23"
}, },
"provide": { "provide": {
"psr/http-server-handler-implementation": "1.0", "psr/http-server-handler-implementation": "1.0",

View File

@ -1,10 +0,0 @@
<?xml version="1.0"?>
<ruleset name="OCC Standard Ruleset">
<description>Open Culture Consulting strictly follows PSR standards.</description>
<file>./src</file>
<arg name="extensions" value="php"/>
<rule ref="PSR12">
<exclude name="PSR2.Classes.PropertyDeclaration.Underscore"/>
<exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
</rule>
</ruleset>

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<phpdocumentor
configVersion="3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://www.phpdoc.org"
xsi:schemaLocation="https://www.phpdoc.org https://raw.githubusercontent.com/phpDocumentor/phpDocumentor/master/data/xsd/phpdoc.xsd">
<title>PSR-15 Queue</title>
<paths>
<output>doc</output>
<cache>.phpdoc/cache</cache>
</paths>
<version number="latest">
<api format="php">
<source dsn=".">
<path>/src</path>
</source>
<extensions>
<extension>php</extension>
</extensions>
<default-package-name>PSR15</default-package-name>
<include-source>true</include-source>
<ignore-tags>
<ignore-tag>extends</ignore-tag>
<ignore-tag>implements</ignore-tag>
<ignore-tag>phpstan-require-implements</ignore-tag>
<ignore-tag>psalm-suppress</ignore-tag>
</ignore-tags>
</api>
<guide format="rst">
<source dsn=".">
<path>/.phpdoc/guide</path>
</source>
<output>guides</output>
</guide>
</version>
<setting name="graphs.enabled" value="false"/>
<setting name="guides.enabled" value="true"/>
<setting name="template.color" value="orange"/>
</phpdocumentor>

View File

@ -1,23 +0,0 @@
<?xml version="1.0"?>
<psalm
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
errorLevel="1"
resolveFromConfigFile="true"
findUnusedBaselineEntry="true"
findUnusedCode="true"
findUnusedVariablesAndParams="true"
>
<issueHandlers>
<RedundantCastGivenDocblockType errorLevel="suppress"/>
<RedundantConditionGivenDocblockType errorLevel="suppress"/>
<RedundantFunctionCallGivenDocblockType errorLevel="suppress"/>
</issueHandlers>
<projectFiles>
<directory name="src"/>
<ignoreFiles>
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>
</psalm>

View File

@ -22,39 +22,34 @@ declare(strict_types=1);
namespace OCC\PSR15; namespace OCC\PSR15;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as ServerRequest; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Psr\Http\Server\RequestHandlerInterface;
/** /**
* Abstract class implementing \Psr\Http\Server\MiddlewareInterface. * Abstract class implementing Psr\Http\Server\MiddlewareInterface.
* *
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com> * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package PSR15 * @package opencultureconsulting/psr15
*/ */
abstract class AbstractMiddleware implements MiddlewareInterface abstract class AbstractMiddleware implements MiddlewareInterface
{ {
/** /**
* The PSR-15 Server Request Handler. * The PSR-15 Server Request Handler.
*
* @var QueueRequestHandler
*
* @internal
*/ */
protected QueueRequestHandler $requestHandler; protected QueueRequestHandler $requestHandler;
/** /**
* Process an incoming server request and produce a response. * Process an incoming server request and produce a response.
* @see MiddlewareInterface::process()
* *
* @param ServerRequest $request The server request to process * @param ServerRequestInterface $request The server request to process
* @param RequestHandler $handler The request handler to delegate to * @param RequestHandlerInterface $handler The request handler to delegate to
* *
* @return Response The response object * @return ResponseInterface The response object
*
* @api
*/ */
final public function process(ServerRequest $request, RequestHandler $handler): Response final public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{ {
/** @var QueueRequestHandler $handler */ /** @var QueueRequestHandler $handler */
$this->requestHandler = $handler; $this->requestHandler = $handler;
@ -71,11 +66,11 @@ abstract class AbstractMiddleware implements MiddlewareInterface
/** /**
* Process an incoming server request before delegating to next middleware. * Process an incoming server request before delegating to next middleware.
* *
* @param ServerRequest $request The incoming server request * @param ServerRequestInterface $request The incoming server request
* *
* @return ServerRequest The processed server request * @return ServerRequestInterface The processed server request
*/ */
protected function processRequest(ServerRequest $request): ServerRequest protected function processRequest(ServerRequestInterface $request): ServerRequestInterface
{ {
return $request; return $request;
} }
@ -83,26 +78,25 @@ abstract class AbstractMiddleware implements MiddlewareInterface
/** /**
* Process an incoming response before returning it to previous middleware. * Process an incoming response before returning it to previous middleware.
* *
* @param Response $response The incoming response * @param ResponseInterface $response The incoming response
* *
* @return Response The processed response * @return ResponseInterface The processed response
*/ */
protected function processResponse(Response $response): Response protected function processResponse(ResponseInterface $response): ResponseInterface
{ {
return $response; return $response;
} }
/** /**
* Allow the middleware to be invoked directly. * Allow the middleware to be invoked directly.
* @see AbstractMiddleware::process()
* *
* @param ServerRequest $request The server request to process * @param ServerRequestInterface $request The server request to process
* @param RequestHandler $handler The request handler to delegate to * @param RequestHandlerInterface $handler The request handler to delegate to
* *
* @return Response The response object * @return ResponseInterface The response object
*
* @api
*/ */
final public function __invoke(ServerRequest $request, RequestHandler $handler): Response final public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{ {
return $this->process($request, $handler); return $this->process($request, $handler);
} }

View File

@ -24,17 +24,15 @@ namespace OCC\PSR15;
use OCC\Basics\DataStructures\StrictQueue; use OCC\Basics\DataStructures\StrictQueue;
use OCC\Basics\Traits\Singleton; use OCC\Basics\Traits\Singleton;
use Psr\Http\Server\MiddlewareInterface as Middleware; use Psr\Http\Server\MiddlewareInterface;
/** /**
* Queue of PSR-15 Middlewares to process HTTP Server Requests. * Queue of PSR-15 Middlewares to process HTTP Server Requests.
* *
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com> * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package PSR15 * @package opencultureconsulting/psr15
* *
* @method static static getInstance(iterable<\Psr\Http\Server\MiddlewareInterface> $middlewares) * @extends StrictQueue<MiddlewareInterface>
*
* @extends StrictQueue<Middleware>
*/ */
class MiddlewareQueue extends StrictQueue class MiddlewareQueue extends StrictQueue
{ {
@ -43,13 +41,11 @@ class MiddlewareQueue extends StrictQueue
/** /**
* Create a queue of PSR-15 compatible middlewares. * Create a queue of PSR-15 compatible middlewares.
* *
* @param iterable<array-key, Middleware> $middlewares Initial set of PSR-15 middlewares * @param iterable<MiddlewareInterface> $middlewares Initial set of PSR-15 middlewares
*
* @return void
*/ */
private function __construct(iterable $middlewares = []) private function __construct(iterable $middlewares = [])
{ {
parent::__construct([Middleware::class]); parent::__construct([MiddlewareInterface::class]);
$this->append(...$middlewares); $this->append(...$middlewares);
} }
} }

View File

@ -24,74 +24,50 @@ namespace OCC\PSR15;
use Exception; use Exception;
use RuntimeException; use RuntimeException;
use GuzzleHttp\Psr7\Response as GuzzleResponse; use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\ServerRequest as GuzzleRequest; use GuzzleHttp\Psr7\ServerRequest;
use OCC\Basics\Traits\Getter; use OCC\Basics\Traits\Getter;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as ServerRequest; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface as Middleware; use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Psr\Http\Server\RequestHandlerInterface;
use function array_keys;
use function count;
use function filter_var;
use function get_debug_type;
use function header;
use function headers_sent;
use function is_null;
use function sprintf;
/** /**
* A queue-based PSR-15 HTTP Server Request Handler. * A queue-based PSR-15 HTTP Server Request Handler.
* *
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com> * @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @package PSR15 * @package opencultureconsulting/psr15
* *
* @property-read MiddlewareQueue $queue * @property-read MiddlewareQueue $queue
* @property-read ServerRequest $request * @property-read ServerRequestInterface $request
* @property-read Response $response
*/ */
class QueueRequestHandler implements RequestHandler class QueueRequestHandler implements RequestHandlerInterface
{ {
use Getter; use Getter;
/** /**
* The PSR-7 HTTP Server Request. * The PSR-7 HTTP Server Request.
*
* @var ServerRequest
*
* @internal
*/ */
protected ServerRequest $request; protected ServerRequestInterface $request;
/** /**
* The queue of middlewares to process the server request. * The queue of middlewares to process the server request.
*
* @var MiddlewareQueue
*
* @internal
*/ */
protected MiddlewareQueue $queue; protected MiddlewareQueue $queue;
/** /**
* The PSR-7 HTTP Response. * The PSR-7 HTTP Response.
*
* @var Response
*
* @internal
*/ */
protected Response $response; protected ResponseInterface $response;
/** /**
* Handles a request by invoking a queue of middlewares. * Handles a request by invoking a queue of middlewares.
* *
* @param ?ServerRequest $request The PSR-7 server request to handle * @param ?ServerRequestInterface $request The PSR-7 server request to handle
* *
* @return Response A PSR-7 compatible HTTP response * @return ResponseInterface A PSR-7 compatible HTTP response
*
* @api
*/ */
public function handle(?ServerRequest $request = null): Response public function handle(?ServerRequestInterface $request = null): ResponseInterface
{ {
$this->request = $request ?? $this->request; $this->request = $request ?? $this->request;
if (count($this->queue) > 0) { if (count($this->queue) > 0) {
@ -102,7 +78,7 @@ class QueueRequestHandler implements RequestHandler
// further processing to ensure that a response is always generated. // further processing to ensure that a response is always generated.
try { try {
$this->response = $middleware->process($this->request, $this); $this->response = $middleware->process($this->request, $this);
} catch (Exception $exception) { } catch(Exception $exception) {
$options = [ $options = [
'options' => [ 'options' => [
'default' => 500, 'default' => 500,
@ -111,7 +87,7 @@ class QueueRequestHandler implements RequestHandler
] ]
]; ];
$statusCode = filter_var($exception->getCode(), FILTER_VALIDATE_INT, $options); $statusCode = filter_var($exception->getCode(), FILTER_VALIDATE_INT, $options);
$this->response = new GuzzleResponse( $this->response = new Response(
$statusCode, $statusCode,
[], [],
sprintf( sprintf(
@ -133,9 +109,7 @@ class QueueRequestHandler implements RequestHandler
* *
* @return void * @return void
* *
* @throws RuntimeException if headers were already sent * @throws RuntimeException
*
* @api
*/ */
public function respond(?int $exitCode = null): void public function respond(?int $exitCode = null): void
{ {
@ -152,7 +126,7 @@ class QueueRequestHandler implements RequestHandler
} }
header( header(
sprintf( sprintf(
'HTTP/%s %d %s', 'HTTP/%s %s %s',
$this->response->getProtocolVersion(), $this->response->getProtocolVersion(),
$this->response->getStatusCode(), $this->response->getStatusCode(),
$this->response->getReasonPhrase() $this->response->getReasonPhrase()
@ -160,7 +134,6 @@ class QueueRequestHandler implements RequestHandler
true true
); );
foreach (array_keys($this->response->getHeaders()) as $name) { foreach (array_keys($this->response->getHeaders()) as $name) {
/** @var string $name */
$header = sprintf('%s: %s', $name, $this->response->getHeaderLine($name)); $header = sprintf('%s: %s', $name, $this->response->getHeaderLine($name));
header($header, false); header($header, false);
} }
@ -172,10 +145,9 @@ class QueueRequestHandler implements RequestHandler
/** /**
* Magic getter method for $this->queue. * Magic getter method for $this->queue.
* @see Getter
* *
* @return MiddlewareQueue The queue of PSR-15 middlewares * @return MiddlewareQueue The queue of PSR-15 middlewares
*
* @internal
*/ */
protected function magicGetQueue(): MiddlewareQueue protected function magicGetQueue(): MiddlewareQueue
{ {
@ -184,24 +156,22 @@ class QueueRequestHandler implements RequestHandler
/** /**
* Magic getter method for $this->request. * Magic getter method for $this->request.
* @see Getter
* *
* @return ServerRequest The PSR-7 server request * @return ServerRequestInterface The PSR-7 server request
*
* @internal
*/ */
protected function _magicGetRequest(): ServerRequest protected function magicGetRequest(): ServerRequestInterface
{ {
return $this->request; return $this->request;
} }
/** /**
* Magic getter method for $this->response. * Magic getter method for $this->response.
* @see Getter
* *
* @return Response The PSR-7 response * @return ResponseInterface The PSR-7 response
*
* @internal
*/ */
protected function _magicGetResponse(): Response protected function magicGetResponse(): ResponseInterface
{ {
return $this->response; return $this->response;
} }
@ -209,27 +179,24 @@ class QueueRequestHandler implements RequestHandler
/** /**
* Create a queue-based PSR-15 HTTP Server Request Handler. * Create a queue-based PSR-15 HTTP Server Request Handler.
* *
* @param iterable<array-key, Middleware> $middlewares Initial set of middlewares * @param iterable<MiddlewareInterface> $middlewares Initial set of middlewares
*
* @return void
*/ */
public function __construct(iterable $middlewares = []) public function __construct(iterable $middlewares = [])
{ {
$this->request = GuzzleRequest::fromGlobals(); $this->request = ServerRequest::fromGlobals();
$this->queue = MiddlewareQueue::getInstance($middlewares); $this->queue = MiddlewareQueue::getInstance($middlewares);
$this->response = new GuzzleResponse(200); $this->response = new Response(200);
} }
/** /**
* Allow the request handler to be invoked directly. * Allow the request handler to be invoked directly.
* @see QueueRequestHandler::handle()
* *
* @param ?ServerRequest $request The PSR-7 server request to handle * @param ?ServerRequestInterface $request The PSR-7 server request to handle
* *
* @return Response A PSR-7 compatible HTTP response * @return ResponseInterface A PSR-7 compatible HTTP response
*
* @api
*/ */
public function __invoke(?ServerRequest $request = null): Response public function __invoke(?ServerRequestInterface $request = null): ResponseInterface
{ {
return $this->handle($request); return $this->handle($request);
} }