Skip to content

Commit 72e8d5d

Browse files
authored
Merge pull request #804 from patchlevel/3.15.x-merge-up-into-4.0.x_XsYYYdyK
Merge release 3.15.0 into 4.0.x
2 parents ed316c7 + bf535b9 commit 72e8d5d

21 files changed

Lines changed: 1269 additions & 339 deletions

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"require-dev": {
3939
"ext-pdo_sqlite": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
4040
"doctrine/orm": "^2.18.0 || ^3.0.0",
41-
"infection/infection": "^0.31.9",
41+
"infection/infection": "^0.32.0",
4242
"league/commonmark": "^2.6.1",
4343
"patchlevel/coding-standard": "^1.3.0",
4444
"patchlevel/event-sourcing-phpstan-extension": "dev-event-sourcing-4.0",

composer.lock

Lines changed: 288 additions & 209 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/getting-started.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,12 @@ use Patchlevel\EventSourcing\Attribute\Projector;
163163
use Patchlevel\EventSourcing\Attribute\Setup;
164164
use Patchlevel\EventSourcing\Attribute\Subscribe;
165165
use Patchlevel\EventSourcing\Attribute\Teardown;
166-
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
167166

168-
#[Projector('hotel')]
167+
#[Projector(self::TABLE)]
169168
final class HotelProjector
170169
{
171-
use SubscriberUtil;
170+
// use a const for easier access in the projector & to keep projector id and table name in sync
171+
private const TABLE = 'hotel';
172172

173173
public function __construct(
174174
private readonly Connection $db,
@@ -178,14 +178,14 @@ final class HotelProjector
178178
/** @return list<array{id: string, name: string, guests: int}> */
179179
public function getHotels(): array
180180
{
181-
return $this->db->fetchAllAssociative("SELECT id, name, guests FROM {$this->table()};");
181+
return $this->db->fetchAllAssociative(sprintf('SELECT id, name, guests FROM %s;'), self::TABLE);
182182
}
183183

184184
#[Subscribe(HotelCreated::class)]
185185
public function handleHotelCreated(HotelCreated $event): void
186186
{
187187
$this->db->insert(
188-
$this->table(),
188+
self::TABLE,
189189
[
190190
'id' => $event->hotelId->toString(),
191191
'name' => $event->hotelName,
@@ -198,7 +198,7 @@ final class HotelProjector
198198
public function handleGuestIsCheckedIn(GuestIsCheckedIn $event): void
199199
{
200200
$this->db->executeStatement(
201-
"UPDATE {$this->table()} SET guests = guests + 1 WHERE id = ?;",
201+
sprintf('UPDATE %s SET guests = guests + 1 WHERE id = ?;', self::TABLE),
202202
[$event->hotelId->toString()],
203203
);
204204
}
@@ -207,26 +207,21 @@ final class HotelProjector
207207
public function handleGuestIsCheckedOut(GuestIsCheckedOut $event): void
208208
{
209209
$this->db->executeStatement(
210-
"UPDATE {$this->table()} SET guests = guests - 1 WHERE id = ?;",
210+
sprintf('UPDATE %s SET guests = guests - 1 WHERE id = ?;', self::TABLE),
211211
[$event->hotelId->toString()],
212212
);
213213
}
214214

215215
#[Setup]
216216
public function create(): void
217217
{
218-
$this->db->executeStatement("CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR, guests INTEGER);");
218+
$this->db->executeStatement(sprintf('CREATE TABLE IF NOT EXISTS %s (id VARCHAR PRIMARY KEY, name VARCHAR, guests INTEGER);', self::TABLE));
219219
}
220220

221221
#[Teardown]
222222
public function drop(): void
223223
{
224-
$this->db->executeStatement("DROP TABLE IF EXISTS {$this->table()};");
225-
}
226-
227-
private function table(): string
228-
{
229-
return 'projection_' . $this->subscriberId();
224+
$this->db->executeStatement(sprintf('DROP TABLE IF EXISTS %s;', self::TABLE));
230225
}
231226
}
232227
```

docs/subscription.md

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,6 @@ use Patchlevel\EventSourcing\Subscription\Lookup;
234234
#[Projector('public_profile')]
235235
final class PublicProfileProjection
236236
{
237-
use SubscriberUtil;
238-
239237
// ... constructor
240238

241239
#[Subscribe(Published::class)]
@@ -310,32 +308,26 @@ use Doctrine\DBAL\Connection;
310308
use Patchlevel\EventSourcing\Attribute\Projector;
311309
use Patchlevel\EventSourcing\Attribute\Setup;
312310
use Patchlevel\EventSourcing\Attribute\Teardown;
313-
use Patchlevel\EventSourcing\Subscription\Subscriber\SubscriberUtil;
314311

315-
#[Projector('profile_1')]
312+
#[Projector(self::TABLE)]
316313
final class ProfileProjector
317314
{
318-
use SubscriberUtil;
315+
private const TABLE = 'profile_v1';
319316

320317
private Connection $connection;
321318

322319
#[Setup]
323320
public function create(): void
324321
{
325322
$this->connection->executeStatement(
326-
"CREATE TABLE IF NOT EXISTS {$this->table()} (id VARCHAR PRIMARY KEY, name VARCHAR NOT NULL);",
323+
sprintf('CREATE TABLE IF NOT EXISTS %s (id VARCHAR PRIMARY KEY, name VARCHAR NOT NULL);', self::TABLE),
327324
);
328325
}
329326

330327
#[Teardown]
331328
public function drop(): void
332329
{
333-
$this->connection->executeStatement("DROP TABLE IF EXISTS {$this->table()};");
334-
}
335-
336-
private function table(): string
337-
{
338-
return 'projection_' . $this->subscriberId();
330+
$this->connection->executeStatement(sprintf('DROP TABLE IF EXISTS %s;', self::TABLE));
339331
}
340332
}
341333
```
@@ -345,12 +337,11 @@ MySQL and MariaDB don't support transactions for DDL statements.
345337
So you must use a different database connection in your projectors,
346338
otherwise you will get an error when the subscription tries to create the table.
347339
:::
348-
340+
349341
:::warning
350342
If you change the subscriber id, you must also change the table/collection name.
351343
The subscription engine will create a new subscription with the new subscriber id.
352344
That means the setup method will be called again and the table/collection will conflict with the old existing projection.
353-
You can use the `SubscriberUtil` to build the table/collection name.
354345
:::
355346

356347
:::note

phpstan-baseline.neon

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ parameters:
4242
count: 1
4343
path: src/Message/Serializer/DefaultHeadersSerializer.php
4444

45-
-
46-
message: '#^Call to an undefined method Patchlevel\\EventSourcing\\Store\\Store\:\:archive\(\)\.$#'
47-
identifier: method.notFound
48-
count: 1
49-
path: src/Repository/DefaultRepository.php
50-
5145
-
5246
message: '#^Property Patchlevel\\EventSourcing\\Serializer\\Normalizer\\IdNormalizer\:\:\$identifierClass \(class\-string\<Patchlevel\\EventSourcing\\Identifier\\Identifier\>\|null\) does not accept string\.$#'
5347
identifier: assign.propertyType
@@ -168,6 +162,24 @@ parameters:
168162
count: 3
169163
path: src/Subscription/ThrowableToErrorContextTransformer.php
170164

165+
-
166+
message: '#^Cannot access offset ''name'' on array\<string, mixed\>\|false\.$#'
167+
identifier: offsetAccess.nonOffsetAccessible
168+
count: 1
169+
path: tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php
170+
171+
-
172+
message: '#^Method Patchlevel\\EventSourcing\\Tests\\Benchmark\\BasicImplementation\\Projection\\ProfileProjector\:\:getProfileName\(\) should return string but returns mixed\.$#'
173+
identifier: return.type
174+
count: 1
175+
path: tests/Benchmark/BasicImplementation/Projection/ProfileProjector.php
176+
177+
-
178+
message: '#^Parameter \#1 \$messageLoader of class Patchlevel\\EventSourcing\\Subscription\\Engine\\DefaultSubscriptionEngine constructor expects Patchlevel\\EventSourcing\\Subscription\\Engine\\MessageLoader, Patchlevel\\EventSourcing\\Store\\StreamDoctrineDbalStore given\.$#'
179+
identifier: argument.type
180+
count: 1
181+
path: tests/Benchmark/CommandToQueryBench.php
182+
171183
-
172184
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Tests\\\\Integration\\\\BankAccountSplitStream\\\\BankAccount'' and Patchlevel\\EventSourcing\\Tests\\Integration\\BankAccountSplitStream\\BankAccount will always evaluate to true\.$#'
173185
identifier: staticMethod.alreadyNarrowedType
@@ -216,6 +228,42 @@ parameters:
216228
count: 1
217229
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
218230

231+
-
232+
message: '#^Instantiated class Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore not found\.$#'
233+
identifier: class.notFound
234+
count: 1
235+
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
236+
237+
-
238+
message: '#^Parameter \#1 \$messageLoader of class Patchlevel\\EventSourcing\\Subscription\\Engine\\DefaultSubscriptionEngine constructor expects Patchlevel\\EventSourcing\\Subscription\\Engine\\MessageLoader, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#'
239+
identifier: argument.type
240+
count: 1
241+
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
242+
243+
-
244+
message: '#^Parameter \#2 \$schemaConfigurator of class Patchlevel\\EventSourcing\\Schema\\DoctrineSchemaDirector constructor expects Patchlevel\\EventSourcing\\Schema\\DoctrineSchemaConfigurator, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#'
245+
identifier: argument.type
246+
count: 1
247+
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
248+
249+
-
250+
message: '#^Parameter \#2 \$store of class Patchlevel\\EventSourcing\\Repository\\DefaultRepositoryManager constructor expects Patchlevel\\EventSourcing\\Store\\Store, Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\DoctrineDbalStore given\.$#'
251+
identifier: argument.type
252+
count: 1
253+
path: tests/Integration/BasicImplementation/BasicIntegrationTest.php
254+
255+
-
256+
message: '#^Cannot access offset ''name'' on array\<string, mixed\>\|false\.$#'
257+
identifier: offsetAccess.nonOffsetAccessible
258+
count: 1
259+
path: tests/Integration/BasicImplementation/Projection/ProfileProjector.php
260+
261+
-
262+
message: '#^Method Patchlevel\\EventSourcing\\Tests\\Integration\\BasicImplementation\\Projection\\ProfileProjector\:\:getProfileName\(\) should return string but returns mixed\.$#'
263+
identifier: return.type
264+
count: 1
265+
path: tests/Integration/BasicImplementation/Projection/ProfileProjector.php
266+
219267
-
220268
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Tests\\\\Integration\\\\MicroAggregate\\\\Profile'' and Patchlevel\\EventSourcing\\Tests\\Integration\\MicroAggregate\\Profile will always evaluate to true\.$#'
221269
identifier: staticMethod.alreadyNarrowedType
@@ -276,12 +324,6 @@ parameters:
276324
count: 1
277325
path: tests/Integration/Subscription/SubscriptionTest.php
278326

279-
-
280-
message: '#^Cannot use array destructuring on list\<mixed\>\|null\.$#'
281-
identifier: offsetAccess.nonArray
282-
count: 1
283-
path: tests/ReturnCallback.php
284-
285327
-
286328
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Patchlevel\\\\EventSourcing\\\\Metadata\\\\AggregateRoot\\\\AggregateRootMetadata'' and Patchlevel\\EventSourcing\\Metadata\\AggregateRoot\\AggregateRootMetadata\<Patchlevel\\EventSourcing\\Aggregate\\BasicAggregateRoot\> will always evaluate to true\.$#'
287329
identifier: staticMethod.alreadyNarrowedType
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Patchlevel\EventSourcing\Console\Command;
6+
7+
use Patchlevel\EventSourcing\Console\InputHelper;
8+
use Patchlevel\EventSourcing\Console\OutputStyle;
9+
use Patchlevel\EventSourcing\Message\Pipe;
10+
use Patchlevel\EventSourcing\Message\Translator\Translator;
11+
use Patchlevel\EventSourcing\Store\Store;
12+
use Symfony\Component\Console\Attribute\AsCommand;
13+
use Symfony\Component\Console\Command\Command;
14+
use Symfony\Component\Console\Input\InputInterface;
15+
use Symfony\Component\Console\Input\InputOption;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
18+
use function count;
19+
20+
#[AsCommand(
21+
'event-sourcing:store:migrate',
22+
'migrate events from one store to another',
23+
)]
24+
final class StoreMigrateCommand extends Command
25+
{
26+
/** @param iterable<int, Translator> $translators */
27+
public function __construct(
28+
private readonly Store $store,
29+
private readonly Store $newStore,
30+
private readonly iterable $translators = [],
31+
) {
32+
parent::__construct();
33+
}
34+
35+
protected function configure(): void
36+
{
37+
$this
38+
->addOption(
39+
'buffer',
40+
null,
41+
InputOption::VALUE_REQUIRED,
42+
'How many messages should be buffered',
43+
1_000,
44+
);
45+
}
46+
47+
protected function execute(InputInterface $input, OutputInterface $output): int
48+
{
49+
$buffer = InputHelper::positiveIntOrZero($input->getOption('buffer'));
50+
$style = new OutputStyle($input, $output);
51+
52+
$style->info('Migration initialization...');
53+
54+
$count = $this->store->count();
55+
$messages = $this->store->load();
56+
57+
$style->progressStart($count);
58+
59+
$bufferedMessages = [];
60+
61+
$pipe = new Pipe(
62+
$messages,
63+
...$this->translators,
64+
);
65+
66+
foreach ($pipe as $message) {
67+
$bufferedMessages[] = $message;
68+
69+
if (count($bufferedMessages) < $buffer) {
70+
continue;
71+
}
72+
73+
$this->newStore->save(...$bufferedMessages);
74+
$bufferedMessages = [];
75+
$style->progressAdvance($buffer);
76+
}
77+
78+
if (count($bufferedMessages) !== 0) {
79+
$this->newStore->save(...$bufferedMessages);
80+
$style->progressAdvance(count($bufferedMessages));
81+
}
82+
83+
$style->progressFinish();
84+
$style->success('Migration finished');
85+
86+
return 0;
87+
}
88+
}

src/Subscription/Engine/SubscriptionManager.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,48 +77,48 @@ public function find(SubscriptionCriteria $criteria): array
7777
public function add(Subscription ...$subscriptions): void
7878
{
7979
foreach ($subscriptions as $sub) {
80-
$this->forAdd->attach($sub);
80+
$this->forAdd->offsetSet($sub);
8181
}
8282
}
8383

8484
public function update(Subscription ...$subscriptions): void
8585
{
8686
foreach ($subscriptions as $sub) {
87-
$this->forUpdate->attach($sub);
87+
$this->forUpdate->offsetSet($sub);
8888
}
8989
}
9090

9191
public function remove(Subscription ...$subscriptions): void
9292
{
9393
foreach ($subscriptions as $sub) {
94-
$this->forRemove->attach($sub);
94+
$this->forRemove->offsetSet($sub);
9595
}
9696
}
9797

9898
public function flush(): void
9999
{
100100
foreach ($this->forAdd as $subscription) {
101-
if ($this->forRemove->contains($subscription)) {
101+
if ($this->forRemove->offsetExists($subscription)) {
102102
continue;
103103
}
104104

105105
$this->subscriptionStore->add($subscription);
106106
}
107107

108108
foreach ($this->forUpdate as $subscription) {
109-
if ($this->forAdd->contains($subscription)) {
109+
if ($this->forAdd->offsetExists($subscription)) {
110110
continue;
111111
}
112112

113-
if ($this->forRemove->contains($subscription)) {
113+
if ($this->forRemove->offsetExists($subscription)) {
114114
continue;
115115
}
116116

117117
$this->subscriptionStore->update($subscription);
118118
}
119119

120120
foreach ($this->forRemove as $subscription) {
121-
if ($this->forAdd->contains($subscription)) {
121+
if ($this->forAdd->offsetExists($subscription)) {
122122
continue;
123123
}
124124

src/Subscription/Subscriber/SubscriberHelper.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadata;
99
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory;
1010

11+
/** @deprecated since 3.15.0 will be removed with 4.0.0 */
1112
final class SubscriberHelper
1213
{
1314
public function __construct(

src/Subscription/Subscriber/SubscriberUtil.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Patchlevel\EventSourcing\Metadata\Subscriber\AttributeSubscriberMetadataFactory;
88
use Patchlevel\EventSourcing\Metadata\Subscriber\SubscriberMetadataFactory;
99

10+
/** @deprecated since 3.15.0 will be removed with 4.0.0 */
1011
trait SubscriberUtil
1112
{
1213
private static SubscriberMetadataFactory|null $metadataFactory = null;

0 commit comments

Comments
 (0)