Passed
Push — master ( 33b14d...e7440c )
by Thorsten
01:45
created

Stream::appendCommit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * This file is part of the daikon-cqrs/cqrs project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace Daikon\EventSourcing\EventStore;
12
13
use Daikon\EventSourcing\Aggregate\AggregateRevision;
14
use Daikon\EventSourcing\Aggregate\DomainEventSequence;
15
use Daikon\MessageBus\Metadata\Metadata;
16
17
final class Stream implements StreamInterface
18
{
19
    /** @var StreamId */
20
    private $streamId;
21
22
    /** @var CommitSequence */
23
    private $commitSequence;
24
25
    /** @var string */
26
    private $commitImplementor;
27
28 2
    public static function fromStreamId(StreamId $streamId, string $commitImplementor = Commit::class): StreamInterface
29
    {
30 2
        return new static($streamId);
31
    }
32
33
    public static function fromArray(array $streamState): Stream
34
    {
35
        return new static(
36
            StreamId::fromNative($streamState["commitStreamId"]),
0 ignored issues
show
Compatibility introduced by
\Daikon\EventSourcing\Ev...tate['commitStreamId']) of type object<Daikon\Entity\Val...t\ValueObjectInterface> is not a sub-type of object<Daikon\EventSourcing\EventStore\StreamId>. It seems like you assume a concrete implementation of the interface Daikon\Entity\ValueObject\ValueObjectInterface 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...
37
            CommitSequence::fromArray($streamState["commitStreamSequence"]),
38
            $streamState["commitImplementor"]
39
        );
40
    }
41
42 2
    public function __construct(
43
        StreamId $streamId,
44
        CommitSequence $commitSequence = null,
45
        string $commitImplementor = Commit::class
46
    ) {
47 2
        $this->streamId = $streamId;
48 2
        $this->commitSequence = $commitSequence ?? new CommitSequence;
49 2
        $this->commitImplementor = $commitImplementor;
50 2
    }
51
52
    public function getStreamId(): StreamId
53
    {
54
        return $this->streamId;
55
    }
56
57 2
    public function getStreamRevision(): StreamRevision
58
    {
59 2
        return StreamRevision::fromNative($this->commitSequence->getLength());
60
    }
61
62 1
    public function getAggregateRevision(): AggregateRevision
63
    {
64 1
        return $this->commitSequence->getHead()->getAggregateRevision();
65
    }
66
67 2
    public function appendEvents(DomainEventSequence $eventLog, Metadata $metadata): StreamInterface
68
    {
69 2
        $previousCommits = $this->findCommitsSince($eventLog->getHeadRevision());
70 2
        if (!$previousCommits->isEmpty()) {
71
            $conflictingEvents = $this->detectConflictingEvents($eventLog, $previousCommits);
0 ignored issues
show
Unused Code introduced by
$conflictingEvents is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
72
            // @todo We need to indicate to the caller, that appending did not work and provide the conflicting events.
73
            // Here are some possible solutions:
74
            // 1. Throw an special exception, that contains the conflicting events
75
            //    Con: This is not nice, because it would be misusing exceptions for control-flow
76
            // 2. Add a two new methods isConflicted, getConflicting eventsto the StreamInterface.
77
            //    Have this method return a new stream that is marked as conflicted and yields the conflicting events.
78
            // 3. Introduce a StreamResultInterface with two implementations for Success/Error.
79
            //    Success would hold the new stream with appended events and Error would yield the conflict infos.
80
            //    Con: More result interfaces/classes.
81
            // 4. Same as 3. but use the Ok/Error monads from shrink0r/monatic.
82
            //    These would then also replace the StoreResultInterface to preserve consistency.
83
            //    Con: As this approach is more generic we lose explicity, no?
84
        }
85 2
        return $this->appendCommit(
86 2
            call_user_func(
87 2
                [ $this->commitImplementor, 'make' ],
88 2
                $this->streamId,
89 2
                $this->getStreamRevision()->increment(),
90 2
                $eventLog,
91 2
                $metadata
92
            )
93
        );
94
    }
95
96 2
    public function appendCommit(CommitInterface $commit): StreamInterface
97
    {
98 2
        $stream = clone $this;
99 2
        $stream->commitSequence = $this->commitSequence->push($commit);
100 2
        return $stream;
101
    }
102
103 2
    public function getHead(): ?CommitInterface
104
    {
105 2
        return $this->commitSequence->isEmpty() ? null : $this->commitSequence->getHead();
106
    }
107
108 2
    public function getCommitRange(StreamRevision $fromRev, StreamRevision $toRev = null): CommitSequence
109
    {
110 2
        return $this->commitSequence->getSlice($fromRev, $toRev ?? $this->getStreamRevision());
111
    }
112
113
    public function count(): int
114
    {
115
        return $this->commitSequence->count();
116
    }
117
118
    public function toNative(): array
119
    {
120
        return [
121
            "commitSequence" => $this->commitSequence->toNative(),
122
            "streamId" => $this->streamId->toNative(),
123
            "commitImplementor" => $this->commitImplementor
124
        ];
125
    }
126
127
    public function getIterator(): \Iterator
128
    {
129
        return $this->commitSequence->getIterator();
130
    }
131
132 2
    private function findCommitsSince(AggregateRevision $incomingRevision): CommitSequence
133
    {
134 2
        $previousCommits = [];
135 2
        $prevCommit = $this->getHead();
136 2
        while ($prevCommit && $incomingRevision->isLessThan($prevCommit->getAggregateRevision())) {
137
            $previousCommits[] = $prevCommit;
138
            $prevCommit = $this->commitSequence->get($prevCommit->getStreamRevision()->decrement());
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $prevCommit is correct as $this->commitSequence->g...evision()->decrement()) (which targets Daikon\EventSourcing\Eve...e\CommitSequence::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
139
        }
140 2
        return new CommitSequence(array_reverse($previousCommits));
141
    }
142
143
    private function detectConflictingEvents(DomainEventSequence $newEvents, CommitSequence $previousCommits): array
144
    {
145
        $conflictingEvents = [];
146
        foreach ($newEvents as $newEvent) {
147
            foreach ($previousCommits as $previousCommit) {
148
                foreach ($previousCommit->getEventLog() as $previousEvent) {
149
                    if ($newEvent->conflictsWith($previousEvent)) {
150
                        $conflictingEvents[] = [ $previousEvent, $newEvent ];
151
                    }
152
                }
153
            }
154
        }
155
        return $conflictingEvents;
156
    }
157
}
158