Skip to content

Commit de3bab8

Browse files
committed
exclude internal symfony urls
1 parent ba5e8ae commit de3bab8

File tree

8 files changed

+268
-1
lines changed

8 files changed

+268
-1
lines changed

src/DependencyInjection/Configuration.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@
4242
* enabled: bool,
4343
* ids: list<string>,
4444
* groups: list<string>,
45+
* exclude_url: string|null
4546
* },
46-
* rebuild_after_file_change: array{enabled: bool, cache_pool: string},
47+
* rebuild_after_file_change: array{
48+
* enabled: bool,
49+
* cache_pool: string,
50+
* exclude_url: string|null
51+
* },
4752
* gap_detection: array{
4853
* enabled: bool,
4954
* retries_in_ms: list<int>,
@@ -277,6 +282,7 @@ public function getConfigTreeBuilder(): TreeBuilder
277282
->children()
278283
->arrayNode('ids')->scalarPrototype()->end()->end()
279284
->arrayNode('groups')->scalarPrototype()->end()->end()
285+
->scalarNode('exclude_url')->defaultValue('^/_(wdt|profiler|error)')->end()
280286
->end()
281287
->end()
282288

@@ -285,6 +291,7 @@ public function getConfigTreeBuilder(): TreeBuilder
285291
->addDefaultsIfNotSet()
286292
->children()
287293
->scalarNode('cache_pool')->defaultValue('cache.app')->end()
294+
->scalarNode('exclude_url')->defaultValue('^/_(wdt|profiler|error)')->end()
288295
->end()
289296
->end()
290297

src/DependencyInjection/PatchlevelEventSourcingExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ static function (ChildDefinition $definition): void {
572572
new Reference(SubscriptionEngine::class),
573573
$config['subscription']['auto_setup']['ids'] ?: null,
574574
$config['subscription']['auto_setup']['groups'] ?: null,
575+
$config['subscription']['auto_setup']['exclude_url'] ?: null,
575576
])
576577
->addTag('kernel.event_listener', [
577578
'event' => 'kernel.request',
@@ -590,6 +591,7 @@ static function (ChildDefinition $definition): void {
590591
new TaggedIteratorArgument('event_sourcing.subscriber'),
591592
new Reference($config['subscription']['rebuild_after_file_change']['cache_pool']),
592593
new Reference(SubscriberMetadataFactory::class),
594+
$config['subscription']['rebuild_after_file_change']['exclude_url'] ?: null,
593595
])
594596
->addTag('kernel.event_listener', [
595597
'event' => 'kernel.request',

src/RequestListener/AutoSetupListener.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Patchlevel\EventSourcing\Subscription\Status;
1010
use Symfony\Component\HttpKernel\Event\RequestEvent;
1111

12+
use function preg_match;
13+
1214
final class AutoSetupListener
1315
{
1416
/**
@@ -19,6 +21,7 @@ public function __construct(
1921
private readonly SubscriptionEngine $subscriptionEngine,
2022
private readonly array|null $ids,
2123
private readonly array|null $groups,
24+
private readonly string|null $excludeUrl = null,
2225
) {
2326
}
2427

@@ -28,6 +31,13 @@ public function onKernelRequest(RequestEvent $event): void
2831
return;
2932
}
3033

34+
if (
35+
$this->excludeUrl !== null
36+
&& preg_match('#' . $this->excludeUrl . '#', $event->getRequest()->getRequestUri())
37+
) {
38+
return;
39+
}
40+
3141
$subscriptions = $this->subscriptionEngine->subscriptions(
3242
new SubscriptionEngineCriteria(
3343
$this->ids,

src/RequestListener/SubscriptionRebuildAfterFileChangeListener.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\HttpKernel\Event\RequestEvent;
1515

1616
use function filemtime;
17+
use function preg_match;
1718

1819
final class SubscriptionRebuildAfterFileChangeListener
1920
{
@@ -23,6 +24,7 @@ public function __construct(
2324
private readonly iterable $subscribers,
2425
private readonly CacheItemPoolInterface $cache,
2526
private readonly SubscriberMetadataFactory $metadataFactory = new AttributeSubscriberMetadataFactory(),
27+
private readonly string|null $excludeUrl = null,
2628
) {
2729
}
2830

@@ -32,6 +34,13 @@ public function onKernelRequest(RequestEvent $event): void
3234
return;
3335
}
3436

37+
if (
38+
$this->excludeUrl !== null
39+
&& preg_match('#' . $this->excludeUrl . '#', $event->getRequest()->getRequestUri())
40+
) {
41+
return;
42+
}
43+
3544
$toRemove = [];
3645
$itemsToSave = [];
3746

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcingBundle\Tests\Fixtures;
6+
7+
use Patchlevel\EventSourcing\Attribute\Subscriber;
8+
use Patchlevel\EventSourcing\Subscription\RunMode;
9+
10+
#[Subscriber('from-beginning', RunMode::FromBeginning)]
11+
final class FromBeginningSubscriber
12+
{
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcingBundle\Tests\Fixtures;
6+
7+
use Patchlevel\EventSourcing\Attribute\Subscriber;
8+
use Patchlevel\EventSourcing\Subscription\RunMode;
9+
10+
#[Subscriber('from-now', RunMode::FromNow)]
11+
final class FromNowSubscriber
12+
{
13+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcingBundle\Tests\Unit\RequestListener;
6+
7+
use Patchlevel\EventSourcing\Subscription\Engine\Result;
8+
use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngine;
9+
use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngineCriteria;
10+
use Patchlevel\EventSourcing\Subscription\Status;
11+
use Patchlevel\EventSourcing\Subscription\Subscription;
12+
use Patchlevel\EventSourcingBundle\RequestListener\AutoSetupListener;
13+
use PHPUnit\Framework\TestCase;
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Event\RequestEvent;
16+
use Symfony\Component\HttpKernel\HttpKernelInterface;
17+
18+
/** @covers \Patchlevel\EventSourcingBundle\RequestListener\AutoSetupListener */
19+
final class AutoSetupListenerTest extends TestCase
20+
{
21+
public function testSkipSubRequest(): void
22+
{
23+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
24+
$subscriptionEngine->expects($this->never())->method('subscriptions');
25+
$subscriptionEngine->expects($this->never())->method('setup');
26+
27+
$listener = new AutoSetupListener($subscriptionEngine, null, null);
28+
$listener->onKernelRequest($this->createRequestEvent('/foo', HttpKernelInterface::SUB_REQUEST));
29+
}
30+
31+
public function testSkipExcludedUrl(): void
32+
{
33+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
34+
$subscriptionEngine->expects($this->never())->method('subscriptions');
35+
$subscriptionEngine->expects($this->never())->method('setup');
36+
37+
$listener = new AutoSetupListener($subscriptionEngine, null, null, '^/_profiler');
38+
$listener->onKernelRequest($this->createRequestEvent('/_profiler/test'));
39+
}
40+
41+
public function testSetupOnlyNewSubscriptions(): void
42+
{
43+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
44+
$subscriptionEngine
45+
->expects($this->once())
46+
->method('subscriptions')
47+
->with($this->callback(static function (SubscriptionEngineCriteria|null $criteria): bool {
48+
return $criteria instanceof SubscriptionEngineCriteria
49+
&& $criteria->ids === ['id-1']
50+
&& $criteria->groups === ['group-1'];
51+
}))
52+
->willReturn([
53+
new Subscription('new-1'),
54+
new Subscription('active-1', status: Status::Active),
55+
new Subscription('new-2'),
56+
]);
57+
58+
$subscriptionEngine
59+
->expects($this->once())
60+
->method('setup')
61+
->with($this->callback(static function (SubscriptionEngineCriteria|null $criteria): bool {
62+
return $criteria instanceof SubscriptionEngineCriteria
63+
&& $criteria->ids === ['new-1', 'new-2']
64+
&& $criteria->groups === null;
65+
}), true)
66+
->willReturn(new Result());
67+
68+
$listener = new AutoSetupListener($subscriptionEngine, ['id-1'], ['group-1']);
69+
$listener->onKernelRequest($this->createRequestEvent('/app'));
70+
}
71+
72+
private function createRequestEvent(
73+
string $uri,
74+
int $requestType = HttpKernelInterface::MAIN_REQUEST,
75+
): RequestEvent {
76+
return new RequestEvent(
77+
$this->createMock(HttpKernelInterface::class),
78+
Request::create($uri),
79+
$requestType,
80+
);
81+
}
82+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcingBundle\Tests\Unit\RequestListener;
6+
7+
use Patchlevel\EventSourcing\Subscription\Engine\ProcessedResult;
8+
use Patchlevel\EventSourcing\Subscription\Engine\Result;
9+
use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngine;
10+
use Patchlevel\EventSourcing\Subscription\Engine\SubscriptionEngineCriteria;
11+
use Patchlevel\EventSourcingBundle\RequestListener\SubscriptionRebuildAfterFileChangeListener;
12+
use Patchlevel\EventSourcingBundle\Tests\Fixtures\FromBeginningSubscriber;
13+
use Patchlevel\EventSourcingBundle\Tests\Fixtures\FromNowSubscriber;
14+
use PHPUnit\Framework\TestCase;
15+
use Psr\Cache\CacheItemInterface;
16+
use Psr\Cache\CacheItemPoolInterface;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpKernel\Event\RequestEvent;
19+
use Symfony\Component\HttpKernel\HttpKernelInterface;
20+
21+
/** @covers \Patchlevel\EventSourcingBundle\RequestListener\SubscriptionRebuildAfterFileChangeListener */
22+
final class SubscriptionRebuildAfterFileChangeListenerTest extends TestCase
23+
{
24+
public function testSkipSubRequest(): void
25+
{
26+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
27+
$subscriptionEngine->expects($this->never())->method('remove');
28+
$subscriptionEngine->expects($this->never())->method('setup');
29+
$subscriptionEngine->expects($this->never())->method('boot');
30+
31+
$cache = $this->createMock(CacheItemPoolInterface::class);
32+
$cache->expects($this->never())->method('getItem');
33+
34+
$listener = new SubscriptionRebuildAfterFileChangeListener(
35+
$subscriptionEngine,
36+
[new FromBeginningSubscriber()],
37+
$cache,
38+
);
39+
40+
$listener->onKernelRequest($this->createRequestEvent('/app', HttpKernelInterface::SUB_REQUEST));
41+
}
42+
43+
public function testSkipExcludedUrl(): void
44+
{
45+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
46+
$subscriptionEngine->expects($this->never())->method('remove');
47+
$subscriptionEngine->expects($this->never())->method('setup');
48+
$subscriptionEngine->expects($this->never())->method('boot');
49+
50+
$cache = $this->createMock(CacheItemPoolInterface::class);
51+
$cache->expects($this->never())->method('getItem');
52+
53+
$listener = new SubscriptionRebuildAfterFileChangeListener(
54+
$subscriptionEngine,
55+
[new FromBeginningSubscriber()],
56+
$cache,
57+
excludeUrl: '^/_wdt',
58+
);
59+
60+
$listener->onKernelRequest($this->createRequestEvent('/_wdt/abc'));
61+
}
62+
63+
public function testRebuildChangedFromBeginningSubscriptionsOnly(): void
64+
{
65+
$item = $this->createMock(CacheItemInterface::class);
66+
$item->expects($this->once())->method('get')->willReturn(1);
67+
$item->expects($this->once())->method('set')->with($this->isType('int'))->willReturnSelf();
68+
69+
$cache = $this->createMock(CacheItemPoolInterface::class);
70+
$cache->expects($this->once())->method('getItem')->with('from-beginning')->willReturn($item);
71+
$cache->expects($this->once())->method('save')->with($item)->willReturn(true);
72+
73+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
74+
$criteriaMatcher = $this->callback(static fn (SubscriptionEngineCriteria|null $criteria): bool => $criteria instanceof SubscriptionEngineCriteria && $criteria->ids === ['from-beginning'] && $criteria->groups === null);
75+
76+
$subscriptionEngine->expects($this->once())->method('remove')->with($criteriaMatcher)->willReturn(new Result());
77+
$subscriptionEngine->expects($this->once())->method('setup')->with($criteriaMatcher)->willReturn(new Result());
78+
$subscriptionEngine->expects($this->once())->method('boot')->with($criteriaMatcher)->willReturn(new ProcessedResult(0));
79+
80+
$listener = new SubscriptionRebuildAfterFileChangeListener(
81+
$subscriptionEngine,
82+
[new FromBeginningSubscriber(), new FromNowSubscriber()],
83+
$cache,
84+
);
85+
86+
$listener->onKernelRequest($this->createRequestEvent('/app'));
87+
}
88+
89+
public function testNoRebuildWhenFileDidNotChange(): void
90+
{
91+
$subscriberFile = (new \ReflectionClass(FromBeginningSubscriber::class))->getFileName();
92+
self::assertIsString($subscriberFile);
93+
94+
$currentModified = filemtime($subscriberFile);
95+
self::assertIsInt($currentModified);
96+
97+
$item = $this->createMock(CacheItemInterface::class);
98+
$item->expects($this->once())->method('get')->willReturn($currentModified);
99+
$item->expects($this->never())->method('set');
100+
101+
$cache = $this->createMock(CacheItemPoolInterface::class);
102+
$cache->expects($this->once())->method('getItem')->with('from-beginning')->willReturn($item);
103+
$cache->expects($this->never())->method('save');
104+
105+
$subscriptionEngine = $this->createMock(SubscriptionEngine::class);
106+
$emptyCriteriaMatcher = $this->callback(static fn (SubscriptionEngineCriteria|null $criteria): bool => $criteria instanceof SubscriptionEngineCriteria && $criteria->ids === []);
107+
108+
$subscriptionEngine->expects($this->once())->method('remove')->with($emptyCriteriaMatcher)->willReturn(new Result());
109+
$subscriptionEngine->expects($this->once())->method('setup')->with($emptyCriteriaMatcher)->willReturn(new Result());
110+
$subscriptionEngine->expects($this->once())->method('boot')->with($emptyCriteriaMatcher)->willReturn(new ProcessedResult(0));
111+
112+
$listener = new SubscriptionRebuildAfterFileChangeListener(
113+
$subscriptionEngine,
114+
[new FromBeginningSubscriber()],
115+
$cache,
116+
);
117+
118+
$listener->onKernelRequest($this->createRequestEvent('/app'));
119+
}
120+
121+
private function createRequestEvent(
122+
string $uri,
123+
int $requestType = HttpKernelInterface::MAIN_REQUEST,
124+
): RequestEvent {
125+
return new RequestEvent(
126+
$this->createMock(HttpKernelInterface::class),
127+
Request::create($uri),
128+
$requestType,
129+
);
130+
}
131+
}

0 commit comments

Comments
 (0)