Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
100 / 100
100.00% covered (success)
100.00%
16 / 16
CRAP
100.00% covered (success)
100.00%
1 / 1
MySQL
100.00% covered (success)
100.00%
100 / 100
100.00% covered (success)
100.00%
16 / 16
33
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 dispatch
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
12
 changeConnection
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 addConnection
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 select
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 update
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 insert
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 executeStatement
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 prepareAndExecute
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 execute
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 query
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 startTransaction
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 endTransaction
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 revertTransaction
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Projom\Storage\Engine\Driver;
6
7use PDOStatement;
8
9use Projom\Storage\Query\Action;
10use Projom\Storage\Engine\Driver\DriverBase;
11use Projom\Storage\Engine\Driver\Connection\ConnectionInterface;
12use Projom\Storage\Engine\Driver\Connection\PDOConnection;
13use Projom\Storage\SQL\QueryObject;
14use Projom\Storage\SQL\QueryBuilder;
15use Projom\Storage\SQL\Statement\StatementInterface;
16use Projom\Storage\SQL\Statement\Delete;
17use Projom\Storage\SQL\Statement\Insert;
18use Projom\Storage\SQL\Statement\Select;
19use Projom\Storage\SQL\Statement\Update;
20
21class MySQL extends DriverBase
22{
23    private array $connections = [];
24    private null|PDOConnection $connection = null;
25    private null|PDOStatement $statement = null;
26
27    public function __construct(null|PDOConnection $connection)
28    {
29        parent::__construct();
30
31        if ($connection === null)
32            return;
33
34        $this->connection = $connection;
35        $this->connections[$connection->name()] = $connection;
36    }
37
38    public static function create(null|PDOConnection $connection = null): MySQL
39    {
40        return new MySQL($connection);
41    }
42
43    public function dispatch(Action $action, mixed $args): mixed
44    {
45        $this->logger->debug(
46            'Method: {method} with {action} and {args}.',
47            ['action' => $action->name, 'args' => $args, 'method' => __METHOD__]
48        );
49
50        return match ($action) {
51            Action::CHANGE_CONNECTION => $this->changeConnection($args),
52            Action::SELECT => $this->select($args),
53            Action::UPDATE => $this->update($args),
54            Action::INSERT => $this->insert($args),
55            Action::DELETE => $this->delete($args),
56            Action::EXECUTE => $this->execute(...$args),
57            Action::QUERY => $this->query(...$args),
58            Action::START_TRANSACTION => $this->startTransaction(),
59            Action::END_TRANSACTION => $this->endTransaction(),
60            Action::REVERT_TRANSACTION => $this->revertTransaction(),
61            default => throw new \Exception("Action: $action is not supported", 400)
62        };
63    }
64
65    public function changeConnection(int|string $name): void
66    {
67        $this->logger->debug(
68            'Method: {method} with "{name}".',
69            ['name' => $name, 'method' => __METHOD__]
70        );
71
72        if (!array_key_exists($name, $this->connections))
73            throw new \Exception("Connection: '$name' does not exist.", 400);
74        $this->connection = $this->connections[$name];
75    }
76
77    public function addConnection(ConnectionInterface $connection): void
78    {
79        $this->logger->debug(
80            'Method: {method} with {connection} named "{name}".',
81            ['connection' => $connection::class, 'name' => $connection->name(), 'method' => __METHOD__]
82        );
83
84        if (!$connection instanceof PDOConnection)
85            throw new \Exception("Provided connection is not a PDO connection", 400);
86        $this->connections[$connection->name()] = $connection;
87    }
88
89    private function select(QueryObject $queryObject): null|array|object
90    {
91        $this->logger->debug(
92            'Method: {method} with {queryObject}.',
93            ['queryObject' => $queryObject, 'method' => __METHOD__]
94        );
95
96        $select = Select::create($queryObject);
97        $this->executeStatement($select);
98
99        $records = $this->statement->fetchAll();
100        if (!$records)
101            return null;
102
103        $records = $this->processRecords($records, $queryObject->formatting);
104
105        return $records;
106    }
107
108    private function update(QueryObject $queryObject): int
109    {
110        $this->logger->debug(
111            'Method: {method} with {queryObject}.',
112            ['queryObject' => $queryObject, 'method' => __METHOD__]
113        );
114
115        $update = Update::create($queryObject);
116        $this->executeStatement($update);
117        return $this->statement->rowCount();
118    }
119
120    private function insert(QueryObject $queryObject): int
121    {
122        $this->logger->debug(
123            'Method: {method} with {queryObject}.',
124            ['queryObject' => $queryObject, 'method' => __METHOD__]
125        );
126
127        $insert = Insert::create($queryObject);
128        $this->executeStatement($insert);
129        return (int) $this->connection->lastInsertId();
130    }
131
132    private function delete(QueryObject $queryObject): int
133    {
134        $this->logger->debug(
135            'Method: {method} with {queryObject}.',
136            ['queryObject' => $queryObject, 'method' => __METHOD__]
137        );
138
139        $delete = Delete::create($queryObject);
140        $this->executeStatement($delete);
141        return (int) $this->statement->rowCount();
142    }
143
144    private function executeStatement(StatementInterface $statement): void
145    {
146        $this->logger->debug(
147            'Method: {method} with {statement}.',
148            ['statement' => $statement, 'method' => __METHOD__]
149        );
150
151        [$sql, $params] = $statement->statement();
152        $this->prepareAndExecute($sql, $params);
153    }
154
155    private function prepareAndExecute(string $sql, null|array $params): void
156    {
157        $this->logger->debug(
158            'Method: {method} with "{sql}" and {params}.',
159            ['sql' => $sql, 'params' => $params, 'method' => __METHOD__]
160        );
161
162        if (!$statement = $this->connection->prepare($sql))
163            throw new \Exception('Failed to prepare statement.', 500);
164
165        $this->statement = $statement;
166        if (!$this->statement->execute($params))
167            throw new \Exception('Failed to execute statement.', 500);
168    }
169
170    private function execute(string $sql, null|array $params): array
171    {
172        $this->logger->debug(
173            'Method {method} with "{sql}" and {params}.',
174            ['sql' => $sql, 'params' => $params, 'method' => __METHOD__]
175        );
176
177        $this->prepareAndExecute($sql, $params);
178        return $this->statement->fetchAll();
179    }
180
181    private function query(array $collections, null|array $options = null): QueryBuilder
182    {
183        $this->logger->debug(
184            'Method: {method} with {collections} and {options}.',
185            ['collections' => $collections, 'options' => $options, 'method' => __METHOD__]
186        );
187
188        $this->setQueryOptions($options);
189
190        return QueryBuilder::create($this, $collections, $this->logger);
191    }
192
193    private function startTransaction(): void
194    {
195        $this->logger->debug('Starting transaction.');
196        $this->connection->beginTransaction();
197    }
198
199    private function endTransaction(): void
200    {
201        $this->logger->debug('Ending transaction.');
202        $this->connection->commit();
203    }
204
205    private function revertTransaction(): void
206    {
207        $this->logger->debug('Reverting transaction.');
208        $this->connection->rollBack();
209    }
210}