Passed
Push — master ( f2f70e...3db0c2 )
by Aleksandar
12:54
created

PdoDriver   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Test Coverage

Coverage 90.83%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 104
dl 0
loc 232
ccs 99
cts 109
cp 0.9083
rs 9.92
c 2
b 0
f 0
wmc 31

20 Methods

Rating   Name   Duplication   Size   Complexity  
A beginTransaction() 0 3 1
A create() 0 6 1
A run() 0 10 1
A runInTransaction() 0 11 2
A open() 0 9 2
A getPdo() 0 4 1
A executeStatement() 0 7 1
A getLastInsertedId() 0 3 1
A createResultReader() 0 6 1
A appendMiddleware() 0 4 1
A configure() 0 15 2
A fetchFirst() 0 10 1
A fetchAll() 0 9 1
A fetch() 0 8 1
A runMiddleware() 0 7 2
A close() 0 8 2
A fetchReader() 0 7 1
A useMiddleware() 0 4 1
A prepareStatement() 0 29 6
A executeMiddlewareChain() 0 10 2
1
<?php
2
/**
3
 * Copyright 2021 Aleksandar Panic
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *   http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
namespace ArekX\PQL\Drivers\Pdo;
19
20
use ArekX\PQL\Contracts\Driver;
21
use ArekX\PQL\Contracts\RawQuery;
22
use ArekX\PQL\Contracts\ResultBuilder;
23
use ArekX\PQL\Contracts\ResultReader;
24
use ArekX\PQL\QueryResultBuilder;
25
26
abstract class PdoDriver implements Driver
27
{
28
    const STEP_OPEN = 'open';
29
    const STEP_CLOSE = 'close';
30
    const STEP_BEFORE_RUN = 'before-run';
31
    const STEP_BEFORE_PREPARE = 'before-prepare';
32
    const STEP_AFTER_PREPARE = 'after-prepare';
33
    const STEP_AFTER_RUN = 'after-run';
34
35
    public $fetchMode = \PDO::FETCH_ASSOC;
36
    public $dsn;
37
    public $username;
38
    public $password;
39
    public $options = [];
40
41
    protected $middlewares = [];
42
    protected ?\PDO $pdo = null;
43
44 11
    public static function create($config = [])
45
    {
46 11
        $instance = new static();
47 11
        $instance->configure($config);
48
49 11
        return $instance;
50
    }
51
52 11
    public function configure(array $config)
53
    {
54 11
        $this->fetchMode ??= $config['fetchMode'];
55 11
        $this->dsn ??= $config['dsn'];
56 11
        $this->username ??= $config['username'];
57 11
        $this->password ??= $config['password'];
58 11
        $this->options ??= $config['options'];
59
60 11
        $middlewares = $config['middleware'] ?? [];
61
62 11
        foreach ($middlewares as $step => $middlewareList) {
63 2
            $this->useMiddleware($step, $middlewareList);
64
        }
65
66 11
        $this->extendConfigure($config);
67
    }
68
69 2
    public function useMiddleware(string $step, array $middlewareList): Driver
70
    {
71 2
        $this->middlewares[$step] = array_values($middlewareList);
72 2
        return $this;
73
    }
74
75
    abstract protected function extendConfigure(array $config);
76
77 2
    public function close()
78
    {
79 2
        if ($this->pdo === null) {
80 2
            return;
81
        }
82
83 1
        $this->pdo = null;
84 1
        $this->runMiddleware(self::STEP_CLOSE);
85
    }
86
87 11
    protected function runMiddleware($step, $result = null, ...$params)
88
    {
89 11
        if (empty($this->middlewares[$step])) {
90 11
            return $result;
91
        }
92
93 3
        return $this->executeMiddlewareChain($step, 0, $result, $params);
94
    }
95
96 3
    protected function executeMiddlewareChain($step, $index, $result, $params)
97
    {
98 3
        if (empty($this->middlewares[$step][$index])) {
99 3
            return $result;
100
        }
101
102 3
        return $this->middlewares[$step][$index](
103 3
            $result,
104 3
            $params,
105 3
            fn($result) => $this->executeMiddlewareChain($step, $index + 1, $result, $params)
106 3
        );
107
    }
108
109
    public function runInTransaction(callable $transactionRunner)
110
    {
111
        $transaction = $this->beginTransaction();
112
113
        try {
114
            $result = $transactionRunner($this);
115
            $transaction->commit();
116
            return $result;
117
        } catch (\Exception $e) {
118
            $transaction->rollback();
119
            throw $e;
120
        }
121
    }
122
123
    public function beginTransaction(): PdoTransaction
124
    {
125
        return PdoTransaction::create($this);
126
    }
127
128 1
    public function getLastInsertedId($sequenceName = null)
129
    {
130 1
        return $this->getPdo()->lastInsertId($sequenceName);
131
    }
132
133 11
    public function getPdo(): \PDO
134
    {
135 11
        $this->open();
136 11
        return $this->pdo;
137
    }
138
139 11
    public function open()
140
    {
141 11
        if ($this->pdo) {
142 11
            return;
143
        }
144
145 11
        $this->pdo = $this->createPdoInstance();
146 11
        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
147 11
        $this->runMiddleware(self::STEP_OPEN, $this->pdo);
148
    }
149
150
    abstract protected function createPdoInstance(): \PDO;
151
152 11
    public function run(RawQuery $query)
153
    {
154
155 11
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'run');
156
157 11
        $statement = $this->executeStatement($query);
158 11
        $affected = $statement->rowCount();
159 11
        $statement->closeCursor();
160
161 11
        return $this->runMiddleware(self::STEP_AFTER_RUN, $affected, $query, 'run');
162
    }
163
164 11
    protected function executeStatement(RawQuery $query): \PDOStatement
165
    {
166 11
        $statement = $this->prepareStatement($query);
167
168 11
        $statement->execute();
169
170 11
        return $statement;
171
    }
172
173 11
    protected function prepareStatement(RawQuery $query): \PDOStatement
174
    {
175 11
        $this->open();
176
177 11
        $statement = $this->getPdo()->prepare($query->getQuery(), $query->getConfig() ?? []);
178
179
        /** @var \PDOStatement $statement */
