Completed
Push — master ( dc226d...19a644 )
by Alexander
02:30
created

PDO::data()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l\data;
4
5
use alkemann\h2l\exceptions\ConnectionError;
6
use alkemann\h2l\interfaces\Source;
7
use alkemann\h2l\Log;
8
use PDO as _PDO;
9
10
/**
11
 * Class PDO
12
 *
13
 * @package alkemann\h2l\data
14
 */
15
class PDO implements Source
16
{
17
18
    /**
19
     * @var array
20
     */
21
    protected $config = [];
22
23
    /**
24
     * @var _PDO
25
     */
26
    protected $db = null;
27
28
    private $pdo_class = _PDO::class;
29
30
    public function __construct(array $config = [], string $pdo_class = _PDO::class)
31
    {
32
        $this->pdo_class = $pdo_class;
33
34
        if (count($config) === 1 && array_key_exists('url', $config)) {
35
            $config = parse_url($config['url']);
36
            $config['db'] = ltrim($config['path'], '/');
37
        }
38
39
        $defaults = [
40
            'host' => 'localhost',
41
            'db' => 'test',
42
            'user' => null,
43
            'pass' => null
44
        ];
45
        $this->config = $config + $defaults;
46
    }
47
48
    private function handler() //: PDO
49
    {
50
        if ($this->db) {
51
            return $this->db;
52
        }
53
54
        $scheme = $this->config['scheme'] ?? 'mysql';
55
        $host = $this->config['host'];
56
        $db = $this->config['db'];
57
        $user = $this->config['user'];
58
        $pass = $this->config['pass'] ?? '';
59
        $port = ($this->config['port'] ?? false) ? ";port={$this->config['port']}" : '';
60
        if (empty($this->config['query'])) {
61
            $modifiers = '';
62
        } else {
63
            $modifiers = ';' . str_replace('&', ';', $this->config['query']);
64
        }
65
        $opts = [
66
            _PDO::ATTR_EMULATE_PREPARES => false,
67
            _PDO::ATTR_ERRMODE => _PDO::ERRMODE_EXCEPTION,
68
69
            'useUnicode' => true,
70
            'characterEncoding' => 'UTF-8',
71
        ];
72
        $dsn = "{$scheme}:host={$host}{$port}{$modifiers};dbname={$db}";
73
        $class = $this->pdo_class;
74
        try {
75
            $this->db = new $class($dsn, $user, $pass, $opts);
76
77
            // @TODO use this?
78
            // $this->db->setAttribute( _PDO::ATTR_EMULATE_PREPARES, false);
79
        } catch (\PDOException $e) {
80
            throw new ConnectionError("Unable to connect to $host : $db with user $user");
81
        }
82
        return $this->db;
83
    }
84
85
    public function query($query, array $params = [])
86
    {
87
        Log::debug("PDO:QUERY [$query]");
88
        $result = $this->handler()->query($query);
89
        return $result->fetchAll(_PDO::FETCH_ASSOC);
90
    }
91
92
    public function one(string $table, array $conditions, array $options = []): ?array
93
    {
94
        $result = $this->find($table, $conditions, $options);
95
        $result = iterator_to_array($result);
96
        $hits = sizeof($result);
97
        if ($hits === 0) {
98
            return null;
99
        }
100
        if ($hits > 1) {
101
            throw new \Error("One request found more than 1 match!");
102
        }
103
104
        return $result[0];
105
    }
106
107
    public function find(string $table, array $conditions, array $options = []): iterable
108
    {
109
        $where = $this->where($conditions);
110
        $limit = $this->limit($options);
111
        $order = $this->order($options);
112
        $query = "SELECT * FROM {$table} {$where}{$order}{$limit};";
113
        $params = $this->boundDebugString($conditions, $options);
114
        Log::debug("PDO:QUERY [$query][$params]");
115
        $dbh = $this->handler();
116
        $stmt = $dbh->prepare($query);
117
        foreach ($conditions as $key => $value) {
118
            if (is_array($value)) {
119
                foreach ($value as $index => $v) {
120
                    $i = $index + 1;
121
                    $stmt->bindValue(":c_{$key}_{$i}", $v);
122
                }
123
            } else {
124
                $stmt->bindValue(":c_{$key}", $value);
125
            }
126
        }
127
        $this->bindPaginationToStatement($options, $stmt);
0 ignored issues
show
Bug introduced by
It seems like $stmt can also be of type boolean; however, parameter $stmt of alkemann\h2l\data\PDO::bindPaginationToStatement() does only seem to accept PDOStatement, maybe add an additional type check? ( Ignorable by Annotation )

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

127
        $this->bindPaginationToStatement($options, /** @scrutinizer ignore-type */ $stmt);
Loading history...
128
        $result = $stmt->execute();
129
        if ($result === false) {
130
            return new \EmptyIterator;
131
        }
132
        // @codeCoverageIgnoreStart
133
        if ($stmt instanceof \PDOStatement) {
134
            $stmt->setFetchMode(_PDO::FETCH_ASSOC);
135
        }
136
        // @codeCoverageIgnoreEnd
137
        return $stmt;
138
    }
139
140
    private function where(array $conditions): string
141
    {
142
        if (empty($conditions)) {
143
            return "";
144
        }
145
        $fun = function ($o, $v) use ($conditions) {
146
            if (is_array($conditions[$v])) {
147
                $qa = [];
148
                foreach ($conditions[$v] as $key => $value) {
149
                    $i = $key + 1;
150
                    $qa[] = ":c_{$v}_{$i}";
151
                }
152
                $qs = join(', ', $qa);
153
                return "{$o} AND {$v} IN ( $qs )";
154
            } else {
155
                return "{$o} AND {$v} = :c_{$v}";
156
            }
157
        };
158
        $where = trim(array_reduce(array_keys($conditions), $fun, ""), ' AND ');
159
        return "WHERE {$where} ";
160
    }
161
162
    private function limit(array $options): string
163
    {
164
        return array_key_exists('limit', $options) ? "LIMIT :o_offset,:o_limit " : '';
165
    }
166
167
    private function order(array $options): string
168
    {
169
        if (array_key_exists('order', $options)) {
170
            // @TODO Add more protection?
171
            return "ORDER BY {$options['order']}";
172
        }
173
        return '';
174
    }
175
176
    private function boundDebugString(array $conditions, array $options, array $data = []): string
177
    {
178
        $out = [];
179
        foreach ($conditions as $k => $v) {
180
            if (is_array($v)) {
181
                $v = join(',', $v);
182
            }
183
            $out[] = "c_{$k}:'{$v}'";
184
        }
185
        foreach ($data as $k => $v) {
186
            $out[] = "d_{$k}:{$v}";
187
        }
188
        foreach ($options as $k => $v) {
189
            $out[] = "o_{$k}:{$v}";
190
        }
191
        return join(", ", $out);
192
    }
193
194
    /**
195
     * @param array $options
196
     * @param \PDOStatement $stmt
197
     */
198
    private function bindPaginationToStatement(array $options, $stmt): void
199
    {
200
        if (array_key_exists('limit', $options)) {
201
            $stmt->bindValue(":o_offset", (int)($options['offset'] ?? 0), _PDO::PARAM_INT);
202
            $stmt->bindValue(":o_limit", (int)$options['limit'], _PDO::PARAM_INT);
203
        }
204
    }
205
206
    public function update(string $table, array $conditions, array $data, array $options = []): int
207
    {
208
        if (empty($conditions) || empty($data)) {
209
            return 0;
210
        }
211
212
        $datasql = $this->data($data);
213
        $where = $this->where($conditions);
214
        $query = "UPDATE {$table} SET {$datasql} {$where};";
215
216
        $params = $this->boundDebugString($conditions, $options, $data);
217
        Log::debug("PDO:QUERY [$query][$params]");
218
        $dbh = $this->handler();
219
        $stmt = $dbh->prepare($query);
220
        foreach ($data as $key => $value) {
221
            $stmt->bindValue(":d_{$key}", $value);
222
        }
223
        foreach ($conditions as $key => $value) {
224
            $stmt->bindValue(":c_{$key}", $value);
225
        }
226
        $result = $stmt->execute();
227
        return ($result === true) ? $stmt->rowCount() : 0;
228
    }
229
230
    private function data(array $data): string
231
    {
232
        $fun = function ($o, $v) {
233
            return "{$o}, {$v} = :d_{$v}";
234
        };
235
        return trim((string) array_reduce(array_keys($data), $fun, ""), ", ");
236
    }
237
238
    public function insert(string $table, array $data, array $options = []): ?string
239
    {
240
        $keys = implode(', ', array_keys($data));
241
        $data_phs = ':d_' . implode(', :d_', array_keys($data));
242
        $query = "INSERT INTO {$table} ({$keys}) VALUES ({$data_phs});";
243
        $params = $this->boundDebugString([], [], $data);
244
        Log::debug("PDO:QUERY [$query][$params]");
245
        $dbh = $this->handler();
246
        $stmt = $dbh->prepare($query);
247
        foreach ($data as $key => $value) {
248
            $stmt->bindValue(':d_' . $key, $value);
249
        }
250
        $result = $stmt->execute();
251
        return ($result === true) ? $dbh->lastInsertId() : null;
252
    }
253
254
    public function delete(string $table, array $conditions, array $options = []): int
255
    {
256
        $where = $this->where($conditions);
257
        if (empty($where)) {
258
            return 0;
259
        }
260
        $limit = $this->limit($options);
261
        $query = "DELETE FROM {$table} {$where}{$limit};";
262
        $params = $this->boundDebugString($conditions, $options);
263
        Log::debug("PDO:QUERY [$query][$params]");
264
        $dbh = $this->handler();
265
        $stmt = $dbh->prepare($query);
266
        foreach ($conditions as $key => $value) {
267
            $stmt->bindValue(":c_{$key}", $value);
268
        }
269
        $this->bindPaginationToStatement($options, $stmt);
0 ignored issues
show
Bug introduced by
It seems like $stmt can also be of type boolean; however, parameter $stmt of alkemann\h2l\data\PDO::bindPaginationToStatement() does only seem to accept PDOStatement, maybe add an additional type check? ( Ignorable by Annotation )

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

269
        $this->bindPaginationToStatement($options, /** @scrutinizer ignore-type */ $stmt);
Loading history...
270
        $result = $stmt->execute();
271
        return ($result === true) ? $stmt->rowCount() : 0;
272
    }
273
}
274