Passed
Push — master ( 3829ec...650305 )
by Aleksandar
03:24 queued 15s
created

PdoDriver::prepareStatement()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7.2694

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 17
c 1
b 0
f 0
nc 11
nop 1
dl 0
loc 31
ccs 14
cts 17
cp 0.8235
crap 7.2694
rs 8.8333
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 2
    public static function create($config = [])
45
    {
46 2
        $instance = new static();
47 2
        $instance->configure($config);
48
49 2
        return $instance;
50
    }
51
52 2
    public function configure(array $config)
53
    {
54 2
        $this->fetchMode ??= $config['fetchMode'];
55 2
        $this->dsn ??= $config['dsn'];
56 2
        $this->username ??= $config['username'];
57 2
        $this->password ??= $config['password'];
58 2
        $this->options ??= $config['options'];
59
60 2
        $middlewares = $config['middleware'] ?? [];
61
62 2
        foreach ($middlewares as $step => $middlewareList) {
63
            $this->useMiddleware($step, $middlewareList);
64
        }
65
66 2
        $this->extendConfigure($config);
67 2
    }
68
69
    public function useMiddleware(string $step, array $middlewareList): Driver
70
    {
71
        $this->middlewares[$step] = array_values($middlewareList);
72
        return $this;
73
    }
74
75
    protected abstract function extendConfigure(array $config);
76
77
    public function close()
78
    {
79
        if ($this->pdo === null) {
80
            return;
81
        }
82
83
        $this->pdo = null;
84
        $this->runMiddleware(self::STEP_CLOSE);
85
    }
86
87 2
    protected function runMiddleware($step, $result = null, ...$params)
88
    {
89 2
        if (empty($this->middlewares[$step])) {
90 2
            return $result;
91
        }
92
93
        return $this->executeMiddlewareChain($step, 0, $result, $params);
94
    }
95
96
    protected function executeMiddlewareChain($step, $index, $result, $params)
97
    {
98
        if (empty($this->middlewares[$step][$index])) {
99
            return $result;
100
        }
101
102
        return $this->middlewares[$step][$index](
103
            $result,
104
            $params,
105
            fn($result) => $this->executeMiddlewareChain($step, $index + 1, $result, $params)
106
        );
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
    public function getLastInsertedId($sequenceName = null)
129
    {
130
        return $this->getPdo()->lastInsertId($sequenceName);
131
    }
132
133 2
    public function getPdo(): \PDO
134
    {
135 2
        $this->open();
136 2
        return $this->pdo;
137
    }
138
139 2
    public function open()
140
    {
141 2
        if ($this->pdo) {
142 2
            return;
143
        }
144
145 2
        $this->pdo = $this->createPdoInstance();
146 2
        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
147 2
        $this->runMiddleware(self::STEP_OPEN, $this->pdo);
148 2
    }
149
150
    protected abstract function createPdoInstance(): \PDO;
151
152 2
    public function run(RawQuery $query)
153
    {
154
155 2
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'run');
156
157 2
        $statement = $this->executeStatement($query);
158 2
        $affected = $statement->rowCount();
159 2
        $statement->closeCursor();
160
161 2
        return $this->runMiddleware(self::STEP_AFTER_RUN, $affected, $query, 'run');
162
    }
163
164 2
    protected function executeStatement(RawQuery $query): \PDOStatement
165
    {
166 2
        $statement = $this->prepareStatement($query);
167
168 2
        $statement->execute();
169
170 2
        return $statement;
171
    }
172
173 2
    protected function prepareStatement(RawQuery $query): \PDOStatement
174
    {
175 2
        $this->open();
176
177 2
        $statement = $this->getPdo()->prepare($query->getQuery(), $query->getConfig() ?? []);
178
179
        /** @var \PDOStatement $statement */
180 2
        $statement = $this->runMiddleware(self::STEP_BEFORE_PREPARE, $statement, $query);
181
182 2
        $params = $query->getParams() ?? [];
183
184 2
        foreach ($params as $paramName => [$value, $type]) {
185 2
            if ($type === null) {
186 2
                if (is_int($value)) {
187
                    $type = \PDO::PARAM_INT;
188 2
                } else if (is_null($value)) {
189 2
                    $type = \PDO::PARAM_NULL;
190 2
                } else if (is_bool($value)) {
191
                    $type = \PDO::PARAM_BOOL;
192
                } else {
193 2
                    $type = \PDO::PARAM_STR;
194
                }
195
            }
196
197 2
            if (!$statement->bindValue($paramName, $value, $type)) {
198
                throw new \Exception('Cannot bind: ' . $paramName);
199
            }
200
        }
201
202
203 2
        return $this->runMiddleware(self::STEP_AFTER_PREPARE, $statement, $query);
204
    }
205
206 1
    public function fetchFirst(RawQuery $query)
207
    {
208
209 1
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'first');
210
211 1
        $statement = $this->executeStatement($query);
212 1
        $result = $statement->fetch($this->fetchMode);
213 1
        $statement->closeCursor();
214
215 1
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'first');
216
    }
217
218 1
    public function fetchAll(RawQuery $query): array
219
    {
220 1
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'all');
221
222 1
        $statement = $this->executeStatement($query);
223 1
        $result = $statement->fetchAll($this->fetchMode);
224 1
        $statement->closeCursor();
225
226 1
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'all');
227
    }
228
229
    public function fetch(RawQuery $query): ResultBuilder
230
    {
231
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'builder');
232
233
        $result = QueryResultBuilder::create()
234
            ->useReader($this->createResultReader($query));
235
236
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'builder');
237
    }
238
239
    protected function createResultReader(RawQuery $query)
240
    {
241
        $result = PdoResultReader::create($this->prepareStatement($query));
242
        $result->fetchMode = $this->fetchMode;
243
244
        return $result;
245
    }
246
247
    public function fetchReader(RawQuery $query): ResultReader
248
    {
249
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'reader');
250
251
        $result = $this->createResultReader($query);
252
253
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'reader');
254
    }
255
256
    public function appendMiddleware(string $step, callable $middleware): Driver
257
    {
258
        $this->middlewares[$step][] = $middleware;
259
        return $this;
260
    }
261
}