Commit 44c32205 authored by Mickaël Bourgier's avatar Mickaël Bourgier
Browse files

🐛 Only stores ClassStub of projections in data collector

Storing the whole projection was causing problems when it wasn't serializable.
Events stored in data collector is now an array of plain arrays with only necessary data, and events themselves are now cloned.
Test of html rendering has been updated to use real class data collector instead of a fake one.
parent 91a1e461
...@@ -10,9 +10,12 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector; ...@@ -10,9 +10,12 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\VarDumper\Caster\ClassStub; use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\Data;
use Throwable;
use Webf\Projections\Core\Event\EventClassManagerInterface; use Webf\Projections\Core\Event\EventClassManagerInterface;
use Webf\Projections\Core\Event\EventInterface; use Webf\Projections\Core\Event\EventInterface;
use Webf\Projections\Core\Service\Data\Caller;
use Webf\Projections\Core\Service\Data\DispatchedEvent; use Webf\Projections\Core\Service\Data\DispatchedEvent;
use Webf\Projections\Core\Service\Data\ProcessedProjection;
use Webf\Projections\Core\Service\EventProjectorInterface; use Webf\Projections\Core\Service\EventProjectorInterface;
use Webf\Projections\Core\Service\ProjectionManagerInterface; use Webf\Projections\Core\Service\ProjectionManagerInterface;
use Webf\Projections\Core\Service\TraceableEventProjector; use Webf\Projections\Core\Service\TraceableEventProjector;
...@@ -32,7 +35,20 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -32,7 +35,20 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
* nb_projections?: int, * nb_projections?: int,
* nb_total_projections?: int, * nb_total_projections?: int,
* execution_time?: float, * execution_time?: float,
* events?: array<DispatchedEvent> * events?: array<array{
* type: Data,
* execution_time: float,
* failed: bool,
* orphaned: bool,
* caller: Caller|null,
* value: Data,
* projections: array<array{
* type: Data,
* execution_time: float,
* failed: bool,
* throwable: Data
* }>
* }>
* } * }
*/ */
protected $data = []; protected $data = [];
...@@ -52,7 +68,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -52,7 +68,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
public function collect( public function collect(
Request $request, Request $request,
Response $response, Response $response,
\Throwable $exception = null Throwable $exception = null
): void { ): void {
// See $this->lateCollect() // See $this->lateCollect()
} }
...@@ -62,6 +78,19 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -62,6 +78,19 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
return self::NAME; return self::NAME;
} }
/**
* @codeCoverageIgnore
*/
protected function getCasters(): array
{
$casters = parent::getCasters();
// Unset the default caster truncating collectors data.
unset($casters['*']);
return $casters;
}
public function reset(): void public function reset(): void
{ {
$this->data = []; $this->data = [];
...@@ -81,7 +110,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -81,7 +110,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
= $nbOrphanedEvents = $nbOrphanedEvents
= $executionTime = 0; = $executionTime = 0;
$eventTypes = $projectionTypes = []; $eventTypes = $projections = [];
foreach ($events as $event) { foreach ($events as $event) {
++$nbEvents; ++$nbEvents;
...@@ -98,7 +127,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -98,7 +127,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
$eventTypes[get_class($event->getValue())] = null; $eventTypes[get_class($event->getValue())] = null;
foreach ($event->getProjections() as $projection) { foreach ($event->getProjections() as $projection) {
$projectionTypes[get_class($projection->getValue())] = null; $projections[spl_object_hash($projection->getValue())] = null;
} }
} }
...@@ -106,7 +135,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -106,7 +135,7 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
EventInterface::class, EventInterface::class,
]); ]);
$projections = $this->projectionManager->getProjections(); $allProjections = $this->projectionManager->getProjections();
$this->data = [ $this->data = [
'nb_events' => $nbEvents, 'nb_events' => $nbEvents,
...@@ -115,10 +144,31 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -115,10 +144,31 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
'nb_orphaned_events' => $nbOrphanedEvents, 'nb_orphaned_events' => $nbOrphanedEvents,
'nb_event_types' => count($eventTypes), 'nb_event_types' => count($eventTypes),
'nb_total_event_types' => count($eventClasses), 'nb_total_event_types' => count($eventClasses),
'nb_projections' => count($projectionTypes), 'nb_projections' => count($projections),
'nb_total_projections' => count($projections), 'nb_total_projections' => count($allProjections),
'execution_time' => $executionTime, 'execution_time' => $executionTime,
'events' => $events, 'events' => array_map(
fn (DispatchedEvent $dispatchedEvent) => [
'type' => $this->getType($dispatchedEvent->getValue()),
'execution_time' => $dispatchedEvent->getExecutionTime(),
'failed' => $dispatchedEvent->hasFailed(),
'orphaned' => 0 === count($dispatchedEvent->getProjections()),
'caller' => $dispatchedEvent->getCaller(),
'value' => $this->cloneVar($dispatchedEvent->getValue()),
'projections' => array_map(
fn (ProcessedProjection $processedProjection) => [
'type' => $this->getType($processedProjection->getValue()),
'execution_time' => $processedProjection->getExecutionTime(),
'failed' => $processedProjection->hasFailed(),
'throwable' => $this->cloneVar(
$processedProjection->getThrowable()
),
],
$dispatchedEvent->getProjections()
),
],
$events
),
]; ];
} }
...@@ -168,17 +218,30 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl ...@@ -168,17 +218,30 @@ class WebfProjectionsDataCollector extends DataCollector implements LateDataColl
} }
/** /**
* @return DispatchedEvent[] * @psalm-return array<array{
* type: Data,
* execution_time: float,
* failed: bool,
* orphaned: bool,
* caller: Caller|null,
* value: Data,
* projections: array<array{
* type: Data,
* execution_time: float,
* failed: bool,
* throwable: Data
* }>
* }>
*/ */
public function getEvents(): array public function getEvents(): array
{ {
return $this->data['events'] ?? []; return $this->data['events'] ?? [];
} }
public function getClassStub(string $className): Data private function getType(object $object): Data
{ {
/** @var Data[] $data */ /** @var Data[] $data */
$data = $this->cloneVar([new ClassStub($className)]); $data = $this->cloneVar([new ClassStub(get_class($object))]);
return $data[0]; return $data[0];
} }
......
...@@ -160,7 +160,7 @@ ...@@ -160,7 +160,7 @@
{{ helper.render_event_table( {{ helper.render_event_table(
event, event,
event.failed event.failed
or event.projections | length == 0 or event.orphaned
or collector.nbFailedEvents == 0 or collector.nbFailedEvents == 0
and collector.nbOrphanedEvents == 0 and collector.nbOrphanedEvents == 0
and loop.index0 == 0, and loop.index0 == 0,
...@@ -250,7 +250,7 @@ ...@@ -250,7 +250,7 @@
{% if collector.nbOrphanedEvents > 0 %} {% if collector.nbOrphanedEvents > 0 %}
{% for event in collector.events %} {% for event in collector.events %}
{% set first_seen = false %} {% set first_seen = false %}
{% if event.projections | length == 0 %} {% if event.orphaned %}
{{ helper.render_event_table( {{ helper.render_event_table(
event, event,
true, true,
...@@ -279,7 +279,6 @@ ...@@ -279,7 +279,6 @@
{% macro render_event_table(event, open = false, collector) %} {% macro render_event_table(event, open = false, collector) %}
{% import _self as helper %} {% import _self as helper %}
{# @var event \Webf\Projections\Core\Service\Data\DispatchedEvent #}
{% set discr = random() %} {% set discr = random() %}
<table class="dispatched-event-item"> <table class="dispatched-event-item">
<thead> <thead>
...@@ -289,15 +288,15 @@ ...@@ -289,15 +288,15 @@
data-toggle-initial="{{ open ? 'display' }}" data-toggle-initial="{{ open ? 'display' }}"
> >
<span class="dump-inline"> <span class="dump-inline">
{{ profiler_dump(collector.classStub(event.type)) }} {{ profiler_dump(event.type) }}
</span> </span>
<span class="text-muted execution-time"> <span class="text-muted execution-time">
({{ '%.2F' | format(event.executionTime) }} ms) ({{ '%.2F' | format(event.execution_time) }} ms)
</span> </span>
{% if event.failed %} {% if event.failed %}
<span class="label status-error failed">failed</span> <span class="label status-error failed">failed</span>
{% endif %} {% endif %}
{% if event.projections | length == 0 %} {% if event.orphaned %}
<span class="label status-warning orphaned">orphaned</span> <span class="label status-warning orphaned">orphaned</span>
{% endif %} {% endif %}
<a class="toggle-button"> <a class="toggle-button">
...@@ -313,7 +312,6 @@ ...@@ -313,7 +312,6 @@
</thead> </thead>
<tbody id="dispatched-event-item-{{ discr }}-details" class="sf-toggle-content"> <tbody id="dispatched-event-item-{{ discr }}-details" class="sf-toggle-content">
{# @var \Webf\Projections\Core\Service\Data\Caller caller #}
{% set caller = event.caller %} {% set caller = event.caller %}
{% if caller %} {% if caller %}
<tr> <tr>
...@@ -349,7 +347,7 @@ ...@@ -349,7 +347,7 @@
<tr> <tr>
<td class="text-bold">Event</td> <td class="text-bold">Event</td>
<td>{{ dump(event.value) }}</td> <td>{{ profiler_dump(event.value) }}</td>
</tr> </tr>
<tr class="projections"> <tr class="projections">
...@@ -380,16 +378,14 @@ ...@@ -380,16 +378,14 @@
{% endmacro %} {% endmacro %}
{% macro render_projection_td(event, projection, collector) %} {% macro render_projection_td(event, projection, collector) %}
{# @var \Webf\Projections\Core\Service\Data\DispatchedEvent event #} <td class="projection {{ projection.failed ? 'status-error' }}">
{# @var \Webf\Projections\Core\Service\Data\ProcessedProjection projection #} <span class="dump-inline">{{ profiler_dump(projection.type) }}</span>
<td class="projection {{ projection.hasFailed() ? 'status-error' }}">
<span class="dump-inline">{{ profiler_dump(collector.classStub(projection.type)) }}</span>
<span class="text-muted"> <span class="text-muted">
({{ '%.2F' | format(projection.executionTime) }} ms) ({{ '%.2F' | format(projection.execution_time) }} ms)
</span> </span>
{% if projection.hasFailed() %} {% if projection.failed %}
<span class="font-normal label status-error">failed</span> <span class="font-normal label status-error">failed</span>
{{ dump(projection.throwable) }} {{ profiler_dump(projection.throwable) }}
{% endif %} {% endif %}
</td> </td>
{% endmacro %} {% endmacro %}
...@@ -7,11 +7,11 @@ namespace Tests\DataCollector; ...@@ -7,11 +7,11 @@ namespace Tests\DataCollector;
use Exception; use Exception;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\Profiler\Profile; use Symfony\Component\HttpKernel\Profiler\Profile;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Tests\Event\Stub\EventStub; use Tests\Event\Stub\EventStub;
use Tests\Event\Stub\EventStubA; use Tests\Event\Stub\EventStubA;
use Tests\Event\Stub\EventStubB; use Tests\Event\Stub\EventStubB;
use Tests\Projection\Stub\ProjectionStub; use Tests\Projection\Stub\ProjectionStub;
use Tests\Projection\Stub\UnserializableProjection;
use Tests\Service\Stub\FakeTraceableEventProjector; use Tests\Service\Stub\FakeTraceableEventProjector;
use Webf\Projections\Bundle\DataCollector\WebfProjectionsDataCollector; use Webf\Projections\Bundle\DataCollector\WebfProjectionsDataCollector;
use Webf\Projections\Core\Event\EventClassManagerInterface; use Webf\Projections\Core\Event\EventClassManagerInterface;
...@@ -70,14 +70,14 @@ class WebfProjectionsDataCollectorTest extends TestCase ...@@ -70,14 +70,14 @@ class WebfProjectionsDataCollectorTest extends TestCase
public function test_late_collect(): void public function test_late_collect(): void
{ {
$e1 = new DispatchedEvent(new EventStubA(), null); $e1 = new DispatchedEvent(new EventStubA(), null);
$e1->projectionStarted(new ProjectionStub()); $e1->projectionStarted($projections[] = new ProjectionStub());
$e1->projectionSucceeded(); $e1->projectionSucceeded();
$e1->projectionStarted(new ProjectionStub()); $e1->projectionStarted($projections[] = new ProjectionStub());
$e1->projectionSucceeded(); $e1->projectionSucceeded();
$e1->dispatched(); $e1->dispatched();
$e2 = new DispatchedEvent(new EventStubA(), null); $e2 = new DispatchedEvent(new EventStubA(), null);
$e2->projectionStarted(new ProjectionStub()); $e2->projectionStarted($projections[] = new ProjectionStub());
$e2->projectionFailed(new Exception()); $e2->projectionFailed(new Exception());
$e2->dispatched(); $e2->dispatched();
...@@ -85,13 +85,13 @@ class WebfProjectionsDataCollectorTest extends TestCase ...@@ -85,13 +85,13 @@ class WebfProjectionsDataCollectorTest extends TestCase
$e3->dispatched(); $e3->dispatched();
$eventClasses = [EventStub::class, EventStubA::class, EventStubB::class]; $eventClasses = [EventStub::class, EventStubA::class, EventStubB::class];
$projections = [new ProjectionStub(), new ProjectionStub()]; $allProjections = [new ProjectionStub(), new ProjectionStub()];
$dataCollector = new WebfProjectionsDataCollector( $dataCollector = new WebfProjectionsDataCollector(
new InMemoryEventClassManager($eventClasses), new InMemoryEventClassManager($eventClasses),
new FakeTraceableEventProjector([$e1, $e2, $e3]), new FakeTraceableEventProjector([$e1, $e2, $e3]),
new ProjectionManager([ new ProjectionManager([
new ProjectionFactory($projections), new ProjectionFactory($allProjections),
]) ])
); );
...@@ -106,13 +106,57 @@ class WebfProjectionsDataCollectorTest extends TestCase ...@@ -106,13 +106,57 @@ class WebfProjectionsDataCollectorTest extends TestCase
count($eventClasses), count($eventClasses),
$dataCollector->getNbTotalEventTypes() $dataCollector->getNbTotalEventTypes()
); );
$this->assertSame(1, $dataCollector->getNbProjections()); $this->assertSame(count($projections), $dataCollector->getNbProjections());
$this->assertSame(count($projections), $dataCollector->getNbTotalProjections()); $this->assertSame(count($allProjections), $dataCollector->getNbTotalProjections());
$this->assertSame( $this->assertSame(
$e1->getExecutionTime() + $e2->getExecutionTime() + $e3->getExecutionTime(), $e1->getExecutionTime() + $e2->getExecutionTime() + $e3->getExecutionTime(),
$dataCollector->getExecutionTime() $dataCollector->getExecutionTime()
); );
$this->assertSame([$e1, $e2, $e3], $dataCollector->getEvents()); $events = $dataCollector->getEvents();
foreach ([$e1, $e2, $e3] as $i => $event) {
$this->assertEquals(
get_class($event->getValue()),
(string) $events[$i]['type']
);
$this->assertEquals(
$event->getExecutionTime(),
$events[$i]['execution_time']
);
$this->assertEquals(
$event->hasFailed(),
$events[$i]['failed']
);
$this->assertEquals(
0 === count($event->getProjections()),
$events[$i]['orphaned']
);
$this->assertEquals(
$event->getCaller(),
$events[$i]['caller']
);
$this->assertEquals(
get_class($event->getValue()),
$events[$i]['value']->getType()
);
foreach ($event->getProjections() as $j => $projection) {
$this->assertEquals(
get_class($projection->getValue()),
(string) $events[$i]['projections'][$j]['type']
);
$this->assertEquals(
$projection->getExecutionTime(),
$events[$i]['projections'][$j]['execution_time']
);
$this->assertEquals(
$projection->hasFailed(),
$events[$i]['projections'][$j]['failed']
);
$this->assertEquals(
is_object($t = $projection->getThrowable()) ? get_class($t) : gettype($t),
$events[$i]['projections'][$j]['throwable']->getType()
);
}
}
} }
public function test_late_collect_need_traceable_event_projector() public function test_late_collect_need_traceable_event_projector()
...@@ -166,19 +210,23 @@ class WebfProjectionsDataCollectorTest extends TestCase ...@@ -166,19 +210,23 @@ class WebfProjectionsDataCollectorTest extends TestCase
$this->assertEquals(0, $dataCollector->getNbSuccessfulEvents()); $this->assertEquals(0, $dataCollector->getNbSuccessfulEvents());
} }
public function test_get_class_stub() public function test_projection_can_be_unserializable()
{ {
$dataCollector = new WebfProjectionsDataCollector( $dataCollector = new WebfProjectionsDataCollector(
$this->createMock(EventClassManagerInterface::class), new InMemoryEventClassManager([EventStub::class]),
$this->createMock(EventProjectorInterface::class), $eventProjector = new TraceableEventProjector(
$this->createMock(ProjectionManagerInterface::class) $projectionManager = new ProjectionManager([
new ProjectionFactory([
new UnserializableProjection([EventStub::class]),
]),
])
),
$projectionManager
); );
$classStubData = $dataCollector->getClassStub(WebfProjectionsDataCollector::class); $eventProjector->dispatch(new EventStub());
$dataCollector->lateCollect();
$this->assertEquals( $this->assertNotEmpty(serialize($dataCollector));
new ClassStub(WebfProjectionsDataCollector::class),
$classStubData->getValue()
);
} }
} }
<?php
declare(strict_types=1);
namespace Tests\Projection\Stub;
use Closure;
use Webf\Projections\Core\ProjectionStorage\InMemoryProjectionStorage;
class UnserializableProjection extends ProjectionStub
{
private Closure $unserializableValue;
public function __construct(array $subscribedEvents = [], ?InMemoryProjectionStorage $storage = null)
{
parent::__construct($subscribedEvents, $storage);
$this->unserializableValue = function () {};
}
}
This diff is collapsed.
<?php
declare(strict_types=1);
namespace Tests\Resources\Views\Stub;
use Webf\Projections\Bundle\DataCollector\WebfProjectionsDataCollector;
class WebfProjectionsDataCollectorStub extends WebfProjectionsDataCollector
{
public function __construct($data = [])
{
$this->data = $data;
}
public function lateCollect(): void
{
throw new \RuntimeException('This method should not be called on this stub. Use the setData method to change the data.');
}
public function setData(array $data): void
{
$this->data = $data;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment