Passed
Push — master ( b979f8...3829ec )
by Aleksandar
07:20 queued 11s
created

PdoDriver::prepareStatement()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 16
nc 6
nop 1
dl 0
loc 27
ccs 0
cts 17
cp 0
crap 42
rs 9.1111
c 1
b 0
f 0
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;
43
44
    public static function create($config = [])
45
    {
46
        $instance = new static();
47
        $instance->configure($config);
48
49
        return $instance;
50
    }
51
52
    public function configure(array $config)
53
    {
54
        $this->fetchMode ??= $config['fetchMode'];
55
        $this->dsn ??= $config['dsn'];
56
        $this->username ??= $config['username'];
57
        $this->password ??= $config['password'];
58
        $this->options ??= $config['options'];
59
60
        $middlewares = $config['middleware'] ?? [];
61
62
        foreach ($middlewares as $step => $middlewareList) {
63
            $this->useMiddleware($step, $middlewareList);
64
        }
65
66
        $this->extendConfigure($config);
67
    }
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
    protected function runMiddleware($step, $result = null, ...$params)
88
    {
89
        if (empty($this->middlewares[$step])) {
90
            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
    public function getPdo(): \PDO
134
    {
135
        $this->open();
136
        return $this->pdo;
137
    }
138
139
    public function open()
140
    {
141
        if ($this->pdo) {
142
            return;
143
        }
144
145
        $this->pdo = $this->createPdoInstance();
146
        $this->runMiddleware(self::STEP_OPEN, $this->pdo);
147
    }
148
149
    protected abstract function createPdoInstance(): \PDO;
150
151
    public function run(RawQuery $query)
152
    {
153
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'run');
154
155
        $statement = $this->executeStatement($query);
156
        $affected = $statement->rowCount();
157
        $statement->closeCursor();
158
159
        return $this->runMiddleware(self::STEP_AFTER_RUN, $affected, $query, 'run');
160
    }
161
162
    protected function executeStatement(RawQuery $query): \PDOStatement
163
    {
164
        $statement = $this->prepareStatement($query);
165
        $statement->execute();
166
167
        return $statement;
168
    }
169
170
    protected function prepareStatement(RawQuery $query): \PDOStatement
171
    {
172
        $this->open();
173
174
        $statement = $this->getPdo()->prepare($query->getQuery(), $query->getConfig() ?? []);
175
176
        $statement = $this->runMiddleware(self::STEP_BEFORE_PREPARE, $statement, $query);
177
178
        $params = $query->getParams() ?? [];
179
180
        foreach ($params as $paramName => [$value, $type]) {
181
            if ($type === null) {
182
                if (is_int($value)) {
183
                    $type = \PDO::PARAM_INT;
184
                } else if (is_null($value)) {
185
                    $type = \PDO::PARAM_NULL;
186
                } else if (is_bool($value)) {
187
                    $type = \PDO::PARAM_BOOL;
188
                } else {
189
                    $type = \PDO::PARAM_STR;
190
                }
191
            }
192
193
            $statement->bindParam($paramName, $value, $type);
194
        }
195
196
        return $this->runMiddleware(self::STEP_AFTER_PREPARE, $statement, $query);
197
    }
198
199
    public function fetchFirst(RawQuery $query)
200
    {
201
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'first');
202
203
        $statement = $this->executeStatement($query);
204
        $result = $statement->fetch($this->fetchMode);
205
        $statement->closeCursor();
206
207
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'first');
208
    }
209
210
    public function fetchAll(RawQuery $query): array
211
    {
212
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'all');
213
214
        $statement = $this->executeStatement($query);
215
        $result = $statement->fetchAll($this->fetchMode);
216
        $statement->closeCursor();
217
218
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'all');
219
    }
220
221
    public function fetch(RawQuery $query): ResultBuilder
222
    {
223
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'builder');
224
225
        $result = QueryResultBuilder::create()
0 ignored issues
show
Bug introduced by
The method create() does not exist on ArekX\PQL\QueryResultBuilder. ( Ignorable by Annotation )

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

225
        $result = QueryResultBuilder::/** @scrutinizer ignore-call */ create()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
226
            ->useReader($this->createResultReader($query));
227
228
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'builder');
229
    }
230
231
    protected function createResultReader(RawQuery $query)
232
    {
233
        $result = PdoResultReader::createFromStatement($this->prepareStatement($query));
234
        $result->fetchMode = $this->fetchMode;
235
236
        return $result;
237
    }
238
239
    public function fetchReader(RawQuery $query): ResultReader
240
    {
241
        $query = $this->runMiddleware(self::STEP_BEFORE_RUN, $query, 'reader');
242
243
        $result = $this->createResultReader($query);
244
245
        return $this->runMiddleware(self::STEP_AFTER_RUN, $result, $query, 'reader');
246
    }
247
248
    public function appendMiddleware(string $step, callable $middleware): Driver
249
    {
250
        $this->middlewares[$step][] = $middleware;
251
        return $this;
252
    }
253
}