Test Failed
Push — v2 ( 078ae9...72b33f )
by Berend
03:08
created
src/Traits/AutoApi.php 1 patch
Indentation   +373 added lines, -373 removed lines patch added patch discarded remove patch
@@ -9,393 +9,393 @@
 block discarded – undo
9 9
 
10 10
 trait AutoApi
11 11
 {
12
-	/* =======================================================================
12
+    /* =======================================================================
13 13
 	 * ===================== Automatic API Support ===========================
14 14
 	 * ======================================================================= */
15 15
 
16
-	/** @var array A map of column name to functions that hook the insert function */
17
-	protected $registeredCreateHooks;
16
+    /** @var array A map of column name to functions that hook the insert function */
17
+    protected $registeredCreateHooks;
18 18
 
19
-	/** @var array A map of column name to functions that hook the read function */
20
-	protected $registeredReadHooks;
19
+    /** @var array A map of column name to functions that hook the read function */
20
+    protected $registeredReadHooks;
21 21
 
22
-	/** @var array A map of column name to functions that hook the update function */
23
-	protected $registeredUpdateHooks;
22
+    /** @var array A map of column name to functions that hook the update function */
23
+    protected $registeredUpdateHooks;
24 24
 
25
-	/** @var array A map of column name to functions that hook the update function */
26
-	protected $registeredDeleteHooks;	
25
+    /** @var array A map of column name to functions that hook the update function */
26
+    protected $registeredDeleteHooks;	
27 27
 
28
-	/** @var array A map of column name to functions that hook the search function */
29
-	protected $registeredSearchHooks;
28
+    /** @var array A map of column name to functions that hook the search function */
29
+    protected $registeredSearchHooks;
30 30
 
31
-	/** @var array A list of table column definitions */
32
-	protected $tableDefinition;
31
+    /** @var array A list of table column definitions */
32
+    protected $tableDefinition;
33 33
 
34 34
 
35
-	/**
36
-	 * @param Array $queryparams associative array of query params. Reserved options are
37
-	 *                             "search_order_by", "search_order_direction", "search_limit", "search_offset"
38
-	 *                             or column names corresponding to an instance of miBadger\Query\QueryExpression
39
-	 * @param Array $fieldWhitelist names of the columns that will appear in the output results
40
-	 */
41
-	public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100)
42
-	{
43
-		$query = $this->search();
35
+    /**
36
+     * @param Array $queryparams associative array of query params. Reserved options are
37
+     *                             "search_order_by", "search_order_direction", "search_limit", "search_offset"
38
+     *                             or column names corresponding to an instance of miBadger\Query\QueryExpression
39
+     * @param Array $fieldWhitelist names of the columns that will appear in the output results
40
+     */
41
+    public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100)
42
+    {
43
+        $query = $this->search();
44 44
 
45
-		// Build query
46
-		$orderColumn = $queryParams['search_order_by'] ?? null;
47
-		if (!in_array($orderColumn, $fieldWhitelist)) {
48
-			$orderColumn = null;
49
-		}
45
+        // Build query
46
+        $orderColumn = $queryParams['search_order_by'] ?? null;
47
+        if (!in_array($orderColumn, $fieldWhitelist)) {
48
+            $orderColumn = null;
49
+        }
50 50
 
51
-		$orderDirection = $queryParams['search_order_direction'] ?? null;
52
-		if ($orderColumn !== null) {
53
-			$query->orderBy($orderColumn, $orderDirection);
54
-		}
51
+        $orderDirection = $queryParams['search_order_direction'] ?? null;
52
+        if ($orderColumn !== null) {
53
+            $query->orderBy($orderColumn, $orderDirection);
54
+        }
55 55
 		
56
-		$limit = (int) $queryParams['search_limit'] ?? $maxResultLimit;
57
-		if ($limit > $maxResultLimit) {
58
-			$limit = $maxResultLimit;
59
-		}
60
-		$query->limit($limit);
61
-
62
-		$offset = $queryParams['search_offset'] ?? 0;
63
-		$query->offset($offset);
64
-
65
-		if ($whereClause !== null) {
66
-			$query->where($whereClause);
67
-		}
68
-
69
-		$numPages = $query->getNumberOfPages();
70
-		$currentPage = $query->getCurrentPage();
71
-
72
-		// Fetch results
73
-		$results = $query->fetchAll();
74
-		$resultsArray = [];
75
-		foreach ($results as $result) {
76
-			$resultsArray[] = $result->toArray($fieldWhitelist);
77
-		}
78
-
79
-		return [
80
-			'search_offset' => $offset,
81
-			'search_limit' => $limit,
82
-			'search_order_by' => $orderColumn,
83
-			'search_order_direction' => $orderDirection,
84
-			'search_pages' => $numPages,
85
-			'search_current' => $currentPage,
86
-			'data' => $resultsArray
87
-		];
88
-	}
89
-
90
-	public function toArray($fieldWhitelist)
91
-	{
92
-		$output = [];
93
-		foreach ($this->tableDefinition as $colName => $definition) {
94
-			if (in_array($colName, $fieldWhitelist)) {
95
-				$output[$colName] = $definition['value'];
96
-			}
97
-		}
98
-
99
-		return $output;
100
-	}
101
-
102
-	public function apiRead($id, Array $fieldWhitelist)
103
-	{
104
-		// @TODO: Should apiRead throw exception or return null on fail?
105
-		$this->read($id);
106
-		return $this->toArray($fieldWhitelist);
107
-	}
108
-
109
-	/* =============================================================
56
+        $limit = (int) $queryParams['search_limit'] ?? $maxResultLimit;
57
+        if ($limit > $maxResultLimit) {
58
+            $limit = $maxResultLimit;
59
+        }
60
+        $query->limit($limit);
61
+
62
+        $offset = $queryParams['search_offset'] ?? 0;
63
+        $query->offset($offset);
64
+
65
+        if ($whereClause !== null) {
66
+            $query->where($whereClause);
67
+        }
68
+
69
+        $numPages = $query->getNumberOfPages();
70
+        $currentPage = $query->getCurrentPage();
71
+
72
+        // Fetch results
73
+        $results = $query->fetchAll();
74
+        $resultsArray = [];
75
+        foreach ($results as $result) {
76
+            $resultsArray[] = $result->toArray($fieldWhitelist);
77
+        }
78
+
79
+        return [
80
+            'search_offset' => $offset,
81
+            'search_limit' => $limit,
82
+            'search_order_by' => $orderColumn,
83
+            'search_order_direction' => $orderDirection,
84
+            'search_pages' => $numPages,
85
+            'search_current' => $currentPage,
86
+            'data' => $resultsArray
87
+        ];
88
+    }
89
+
90
+    public function toArray($fieldWhitelist)
91
+    {
92
+        $output = [];
93
+        foreach ($this->tableDefinition as $colName => $definition) {
94
+            if (in_array($colName, $fieldWhitelist)) {
95
+                $output[$colName] = $definition['value'];
96
+            }
97
+        }
98
+
99
+        return $output;
100
+    }
101
+
102
+    public function apiRead($id, Array $fieldWhitelist)
103
+    {
104
+        // @TODO: Should apiRead throw exception or return null on fail?
105
+        $this->read($id);
106
+        return $this->toArray($fieldWhitelist);
107
+    }
108
+
109
+    /* =============================================================
110 110
 	 * ===================== Constraint validation =================
111 111
 	 * ============================================================= */
