Issues (354)

classes/Db/Query.php (2 issues)

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
The property conn does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
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
The type MySociety\TheyWorkForYou\Db\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
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