Completed
Push — v2 ( 51be3e...514465 )
by Berend
02:55
created
src/AbstractActiveRecord.php 2 patches
Indentation   +602 added lines, -602 removed lines patch added patch discarded remove patch
@@ -18,613 +18,613 @@
 block discarded – undo
18 18
  */
19 19
 abstract class AbstractActiveRecord implements ActiveRecordInterface
20 20
 {
21
-	const COLUMN_NAME_ID = 'id';
22
-	const COLUMN_TYPE_ID = 'INT UNSIGNED';
23
-
24
-	/** @var \PDO The PDO object. */
25
-	protected $pdo;
26
-
27
-	/** @var null|int The ID. */
28
-	private $id;
29
-
30
-	/** @var array A map of column name to functions that hook the insert function */
31
-	protected $registeredCreateHooks;
32
-
33
-	/** @var array A map of column name to functions that hook the read function */
34
-	protected $registeredReadHooks;
35
-
36
-	/** @var array A map of column name to functions that hook the update function */
37
-	protected $registeredUpdateHooks;
38
-
39
-	/** @var array A map of column name to functions that hook the update function */
40
-	protected $registeredDeleteHooks;	
41
-
42
-	/** @var array A map of column name to functions that hook the search function */
43
-	protected $registeredSearchHooks;
44
-
45
-	/** @var array A list of table column definitions */
46
-	protected $tableDefinition;
47
-
48
-	/**
49
-	 * Construct an abstract active record with the given PDO.
50
-	 *
51
-	 * @param \PDO $pdo
52
-	 */
53
-	public function __construct(\PDO $pdo)
54
-	{
55
-		$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
56
-		$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
57
-
58
-		$this->setPdo($pdo);
59
-
60
-		$this->registeredCreateHooks = [];
61
-		$this->registeredReadHooks = [];
62
-		$this->registeredUpdateHooks = [];
63
-		$this->registeredDeleteHooks = [];
64
-		$this->registeredSearchHooks = [];
65
-		$this->tableDefinition = $this->getTableDefinition();
66
-
67
-		// Extend table definition with default ID field, throw exception if field already exists
68
-		if (array_key_exists('id', $this->tableDefinition)) {
69
-			$message = "Table definition in record contains a field with name \"id\"";
70
-			$message .= ", which is a reserved name by ActiveRecord";
71
-			throw new ActiveRecordException($message, 0);
72
-		}
73
-
74
-		$this->tableDefinition[self::COLUMN_NAME_ID] =
75
-		[
76
-			'value' => &$this->id,
77
-			'validate' => null,
78
-			'type' => self::COLUMN_TYPE_ID,
79
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE | ColumnProperty::AUTO_INCREMENT | ColumnProperty::PRIMARY_KEY
80
-		];
81
-	}
82
-
83
-	private function checkHookConstraints($columnName, $hookMap)
84
-	{
85
-		// Check whether column exists
86
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
87
-		{
88
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
89
-		}
90
-
91
-		// Enforcing 1 hook per table column
92
-		if (array_key_exists($columnName, $hookMap)) {
93
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
94
-			$message .= "do you have conflicting traits?";
95
-			throw new ActiveRecordException($message, 0);
96
-		}
97
-	}
98
-
99
-	/**
100
-	 * Register a new hook for a specific column that gets called before execution of the create() method
101
-	 * Only one hook per column can be registered at a time
102
-	 * @param string $columnName The name of the column that is registered.
103
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
104
-	 */
105
-	public function registerCreateHook($columnName, $fn)
106
-	{
107
-		$this->checkHookConstraints($columnName, $this->registeredCreateHooks);
108
-
109
-		if (is_string($fn) && is_callable([$this, $fn])) {
110
-			$this->registeredCreateHooks[$columnName] = [$this, $fn];
111
-		} else if (is_callable($fn)) {
112
-			$this->registeredCreateHooks[$columnName] = $fn;
113
-		} else {
114
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
115
-		}
116
-	}
117
-
118
-	/**
119
-	 * Register a new hook for a specific column that gets called before execution of the read() method
120
-	 * Only one hook per column can be registered at a time
121
-	 * @param string $columnName The name of the column that is registered.
122
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
123
-	 */
124
-	public function registerReadHook($columnName, $fn)
125
-	{
126
-		$this->checkHookConstraints($columnName, $this->registeredReadHooks);
127
-
128
-		if (is_string($fn) && is_callable([$this, $fn])) {
129
-			$this->registeredReadHooks[$columnName] = [$this, $fn];
130
-		} else if (is_callable($fn)) {
131
-			$this->registeredReadHooks[$columnName] = $fn;
132
-		} else {
133
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
134
-		}
135
-	}
136
-
137
-	/**
138
-	 * Register a new hook for a specific column that gets called before execution of the update() method
139
-	 * Only one hook per column can be registered at a time
140
-	 * @param string $columnName The name of the column that is registered.
141
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
142
-	 */
143
-	public function registerUpdateHook($columnName, $fn)
144
-	{
145
-		$this->checkHookConstraints($columnName, $this->registeredUpdateHooks);
146
-
147
-		if (is_string($fn) && is_callable([$this, $fn])) {
148
-			$this->registeredUpdateHooks[$columnName] = [$this, $fn];
149
-		} else if (is_callable($fn)) {
150
-			$this->registeredUpdateHooks[$columnName] = $fn;
151
-		} else {
152
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
153
-		}
154
-	}
155
-
156
-	/**
157
-	 * Register a new hook for a specific column that gets called before execution of the delete() method
158
-	 * Only one hook per column can be registered at a time
159
-	 * @param string $columnName The name of the column that is registered.
160
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
161
-	 */
162
-	public function registerDeleteHook($columnName, $fn)
163
-	{
164
-		$this->checkHookConstraints($columnName, $this->registeredDeleteHooks);
165
-
166
-		if (is_string($fn) && is_callable([$this, $fn])) {
167
-			$this->registeredDeleteHooks[$columnName] = [$this, $fn];
168
-		} else if (is_callable($fn)) {
169
-			$this->registeredDeleteHooks[$columnName] = $fn;
170
-		} else {
171
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
172
-		}
173
-	}
174
-
175
-	/**
176
-	 * Register a new hook for a specific column that gets called before execution of the search() method
177
-	 * Only one hook per column can be registered at a time
178
-	 * @param string $columnName The name of the column that is registered.
179
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. The callable is required to take one argument: an instance of miBadger\Query\Query; 
180
-	 */
181
-	public function registerSearchHook($columnName, $fn)
182
-	{
183
-		$this->checkHookConstraints($columnName, $this->registeredSearchHooks);
184
-
185
-		if (is_string($fn) && is_callable([$this, $fn])) {
186
-			$this->registeredSearchHooks[$columnName] = [$this, $fn];
187
-		} else if (is_callable($fn)) {
188
-			$this->registeredSearchHooks[$columnName] = $fn;
189
-		} else {
190
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
191
-		}
192
-	}
193
-
194
-	/**
195
-	 * Adds a new column definition to the table.
196
-	 * @param string $columnName The name of the column that is registered.
197
-	 * @param Array $definition The definition of that column.
198
-	 */
199
-	public function extendTableDefinition($columnName, $definition)
200
-	{
201
-		if ($this->tableDefinition === null) {
202
-			throw new ActiveRecordException("tableDefinition is null, most likely due to parent class not having been initialized in constructor");
203
-		}
204
-
205
-		// Enforcing table can only be extended with new columns
206
-		if (array_key_exists($columnName, $this->tableDefinition)) {
207
-			$message = "Table is being extended with a column that already exists, ";
208
-			$message .= "\"$columnName\" conflicts with your table definition";
209
-			throw new ActiveRecordException($message, 0);
210
-		}
211
-
212
-		$this->tableDefinition[$columnName] = $definition;
213
-	}
214
-
215
-	/**
216
-	 * Returns the type string as it should appear in the mysql create table statement for the given column
217
-	 * @return string The type string
218
-	 */
219
-	private function getDatabaseTypeString($colName, $type, $length)
220
-	{
221
-		switch (strtoupper($type)) {
222
-			case '':
223
-				throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
21
+    const COLUMN_NAME_ID = 'id';
22
+    const COLUMN_TYPE_ID = 'INT UNSIGNED';
23
+
24
+    /** @var \PDO The PDO object. */
25
+    protected $pdo;
26
+
27
+    /** @var null|int The ID. */
28
+    private $id;
29
+
30
+    /** @var array A map of column name to functions that hook the insert function */
31
+    protected $registeredCreateHooks;
32
+
33
+    /** @var array A map of column name to functions that hook the read function */
34
+    protected $registeredReadHooks;
35
+
36
+    /** @var array A map of column name to functions that hook the update function */
37
+    protected $registeredUpdateHooks;
38
+
39
+    /** @var array A map of column name to functions that hook the update function */
40
+    protected $registeredDeleteHooks;	
41
+
42
+    /** @var array A map of column name to functions that hook the search function */
43
+    protected $registeredSearchHooks;
44
+
45
+    /** @var array A list of table column definitions */
46
+    protected $tableDefinition;
47
+
48
+    /**
49
+     * Construct an abstract active record with the given PDO.
50
+     *
51
+     * @param \PDO $pdo
52
+     */
53
+    public function __construct(\PDO $pdo)
54
+    {
55
+        $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
56
+        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
57
+
58
+        $this->setPdo($pdo);
59
+
60
+        $this->registeredCreateHooks = [];
61
+        $this->registeredReadHooks = [];
62
+        $this->registeredUpdateHooks = [];
63
+        $this->registeredDeleteHooks = [];
64
+        $this->registeredSearchHooks = [];
65
+        $this->tableDefinition = $this->getTableDefinition();
66
+
67
+        // Extend table definition with default ID field, throw exception if field already exists
68
+        if (array_key_exists('id', $this->tableDefinition)) {
69
+            $message = "Table definition in record contains a field with name \"id\"";
70
+            $message .= ", which is a reserved name by ActiveRecord";
71
+            throw new ActiveRecordException($message, 0);
72
+        }
73
+
74
+        $this->tableDefinition[self::COLUMN_NAME_ID] =
75
+        [
76
+            'value' => &$this->id,
77
+            'validate' => null,
78
+            'type' => self::COLUMN_TYPE_ID,
79
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE | ColumnProperty::AUTO_INCREMENT | ColumnProperty::PRIMARY_KEY
80
+        ];
81
+    }
82
+
83
+    private function checkHookConstraints($columnName, $hookMap)
84
+    {
85
+        // Check whether column exists
86
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
87
+        {
88
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
89
+        }
90
+
91
+        // Enforcing 1 hook per table column
92
+        if (array_key_exists($columnName, $hookMap)) {
93
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
94
+            $message .= "do you have conflicting traits?";
95
+            throw new ActiveRecordException($message, 0);
96
+        }
97
+    }
98
+
99
+    /**
100
+     * Register a new hook for a specific column that gets called before execution of the create() method
101
+     * Only one hook per column can be registered at a time
102
+     * @param string $columnName The name of the column that is registered.
103
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
104
+     */
105
+    public function registerCreateHook($columnName, $fn)
106
+    {
107
+        $this->checkHookConstraints($columnName, $this->registeredCreateHooks);
108
+
109
+        if (is_string($fn) && is_callable([$this, $fn])) {
110
+            $this->registeredCreateHooks[$columnName] = [$this, $fn];
111
+        } else if (is_callable($fn)) {
112
+            $this->registeredCreateHooks[$columnName] = $fn;
113
+        } else {
114
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
115
+        }
116
+    }
117
+
118
+    /**
119
+     * Register a new hook for a specific column that gets called before execution of the read() method
120
+     * Only one hook per column can be registered at a time
121
+     * @param string $columnName The name of the column that is registered.
122
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
123
+     */
124
+    public function registerReadHook($columnName, $fn)
125
+    {
126
+        $this->checkHookConstraints($columnName, $this->registeredReadHooks);
127
+
128
+        if (is_string($fn) && is_callable([$this, $fn])) {
129
+            $this->registeredReadHooks[$columnName] = [$this, $fn];
130
+        } else if (is_callable($fn)) {
131
+            $this->registeredReadHooks[$columnName] = $fn;
132
+        } else {
133
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
134
+        }
135
+    }
136
+
137
+    /**
138
+     * Register a new hook for a specific column that gets called before execution of the update() method
139
+     * Only one hook per column can be registered at a time
140
+     * @param string $columnName The name of the column that is registered.
141
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
142
+     */
143
+    public function registerUpdateHook($columnName, $fn)
144
+    {
145
+        $this->checkHookConstraints($columnName, $this->registeredUpdateHooks);
146
+
147
+        if (is_string($fn) && is_callable([$this, $fn])) {
148
+            $this->registeredUpdateHooks[$columnName] = [$this, $fn];
149
+        } else if (is_callable($fn)) {
150
+            $this->registeredUpdateHooks[$columnName] = $fn;
151
+        } else {
152
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
153
+        }
154
+    }
155
+
156
+    /**
157
+     * Register a new hook for a specific column that gets called before execution of the delete() method
158
+     * Only one hook per column can be registered at a time
159
+     * @param string $columnName The name of the column that is registered.
160
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
161
+     */
162
+    public function registerDeleteHook($columnName, $fn)
163
+    {
164
+        $this->checkHookConstraints($columnName, $this->registeredDeleteHooks);
165
+
166
+        if (is_string($fn) && is_callable([$this, $fn])) {
167
+            $this->registeredDeleteHooks[$columnName] = [$this, $fn];
168
+        } else if (is_callable($fn)) {
169
+            $this->registeredDeleteHooks[$columnName] = $fn;
170
+        } else {
171
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
172
+        }
173
+    }
174
+
175
+    /**
176
+     * Register a new hook for a specific column that gets called before execution of the search() method
177
+     * Only one hook per column can be registered at a time
178
+     * @param string $columnName The name of the column that is registered.
179
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. The callable is required to take one argument: an instance of miBadger\Query\Query; 
180
+     */
181
+    public function registerSearchHook($columnName, $fn)
182
+    {
183
+        $this->checkHookConstraints($columnName, $this->registeredSearchHooks);
184
+
185
+        if (is_string($fn) && is_callable([$this, $fn])) {
186
+            $this->registeredSearchHooks[$columnName] = [$this, $fn];
187
+        } else if (is_callable($fn)) {
188
+            $this->registeredSearchHooks[$columnName] = $fn;
189
+        } else {
190
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
191
+        }
192
+    }
193
+
194
+    /**
195
+     * Adds a new column definition to the table.
196
+     * @param string $columnName The name of the column that is registered.
197
+     * @param Array $definition The definition of that column.
198
+     */
199
+    public function extendTableDefinition($columnName, $definition)
200
+    {
201
+        if ($this->tableDefinition === null) {
202
+            throw new ActiveRecordException("tableDefinition is null, most likely due to parent class not having been initialized in constructor");
203
+        }
204
+
205
+        // Enforcing table can only be extended with new columns
206
+        if (array_key_exists($columnName, $this->tableDefinition)) {
207
+            $message = "Table is being extended with a column that already exists, ";
208
+            $message .= "\"$columnName\" conflicts with your table definition";
209
+            throw new ActiveRecordException($message, 0);
210
+        }
211
+
212
+        $this->tableDefinition[$columnName] = $definition;
213
+    }
214
+
215
+    /**
216
+     * Returns the type string as it should appear in the mysql create table statement for the given column
217
+     * @return string The type string
218
+     */
219
+    private function getDatabaseTypeString($colName, $type, $length)
220
+    {
221
+        switch (strtoupper($type)) {
222
+            case '':
223
+                throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
224 224
 			
225
-			case 'BOOL';
226
-			case 'BOOLEAN':
227
-			case 'DATETIME':
228
-			case 'DATE':
229
-			case 'TIME':
230
-			case 'TEXT':
231
-			case 'INT UNSIGNED':
232
-				return $type;
233
-
234
-			case 'VARCHAR':
235
-				if ($length === null) {
236
-					throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
237
-				} else {
238
-					return sprintf('%s(%d)', $type, $length);	
239
-				}
240
-
241
-			case 'INT':
242
-			case 'TINYINT':
243
-			case 'BIGINT':
244
-			default: 	
245
-				// Implicitly assuming that non-specified cases are correct without a length parameter
246
-				if ($length === null) {
247
-					return $type;
248
-				} else {
249
-					return sprintf('%s(%d)', $type, $length);	
250
-				}
251
-		}
252
-	}
253
-
254
-	/**
255
-	 * Builds the part of a MySQL create table statement that corresponds to the supplied column
256
-	 * @param string $colName 	Name of the database column
257
-	 * @param string $type 		The type of the string
258
-	 * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
259
-	 * @return string
260
-	 */
261
-	private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
262
-	{
263
-		$stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
264
-		if ($properties & ColumnProperty::NOT_NULL) {
265
-			$stmnt .= 'NOT NULL ';
266
-		} else {
267
-			$stmnt .= 'NULL ';
268
-		}
269
-
270
-		if ($default !== NULL) {
271
-			$stmnt .= ' DEFAULT ' . $default . ' ';
272
-		}
273
-
274
-		if ($properties & ColumnProperty::AUTO_INCREMENT) {
275
-			$stmnt .= 'AUTO_INCREMENT ';
276
-		}
277
-
278
-		if ($properties & ColumnProperty::UNIQUE) {
279
-			$stmnt .= 'UNIQUE ';
280
-		}
281
-
282
-		if ($properties & ColumnProperty::PRIMARY_KEY) {
283
-			$stmnt .= 'PRIMARY KEY ';
284
-		}
285
-
286
-		return $stmnt;
287
-	}
288
-
289
-	/**
290
-	 * Sorts the column statement components in the order such that the id appears first, 
291
-	 * 		followed by all other columns in alphabetical ascending order
292
-	 * @param   Array $colStatements Array of column statements
293
-	 * @return  Array
294
-	 */
295
-	private function sortColumnStatements($colStatements)
296
-	{
297
-		// Find ID statement and put it first
298
-		$sortedStatements = [];
299
-
300
-		$sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
301
-		unset($colStatements[self::COLUMN_NAME_ID]);
302
-
303
-		// Sort remaining columns in alphabetical order
304
-		$columns = array_keys($colStatements);
305
-		sort($columns);
306
-		foreach ($columns as $colName) {
307
-			$sortedStatements[] = $colStatements[$colName];
308
-		}
309
-
310
-		return $sortedStatements;
311
-	}
312
-
313
-	/**
314
-	 * Builds the MySQL Create Table statement for the internal table definition
315
-	 * @return string
316
-	 */
317
-	public function buildCreateTableSQL()
318
-	{
319
-		$columnStatements = [];
320
-		foreach ($this->tableDefinition as $colName => $definition) {
321
-			// Destructure column definition
322
-			$type    = $definition['type'] ?? null;
323
-			$default = $definition['default'] ?? null;
324
-			$length  = $definition['length'] ?? null;
325
-			$properties = $definition['properties'] ?? null;
326
-
327
-			if (isset($definition['relation']) && $type !== null) {
328
-				$tableName = $this->getTableName();
329
-				$msg = "Column \"$colName\" on table \"$tableName\": ";
330
-				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
331
-				throw new ActiveRecordException($msg);
332
-			} else if (isset($definition['relation'])) {
333
-				$type = self::COLUMN_TYPE_ID;
334
-			}
335
-
336
-			$columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
337
-		}
338
-
339
-		// Sort table (first column is id, the remaining are alphabetically sorted)
340
-		$columnStatements = $this->sortColumnStatements($columnStatements);
341
-
342
-		$sql = sprintf("CREATE TABLE %s (\n%s\n);", 
343
-			$this->getTableName(), 
344
-			implode(",\n", $columnStatements));
345
-
346
-		return $sql;
347
-	}
348
-
349
-	/**
350
-	 * Creates the entity as a table in the database
351
-	 */
352
-	public function createTable()
353
-	{
354
-		$this->pdo->query($this->buildCreateTableSQL());
355
-	}
356
-
357
-	/**
358
-	 * builds a MySQL constraint statement for the given parameters
359
-	 * @param string $parentTable
360
-	 * @param string $parentColumn
361
-	 * @param string $childTable
362
-	 * @param string $childColumn
363
-	 * @return string The MySQL table constraint string
364
-	 */
365
-	protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
366
-	{
367
-		$template = <<<SQL
225
+            case 'BOOL';
226
+            case 'BOOLEAN':
227
+            case 'DATETIME':
228
+            case 'DATE':
229
+            case 'TIME':
230
+            case 'TEXT':
231
+            case 'INT UNSIGNED':
232
+                return $type;
233
+
234
+            case 'VARCHAR':
235
+                if ($length === null) {
236
+                    throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
237
+                } else {
238
+                    return sprintf('%s(%d)', $type, $length);	
239
+                }
240
+
241
+            case 'INT':
242
+            case 'TINYINT':
243
+            case 'BIGINT':
244
+            default: 	
245
+                // Implicitly assuming that non-specified cases are correct without a length parameter
246
+                if ($length === null) {
247
+                    return $type;
248
+                } else {
249
+                    return sprintf('%s(%d)', $type, $length);	
250
+                }
251
+        }
252
+    }
253
+
254
+    /**
255
+     * Builds the part of a MySQL create table statement that corresponds to the supplied column
256
+     * @param string $colName 	Name of the database column
257
+     * @param string $type 		The type of the string
258
+     * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
259
+     * @return string
260
+     */
261
+    private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
262
+    {
263
+        $stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
264
+        if ($properties & ColumnProperty::NOT_NULL) {
265
+            $stmnt .= 'NOT NULL ';
266
+        } else {
267
+            $stmnt .= 'NULL ';
268
+        }
269
+
270
+        if ($default !== NULL) {
271
+            $stmnt .= ' DEFAULT ' . $default . ' ';
272
+        }
273
+
274
+        if ($properties & ColumnProperty::AUTO_INCREMENT) {
275
+            $stmnt .= 'AUTO_INCREMENT ';
276
+        }
277
+
278
+        if ($properties & ColumnProperty::UNIQUE) {
279
+            $stmnt .= 'UNIQUE ';
280
+        }
281
+
282
+        if ($properties & ColumnProperty::PRIMARY_KEY) {
283
+            $stmnt .= 'PRIMARY KEY ';
284
+        }
285
+
286
+        return $stmnt;
287
+    }
288
+
289
+    /**
290
+     * Sorts the column statement components in the order such that the id appears first, 
291
+     * 		followed by all other columns in alphabetical ascending order
292
+     * @param   Array $colStatements Array of column statements
293
+     * @return  Array
294
+     */
295
+    private function sortColumnStatements($colStatements)
296
+    {
297
+        // Find ID statement and put it first
298
+        $sortedStatements = [];
299
+
300
+        $sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
301
+        unset($colStatements[self::COLUMN_NAME_ID]);
302
+
303
+        // Sort remaining columns in alphabetical order
304
+        $columns = array_keys($colStatements);
305
+        sort($columns);
306
+        foreach ($columns as $colName) {
307
+            $sortedStatements[] = $colStatements[$colName];
308
+        }
309
+
310
+        return $sortedStatements;
311
+    }
312
+
313
+    /**
314
+     * Builds the MySQL Create Table statement for the internal table definition
315
+     * @return string
316
+     */
317
+    public function buildCreateTableSQL()
318
+    {
319
+        $columnStatements = [];
320
+        foreach ($this->tableDefinition as $colName => $definition) {
321
+            // Destructure column definition
322
+            $type    = $definition['type'] ?? null;
323
+            $default = $definition['default'] ?? null;
324
+            $length  = $definition['length'] ?? null;
325
+            $properties = $definition['properties'] ?? null;
326
+
327
+            if (isset($definition['relation']) && $type !== null) {
328
+                $tableName = $this->getTableName();
329
+                $msg = "Column \"$colName\" on table \"$tableName\": ";
330
+                $msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
331
+                throw new ActiveRecordException($msg);
332
+            } else if (isset($definition['relation'])) {
333
+                $type = self::COLUMN_TYPE_ID;
334
+            }
335
+
336
+            $columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
337
+        }
338
+
339
+        // Sort table (first column is id, the remaining are alphabetically sorted)
340
+        $columnStatements = $this->sortColumnStatements($columnStatements);
341
+
342
+        $sql = sprintf("CREATE TABLE %s (\n%s\n);", 
343
+            $this->getTableName(), 
344
+            implode(",\n", $columnStatements));
345
+
346
+        return $sql;
347
+    }
348
+
349
+    /**
350
+     * Creates the entity as a table in the database
351
+     */
352
+    public function createTable()
353
+    {
354
+        $this->pdo->query($this->buildCreateTableSQL());
355
+    }
356
+
357
+    /**
358
+     * builds a MySQL constraint statement for the given parameters
359
+     * @param string $parentTable
360
+     * @param string $parentColumn
361
+     * @param string $childTable
362
+     * @param string $childColumn
363
+     * @return string The MySQL table constraint string
364
+     */
365
+    protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
366
+    {
367
+        $template = <<<SQL
368 368
 ALTER TABLE `%s`
369 369
 ADD CONSTRAINT
370 370
 FOREIGN KEY (`%s`)
371 371
 REFERENCES `%s`(`%s`)
372 372
 ON DELETE CASCADE;
373 373
 SQL;
374
-		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
375
-	}
376
-
377
-	/**
378
-	 * Iterates over the specified constraints in the table definition, 
379
-	 * 		and applies these to the database.
380
-	 */
381
-	public function createTableConstraints()
382
-	{
383
-		// Iterate over columns, check whether "relation" field exists, if so create constraint
384
-		foreach ($this->tableDefinition as $colName => $definition) {
385
-			if (isset($definition['relation']) && $definition['relation'] instanceof AbstractActiveRecord) {
386
-				// Forge new relation
387
-				$target = $definition['relation'];
388
-				$constraintSql = $this->buildConstraint($target->getTableName(), 'id', $this->getTableName(), $colName);
389
-
390
-				$this->pdo->query($constraintSql);
391
-			} else if (isset($definition['relation'])) {
392
-				$msg = sprintf("Relation constraint on column \"%s\" of table \"%s\" does not contain a valid ActiveRecord instance", 
393
-					$colName,
394
-					$this->getTableName());
395
-				throw new ActiveRecordException($msg);
396
-			}
397
-		}
398
-	}
399
-
400
-	/**
401
-	 * Returns the name -> variable mapping for the table definition.
402
-	 * @return Array The mapping
403
-	 */
404
-	protected function getActiveRecordColumns()
405
-	{
406
-		$bindings = [];
407
-		foreach ($this->tableDefinition as $colName => $definition) {
408
-
409
-			// Ignore the id column (key) when inserting or updating
410
-			if ($colName == self::COLUMN_NAME_ID) {
411
-				continue;
412
-			}
413
-
414
-			$bindings[$colName] = &$definition['value'];
415
-		}
416
-		return $bindings;
417
-	}
418
-
419
-	/**
420
-	 * {@inheritdoc}
421
-	 */
422
-	public function create()
423
-	{
424
-		foreach ($this->registeredCreateHooks as $colName => $fn) {
425
-			$fn();
426
-		}
427
-
428
-		try {
429
-			(new Query($this->getPdo(), $this->getTableName()))
430
-				->insert($this->getActiveRecordColumns())
431
-				->execute();
432
-
433
-			$this->setId(intval($this->getPdo()->lastInsertId()));
434
-		} catch (\PDOException $e) {
435
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
436
-		}
437
-
438
-		return $this;
439
-	}
440
-
441
-	/**
442
-	 * {@inheritdoc}
443
-	 */
444
-	public function read($id)
445
-	{
446
-		foreach ($this->registeredReadHooks as $colName => $fn) {
447
-			$fn();
448
-		}
449
-
450
-		try {
451
-			$row = (new Query($this->getPdo(), $this->getTableName()))
452
-				->select()
453
-				->where(Query::Equal('id', $id))
454
-				->execute()
455
-				->fetch();
456
-
457
-			if ($row === false) {
458
-				throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getTableName()));
459
-			}
460
-
461
-			$this->fill($row)->setId($id);
462
-		} catch (\PDOException $e) {
463
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
464
-		}
465
-
466
-		return $this;
467
-	}
468
-
469
-	/**
470
-	 * {@inheritdoc}
471
-	 */
472
-	public function update()
473
-	{
474
-		foreach ($this->registeredUpdateHooks as $colName => $fn) {
475
-			$fn();
476
-		}
477
-
478
-		try {
479
-			(new Query($this->getPdo(), $this->getTableName()))
480
-				->update($this->getActiveRecordColumns())
481
-				->where(Query::Equal('id', $this->getId()))
482
-				->execute();
483
-		} catch (\PDOException $e) {
484
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
485
-		}
486
-
487
-		return $this;
488
-	}
489
-
490
-	/**
491
-	 * {@inheritdoc}
492
-	 */
493
-	public function delete()
494
-	{
495
-		foreach ($this->registeredDeleteHooks as $colName => $fn) {
496
-			$fn();
497
-		}
498
-
499
-		try {
500
-			(new Query($this->getPdo(), $this->getTableName()))
501
-				->delete()
502
-				->where(Query::Equal('id', $this->getId()))
503
-				->execute();
504
-
505
-			$this->setId(null);
506
-		} catch (\PDOException $e) {
507
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
508
-		}
509
-
510
-		return $this;
511
-	}
512
-
513
-	/**
514
-	 * {@inheritdoc}
515
-	 */
516
-	public function sync()
517
-	{
518
-		if (!$this->exists()) {
519
-			return $this->create();
520
-		}
521
-
522
-		return $this->update();
523
-	}
524
-
525
-	/**
526
-	 * {@inheritdoc}
527
-	 */
528
-	public function exists()
529
-	{
530
-		return $this->getId() !== null;
531
-	}
532
-
533
-	/**
534
-	 * {@inheritdoc}
535
-	 */
536
-	public function fill(array $attributes)
537
-	{
538
-		$columns = $this->getActiveRecordColumns();
539
-		$columns['id'] = &$this->id;
540
-
541
-		foreach ($attributes as $key => $value) {
542
-			if (array_key_exists($key, $columns)) {
543
-				$columns[$key] = $value;
544
-			}
545
-		}
546
-
547
-		return $this;
548
-	}
549
-
550
-	/**
551
-	 * {@inheritdoc}
552
-	 */
553
-	public function search(array $ignoredTraits = [])
554
-	{
555
-		$clauses = [];
556
-		foreach ($this->registeredSearchHooks as $column => $fn) {
557
-			if (!in_array($column, $ignoredTraits)) {
558
-				$clauses[] = $fn();
559
-			}
560
-		}
561
-
562
-		return new ActiveRecordQuery($this, $this->getTableName(), $clauses);
563
-	}
564
-
565
-	/**
566
-	 * Returns the PDO.
567
-	 *
568
-	 * @return \PDO the PDO.
569
-	 */
570
-	public function getPdo()
571
-	{
572
-		return $this->pdo;
573
-	}
574
-
575
-	/**
576
-	 * Set the PDO.
577
-	 *
578
-	 * @param \PDO $pdo
579
-	 * @return $this
580
-	 */
581
-	protected function setPdo($pdo)
582
-	{
583
-		$this->pdo = $pdo;
584
-
585
-		return $this;
586
-	}
587
-
588
-	/**
589
-	 * Returns the ID.
590
-	 *
591
-	 * @return null|int The ID.
592
-	 */
593
-	public function getId()
594
-	{
595
-		return $this->id;
596
-	}
597
-
598
-	/**
599
-	 * Set the ID.
600
-	 *
601
-	 * @param int $id
602
-	 * @return $this
603
-	 */
604
-	protected function setId($id)
605
-	{
606
-		$this->id = $id;
607
-
608
-		return $this;
609
-	}
610
-
611
-
612
-	public function newInstance()
613
-	{
614
-		return new static($this->pdo);
615
-	}
616
-
617
-	/**
618
-	 * Returns the active record table.
619
-	 *
620
-	 * @return string the active record table name.
621
-	 */
622
-	abstract protected function getTableName();
623
-
624
-	/**
625
-	 * Returns the active record columns.
626
-	 *
627
-	 * @return array the active record columns.
628
-	 */
629
-	abstract protected function getTableDefinition();
374
+        return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
375
+    }
376
+
377
+    /**
378
+     * Iterates over the specified constraints in the table definition, 
379
+     * 		and applies these to the database.
380
+     */
381
+    public function createTableConstraints()
382
+    {
383
+        // Iterate over columns, check whether "relation" field exists, if so create constraint
384
+        foreach ($this->tableDefinition as $colName => $definition) {
385
+            if (isset($definition['relation']) && $definition['relation'] instanceof AbstractActiveRecord) {
386
+                // Forge new relation
387
+                $target = $definition['relation'];
388
+                $constraintSql = $this->buildConstraint($target->getTableName(), 'id', $this->getTableName(), $colName);
389
+
390
+                $this->pdo->query($constraintSql);
391
+            } else if (isset($definition['relation'])) {
392
+                $msg = sprintf("Relation constraint on column \"%s\" of table \"%s\" does not contain a valid ActiveRecord instance", 
393
+                    $colName,
394
+                    $this->getTableName());
395
+                throw new ActiveRecordException($msg);
396
+            }
397
+        }
398
+    }
399
+
400
+    /**
401
+     * Returns the name -> variable mapping for the table definition.
402
+     * @return Array The mapping
403
+     */
404
+    protected function getActiveRecordColumns()
405
+    {
406
+        $bindings = [];
407
+        foreach ($this->tableDefinition as $colName => $definition) {
408
+
409
+            // Ignore the id column (key) when inserting or updating
410
+            if ($colName == self::COLUMN_NAME_ID) {
411
+                continue;
412
+            }
413
+
414
+            $bindings[$colName] = &$definition['value'];
415
+        }
416
+        return $bindings;
417
+    }
418
+
419
+    /**
420
+     * {@inheritdoc}
421
+     */
422
+    public function create()
423
+    {
424
+        foreach ($this->registeredCreateHooks as $colName => $fn) {
425
+            $fn();
426
+        }
427
+
428
+        try {
429
+            (new Query($this->getPdo(), $this->getTableName()))
430
+                ->insert($this->getActiveRecordColumns())
431
+                ->execute();
432
+
433
+            $this->setId(intval($this->getPdo()->lastInsertId()));
434
+        } catch (\PDOException $e) {
435
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
436
+        }
437
+
438
+        return $this;
439
+    }
440
+
441
+    /**
442
+     * {@inheritdoc}
443
+     */
444
+    public function read($id)
445
+    {
446
+        foreach ($this->registeredReadHooks as $colName => $fn) {
447
+            $fn();
448
+        }
449
+
450
+        try {
451
+            $row = (new Query($this->getPdo(), $this->getTableName()))
452
+                ->select()
453
+                ->where(Query::Equal('id', $id))
454
+                ->execute()
455
+                ->fetch();
456
+
457
+            if ($row === false) {
458
+                throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getTableName()));
459
+            }
460
+
461
+            $this->fill($row)->setId($id);
462
+        } catch (\PDOException $e) {
463
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
464
+        }
465
+
466
+        return $this;
467
+    }
468
+
469
+    /**
470
+     * {@inheritdoc}
471
+     */
472
+    public function update()
473
+    {
474
+        foreach ($this->registeredUpdateHooks as $colName => $fn) {
475
+            $fn();
476
+        }
477
+
478
+        try {
479
+            (new Query($this->getPdo(), $this->getTableName()))
480
+                ->update($this->getActiveRecordColumns())
481
+                ->where(Query::Equal('id', $this->getId()))
482
+                ->execute();
483
+        } catch (\PDOException $e) {
484
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
485
+        }
486
+
487
+        return $this;
488
+    }
489
+
490
+    /**
491
+     * {@inheritdoc}
492
+     */
493
+    public function delete()
494
+    {
495
+        foreach ($this->registeredDeleteHooks as $colName => $fn) {
496
+            $fn();
497
+        }
498
+
499
+        try {
500
+            (new Query($this->getPdo(), $this->getTableName()))
501
+                ->delete()
502
+                ->where(Query::Equal('id', $this->getId()))
503
+                ->execute();
504
+
505
+            $this->setId(null);
506
+        } catch (\PDOException $e) {
507
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
508
+        }
509
+
510
+        return $this;
511
+    }
512
+
513
+    /**
514
+     * {@inheritdoc}
515
+     */
516
+    public function sync()
517
+    {
518
+        if (!$this->exists()) {
519
+            return $this->create();
520
+        }
521
+
522
+        return $this->update();
523
+    }
524
+
525
+    /**
526
+     * {@inheritdoc}
527
+     */
528
+    public function exists()
529
+    {
530
+        return $this->getId() !== null;
531
+    }
532
+
533
+    /**
534
+     * {@inheritdoc}
535
+     */
536
+    public function fill(array $attributes)
537
+    {
538
+        $columns = $this->getActiveRecordColumns();
539
+        $columns['id'] = &$this->id;
540
+
541
+        foreach ($attributes as $key => $value) {
542
+            if (array_key_exists($key, $columns)) {
543
+                $columns[$key] = $value;
544
+            }
545
+        }
546
+
547
+        return $this;
548
+    }
549
+
550
+    /**
551
+     * {@inheritdoc}
552
+     */
553
+    public function search(array $ignoredTraits = [])
554
+    {
555
+        $clauses = [];
556
+        foreach ($this->registeredSearchHooks as $column => $fn) {
557
+            if (!in_array($column, $ignoredTraits)) {
558
+                $clauses[] = $fn();
559
+            }
560
+        }
561
+
562
+        return new ActiveRecordQuery($this, $this->getTableName(), $clauses);
563
+    }
564
+
565
+    /**
566
+     * Returns the PDO.
567
+     *
568
+     * @return \PDO the PDO.
569
+     */
570
+    public function getPdo()
571
+    {
572
+        return $this->pdo;
573
+    }
574
+
575
+    /**
576
+     * Set the PDO.
577
+     *
578
+     * @param \PDO $pdo
579
+     * @return $this
580
+     */
581
+    protected function setPdo($pdo)
582
+    {
583
+        $this->pdo = $pdo;
584
+
585
+        return $this;
586
+    }
587
+
588
+    /**
589
+     * Returns the ID.
590
+     *
591
+     * @return null|int The ID.
592
+     */
593
+    public function getId()
594
+    {
595
+        return $this->id;
596
+    }
597
+
598
+    /**
599
+     * Set the ID.
600
+     *
601
+     * @param int $id
602
+     * @return $this
603
+     */
604
+    protected function setId($id)
605
+    {
606
+        $this->id = $id;
607
+
608
+        return $this;
609
+    }
610
+
611
+
612
+    public function newInstance()
613
+    {
614
+        return new static($this->pdo);
615
+    }
616
+
617
+    /**
618
+     * Returns the active record table.
619
+     *
620
+     * @return string the active record table name.
621
+     */
622
+    abstract protected function getTableName();
623
+
624
+    /**
625
+     * Returns the active record columns.
626
+     *
627
+     * @return array the active record columns.
628
+     */
629
+    abstract protected function getTableDefinition();
630 630
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -268,7 +268,7 @@
 block discarded – undo