180 11
        $statement = $this->runMiddleware(self::STEP_BEFORE_PREPARE, $statement, $query);
181
182 11
        $params = $query->getParams() ?? [];
183
184 11
        foreach ($params as $paramName => [$value, $type]) {
185 11
            if ($type === null) {
186 11
                if (is_int($value)) {
187 11
                    $type = \PDO::PARAM_INT;
188 11
                } elseif (is_null($value)) {
189 11
                    $type = \PDO::PARAM_NULL;
190 11
                } elseif (is_bool($value)) {
191 1
                    $type = \PDO::PARAM_BOOL;
192
                } else {
193 11
                    $type = \PDO::PARAM_STR;
194
                }
195
            }
196
197 11
            $statement->bindValue($paramName, $value, $type);
198
        }
199
200
201 11
        return $this->runMiddleware(self::STEP_AFTER_PREPARE, $statement, $query);
202
    }
203
204 4
    public function fetchFirst(RawQuery $query)
205
    {
206
207 4
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'first');
208
209 4
        $statement = $this->executeStatement($query);
210 4
        $result = $statement->fetch($this->fetchMode);
211 4
        $statement->closeCursor();
212
213 4
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'first');
214
    }
215
216 1
    public function fetchAll(RawQuery $query): array
217
    {
218 1
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'all');
219
220 1
        $statement = $this->executeStatement($query);
221 1
        $result = $statement->fetchAll($this->fetchMode);
222 1
        $statement->closeCursor();
223
224 1
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'all');
225
    }
226
227 4
    public function fetch(RawQuery $query): ResultBuilder
228
    {
229 4
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'builder');
230
231 4
        $result = QueryResultBuilder::create()
232 4
            ->useReader($this->createResultReader($query));
233
234 4
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'builder');
235
    }
236
237 5
    protected function createResultReader(RawQuery $query)
238
    {
239 5
        $result = PdoResultReader::create($this->executeStatement($query));
240 5
        $result->fetchMode = $this->fetchMode;
241
242 5
        return $result;
243
    }
244
245 1
    public function fetchReader(RawQuery $query): ResultReader
246
    {
247 1
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'reader');
248
249 1
        $result = $this->createResultReader($query);
250
251 1
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'reader');
252
    }
253
254 1
    public function appendMiddleware(string $step, callable $middleware): Driver
255
    {
256 1
        $this->middlewares[$step][] = $middleware;
257 1
        return $this;
258
    }
259
}
260