Skip to content

Commit 6efd7bb

Browse files
author
Pavel Babushkin
authored
feat: add middleware registry (#4)
2 parents 6422989 + 6ca5847 commit 6efd7bb

File tree

13 files changed

+661
-20
lines changed

13 files changed

+661
-20
lines changed

.github/workflows/main.yml

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313

1414
env:
1515
COVERAGE: '1'
16-
default_php_version: '8.0'
16+
default_php_version: '8.1'
1717
php_extensions: 'pcov, json'
1818

1919
jobs:
@@ -46,18 +46,27 @@ jobs:
4646
php-extensions: ${{ needs.vars.outputs.php_extensions }}
4747
psalm-config: psalm-${{ matrix.php-versions }}.xml
4848

49-
composer-require-check:
50-
name: 'Composer Require check'
49+
composer-audit:
50+
name: 'Composer Audit'
5151
needs: vars
52-
uses: 'tochka-developers/actions/.github/workflows/composer-require-check.yml@v1'
52+
uses: 'tochka-developers/actions/.github/workflows/composer-audit.yml@v1'
5353
with:
5454
php-version: ${{ needs.vars.outputs.default_php_version }}
5555
php-extensions: ${{ needs.vars.outputs.php_extensions }}
5656

57-
composer-audit:
58-
name: 'Composer Audit'
57+
tests:
58+
name: 'Tests'
5959
needs: vars
60-
uses: 'tochka-developers/actions/.github/workflows/composer-audit.yml@v1'
60+
strategy:
61+
fail-fast: false
62+
matrix:
63+
php-versions: [ '8.0', '8.1', '8.2' ]
64+
uses: 'tochka-developers/actions/.github/workflows/tests.yml@v1'
6165
with:
62-
php-version: ${{ needs.vars.outputs.default_php_version }}
66+
php-version: ${{ matrix.php-versions }}
6367
php-extensions: ${{ needs.vars.outputs.php_extensions }}
68+
phpunit-config: phpunit-${{ matrix.php-versions }}.xml
69+
70+
coverage:
71+
needs: tests
72+
uses: 'tochka-developers/actions/.github/workflows/coveralls.yml@v1'

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
[![build](https://github.com/tochka-developers/jsonrpc-standard/actions/workflows/main.yml/badge.svg?)](https://github.com/tochka-developers/jsonrpc-standard/actions/workflows/main.yml)
55
[![Psalm coverage](https://shepherd.dev/github/tochka-developers/jsonrpc-standard/coverage.svg)](https://shepherd.dev/github/tochka-developers/jsonrpc-standard)
66
[![Psalm level](https://shepherd.dev/github/tochka-developers/jsonrpc-standard/level.svg)](https://psalm.dev)
7+
[![Coverage Status](https://coveralls.io/repos/github/tochka-developers/jsonrpc-standard/badge.svg?branch=master)](https://coveralls.io/github/tochka-developers/jsonrpc-standard)
78

89
DTO and exceptions from JsonRpc 2.0 specification

composer.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
],
1212
"require": {
1313
"php": "8.0.*|8.1.*|8.2.*",
14+
"illuminate/container": "^9.0",
1415
"illuminate/contracts": "^9.0"
1516
},
1617
"require-dev": {
17-
"laravel/pint": "^1.1",
18-
"mockery/mockery": "^1.0",
19-
"phpunit/phpunit": "^9.5",
18+
"laravel/pint": "^1.4",
19+
"mockery/mockery": "^1.5",
20+
"phpunit/phpunit": "^9.6",
2021
"roave/security-advisories": "dev-latest",
2122
"vimeo/psalm": "^5.6"
2223
},
@@ -25,6 +26,11 @@
2526
"Tochka\\JsonRpc\\Standard\\": "src/"
2627
}
2728
},
29+
"autoload-dev": {
30+
"psr-4": {
31+
"Tochka\\JsonRpc\\Standard\\Tests\\": "tests"
32+
}
33+
},
2834
"scripts": {
2935
"lint": "pint --test",
3036
"lint-fix": "pint",

phpunit.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true">
6+
<coverage processUncoveredFiles="true">
7+
<include>
8+
<directory suffix=".php">./src</directory>
9+
</include>
10+
<exclude>
11+
<directory suffix=".php">./src/Contracts</directory>
12+
</exclude>
13+
</coverage>
14+
<testsuites>
15+
<testsuite name="Unit">
16+
<directory>./tests/Units</directory>
17+
</testsuite>
18+
</testsuites>
19+
<php>
20+
<env name="APP_ENV" value="testing"/>
21+
<env name="CACHE_DRIVER" value="array"/>
22+
<env name="SESSION_DRIVER" value="array"/>
23+
</php>
24+
</phpunit>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Tochka\JsonRpc\Standard\Contracts;
4+
5+
interface MiddlewareInterface
6+
{
7+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Tochka\JsonRpc\Standard\Contracts;
4+
5+
/**
6+
* @psalm-api
7+
*/
8+
interface MiddlewareRegistryInterface
9+
{
10+
public function addMiddlewaresFromConfig(string $groupName, array $middlewareConfig): void;
11+
12+
/**
13+
* @template T of MiddlewareInterface
14+
* @param string $groupName
15+
* @param class-string<T>|null $instanceOf
16+
* @return array<T>|array<MiddlewareInterface>
17+
*/
18+
public function getMiddleware(string $groupName, ?string $instanceOf = null): array;
19+
20+
public function prependMiddleware(MiddlewareInterface $middleware, ?string $groupName = null): void;
21+
22+
public function appendMiddleware(MiddlewareInterface $middleware, ?string $groupName = null): void;
23+
24+
/**
25+
* @param MiddlewareInterface $middleware
26+
* @param class-string $afterMiddleware
27+
* @param string|null $groupName
28+
* @return void
29+
*/
30+
public function addMiddlewareAfter(
31+
MiddlewareInterface $middleware,
32+
string $afterMiddleware,
33+
?string $groupName = null
34+
): void;
35+
36+
/**
37+
* @param MiddlewareInterface $middleware
38+
* @param class-string $beforeMiddleware
39+
* @param string|null $groupName
40+
* @return void
41+
*/
42+
public function addMiddlewareBefore(
43+
MiddlewareInterface $middleware,
44+
string $beforeMiddleware,
45+
?string $groupName = null
46+
): void;
47+
}

src/DTO/JsonRpcError.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ final class JsonRpcError implements Arrayable, Jsonable, \JsonSerializable
2323
public int $code;
2424
public string $message;
2525
public array|object|null $data = null;
26-
public ?object $rawError = null;
2726

28-
public function __construct(int $code, string $message, array|object|null $data = null, ?object $rawError = null)
27+
public function __construct(int $code, string $message, array|object|null $data = null)
2928
{
3029
$this->code = $code;
3130
$this->message = $message;
3231
$this->data = $data;
33-
$this->rawError = $rawError;
3432
}
3533

3634
public static function from(object|array $value): self
@@ -51,12 +49,7 @@ public static function from(object|array $value): self
5149
? $value->data
5250
: null;
5351

54-
return new self(
55-
$code,
56-
$message,
57-
$data,
58-
$value
59-
);
52+
return new self($code, $message, $data);
6053
}
6154

6255
/**

src/Support/MiddlewareRegistry.php

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
namespace Tochka\JsonRpc\Standard\Support;
4+
5+
use Illuminate\Container\Container;
6+
use Tochka\JsonRpc\Standard\Contracts\MiddlewareInterface;
7+
use Tochka\JsonRpc\Standard\Contracts\MiddlewareRegistryInterface;
8+
use Tochka\JsonRpc\Standard\Exceptions\InternalErrorException;
9+
10+
/**
11+
* @psalm-api
12+
*/
13+
class MiddlewareRegistry implements MiddlewareRegistryInterface
14+
{
15+
/** @var array<string, array<MiddlewareInterface>> */
16+
private array $middleware = [];
17+
private Container $container;
18+
19+
/**
20+
* @psalm-suppress PossiblyUnusedMethod
21+
*/
22+
public function __construct(Container $container)
23+
{
24+
$this->container = $container;
25+
}
26+
27+
public function addMiddlewaresFromConfig(string $groupName, array $middlewareConfig): void
28+
{
29+
/** @psalm-suppress MixedAssignment */
30+
foreach ($middlewareConfig as $key => $value) {
31+
if (is_string($value)) {
32+
$this->middleware[$groupName][] = $this->instantiateMiddleware($value);
33+
} elseif (is_string($key) && is_array($value)) {
34+
$this->middleware[$groupName][] = $this->instantiateMiddleware($key, $value);
35+
}
36+
}
37+
}
38+
39+
public function getMiddleware(string $groupName, ?string $instanceOf = null): array
40+
{
41+
if (!array_key_exists($groupName, $this->middleware)) {
42+
return [];
43+
}
44+
45+
if ($instanceOf === null) {
46+
return $this->middleware[$groupName];
47+
}
48+
49+
return array_filter(
50+
$this->middleware[$groupName],
51+
function (MiddlewareInterface $middleware) use ($instanceOf) {
52+
return $middleware instanceof $instanceOf;
53+
}
54+
);
55+
}
56+
57+
public function prependMiddleware(MiddlewareInterface $middleware, ?string $groupName = null): void
58+
{
59+
if ($groupName === null) {
60+
foreach ($this->middleware as $groupName => $_) {
61+
$this->prependMiddleware($middleware, $groupName);
62+
}
63+
} else {
64+
array_unshift($this->middleware[$groupName], $middleware);
65+
}
66+
}
67+
68+
public function appendMiddleware(MiddlewareInterface $middleware, ?string $groupName = null): void
69+
{
70+
if ($groupName === null) {
71+
foreach ($this->middleware as $groupName => $_) {
72+
$this->appendMiddleware($middleware, $groupName);
73+
}
74+
} else {
75+
$this->middleware[$groupName][] = $middleware;
76+
}
77+
}
78+
79+
public function addMiddlewareAfter(
80+
MiddlewareInterface $middleware,
81+
string $afterMiddleware,
82+
?string $groupName = null
83+
): void {
84+
if ($groupName === null) {
85+
foreach ($this->middleware as $groupName => $_) {
86+
$this->addMiddlewareAfter($middleware, $afterMiddleware, $groupName);
87+
}
88+
} else {
89+
$resultedMiddleware = [];
90+
$find = false;
91+
92+
foreach ($this->middleware[$groupName] as $middlewareInstance) {
93+
$resultedMiddleware[] = $middlewareInstance;
94+
if ($middlewareInstance::class === $afterMiddleware) {
95+
$find = true;
96+
$resultedMiddleware[] = $middleware;
97+
}
98+
}
99+
100+
if ($find) {
101+
$this->middleware[$groupName] = $resultedMiddleware;
102+
} else {
103+
$this->appendMiddleware($middleware, $groupName);
104+
}
105+
}
106+
}
107+
108+
public function addMiddlewareBefore(
109+
MiddlewareInterface $middleware,
110+
string $beforeMiddleware,
111+
?string $groupName = null
112+
): void {
113+
if ($groupName === null) {
114+
foreach ($this->middleware as $groupName => $_) {
115+
$this->addMiddlewareBefore($middleware, $beforeMiddleware, $groupName);
116+
}
117+
} else {
118+
$resultedMiddleware = [];
119+
$find = false;
120+
121+
foreach ($this->middleware[$groupName] as $middlewareInstance) {
122+
if (get_class($middlewareInstance) === $beforeMiddleware) {
123+
$find = true;
124+
$resultedMiddleware[] = $middleware;
125+
}
126+
127+
$resultedMiddleware[] = $middlewareInstance;
128+
}
129+
130+
if ($find) {
131+
$this->middleware[$groupName] = $resultedMiddleware;
132+
} else {
133+
$this->prependMiddleware($middleware, $groupName);
134+
}
135+
}
136+
}
137+
138+
private function instantiateMiddleware(string $className, array $params = []): MiddlewareInterface
139+
{
140+
try {
141+
$instance = $this->container->make($className, $params);
142+
143+
if (!$instance instanceof MiddlewareInterface) {
144+
throw new \RuntimeException(
145+
sprintf('Middleware [%s] must implement [%s]', $className, MiddlewareInterface::class)
146+
);
147+
}
148+
149+
return $instance;
150+
} catch (\Throwable $e) {
151+
throw InternalErrorException::from($e);
152+
}
153+
}
154+
}

tests/Stubs/FakeBarMiddleware.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Tochka\JsonRpc\Standard\Tests\Stubs;
4+
5+
use Tochka\JsonRpc\Standard\Contracts\MiddlewareInterface;
6+
7+
class FakeBarMiddleware implements MiddlewareInterface
8+
{
9+
}

tests/Stubs/FakeFooMiddleware.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Tochka\JsonRpc\Standard\Tests\Stubs;
4+
5+
use Illuminate\Container\Container;
6+
use Tochka\JsonRpc\Standard\Contracts\MiddlewareInterface;
7+
8+
class FakeFooMiddleware implements MiddlewareInterface
9+
{
10+
public Container $container;
11+
public int $foo;
12+
public string $bar;
13+
14+
public function __construct(Container $container, int $foo, string $bar)
15+
{
16+
$this->container = $container;
17+
$this->foo = $foo;
18+
$this->bar = $bar;
19+
}
20+
}

0 commit comments

Comments
 (0)