Passed
Push — v2 ( 67b65f...36f4b4 )
by Berend
03:00
created
src/Traits/AutoApi.php 2 patches
Doc Comments   +11 added lines patch added patch discarded remove patch
@@ -44,6 +44,8 @@  discard block
 block discarded – undo
44 44
 
45 45
 	/**
46 46
 	 * Copy all table variables between two instances
47
+	 * @param AutoApi $to
48
+	 * @param AutoApi $from
47 49
 	 */
48 50
 	private function syncInstances($to, $from)
49 51
 	{
@@ -272,10 +274,19 @@  discard block
 block discarded – undo
272 274
 
273 275
 	abstract public function read($id);
274 276
 
277
+	/**
278
+	 * @return \PDO
279
+	 */
275 280
 	abstract public function getPdo();
276 281
 
282
+	/**
283
+	 * @param integer $id
284
+	 */
277 285
 	abstract function setId($id);
278 286
 
287
+	/**
288
+	 * @return string
289
+	 */
279 290
 	abstract function getActiveRecordTable();
280 291
 
281 292
 	abstract function getActiveRecordColumns();
Please login to merge, or discard this patch.
Indentation   +262 added lines, -262 removed lines patch added patch discarded remove patch
@@ -8,275 +8,275 @@
 block discarded – undo
8 8
 
9 9
 trait AutoApi
10 10
 {
11
-	/* =======================================================================
11
+    /* =======================================================================
12 12
 	 * ===================== Automatic API Support ===========================
13 13
 	 * ======================================================================= */
14 14
 
15
-	public function apiSearch($inputs, $fieldWhitelist)
16
-	{
17
-		// @TODO: How to handle this case?
18
-		// => Default parameter names for searching? (limit, pagination, sort order etc)
19
-		//		Find default names for this and store in class
20
-		// => Limited search parameters? (We don't want to be able to search on a password field for example)
21
-	}
22
-
23
-	public function toArray($fieldWhitelist)
24
-	{
25
-		$output = [];
26
-		foreach ($this->tableDefinition as $colName => $definition) {
27
-			if (in_array($colName, $fieldWhitelist)) {
28
-				$output[$colName] = $definition['value'];
29
-			}
30
-		}
31
-
32
-		return $output;
33
-	}
34
-
35
-	public function apiRead($id, $fieldWhitelist)
36
-	{
37
-		$this->read($id);
38
-		return $this->toArray($fieldWhitelist);
39
-	}
40
-
41
-	/* =============================================================
15
+    public function apiSearch($inputs, $fieldWhitelist)
16
+    {
17
+        // @TODO: How to handle this case?
18
+        // => Default parameter names for searching? (limit, pagination, sort order etc)
19
+        //		Find default names for this and store in class
20
+        // => Limited search parameters? (We don't want to be able to search on a password field for example)
21
+    }
22
+
23
+    public function toArray($fieldWhitelist)
24
+    {
25
+        $output = [];
26
+        foreach ($this->tableDefinition as $colName => $definition) {
27
+            if (in_array($colName, $fieldWhitelist)) {
28
+                $output[$colName] = $definition['value'];
29
+            }
30
+        }
31
+
32
+        return $output;
33
+    }
34
+
35
+    public function apiRead($id, $fieldWhitelist)
36
+    {
37
+        $this->read($id);
38
+        return $this->toArray($fieldWhitelist);
39
+    }
40
+
41
+    /* =============================================================
42 42
 	 * ===================== Constraint validation =================
43 43
 	 * ============================================================= */
44 44
 
45
-	/**
46
-	 * Copy all table variables between two instances
47
-	 */
48
-	private function syncInstances($to, $from)
49
-	{
50
-		foreach ($to->tableDefinition as $colName => $definition) {
51
-			$definition['value'] = $from->tableDefinition[$colName]['value'];
52
-		}
53
-	}
54
-
55
-	private function filterInputColumns($input, $whitelist)
56
-	{
57
-		$filteredInput = $input;
58
-		foreach ($input as $colName => $value) {
59
-			if (!in_array($colName, $whitelist)) {
60
-				unset($filteredInput[$colName]);
61
-			}
62
-		}
63
-		return $filteredInput;
64
-	}
65
-
66
-	private function validateExcessKeys($input)
67
-	{
68
-		$errors = [];
69
-		foreach ($input as $colName => $value) {
70
-			if (!array_key_exists($colName, $this->tableDefinition)) {
71
-				$errors[$colName] = "Unknown input field";
72
-				continue;
73
-			}
74
-		}
75
-		return $errors;
76
-	}
77
-
78
-	private function validateImmutableColumns($input)
79
-	{
80
-		$errors = [];
81
-		foreach ($this->tableDefinition as $colName => $definition) {
82
-			$property = $definition['properties'] ?? null;
83
-			if (array_key_exists($colName, $input)
84
-				&& $property & ColumnProperty::IMMUTABLE) {
85
-				$errors[$colName] = "Field cannot be changed";
86
-			}
87
-		}
88
-		return $errors;
89
-	}
90
-
91
-	private function validateInputValues($input)
92
-	{
93
-		$errors = [];
94
-		foreach ($this->tableDefinition as $colName => $definition) {
95
-			// Validation check 1: If validate function is present
96
-			if (array_key_exists($colName, $input) 
97
-				&& is_callable($definition['validate'] ?? null)) {
98
-				$inputValue = $input[$colName];
99
-
100
-				// If validation function fails
101
-				[$status, $message] = $definition['validate']($inputValue);
102
-				if (!$status) {
103
-					$errors[$colName] = $message;
104
-				}	
105
-			}
106
-
107
-			// Validation check 2: If relation column, check whether entity exists
108
-			$properties = $definition['properties'] ?? null;
109
-			if (isset($definition['relation'])
110
-				&& ($properties & ColumnProperty::NOT_NULL)) {
111
-				$instance = clone $definition['relation'];
112
-				try {
113
-					$instance->read($input[$colName] ?? null);
114
-				} catch (ActiveRecordException $e) {
115
-					$errors[$colName] = "Entity for this value doesn't exist";
116
-				}
117
-			}
118
-		}
119
-		return $errors;
120
-	}
121
-
122
-	/**
123
-	 * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
124
-	 */
125
-	private function validateMissingKeys()
126
-	{
127
-		$errors = [];
128
-
129
-		foreach ($this->tableDefinition as $colName => $colDefinition) {
130
-			$default = $colDefinition['default'] ?? null;
131
-			$properties = $colDefinition['properties'] ?? null;
132
-			$value = $colDefinition['value'];
133
-
134
-			// If nullable and default not set => null
135
-			// If nullable and default null => default (null)
136
-			// If nullable and default set => default (value)
137
-
138
-			// if not nullable and default not set => error
139
-			// if not nullable and default null => error
140
-			// if not nullable and default st => default (value)
141
-			// => if not nullable and default null and value not set => error message in this method
142
-			if ($properties & ColumnProperty::NOT_NULL
143
-				&& $default === null
144
-				&& !($properties & ColumnProperty::AUTO_INCREMENT)
145
-				// && !array_key_exists($colName, $input)
146
-				&& $value === null) {
147
-				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
148
-			}
149
-		}
150
-
151
-		return $errors;
152
-	}
153
-
154
-	/**
155
-	 * Copies the values for entries in the input with matching variable names in the record definition
156
-	 * @param Array $input The input data to be loaded into $this record
157
-	 */
158
-	private function loadData($input)
159
-	{
160
-		foreach ($this->tableDefinition as $colName => $definition) {
161
-			if (array_key_exists($colName, $input)) {
162
-				$definition['value'] = $input[$colName];
163
-			}
164
-		}
165
-	}
166
-
167
-	/**
168
-	 * @param Array $input Associative array of input values
169
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
170
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
171
-	 * 					of the modified data.
172
-	 */
173
-	public function apiCreate($input, $fieldWhitelist)
174
-	{
175
-		// Clone $this to new instance (for restoring if validation goes wrong)
176
-		$transaction = clone $this;
177
-		$errors = [];
178
-
179
-		// Filter out all non-whitelisted input values
180
-		$input = $this->filterInputColumns($input, $fieldWhitelist);
181
-
182
-		// Validate excess keys
183
-		$errors += $transaction->validateExcessKeys($input);
184
-
185
-		// Validate input values (using validation function)
186
-		$errors += $transaction->validateInputValues($input);
187
-
188
-		// "Copy" data into transaction
189
-		$transaction->loadData($input);
190
-
191
-		// Run create hooks
192
-		foreach ($this->registeredCreateHooks as $colName => $fn) {
193
-			$fn();
194
-		}
195
-
196
-		// Validate missing keys
197
-		$errors += $transaction->validateMissingKeys();
198
-
199
-		// If no errors, commit the pending data
200
-		if (empty($errors)) {
201
-			$this->syncInstances($this, $transaction);
202
-
203
-			try {
204
-				$q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
205
-					->insert($this->getActiveRecordColumns())
206
-					->execute();
207
-
208
-				$this->setId(intval($this->getPdo()->lastInsertId()));
209
-			} catch (\PDOException $e) {
210
-				// @TODO: Potentially filter and store mysql messages (where possible) in error messages
211
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
212
-			}
213
-
214
-			return [null, $this->toArray($fieldWhitelist)];
215
-		} else {
216
-			return [$errors, null];
217
-		}
218
-	}
219
-
220
-	/**
221
-	 * @param Array $input Associative array of input values
222
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
223
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
224
-	 * 					of the modified data.
225
-	 */
226
-	public function apiUpdate($input, $fieldWhitelist)
227
-	{
228
-		$transaction = clone $this;
229
-		$errors = [];
230
-
231
-		// Filter out all non-whitelisted input values
232
-		$input = $this->filterInputColumns($input, $fieldWhitelist);
233
-
234
-		// Check for excess keys
235
-		$errors += $transaction->validateExcessKeys($input);
236
-
237
-		// Check for immutable keys
238
-		$errors += $transaction->validateImmutableColumns($input);
239
-
240
-		// Validate input values (using validation function)
241
-		$errors += $transaction->validateInputValues($input);
242
-
243
-		// "Copy" data into transaction
244
-		$transaction->loadData($input);
245
-
246
-		// Run create hooks
247
-		foreach ($this->registeredUpdateHooks as $colName => $fn) {
248
-			$fn();
249
-		}
250
-
251
-		// Validate missing keys
252
-		$errors += $transaction->validateMissingKeys();
253
-
254
-		// Update database
255
-		if (empty($errors)) {
256
-			$this->syncInstances($this, $transaction);
257
-
258
-			try {
259
-				(new Query($this->getPdo(), $this->getActiveRecordTable()))
260
-					->update($this->getActiveRecordColumns())
261
-					->where('id', '=', $this->getId())
262
-					->execute();
263
-			} catch (\PDOException $e) {
264
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
265
-			}
266
-
267
-			return [null, $this->toArray($fieldWhitelist)];
268
-		} else {
269
-			return [$errors, null];
270
-		}
271
-	}
272
-
273
-	abstract public function read($id);
274
-
275
-	abstract public function getPdo();
45
+    /**
46
+     * Copy all table variables between two instances
47
+     */
48
+    private function syncInstances($to, $from)
49
+    {
50
+        foreach ($to->tableDefinition as $colName => $definition) {
51
+            $definition['value'] = $from->tableDefinition[$colName]['value'];
52
+        }
53
+    }
54
+
55
+    private function filterInputColumns($input, $whitelist)
56
+    {
57
+        $filteredInput = $input;
58
+        foreach ($input as $colName => $value) {
59
+            if (!in_array($colName, $whitelist)) {
60
+                unset($filteredInput[$colName]);
61
+            }
62
+        }
63
+        return $filteredInput;
64
+    }
65
+
66
+    private function validateExcessKeys($input)
67
+    {
68
+        $errors = [];
69
+        foreach ($input as $colName => $value) {
70
+            if (!array_key_exists($colName, $this->tableDefinition)) {
71
+                $errors[$colName] = "Unknown input field";
72
+                continue;
73
+            }
74
+        }
75
+        return $errors;
76
+    }
77
+
78
+    private function validateImmutableColumns($input)
79
+    {
80
+        $errors = [];
81
+        foreach ($this->tableDefinition as $colName => $definition) {
82
+            $property = $definition['properties'] ?? null;
83
+            if (array_key_exists($colName, $input)
84
+                && $property & ColumnProperty::IMMUTABLE) {
85
+                $errors[$colName] = "Field cannot be changed";
86
+            }
87
+        }
88
+        return $errors;
89
+    }
90
+
91
+    private function validateInputValues($input)
92
+    {
93
+        $errors = [];
94
+        foreach ($this->tableDefinition as $colName => $definition) {
95
+            // Validation check 1: If validate function is present
96
+            if (array_key_exists($colName, $input) 
97
+                && is_callable($definition['validate'] ?? null)) {
98
+                $inputValue = $input[$colName];
99
+
100
+                // If validation function fails
101
+                [$status, $message] = $definition['validate']($inputValue);
102
+                if (!$status) {
103
+                    $errors[$colName] = $message;
104
+                }	
105
+            }
106
+
107
+            // Validation check 2: If relation column, check whether entity exists
108
+            $properties = $definition['properties'] ?? null;
109
+            if (isset($definition['relation'])
110
+                && ($properties & ColumnProperty::NOT_NULL)) {
111
+                $instance = clone $definition['relation'];
112
+                try {
113
+                    $instance->read($input[$colName] ?? null);
114
+                } catch (ActiveRecordException $e) {
115
+                    $errors[$colName] = "Entity for this value doesn't exist";
116
+                }
117
+            }
118
+        }
119
+        return $errors;
120
+    }
121
+
122
+    /**
123
+     * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
124
+     */
125
+    private function validateMissingKeys()
126
+    {
127
+        $errors = [];
128
+
129
+        foreach ($this->tableDefinition as $colName => $colDefinition) {
130
+            $default = $colDefinition['default'] ?? null;
131
+            $properties = $colDefinition['properties'] ?? null;
132
+            $value = $colDefinition['value'];
133
+
134
+            // If nullable and default not set => null
135
+            // If nullable and default null => default (null)
136
+            // If nullable and default set => default (value)
137
+
138
+            // if not nullable and default not set => error
139
+            // if not nullable and default null => error
140
+            // if not nullable and default st => default (value)
141
+            // => if not nullable and default null and value not set => error message in this method
142
+            if ($properties & ColumnProperty::NOT_NULL
143
+                && $default === null
144
+                && !($properties & ColumnProperty::AUTO_INCREMENT)
145
+                // && !array_key_exists($colName, $input)
146
+                && $value === null) {
147
+                $errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
148
+            }
149
+        }
150
+
151
+        return $errors;
152
+    }
153
+
154
+    /**
155
+     * Copies the values for entries in the input with matching variable names in the record definition
156
+     * @param Array $input The input data to be loaded into $this record
157
+     */
158
+    private function loadData($input)
159
+    {
160
+        foreach ($this->tableDefinition as $colName => $definition) {
161
+            if (array_key_exists($colName, $input)) {
162
+                $definition['value'] = $input[$colName];
163
+            }
164
+        }
165
+    }
166
+
167
+    /**
168
+     * @param Array $input Associative array of input values
169
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
170
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
171
+     * 					of the modified data.
172
+     */
173
+    public function apiCreate($input, $fieldWhitelist)
174
+    {
175
+        // Clone $this to new instance (for restoring if validation goes wrong)
176
+        $transaction = clone $this;
177
+        $errors = [];
178
+
179
+        // Filter out all non-whitelisted input values
180
+        $input = $this->filterInputColumns($input, $fieldWhitelist);
181
+
182
+        // Validate excess keys
183
+        $errors += $transaction->validateExcessKeys($input);
184
+
185
+        // Validate input values (using validation function)
186
+        $errors += $transaction->validateInputValues($input);
187
+
188
+        // "Copy" data into transaction
189
+        $transaction->loadData($input);
190
+
191
+        // Run create hooks
192
+        foreach ($this->registeredCreateHooks as $colName => $fn) {
193
+            $fn();
194
+        }
195
+
196
+        // Validate missing keys
197
+        $errors += $transaction->validateMissingKeys();
198
+
199
+        // If no errors, commit the pending data
200
+        if (empty($errors)) {
201
+            $this->syncInstances($this, $transaction);
202
+
203
+            try {
204
+                $q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
205
+                    ->insert($this->getActiveRecordColumns())
206
+                    ->execute();
207
+
208
+                $this->setId(intval($this->getPdo()->lastInsertId()));
209
+            } catch (\PDOException $e) {
210
+                // @TODO: Potentially filter and store mysql messages (where possible) in error messages
211
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
212
+            }
213
+
214
+            return [null, $this->toArray($fieldWhitelist)];
215
+        } else {
216
+            return [$errors, null];
217
+        }
218
+    }
219
+
220
+    /**
221
+     * @param Array $input Associative array of input values
222
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
223
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
224
+     * 					of the modified data.
225
+     */
226
+    public function apiUpdate($input, $fieldWhitelist)
227
+    {
228
+        $transaction = clone $this;
229
+        $errors = [];
230
+
231
+        // Filter out all non-whitelisted input values
232
+        $input = $this->filterInputColumns($input, $fieldWhitelist);
233
+
234
+        // Check for excess keys
235
+        $errors += $transaction->validateExcessKeys($input);
236
+
237
+        // Check for immutable keys
238
+        $errors += $transaction->validateImmutableColumns($input);
239
+
240
+        // Validate input values (using validation function)
241
+        $errors += $transaction->validateInputValues($input);
242
+
243
+        // "Copy" data into transaction
244
+        $transaction->loadData($input);
245
+
246
+        // Run create hooks
247
+        foreach ($this->registeredUpdateHooks as $colName => $fn) {
248
+            $fn();
249
+        }
250
+
251
+        // Validate missing keys
252
+        $errors += $transaction->validateMissingKeys();
253
+
254
+        // Update database
255
+        if (empty($errors)) {
256
+            $this->syncInstances($this, $transaction);
257
+
258
+            try {
259
+                (new Query($this->getPdo(), $this->getActiveRecordTable()))
260
+                    ->update($this->getActiveRecordColumns())
261
+                    ->where('id', '=', $this->getId())
262
+                    ->execute();
263
+            } catch (\PDOException $e) {
264
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
265
+            }
266
+
267
+            return [null, $this->toArray($fieldWhitelist)];
268
+        } else {
269
+            return [$errors, null];
270
+        }
271
+    }
272
+
273
+    abstract public function read($id);
274
+
275
+    abstract public function getPdo();
276 276
 
277
-	abstract function setId($id);
277
+    abstract function setId($id);
278 278
 
279
-	abstract function getActiveRecordTable();
279
+    abstract function getActiveRecordTable();
280 280
 
281
-	abstract function getActiveRecordColumns();
281
+    abstract function getActiveRecordColumns();
282 282
 }
Please login to merge, or discard this patch.
src/Traits/Datefields.php 2 patches
Doc Comments   +8 added lines, -2 removed lines patch added patch discarded remove patch
@@ -65,7 +65,7 @@  discard block
 block discarded – undo
65 65
 
66 66
 	/**
67 67
 	 * Returns the timestamp of last update for this record
68
-	 * @return DateTime
68
+	 * @return \DateTime
69 69
 	 */
70 70
 	public function getLastModifiedDate()
71 71
 	{
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 
75 75
 	/**
76 76
 	 * Returns the timestamp of when this record was created
77
-	 * @return DateTime
77
+	 * @return \DateTime
78 78
 	 */
79 79
 	public function getCreationDate()
80 80
 	{
@@ -88,10 +88,16 @@  discard block
 block discarded – undo
88 88
 
89 89
 	abstract function registerDeleteHook($columnName, $fn);
90 90
 
91
+	/**
92
+	 * @param string $fn
93
+	 */
91 94
 	abstract function registerUpdateHook($columnName, $fn);
92 95
 
93 96
 	abstract function registerReadHook($columnName, $fn);
94 97
 
98
+	/**
99
+	 * @param string $fn
100
+	 */
95 101
 	abstract function registerCreateHook($columnName, $fn);
96 102
 }
97 103
 
Please login to merge, or discard this patch.
Indentation   +83 added lines, -83 removed lines patch added patch discarded remove patch
@@ -10,89 +10,89 @@
 block discarded – undo
10 10
 
11 11
 trait Datefields
12 12
 {
13
-	/** @var string The timestamp representing the moment this record was created */
14
-	protected $created;
15
-
16
-	/** @var string The timestamp representing the moment this record was last updated */
17
-	protected $lastModified;
18
-
19
-	/**
20
-	 * this method is required to be called in the constructor for each class that uses this trait. 
21
-	 * It adds the datefields to the table definition and registers the callback hooks
22
-	 */
23
-	protected function initDatefields()
24
-	{
25
-		$this->extendTableDefinition(TRAIT_DATEFIELDS_CREATED, [
26
-			'value' => &$this->created,
27
-			'validate' => null,
28
-			'type' => 'DATETIME',
29
-			'default' => 'CURRENT_TIMESTAMP',
30
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
31
-		]);
32
-
33
-		$this->extendTableDefinition(TRAIT_DATEFIELDS_LAST_MODIFIED, [
34
-			'value' => &$this->lastModified,
35
-			'validate' => null,
36
-			'type' => 'DATETIME',
37
-			'default' => 'CURRENT_TIMESTAMP',
38
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
39
-		]);
13
+    /** @var string The timestamp representing the moment this record was created */
14
+    protected $created;
15
+
16
+    /** @var string The timestamp representing the moment this record was last updated */
17
+    protected $lastModified;
18
+
19
+    /**
20
+     * this method is required to be called in the constructor for each class that uses this trait. 
21
+     * It adds the datefields to the table definition and registers the callback hooks
22
+     */
23
+    protected function initDatefields()
24
+    {
25
+        $this->extendTableDefinition(TRAIT_DATEFIELDS_CREATED, [
26
+            'value' => &$this->created,
27
+            'validate' => null,
28
+            'type' => 'DATETIME',
29
+            'default' => 'CURRENT_TIMESTAMP',
30
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
31
+        ]);
32
+
33
+        $this->extendTableDefinition(TRAIT_DATEFIELDS_LAST_MODIFIED, [
34
+            'value' => &$this->lastModified,
35
+            'validate' => null,
36
+            'type' => 'DATETIME',
37
+            'default' => 'CURRENT_TIMESTAMP',
38
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
39
+        ]);
40 40
 		
41
-		$this->registerUpdateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsUpdateHook');
42
-		$this->registerCreateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsCreateHook');
43
-
44
-		$this->created = null;
45
-		$this->lastModified = null;
46
-	}
47
-
48
-	/**
49
-	 * The hook that gets called to set the timestamp whenever a new record is created
50
-	 */
51
-	protected function DatefieldsCreateHook()
52
-	{
53
-		// @TODO: Should this be split up to seperate hooks for "last_modified" and "created" for consistency?
54
-		$this->created = (new \DateTime('now'))->format('Y-m-d H:i:s');
55
-		$this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
56
-	}
57
-
58
-	/**
59
-	 * The hook that gets called to set the timestamp whenever a record gets updated
60
-	 */
61
-	protected function DatefieldsUpdateHook()
62
-	{
63
-		$this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
64
-	}
65
-
66
-	/**
67
-	 * Returns the timestamp of last update for this record
68
-	 * @return DateTime
69
-	 */
70
-	public function getLastModifiedDate()
71
-	{
72
-		return new \DateTime($this->lastModified);
73
-	}
74
-
75
-	/**
76
-	 * Returns the timestamp of when this record was created
77
-	 * @return DateTime
78
-	 */
79
-	public function getCreationDate()
80
-	{
81
-		return new \DateTime($this->created);
82
-	}
83
-
84
-
85
-	abstract function extendTableDefinition($columnName, $definition);
86
-
87
-	abstract function registerSearchHook($columnName, $fn);
88
-
89
-	abstract function registerDeleteHook($columnName, $fn);
90
-
91
-	abstract function registerUpdateHook($columnName, $fn);
92
-
93
-	abstract function registerReadHook($columnName, $fn);
94
-
95
-	abstract function registerCreateHook($columnName, $fn);
41
+        $this->registerUpdateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsUpdateHook');
42
+        $this->registerCreateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsCreateHook');
43
+
44
+        $this->created = null;
45
+        $this->lastModified = null;
46
+    }
47
+
48
+    /**
49
+     * The hook that gets called to set the timestamp whenever a new record is created
50
+     */
51
+    protected function DatefieldsCreateHook()
52
+    {
53
+        // @TODO: Should this be split up to seperate hooks for "last_modified" and "created" for consistency?
54
+        $this->created = (new \DateTime('now'))->format('Y-m-d H:i:s');
55
+        $this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
56
+    }
57
+
58
+    /**
59
+     * The hook that gets called to set the timestamp whenever a record gets updated
60
+     */
61
+    protected function DatefieldsUpdateHook()
62
+    {
63
+        $this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
64
+    }
65
+
66
+    /**
67
+     * Returns the timestamp of last update for this record
68
+     * @return DateTime
69
+     */
70
+    public function getLastModifiedDate()
71
+    {
72
+        return new \DateTime($this->lastModified);
73
+    }
74
+
75
+    /**
76
+     * Returns the timestamp of when this record was created
77
+     * @return DateTime
78
+     */
79
+    public function getCreationDate()
80
+    {
81
+        return new \DateTime($this->created);
82
+    }
83
+
84
+
85
+    abstract function extendTableDefinition($columnName, $definition);
86
+
87
+    abstract function registerSearchHook($columnName, $fn);
88
+
89
+    abstract function registerDeleteHook($columnName, $fn);
90
+
91
+    abstract function registerUpdateHook($columnName, $fn);
92
+
93
+    abstract function registerReadHook($columnName, $fn);
94
+
95
+    abstract function registerCreateHook($columnName, $fn);
96 96
 }
97 97
 
98
-	
99 98
\ No newline at end of file
99
+    
100 100
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/ManyToManyRelation.php 2 patches
Doc Comments   +3 added lines patch added patch discarded remove patch
@@ -74,6 +74,9 @@
 block discarded – undo
74 74
 		$this->pdo->query($rightConstraint);
75 75
 	}
76 76
 	
77
+	/**
78
+	 * @param string $columnName
79
+	 */
77 80
 	abstract function extendTableDefinition($columnName, $definition);
78 81
 
79 82
 	abstract function registerSearchHook($columnName, $fn);
Please login to merge, or discard this patch.
Indentation   +71 added lines, -71 removed lines patch added patch discarded remove patch
@@ -8,82 +8,82 @@
 block discarded – undo
8 8
 
9 9
 Trait ManyToManyRelation
10 10
 {
11
-	// These variables are relevant for internal bookkeeping (constraint generation etc)
12
-	/** @var string The name of the left column of the relation. */
13
-	private $_leftColumnName;
14
-
15
-	/** @var string The name of the right column of the relation. */
16
-	private $_rightColumnName;
17
-
18
-	/** @var string The name of the left table of the relation. */
19
-	private $_leftEntityTable;
20
-
21
-	/** @var string The name of the right table of the relation. */
22
-	private $_rightEntityTable;
23
-
24
-	/**
25
-	 * Initializes the the ManyToManyRelation trait on the included object
26
-	 * 
27
-	 * @param AbstractActiveRecord $leftEntity The left entity of the relation
28
-	 * @param &variable $leftVariable The variable where the id for the left entity will be stored
29
-	 * @param AbstractActiveRecord $rightEntity The left entity of the relation
30
-	 * @param &variable $leftVariable The variable where the id for the right entity will be stored
31
-	 */
32
-	protected function initManyToManyRelation(AbstractActiveRecord $leftEntity, &$leftVariable, AbstractActiveRecord $rightEntity, &$rightVariable)
33
-	{
34
-		$this->_leftEntityTable = $leftEntity->getActiveRecordTable();
35
-		$this->_rightEntityTable = $rightEntity->getActiveRecordTable();
36
-
37
-		if (get_class($leftEntity) === get_class($rightEntity)) {
38
-			$this->_leftColumnName = sprintf("id_%s_left", $leftEntity->getActiveRecordTable());
39
-			$this->_rightColumnName = sprintf("id_%s_right", $rightEntity->getActiveRecordTable());
40
-		} else {
41
-			$this->_leftColumnName = sprintf("id_%s", $leftEntity->getActiveRecordTable());
42
-			$this->_rightColumnName = sprintf("id_%s", $rightEntity->getActiveRecordTable());
43
-		}
44
-
45
-		$this->extendTableDefinition($this->_leftColumnName, [
46
-			'value' => &$leftVariable,
47
-			'validate' => null,
48
-			'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
49
-			'properties' => ColumnProperty::NOT_NULL
50
-		]);
51
-
52
-		$this->extendTableDefinition($this->_rightColumnName, [
53
-			'value' => &$rightVariable,
54
-			'validate' => null,
55
-			'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
56
-			'properties' => ColumnProperty::NOT_NULL
57
-		]);
58
-	}
59
-
60
-	/**
61
-	 * Build the constraints for the many-to-many relation table
62
-	 */
63
-	public function createTableConstraints()
64
-	{
65
-		$childTable = $this->getActiveRecordTable();
66
-
67
-		$leftParentTable = $this->_leftEntityTable;
68
-		$rightParentTable = $this->_rightEntityTable;
69
-
70
-		$leftConstraint = $this->buildConstraint($leftParentTable, 'id', $childTable, $this->_leftColumnName);
71
-		$rightConstraint = $this->buildConstraint($rightParentTable, 'id', $childTable, $this->_rightColumnName);
72
-
73
-		$this->pdo->query($leftConstraint);
74
-		$this->pdo->query($rightConstraint);
75
-	}
11
+    // These variables are relevant for internal bookkeeping (constraint generation etc)
12
+    /** @var string The name of the left column of the relation. */
13
+    private $_leftColumnName;
14
+
15
+    /** @var string The name of the right column of the relation. */
16
+    private $_rightColumnName;
17
+
18
+    /** @var string The name of the left table of the relation. */
19
+    private $_leftEntityTable;
20
+
21
+    /** @var string The name of the right table of the relation. */
22
+    private $_rightEntityTable;
23
+
24
+    /**
25
+     * Initializes the the ManyToManyRelation trait on the included object
26
+     * 
27
+     * @param AbstractActiveRecord $leftEntity The left entity of the relation
28
+     * @param &variable $leftVariable The variable where the id for the left entity will be stored
29
+     * @param AbstractActiveRecord $rightEntity The left entity of the relation
30
+     * @param &variable $leftVariable The variable where the id for the right entity will be stored
31
+     */
32
+    protected function initManyToManyRelation(AbstractActiveRecord $leftEntity, &$leftVariable, AbstractActiveRecord $rightEntity, &$rightVariable)
33
+    {
34
+        $this->_leftEntityTable = $leftEntity->getActiveRecordTable();
35
+        $this->_rightEntityTable = $rightEntity->getActiveRecordTable();
36
+
37
+        if (get_class($leftEntity) === get_class($rightEntity)) {
38
+            $this->_leftColumnName = sprintf("id_%s_left", $leftEntity->getActiveRecordTable());
39
+            $this->_rightColumnName = sprintf("id_%s_right", $rightEntity->getActiveRecordTable());
40
+        } else {
41
+            $this->_leftColumnName = sprintf("id_%s", $leftEntity->getActiveRecordTable());
42
+            $this->_rightColumnName = sprintf("id_%s", $rightEntity->getActiveRecordTable());
43
+        }
44
+
45
+        $this->extendTableDefinition($this->_leftColumnName, [
46
+            'value' => &$leftVariable,
47
+            'validate' => null,
48
+            'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
49
+            'properties' => ColumnProperty::NOT_NULL
50
+        ]);
51
+
52
+        $this->extendTableDefinition($this->_rightColumnName, [
53
+            'value' => &$rightVariable,
54
+            'validate' => null,
55
+            'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
56
+            'properties' => ColumnProperty::NOT_NULL
57
+        ]);
58
+    }
59
+
60
+    /**
61
+     * Build the constraints for the many-to-many relation table
62
+     */
63
+    public function createTableConstraints()
64
+    {
65
+        $childTable = $this->getActiveRecordTable();
66
+
67
+        $leftParentTable = $this->_leftEntityTable;
68
+        $rightParentTable = $this->_rightEntityTable;
69
+
70
+        $leftConstraint = $this->buildConstraint($leftParentTable, 'id', $childTable, $this->_leftColumnName);
71
+        $rightConstraint = $this->buildConstraint($rightParentTable, 'id', $childTable, $this->_rightColumnName);
72
+
73
+        $this->pdo->query($leftConstraint);
74
+        $this->pdo->query($rightConstraint);
75
+    }
76 76
 	
77
-	abstract function extendTableDefinition($columnName, $definition);
77
+    abstract function extendTableDefinition($columnName, $definition);
78 78
 
79
-	abstract function registerSearchHook($columnName, $fn);
79
+    abstract function registerSearchHook($columnName, $fn);
80 80
 
81
-	abstract function registerDeleteHook($columnName, $fn);
81
+    abstract function registerDeleteHook($columnName, $fn);
82 82
 
83
-	abstract function registerUpdateHook($columnName, $fn);
83
+    abstract function registerUpdateHook($columnName, $fn);
84 84
 
85
-	abstract function registerReadHook($columnName, $fn);
85
+    abstract function registerReadHook($columnName, $fn);
86 86
 
87
-	abstract function registerCreateHook($columnName, $fn);
87
+    abstract function registerCreateHook($columnName, $fn);
88 88
 
89 89
 }
Please login to merge, or discard this patch.
src/Traits/SoftDelete.php 2 patches
Doc Comments   +3 added lines patch added patch discarded remove patch
@@ -80,6 +80,9 @@
 block discarded – undo
80 80
 
81 81
 	abstract function extendTableDefinition($columnName, $definition);
82 82
 
83
+	/**
84
+	 * @param string $fn
85
+	 */
83 86
 	abstract function registerSearchHook($columnName, $fn);
84 87
 
85 88
 	abstract function registerDeleteHook($columnName, $fn);
Please login to merge, or discard this patch.
Indentation   +79 added lines, -79 removed lines patch added patch discarded remove patch
@@ -9,84 +9,84 @@
 block discarded – undo
9 9
 
10 10
 trait SoftDelete
11 11
 {
12
-	/** @var boolean the soft delete status for the entity this trait is embedded into. */
13
-	protected $softDelete;
14
-
15
-	/**
16
-	 * this method is required to be called in the constructor for each class that uses this trait. 
17
-	 * It adds the required fields to the table definition and registers hooks
18
-	 */
19
-	protected function initSoftDelete()
20
-	{
21
-		$this->softDelete = false;
22
-
23
-		$this->extendTableDefinition(TRAIT_SOFT_DELETE_FIELD_KEY, [
24
-			'value' => &$this->softDelete,
25
-			'validate' => null,
26
-			'default' => 0,
27
-			'type' => 'INT',
28
-			'length' => 1,
29
-			'properties' => ColumnProperty::NOT_NULL
30
-		]);
31
-
32
-		$this->registerSearchHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteSearchHook');
33
-	}
34
-
35
-	/**
36
-	 * The hook that gets called whenever a query is made
37
-	 */
38
-	protected function softDeleteSearchHook(Query $query)
39
-	{
40
-		$query->where(TRAIT_SOFT_DELETE_FIELD_KEY, '=', 0);
41
-	}
42
-
43
-	/**
44
-	 * returns the name for the soft delete field in the database
45
-	 * @return string
46
-	 */
47
-	public function getSoftDeleteFieldName()
48
-	{
49
-		return TRAIT_SOFT_DELETE_FIELD_KEY;
50
-	}
12
+    /** @var boolean the soft delete status for the entity this trait is embedded into. */
13
+    protected $softDelete;
14
+
15
+    /**
16
+     * this method is required to be called in the constructor for each class that uses this trait. 
17
+     * It adds the required fields to the table definition and registers hooks
18
+     */
19
+    protected function initSoftDelete()
20
+    {
21
+        $this->softDelete = false;
22
+
23
+        $this->extendTableDefinition(TRAIT_SOFT_DELETE_FIELD_KEY, [
24
+            'value' => &$this->softDelete,
25
+            'validate' => null,
26
+            'default' => 0,
27
+            'type' => 'INT',
28
+            'length' => 1,
29
+            'properties' => ColumnProperty::NOT_NULL
30
+        ]);
31
+
32
+        $this->registerSearchHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteSearchHook');
33
+    }
34
+
35
+    /**
36
+     * The hook that gets called whenever a query is made
37
+     */
38
+    protected function softDeleteSearchHook(Query $query)
39
+    {
40
+        $query->where(TRAIT_SOFT_DELETE_FIELD_KEY, '=', 0);
41
+    }
42
+
43
+    /**
44
+     * returns the name for the soft delete field in the database
45
+     * @return string
46
+     */
47
+    public function getSoftDeleteFieldName()
48
+    {
49
+        return TRAIT_SOFT_DELETE_FIELD_KEY;
50
+    }
51 51
 	
52
-	/**
53
-	 * Mark the current record as soft deleted
54
-	 * @return $this
55
-	 */
56
-	public function softDelete()
57
-	{
58
-		$this->softDelete = true;
59
-		return $this;
60
-	}
61
-
62
-	/**
63
-	 * Undo the current soft deletion status (mark it as non-soft deleted)
64
-	 * @return $this
65
-	 */
66
-	public function softRestore()
67
-	{
68
-		$this->softDelete = false;
69
-		return $this;
70
-	}
71
-
72
-	/**
73
-	 * returns the current soft deletion status
74
-	 * @return $this
75
-	 */
76
-	public function getDeletionStatus() 
77
-	{
78
-		return $this->softDelete;
79
-	}
80
-
81
-	abstract function extendTableDefinition($columnName, $definition);
82
-
83
-	abstract function registerSearchHook($columnName, $fn);
84
-
85
-	abstract function registerDeleteHook($columnName, $fn);
86
-
87
-	abstract function registerUpdateHook($columnName, $fn);
88
-
89
-	abstract function registerReadHook($columnName, $fn);
90
-
91
-	abstract function registerCreateHook($columnName, $fn);
52
+    /**
53
+     * Mark the current record as soft deleted
54
+     * @return $this
55
+     */
56
+    public function softDelete()
57
+    {
58
+        $this->softDelete = true;
59
+        return $this;
60
+    }
61
+
62
+    /**
63
+     * Undo the current soft deletion status (mark it as non-soft deleted)
64
+     * @return $this
65
+     */
66
+    public function softRestore()
67
+    {
68
+        $this->softDelete = false;
69
+        return $this;
70
+    }
71
+
72
+    /**
73
+     * returns the current soft deletion status
74
+     * @return $this
75
+     */
76
+    public function getDeletionStatus() 
77
+    {
78
+        return $this->softDelete;
79
+    }
80
+
81
+    abstract function extendTableDefinition($columnName, $definition);
82
+
83
+    abstract function registerSearchHook($columnName, $fn);
84
+
85
+    abstract function registerDeleteHook($columnName, $fn);
86
+
87
+    abstract function registerUpdateHook($columnName, $fn);
88
+
89
+    abstract function registerReadHook($columnName, $fn);
90
+
91
+    abstract function registerCreateHook($columnName, $fn);
92 92
 }
93 93
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/Password.php 1 patch
Indentation   +125 added lines, -125 removed lines patch added patch discarded remove patch
@@ -13,136 +13,136 @@
 block discarded – undo
13 13
 
14 14
 trait Password
15 15
 {
16
-	/** @var string The password hash. */
17
-	protected $password;
18
-
19
-	/** @var string|null The password reset token. */
20
-	protected $passwordResetToken;
21
-
22
-	/**
23
-	 * this method is required to be called in the constructor for each class that uses this trait. 
24
-	 * It adds the fields necessary for the passwords struct to the table definition
25
-	 */
26
-	protected function initPassword()
27
-	{
28
-		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD, [
29
-			'value' => &$this->password,
30
-			'validate' => null,
31
-			'type' => 'VARCHAR',
32
-			'length' => 1024,
33
-			'properties' => null
34
-		]);
35
-
36
-		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD_RESET_TOKEN, [
37
-			'value' => &$this->passwordResetToken,
38
-			'validate' => null,
39
-			'default' => 0,
40
-			'type' => 'VARCHAR',
41
-			'length' => 1024
42
-		]);
43
-	}
44
-
45
-
46
-	/**
47
-	 * Returns whether the users password has been set
48
-	 * @return boolean true if the user has a password
49
-	 */
50
-	public function hasPasswordBeenSet()
51
-	{
52
-		return $this->password !== null;
53
-	}
54
-
55
-	/**
56
-	 * Returns true if the credentials are correct.
57
-	 *
58
-	 * @param string $password
59
-	 * @return boolean true if the credentials are correct
60
-	 */
61
-	public function isPassword($password)
62
-	{ 
63
-		if (!$this->hasPasswordBeenSet())
64
-		{
65
-			throw new ActiveRecordTraitException("Password field has not been set");
66
-		}
67
-
68
-		if (!password_verify($password, $this->password)) {
69
-			return false;
70
-		}
71
-
72
-		if (password_needs_rehash($this->password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH])) {
73
-			$this->setPassword($password)->sync();
74
-		}
75
-
76
-		return true;
77
-	}
78
-
79
-	/**
80
-	 * Set the password.
81
-	 *
82
-	 * @param string $password
83
-	 * @return $this
84
-	 * @throws \Exception
85
-	 */
86
-	public function setPassword($password)
87
-	{
88
-		if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
89
-			throw new ActiveRecordTraitException(sprintf('\'Password\' must be atleast %s characters long. %s characters provied.', self::PASSWORD_MIN_LENGTH, strlen($password)));
90
-		}
91
-
92
-		$passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
93
-
94
-		if ($passwordHash === false) {
95
-			throw new ActiveRecordTraitException('\'Password\' hash failed.');
96
-		}
97
-
98
-		$this->password = $passwordHash;
99
-
100
-		return $this;
101
-	}
102
-
103
-	/**
104
-	 * @return string The Hash of the password
105
-	 */
106
-	public function getPasswordHash()
107
-	{
108
-		return $this->password;
109
-	}
110
-
111
-	/**
112
-	 * Returns the currently set password token for the entity, or null if not set
113
-	 * @return string|null The password reset token
114
-	 */
115
-	public function getPasswordResetToken()
116
-	{
117
-		return $this->passwordResetToken;
118
-	}
119
-
120
-	/**
121
-	 * Generates a new password reset token for the user
122
-	 */
123
-	public function generatePasswordResetToken()
124
-	{
125
-		$this->passwordResetToken = md5(uniqid(mt_rand(), true));
126
-	}
127
-
128
-	/**
129
-	 * Clears the current password reset token
130
-	 */
131
-	public function clearPasswordResetToken()
132
-	{
133
-		$this->passwordResetToken = null;
134
-	}
16
+    /** @var string The password hash. */
17
+    protected $password;
18
+
19
+    /** @var string|null The password reset token. */
20
+    protected $passwordResetToken;
21
+
22
+    /**
23
+     * this method is required to be called in the constructor for each class that uses this trait. 
24
+     * It adds the fields necessary for the passwords struct to the table definition
25
+     */
26
+    protected function initPassword()
27
+    {
28
+        $this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD, [
29
+            'value' => &$this->password,
30
+            'validate' => null,
31
+            'type' => 'VARCHAR',
32
+            'length' => 1024,
33
+            'properties' => null
34
+        ]);
35
+
36
+        $this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD_RESET_TOKEN, [
37
+            'value' => &$this->passwordResetToken,
38
+            'validate' => null,
39
+            'default' => 0,
40
+            'type' => 'VARCHAR',
41
+            'length' => 1024
42
+        ]);
43
+    }
44
+
45
+
46
+    /**
47
+     * Returns whether the users password has been set
48
+     * @return boolean true if the user has a password
49
+     */
50
+    public function hasPasswordBeenSet()
51
+    {
52
+        return $this->password !== null;
53
+    }
54
+
55
+    /**
56
+     * Returns true if the credentials are correct.
57
+     *
58
+     * @param string $password
59
+     * @return boolean true if the credentials are correct
60
+     */
61
+    public function isPassword($password)
62
+    { 
63
+        if (!$this->hasPasswordBeenSet())
64
+        {
65
+            throw new ActiveRecordTraitException("Password field has not been set");
66
+        }
67
+
68
+        if (!password_verify($password, $this->password)) {
69
+            return false;
70
+        }
71
+
72
+        if (password_needs_rehash($this->password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH])) {
73
+            $this->setPassword($password)->sync();
74
+        }
75
+
76
+        return true;
77
+    }
78
+
79
+    /**
80
+     * Set the password.
81
+     *
82
+     * @param string $password
83
+     * @return $this
84
+     * @throws \Exception
85
+     */
86
+    public function setPassword($password)
87
+    {
88
+        if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
89
+            throw new ActiveRecordTraitException(sprintf('\'Password\' must be atleast %s characters long. %s characters provied.', self::PASSWORD_MIN_LENGTH, strlen($password)));
90
+        }
91
+
92
+        $passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
93
+
94
+        if ($passwordHash === false) {
95
+            throw new ActiveRecordTraitException('\'Password\' hash failed.');
96
+        }
97
+
98
+        $this->password = $passwordHash;
99
+
100
+        return $this;
101
+    }
102
+
103
+    /**
104
+     * @return string The Hash of the password
105
+     */
106
+    public function getPasswordHash()
107
+    {
108
+        return $this->password;
109
+    }
110
+
111
+    /**
112
+     * Returns the currently set password token for the entity, or null if not set
113
+     * @return string|null The password reset token
114
+     */
115
+    public function getPasswordResetToken()
116
+    {
117
+        return $this->passwordResetToken;
118
+    }
119
+
120
+    /**
121
+     * Generates a new password reset token for the user
122
+     */
123
+    public function generatePasswordResetToken()
124
+    {
125
+        $this->passwordResetToken = md5(uniqid(mt_rand(), true));
126
+    }
127
+
128
+    /**
129
+     * Clears the current password reset token
130
+     */
131
+    public function clearPasswordResetToken()
132
+    {
133
+        $this->passwordResetToken = null;
134
+    }
135 135
 	
136
-	abstract function extendTableDefinition($columnName, $definition);
136
+    abstract function extendTableDefinition($columnName, $definition);
137 137
 
138
-	abstract function registerSearchHook($columnName, $fn);
138
+    abstract function registerSearchHook($columnName, $fn);
139 139
 
140
-	abstract function registerDeleteHook($columnName, $fn);
140
+    abstract function registerDeleteHook($columnName, $fn);
141 141
 
142
-	abstract function registerUpdateHook($columnName, $fn);
142
+    abstract function registerUpdateHook($columnName, $fn);
143 143
 
144
-	abstract function registerReadHook($columnName, $fn);
144
+    abstract function registerReadHook($columnName, $fn);
145 145
 
146
-	abstract function registerCreateHook($columnName, $fn);
146
+    abstract function registerCreateHook($columnName, $fn);
147 147
 
148 148
 }
149 149
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/Address.php 1 patch
Indentation   +89 added lines, -89 removed lines patch added patch discarded remove patch
@@ -11,104 +11,104 @@
 block discarded – undo
11 11
 
12 12
 trait Address
13 13
 {
14
-	protected $address;
15
-
16
-	protected $zipcode;
17
-
18
-	protected $city;
19
-
20
-	protected $country;
21
-
22
-	protected function initAddress() 
23
-	{
24
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ADDRESS, [
25
-			'value' => &$this->address,
26
-			'validate' => null,
27
-			'type' => 'VARCHAR',
28
-			'length' => 1024,
29
-			'properties' => null
30
-		]);
31
-
32
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ZIPCODE, [
33
-			'value' => &$this->zipcode,
34
-			'validate' => null,
35
-			'type' => 'VARCHAR',
36
-			'length' => 1024,
37
-			'properties' => null
38
-		]);
39
-
40
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_CITY, [
41
-			'value' => &$this->city,
42
-			'validate' => null,
43
-			'type' => 'VARCHAR',
44
-			'length' => 1024,
45
-			'properties' => null
46
-		]);
47
-
48
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_COUNTRY, [
49
-			'value' => &$this->country,
50
-			'validate' => null,
51
-			'type' => 'VARCHAR',
52
-			'length' => 1024,
53
-			'properties' => null
54
-		]);
55
-
56
-		$this->address = null;
57
-		$this->zipcode = null;
58
-		$this->city = null;
59
-		$this->country = null;
60
-	}
61
-
62
-	public function getAddress()
63
-	{
64
-		return $this->address;
65
-	}
14
+    protected $address;
15
+
16
+    protected $zipcode;
17
+
18
+    protected $city;
19
+
20
+    protected $country;
21
+
22
+    protected function initAddress() 
23
+    {
24
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ADDRESS, [
25
+            'value' => &$this->address,
26
+            'validate' => null,
27
+            'type' => 'VARCHAR',
28
+            'length' => 1024,
29
+            'properties' => null
30
+        ]);
31
+
32
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ZIPCODE, [
33
+            'value' => &$this->zipcode,
34
+            'validate' => null,
35
+            'type' => 'VARCHAR',
36
+            'length' => 1024,
37
+            'properties' => null
38
+        ]);
39
+
40
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_CITY, [
41
+            'value' => &$this->city,
42
+            'validate' => null,
43
+            'type' => 'VARCHAR',
44
+            'length' => 1024,
45
+            'properties' => null
46
+        ]);
47
+
48
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_COUNTRY, [
49
+            'value' => &$this->country,
50
+            'validate' => null,
51
+            'type' => 'VARCHAR',
52
+            'length' => 1024,
53
+            'properties' => null
54
+        ]);
55
+
56
+        $this->address = null;
57
+        $this->zipcode = null;
58
+        $this->city = null;
59
+        $this->country = null;
60
+    }
61
+
62
+    public function getAddress()
63
+    {
64
+        return $this->address;
65
+    }
66 66
 	
67
-	public function setAddress($address)
68
-	{
69
-		$this->address = $address;
70
-	}
71
-
72
-	public function getZipcode()
73
-	{
74
-		return $this->zipcode;
75
-	}
67
+    public function setAddress($address)
68
+    {
69
+        $this->address = $address;
70
+    }
71
+
72
+    public function getZipcode()
73
+    {
74
+        return $this->zipcode;
75
+    }
76 76
 	
77
-	public function setZipcode($zipcode)
78
-	{
79
-		$this->zipcode = $zipcode;
80
-	}
81
-
82
-	public function getCity()
83
-	{
84
-		return $this->city;
85
-	}
77
+    public function setZipcode($zipcode)
78
+    {
79
+        $this->zipcode = $zipcode;
80
+    }
81
+
82
+    public function getCity()
83
+    {
84
+        return $this->city;
85
+    }
86 86
 	
87
-	public function setCity($city)
88
-	{
89
-		$this->city = $city;
90
-	}
91
-
92
-	public function getCountry()
93
-	{
94
-		return $this->country;
95
-	}
87
+    public function setCity($city)
88
+    {
89
+        $this->city = $city;
90
+    }
91
+
92
+    public function getCountry()
93
+    {
94
+        return $this->country;
95
+    }
96 96
 	
97
-	public function setCountry($country)
98
-	{
99
-		$this->country = $country;
100
-	}
97
+    public function setCountry($country)
98
+    {
99
+        $this->country = $country;
100
+    }
101 101
 
102
-	abstract function extendTableDefinition($columnName, $definition);
102
+    abstract function extendTableDefinition($columnName, $definition);
103 103
 
104
-	abstract function registerSearchHook($columnName, $fn);
104
+    abstract function registerSearchHook($columnName, $fn);
105 105
 
106
-	abstract function registerDeleteHook($columnName, $fn);
106
+    abstract function registerDeleteHook($columnName, $fn);
107 107
 
108
-	abstract function registerUpdateHook($columnName, $fn);
108
+    abstract function registerUpdateHook($columnName, $fn);
109 109
 
110
-	abstract function registerReadHook($columnName, $fn);
110
+    abstract function registerReadHook($columnName, $fn);
111 111
 
112
-	abstract function registerCreateHook($columnName, $fn);
112
+    abstract function registerCreateHook($columnName, $fn);
113 113
 	
114 114
 }
115 115
\ No newline at end of file
Please login to merge, or discard this patch.
src/AbstractActiveRecord.php 1 patch
Indentation   +742 added lines, -742 removed lines patch added patch discarded remove patch
@@ -18,752 +18,752 @@
 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
