GivenWhenThenTestFixture::getCommandBus()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
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();
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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();
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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();
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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);
0 ignored issues
show
Compatibility introduced by
$aggregate2 of type object<Governor\Framewor...AggregateRootInterface> is not a sub-type of object<Governor\Framewor...AggregateRootInterface>. It seems like you assume a child interface of the interface Governor\Framework\Domain\AggregateRootInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Unused Code introduced by
The call to the method Governor\Framework\Test\...WorkingAggregateState() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
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
0 ignored issues
show
Unused Code introduced by
The call to Logger::warn() has too many arguments starting with $ex.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
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)
0 ignored issues
show
Unused Code introduced by
The parameter $eventSourcedAggregate is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
416
    {
417
        /*HashSet<ComparationEntry> comparedEntries = new HashSet<ComparationEntry>();
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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
    /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
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
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
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
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
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
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
703
{
704
    private $workingAggregate;
705
706 3
    function __construct(&$workingAggregate)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
725
{
726
    private $workingAggregate;
727
728 3
    function __construct(&$workingAggregate)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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
}