1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace alkemann\h2l\data; |
4
|
|
|
|
5
|
|
|
use alkemann\h2l\exceptions\ConnectionError; |
6
|
|
|
use PDO; |
7
|
|
|
use alkemann\h2l\Log; |
8
|
|
|
|
9
|
|
|
class Mysql implements Source |
10
|
|
|
{ |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* @var array |
14
|
|
|
*/ |
15
|
|
|
protected $config = []; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @var PDO |
19
|
|
|
*/ |
20
|
|
|
protected $db = null; |
21
|
|
|
|
22
|
|
|
public function __construct(array $config = []) |
23
|
|
|
{ |
24
|
|
|
$defaults = [ |
25
|
|
|
'host' => 'localhost', |
26
|
|
|
'db' => 'test', |
27
|
|
|
'user' => null, |
28
|
|
|
'pass' => null |
29
|
|
|
]; |
30
|
|
|
$this->config = $config + $defaults; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
private function handler() //: PDO |
34
|
|
|
{ |
35
|
|
|
if ($this->db) { |
36
|
|
|
return $this->db; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
$host = $this->config['host']; |
40
|
|
|
$db = $this->config['db']; |
41
|
|
|
$user = $this->config['user']; |
42
|
|
|
$pass = $this->config['pass']; |
43
|
|
|
$opts = [ |
44
|
|
|
PDO::ATTR_EMULATE_PREPARES => false, |
45
|
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION |
46
|
|
|
]; |
47
|
|
|
try { |
48
|
|
|
$this->db = new PDO("mysql:host={$host};dbname={$db}", $user, $pass, $opts); |
49
|
|
|
// @TODO use this? |
|
|
|
|
50
|
|
|
// $this->db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false); |
|
|
|
|
51
|
|
|
} catch (\PDOException $e) { |
52
|
|
|
throw new ConnectionError("Unable to connect to $host : $db with user $user"); |
|
|
|
|
53
|
|
|
} |
54
|
|
|
return $this->db; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
public function query($query, array $params = []) |
58
|
|
|
{ |
59
|
|
|
Log::debug("PDO:QUERY [$query]"); |
60
|
|
|
$result = $this->handler()->query($query); |
61
|
|
|
return $result->fetchAll(PDO::FETCH_ASSOC); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function one(string $table, array $conditions, array $options = []): ?array |
65
|
|
|
{ |
66
|
|
|
$result = $this->find($table, $conditions, $options); |
67
|
|
|
$result = iterator_to_array($result); |
68
|
|
|
$hits = sizeof($result); |
69
|
|
|
if ($hits === 0) { |
70
|
|
|
return null; |
71
|
|
|
} |
72
|
|
|
if ($hits > 1) { |
73
|
|
|
throw new \Error("One request found more than 1 match!"); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
return $result[0]; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
public function find(string $table, array $conditions, array $options = []): iterable |
80
|
|
|
{ |
81
|
|
|
$where = $this->where($conditions); |
82
|
|
|
$limit = array_key_exists('limit', $options) ? "LIMIT :o_offset,:o_limit " : ''; |
83
|
|
|
$query = "SELECT * FROM `{$table}` {$where}{$limit};"; |
84
|
|
|
$params = $this->boundDebugString($conditions, $options); |
85
|
|
|
Log::debug("PDO:QUERY [$query][$params]"); |
86
|
|
|
$dbh = $this->handler(); |
87
|
|
|
$stmt = $dbh->prepare($query); |
88
|
|
|
foreach ($conditions as $key => $value) { |
89
|
|
|
$stmt->bindValue(":c_{$key}", $value); |
90
|
|
|
} |
91
|
|
|
if (empty($limit) === false) { |
92
|
|
|
$stmt->bindValue(":o_offset", $options['offset'] ?? 0, PDO::PARAM_INT); |
93
|
|
|
$stmt->bindValue(":o_limit", $options['limit'], PDO::PARAM_INT); |
94
|
|
|
} |
95
|
|
|
if ($stmt->execute() === false) { |
96
|
|
|
return new \EmptyIterator; |
97
|
|
|
} |
98
|
|
|
if ($stmt && $stmt instanceof \PDOStatement) { |
99
|
|
|
// @codeCoverageIgnoreStart |
100
|
|
|
$stmt->setFetchMode(PDO::FETCH_ASSOC); |
101
|
|
|
// codeCoverageIgnoreEnd |
102
|
|
|
} |
103
|
|
|
return $stmt; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
private function boundDebugString(array $conditions, array $options, array $data = []): string |
107
|
|
|
{ |
108
|
|
|
$out = []; |
109
|
|
|
foreach ($conditions as $k => $v) { |
110
|
|
|
$out[] = "c_{$k}:'{$v}'"; |
111
|
|
|
} |
112
|
|
|
foreach ($data as $k => $v) { |
113
|
|
|
$out[] = "d_{$k}:{$v}"; |
114
|
|
|
} |
115
|
|
|
foreach ($options as $k => $v) { |
116
|
|
|
$out[] = "o_{$k}:{$v}"; |
117
|
|
|
} |
118
|
|
|
return join(", ", $out); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
private function where(array $conditions): string |
122
|
|
|
{ |
123
|
|
|
if (empty($conditions)) { |
124
|
|
|
return ""; |
125
|
|
|
} |
126
|
|
|
$fun = function ($o, $v) { |
127
|
|
|
return "{$o}{$v} = :c_{$v}"; |
128
|
|
|
}; |
129
|
|
|
$where = array_reduce(array_keys($conditions), $fun, ""); |
130
|
|
|
return "WHERE {$where} "; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
public function update(string $table, array $conditions, array $data, array $options = []): int |
134
|
|
|
{ |
135
|
|
|
if (empty($conditions) || empty($data)) { |
136
|
|
|
return 0; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
$datasql = $this->data($data); |
140
|
|
|
$where = $this->where($conditions); |
141
|
|
|
$query = "UPDATE `{$table}` SET {$datasql} {$where};"; |
142
|
|
|
|
143
|
|
|
$params = $this->boundDebugString($conditions, $options, $data); |
144
|
|
|
Log::debug("PDO:QUERY [$query][$params]"); |
145
|
|
|
$dbh = $this->handler(); |
146
|
|
|
$stmt = $dbh->prepare($query); |
147
|
|
|
foreach ($data as $key => $value) { |
148
|
|
|
$stmt->bindValue(":d_{$key}", $value); |
149
|
|
|
} |
150
|
|
|
foreach ($conditions as $key => $value) { |
151
|
|
|
$stmt->bindValue(":c_{$key}", $value); |
152
|
|
|
} |
153
|
|
|
$result = $stmt->execute(); |
154
|
|
|
return ($result === true) ? $stmt->rowCount() : 0; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
private function data(array $data): string |
158
|
|
|
{ |
159
|
|
|
$fun = function ($o, $v) { |
160
|
|
|
return "{$o}{$v} = :d_{$v}"; |
161
|
|
|
}; |
162
|
|
|
return array_reduce(array_keys($data), $fun, ""); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function insert(string $table, array $data, array $options = []): ?int |
166
|
|
|
{ |
167
|
|
|
$keys = implode(', ', array_keys($data)); |
168
|
|
|
$data_phs = ':d_' . implode(', :d_', array_keys($data)); |
169
|
|
|
$query = "INSERT INTO `{$table}` ({$keys}) VALUES ({$data_phs});"; |
170
|
|
|
$params = $this->boundDebugString([], [], $data); |
171
|
|
|
Log::debug("PDO:QUERY [$query][$params]"); |
172
|
|
|
$dbh = $this->handler(); |
173
|
|
|
$stmt = $dbh->prepare($query); |
174
|
|
|
foreach ($data as $key => $value) { |
175
|
|
|
$stmt->bindValue(':d_' . $key, $value); |
176
|
|
|
} |
177
|
|
|
$result = $stmt->execute(); |
178
|
|
|
return ($result === true) ? $dbh->lastInsertId() : null; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
public function delete(string $table, array $conditions, array $options = []): int |
182
|
|
|
{ |
183
|
|
|
$where = $this->where($conditions); |
184
|
|
|
if (empty($conditions) || empty($where)) { |
185
|
|
|
return 0; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
$limit = array_key_exists('limit', $options) ? "LIMIT :o_offset,:o_limit " : ''; |
189
|
|
|
$query = "DELETE FROM `{$table}` {$where}{$limit};"; |
190
|
|
|
$params = $this->boundDebugString($conditions, $options); |
191
|
|
|
Log::debug("PDO:QUERY [$query][$params]"); |
192
|
|
|
$dbh = $this->handler(); |
193
|
|
|
$stmt = $dbh->prepare($query); |
194
|
|
|
foreach ($conditions as $key => $value) { |
195
|
|
|
$stmt->bindValue(":c_{$key}", $value); |
196
|
|
|
} |
197
|
|
|
if (empty($limit) === false) { |
198
|
|
|
$stmt->bindValue(":o_offset", $options['offset'] ?? 0, PDO::PARAM_INT); |
199
|
|
|
$stmt->bindValue(":o_limit", $options['limit'], PDO::PARAM_INT); |
200
|
|
|
} |
201
|
|
|
$result = $stmt->execute(); |
202
|
|
|
return ($result === true) ? $stmt->rowCount() : 0; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.