268 268
 		}
269 269
 
270 270
 		if ($default !== NULL) {
271
-			$stmnt .= ' DEFAULT ' . $default . ' ';
271
+			$stmnt .= ' DEFAULT '.$default.' ';
272 272
 		}
273 273
 
274 274
 		if ($properties & ColumnProperty::AUTO_INCREMENT) {
Please login to merge, or discard this patch.
src/ActiveRecordQuery.php 1 patch
Indentation   +181 added lines, -181 removed lines patch added patch discarded remove patch
@@ -21,190 +21,190 @@
 block discarded – undo
21 21
 class ActiveRecordQuery implements \IteratorAggregate
22 22
 {
23 23
 
24
-	private $clauses = [];
24
+    private $clauses = [];
25 25
 
26
-	private $query;
26
+    private $query;
27 27
 
28
-	private $results;
28
+    private $results;
29 29
 
30
-	private $table;
30
+    private $table;
31 31
 
32
-	private $type;
32
+    private $type;
33 33
 	
34
-	private $whereExpression = null;
35
-
36
-	/**
37
-	 * Constructs a new Active Record Query
38
-	 */
39
-	public function __construct(AbstractActiveRecord $instance, $table, Array $additionalWhereClauses)
40
-	{
41
-		$this->table = $table;
42
-		$this->query = new Query($instance->getPdo(), $table);
43
-		$this->type = $instance;
44
-		$this->clauses = $additionalWhereClauses;
45
-		$this->results = null;
46
-	}
47
-
48
-	/**
49
-	 * Executes the query
50
-	 */
51
-	private function execute()
52
-	{
53
-		$clauses = $this->clauses;
54
-
55
-		// Optionally add user concatenated where expression
56
-		if ($this->whereExpression !== null)
57
-		{
58
-			$clauses[] = $this->whereExpression;
59
-		}
60
-
61
-		// Construct where clause
62
-		if (count($clauses) == 1)
63
-		{
64
-			$this->query->where($clauses[0]);
65
-		} else if (count($clauses) >= 2)
66
-		{
67
-			$rest = array_slice($clauses, 1);
68
-			$this->query->where(Query::And($clauses[0], ...$rest));
69
-		}
70
-
71
-		$this->query->select();
72
-
73
-		$this->results = $this->query->execute();
74
-
75
-		return $this;
76
-	}
77
-
78
-	/**
79
-	 * Returns an iterator for the result set
80
-	 * @return ArrayIterator
81
-	 */
82
-	public function getIterator()
83
-	{
84
-		return new \ArrayIterator($this->fetchAll());
85
-	}
86
-
87
-	/**
88
-	 * returns the result set of ActiveRecord instances for this query
89
-	 * @return Array
90
-	 */
91
-	public function fetchAll()
92
-	{
93
-		try {
94
-			if ($this->results === null) {
95
-				$this->execute();	
96
-			}
97
-
98
-			$entries = $this->results->fetchAll();
99
-			if ($entries === false) {
100
-				throw new ActiveRecordException(sprintf('Can not search non-existent entries from the `%s` table.', $this->table));
101
-			}
102
-
103
-			$typedResults = [];
104
-
105
-			foreach ($entries as $entry) {
106
-				$typedEntry = $this->type->newInstance();
107
-				$typedEntry->fill($entry);
108
-				$typedResults[] = $typedEntry;
109
-			}
110
-
111
-			return $typedResults;
112
-		} catch (\PDOException $e) {
113
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
114
-		}
115
-	}
116
-
117
-	/**
118
-	 * Fetch one record from the database
119
-	 * @return AbstractActiveRecord 
120
-	 */
121
-	public function fetch()
122
-	{
123
-		try {
124
-			if ($this->results === null) 
125
-			{
126
-				$this->execute();
127
-			}
128
-
129
-			$typedResult = $this->type->newInstance();
130
-
131
-			$entry = $this->results->fetch();
132
-			if ($entry === false) {
133
-				throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->table));
134
-			}
135
-
136
-			$typedResult->fill($entry);
137
-
138
-			return $typedResult;
139
-		} catch (\PDOException $e) {
140
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
141
-		}
142
-	}
143
-
144
-
145
-	/**
146
-	 * Set the where condition
147
-	 *
148
-	 * @param QueryExpression $expression the query expression
149
-	 * @return $this
150
-	 * @see https://en.wikipedia.org/wiki/SQL#Operators
151
-	 * @see https://en.wikipedia.org/wiki/Where_(SQL)
152
-	 */
153
-	public function where(QueryExpression $expression)
154
-	{
155
-		$this->whereExpression = $expression;
156
-		return $this;
157
-	}
158
-
159
-	/**
160
-	 * Set an additional group by.
161
-	 *
162
-	 * @param string $column
163
-	 * @return $this
164
-	 * @see https://en.wikipedia.org/wiki/SQL#Queries
165
-	 */
166
-	public function groupBy($column)
167
-	{
168
-		$this->query->groupBy($column);
169
-		return $this;
170
-	}
171
-
172
-	/**
173
-	 * Set an additional order condition.
174
-	 *
175
-	 * @param string $column
176
-	 * @param string|null $order
177
-	 * @return $this
178
-	 * @see https://en.wikipedia.org/wiki/SQL#Queries
179
-	 * @see https://en.wikipedia.org/wiki/Order_by
180
-	 */
181
-	public function orderBy($column, $order = null)
182
-	{
183
-		$this->query->orderBy($column, $order);	
184
-		return $this;
185
-	}
186
-
187
-	/**
188
-	 * Set the limit.
189
-	 *
190
-	 * @param mixed $limit
191
-	 * @return $this
192
-	 */
193
-	public function limit($limit)
194
-	{
195
-		$this->query->limit($limit);
196
-		return $this;
197
-	}
198
-
199
-	/**
200
-	 * Set the offset.
201
-	 *
202
-	 * @param mixed $offset
203
- 	 * @return $this
204
-	 */
205
-	public function offset($offset)
206
-	{
207
-		$this->query->offset($offset);
208
-		return $this;
209
-	}
34
+    private $whereExpression = null;
35
+
36
+    /**
37
+     * Constructs a new Active Record Query
38
+     */
39
+    public function __construct(AbstractActiveRecord $instance, $table, Array $additionalWhereClauses)
40
+    {
41
+        $this->table = $table;
42
+        $this->query = new Query($instance->getPdo(), $table);
43
+        $this->type = $instance;
44
+        $this->clauses = $additionalWhereClauses;
45
+        $this->results = null;
46
+    }
47
+
48
+    /**
49
+     * Executes the query
50
+     */
51
+    private function execute()
52
+    {
53
+        $clauses = $this->clauses;
54
+
55
+        // Optionally add user concatenated where expression
56
+        if ($this->whereExpression !== null)
57
+        {
58
+            $clauses[] = $this->whereExpression;
59
+        }
60
+
61
+        // Construct where clause
62
+        if (count($clauses) == 1)
63
+        {
64
+            $this->query->where($clauses[0]);
65
+        } else if (count($clauses) >= 2)
66
+        {
67
+            $rest = array_slice($clauses, 1);
68
+            $this->query->where(Query::And($clauses[0], ...$rest));
69
+        }
70
+
71
+        $this->query->select();
72
+
73
+        $this->results = $this->query->execute();
74
+
75
+        return $this;
76
+    }
77
+
78
+    /**
79
+     * Returns an iterator for the result set
80
+     * @return ArrayIterator
81
+     */
82
+    public function getIterator()
83
+    {
84
+        return new \ArrayIterator($this->fetchAll());
85
+    }
86
+
87
+    /**
88
+     * returns the result set of ActiveRecord instances for this query
89
+     * @return Array
90
+     */
91
+    public function fetchAll()
92
+    {
93
+        try {
94
+            if ($this->results === null) {
95
+                $this->execute();	
96
+            }
97
+
98
+            $entries = $this->results->fetchAll();
99
+            if ($entries === false) {
100
+                throw new ActiveRecordException(sprintf('Can not search non-existent entries from the `%s` table.', $this->table));
101
+            }
102
+
103
+            $typedResults = [];
104
+
105
+            foreach ($entries as $entry) {
106
+                $typedEntry = $this->type->newInstance();
107
+                $typedEntry->fill($entry);
108
+                $typedResults[] = $typedEntry;
109
+            }
110
+
111
+            return $typedResults;
112
+        } catch (\PDOException $e) {
113
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
114
+        }
115
+    }
116
+
117
+    /**
118
+     * Fetch one record from the database
119
+     * @return AbstractActiveRecord 
120
+     */
121
+    public function fetch()
122
+    {
123
+        try {
124
+            if ($this->results === null) 
125
+            {
126
+                $this->execute();
127
+            }
128
+
129
+            $typedResult = $this->type->newInstance();
130
+
131
+            $entry = $this->results->fetch();
132
+            if ($entry === false) {
133
+                throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->table));
134
+            }
135
+
136
+            $typedResult->fill($entry);
137
+
138
+            return $typedResult;
139
+        } catch (\PDOException $e) {
140
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
141
+        }
142
+    }
143
+
144
+
145
+    /**
146
+     * Set the where condition
147
+     *
148
+     * @param QueryExpression $expression the query expression
149
+     * @return $this
150
+     * @see https://en.wikipedia.org/wiki/SQL#Operators
151
+     * @see https://en.wikipedia.org/wiki/Where_(SQL)
152
+     */
153
+    public function where(QueryExpression $expression)
154
+    {
155
+        $this->whereExpression = $expression;
156
+        return $this;
157
+    }
158
+
159
+    /**
160
+     * Set an additional group by.
161
+     *
162
+     * @param string $column
163
+     * @return $this
164
+     * @see https://en.wikipedia.org/wiki/SQL#Queries
165
+     */
166
+    public function groupBy($column)
167
+    {
168
+        $this->query->groupBy($column);
169
+        return $this;
170
+    }
171
+
172
+    /**
173
+     * Set an additional order condition.
174
+     *
175
+     * @param string $column
176
+     * @param string|null $order
177
+     * @return $this
178
+     * @see https://en.wikipedia.org/wiki/SQL#Queries
179
+     * @see https://en.wikipedia.org/wiki/Order_by
180
+     */
181
+    public function orderBy($column, $order = null)
182
+    {
183
+        $this->query->orderBy($column, $order);	
184
+        return $this;
185
+    }
186
+
187
+    /**
188
+     * Set the limit.
189
+     *
190
+     * @param mixed $limit
191
+     * @return $this
192
+     */
193
+    public function limit($limit)
194
+    {
195
+        $this->query->limit($limit);
196
+        return $this;
197
+    }
198
+
199
+    /**
200
+     * Set the offset.
201
+     *
202
+     * @param mixed $offset
203
+     * @return $this
204
+     */
205
+    public function offset($offset)
206
+    {
207
+        $this->query->offset($offset);
208
+        return $this;
209
+    }
210 210
 }
Please login to merge, or discard this patch.