112 112
 
113
-	/**
114
-	 * Copy all table variables between two instances
115
-	 */
116
-	public function syncInstanceFrom($from)
117
-	{
118
-		foreach ($this->tableDefinition as $colName => $definition) {
119
-			$this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
120
-		}
121
-	}
122
-
123
-	private function filterInputColumns($input, $whitelist)
124
-	{
125
-		$filteredInput = $input;
126
-		foreach ($input as $colName => $value) {
127
-			if (!in_array($colName, $whitelist)) {
128
-				unset($filteredInput[$colName]);
129
-			}
130
-		}
131
-		return $filteredInput;
132
-	}
133
-
134
-	private function validateExcessKeys($input)
135
-	{
136
-		$errors = [];
137
-		foreach ($input as $colName => $value) {
138
-			if (!array_key_exists($colName, $this->tableDefinition)) {
139
-				$errors[$colName] = "Unknown input field";
140
-				continue;
141
-			}
142
-		}
143
-		return $errors;
144
-	}
145
-
146
-	private function validateImmutableColumns($input)
147
-	{
148
-		$errors = [];
149
-		foreach ($this->tableDefinition as $colName => $definition) {
150
-			$property = $definition['properties'] ?? null;
151
-			if (array_key_exists($colName, $input)
152
-				&& $property & ColumnProperty::IMMUTABLE) {
153
-				$errors[$colName] = "Field cannot be changed";
154
-			}
155
-		}
156
-		return $errors;
157
-	}
158
-
159
-	/**
160
-	 * Checks whether input values are correct:
161
-	 * 1. Checks whether a value passes the validation function for that column
162
-	 * 2. Checks whether a value supplied to a relationship column is a valid value
163
-	 */
164
-	private function validateInputValues($input)
165
-	{
166
-		$errors = [];
167
-		foreach ($this->tableDefinition as $colName => $definition) {
168
-			// Validation check 1: If validate function is present
169
-			if (array_key_exists($colName, $input) 
170
-				&& is_callable($definition['validate'] ?? null)) {
171
-				$inputValue = $input[$colName];
172
-
173
-				// If validation function fails
174
-				[$status, $message] = $definition['validate']($inputValue);
175
-				if (!$status) {
176
-					$errors[$colName] = $message;
177
-				}	
178
-			}
179
-
180
-			// Validation check 2: If relation column, check whether entity exists
181
-			$properties = $definition['properties'] ?? null;
182
-			if (isset($definition['relation'])
183
-				&& ($properties & ColumnProperty::NOT_NULL)) {
184
-				$instance = clone $definition['relation'];
185
-				try {
186
-					$instance->read($input[$colName] ?? $definition['value'] ?? null);
187
-				} catch (ActiveRecordException $e) {
188
-					$errors[$colName] = "Entity for this value doesn't exist";
189
-				}
190
-			}
191
-		}
192
-		return $errors;
193
-	}
194
-
195
-	/**
196
-	 * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
197
-	 * Determines whether there are required columns for which no data is provided
198
-	 */
199
-	private function validateMissingKeys($input)
200
-	{
201
-		$errors = [];
202
-
203
-		foreach ($this->tableDefinition as $colName => $colDefinition) {
204
-			$default = $colDefinition['default'] ?? null;
205
-			$properties = $colDefinition['properties'] ?? null;
206
-			$value = $colDefinition['value'];
207
-
208
-			// If nullable and default not set => null
209
-			// If nullable and default null => default (null)
210
-			// If nullable and default set => default (value)
211
-
212
-			// if not nullable and default not set => error
213
-			// if not nullable and default null => error
214
-			// if not nullable and default st => default (value)
215
-			// => if not nullable and default null and value not set (or null) => error message in this method
216
-			if ($properties & ColumnProperty::NOT_NULL
217
-				&& $default === null
218
-				&& !($properties & ColumnProperty::AUTO_INCREMENT)
219
-				&& (!array_key_exists($colName, $input) || $input[$colName] === null)
220
-				&& $value === null) {
221
-				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
222
-			} 
223
-		}
224
-
225
-		return $errors;
226
-	}
227
-
228
-	/**
229
-	 * Copies the values for entries in the input with matching variable names in the record definition
230
-	 * @param Array $input The input data to be loaded into $this record
231
-	 */
232
-	private function loadData($input)
233
-	{
234
-		foreach ($this->tableDefinition as $colName => $definition) {
235
-			if (array_key_exists($colName, $input)) {
236
-				$definition['value'] = $input[$colName];
237
-			}
238
-		}
239
-	}
240
-
241
-	/**
242
-	 * @param Array $input Associative array of input values
243
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
244
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
245
-	 * 					of the modified data.
246
-	 */
247
-	public function apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
248
-	{
249
-		// Clone $this to new instance (for restoring if validation goes wrong)
250
-		$transaction = $this->newInstance();
251
-		$errors = [];
252
-
253
-		// Filter out all non-whitelisted input values
254
-		$input = $this->filterInputColumns($input, $createWhitelist);
255
-
256
-		// Validate excess keys
257
-		$errors += $transaction->validateExcessKeys($input);
258
-
259
-		// Validate input values (using validation function)
260
-		$errors += $transaction->validateInputValues($input);
261
-
262
-		// "Copy" data into transaction
263
-		$transaction->loadData($input);
264
-
265
-		// Run create hooks
266
-		foreach ($transaction->registeredCreateHooks as $colName => $fn) {
267
-			$fn();
268
-		}
269
-
270
-		// Validate missing keys
271
-		$errors += $transaction->validateMissingKeys($input);
272
-
273
-		// If no errors, commit the pending data
274
-		if (empty($errors)) {
275
-			$this->syncInstanceFrom($transaction);
276
-
277
-			// Insert default values for not-null fields
278
-			foreach ($this->tableDefinition as $colName => $colDef) {
279
-				if ($this->tableDefinition[$colName]['value'] === null
280
-					&& isset($this->tableDefinition[$colName]['properties'])
281
-					&& $this->tableDefinition[$colName]['properties'] && ColumnProperty::NOT_NULL > 0
282
-					&& isset($this->tableDefinition[$colName]['default'])) {
283
-					$this->tableDefinition[$colName]['value'] = $this->tableDefinition[$colName]['default'];
284
-				}
285
-			}
286
-
287
-			try {
288
-				(new Query($this->getPdo(), $this->getTableName()))
289
-					->insert($this->getActiveRecordColumns())
290
-					->execute();
291
-
292
-				$this->setId(intval($this->getPdo()->lastInsertId()));
293
-			} catch (\PDOException $e) {
294
-				// @TODO: Potentially filter and store mysql messages (where possible) in error messages
295
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
296
-			}
297
-
298
-			return [null, $this->toArray($readWhitelist)];
299
-		} else {
300
-			return [$errors, null];
301
-		}
302
-	}
303
-
304
-	/**
305
-	 * @param Array $input Associative array of input values
306
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
307
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
308
-	 * 					of the modified data.
309
-	 */
310
-	public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
311
-	{
312
-		$transaction = $this->newInstance();
313
-		$transaction->syncInstanceFrom($this);
314
-		$errors = [];
315
-
316
-		// Filter out all non-whitelisted input values
317
-		$input = $this->filterInputColumns($input, $updateWhitelist);
318
-
319
-		// Check for excess keys
320
-		$errors += $transaction->validateExcessKeys($input);
321
-
322
-		// Check for immutable keys
323
-		$errors += $transaction->validateImmutableColumns($input);
324
-
325
-		// Validate input values (using validation function)
326
-		$errors += $transaction->validateInputValues($input);
327
-
328
-		// "Copy" data into transaction
329
-		$transaction->loadData($input);
330
-
331
-		// Run create hooks
332
-		foreach ($transaction->registeredUpdateHooks as $colName => $fn) {
333
-			$fn();
334
-		}
335
-
336
-		// Validate missing keys
337
-		$errors += $transaction->validateMissingKeys($input);
338
-
339
-		// Update database
340
-		if (empty($errors)) {
341
-			$this->syncInstanceFrom($transaction);
342
-
343
-			try {
344
-				(new Query($this->getPdo(), $this->getTableName()))
345
-					->update($this->getActiveRecordColumns())
346
-					->where(Query::Equal('id', $this->getId()))
347
-					->execute();
348
-			} catch (\PDOException $e) {
349
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
350
-			}
351
-
352
-			return [null, $this->toArray($readWhitelist)];
353
-		} else {
354
-			return [$errors, null];
355
-		}
356
-	}
357
-
358
-	/**
359
-	 * Returns this active record after reading the attributes from the entry with the given identifier.
360
-	 *
361
-	 * @param mixed $id
362
-	 * @return $this
363
-	 * @throws ActiveRecordException on failure.
364
-	 */
365
-	abstract public function read($id);
366
-
367
-	/**
368
-	 * Returns the PDO.
369
-	 *
370
-	 * @return \PDO the PDO.
371
-	 */
372
-	abstract public function getPdo();
373
-
374
-	/**
375
-	 * Set the ID.
376
-	 *
377
-	 * @param int $id
378
-	 * @return $this
379
-	 */
380
-	abstract protected function setId($id);
381
-
382
-	/**
383
-	 * Returns the ID.
384
-	 *
385
-	 * @return null|int The ID.
386
-	 */
387
-	abstract protected function getId();
388
-
389
-	/**
390
-	 * Returns the active record table.
391
-	 *
392
-	 * @return string the active record table name.
393
-	 */
394
-	abstract public function getTableName();
395
-
396
-	/**
397
-	 * Returns the name -> variable mapping for the table definition.
398
-	 * @return Array The mapping
399
-	 */
400
-	abstract protected function getActiveRecordColumns();
113
+    /**
114
+     * Copy all table variables between two instances
115
+     */
116
+    public function syncInstanceFrom($from)
117
+    {
118
+        foreach ($this->tableDefinition as $colName => $definition) {
119
+            $this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
120
+        }
121
+    }
122
+
123
+    private function filterInputColumns($input, $whitelist)
124
+    {
125
+        $filteredInput = $input;
126
+        foreach ($input as $colName => $value) {
127
+            if (!in_array($colName, $whitelist)) {
128
+                unset($filteredInput[$colName]);
129
+            }
130
+        }
131
+        return $filteredInput;
132
+    }
133
+
134
+    private function validateExcessKeys($input)
135
+    {
136
+        $errors = [];
137
+        foreach ($input as $colName => $value) {
138
+            if (!array_key_exists($colName, $this->tableDefinition)) {
139
+                $errors[$colName] = "Unknown input field";
140
+                continue;
141
+            }
142
+        }
143
+        return $errors;
144
+    }
145
+
146
+    private function validateImmutableColumns($input)
147
+    {
148
+        $errors = [];
149
+        foreach ($this->tableDefinition as $colName => $definition) {
150
+            $property = $definition['properties'] ?? null;
151
+            if (array_key_exists($colName, $input)
152
+                && $property & ColumnProperty::IMMUTABLE) {
153
+                $errors[$colName] = "Field cannot be changed";
154
+            }
155
+        }
156
+        return $errors;
157
+    }
158
+
159
+    /**
160
+     * Checks whether input values are correct:
161
+     * 1. Checks whether a value passes the validation function for that column
162
+     * 2. Checks whether a value supplied to a relationship column is a valid value
163
+     */
164
+    private function validateInputValues($input)
165
+    {
166
+        $errors = [];
167
+        foreach ($this->tableDefinition as $colName => $definition) {
168
+            // Validation check 1: If validate function is present
169
+            if (array_key_exists($colName, $input) 
170
+                && is_callable($definition['validate'] ?? null)) {
171
+                $inputValue = $input[$colName];
172
+
173
+                // If validation function fails
174
+                [$status, $message] = $definition['validate']($inputValue);
175
+                if (!$status) {
176
+                    $errors[$colName] = $message;
177
+                }	
178
+            }
179
+
180
+            // Validation check 2: If relation column, check whether entity exists
181
+            $properties = $definition['properties'] ?? null;
182
+            if (isset($definition['relation'])
183
+                && ($properties & ColumnProperty::NOT_NULL)) {
184
+                $instance = clone $definition['relation'];
185
+                try {
186
+                    $instance->read($input[$colName] ?? $definition['value'] ?? null);
187
+                } catch (ActiveRecordException $e) {
188
+                    $errors[$colName] = "Entity for this value doesn't exist";
189
+                }
190
+            }
191
+        }
192
+        return $errors;
193
+    }
194
+
195
+    /**
196
+     * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
197
+     * Determines whether there are required columns for which no data is provided
198
+     */
199
+    private function validateMissingKeys($input)
200
+    {
201
+        $errors = [];
202
+
203
+        foreach ($this->tableDefinition as $colName => $colDefinition) {
204
+            $default = $colDefinition['default'] ?? null;
205
+            $properties = $colDefinition['properties'] ?? null;
206
+            $value = $colDefinition['value'];
207
+
208
+            // If nullable and default not set => null
209
+            // If nullable and default null => default (null)
210
+            // If nullable and default set => default (value)
211
+
212
+            // if not nullable and default not set => error
213
+            // if not nullable and default null => error
214
+            // if not nullable and default st => default (value)
215
+            // => if not nullable and default null and value not set (or null) => error message in this method
216
+            if ($properties & ColumnProperty::NOT_NULL
217
+                && $default === null
218
+                && !($properties & ColumnProperty::AUTO_INCREMENT)
219
+                && (!array_key_exists($colName, $input) || $input[$colName] === null)
220
+                && $value === null) {
221
+                $errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
222
+            } 
223
+        }
224
+
225
+        return $errors;
226
+    }
227
+
228
+    /**
229
+     * Copies the values for entries in the input with matching variable names in the record definition
230
+     * @param Array $input The input data to be loaded into $this record
231
+     */
232
+    private function loadData($input)
233
+    {
234
+        foreach ($this->tableDefinition as $colName => $definition) {
235
+            if (array_key_exists($colName, $input)) {
236
+                $definition['value'] = $input[$colName];
237
+            }
238
+        }
239
+    }
240
+
241
+    /**
242
+     * @param Array $input Associative array of input values
243
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
244
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
245
+     * 					of the modified data.
246
+     */
247
+    public function apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
248
+    {
249
+        // Clone $this to new instance (for restoring if validation goes wrong)
250
+        $transaction = $this->newInstance();
251
+        $errors = [];
252
+
253
+        // Filter out all non-whitelisted input values
254
+        $input = $this->filterInputColumns($input, $createWhitelist);
255
+
256
+        // Validate excess keys
257
+        $errors += $transaction->validateExcessKeys($input);
258
+
259
+        // Validate input values (using validation function)
260
+        $errors += $transaction->validateInputValues($input);
261
+
262
+        // "Copy" data into transaction
263
+        $transaction->loadData($input);
264
+
265
+        // Run create hooks
266
+        foreach ($transaction->registeredCreateHooks as $colName => $fn) {
267
+            $fn();
268
+        }
269
+
270
+        // Validate missing keys
271
+        $errors += $transaction->validateMissingKeys($input);
272
+
273
+        // If no errors, commit the pending data
274
+        if (empty($errors)) {
275
+            $this->syncInstanceFrom($transaction);
276
+
277
+            // Insert default values for not-null fields
278
+            foreach ($this->tableDefinition as $colName => $colDef) {
279
+                if ($this->tableDefinition[$colName]['value'] === null
280
+                    && isset($this->tableDefinition[$colName]['properties'])
281
+                    && $this->tableDefinition[$colName]['properties'] && ColumnProperty::NOT_NULL > 0
282
+                    && isset($this->tableDefinition[$colName]['default'])) {
283
+                    $this->tableDefinition[$colName]['value'] = $this->tableDefinition[$colName]['default'];
284
+                }
285
+            }
286
+
287
+            try {
288
+                (new Query($this->getPdo(), $this->getTableName()))
289
+                    ->insert($this->getActiveRecordColumns())
290
+                    ->execute();
291
+
292
+                $this->setId(intval($this->getPdo()->lastInsertId()));
293
+            } catch (\PDOException $e) {
294
+                // @TODO: Potentially filter and store mysql messages (where possible) in error messages
295
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
296
+            }
297
+
298
+            return [null, $this->toArray($readWhitelist)];
299
+        } else {
300
+            return [$errors, null];
301
+        }
302
+    }
303
+
304
+    /**
305
+     * @param Array $input Associative array of input values
306
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
307
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
308
+     * 					of the modified data.
309
+     */
310
+    public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
311
+    {
312
+        $transaction = $this->newInstance();
313
+        $transaction->syncInstanceFrom($this);
314
+        $errors = [];
315
+
316
+        // Filter out all non-whitelisted input values
317
+        $input = $this->filterInputColumns($input, $updateWhitelist);
318
+
319
+        // Check for excess keys
320
+        $errors += $transaction->validateExcessKeys($input);
321
+
322
+        // Check for immutable keys
323
+        $errors += $transaction->validateImmutableColumns($input);
324
+
325
+        // Validate input values (using validation function)
326
+        $errors += $transaction->validateInputValues($input);
327
+
328
+        // "Copy" data into transaction
329
+        $transaction->loadData($input);
330
+
331
+        // Run create hooks
332
+        foreach ($transaction->registeredUpdateHooks as $colName => $fn) {
333
+            $fn();
334
+        }
335
+
336
+        // Validate missing keys
337
+        $errors += $transaction->validateMissingKeys($input);
338
+
339
+        // Update database
340
+        if (empty($errors)) {
341
+            $this->syncInstanceFrom($transaction);
342
+
343
+            try {
344
+                (new Query($this->getPdo(), $this->getTableName()))
345
+                    ->update($this->getActiveRecordColumns())
346
+                    ->where(Query::Equal('id', $this->getId()))
347
+                    ->execute();
348
+            } catch (\PDOException $e) {
349
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
350
+            }
351
+
352
+            return [null, $this->toArray($readWhitelist)];
353
+        } else {
354
+            return [$errors, null];
355
+        }
356
+    }
357
+
358
+    /**
359
+     * Returns this active record after reading the attributes from the entry with the given identifier.
360
+     *
361
+     * @param mixed $id
362
+     * @return $this
363
+     * @throws ActiveRecordException on failure.
364
+     */
365
+    abstract public function read($id);
366
+
367
+    /**
368
+     * Returns the PDO.
369
+     *
370
+     * @return \PDO the PDO.
371
+     */
372
+    abstract public function getPdo();
373
+
374
+    /**
375
+     * Set the ID.
376
+     *
377
+     * @param int $id
378
+     * @return $this
379
+     */
380
+    abstract protected function setId($id);
381
+
382
+    /**
383
+     * Returns the ID.
384
+     *
385
+     * @return null|int The ID.
386
+     */
387
+    abstract protected function getId();
388
+
389
+    /**
390
+     * Returns the active record table.
391
+     *
392
+     * @return string the active record table name.
393
+     */
394
+    abstract public function getTableName();
395
+
396
+    /**
397
+     * Returns the name -> variable mapping for the table definition.
398
+     * @return Array The mapping
399
+     */
400
+    abstract protected function getActiveRecordColumns();
401 401
 }
Please login to merge, or discard this patch.