Skip to content

Commit 91d8895

Browse files
xepozzvjik
andauthored
Move MiddlewareCollector (#101)
Co-authored-by: Sergei Predvoditelev <[email protected]>
1 parent 5de2559 commit 91d8895

7 files changed

+275
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 5.2.1 under development
44

5+
- New #101: Add `MiddlewareCollector` for Yii Debug package (@xepozz)
56
- Enh #95: Raise minimum PHP version to `^8.1` and make all possible properties readonly (@xepozz)
67

78
## 5.2.0 September 25, 2023

composer-require-checker.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"symbol-whitelist": [
3+
"Yiisoft\\Yii\\Debug\\Collector\\CollectorTrait",
4+
"Yiisoft\\Yii\\Debug\\Collector\\SummaryCollectorInterface",
5+
"Yiisoft\\Yii\\Debug\\Collector\\TimelineCollector"
6+
],
7+
"php-core-extensions": [
8+
"Core",
9+
"date",
10+
"json",
11+
"pcre",
12+
"Phar",
13+
"Reflection",
14+
"SPL",
15+
"standard"
16+
],
17+
"scan-files": []
18+
}

composer.json

+12-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"roave/infection-static-analysis-plugin": "^1.18",
4646
"spatie/phpunit-watcher": "^1.23",
4747
"vimeo/psalm": "^5.3",
48-
"yiisoft/test-support": "^1.3"
48+
"yiisoft/test-support": "^3.0",
49+
"yiisoft/yii-debug": "dev-master"
4950
},
5051
"autoload": {
5152
"psr-4": {
@@ -57,11 +58,20 @@
5758
"Yiisoft\\Middleware\\Dispatcher\\Tests\\": "tests"
5859
}
5960
},
61+
"extra": {
62+
"config-plugin-options": {
63+
"source-directory": "config"
64+
},
65+
"config-plugin": {
66+
"events-web": "events-web.php"
67+
}
68+
},
6069
"config": {
6170
"sort-packages": true,
6271
"allow-plugins": {
6372
"infection/extension-installer": true,
64-
"composer/package-versions-deprecated": true
73+
"composer/package-versions-deprecated": true,
74+
"yiisoft/config": false
6575
}
6676
},
6777
"scripts": {

config/events-web.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Yiisoft\Middleware\Dispatcher\Debug\MiddlewareCollector;
6+
use Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware;
7+
use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware;
8+
9+
if (!(bool) ($params['yiisoft/yii-debug']['enabled'] ?? false)) {
10+
return [];
11+
}
12+
13+
return [
14+
BeforeMiddleware::class => [
15+
[MiddlewareCollector::class, 'collect'],
16+
],
17+
AfterMiddleware::class => [
18+
[MiddlewareCollector::class, 'collect'],
19+
],
20+
];

src/Debug/MiddlewareCollector.php

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Middleware\Dispatcher\Debug;
6+
7+
use ReflectionClass;
8+
use Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware;
9+
use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware;
10+
use Yiisoft\Yii\Debug\Collector\CollectorTrait;
11+
use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface;
12+
use Yiisoft\Yii\Debug\Collector\TimelineCollector;
13+
14+
final class MiddlewareCollector implements SummaryCollectorInterface
15+
{
16+
use CollectorTrait;
17+
18+
/**
19+
* @var array[]
20+
*/
21+
private array $beforeStack = [];
22+
23+
/**
24+
* @var array[]
25+
*/
26+
private array $afterStack = [];
27+
28+
public function __construct(
29+
private readonly TimelineCollector $timelineCollector
30+
) {
31+
}
32+
33+
public function getCollected(): array
34+
{
35+
if (!$this->isActive()) {
36+
return [];
37+
}
38+
$beforeStack = $this->beforeStack;
39+
$afterStack = $this->afterStack;
40+
$beforeAction = array_pop($beforeStack);
41+
$afterAction = array_shift($afterStack);
42+
$actionHandler = [];
43+
44+
if (is_array($beforeAction) && is_array($afterAction)) {
45+
$actionHandler = $this->getActionHandler($beforeAction, $afterAction);
46+
}
47+
48+
return [
49+
'beforeStack' => $beforeStack,
50+
'actionHandler' => $actionHandler,
51+
'afterStack' => $afterStack,
52+
];
53+
}
54+
55+
public function collect(BeforeMiddleware|AfterMiddleware $event): void
56+
{
57+
if (!$this->isActive()) {
58+
return;
59+
}
60+
61+
if (
62+
method_exists($event->getMiddleware(), '__debugInfo')
63+
&& (new ReflectionClass($event->getMiddleware()))->isAnonymous()
64+
) {
65+
/**
66+
* @var callable $callback
67+
* @psalm-suppress MixedArrayAccess
68+
*/
69+
$callback = $event->getMiddleware()->__debugInfo()['callback'];
70+
if (is_array($callback)) {
71+
if (is_string($callback[0])) {
72+
$name = implode('::', $callback);
73+
} else {
74+
$name = $callback[0]::class . '::' . $callback[1];
75+
}
76+
} elseif (is_string($callback)) {
77+
$name = '{closure:' . $callback . '}';
78+
} else {
79+
$name = 'object(Closure)#' . spl_object_id($callback);
80+
}
81+
} else {
82+
$name = $event->getMiddleware()::class;
83+
}
84+
if ($event instanceof BeforeMiddleware) {
85+
$this->beforeStack[] = [
86+
'name' => $name,
87+
'time' => microtime(true),
88+
'memory' => memory_get_usage(),
89+
'request' => $event->getRequest(),
90+
];
91+
} else {
92+
$this->afterStack[] = [
93+
'name' => $name,
94+
'time' => microtime(true),
95+
'memory' => memory_get_usage(),
96+
'response' => $event->getResponse(),
97+
];
98+
}
99+
$this->timelineCollector->collect($this, spl_object_id($event));
100+
}
101+
102+
private function reset(): void
103+
{
104+
$this->beforeStack = [];
105+
$this->afterStack = [];
106+
}
107+
108+
public function getSummary(): array
109+
{
110+
if (!$this->isActive()) {
111+
return [];
112+
}
113+
return [
114+
'middleware' => [
115+
'total' => ($total = count($this->beforeStack)) > 0 ? $total - 1 : 0, // Remove action handler
116+
],
117+
];
118+
}
119+
120+
private function getActionHandler(array $beforeAction, array $afterAction): array
121+
{
122+
return [
123+
'name' => $beforeAction['name'],
124+
'startTime' => $beforeAction['time'],
125+
'request' => $beforeAction['request'],
126+
'response' => $afterAction['response'],
127+
'endTime' => $afterAction['time'],
128+
'memory' => $afterAction['memory'],
129+
];
130+
}
131+
}
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Middleware\Dispatcher\Debug\Tests;
6+
7+
use Nyholm\Psr7\Response;
8+
use Nyholm\Psr7\ServerRequest;
9+
use Psr\Http\Server\MiddlewareInterface;
10+
use Yiisoft\Di\Container;
11+
use Yiisoft\Di\ContainerConfig;
12+
use Yiisoft\Middleware\Dispatcher\Debug\MiddlewareCollector;
13+
use Yiisoft\Middleware\Dispatcher\Event\AfterMiddleware;
14+
use Yiisoft\Middleware\Dispatcher\Event\BeforeMiddleware;
15+
use Yiisoft\Middleware\Dispatcher\MiddlewareFactory;
16+
use Yiisoft\Middleware\Dispatcher\Tests\Support\DummyMiddleware;
17+
use Yiisoft\Yii\Debug\Collector\CollectorInterface;
18+
use Yiisoft\Yii\Debug\Collector\TimelineCollector;
19+
use Yiisoft\Yii\Debug\Tests\Shared\AbstractCollectorTestCase;
20+
21+
final class MiddlewareCollectorTest extends AbstractCollectorTestCase
22+
{
23+
/**
24+
* @param CollectorInterface|MiddlewareCollector $collector
25+
*/
26+
protected function collectTestData(CollectorInterface $collector): void
27+
{
28+
// before
29+
$collector->collect(new BeforeMiddleware($this->createCallableMiddleware(static fn () => 1), new ServerRequest('GET', '/test')));
30+
$collector->collect(new BeforeMiddleware($this->createCallableMiddleware([DummyMiddleware::class, 'process']), new ServerRequest('GET', '/test')));
31+
$collector->collect(new BeforeMiddleware($this->createCallableMiddleware('time'), new ServerRequest('GET', '/test')));
32+
33+
// action
34+
$collector->collect(new BeforeMiddleware(new DummyMiddleware(), new ServerRequest('GET', '/test')));
35+
$collector->collect(new AfterMiddleware(new DummyMiddleware(), new Response(200)));
36+
37+
// after
38+
$collector->collect(new AfterMiddleware($this->createCallableMiddleware(static fn () => 1), new Response(200)));
39+
$collector->collect(new AfterMiddleware($this->createCallableMiddleware([DummyMiddleware::class, 'process']), new Response(200)));
40+
$collector->collect(new AfterMiddleware($this->createCallableMiddleware('time'), new Response(200)));
41+
}
42+
43+
protected function getCollector(): CollectorInterface
44+
{
45+
return new MiddlewareCollector(new TimelineCollector());
46+
}
47+
48+
protected function checkCollectedData(array $data): void
49+
{
50+
parent::checkCollectedData($data);
51+
52+
$this->assertNotEmpty($data['beforeStack']);
53+
$this->assertNotEmpty($data['afterStack']);
54+
$this->assertNotEmpty($data['actionHandler']);
55+
56+
$this->assertEquals(DummyMiddleware::class, $data['actionHandler']['name']);
57+
$this->assertEquals('GET', $data['actionHandler']['request']->getMethod());
58+
59+
$this->assertCount(3, $data['beforeStack']);
60+
$this->assertStringStartsWith('object(Closure)#', $data['beforeStack'][0]['name']);
61+
$this->assertEquals(DummyMiddleware::class . '::process', $data['beforeStack'][1]['name']);
62+
$this->assertEquals('{closure:time}', $data['beforeStack'][2]['name']);
63+
64+
$this->assertCount(3, $data['afterStack']);
65+
$this->assertStringStartsWith('object(Closure)#', $data['afterStack'][0]['name']);
66+
$this->assertEquals(DummyMiddleware::class . '::process', $data['afterStack'][1]['name']);
67+
$this->assertEquals('{closure:time}', $data['afterStack'][2]['name']);
68+
}
69+
70+
private function createCallableMiddleware(callable|array $callable): MiddlewareInterface
71+
{
72+
$factory = new MiddlewareFactory(new Container(ContainerConfig::create()));
73+
return $factory->create($callable);
74+
}
75+
}

tests/Support/DummyMiddleware.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\Middleware\Dispatcher\Tests\Support;
6+
7+
use Psr\Http\Message\ResponseInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Psr\Http\Server\MiddlewareInterface;
10+
use Psr\Http\Server\RequestHandlerInterface;
11+
12+
final class DummyMiddleware implements MiddlewareInterface
13+
{
14+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
15+
{
16+
return $handler->handle($request);
17+
}
18+
}

0 commit comments

Comments
 (0)