Completed
Push — v2 ( 7e0bb1...9c04df )
by Berend
09:41
created
src/Traits/AutoApi.php 1 patch
Indentation   +307 added lines, -307 removed lines patch added patch discarded remove patch
@@ -8,327 +8,327 @@
 block discarded – undo
8 8
 
9 9
 trait AutoApi
10 10
 {
11
-	/* =======================================================================
11
+    /* =======================================================================
12 12
 	 * ===================== Automatic API Support ===========================
13 13
 	 * ======================================================================= */
14 14
 
15
-	/** @var array A map of column name to functions that hook the insert function */
16
-	protected $registeredCreateHooks;
15
+    /** @var array A map of column name to functions that hook the insert function */
16
+    protected $registeredCreateHooks;
17 17
 
18
-	/** @var array A map of column name to functions that hook the read function */
19
-	protected $registeredReadHooks;
18
+    /** @var array A map of column name to functions that hook the read function */
19
+    protected $registeredReadHooks;
20 20
 
21
-	/** @var array A map of column name to functions that hook the update function */
22
-	protected $registeredUpdateHooks;
21
+    /** @var array A map of column name to functions that hook the update function */
22
+    protected $registeredUpdateHooks;
23 23
 
24
-	/** @var array A map of column name to functions that hook the update function */
25
-	protected $registeredDeleteHooks;	
24
+    /** @var array A map of column name to functions that hook the update function */
25
+    protected $registeredDeleteHooks;	
26 26
 
27
-	/** @var array A map of column name to functions that hook the search function */
28
-	protected $registeredSearchHooks;
27
+    /** @var array A map of column name to functions that hook the search function */
28
+    protected $registeredSearchHooks;
29 29
 
30
-	/** @var array A list of table column definitions */
31
-	protected $tableDefinition;
30
+    /** @var array A list of table column definitions */
31
+    protected $tableDefinition;
32 32
 
33
-	public function apiSearch($inputs, $fieldWhitelist)
34
-	{
35
-		// @TODO: How to handle this case?
36
-		// => Default parameter names for searching? (limit, pagination, sort order etc)
37
-		//		Find default names for this and store in class
38
-		// => Limited search parameters? (We don't want to be able to search on a password field for example)
39
-	}
33
+    public function apiSearch($inputs, $fieldWhitelist)
34
+    {
35
+        // @TODO: How to handle this case?
36
+        // => Default parameter names for searching? (limit, pagination, sort order etc)
37
+        //		Find default names for this and store in class
38
+        // => Limited search parameters? (We don't want to be able to search on a password field for example)
39
+    }
40 40
 
41
-	public function toArray($fieldWhitelist)
42
-	{
43
-		$output = [];
44
-		foreach ($this->tableDefinition as $colName => $definition) {
45
-			if (in_array($colName, $fieldWhitelist)) {
46
-				$output[$colName] = $definition['value'];
47
-			}
48
-		}
41
+    public function toArray($fieldWhitelist)
42
+    {
43
+        $output = [];
44
+        foreach ($this->tableDefinition as $colName => $definition) {
45
+            if (in_array($colName, $fieldWhitelist)) {
46
+                $output[$colName] = $definition['value'];
47
+            }
48
+        }
49 49
 
50
-		return $output;
51
-	}
50
+        return $output;
51
+    }
52 52
 
53
-	public function apiRead($id, $fieldWhitelist)
54
-	{
55
-		$this->read($id);
56
-		return $this->toArray($fieldWhitelist);
57
-	}
53
+    public function apiRead($id, $fieldWhitelist)
54
+    {
55
+        $this->read($id);
56
+        return $this->toArray($fieldWhitelist);
57
+    }
58 58
 
59
-	/* =============================================================
59
+    /* =============================================================
60 60
 	 * ===================== Constraint validation =================
61 61
 	 * ============================================================= */
62 62
 
63
-	/**
64
-	 * Copy all table variables between two instances
65
-	 */
66
-	private function syncInstances($to, $from)
67
-	{
68
-		foreach ($to->tableDefinition as $colName => $definition) {
69
-			$definition['value'] = $from->tableDefinition[$colName]['value'];
70
-		}
71
-	}
72
-
73
-	private function filterInputColumns($input, $whitelist)
74
-	{
75
-		$filteredInput = $input;
76
-		foreach ($input as $colName => $value) {
77
-			if (!in_array($colName, $whitelist)) {
78
-				unset($filteredInput[$colName]);
79
-			}
80
-		}
81
-		return $filteredInput;
82
-	}
83
-
84
-	private function validateExcessKeys($input)
85
-	{
86
-		$errors = [];
87
-		foreach ($input as $colName => $value) {
88
-			if (!array_key_exists($colName, $this->tableDefinition)) {
89
-				$errors[$colName] = "Unknown input field";
90
-				continue;
91
-			}
92
-		}
93
-		return $errors;
94
-	}
95
-
96
-	private function validateImmutableColumns($input)
97
-	{
98
-		$errors = [];
99
-		foreach ($this->tableDefinition as $colName => $definition) {
100
-			$property = $definition['properties'] ?? null;
101
-			if (array_key_exists($colName, $input)
102
-				&& $property & ColumnProperty::IMMUTABLE) {
103
-				$errors[$colName] = "Field cannot be changed";
104
-			}
105
-		}
106
-		return $errors;
107
-	}
108
-
109
-	private function validateInputValues($input)
110
-	{
111
-		$errors = [];
112
-		foreach ($this->tableDefinition as $colName => $definition) {
113
-			// Validation check 1: If validate function is present
114
-			if (array_key_exists($colName, $input) 
115
-				&& is_callable($definition['validate'] ?? null)) {
116
-				$inputValue = $input[$colName];
117
-
118
-				// If validation function fails
119
-				[$status, $message] = $definition['validate']($inputValue);
120
-				if (!$status) {
121
-					$errors[$colName] = $message;
122
-				}	
123
-			}
124
-
125
-			// Validation check 2: If relation column, check whether entity exists
126
-			$properties = $definition['properties'] ?? null;
127
-			if (isset($definition['relation'])
128
-				&& ($properties & ColumnProperty::NOT_NULL)) {
129
-				$instance = clone $definition['relation'];
130
-				try {
131
-					$instance->read($input[$colName] ?? null);
132
-				} catch (ActiveRecordException $e) {
133
-					$errors[$colName] = "Entity for this value doesn't exist";
134
-				}
135
-			}
136
-		}
137
-		return $errors;
138
-	}
139
-
140
-	/**
141
-	 * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
142
-	 */
143
-	private function validateMissingKeys()
144
-	{
145
-		$errors = [];
146
-
147
-		foreach ($this->tableDefinition as $colName => $colDefinition) {
148
-			$default = $colDefinition['default'] ?? null;
149
-			$properties = $colDefinition['properties'] ?? null;
150
-			$value = $colDefinition['value'];
151
-
152
-			// If nullable and default not set => null
153
-			// If nullable and default null => default (null)
154
-			// If nullable and default set => default (value)
155
-
156
-			// if not nullable and default not set => error
157
-			// if not nullable and default null => error
158
-			// if not nullable and default st => default (value)
159
-			// => if not nullable and default null and value not set => error message in this method
160
-			if ($properties & ColumnProperty::NOT_NULL
161
-				&& $default === null
162
-				&& !($properties & ColumnProperty::AUTO_INCREMENT)
163
-				// && !array_key_exists($colName, $input)
164
-				&& $value === null) {
165
-				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
166
-			}
167
-		}
168
-
169
-		return $errors;
170
-	}
171
-
172
-	/**
173
-	 * Copies the values for entries in the input with matching variable names in the record definition
174
-	 * @param Array $input The input data to be loaded into $this record
175
-	 */
176
-	private function loadData($input)
177
-	{
178
-		foreach ($this->tableDefinition as $colName => $definition) {
179
-			if (array_key_exists($colName, $input)) {
180
-				$definition['value'] = $input[$colName];
181
-			}
182
-		}
183
-	}
184
-
185
-	/**
186
-	 * @param Array $input Associative array of input values
187
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
188
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
189
-	 * 					of the modified data.
190
-	 */
191
-	public function apiCreate($input, $fieldWhitelist)
192
-	{
193
-		// Clone $this to new instance (for restoring if validation goes wrong)
194
-		$transaction = clone $this;
195
-		$errors = [];
196
-
197
-		// Filter out all non-whitelisted input values
198
-		$input = $this->filterInputColumns($input, $fieldWhitelist);
199
-
200
-		// Validate excess keys
201
-		$errors += $transaction->validateExcessKeys($input);
202
-
203
-		// Validate input values (using validation function)
204
-		$errors += $transaction->validateInputValues($input);
205
-
206
-		// "Copy" data into transaction
207
-		$transaction->loadData($input);
208
-
209
-		// Run create hooks
210
-		foreach ($this->registeredCreateHooks as $colName => $fn) {
211
-			$fn();
212
-		}
213
-
214
-		// Validate missing keys
215
-		$errors += $transaction->validateMissingKeys();
216
-
217
-		// If no errors, commit the pending data
218
-		if (empty($errors)) {
219
-			$this->syncInstances($this, $transaction);
220
-
221
-			try {
222
-				$q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
223
-					->insert($this->getActiveRecordColumns())
224
-					->execute();
225
-
226
-				$this->setId(intval($this->getPdo()->lastInsertId()));
227
-			} catch (\PDOException $e) {
228
-				// @TODO: Potentially filter and store mysql messages (where possible) in error messages
229
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
230
-			}
231
-
232
-			return [null, $this->toArray($fieldWhitelist)];
233
-		} else {
234
-			return [$errors, null];
235
-		}
236
-	}
237
-
238
-	/**
239
-	 * @param Array $input Associative array of input values
240
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
241
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
242
-	 * 					of the modified data.
243
-	 */
244
-	public function apiUpdate($input, $fieldWhitelist)
245
-	{
246
-		$transaction = clone $this;
247
-		$errors = [];
248
-
249
-		// Filter out all non-whitelisted input values
250
-		$input = $this->filterInputColumns($input, $fieldWhitelist);
251
-
252
-		// Check for excess keys
253
-		$errors += $transaction->validateExcessKeys($input);
254
-
255
-		// Check for immutable keys
256
-		$errors += $transaction->validateImmutableColumns($input);
257
-
258
-		// Validate input values (using validation function)
259
-		$errors += $transaction->validateInputValues($input);
260
-
261
-		// "Copy" data into transaction
262
-		$transaction->loadData($input);
263
-
264
-		// Run create hooks
265
-		foreach ($this->registeredUpdateHooks as $colName => $fn) {
266
-			$fn();
267
-		}
268
-
269
-		// Validate missing keys
270
-		$errors += $transaction->validateMissingKeys();
271
-
272
-		// Update database
273
-		if (empty($errors)) {
274
-			$this->syncInstances($this, $transaction);
275
-
276
-			try {
277
-				(new Query($this->getPdo(), $this->getActiveRecordTable()))
278
-					->update($this->getActiveRecordColumns())
279
-					->where('id', '=', $this->getId())
280
-					->execute();
281
-			} catch (\PDOException $e) {
282
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
283
-			}
284
-
285
-			return [null, $this->toArray($fieldWhitelist)];
286
-		} else {
287
-			return [$errors, null];
288
-		}
289
-	}
290
-
291
-	/**
292
-	 * Returns this active record after reading the attributes from the entry with the given identifier.
293
-	 *
294
-	 * @param mixed $id
295
-	 * @return $this
296
-	 * @throws ActiveRecordException on failure.
297
-	 */
298
-	abstract public function read($id);
299
-
300
-	/**
301
-	 * Returns the PDO.
302
-	 *
303
-	 * @return \PDO the PDO.
304
-	 */
305
-	abstract public function getPdo();
306
-
307
-	/**
308
-	 * Set the ID.
309
-	 *
310
-	 * @param int $id
311
-	 * @return $this
312
-	 */
313
-	abstract protected function setId($id);
314
-
315
-	/**
316
-	 * Returns the ID.
317
-	 *
318
-	 * @return null|int The ID.
319
-	 */
320
-	abstract protected function getId();
321
-
322
-	/**
323
-	 * Returns the active record table.
324
-	 *
325
-	 * @return string the active record table name.
326
-	 */
327
-	abstract protected function getActiveRecordTable();
328
-
329
-	/**
330
-	 * Returns the name -> variable mapping for the table definition.
331
-	 * @return Array The mapping
332
-	 */
333
-	abstract protected function getActiveRecordColumns();
63
+    /**
64
+     * Copy all table variables between two instances
65
+     */
66
+    private function syncInstances($to, $from)
67
+    {
68
+        foreach ($to->tableDefinition as $colName => $definition) {
69
+            $definition['value'] = $from->tableDefinition[$colName]['value'];
70
+        }
71
+    }
72
+
73
+    private function filterInputColumns($input, $whitelist)
74
+    {
75
+        $filteredInput = $input;
76
+        foreach ($input as $colName => $value) {
77
+            if (!in_array($colName, $whitelist)) {
78
+                unset($filteredInput[$colName]);
79
+            }
80
+        }
81
+        return $filteredInput;
82
+    }
83
+
84
+    private function validateExcessKeys($input)
85
+    {
86
+        $errors = [];
87
+        foreach ($input as $colName => $value) {
88
+            if (!array_key_exists($colName, $this->tableDefinition)) {
89
+                $errors[$colName] = "Unknown input field";
90
+                continue;
91
+            }
92
+        }
93
+        return $errors;
94
+    }
95
+
96
+    private function validateImmutableColumns($input)
97
+    {
98
+        $errors = [];
99
+        foreach ($this->tableDefinition as $colName => $definition) {
100
+            $property = $definition['properties'] ?? null;
101
+            if (array_key_exists($colName, $input)
102
+                && $property & ColumnProperty::IMMUTABLE) {
103
+                $errors[$colName] = "Field cannot be changed";
104
+            }
105
+        }
106
+        return $errors;
107
+    }
108
+
109
+    private function validateInputValues($input)
110
+    {
111
+        $errors = [];
112
+        foreach ($this->tableDefinition as $colName => $definition) {
113
+            // Validation check 1: If validate function is present
114
+            if (array_key_exists($colName, $input) 
115
+                && is_callable($definition['validate'] ?? null)) {
116
+                $inputValue = $input[$colName];
117
+
118
+                // If validation function fails
119
+                [$status, $message] = $definition['validate']($inputValue);
120
+                if (!$status) {
121
+                    $errors[$colName] = $message;
122
+                }	
123
+            }
124
+
125
+            // Validation check 2: If relation column, check whether entity exists
126
+            $properties = $definition['properties'] ?? null;
127
+            if (isset($definition['relation'])
128
+                && ($properties & ColumnProperty::NOT_NULL)) {
129
+                $instance = clone $definition['relation'];
130
+                try {
131
+                    $instance->read($input[$colName] ?? null);
132
+                } catch (ActiveRecordException $e) {
133
+                    $errors[$colName] = "Entity for this value doesn't exist";
134
+                }
135
+            }
136
+        }
137
+        return $errors;
138
+    }
139
+
140
+    /**
141
+     * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
142
+     */
143
+    private function validateMissingKeys()
144
+    {
145
+        $errors = [];
146
+
147
+        foreach ($this->tableDefinition as $colName => $colDefinition) {
148
+            $default = $colDefinition['default'] ?? null;
149
+            $properties = $colDefinition['properties'] ?? null;
150
+            $value = $colDefinition['value'];
151
+
152
+            // If nullable and default not set => null
153
+            // If nullable and default null => default (null)
154
+            // If nullable and default set => default (value)
155
+
156
+            // if not nullable and default not set => error
157
+            // if not nullable and default null => error
158
+            // if not nullable and default st => default (value)
159
+            // => if not nullable and default null and value not set => error message in this method
160
+            if ($properties & ColumnProperty::NOT_NULL
161
+                && $default === null
162
+                && !($properties & ColumnProperty::AUTO_INCREMENT)
163
+                // && !array_key_exists($colName, $input)
164
+                && $value === null) {
165
+                $errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
166
+            }
167
+        }
168
+
169
+        return $errors;
170
+    }
171
+
172
+    /**
173
+     * Copies the values for entries in the input with matching variable names in the record definition
174
+     * @param Array $input The input data to be loaded into $this record
175
+     */
176
+    private function loadData($input)
177
+    {
178
+        foreach ($this->tableDefinition as $colName => $definition) {
179
+            if (array_key_exists($colName, $input)) {
180
+                $definition['value'] = $input[$colName];
181
+            }
182
+        }
183
+    }
184
+
185
+    /**
186
+     * @param Array $input Associative array of input values
187
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
188
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
189
+     * 					of the modified data.
190
+     */
191
+    public function apiCreate($input, $fieldWhitelist)
192
+    {
193
+        // Clone $this to new instance (for restoring if validation goes wrong)
194
+        $transaction = clone $this;
195
+        $errors = [];
196
+
197
+        // Filter out all non-whitelisted input values
198
+        $input = $this->filterInputColumns($input, $fieldWhitelist);
199
+
200
+        // Validate excess keys
201
+        $errors += $transaction->validateExcessKeys($input);
202
+
203
+        // Validate input values (using validation function)
204
+        $errors += $transaction->validateInputValues($input);
205
+
206
+        // "Copy" data into transaction
207
+        $transaction->loadData($input);
208
+
209
+        // Run create hooks
210
+        foreach ($this->registeredCreateHooks as $colName => $fn) {
211
+            $fn();
212
+        }
213
+
214
+        // Validate missing keys
215
+        $errors += $transaction->validateMissingKeys();
216
+
217
+        // If no errors, commit the pending data
218
+        if (empty($errors)) {
219
+            $this->syncInstances($this, $transaction);
220
+
221
+            try {
222
+                $q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
223
+                    ->insert($this->getActiveRecordColumns())
224
+                    ->execute();
225
+
226
+                $this->setId(intval($this->getPdo()->lastInsertId()));
227
+            } catch (\PDOException $e) {
228
+                // @TODO: Potentially filter and store mysql messages (where possible) in error messages
229
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
230
+            }
231
+
232
+            return [null, $this->toArray($fieldWhitelist)];
233
+        } else {
234
+            return [$errors, null];
235
+        }
236
+    }
237
+
238
+    /**
239
+     * @param Array $input Associative array of input values
240
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
241
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
242
+     * 					of the modified data.
243
+     */
244
+    public function apiUpdate($input, $fieldWhitelist)
245
+    {
246
+        $transaction = clone $this;
247
+        $errors = [];
248
+
249
+        // Filter out all non-whitelisted input values
250
+        $input = $this->filterInputColumns($input, $fieldWhitelist);
251
+
252
+        // Check for excess keys
253
+        $errors += $transaction->validateExcessKeys($input);
254
+
255
+        // Check for immutable keys
256
+        $errors += $transaction->validateImmutableColumns($input);
257
+
258
+        // Validate input values (using validation function)
259
+        $errors += $transaction->validateInputValues($input);
260
+
261
+        // "Copy" data into transaction
262
+        $transaction->loadData($input);
263
+
264
+        // Run create hooks
265
+        foreach ($this->registeredUpdateHooks as $colName => $fn) {
266
+            $fn();
267
+        }
268
+
269
+        // Validate missing keys
270
+        $errors += $transaction->validateMissingKeys();
271
+
272
+        // Update database
273
+        if (empty($errors)) {
274
+            $this->syncInstances($this, $transaction);
275
+
276
+            try {
277
+                (new Query($this->getPdo(), $this->getActiveRecordTable()))
278
+                    ->update($this->getActiveRecordColumns())
279
+                    ->where('id', '=', $this->getId())
280
+                    ->execute();
281
+            } catch (\PDOException $e) {
282
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
283
+            }
284
+
285
+            return [null, $this->toArray($fieldWhitelist)];
286
+        } else {
287
+            return [$errors, null];
288
+        }
289
+    }
290
+
291
+    /**
292
+     * Returns this active record after reading the attributes from the entry with the given identifier.
293
+     *
294
+     * @param mixed $id
295
+     * @return $this
296
+     * @throws ActiveRecordException on failure.
297
+     */
298
+    abstract public function read($id);
299
+
300
+    /**
301
+     * Returns the PDO.
302
+     *
303
+     * @return \PDO the PDO.
304
+     */
305
+    abstract public function getPdo();
306
+
307
+    /**
308
+     * Set the ID.
309
+     *
310
+     * @param int $id
311
+     * @return $this
312
+     */
313
+    abstract protected function setId($id);
314
+
315
+    /**
316
+     * Returns the ID.
317
+     *
318
+     * @return null|int The ID.
319
+     */
320
+    abstract protected function getId();
321
+
322
+    /**
323
+     * Returns the active record table.
324
+     *
325
+     * @return string the active record table name.
326
+     */
327
+    abstract protected function getActiveRecordTable();
328
+
329
+    /**
330
+     * Returns the name -> variable mapping for the table definition.
331
+     * @return Array The mapping
332
+     */
333
+    abstract protected function getActiveRecordColumns();
334 334
 }
Please login to merge, or discard this patch.