Completed
Pull Request — master (#41)
by David
11:45 queued 08:52
created

DBALEventStore::eventLog()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
ccs 0
cts 12
cp 0
rs 9.4286
cc 2
eloc 10
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Rawkode\Eidetic\EventStore\DBALEventStore;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\DriverManager;
7
use Rawkode\Eidetic\EventStore\EventStore;
8
use Rawkode\Eidetic\EventStore\NoEventsFoundForKeyException;
9
use Rawkode\Eidetic\EventSourcing\EventSourcedEntity;
10
11
final class DBALEventStore extends EventStore
12
{
13
    /**
14
     * @var string
15
     */
16
    private $tableName;
17
18
    /**
19
     * @var Connection
20
     */
21
    private $connection;
22
23
    /**
24
     * @param string     $tableName
25
     * @param Connection $connection
26
     */
27
    private function __construct($tableName, Connection $connection)
28
    {
29
        $this->tableName = $tableName;
30
        $this->connection = $connection;
31
    }
32
33
    /**
34
     * @param string $tableName
35
     * @param array  $options
36
     *
37
     * @return self
38
     */
39
    public static function createWithOptions($tableName, array $options)
40
    {
41
        $connection = DriverManager::getConnection($options);
42
43
        return new self($tableName, $connection);
44
    }
45
46
    /**
47
     * @param EventSourcedEntity $eventSourcedEntity
48
     */
49
    protected function persist(EventSourcedEntity $eventSourcedEntity)
50
    {
51
        $eventCount = $this->countEntityEvents($eventSourcedEntity->identifier());
52
53
        array_map(function ($event) use ($eventSourcedEntity, &$eventCount) {
54
            $this->connection->insert($this->tableName, [
55
                'entity_identifier' => $eventSourcedEntity->identifier(),
56
                'serial_number' => ++$eventCount,
57
                'entity_class' => get_class($eventSourcedEntity),
58
                'recorded_at' => new \DateTime('now', new \DateTimeZone('UTC')),
59
                'event_class' => get_class($event),
60
                'event' => $this->serialize($event),
61
            ], [
62
                \PDO::PARAM_STR,
63
                \PDO::PARAM_INT,
64
                \PDO::PARAM_STR,
65
                'datetime',
66
                \PDO::PARAM_STR,
67
                \PDO::PARAM_STR,
68
            ]);
69
70
            array_push($this->stagedEvents, $event);
0 ignored issues
show
Bug introduced by
The property stagedEvents cannot be accessed from this context as it is declared private in class Rawkode\Eidetic\EventStore\EventStore.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
71
        }, $eventSourcedEntity->stagedEvents());
72
    }
73
74
    /**
75
     * @param string $entityIdentifier
76
     *
77
     * @throws NoEventsFoundForKeyException
78
     *
79
     * @return array
80
     */
81
    protected function eventLog($entityIdentifier)
82
    {
83
        if (0 === $this->countEntityEvents($entityIdentifier)) {
84
            throw new NoEventsFoundForKeyException();
85
        }
86
87
        $statement = $this->eventLogQuery($entityIdentifier)->execute();
88
89
        $eventLog = $statement->fetchAll();
90
91
        return array_map(function ($eventLogEntry) {
92
            $eventLogEntry['event'] = $this->unserialize($eventLogEntry['event']);
93
            $eventLogEntry['recorded_at'] = new \DateTime($eventLogEntry['recorded_at']);
94
95
            return $eventLogEntry;
96
        }, $eventLog);
97
    }
98
99
    /**
100
     */
101
    protected function startTransaction()
102
    {
103
        $this->connection->beginTransaction();
104
        $this->stagedEvents = [];
0 ignored issues
show
Bug introduced by
The property stagedEvents cannot be accessed from this context as it is declared private in class Rawkode\Eidetic\EventStore\EventStore.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
105
    }
106
107
    /**
108
     */
109
    protected function abortTransaction()
110
    {
111
        $this->connection->rollBack();
112
        $this->stagedEvents = [];
0 ignored issues
show
Bug introduced by
The property stagedEvents cannot be accessed from this context as it is declared private in class Rawkode\Eidetic\EventStore\EventStore.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
113
    }
114
115
    /**
116
     */
117
    protected function completeTransaction()
118
    {
119
        $this->connection->commit();
120
121
        array_map(function ($event) {
122
            $this->publish(self::EVENT_STORED, $event);
123
        }, $this->stagedEvents);
0 ignored issues
show
Bug introduced by
The property stagedEvents cannot be accessed from this context as it is declared private in class Rawkode\Eidetic\EventStore\EventStore.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
124
125
        $this->stagedEvents = [];
0 ignored issues
show
Bug introduced by
The property stagedEvents cannot be accessed from this context as it is declared private in class Rawkode\Eidetic\EventStore\EventStore.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
126
    }
127
128
    /**
129
     */
130
    public function createTable()
131
    {
132
        $schemaManager = $this->connection->getSchemaManager();
133
        $schema = $schemaManager->createSchema();
134
135
        if ($schema->hasTable($this->tableName)) {
136
            return;
137
        }
138
139
        $table = $schema->createTable($this->tableName);
140
141
        $table->addColumn('entity_identifier', 'string', ['length' => 255]);
142
        $table->addColumn('serial_number', 'integer');
143
144
        $table->setPrimaryKey(['entity_identifier', 'serial_number']);
145
146
        $table->addColumn('entity_class', 'string', ['length' => 255]);
147
        $table->addColumn('recorded_at', 'datetime');
148
        $table->addColumn('event_class', 'string', ['length' => 255]);
149
        $table->addColumn('event', 'text');
150
151
        $table->addIndex(['entity_class']);
152
        $table->addIndex(['recorded_at']);
153
        $table->addIndex(['event_class']);
154
155
        $schemaManager->createTable($table);
156
    }
157
158
    /**
159
     */
160
    public function dropTable()
161
    {
162
        $this->connection->getSchemaManager()->dropTable($this->tableName);
163
    }
164
165
    /**
166
     * @param string $entityIdentifier
167
     *
168
     * @return int
169
     */
170
    private function countEntityEvents($entityIdentifier)
171
    {
172
        $queryBuilder = $this->connection->createQueryBuilder();
173
174
        $queryBuilder->select('COUNT(entity_identifier)');
175
        $queryBuilder->from($this->tableName);
176
        $queryBuilder->where('entity_identifier = :entity_identifier');
177
178
        $queryBuilder->setParameter('entity_identifier', $entityIdentifier);
179
180
        return (int) $queryBuilder->execute()->fetchColumn(0);
181
    }
182
183
    /**
184
     * @param string $entityIdentifier
185
     *
186
     * @return \Doctrine\DBAL\Query\QueryBuilder
187
     */
188
    protected function eventLogQuery($entityIdentifier)
189
    {
190
        $queryBuilder = $this->connection->createQueryBuilder();
191
192
        $queryBuilder->select('*');
193
        $queryBuilder->from($this->tableName);
194
        $queryBuilder->where('entity_identifier = :entity_identifier');
195
        $queryBuilder->orderBy('serial_number', 'ASC');
196
197
        $queryBuilder->setParameter('entity_identifier', $entityIdentifier);
198
199
        return $queryBuilder;
200
    }
201
202
    /**
203
     * @param string $entityIdentifier
204
     *
205
     * @return array
206
     */
207
    protected function singleLogForKey($entityIdentifier)
208
    {
209
        $queryBuilder = $this->eventLogQuery($entityIdentifier);
210
211
        $queryBuilder->setMaxResults(1);
212
213
        $statement = $queryBuilder->execute();
214
215
        $results = $statement->fetchAll();
216
217
        return $results;
218
    }
219
}
220