__construct()   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
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Antidot\EventSource\Infrastructure\Repository;
6
7
use Antidot\EventSource\Domain\Event\AggregateChanged;
8
use Antidot\EventSource\Domain\Model\Aggregate\AggregateRoot;
9
use Antidot\EventSource\Domain\Model\ValueObject\AggregateRootId;
10
use DateTimeImmutable;
11
use Doctrine\DBAL\Connection;
12
use function get_class;
13
use function json_encode;
14
15
abstract class DbalAggregateRootSingleTableRepository
16
{
17
    protected Connection $connection;
18
    protected string $streamName;
19
20
    public function __construct(Connection $connection, string $streamName)
21
    {
22
        $this->connection = $connection;
23
        $this->streamName = $streamName;
24
    }
25
26
    protected function getAggregate(AggregateRootId $aggregateRootId, string $aggregateClass): ?AggregateRoot
27
    {
28
        $tableName = $this->streamName;
29
        if (false === $this->tableExist($tableName)) {
30
            return null;
31
        }
32
33
        $statement = $this->connection->prepare(<<<SQL
34
            SELECT * FROM 
35
                `$tableName` 
36
            WHERE 
37
                aggregate_id = :aggregate_id 
38
            ORDER BY  
39
                no ASC;
40
            SQL
41
        );
42
        $statement->bindValue('aggregate_id', $aggregateRootId->value());
43
        $statement->execute();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::execute() has been deprecated: Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

43
        /** @scrutinizer ignore-deprecated */ $statement->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
44
        /** @var null|array $queryResult */
45
        $queryResult = $statement->fetchAll();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetchAll() has been deprecated: Use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

45
        $queryResult = /** @scrutinizer ignore-deprecated */ $statement->fetchAll();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
46
        /** @var AggregateRoot $aggregate */
47
        $aggregate = new $aggregateClass();
48
49
        /** @var array<string, mixed> $item */
50
        foreach ($queryResult ?? [] as $item) {
51
            /** @var string $eventClass */
52
            $eventClass = $item['event_class'];
53
            /** @var string $payload */
54
            $payload = $item['payload'];
55
            /** @var string $occurredOn */
56
            $occurredOn = $item['occurred_on'];
57
            /** @var AggregateChanged $event */
58
            $event = new $eventClass(
59
                $item['event_id'],
60
                $item['aggregate_id'],
61
                json_decode($payload, true, 16, JSON_THROW_ON_ERROR),
62
                DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $occurredOn)
63
            );
64
            $aggregate->apply($event);
65
        }
66
67
        return $aggregate;
68
    }
69
70
    protected function saveAggregate(AggregateRoot $aggregateRoot): void
71
    {
72
        $tableName = $this->streamName;
73
        $this->createAggregateStream($tableName);
74
        $this->connection->beginTransaction();
75
        try {
76
            /** @var AggregateChanged $event */
77
            foreach ($aggregateRoot->popEvents() as $event) {
78
                $this->connection->insert($tableName, [
79
                    'event_id' => $event->eventId(),
80
                    'event_class' => get_class($event),
0 ignored issues
show
Bug introduced by
$event of type array is incompatible with the type object expected by parameter $object of get_class(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

80
                    'event_class' => get_class(/** @scrutinizer ignore-type */ $event),
Loading history...
81
                    'aggregate_id' => $event->aggregateId(),
82
                    'aggregate_class' => get_class($aggregateRoot),
83
                    'payload' => json_encode($event->payload(), JSON_THROW_ON_ERROR),
84
                    'occurred_on' => $event->occurredOn()->format('Y-m-d H:i:s'),
85
                    'version' => $event->version(),
86
                ]);
87
            }
88
            $this->connection->commit();
89
        } catch (\Throwable $exception) {
90
            $this->connection->rollBack();
91
            throw $exception;
92
        }
93
    }
94
95
    protected function createAggregateStream(string $tableName): void
96
    {
97
        if ($this->tableExist($tableName)) {
98
            return;
99
        }
100
101
        $statement = $this->connection->prepare(<<<SQL
102
            CREATE TABLE `$tableName` (
103
              `no` BIGINT(20) NOT NULL AUTO_INCREMENT,
104
              `event_id` VARCHAR(36) NOT NULL,
105
              `event_class` CHAR(180) NOT NULL,
106
              `aggregate_id` VARCHAR(36) NOT NULL,
107
              `aggregate_class` CHAR(180) NOT NULL,
108
              `payload` JSON,
109
              `occurred_on` DATETIME NOT NULL,
110
              `version` INT(3) NOT NULL,
111
              PRIMARY KEY (`no`),
112
              UNIQUE KEY `ix_rsn` (`event_id`),
113
              KEY `ix_aggregate_id` (`aggregate_id`)
114
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
115
        SQL
116
        );
117
        $statement->execute();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::execute() has been deprecated: Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

117
        /** @scrutinizer ignore-deprecated */ $statement->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
118
    }
119
120
    private function tableExist(string $tableName): bool
121
    {
122
        $statement = $this->connection->prepare(<<<SQL
123
            SELECT * FROM 
124
                information_schema.tables t
125
            WHERE 
126
                t.table_schema = :db_name 
127
                AND t.table_name = :aggregate_stream
128
            LIMIT 1;
129
        SQL
130
        );
131
132
        $statement->bindValue(':db_name', $this->connection->getDatabase());
133
        $statement->bindValue(':aggregate_stream', $tableName);
134
        $statement->execute();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::execute() has been deprecated: Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

134
        /** @scrutinizer ignore-deprecated */ $statement->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
135
136
        /** @var array|false $result */
137
        $result = $statement->fetch();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Statement::fetch() has been deprecated: Use fetchNumeric(), fetchAssociative() or fetchOne() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

137
        $result = /** @scrutinizer ignore-deprecated */ $statement->fetch();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
138
139
        return false === empty($result);
140
    }
141
}
142