1 | <?php |
||
2 | |||
3 | namespace MySociety\TheyWorkForYou\Db; |
||
4 | |||
5 | /** |
||
6 | * Database Query |
||
7 | * |
||
8 | * Represents a single query to the database. |
||
9 | * |
||
10 | * ### After a `SELECT` |
||
11 | * |
||
12 | * $q can be used as an iterator with foreach to loop over the rows. |
||
13 | * $q->first() returns the first row if present, else null. |
||
14 | * $q->exists() returns a boolean as to whether there were any results. |
||
15 | * $q->rows() returns the number of rows selected |
||
16 | * $q->insert_id() returns NULL. |
||
17 | * $q->affected_rows() returns NULL. |
||
18 | * |
||
19 | * ### After an `INSERT` |
||
20 | * |
||
21 | * $q->rows() returns NULL. |
||
22 | * $q->insert_id() returns the last_insert_id (if there's AUTO_INCREMENT on a column). |
||
23 | * $q->affected_rows() returns 1. |
||
24 | * |
||
25 | * ### After an `UPDATE` |
||
26 | * |
||
27 | * If rows have been changed: |
||
28 | * $q->rows() returns NULL. |
||
29 | * $q->insert_id() returns 0. |
||
30 | * $q->affected_rows() returns the number of rows changed. |
||
31 | * |
||
32 | * ### After a `DELETE` |
||
33 | * |
||
34 | * $q->rows() returns NULL. |
||
35 | * $q->insert_id() returns 0. |
||
36 | * $q->affected_rows() returns the number of rows changed. |
||
37 | */ |
||
38 | |||
39 | class Query implements \IteratorAggregate, \ArrayAccess { |
||
40 | |||
41 | private $success = true; |
||
42 | private $rows = null; |
||
43 | private $data = array(); |
||
44 | private $insert_id = null; |
||
45 | private $affected_rows = null; |
||
46 | |||
47 | 92 | public function __construct($conn) { |
|
48 | 92 | $this->conn = $conn; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
49 | 92 | } |
|
50 | |||
51 | 92 | public function query($sql = "", $params = null) { |
|
52 | |||
53 | 92 | if (empty($sql)) { |
|
54 | $this->success = false; |
||
55 | |||
56 | return; |
||
57 | } |
||
58 | |||
59 | 92 | if (empty($this->conn)) { |
|
60 | $this->success = false; |
||
61 | |||
62 | return; |
||
63 | } |
||
64 | |||
65 | 92 | twfy_debug("SQL", $sql); |
|
66 | 92 | twfy_debug("SQL", print_r($params, 1)); |
|
67 | |||
68 | 92 | if ($params !== null) { |
|
69 | // Prepare and execute a statement |
||
70 | 89 | $pdoStatement = $this->conn->prepare($sql); |
|
71 | |||
72 | 89 | foreach ($params as $paramKey => $paramValue) { |
|
73 | |||
74 | 82 | if (is_int($paramValue)) { |
|
75 | 46 | $paramType = \PDO::PARAM_INT; |
|
76 | } else { |
||
77 | 81 | $paramType = \PDO::PARAM_STR; |
|
78 | } |
||
79 | |||
80 | 82 | $pdoStatement->bindValue($paramKey, $paramValue, $paramType); |
|
81 | } |
||
82 | |||
83 | 89 | $pdoStatement->execute(); |
|
84 | |||
85 | } else { |
||
86 | // Execute the raw query |
||
87 | 35 | $pdoStatement = $this->conn->query($sql); |
|
88 | } |
||
89 | |||
90 | // Test the query actually worked |
||
91 | 92 | if (!$pdoStatement) { |
|
92 | $this->error($this->conn->errorCode() . ': ' . $this->conn->errorInfo()[2]); |
||
93 | } |
||
94 | |||
95 | 92 | if (!$this->success) { |
|
96 | return; |
||
97 | } |
||
98 | |||
99 | 92 | if ((!$pdoStatement) or (empty($pdoStatement))) { |
|
100 | // A failed query. |
||
101 | $this->success = false; |
||
102 | |||
103 | } else { |
||
104 | |||
105 | // A successful SELECT, SHOW, EXPLAIN or DESCRIBE query. |
||
106 | 92 | $this->success = true; |
|
107 | |||
108 | 92 | $result = $pdoStatement->fetchAll(\PDO::FETCH_ASSOC); |
|
109 | |||
110 | 92 | $this->rows = count($result); |
|
111 | 92 | $this->data = $result; |
|
112 | |||
113 | // Sanity check that lastInsertId() is actually a number, otherwise panic |
||
114 | 92 | if (is_numeric($this->conn->lastInsertId())) { |
|
115 | 92 | $this->insert_id = (int) $this->conn->lastInsertId(); |
|
116 | } else { |
||
117 | throw new Exception('Last connection ID was not numeric!'); |
||
0 ignored issues
–
show
|
|||
118 | } |
||
119 | 92 | $this->affected_rows = $pdoStatement->rowCount(); |
|
120 | |||
121 | 92 | twfy_debug("SQLRESULT", array($this, 'displayResult')); |
|
122 | // mysql_free_result($q); |
||
123 | } |
||
124 | 92 | } |
|
125 | |||
126 | 17 | public function success() { |
|
127 | 17 | return $this->success; |
|
128 | } |
||
129 | |||
130 | // After INSERTS. |
||
131 | 2 | public function insert_id() { |
|
132 | 2 | return $this->insert_id; |
|
133 | } |
||
134 | |||
135 | // After INSERT, UPDATE, DELETE. |
||
136 | public function affected_rows() { |
||
137 | return $this->affected_rows; |
||
138 | } |
||
139 | |||
140 | 60 | public function getIterator() { |
|
141 | 60 | return new \ArrayIterator($this->data); |
|
142 | } |
||
143 | |||
144 | 49 | public function offsetGet($offset) { |
|
145 | 49 | return $this->data[$offset]; |
|
146 | } |
||
147 | |||
148 | public function offsetSet($offset, $value) { |
||
149 | throw new \Exception; |
||
150 | } |
||
151 | |||
152 | public function offsetExists($offset) { |
||
153 | return isset($this->data[$offset]); |
||
154 | } |
||
155 | |||
156 | public function offsetUnset($offset) { |
||
157 | throw new \Exception; |
||
158 | } |
||
159 | |||
160 | 6 | public function fetchAll() { |
|
161 | 6 | return $this->data; |
|
162 | } |
||
163 | |||
164 | /** |
||
165 | * @param integer $row_index |
||
166 | * @param string $column_name |
||
167 | */ |
||
168 | public function field($row_index, $column_name) { |
||
169 | if ($this->rows > 0) { |
||
170 | return $this->data[$row_index][$column_name]; |
||
171 | } |
||
172 | return ""; |
||
173 | } |
||
174 | |||
175 | 50 | public function rows() { |
|
176 | 50 | return $this->rows; |
|
177 | } |
||
178 | |||
179 | // After SELECT. |
||
180 | |||
181 | /** |
||
182 | * @param integer $row_index |
||
183 | */ |
||
184 | public function row($row_index) { |
||
185 | if ($this->success && $this->rows > 0) { |
||
186 | return $this->data[$row_index]; |
||
187 | } |
||
188 | return array(); |
||
189 | } |
||
190 | |||
191 | public function exists() { |
||
192 | return $this->rows > 0; |
||
193 | } |
||
194 | |||
195 | 68 | public function first() { |
|
196 | 68 | return $this->rows > 0 ? $this[0] : null; |
|
197 | } |
||
198 | |||
199 | # Used when debugging |
||
200 | public function displayResult() { |
||
201 | $html = ""; |
||
202 | |||
203 | if ($this->rows > 0) { |
||
204 | |||
205 | $html .= "<table border=\"1\">\n<tr>\n"; |
||
206 | |||
207 | foreach (array_keys($this->data[0]) as $fieldname) { |
||
208 | $html .= "<th>" . _htmlentities($fieldname) . "</th>"; |
||
209 | } |
||
210 | $html .= "</tr>\n"; |
||
211 | |||
212 | foreach ($this->data as $index => $row) { |
||
213 | $html .= "<tr>"; |
||
214 | foreach ($row as $n => $field) { |
||
215 | if ($n == "email" || $n == "password" || $n == "postcode") { |
||
216 | // Don't want to risk this data being displayed on any page. |
||
217 | $html .= "<td>**MASKED**</td>"; |
||
218 | } else { |
||
219 | $html .= "<td>" . _htmlentities($field) . "</td>"; |
||
220 | } |
||
221 | } |
||
222 | $html .= "</tr>\n"; |
||
223 | } |
||
224 | $html .= "</table>\n"; |
||
225 | } |
||
226 | |||
227 | return $html; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * @param string $errormsg |
||
232 | */ |
||
233 | public function error($errormsg) { |
||
234 | // When a query goes wrong... |
||
235 | $this->success = false; |
||
236 | trigger_error($errormsg, E_USER_ERROR); |
||
237 | } |
||
238 | |||
239 | } |
||
240 |