Milestone 3: Middleware Pipeline (The Onion) with Telemetry
This commit is contained in:
parent
9aa63f2744
commit
9f92227547
53
src/MiddlewarePipeline.php
Normal file
53
src/MiddlewarePipeline.php
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Phred\Beacon;
|
||||||
|
|
||||||
|
use Phred\BeaconContracts\DispatchMiddlewareInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MiddlewarePipeline
|
||||||
|
*
|
||||||
|
* Coordinates the execution of global middleware before listeners are called.
|
||||||
|
* Uses a recursive "Onion" style closure chain.
|
||||||
|
*/
|
||||||
|
class MiddlewarePipeline
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<DispatchMiddlewareInterface> The list of global middleware.
|
||||||
|
*/
|
||||||
|
private array $middlewares = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a middleware to the pipeline.
|
||||||
|
*
|
||||||
|
* @param DispatchMiddlewareInterface $middleware
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function add(DispatchMiddlewareInterface $middleware): void
|
||||||
|
{
|
||||||
|
$this->middlewares[] = $middleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the middleware pipeline, wrapping the final action.
|
||||||
|
*
|
||||||
|
* @param object $event The event object.
|
||||||
|
* @param callable $finalAction The final action (typically listener execution).
|
||||||
|
* @return object The potentially modified event object.
|
||||||
|
*/
|
||||||
|
public function execute(object $event, callable $finalAction): object
|
||||||
|
{
|
||||||
|
$pipeline = array_reverse($this->middlewares);
|
||||||
|
|
||||||
|
$runner = array_reduce(
|
||||||
|
$pipeline,
|
||||||
|
fn(callable $next, DispatchMiddlewareInterface $middleware) =>
|
||||||
|
fn(object $event) => $middleware->handle($event, $next),
|
||||||
|
$finalAction
|
||||||
|
);
|
||||||
|
|
||||||
|
return $runner($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/TelemetryMiddleware.php
Normal file
54
src/TelemetryMiddleware.php
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Phred\Beacon;
|
||||||
|
|
||||||
|
use Phred\BeaconContracts\DispatchMiddlewareInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TelemetryMiddleware
|
||||||
|
*
|
||||||
|
* A middleware that records execution time and memory usage for a dispatch.
|
||||||
|
* In a real-world scenario, this would push data to a Telemetry system.
|
||||||
|
* For now, it provides a basic structure for performance tracking.
|
||||||
|
*/
|
||||||
|
class TelemetryMiddleware implements DispatchMiddlewareInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<string, array{time: float, memory: int}> Recorded telemetry data.
|
||||||
|
*/
|
||||||
|
private array $data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the event and records telemetry.
|
||||||
|
*
|
||||||
|
* @param object $event
|
||||||
|
* @param callable $next
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
public function handle(object $event, callable $next): object
|
||||||
|
{
|
||||||
|
$start = microtime(true);
|
||||||
|
$mem = memory_get_usage();
|
||||||
|
|
||||||
|
$result = $next($event);
|
||||||
|
|
||||||
|
$this->data[$event::class] = [
|
||||||
|
'time' => microtime(true) - $start,
|
||||||
|
'memory' => memory_get_usage() - $mem,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the recorded telemetry data.
|
||||||
|
*
|
||||||
|
* @return array<string, array{time: float, memory: int}>
|
||||||
|
*/
|
||||||
|
public function getData(): array
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
}
|
||||||
103
tests/Integration/MiddlewarePipelineTest.php
Normal file
103
tests/Integration/MiddlewarePipelineTest.php
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Phred\Beacon\Tests\Integration;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Phred\Beacon\EventDispatcher;
|
||||||
|
use Phred\Beacon\ListenerProvider;
|
||||||
|
use Phred\Beacon\TelemetryMiddleware;
|
||||||
|
use Phred\BeaconContracts\DispatchMiddlewareInterface;
|
||||||
|
|
||||||
|
class MiddlewarePipelineTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_it_executes_middleware_in_order(): void
|
||||||
|
{
|
||||||
|
$provider = new ListenerProvider();
|
||||||
|
$dispatcher = new EventDispatcher($provider);
|
||||||
|
$order = [];
|
||||||
|
|
||||||
|
$middleware1 = new class($order) implements DispatchMiddlewareInterface {
|
||||||
|
public function __construct(private array &$order) {}
|
||||||
|
public function handle(object $event, callable $next): object {
|
||||||
|
$this->order[] = 'm1_before';
|
||||||
|
$result = $next($event);
|
||||||
|
$this->order[] = 'm1_after';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$middleware2 = new class($order) implements DispatchMiddlewareInterface {
|
||||||
|
public function __construct(private array &$order) {}
|
||||||
|
public function handle(object $event, callable $next): object {
|
||||||
|
$this->order[] = 'm2_before';
|
||||||
|
$result = $next($event);
|
||||||
|
$this->order[] = 'm2_after';
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$dispatcher->addMiddleware($middleware1);
|
||||||
|
$dispatcher->addMiddleware($middleware2);
|
||||||
|
|
||||||
|
$provider->addListener('stdClass', function() use (&$order) {
|
||||||
|
$order[] = 'listener';
|
||||||
|
});
|
||||||
|
|
||||||
|
$dispatcher->dispatch(new \stdClass());
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
'm1_before',
|
||||||
|
'm2_before',
|
||||||
|
'listener',
|
||||||
|
'm2_after',
|
||||||
|
'm1_after'
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_middleware_can_modify_event(): void
|
||||||
|
{
|
||||||
|
$provider = new ListenerProvider();
|
||||||
|
$dispatcher = new EventDispatcher($provider);
|
||||||
|
|
||||||
|
$middleware = new class implements DispatchMiddlewareInterface {
|
||||||
|
public function handle(object $event, callable $next): object {
|
||||||
|
$event->modified = true;
|
||||||
|
return $next($event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$dispatcher->addMiddleware($middleware);
|
||||||
|
|
||||||
|
$event = new \stdClass();
|
||||||
|
$event->modified = false;
|
||||||
|
|
||||||
|
$dispatcher->dispatch($event);
|
||||||
|
|
||||||
|
$this->assertTrue($event->modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_telemetry_middleware_records_data(): void
|
||||||
|
{
|
||||||
|
$provider = new ListenerProvider();
|
||||||
|
$dispatcher = new EventDispatcher($provider);
|
||||||
|
$telemetry = new TelemetryMiddleware();
|
||||||
|
|
||||||
|
$dispatcher->addMiddleware($telemetry);
|
||||||
|
|
||||||
|
$provider->addListener('stdClass', function() {
|
||||||
|
usleep(1000); // 1ms
|
||||||
|
});
|
||||||
|
|
||||||
|
$event = new \stdClass();
|
||||||
|
$dispatcher->dispatch($event);
|
||||||
|
|
||||||
|
$data = $telemetry->getData();
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('stdClass', $data);
|
||||||
|
$this->assertGreaterThan(0, $data['stdClass']['time']);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue