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

DBALEventStore::persist()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
ccs 11
cts 11
cp 1
rs 8.9714
cc 1
eloc 18
nc 1
nop 1
crap 1
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 6
     * @return self
38
     */
39 6
    public static function createWithOptions($tableName, array $options)
40 6
    {
41 6
        $connection = DriverManager::getConnection($options);
42
43
        return new self($tableName, $connection);
44
    }
45
46
    /**
47
     * @param EventSourcedEntity $eventSourcedEntity
48
     */
49 6
    protected function persist(EventSourcedEntity $eventSourcedEntity)
50
    {
51 6
        $eventCount = $this->countEntityEvents($eventSourcedEntity->identifier());
52
53 6
        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 5
                'event' => $this->serialize($event),
61
            ], [
62
                \PDO::PARAM_STR,
63 5
                \PDO::PARAM_INT,
64
                \PDO::PARAM_STR,
65 5
                'datetime',
66 5
                \PDO::PARAM_STR,
67 5
                \PDO::PARAM_STR,
68 5
            ]);
69 2
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 2
        }, $eventSourcedEntity->stagedEvents());
72
    }
73
74 4
    /**
75 4
     * @param string $entityIdentifier
76
     *
77
     * @throws NoEventsFoundForKeyException
78
     *
79
     * @return array
80
     */
81
    protected function eventLog($entityIdentifier)
82 2
    {
83
        if (0 === $this->countEntityEvents($entityIdentifier)) {
84 2
            throw new NoEventsFoundForKeyException();
85
        }
86
87 1
        $statement = $this->eventLogQuery($entityIdentifier)->execute();
88 1
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 1
        }, $eventLog);
97
    }
98 1
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 3
107
    /**
108 3
     */
109
    protected function abortTransaction()
110 2
    {
111
        $this->connection->rollBack();
112 2
        $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 2
115 2
    /**
116 2
     */
117 2
    protected function completeTransaction()
118
    {
119 2
        $this->connection->commit();
120 2
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 5
        $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 5
128 5
    /**
129 5
     */
130
    public function createTable()
131
    {
132
        $schemaManager = $this->connection->getSchemaManager();
133 2
        $schema = $schemaManager->createSchema();
134
135 2
        if ($schema->hasTable($this->tableName)) {
136 2
            return;
137 2
        }
138
139
        $table = $schema->createTable($this->tableName);
140
141 4
        $table->addColumn('entity_identifier', 'string', ['length' => 255]);
142
        $table->addColumn('serial_number', 'integer');
143 4
144
        $table->setPrimaryKey(['entity_identifier', 'serial_number']);
145 4
146 4
        $table->addColumn('entity_class', 'string', ['length' => 255]);
147 4
        $table->addColumn('recorded_at', 'datetime');
148
        $table->addColumn('event_class', 'string', ['length' => 255]);
149 4
        $table->addColumn('event', 'text');
150 4
151
        $table->addIndex(['entity_class']);
152
        $table->addIndex(['recorded_at']);
153
        $table->addIndex(['event_class']);
154
155
        $schemaManager->createTable($table);
156 6
    }
157
158 5
    /**
159
     */
160 5
    public function dropTable()
161 6
    {
162 5
        $this->connection->getSchemaManager()->dropTable($this->tableName);
163 5
    }
164 5
165 5
    /**
166 5
     * @param string $entityIdentifier
167 5
     *
168 5
     * @return int
169 5
     */
170 5
    private function countEntityEvents($entityIdentifier)
171
    {
172 6
        $queryBuilder = $this->connection->createQueryBuilder();
173 5
174
        $queryBuilder->select('COUNT(entity_identifier)');
175
        $queryBuilder->from($this->tableName);
176
        $queryBuilder->where('entity_identifier = :entity_identifier');
177 6
178
        $queryBuilder->setParameter('entity_identifier', $entityIdentifier);
179 6
180 6
        return (int) $queryBuilder->execute()->fetchColumn(0);
181
    }
182 6
183
    /**
184
     * @param string $entityIdentifier
185
     *
186 6
     * @return \Doctrine\DBAL\Query\QueryBuilder
187
     */
188 6
    protected function eventLogQuery($entityIdentifier)
189 6
    {
190 6
        $queryBuilder = $this->connection->createQueryBuilder();
191
192 6
        $queryBuilder->select('*');
193 6
        $queryBuilder->from($this->tableName);
194 6
        $queryBuilder->where('entity_identifier = :entity_identifier');
195 6
        $queryBuilder->orderBy('serial_number', 'ASC');
196
197 6
        $queryBuilder->setParameter('entity_identifier', $entityIdentifier);
198 6
199 6
        return $queryBuilder;
200
    }
201 6
202 6
    /**
203
     * @param string $entityIdentifier
204
     *
205
     * @return array
206 6
     */
207
    protected function singleLogForKey($entityIdentifier)
208 6
    {
209 6
        $queryBuilder = $this->eventLogQuery($entityIdentifier);
210
211
        $queryBuilder->setMaxResults(1);
212
213
        $statement = $queryBuilder->execute();
214
215 3
        $results = $statement->fetchAll();
216
217 3
        return $results;
218
    }
219
}
220