-		$this->tableDefinition = $this->getActiveRecordTableDefinition();
60
-		$this->registeredCreateHooks = [];
61
-		$this->registeredReadHooks = [];
62
-		$this->registeredUpdateHooks = [];
63
-		$this->registeredDeleteHooks = [];
64
-		$this->registeredSearchHooks = [];
65
-
66
-		// Extend table definition with default ID field, throw exception if field already exists
67
-		if (array_key_exists('id', $this->tableDefinition)) {
68
-			$message = "Table definition in record contains a field with name \"id\"";
69
-			$message .= ", which is a reserved name by ActiveRecord";
70
-			throw new ActiveRecordException($message, 0);
71
-		}
72
-
73
-		$this->tableDefinition[self::COLUMN_NAME_ID] =
74
-		[
75
-			'value' => &$this->id,
76
-			'validate' => null,
77
-			'type' => self::COLUMN_TYPE_ID,
78
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE | ColumnProperty::AUTO_INCREMENT | ColumnProperty::PRIMARY_KEY
79
-		];
80
-	}
81
-
82
-	/**
83
-	 * Register a new hook for a specific column that gets called before execution of the create() method
84
-	 * Only one hook per column can be registered at a time
85
-	 * @param string $columnName The name of the column that is registered.
86
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
87
-	 */
88
-	public function registerCreateHook($columnName, $fn)
89
-	{
90
-		// Check whether column exists
91
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
92
-		{
93
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
94
-		}
95
-
96
-		// Enforcing 1 hook per table column
97
-		if (array_key_exists($columnName, $this->registeredCreateHooks)) {
98
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
99
-			$message .= "do you have conflicting traits?";
100
-			throw new ActiveRecordException($message, 0);
101
-		}
102
-
103
-		if (is_string($fn) && is_callable([$this, $fn])) {
104
-			$this->registeredCreateHooks[$columnName] = [$this, $fn];
105
-		} else if (is_callable($fn)) {
106
-			$this->registeredCreateHooks[$columnName] = $fn;
107
-		} else {
108
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
109
-		}
110
-	}
111
-
112
-	/**
113
-	 * Register a new hook for a specific column that gets called before execution of the read() method
114
-	 * Only one hook per column can be registered at a time
115
-	 * @param string $columnName The name of the column that is registered.
116
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
117
-	 */
118
-	public function registerReadHook($columnName, $fn)
119
-	{
120
-		// Check whether column exists
121
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
122
-		{
123
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
124
-		}
125
-
126
-		// Enforcing 1 hook per table column
127
-		if (array_key_exists($columnName, $this->registeredReadHooks)) {
128
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
129
-			$message .= "do you have conflicting traits?";
130
-			throw new ActiveRecordException($message, 0);
131
-		}
132
-
133
-		if (is_string($fn) && is_callable([$this, $fn])) {
134
-			$this->registeredReadHooks[$columnName] = [$this, $fn];
135
-		} else if (is_callable($fn)) {
136
-			$this->registeredReadHooks[$columnName] = $fn;
137
-		} else {
138
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
139
-		}
140
-	}
141
-
142
-	/**
143
-	 * Register a new hook for a specific column that gets called before execution of the update() method
144
-	 * Only one hook per column can be registered at a time
145
-	 * @param string $columnName The name of the column that is registered.
146
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
147
-	 */
148
-	public function registerUpdateHook($columnName, $fn)
149
-	{
150
-		// Check whether column exists
151
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
152
-		{
153
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
154
-		}
155
-
156
-		// Enforcing 1 hook per table column
157
-		if (array_key_exists($columnName, $this->registeredUpdateHooks)) {
158
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
159
-			$message .= "do you have conflicting traits?";
160
-			throw new ActiveRecordException($message, 0);
161
-		}
162
-
163
-		if (is_string($fn) && is_callable([$this, $fn])) {
164
-			$this->registeredUpdateHooks[$columnName] = [$this, $fn];
165
-		} else if (is_callable($fn)) {
166
-			$this->registeredUpdateHooks[$columnName] = $fn;
167
-		} else {
168
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
169
-		}
170
-	}
171
-
172
-	/**
173
-	 * Register a new hook for a specific column that gets called before execution of the delete() method
174
-	 * Only one hook per column can be registered at a time
175
-	 * @param string $columnName The name of the column that is registered.
176
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
177
-	 */
178
-	public function registerDeleteHook($columnName, $fn)
179
-	{
180
-		// Check whether column exists
181
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
182
-		{
183
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
184
-		}
185
-
186
-		// Enforcing 1 hook per table column
187
-		if (array_key_exists($columnName, $this->registeredDeleteHooks)) {
188
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
189
-			$message .= "do you have conflicting traits?";
190
-			throw new ActiveRecordException($message, 0);
191
-		}
192
-
193
-		if (is_string($fn) && is_callable([$this, $fn])) {
194
-			$this->registeredDeleteHooks[$columnName] = [$this, $fn];
195
-		} else if (is_callable($fn)) {
196
-			$this->registeredDeleteHooks[$columnName] = $fn;
197
-		} else {
198
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * Register a new hook for a specific column that gets called before execution of the search() method
204
-	 * Only one hook per column can be registered at a time
205
-	 * @param string $columnName The name of the column that is registered.
206
-	 * @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; 
207
-	 */
208
-	public function registerSearchHook($columnName, $fn)
209
-	{
210
-		// Check whether column exists
211
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
212
-		{
213
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
214
-		}
215
-
216
-		// Enforcing 1 hook per table column
217
-		if (array_key_exists($columnName, $this->registeredSearchHooks)) {
218
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
219
-			$message .= "do you have conflicting traits?";
220
-			throw new ActiveRecordException($message, 0);
221
-		}
222
-
223
-		if (is_string($fn) && is_callable([$this, $fn])) {
224
-			$this->registeredSearchHooks[$columnName] = [$this, $fn];
225
-		} else if (is_callable($fn)) {
226
-			$this->registeredSearchHooks[$columnName] = $fn;
227
-		} else {
228
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
229
-		}
230
-	}
231
-
232
-	/**
233
-	 * Adds a new column definition to the table.
234
-	 * @param string $columnName The name of the column that is registered.
235
-	 * @param Array $definition The definition of that column.
236
-	 */
237
-	public function extendTableDefinition($columnName, $definition)
238
-	{
239
-		if ($this->tableDefinition === null) {
240
-			throw new ActiveRecordException("tableDefinition is null, most likely due to parent class not having been initialized in constructor");
241
-		}
242
-
243
-		// Enforcing table can only be extended with new columns
244
-		if (array_key_exists($columnName, $this->tableDefinition)) {
245
-			$message = "Table is being extended with a column that already exists, ";
246
-			$message .= "\"$columnName\" conflicts with your table definition";
247
-			throw new ActiveRecordException($message, 0);
248
-		}
249
-
250
-		$this->tableDefinition[$columnName] = $definition;
251
-	}
252
-
253
-	/**
254
-	 * Returns the type string as it should appear in the mysql create table statement for the given column
255
-	 * @return string The type string
256
-	 */
257
-	private function getDatabaseTypeString($colName, $type, $length)
258
-	{
259
-		if ($type === null) 
260
-		{
261
-			throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
262
-		}
263
-
264
-		switch (strtoupper($type)) {
265
-			case 'DATETIME':
266
-			case 'DATE':
267
-			case 'TIME':
268
-			case 'TEXT':
269
-			case 'INT UNSIGNED':
270
-				return $type;
271
-
272
-			case 'VARCHAR':
273
-				if ($length === null) {
274
-					throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
275
-				} else {
276
-					return sprintf('%s(%d)', $type, $length);	
277
-				}
278
-
279
-			case 'INT':
280
-			case 'TINYINT':
281
-			case 'BIGINT':
282
-			default: 	
283
-			// @TODO(Default): throw exception, or implicitly assume that type is correct? (For when using SQL databases with different types)
284
-				if ($length === null) {
285
-					return $type;
286
-				} else {
287
-					return sprintf('%s(%d)', $type, $length);	
288
-				}
289
-		}
290
-	}
291
-
292
-	/**
293
-	 * Builds the part of a MySQL create table statement that corresponds to the supplied column
294
-	 * @param string $colName 	Name of the database column
295
-	 * @param string $type 		The type of the string
296
-	 * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
297
-	 * @return string
298
-	 */
299
-	private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
300
-	{
301
-
302
-		$stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
303
-		if ($properties & ColumnProperty::NOT_NULL) {
304
-			$stmnt .= 'NOT NULL ';
305
-		} else {
306
-			$stmnt .= 'NULL ';
307
-		}
308
-
309
-		if ($default !== NULL) {
310
-			$stmnt .= ' DEFAULT ' . $default . ' ';
311
-		}
312
-
313
-		if ($properties & ColumnProperty::AUTO_INCREMENT) {
314
-			$stmnt .= 'AUTO_INCREMENT ';
315
-		}
316
-
317
-		if ($properties & ColumnProperty::UNIQUE) {
318
-			$stmnt .= 'UNIQUE ';
319
-		}
320
-
321
-		if ($properties & ColumnProperty::PRIMARY_KEY) {
322
-			$stmnt .= 'PRIMARY KEY ';
323
-		}
324
-
325
-		return $stmnt;
326
-	}
327
-
328
-	/**
329
-	 * Sorts the column statement components in the order such that the id appears first, 
330
-	 * 		followed by all other columns in alphabetical ascending order
331
-	 * @param   Array $colStatements Array of column statements
332
-	 * @return  Array
333
-	 */
334
-	private function sortColumnStatements($colStatements)
335
-	{
336
-		// Find ID statement and put it first
337
-		$sortedStatements = [];
338
-
339
-		$sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
340
-		unset($colStatements[self::COLUMN_NAME_ID]);
341
-
342
-		// Sort remaining columns in alphabetical order
343
-		$columns = array_keys($colStatements);
344
-		sort($columns);
345
-		foreach ($columns as $colName) {
346
-			$sortedStatements[] = $colStatements[$colName];
347
-		}
348
-
349
-		return $sortedStatements;
350
-	}
351
-
352
-	/**
353
-	 * Builds the MySQL Create Table statement for the internal table definition
354
-	 * @return string
355
-	 */
356
-	public function buildCreateTableSQL()
357
-	{
358
-		$columnStatements = [];
359
-		foreach ($this->tableDefinition as $colName => $definition) {
360
-			// Destructure column definition
361
-			$type    = $definition['type'] ?? null;
362
-			$default = $definition['default'] ?? null;
363
-			$length  = $definition['length'] ?? null;
364
-			$properties = $definition['properties'] ?? null;
365
-
366
-			if (isset($definition['relation']) && $type !== null) {
367
-				$msg = "Column \"$colName\": ";
368
-				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
369
-				throw new ActiveRecordException($msg);
370
-			} else if (isset($definition['relation'])) {
371
-				$type = self::COLUMN_TYPE_ID;
372
-			}
373
-
374
-			$columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
375
-		}
376
-
377
-		// Sort table (first column is id, the remaining are alphabetically sorted)
378
-		$columnStatements = $this->sortColumnStatements($columnStatements);
379
-
380
-		$sql = 'CREATE TABLE ' . $this->getActiveRecordTable() . ' ';
381
-		$sql .= "(\n";
382
-		$sql .= join($columnStatements, ",\n");
383
-		$sql .= "\n);";
384
-
385
-		return $sql;
386
-	}
387
-
388
-	/**
389
-	 * Creates the entity as a table in the database
390
-	 */
391
-	public function createTable()
392
-	{
393
-		$this->pdo->query($this->buildCreateTableSQL());
394
-	}
395
-
396
-	/**
397
-	 * builds a MySQL constraint statement for the given parameters
398
-	 * @param string $parentTable
399
-	 * @param string $parentColumn
400
-	 * @param string $childTable
401
-	 * @param string $childColumn
402
-	 * @return string The MySQL table constraint string
403
-	 */
404
-	protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
405
-	{
406
-		$template = <<<SQL
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
+        $this->tableDefinition = $this->getActiveRecordTableDefinition();
60
+        $this->registeredCreateHooks = [];
61
+        $this->registeredReadHooks = [];
62
+        $this->registeredUpdateHooks = [];
63
+        $this->registeredDeleteHooks = [];
64
+        $this->registeredSearchHooks = [];
65
+
66
+        // Extend table definition with default ID field, throw exception if field already exists
67
+        if (array_key_exists('id', $this->tableDefinition)) {
68
+            $message = "Table definition in record contains a field with name \"id\"";
69
+            $message .= ", which is a reserved name by ActiveRecord";
70
+            throw new ActiveRecordException($message, 0);
71
+        }
72
+
73
+        $this->tableDefinition[self::COLUMN_NAME_ID] =
74
+        [
75
+            'value' => &$this->id,
76
+            'validate' => null,
77
+            'type' => self::COLUMN_TYPE_ID,
78
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE | ColumnProperty::AUTO_INCREMENT | ColumnProperty::PRIMARY_KEY
79
+        ];
80
+    }
81
+
82
+    /**
83
+     * Register a new hook for a specific column that gets called before execution of the create() method
84
+     * Only one hook per column can be registered at a time
85
+     * @param string $columnName The name of the column that is registered.
86
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
87
+     */
88
+    public function registerCreateHook($columnName, $fn)
89
+    {
90
+        // Check whether column exists
91
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
92
+        {
93
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
94
+        }
95
+
96
+        // Enforcing 1 hook per table column
97
+        if (array_key_exists($columnName, $this->registeredCreateHooks)) {
98
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
99
+            $message .= "do you have conflicting traits?";
100
+            throw new ActiveRecordException($message, 0);
101
+        }
102
+
103
+        if (is_string($fn) && is_callable([$this, $fn])) {
104
+            $this->registeredCreateHooks[$columnName] = [$this, $fn];
105
+        } else if (is_callable($fn)) {
106
+            $this->registeredCreateHooks[$columnName] = $fn;
107
+        } else {
108
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
109
+        }
110
+    }
111
+
112
+    /**
113
+     * Register a new hook for a specific column that gets called before execution of the read() method
114
+     * Only one hook per column can be registered at a time
115
+     * @param string $columnName The name of the column that is registered.
116
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
117
+     */
118
+    public function registerReadHook($columnName, $fn)
119
+    {
120
+        // Check whether column exists
121
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
122
+        {
123
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
124
+        }
125
+
126
+        // Enforcing 1 hook per table column
127
+        if (array_key_exists($columnName, $this->registeredReadHooks)) {
128
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
129
+            $message .= "do you have conflicting traits?";
130
+            throw new ActiveRecordException($message, 0);
131
+        }
132
+
133
+        if (is_string($fn) && is_callable([$this, $fn])) {
134
+            $this->registeredReadHooks[$columnName] = [$this, $fn];
135
+        } else if (is_callable($fn)) {
136
+            $this->registeredReadHooks[$columnName] = $fn;
137
+        } else {
138
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
139
+        }
140
+    }
141
+
142
+    /**
143
+     * Register a new hook for a specific column that gets called before execution of the update() method
144
+     * Only one hook per column can be registered at a time
145
+     * @param string $columnName The name of the column that is registered.
146
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
147
+     */
148
+    public function registerUpdateHook($columnName, $fn)
149
+    {
150
+        // Check whether column exists
151
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
152
+        {
153
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
154
+        }
155
+
156
+        // Enforcing 1 hook per table column
157
+        if (array_key_exists($columnName, $this->registeredUpdateHooks)) {
158
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
159
+            $message .= "do you have conflicting traits?";
160
+            throw new ActiveRecordException($message, 0);
161
+        }
162
+
163
+        if (is_string($fn) && is_callable([$this, $fn])) {
164
+            $this->registeredUpdateHooks[$columnName] = [$this, $fn];
165
+        } else if (is_callable($fn)) {
166
+            $this->registeredUpdateHooks[$columnName] = $fn;
167
+        } else {
168
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
169
+        }
170
+    }
171
+
172
+    /**
173
+     * Register a new hook for a specific column that gets called before execution of the delete() method
174
+     * Only one hook per column can be registered at a time
175
+     * @param string $columnName The name of the column that is registered.
176
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
177
+     */
178
+    public function registerDeleteHook($columnName, $fn)
179
+    {
180
+        // Check whether column exists
181
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
182
+        {
183
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
184
+        }
185
+
186
+        // Enforcing 1 hook per table column
187
+        if (array_key_exists($columnName, $this->registeredDeleteHooks)) {
188
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
189
+            $message .= "do you have conflicting traits?";
190
+            throw new ActiveRecordException($message, 0);
191
+        }
192
+
193
+        if (is_string($fn) && is_callable([$this, $fn])) {
194
+            $this->registeredDeleteHooks[$columnName] = [$this, $fn];
195
+        } else if (is_callable($fn)) {
196
+            $this->registeredDeleteHooks[$columnName] = $fn;
197
+        } else {
198
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
199
+        }
200
+    }
201
+
202
+    /**
203
+     * Register a new hook for a specific column that gets called before execution of the search() method
204
+     * Only one hook per column can be registered at a time
205
+     * @param string $columnName The name of the column that is registered.
206
+     * @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; 
207
+     */
208
+    public function registerSearchHook($columnName, $fn)
209
+    {
210
+        // Check whether column exists
211
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
212
+        {
213
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
214
+        }
215
+
216
+        // Enforcing 1 hook per table column
217
+        if (array_key_exists($columnName, $this->registeredSearchHooks)) {
218
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
219
+            $message .= "do you have conflicting traits?";
220
+            throw new ActiveRecordException($message, 0);
221
+        }
222
+
223
+        if (is_string($fn) && is_callable([$this, $fn])) {
224
+            $this->registeredSearchHooks[$columnName] = [$this, $fn];
225
+        } else if (is_callable($fn)) {
226
+            $this->registeredSearchHooks[$columnName] = $fn;
227
+        } else {
228
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
229
+        }
230
+    }
231
+
232
+    /**
233
+     * Adds a new column definition to the table.
234
+     * @param string $columnName The name of the column that is registered.
235
+     * @param Array $definition The definition of that column.
236
+     */
237
+    public function extendTableDefinition($columnName, $definition)
238
+    {
239
+        if ($this->tableDefinition === null) {
240
+            throw new ActiveRecordException("tableDefinition is null, most likely due to parent class not having been initialized in constructor");
241
+        }
242
+
243
+        // Enforcing table can only be extended with new columns
244
+        if (array_key_exists($columnName, $this->tableDefinition)) {
245
+            $message = "Table is being extended with a column that already exists, ";
246
+            $message .= "\"$columnName\" conflicts with your table definition";
247
+            throw new ActiveRecordException($message, 0);
248
+        }
249
+
250
+        $this->tableDefinition[$columnName] = $definition;
251
+    }
252
+
253
+    /**
254
+     * Returns the type string as it should appear in the mysql create table statement for the given column
255
+     * @return string The type string
256
+     */
257
+    private function getDatabaseTypeString($colName, $type, $length)
258
+    {
259
+        if ($type === null) 
260
+        {
261
+            throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
262
+        }
263
+
264
+        switch (strtoupper($type)) {
265
+            case 'DATETIME':
266
+            case 'DATE':
267
+            case 'TIME':
268
+            case 'TEXT':
269
+            case 'INT UNSIGNED':
270
+                return $type;
271
+
272
+            case 'VARCHAR':
273
+                if ($length === null) {
274
+                    throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
275
+                } else {
276
+                    return sprintf('%s(%d)', $type, $length);	
277
+                }
278
+
279
+            case 'INT':
280
+            case 'TINYINT':
281
+            case 'BIGINT':
282
+            default: 	
283
+            // @TODO(Default): throw exception, or implicitly assume that type is correct? (For when using SQL databases with different types)
284
+                if ($length === null) {
285
+                    return $type;
286
+                } else {
287
+                    return sprintf('%s(%d)', $type, $length);	
288
+                }
289
+        }
290
+    }
291
+
292
+    /**
293
+     * Builds the part of a MySQL create table statement that corresponds to the supplied column
294
+     * @param string $colName 	Name of the database column
295
+     * @param string $type 		The type of the string
296
+     * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
297
+     * @return string
298
+     */
299
+    private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
300
+    {
301
+
302
+        $stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
303
+        if ($properties & ColumnProperty::NOT_NULL) {
304
+            $stmnt .= 'NOT NULL ';
305
+        } else {
306
+            $stmnt .= 'NULL ';
307
+        }
308
+
309
+        if ($default !== NULL) {
310
+            $stmnt .= ' DEFAULT ' . $default . ' ';
311
+        }
312
+
313
+        if ($properties & ColumnProperty::AUTO_INCREMENT) {
314
+            $stmnt .= 'AUTO_INCREMENT ';
315
+        }
316
+
317
+        if ($properties & ColumnProperty::UNIQUE) {
318
+            $stmnt .= 'UNIQUE ';
319
+        }
320
+
321
+        if ($properties & ColumnProperty::PRIMARY_KEY) {
322
+            $stmnt .= 'PRIMARY KEY ';
323
+        }
324
+
325
+        return $stmnt;
326
+    }
327
+
328
+    /**
329
+     * Sorts the column statement components in the order such that the id appears first, 
330
+     * 		followed by all other columns in alphabetical ascending order
331
+     * @param   Array $colStatements Array of column statements
332
+     * @return  Array
333
+     */
334
+    private function sortColumnStatements($colStatements)
335
+    {
336
+        // Find ID statement and put it first
337
+        $sortedStatements = [];
338
+
339
+        $sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
340
+        unset($colStatements[self::COLUMN_NAME_ID]);
341
+
342
+        // Sort remaining columns in alphabetical order
343
+        $columns = array_keys($colStatements);
344
+        sort($columns);
345
+        foreach ($columns as $colName) {
346
+            $sortedStatements[] = $colStatements[$colName];
347
+        }
348
+
349
+        return $sortedStatements;
350
+    }
351
+
352
+    /**
353
+     * Builds the MySQL Create Table statement for the internal table definition
354
+     * @return string
355
+     */
356
+    public function buildCreateTableSQL()
357
+    {
358
+        $columnStatements = [];
359
+        foreach ($this->tableDefinition as $colName => $definition) {
360
+            // Destructure column definition
361
+            $type    = $definition['type'] ?? null;
362
+            $default = $definition['default'] ?? null;
363
+            $length  = $definition['length'] ?? null;
364
+            $properties = $definition['properties'] ?? null;
365
+
366
+            if (isset($definition['relation']) && $type !== null) {
367
+                $msg = "Column \"$colName\": ";
368
+                $msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
369
+                throw new ActiveRecordException($msg);
370
+            } else if (isset($definition['relation'])) {
371
+                $type = self::COLUMN_TYPE_ID;
372
+            }
373
+
374
+            $columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
375
+        }
376
+
377
+        // Sort table (first column is id, the remaining are alphabetically sorted)
378
+        $columnStatements = $this->sortColumnStatements($columnStatements);
379
+
380
+        $sql = 'CREATE TABLE ' . $this->getActiveRecordTable() . ' ';
381
+        $sql .= "(\n";
382
+        $sql .= join($columnStatements, ",\n");
383
+        $sql .= "\n);";
384
+
385
+        return $sql;
386
+    }
387
+
388
+    /**
389
+     * Creates the entity as a table in the database
390
+     */
391
+    public function createTable()
392
+    {
393
+        $this->pdo->query($this->buildCreateTableSQL());
394
+    }
395
+
396
+    /**
397
+     * builds a MySQL constraint statement for the given parameters
398
+     * @param string $parentTable
399
+     * @param string $parentColumn
400
+     * @param string $childTable
401
+     * @param string $childColumn
402
+     * @return string The MySQL table constraint string
403
+     */
404
+    protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
405
+    {
406
+        $template = <<<SQL
407 407
 ALTER TABLE `%s`
408 408
 ADD CONSTRAINT
409 409
 FOREIGN KEY (`%s`)
410 410
 REFERENCES `%s`(`%s`)
411 411
 ON DELETE CASCADE;
412 412
 SQL;
413
-		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
414
-	}
415
-
416
-	/**
417
-	 * Iterates over the specified constraints in the table definition, 
418
-	 * 		and applies these to the database.
419
-	 */
420
-	public function createTableConstraints()
421
-	{
422
-		// Iterate over columns, check whether "relation" field exists, if so create constraint
423
-		foreach ($this->tableDefinition as $colName => $definition) {
424
-			if (isset($definition['relation']) && $definition['relation'] instanceof AbstractActiveRecord) {
425
-				// Forge new relation
426
-				$target = $definition['relation'];
427
-				$constraintSql = $this->buildConstraint($target->getActiveRecordTable(), 'id', $this->getActiveRecordTable(), $colName);
428
-
429
-				$this->pdo->query($constraintSql);
430
-			}
431
-		}
432
-	}
433
-
434
-	/**
435
-	 * Returns the name -> variable mapping for the table definition.
436
-	 * @return Array The mapping
437
-	 */
438
-	protected function getActiveRecordColumns()
439
-	{
440
-		$bindings = [];
441
-		foreach ($this->tableDefinition as $colName => $definition) {
442
-
443
-			// Ignore the id column (key) when inserting or updating
444
-			if ($colName == self::COLUMN_NAME_ID) {
445
-				continue;
446
-			}
447
-
448
-			$bindings[$colName] = &$definition['value'];
449
-		}
450
-		return $bindings;
451
-	}
452
-
453
-	/**
454
-	 * {@inheritdoc}
455
-	 */
456
-	public function create()
457
-	{
458
-		foreach ($this->registeredCreateHooks as $colName => $fn) {
459
-			// @TODO: Would it be better to pass the Query to the function?
460
-			$fn();
461
-		}
462
-
463
-		try {
464
-			$q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
465
-				->insert($this->getActiveRecordColumns())
466
-				->execute();
467
-
468
-			$this->setId(intval($this->getPdo()->lastInsertId()));
469
-		} catch (\PDOException $e) {
470
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
471
-		}
472
-
473
-		return $this;
474
-	}
475
-
476
-	/**
477
-	 * {@inheritdoc}
478
-	 */
479
-	public function read($id)
480
-	{
481
-		foreach ($this->registeredReadHooks as $colName => $fn) {
482
-			// @TODO: Would it be better to pass the Query to the function?
483
-			$fn();
484
-		}
485
-
486
-		try {
487
-			$row = (new Query($this->getPdo(), $this->getActiveRecordTable()))
488
-				->select()
489
-				->where('id', '=', $id)
490
-				->execute()
491
-				->fetch();
492
-
493
-			if ($row === false) {
494
-				throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()));
495
-			}
496
-
497
-			$this->fill($row)->setId($id);
498
-		} catch (\PDOException $e) {
499
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
500
-		}
501
-
502
-		return $this;
503
-	}
504
-
505
-	/**
506
-	 * {@inheritdoc}
507
-	 */
508
-	public function update()
509
-	{
510
-		foreach ($this->registeredUpdateHooks as $colName => $fn) {
511
-			// @TODO: Would it be better to pass the Query to the function?
512
-			$fn();
513
-		}
514
-
515
-		try {
516
-			(new Query($this->getPdo(), $this->getActiveRecordTable()))
517
-				->update($this->getActiveRecordColumns())
518
-				->where('id', '=', $this->getId())
519
-				->execute();
520
-		} catch (\PDOException $e) {
521
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
522
-		}
523
-
524
-		return $this;
525
-	}
526
-
527
-	/**
528
-	 * {@inheritdoc}
529
-	 */
530
-	public function delete()
531
-	{
532
-		foreach ($this->registeredDeleteHooks as $colName => $fn) {
533
-			// @TODO: Would it be better to pass the Query to the function?
534
-			$fn();
535
-		}
536
-
537
-		try {
538
-			(new Query($this->getPdo(), $this->getActiveRecordTable()))
539
-				->delete()
540
-				->where('id', '=', $this->getId())
541
-				->execute();
542
-
543
-			$this->setId(null);
544
-		} catch (\PDOException $e) {
545
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
546
-		}
547
-
548
-		return $this;
549
-	}
550
-
551
-	/**
552
-	 * {@inheritdoc}
553
-	 */
554
-	public function sync()
555
-	{
556
-		if (!$this->exists()) {
557
-			return $this->create();
558
-		}
559
-
560
-		return $this->update();
561
-	}
562
-
563
-	/**
564
-	 * {@inheritdoc}
565
-	 */
566
-	public function exists()
567
-	{
568
-		return $this->getId() !== null;
569
-	}
570
-
571
-	/**
572
-	 * {@inheritdoc}
573
-	 */
574
-	public function fill(array $attributes)
575
-	{
576
-		$columns = $this->getActiveRecordColumns();
577
-		$columns['id'] = &$this->id;
578
-
579
-		foreach ($attributes as $key => $value) {
580
-			if (array_key_exists($key, $columns)) {
581
-				$columns[$key] = $value;
582
-			}
583
-		}
584
-
585
-		return $this;
586
-	}
587
-
588
-	/**
589
-	 * {@inheritdoc}
590
-	 */
591
-	public function searchOne(array $where = [], array $orderBy = [])
592
-	{
593
-		try {
594
-			$row = $this->getSearchQueryResult($where, $orderBy, 1)->fetch();
595
-
596
-			if ($row === false) {
597
-				throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->getActiveRecordTable()));
598
-			}
599
-
600
-			return $this->fill($row)->setId($row['id']);
601
-		} catch (\PDOException $e) {
602
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
603
-		}
604
-	}
605
-
606
-	/**
607
-	 * {@inheritdoc}
608
-	 */
609
-	public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
610
-	{
611
-		try {
612
-			$queryResult = $this->getSearchQueryResult($where, $orderBy, $limit, $offset);
613
-			$result = [];
614
-
615
-			foreach ($queryResult as $row) {
616
-				$new = clone $this;
617
-
618
-				$result[] = $new->fill($row)->setId($row['id']);
619
-			}
620
-
621
-			return $result;
622
-		} catch (\PDOException $e) {
623
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
624
-		}
625
-	}
626
-
627
-	/**
628
-	 * Returns the search query result with the given where, order by, limit and offset clauses.
629
-	 *
630
-	 * @param array $where = []
631
-	 * @param array $orderBy = []
632
-	 * @param int $limit = -1
633
-	 * @param int $offset = 0
634
-	 * @return \miBadger\Query\QueryResult the search query result with the given where, order by, limit and offset clauses.
635
-	 */
636
-	private function getSearchQueryResult(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
637
-	{
638
-		$query = (new Query($this->getPdo(), $this->getActiveRecordTable()))
639
-			->select();
640
-
641
-		$this->getSearchQueryWhere($query, $where);
642
-		$this->getSearchQueryOrderBy($query, $orderBy);
643
-		$this->getSearchQueryLimit($query, $limit, $offset);
644
-
645
-		// Ignore all trait modifiers for which a where clause was specified
646
-		$registeredSearchHooks = $this->registeredSearchHooks;
647
-		foreach ($where as $index => $clause) {
648
-			$colName = $clause[0];
649
-			unset($registeredSearchHooks[$colName]);
650
-		}
651
-
652
-		// Allow traits to modify the query
653
-		foreach ($registeredSearchHooks as $column => $searchFunction) {
654
-			$searchFunction($query);
655
-		}
656
-
657
-		return $query->execute();
658
-	}
659
-
660
-	/**
661
-	 * Returns the given query after adding the given where conditions.
662
-	 *
663
-	 * @param \miBadger\Query\Query $query
664
-	 * @param array $where
665
-	 * @return \miBadger\Query\Query the given query after adding the given where conditions.
666
-	 */
667
-	private function getSearchQueryWhere($query, $where)
668
-	{
669
-		foreach ($where as $key => $value) {
670
-			$query->where($value[0], $value[1], $value[2]);
671
-		}
672
-
673
-		return $query;
674
-	}
675
-
676
-	/**
677
-	 * Returns the given query after adding the given order by conditions.
678
-	 *
679
-	 * @param \miBadger\Query\Query $query
680
-	 * @param array $orderBy
681
-	 * @return \miBadger\Query\Query the given query after adding the given order by conditions.
682
-	 */
683
-	private function getSearchQueryOrderBy($query, $orderBy)
684
-	{
685
-		foreach ($orderBy as $key => $value) {
686
-			$query->orderBy($key, $value);
687
-		}
688
-
689
-		return $query;
690
-	}
691
-
692
-	/**
693
-	 * Returns the given query after adding the given limit and offset conditions.
694
-	 *
695
-	 * @param \miBadger\Query\Query $query
696
-	 * @param int $limit
697
-	 * @param int $offset
698
-	 * @return \miBadger\Query\Query the given query after adding the given limit and offset conditions.
699
-	 */
700
-	private function getSearchQueryLimit($query, $limit, $offset)
701
-	{
702
-		if ($limit > -1) {
703
-			$query->limit($limit);
704
-			$query->offset($offset);
705
-		}
706
-
707
-		return $query;
708
-	}
709
-
710
-	/**
711
-	 * Returns the PDO.
712
-	 *
713
-	 * @return \PDO the PDO.
714
-	 */
715
-	public function getPdo()
716
-	{
717
-		return $this->pdo;
718
-	}
719
-
720
-	/**
721
-	 * Set the PDO.
722
-	 *
723
-	 * @param \PDO $pdo
724
-	 * @return $this
725
-	 */
726
-	protected function setPdo($pdo)
727
-	{
728
-		$this->pdo = $pdo;
729
-
730
-		return $this;
731
-	}
732
-
733
-	/**
734
-	 * Returns the ID.
735
-	 *
736
-	 * @return null|int The ID.
737
-	 */
738
-	public function getId()
739
-	{
740
-		return $this->id;
741
-	}
742
-
743
-	/**
744
-	 * Set the ID.
745
-	 *
746
-	 * @param int $id
747
-	 * @return $this
748
-	 */
749
-	protected function setId($id)
750
-	{
751
-		$this->id = $id;
752
-
753
-		return $this;
754
-	}
755
-
756
-	/**
757
-	 * Returns the active record table.
758
-	 *
759
-	 * @return string the active record table.
760
-	 */
761
-	abstract protected function getActiveRecordTable();
762
-
763
-	/**
764
-	 * Returns the active record columns.
765
-	 *
766
-	 * @return array the active record columns.
767
-	 */
768
-	abstract protected function getActiveRecordTableDefinition();
413
+        return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
414
+    }
415
+
416
+    /**
417
+     * Iterates over the specified constraints in the table definition, 
418
+     * 		and applies these to the database.
419
+     */
420
+    public function createTableConstraints()
421
+    {
422
+        // Iterate over columns, check whether "relation" field exists, if so create constraint
423
+        foreach ($this->tableDefinition as $colName => $definition) {
424
+            if (isset($definition['relation']) && $definition['relation'] instanceof AbstractActiveRecord) {
425
+                // Forge new relation
426
+                $target = $definition['relation'];
427
+                $constraintSql = $this->buildConstraint($target->getActiveRecordTable(), 'id', $this->getActiveRecordTable(), $colName);
428
+
429
+                $this->pdo->query($constraintSql);
430
+            }
431
+        }
432
+    }
433
+
434
+    /**
435
+     * Returns the name -> variable mapping for the table definition.
436
+     * @return Array The mapping
437
+     */
438
+    protected function getActiveRecordColumns()
439
+    {
440
+        $bindings = [];
441
+        foreach ($this->tableDefinition as $colName => $definition) {
442
+
443
+            // Ignore the id column (key) when inserting or updating
444
+            if ($colName == self::COLUMN_NAME_ID) {
445
+                continue;
446
+            }
447
+
448
+            $bindings[$colName] = &$definition['value'];
449
+        }
450
+        return $bindings;
451
+    }
452
+
453
+    /**
454
+     * {@inheritdoc}
455
+     */
456
+    public function create()
457
+    {
458
+        foreach ($this->registeredCreateHooks as $colName => $fn) {
459
+            // @TODO: Would it be better to pass the Query to the function?
460
+            $fn();
461
+        }
462
+
463
+        try {
464
+            $q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
465
+                ->insert($this->getActiveRecordColumns())
466
+                ->execute();
467
+
468
+            $this->setId(intval($this->getPdo()->lastInsertId()));
469
+        } catch (\PDOException $e) {
470
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
471
+        }
472
+
473
+        return $this;
474
+    }
475
+
476
+    /**
477
+     * {@inheritdoc}
478
+     */
479
+    public function read($id)
480
+    {
481
+        foreach ($this->registeredReadHooks as $colName => $fn) {
482
+            // @TODO: Would it be better to pass the Query to the function?
483
+            $fn();
484
+        }
485
+
486
+        try {
487
+            $row = (new Query($this->getPdo(), $this->getActiveRecordTable()))
488
+                ->select()
489
+                ->where('id', '=', $id)
490
+                ->execute()
491
+                ->fetch();
492
+
493
+            if ($row === false) {
494
+                throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()));
495
+            }
496
+
497
+            $this->fill($row)->setId($id);
498
+        } catch (\PDOException $e) {
499
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
500
+        }
501
+
502
+        return $this;
503
+    }
504
+
505
+    /**
506
+     * {@inheritdoc}
507
+     */
508
+    public function update()
509
+    {
510
+        foreach ($this->registeredUpdateHooks as $colName => $fn) {
511
+            // @TODO: Would it be better to pass the Query to the function?
512
+            $fn();
513
+        }
514
+
515
+        try {
516
+            (new Query($this->getPdo(), $this->getActiveRecordTable()))
517
+                ->update($this->getActiveRecordColumns())
518
+                ->where('id', '=', $this->getId())
519
+                ->execute();
520
+        } catch (\PDOException $e) {
521
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
522
+        }
523
+
524
+        return $this;
525
+    }
526
+
527
+    /**
528
+     * {@inheritdoc}
529
+     */
530
+    public function delete()
531
+    {
532
+        foreach ($this->registeredDeleteHooks as $colName => $fn) {
533
+            // @TODO: Would it be better to pass the Query to the function?
534
+            $fn();
535
+        }
536
+
537
+        try {
538
+            (new Query($this->getPdo(), $this->getActiveRecordTable()))
539
+                ->delete()
540
+                ->where('id', '=', $this->getId())
541
+                ->execute();
542
+
543
+            $this->setId(null);
544
+        } catch (\PDOException $e) {
545
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
546
+        }
547
+
548
+        return $this;
549
+    }
550
+
551
+    /**
552
+     * {@inheritdoc}
553
+     */
554
+    public function sync()
555
+    {
556
+        if (!$this->exists()) {
557
+            return $this->create();
558
+        }
559
+
560
+        return $this->update();
561
+    }
562
+
563
+    /**
564
+     * {@inheritdoc}
565
+     */
566
+    public function exists()
567
+    {
568
+        return $this->getId() !== null;
569
+    }
570
+
571
+    /**
572
+     * {@inheritdoc}
573
+     */
574
+    public function fill(array $attributes)
575
+    {
576
+        $columns = $this->getActiveRecordColumns();
577
+        $columns['id'] = &$this->id;
578
+
579
+        foreach ($attributes as $key => $value) {
580
+            if (array_key_exists($key, $columns)) {
581
+                $columns[$key] = $value;
582
+            }
583
+        }
584
+
585
+        return $this;
586
+    }
587
+
588
+    /**
589
+     * {@inheritdoc}
590
+     */
591
+    public function searchOne(array $where = [], array $orderBy = [])
592
+    {
593
+        try {
594
+            $row = $this->getSearchQueryResult($where, $orderBy, 1)->fetch();
595
+
596
+            if ($row === false) {
597
+                throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->getActiveRecordTable()));
598
+            }
599
+
600
+            return $this->fill($row)->setId($row['id']);
601
+        } catch (\PDOException $e) {
602
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
603
+        }
604
+    }
605
+
606
+    /**
607
+     * {@inheritdoc}
608
+     */
609
+    public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
610
+    {
611
+        try {
612
+            $queryResult = $this->getSearchQueryResult($where, $orderBy, $limit, $offset);
613
+            $result = [];
614
+
615
+            foreach ($queryResult as $row) {
616
+                $new = clone $this;
617
+
618
+                $result[] = $new->fill($row)->setId($row['id']);
619
+            }
620
+
621
+            return $result;
622
+        } catch (\PDOException $e) {
623
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
624
+        }
625
+    }
626
+
627
+    /**
628
+     * Returns the search query result with the given where, order by, limit and offset clauses.
629
+     *
630
+     * @param array $where = []
631
+     * @param array $orderBy = []
632
+     * @param int $limit = -1
633
+     * @param int $offset = 0
634
+     * @return \miBadger\Query\QueryResult the search query result with the given where, order by, limit and offset clauses.
635
+     */
636
+    private function getSearchQueryResult(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
637
+    {
638
+        $query = (new Query($this->getPdo(), $this->getActiveRecordTable()))
639
+            ->select();
640
+
641
+        $this->getSearchQueryWhere($query, $where);
642
+        $this->getSearchQueryOrderBy($query, $orderBy);
643
+        $this->getSearchQueryLimit($query, $limit, $offset);
644
+
645
+        // Ignore all trait modifiers for which a where clause was specified
646
+        $registeredSearchHooks = $this->registeredSearchHooks;
647
+        foreach ($where as $index => $clause) {
648
+            $colName = $clause[0];
649
+            unset($registeredSearchHooks[$colName]);
650
+        }
651
+
652
+        // Allow traits to modify the query
653
+        foreach ($registeredSearchHooks as $column => $searchFunction) {
654
+            $searchFunction($query);
655
+        }
656
+
657
+        return $query->execute();
658
+    }
659
+
660
+    /**
661
+     * Returns the given query after adding the given where conditions.
662
+     *
663
+     * @param \miBadger\Query\Query $query
664
+     * @param array $where
665
+     * @return \miBadger\Query\Query the given query after adding the given where conditions.
666
+     */
667
+    private function getSearchQueryWhere($query, $where)
668
+    {
669
+        foreach ($where as $key => $value) {
670
+            $query->where($value[0], $value[1], $value[2]);
671
+        }
672
+
673
+        return $query;
674
+    }
675
+
676
+    /**
677
+     * Returns the given query after adding the given order by conditions.
678
+     *
679
+     * @param \miBadger\Query\Query $query
680
+     * @param array $orderBy
681
+     * @return \miBadger\Query\Query the given query after adding the given order by conditions.
682
+     */
683
+    private function getSearchQueryOrderBy($query, $orderBy)
684
+    {
685
+        foreach ($orderBy as $key => $value) {
686
+            $query->orderBy($key, $value);
687
+        }
688
+
689
+        return $query;
690
+    }
691
+
692
+    /**
693
+     * Returns the given query after adding the given limit and offset conditions.
694
+     *
695
+     * @param \miBadger\Query\Query $query
696
+     * @param int $limit
697
+     * @param int $offset
698
+     * @return \miBadger\Query\Query the given query after adding the given limit and offset conditions.
699
+     */
700
+    private function getSearchQueryLimit($query, $limit, $offset)
701
+    {
702
+        if ($limit > -1) {
703
+            $query->limit($limit);
704
+            $query->offset($offset);
705
+        }
706
+
707
+        return $query;
708
+    }
709
+
710
+    /**
711
+     * Returns the PDO.
712
+     *
713
+     * @return \PDO the PDO.
714
+     */
715
+    public function getPdo()
716
+    {
717
+        return $this->pdo;
718
+    }
719
+
720
+    /**
721
+     * Set the PDO.
722
+     *
723
+     * @param \PDO $pdo
724
+     * @return $this
725
+     */
726
+    protected function setPdo($pdo)
727
+    {
728
+        $this->pdo = $pdo;
729
+
730
+        return $this;
731
+    }
732
+
733
+    /**
734
+     * Returns the ID.
735
+     *
736
+     * @return null|int The ID.
737
+     */
738
+    public function getId()
739
+    {
740
+        return $this->id;
741
+    }
742
+
743
+    /**
744
+     * Set the ID.
745
+     *
746
+     * @param int $id
747
+     * @return $this
748
+     */
749
+    protected function setId($id)
750
+    {
751
+        $this->id = $id;
752
+
753
+        return $this;
754
+    }
755
+
756
+    /**
757
+     * Returns the active record table.
758
+     *
759
+     * @return string the active record table.
760
+     */
761
+    abstract protected function getActiveRecordTable();
762
+
763
+    /**
764
+     * Returns the active record columns.
765
+     *
766
+     * @return array the active record columns.
767
+     */
768
+    abstract protected function getActiveRecordTableDefinition();
769 769
 }
Please login to merge, or discard this patch.