1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
5
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
6
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
7
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
8
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
9
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
10
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
11
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
12
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
13
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
14
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
15
|
|
|
* |
16
|
|
|
* The software is based on the Axon Framework project which is |
17
|
|
|
* licensed under the Apache 2.0 license. For more information on the Axon Framework |
18
|
|
|
* see <http://www.axonframework.org/>. |
19
|
|
|
* |
20
|
|
|
* This software consists of voluntary contributions made by many individuals |
21
|
|
|
* and is licensed under the MIT license. For more information, see |
22
|
|
|
* <http://www.governor-framework.org/>. |
23
|
|
|
*/ |
24
|
|
|
|
25
|
|
|
namespace Governor\Framework\Test; |
26
|
|
|
|
27
|
|
|
use Governor\Framework\Annotations\CommandHandler; |
28
|
|
|
use Governor\Framework\CommandHandling\CommandBusInterface; |
29
|
|
|
use Governor\Framework\CommandHandling\CommandCallbackInterface; |
30
|
|
|
use Governor\Framework\CommandHandling\CommandHandlerInterceptorInterface; |
31
|
|
|
use Governor\Framework\CommandHandling\CommandHandlerInterface; |
32
|
|
|
use Governor\Framework\CommandHandling\CommandMessageInterface; |
33
|
|
|
use Governor\Framework\CommandHandling\GenericCommandMessage; |
34
|
|
|
use Governor\Framework\CommandHandling\Handlers\AnnotatedAggregateCommandHandler; |
35
|
|
|
use Governor\Framework\CommandHandling\Handlers\AnnotatedCommandHandler; |
36
|
|
|
use Governor\Framework\CommandHandling\InterceptorChainInterface; |
37
|
|
|
use Governor\Framework\CommandHandling\SimpleCommandBus; |
38
|
|
|
use Governor\Framework\Common\Annotation\MethodMessageHandlerInspector; |
39
|
|
|
use Governor\Framework\Common\Annotation\SimpleAnnotationReaderFactory; |
40
|
|
|
use Governor\Framework\Common\IdentifierValidator; |
41
|
|
|
use Governor\Framework\Domain\AggregateRootInterface; |
42
|
|
|
use Governor\Framework\Domain\DomainEventMessageInterface; |
43
|
|
|
use Governor\Framework\Domain\DomainEventStreamInterface; |
44
|
|
|
use Governor\Framework\Domain\EventMessageInterface; |
45
|
|
|
use Governor\Framework\Domain\GenericDomainEventMessage; |
46
|
|
|
use Governor\Framework\Domain\MessageInterface; |
47
|
|
|
use Governor\Framework\Domain\SimpleDomainEventStream; |
48
|
|
|
use Governor\Framework\EventHandling\EventBusInterface; |
49
|
|
|
use Governor\Framework\EventSourcing\AggregateFactoryInterface; |
50
|
|
|
use Governor\Framework\EventSourcing\EventSourcedAggregateRootInterface; |
51
|
|
|
use Governor\Framework\EventSourcing\EventSourcingRepository; |
52
|
|
|
use Governor\Framework\EventSourcing\GenericAggregateFactory; |
53
|
|
|
use Governor\Framework\EventStore\EventStoreException; |
54
|
|
|
use Governor\Framework\EventStore\EventStoreInterface; |
55
|
|
|
use Governor\Framework\Repository\AggregateNotFoundException; |
56
|
|
|
use Governor\Framework\Repository\NullLockManager; |
57
|
|
|
use Governor\Framework\Repository\RepositoryInterface; |
58
|
|
|
use Governor\Framework\Test\Utils\RecordingEventBus; |
59
|
|
|
use Governor\Framework\UnitOfWork\DefaultUnitOfWork; |
60
|
|
|
use Governor\Framework\UnitOfWork\DefaultUnitOfWorkFactory; |
61
|
|
|
use Governor\Framework\UnitOfWork\UnitOfWorkInterface; |
62
|
|
|
use Governor\Framework\UnitOfWork\UnitOfWorkListenerAdapter; |
63
|
|
|
use Monolog\Handler\StreamHandler; |
64
|
|
|
use Monolog\Logger; |
65
|
|
|
use Psr\Log\LoggerInterface; |
66
|
|
|
|
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Description of GivenWhenThenTestFixture |
70
|
|
|
* |
71
|
|
|
* @author "David Kalosi" <[email protected]> |
72
|
|
|
* @license <a href="http://www.opensource.org/licenses/mit-license.php">MIT License</a> |
73
|
|
|
*/ |
74
|
|
|
class GivenWhenThenTestFixture implements FixtureConfigurationInterface, TestExecutorInterface |
75
|
|
|
{ |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @var Logger |
79
|
|
|
*/ |
80
|
|
|
private $logger; |
81
|
|
|
/** |
82
|
|
|
* @var IdentifierValidatingRepository |
83
|
|
|
*/ |
84
|
|
|
private $repository; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @var SimpleCommandBus |
88
|
|
|
*/ |
89
|
|
|
private $commandBus; |
90
|
|
|
/** |
91
|
|
|
* @var RecordingEventBus |
92
|
|
|
*/ |
93
|
|
|
private $eventBus; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @var string |
97
|
|
|
*/ |
98
|
|
|
private $aggregateIdentifier; |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @var RecordingEventStore |
102
|
|
|
*/ |
103
|
|
|
private $eventStore; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @var DomainEventMessageInterface[] |
107
|
|
|
*/ |
108
|
|
|
private $givenEvents = []; |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @var DomainEventMessageInterface[] |
112
|
|
|
*/ |
113
|
|
|
private $storedEvents = array(); //Deque<DomainEventMessage> storedEvents; |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @var EventMessageInterface[] |
117
|
|
|
*/ |
118
|
|
|
private $publishedEvents = array(); //List<EventMessage> publishedEvents; |
119
|
|
|
/** |
120
|
|
|
* @var int |
121
|
|
|
*/ |
122
|
|
|
private $sequenceNumber = 0; |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @var AggregateRootInterface |
126
|
|
|
*/ |
127
|
|
|
private $workingAggregate; |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @var bool |
131
|
|
|
*/ |
132
|
|
|
private $reportIllegalStateChange = true; |
133
|
|
|
/** |
134
|
|
|
* @var string |
135
|
|
|
*/ |
136
|
|
|
private $aggregateType; |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @var boolean |
140
|
|
|
*/ |
141
|
|
|
private $explicitCommandHandlersSet; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @var FixtureParameterResolverFactory |
145
|
|
|
*/ |
146
|
|
|
private $parameterResolver; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Initializes a new given-when-then style test fixture for the given <code>aggregateType</code>. |
150
|
|
|
* |
151
|
|
|
* @param string $aggregateType The aggregate to initialize the test fixture for |
152
|
|
|
*/ |
153
|
8 |
|
public function __construct($aggregateType) |
154
|
|
|
{ |
155
|
8 |
|
$this->logger = new Logger('fixture'); |
156
|
8 |
|
$this->logger->pushHandler( |
157
|
8 |
|
new StreamHandler( |
158
|
8 |
|
'php://stdout', |
159
|
|
|
Logger::DEBUG |
160
|
8 |
|
) |
161
|
8 |
|
); |
162
|
|
|
|
163
|
8 |
|
$this->eventBus = new RecordingEventBus($this->publishedEvents); |
164
|
8 |
|
$this->commandBus = new SimpleCommandBus(new DefaultUnitOfWorkFactory()); |
165
|
8 |
|
$this->commandBus->setLogger($this->logger); |
166
|
8 |
|
$this->eventStore = new RecordingEventStore( |
167
|
8 |
|
$this->storedEvents, |
168
|
8 |
|
$this->givenEvents, $this->aggregateIdentifier |
169
|
8 |
|
); |
170
|
|
|
|
171
|
8 |
|
$this->parameterResolver = new FixtureParameterResolverFactory(); |
172
|
|
|
|
173
|
8 |
|
$this->aggregateType = $aggregateType; |
174
|
8 |
|
$this->clearGivenWhenState(); |
175
|
8 |
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* @return Logger |
179
|
|
|
*/ |
180
|
|
|
public function getLogger() |
181
|
|
|
{ |
182
|
|
|
return $this->logger; |
183
|
|
|
} |
184
|
|
|
|
185
|
6 |
|
public function registerRepository(EventSourcingRepository $eventSourcingRepository) |
186
|
|
|
{ |
187
|
6 |
|
$this->repository = new IdentifierValidatingRepository($eventSourcingRepository); |
188
|
|
|
|
189
|
6 |
|
return $this; |
190
|
|
|
} |
191
|
|
|
|
192
|
4 |
|
public function registerAggregateFactory(AggregateFactoryInterface $aggregateFactory) |
193
|
|
|
{ |
194
|
4 |
|
return $this->registerRepository( |
195
|
4 |
|
new EventSourcingRepository( |
196
|
4 |
|
$aggregateFactory->getAggregateType(), |
197
|
4 |
|
$this->eventBus, new NullLockManager(), |
198
|
4 |
|
$this->eventStore, $aggregateFactory |
199
|
4 |
|
) |
200
|
4 |
|
); |
201
|
|
|
} |
202
|
|
|
|
203
|
4 |
|
public function registerAnnotatedCommandHandler($annotatedCommandHandler) |
204
|
|
|
{ |
205
|
4 |
|
$this->registerAggregateCommandHandlers(); |
206
|
4 |
|
$this->explicitCommandHandlersSet = true; |
207
|
|
|
|
208
|
4 |
|
$reflectionClass = new \ReflectionClass($annotatedCommandHandler); |
209
|
4 |
|
$inspector = new MethodMessageHandlerInspector( |
210
|
4 |
|
new SimpleAnnotationReaderFactory(), |
211
|
4 |
|
$reflectionClass, |
212
|
|
|
CommandHandler::class |
213
|
4 |
|
); |
214
|
|
|
|
215
|
4 |
|
foreach ($inspector->getHandlerDefinitions() as $handlerDefinition) { |
216
|
4 |
|
$handler = new AnnotatedCommandHandler( |
217
|
4 |
|
$reflectionClass->name, |
218
|
4 |
|
$handlerDefinition->getMethod()->name, |
219
|
4 |
|
$this->parameterResolver, |
220
|
|
|
$annotatedCommandHandler |
221
|
4 |
|
); |
222
|
|
|
|
223
|
4 |
|
$this->commandBus->subscribe($handlerDefinition->getPayloadType(), $handler); |
224
|
4 |
|
} |
225
|
|
|
|
226
|
4 |
|
return $this; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
public function registerCommandHandler( |
230
|
|
|
$commandName, |
231
|
|
|
CommandHandlerInterface $commandHandler |
232
|
|
|
) { |
233
|
|
|
$this->registerAggregateCommandHandlers(); |
234
|
|
|
$this->explicitCommandHandlersSet = true; |
235
|
|
|
$this->commandBus->subscribe($commandName, $commandHandler); |
236
|
|
|
|
237
|
|
|
return $this; |
238
|
|
|
} |
239
|
|
|
|
240
|
1 |
|
public function registerInjectableResource($id, $resource) |
241
|
|
|
{ |
242
|
1 |
|
if ($this->explicitCommandHandlersSet) { |
243
|
1 |
|
throw new FixtureExecutionException( |
244
|
|
|
"Cannot inject resources after command handler has been created. ". |
245
|
1 |
|
"Configure all resource before calling ". |
246
|
1 |
|
"registerCommandHandler() or ". |
247
|
|
|
"registerAnnotatedCommandHandler()" |
248
|
1 |
|
); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
$this->parameterResolver->registerService($id, $resource); |
252
|
|
|
|
253
|
|
|
return $this; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
public function givenNoPriorActivity() |
257
|
|
|
{ |
258
|
|
|
return $this->given(array()); |
259
|
|
|
} |
260
|
|
|
|
261
|
4 |
|
public function given(array $domainEvents = array()) |
262
|
|
|
{ |
263
|
4 |
|
$this->ensureRepositoryConfiguration(); |
264
|
4 |
|
$this->clearGivenWhenState(); |
265
|
|
|
try { |
266
|
4 |
|
foreach ($domainEvents as $event) { |
267
|
4 |
|
$payload = $event; |
268
|
4 |
|
$metaData = null; |
269
|
4 |
|
if ($event instanceof MessageInterface) { |
270
|
|
|
$payload = $event->getPayload(); |
271
|
|
|
$metaData = $event->getMetaData(); |
272
|
|
|
} |
273
|
4 |
|
$this->givenEvents[] = new GenericDomainEventMessage( |
274
|
4 |
|
$this->aggregateIdentifier, |
275
|
4 |
|
$this->sequenceNumber++, $payload, $metaData |
276
|
4 |
|
); |
277
|
4 |
|
} |
278
|
4 |
|
} catch (\RuntimeException $ex) { |
279
|
|
|
//FixtureResourceParameterResolverFactory.clear(); |
|
|
|
|
280
|
|
|
} |
281
|
|
|
|
282
|
4 |
|
return $this; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
public function givenCommands(array $commands) |
286
|
|
|
{ |
287
|
|
|
$this->finalizeConfiguration(); |
288
|
|
|
$this->clearGivenWhenState(); |
289
|
|
|
try { |
290
|
|
|
foreach ($commands as $command) { |
291
|
|
|
$callback = new ExecutionExceptionAwareCallback(); |
292
|
|
|
$this->commandBus->dispatch( |
293
|
|
|
GenericCommandMessage::asCommandMessage($command), |
294
|
|
|
$callback |
295
|
|
|
); |
296
|
|
|
$callback->assertSuccessful(); |
297
|
|
|
|
298
|
|
|
foreach ($this->storedEvents as $event) { |
299
|
|
|
$this->givenEvents[] = $event; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$this->storedEvents = array(); |
303
|
|
|
} |
304
|
|
|
$this->publishedEvents = array(); |
305
|
|
|
} catch (\RuntimeException $ex) { |
306
|
|
|
//FixtureResourceParameterResolverFactory.clear(); |
|
|
|
|
307
|
|
|
throw $ex; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
return $this; |
311
|
|
|
} |
312
|
|
|
|
313
|
3 |
|
public function when($command, array $metaData = array()) |
314
|
|
|
{ |
315
|
|
|
try { |
316
|
3 |
|
$this->finalizeConfiguration(); |
317
|
3 |
|
$resultValidator = new ResultValidatorImpl( |
318
|
3 |
|
$this->storedEvents, |
319
|
3 |
|
$this->publishedEvents |
320
|
3 |
|
); |
321
|
3 |
|
$this->commandBus->setHandlerInterceptors( |
322
|
3 |
|
array(new AggregateRegisteringInterceptor($this->workingAggregate)) |
323
|
3 |
|
); |
324
|
|
|
|
325
|
3 |
|
$this->commandBus->dispatch( |
326
|
3 |
|
GenericCommandMessage::asCommandMessage($command)->andMetaData($metaData), |
327
|
|
|
$resultValidator |
328
|
3 |
|
); |
329
|
|
|
|
330
|
3 |
|
$this->detectIllegalStateChanges(); |
331
|
3 |
|
$resultValidator->assertValidRecording(); |
332
|
|
|
|
333
|
3 |
|
return $resultValidator; |
334
|
|
|
} finally { |
335
|
|
|
//FixtureResourceParameterResolverFactory.clear(); |
|
|
|
|
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
339
|
6 |
|
private function ensureRepositoryConfiguration() |
340
|
|
|
{ |
341
|
6 |
|
if (null === $this->repository) { |
342
|
2 |
|
$this->registerRepository( |
343
|
2 |
|
new EventSourcingRepository( |
344
|
2 |
|
$this->aggregateType, |
345
|
2 |
|
$this->eventBus, |
346
|
2 |
|
new NullLockManager(), |
347
|
2 |
|
$this->eventStore, |
348
|
2 |
|
new GenericAggregateFactory($this->aggregateType) |
349
|
2 |
|
) |
350
|
2 |
|
); |
351
|
2 |
|
} |
352
|
6 |
|
} |
353
|
|
|
|
354
|
3 |
|
private function finalizeConfiguration() |
355
|
|
|
{ |
356
|
3 |
|
$this->registerAggregateCommandHandlers(); |
357
|
3 |
|
$this->explicitCommandHandlersSet = true; |
358
|
3 |
|
} |
359
|
|
|
|
360
|
4 |
|
private function registerAggregateCommandHandlers() |
361
|
|
|
{ |
362
|
4 |
|
$this->ensureRepositoryConfiguration(); |
363
|
|
|
|
364
|
4 |
|
if (!$this->explicitCommandHandlersSet) { |
365
|
4 |
|
AnnotatedAggregateCommandHandler::subscribe( |
366
|
4 |
|
$this->aggregateType, |
367
|
4 |
|
$this->repository, |
368
|
4 |
|
$this->commandBus, |
369
|
4 |
|
$this->parameterResolver, |
370
|
4 |
|
null, |
371
|
4 |
|
new SimpleAnnotationReaderFactory() |
372
|
4 |
|
); |
373
|
4 |
|
} |
374
|
4 |
|
} |
375
|
|
|
|
376
|
3 |
|
private function detectIllegalStateChanges() |
377
|
|
|
{ |
378
|
3 |
|
if (null !== $this->aggregateIdentifier && null !== $this->workingAggregate |
379
|
3 |
|
&& $this->reportIllegalStateChange |
380
|
3 |
|
) { |
381
|
3 |
|
$uow = DefaultUnitOfWork::startAndGet(); |
382
|
|
|
try { |
383
|
3 |
|
$aggregate2 = $this->repository->load($this->aggregateIdentifier); |
384
|
3 |
|
if ($this->workingAggregate->isDeleted()) { |
385
|
|
|
throw new GovernorAssertionError( |
386
|
|
|
"The working aggregate was considered deleted, ". |
387
|
|
|
"but the Repository still contains a non-deleted copy of ". |
388
|
|
|
"the aggregate. Make sure the aggregate explicitly marks ". |
389
|
|
|
"itself as deleted in an EventHandler." |
390
|
|
|
); |
391
|
|
|
} |
392
|
3 |
|
$this->assertValidWorkingAggregateState($aggregate2); |
|
|
|
|
393
|
3 |
|
} catch (AggregateNotFoundException $notFound) { |
394
|
|
|
if (!$this->workingAggregate->isDeleted()) { |
395
|
|
|
throw new GovernorAssertionError( |
396
|
|
|
"The working aggregate was not considered deleted, ". |
397
|
|
|
"but the Repository cannot recover the state of the ". |
398
|
|
|
"aggregate, as it is considered deleted there." |
399
|
|
|
); |
400
|
|
|
} |
401
|
|
|
} catch (\Exception $ex) { |
402
|
|
|
$this->logger->warn( |
403
|
|
|
"An Exception occurred while detecting illegal state changes in {class}.", |
404
|
|
|
array('class' => get_class($this->workingAggregate)), |
405
|
|
|
$ex |
|
|
|
|
406
|
|
|
); |
407
|
3 |
|
} finally { |
408
|
|
|
// rollback to prevent changes bing pushed to event store |
409
|
3 |
|
$uow->rollback(); |
410
|
|
|
} |
411
|
3 |
|
} |
412
|
3 |
|
} |
413
|
|
|
|
414
|
|
|
|
415
|
3 |
|
private function assertValidWorkingAggregateState(EventSourcedAggregateRootInterface $eventSourcedAggregate) |
|
|
|
|
416
|
|
|
{ |
417
|
|
|
/*HashSet<ComparationEntry> comparedEntries = new HashSet<ComparationEntry>(); |
|
|
|
|
418
|
|
|
if (!workingAggregate.getClass().equals(eventSourcedAggregate.getClass())) { |
419
|
|
|
throw new AxonAssertionError(String.format("The aggregate loaded based on the generated events seems to " |
420
|
|
|
+ "be of another type than the original.\n" |
421
|
|
|
+ "Working type: <%s>\nEvent Sourced type: <%s>", |
422
|
|
|
workingAggregate.getClass().getName(), |
423
|
|
|
eventSourcedAggregate.getClass().getName())); |
424
|
|
|
} |
425
|
|
|
ensureValuesEqual(workingAggregate, |
426
|
|
|
eventSourcedAggregate, |
427
|
|
|
eventSourcedAggregate.getClass().getName(), |
428
|
|
|
comparedEntries);*/ |
429
|
3 |
|
} |
430
|
|
|
|
431
|
|
|
/* |
|
|
|
|
432
|
|
|
private void ensureValuesEqual(Object workingValue, Object eventSourcedValue, String propertyPath, |
433
|
|
|
Set<ComparationEntry> comparedEntries) { |
434
|
|
|
if (explicitlyUnequal(workingValue, eventSourcedValue)) { |
435
|
|
|
throw new AxonAssertionError(format("Illegal state change detected! " |
436
|
|
|
+ "Property \"%s\" has different value when sourcing events.\n" |
437
|
|
|
+ "Working aggregate value: <%s>\n" |
438
|
|
|
+ "Value after applying events: <%s>", |
439
|
|
|
propertyPath, workingValue, eventSourcedValue)); |
440
|
|
|
} else if (workingValue != null && comparedEntries.add(new ComparationEntry(workingValue, eventSourcedValue)) |
441
|
|
|
&& !hasEqualsMethod(workingValue.getClass())) { |
442
|
|
|
for (Field field : fieldsOf(workingValue.getClass())) { |
443
|
|
|
if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isTransient(field.getModifiers())) { |
444
|
|
|
ensureAccessible(field); |
445
|
|
|
String newPropertyPath = propertyPath + "." + field.getName(); |
446
|
|
|
try { |
447
|
|
|
Object workingFieldValue = field.get(workingValue); |
448
|
|
|
Object eventSourcedFieldValue = field.get(eventSourcedValue); |
449
|
|
|
ensureValuesEqual(workingFieldValue, eventSourcedFieldValue, newPropertyPath, comparedEntries); |
450
|
|
|
} catch (IllegalAccessException e) { |
451
|
|
|
logger.warn("Could not access field \"{}\". Unable to detect inappropriate state changes.", |
452
|
|
|
newPropertyPath); |
453
|
|
|
} |
454
|
|
|
} |
455
|
|
|
} |
456
|
|
|
} |
457
|
|
|
} */ |
458
|
|
|
|
459
|
8 |
|
private function clearGivenWhenState() |
460
|
|
|
{ |
461
|
8 |
|
$this->storedEvents = array(); |
462
|
8 |
|
$this->publishedEvents = array(); |
463
|
8 |
|
$this->givenEvents = array(); |
464
|
8 |
|
$this->sequenceNumber = 0; |
465
|
8 |
|
} |
466
|
|
|
|
467
|
|
|
public function setReportIllegalStateChange($reportIllegalStateChange) |
468
|
|
|
{ |
469
|
|
|
$this->reportIllegalStateChange = $reportIllegalStateChange; |
470
|
|
|
} |
471
|
|
|
|
472
|
|
|
/** |
473
|
|
|
* Returns the {@see CommandBusInterface} used by this fixture. |
474
|
|
|
* |
475
|
|
|
* @return CommandBusInterface |
476
|
|
|
*/ |
477
|
|
|
public function getCommandBus() |
478
|
|
|
{ |
479
|
|
|
return $this->commandBus; |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
/** |
483
|
|
|
* Returns the {@see EventBusInterface} used by this fixture. |
484
|
|
|
* |
485
|
|
|
* @return EventBusInterface |
486
|
|
|
*/ |
487
|
4 |
|
public function getEventBus() |
488
|
|
|
{ |
489
|
4 |
|
return $this->eventBus; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Returns the {@see EventStoreInterface} used by this fixture. |
494
|
|
|
* |
495
|
|
|
* @return EventStoreInterface |
496
|
|
|
*/ |
497
|
3 |
|
public function getEventStore() |
498
|
|
|
{ |
499
|
3 |
|
return $this->eventStore; |
500
|
|
|
} |
501
|
|
|
|
502
|
5 |
|
public function getRepository() |
503
|
|
|
{ |
504
|
5 |
|
$this->ensureRepositoryConfiguration(); |
505
|
|
|
|
506
|
5 |
|
return $this->repository; |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
class IdentifierValidatingRepository implements RepositoryInterface |
|
|
|
|
512
|
|
|
{ |
513
|
|
|
|
514
|
|
|
private $delegate; |
515
|
|
|
|
516
|
6 |
|
public function __construct(RepositoryInterface $delegate) |
517
|
|
|
{ |
518
|
6 |
|
$this->delegate = $delegate; |
519
|
6 |
|
} |
520
|
|
|
|
521
|
3 |
|
public function load($aggregateIdentifier, $expectedVersion = null) |
522
|
|
|
{ |
523
|
3 |
|
$aggregate = $this->delegate->load( |
524
|
3 |
|
$aggregateIdentifier, |
525
|
|
|
$expectedVersion |
526
|
3 |
|
); |
527
|
|
|
|
528
|
3 |
|
$this->validateIdentifier($aggregateIdentifier, $aggregate); |
529
|
|
|
|
530
|
3 |
|
return $aggregate; |
531
|
|
|
} |
532
|
|
|
|
533
|
3 |
|
private function validateIdentifier( |
534
|
|
|
$aggregateIdentifier, |
535
|
|
|
AggregateRootInterface $aggregate |
536
|
|
|
) { |
537
|
3 |
|
if (null !== $aggregateIdentifier && !$aggregateIdentifier === $aggregate->getIdentifier()) { |
538
|
|
|
throw new \RuntimeException( |
539
|
|
|
sprintf( |
540
|
|
|
"The aggregate used in this fixture was initialized with an identifier different than ". |
541
|
|
|
"the one used to load it. Loaded [%s], but actual identifier is [%s].\n". |
542
|
|
|
"Make sure the identifier passed in the Command matches that of the given Events.", |
543
|
|
|
$aggregateIdentifier, |
544
|
|
|
$aggregate->getIdentifier() |
545
|
|
|
) |
546
|
|
|
); |
547
|
|
|
} |
548
|
3 |
|
} |
549
|
|
|
|
550
|
|
|
public function add(AggregateRootInterface $aggregate) |
551
|
|
|
{ |
552
|
|
|
$this->delegate->add($aggregate); |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
public function supportsClass($class) |
556
|
|
|
{ |
557
|
|
|
return true; |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
class ExecutionExceptionAwareCallback implements CommandCallbackInterface |
|
|
|
|
563
|
|
|
{ |
564
|
|
|
|
565
|
|
|
private $exception; |
566
|
|
|
|
567
|
|
|
public function onSuccess($result) |
568
|
|
|
{ |
569
|
|
|
|
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
public function onFailure(\Exception $cause) |
573
|
|
|
{ |
574
|
|
|
if ($cause instanceof FixtureExecutionException) { |
575
|
|
|
$this->exception = $cause; |
576
|
|
|
} |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
public function assertSuccessful() |
580
|
|
|
{ |
581
|
|
|
if (null !== $this->exception) { |
582
|
|
|
throw $this->exception; |
583
|
|
|
} |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
class RecordingEventStore implements EventStoreInterface |
|
|
|
|
589
|
|
|
{ |
590
|
|
|
|
591
|
|
|
private $storedEvents; |
592
|
|
|
private $givenEvents; |
593
|
|
|
private $aggregateIdentifier; |
594
|
|
|
|
595
|
8 |
|
public function __construct( |
596
|
|
|
array &$storedEvents, |
597
|
|
|
array &$givenEvents, |
598
|
|
|
&$aggregateIdentifier |
599
|
|
|
) { |
600
|
8 |
|
$this->storedEvents = &$storedEvents; |
601
|
8 |
|
$this->givenEvents = &$givenEvents; |
602
|
8 |
|
$this->aggregateIdentifier = &$aggregateIdentifier; |
603
|
8 |
|
} |
604
|
|
|
|
605
|
5 |
|
public function appendEvents($type, DomainEventStreamInterface $events) |
606
|
|
|
{ |
607
|
5 |
|
while ($events->hasNext()) { |
608
|
5 |
|
$next = $events->next(); |
609
|
5 |
|
IdentifierValidator::validateIdentifier($next->getAggregateIdentifier()); |
610
|
|
|
|
611
|
5 |
|
if (!empty($this->storedEvents)) { |
612
|
2 |
|
$lastEvent = end($this->storedEvents); |
613
|
|
|
|
614
|
2 |
|
if ($lastEvent->getAggregateIdentifier() !== $next->getAggregateIdentifier()) { |
615
|
1 |
|
throw new EventStoreException( |
616
|
|
|
"Writing events for an unexpected aggregate. This could ". |
617
|
|
|
"indicate that a wrong aggregate is being triggered." |
618
|
1 |
|
); |
619
|
|
|
} else { |
620
|
1 |
|
if ($lastEvent->getScn() !== $next->getScn() - 1) { |
621
|
1 |
|
throw new EventStoreException( |
622
|
1 |
|
sprintf( |
623
|
|
|
"Unexpected sequence number on stored event. ". |
624
|
1 |
|
"Expected %s, but got %s.", |
625
|
1 |
|
$lastEvent->getScn() + 1, |
626
|
1 |
|
$next->getScn() |
627
|
1 |
|
) |
628
|
1 |
|
); |
629
|
|
|
} |
630
|
|
|
} |
631
|
|
|
} |
632
|
|
|
|
633
|
5 |
|
if (null === $this->aggregateIdentifier) { |
634
|
2 |
|
$this->aggregateIdentifier = $next->getAggregateIdentifier(); |
635
|
2 |
|
$this->injectAggregateIdentifier(); |
636
|
2 |
|
} |
637
|
|
|
|
638
|
5 |
|
$this->storedEvents[] = $next; |
639
|
5 |
|
} |
640
|
3 |
|
} |
641
|
|
|
|
642
|
3 |
|
public function readEvents($type, $identifier) |
643
|
|
|
{ |
644
|
3 |
|
if (null !== $identifier) { |
645
|
3 |
|
IdentifierValidator::validateIdentifier($identifier); |
646
|
3 |
|
} |
647
|
|
|
|
648
|
3 |
|
if (null !== $this->aggregateIdentifier && $this->aggregateIdentifier !== $identifier) { |
649
|
|
|
throw new EventStoreException( |
650
|
|
|
"You probably want to use aggregateIdentifier() on your fixture ". |
651
|
|
|
"to get the aggregate identifier to use" |
652
|
|
|
); |
653
|
|
|
} else { |
654
|
3 |
|
if (null === $this->aggregateIdentifier) { |
655
|
3 |
|
$this->aggregateIdentifier = $identifier; |
656
|
3 |
|
$this->injectAggregateIdentifier(); |
657
|
3 |
|
} |
658
|
|
|
} |
659
|
|
|
|
660
|
3 |
|
$allEvents = $this->givenEvents; |
661
|
3 |
|
$allEvents = array_merge($allEvents, $this->storedEvents); |
662
|
|
|
|
663
|
3 |
|
if (empty($allEvents)) { |
664
|
|
|
throw new AggregateNotFoundException( |
665
|
|
|
$identifier, |
666
|
|
|
"No 'given' events were configured for this aggregate, ". |
667
|
|
|
"nor have any events been stored." |
668
|
|
|
); |
669
|
|
|
} |
670
|
|
|
|
671
|
3 |
|
return new SimpleDomainEventStream($allEvents); |
672
|
|
|
} |
673
|
|
|
|
674
|
5 |
|
private function injectAggregateIdentifier() |
675
|
|
|
{ |
676
|
5 |
|
$oldEvents = $this->givenEvents; |
677
|
5 |
|
$this->givenEvents = array(); |
678
|
|
|
|
679
|
5 |
|
foreach ($oldEvents as $oldEvent) { |
680
|
3 |
|
if (null === $oldEvent->getAggregateIdentifier()) { |
681
|
3 |
|
$this->givenEvents[] = new GenericDomainEventMessage( |
682
|
3 |
|
$this->aggregateIdentifier, |
683
|
3 |
|
$oldEvent->getScn(), |
684
|
3 |
|
$oldEvent->getPayload(), |
685
|
3 |
|
$oldEvent->getMetaData(), |
686
|
3 |
|
$oldEvent->getIdentifier(), |
687
|
3 |
|
$oldEvent->getTimestamp() |
688
|
3 |
|
); |
689
|
3 |
|
} else { |
690
|
|
|
$this->givenEvents[] = $oldEvent; |
691
|
|
|
} |
692
|
5 |
|
} |
693
|
5 |
|
} |
694
|
|
|
|
695
|
|
|
public function setLogger(LoggerInterface $logger) |
696
|
|
|
{ |
697
|
|
|
|
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
class AggregateRegisteringInterceptor implements CommandHandlerInterceptorInterface |
|
|
|
|
703
|
|
|
{ |
704
|
|
|
private $workingAggregate; |
705
|
|
|
|
706
|
3 |
|
function __construct(&$workingAggregate) |
|
|
|
|
707
|
|
|
{ |
708
|
3 |
|
$this->workingAggregate = &$workingAggregate; |
709
|
3 |
|
} |
710
|
|
|
|
711
|
3 |
|
public function handle( |
712
|
|
|
CommandMessageInterface $commandMessage, |
713
|
|
|
UnitOfWorkInterface $unitOfWork, |
714
|
|
|
InterceptorChainInterface $interceptorChain |
715
|
|
|
) { |
716
|
|
|
|
717
|
3 |
|
$unitOfWork->registerListener(new AggregateListenerAdapter($this->workingAggregate)); |
718
|
|
|
|
719
|
3 |
|
return $interceptorChain->proceed(); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
} |
723
|
|
|
|
724
|
|
|
class AggregateListenerAdapter extends UnitOfWorkListenerAdapter |
|
|
|
|
725
|
|
|
{ |
726
|
|
|
private $workingAggregate; |
727
|
|
|
|
728
|
3 |
|
function __construct(&$workingAggregate) |
|
|
|
|
729
|
|
|
{ |
730
|
3 |
|
$this->workingAggregate = &$workingAggregate; |
731
|
3 |
|
} |
732
|
|
|
|
733
|
|
|
|
734
|
3 |
|
public function onPrepareCommit( |
735
|
|
|
UnitOfWorkInterface $unitOfWork, |
736
|
|
|
array $aggregateRoots, |
737
|
|
|
array $events |
738
|
|
|
) { |
739
|
3 |
|
foreach ($aggregateRoots as $aggregateRoot) { |
740
|
3 |
|
$this->workingAggregate = $aggregateRoot; |
741
|
3 |
|
} |
742
|
3 |
|
} |
743
|
|
|
|
744
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.