Completed
Push — v2 ( e36f84...6821a7 )
by Berend
02:44
created
src/SchemaBuilder.php 1 patch
Indentation   +150 added lines, -150 removed lines patch added patch discarded remove patch
@@ -18,169 +18,169 @@
 block discarded – undo
18 18
  */
19 19
 class SchemaBuilder
20 20
 {
21
-	/**
22
-	 * builds a MySQL constraint statement for the given parameters
23
-	 * @param string $parentTable
24
-	 * @param string $parentColumn
25
-	 * @param string $childTable
26
-	 * @param string $childColumn
27
-	 * @return string The MySQL table constraint string
28
-	 */
29
-	public static function buildConstraintOnDeleteCascade($parentTable, $parentColumn, $childTable, $childColumn)
30
-	{
31
-		$template = <<<SQL
21
+    /**
22
+     * builds a MySQL constraint statement for the given parameters
23
+     * @param string $parentTable
24
+     * @param string $parentColumn
25
+     * @param string $childTable
26
+     * @param string $childColumn
27
+     * @return string The MySQL table constraint string
28
+     */
29
+    public static function buildConstraintOnDeleteCascade($parentTable, $parentColumn, $childTable, $childColumn)
30
+    {
31
+        $template = <<<SQL
32 32
 ALTER TABLE `%s`
33 33
 ADD CONSTRAINT
34 34
 FOREIGN KEY (`%s`)
35 35
 REFERENCES `%s`(`%s`)
36 36
 ON DELETE CASCADE;
37 37
 SQL;
38
-		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
39
-	}
38
+        return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
39
+    }
40 40
  
41
-	public static function buildConstraintOnDeleteSetNull($parentTable, $parentColumn, $childTable, $childColumn)
42
-	{
43
-		$template = <<<SQL
41
+    public static function buildConstraintOnDeleteSetNull($parentTable, $parentColumn, $childTable, $childColumn)
42
+    {
43
+        $template = <<<SQL
44 44
 ALTER TABLE `%s`
45 45
 ADD CONSTRAINT 
46 46
 FOREIGN KEY (`%s`) 
47 47
 REFERENCES `%s` (`%s`)
48 48
 ON DELETE SET NULL
49 49
 SQL;
50
-  		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
51
-	}
52
-
53
-	/**
54
-	 * Returns the type string as it should appear in the mysql create table statement for the given column
55
-	 * @return string The type string
56
-	 */
57
-	public static function getDatabaseTypeString($colName, $type, $length)
58
-	{
59
-		switch (strtoupper($type)) {
60
-			case '':
61
-				throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
50
+            return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
51
+    }
52
+
53
+    /**
54
+     * Returns the type string as it should appear in the mysql create table statement for the given column
55
+     * @return string The type string
56
+     */
57
+    public static function getDatabaseTypeString($colName, $type, $length)
58
+    {
59
+        switch (strtoupper($type)) {
60
+            case '':
61
+                throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
62 62
 			
63
-			case 'BOOL';
64
-			case 'BOOLEAN':
65
-			case 'DATETIME':
66
-			case 'DATE':
67
-			case 'TIME':
68
-			case 'TEXT':
69
-			case 'INT UNSIGNED':
70
-				return $type;
71
-
72
-			case 'VARCHAR':
73
-				if ($length === null) {
74
-					throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
75
-				} else {
76
-					return sprintf('%s(%d)', $type, $length);	
77
-				}
78
-
79
-			case 'INT':
80
-			case 'TINYINT':
81
-			case 'BIGINT':
82
-			default: 	
83
-				// Implicitly assuming that non-specified cases are correct without a length parameter
84
-				if ($length === null) {
85
-					return $type;
86
-				} else {
87
-					return sprintf('%s(%d)', $type, $length);	
88
-				}
89
-		}
90
-	}
91
-
92
-	/**
93
-	 * Builds the part of a MySQL create table statement that corresponds to the supplied column
94
-	 * @param string $colName 	Name of the database column
95
-	 * @param string $type 		The type of the string
96
-	 * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
97
-	 * @return string
98
-	 */
99
-	public static function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
100
-	{
101
-		$stmnt = sprintf('`%s` %s ', $colName, self::getDatabaseTypeString($colName, $type, $length));
102
-		if ($properties & ColumnProperty::NOT_NULL) {
103
-			$stmnt .= 'NOT NULL ';
104
-		} else {
105
-			$stmnt .= 'NULL ';
106
-		}
107
-
108
-		if ($default !== NULL) {
109
-			$stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
110
-		}
111
-
112
-		if ($properties & ColumnProperty::AUTO_INCREMENT) {
113
-			$stmnt .= 'AUTO_INCREMENT ';
114
-		}
115
-
116
-		if ($properties & ColumnProperty::UNIQUE) {
117
-			$stmnt .= 'UNIQUE ';
118
-		}
119
-
120
-		if ($properties & ColumnProperty::PRIMARY_KEY) {
121
-			$stmnt .= 'PRIMARY KEY ';
122
-		}
123
-
124
-		return $stmnt;
125
-	}
126
-
127
-	/**
128
-	 * Sorts the column statement components in the order such that the id appears first, 
129
-	 * 		followed by all other columns in alphabetical ascending order
130
-	 * @param   Array $colStatements Array of column statements
131
-	 * @return  Array
132
-	 */
133
-	private static function sortColumnStatements($colStatements)
134
-	{
135
-		// Find ID statement and put it first
136
-		$sortedStatements = [];
137
-
138
-		$sortedStatements[] = $colStatements[AbstractActiveRecord::COLUMN_NAME_ID];
139
-		unset($colStatements[AbstractActiveRecord::COLUMN_NAME_ID]);
140
-
141
-		// Sort remaining columns in alphabetical order
142
-		$columns = array_keys($colStatements);
143
-		sort($columns);
144
-		foreach ($columns as $colName) {
145
-			$sortedStatements[] = $colStatements[$colName];
146
-		}
147
-
148
-		return $sortedStatements;
149
-	}
150
-
151
-	/**
152
-	 * Builds the MySQL Create Table statement for the internal table definition
153
-	 * @return string
154
-	 */
155
-	public static function buildCreateTableSQL($tableName, $tableDefinition)
156
-	{
157
-		$columnStatements = [];
158
-		foreach ($tableDefinition as $colName => $definition) {
159
-			// Destructure column definition
160
-			$type    = $definition['type'] ?? null;
161
-			$default = $definition['default'] ?? null;
162
-			$length  = $definition['length'] ?? null;
163
-			$properties = $definition['properties'] ?? null;
164
-
165
-			if (isset($definition['relation']) && $type !== null) {
166
-				$msg = sprintf("Column \"%s\" on table \"%s\": ", $colName, $tableName);
167
-				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
168
-				throw new ActiveRecordException($msg);
169
-			} else if (isset($definition['relation'])) {
170
-				$type = AbstractActiveRecord::COLUMN_TYPE_ID;
171
-			}
172
-
173
-			$columnStatements[$colName] = self::buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
174
-		}
175
-
176
-		// Sort table (first column is id, the remaining are alphabetically sorted)
177
-		$columnStatements = self::sortColumnStatements($columnStatements);
178
-
179
-		$sql = sprintf("CREATE TABLE %s (\n%s\n) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", 
180
-			$tableName, 
181
-			implode(",\n", $columnStatements));
182
-
183
-		return $sql;
184
-	}
63
+            case 'BOOL';
64
+            case 'BOOLEAN':
65
+            case 'DATETIME':
66
+            case 'DATE':
67
+            case 'TIME':
68
+            case 'TEXT':
69
+            case 'INT UNSIGNED':
70
+                return $type;
71
+
72
+            case 'VARCHAR':
73
+                if ($length === null) {
74
+                    throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
75
+                } else {
76
+                    return sprintf('%s(%d)', $type, $length);	
77
+                }
78
+
79
+            case 'INT':
80
+            case 'TINYINT':
81
+            case 'BIGINT':
82
+            default: 	
83
+                // Implicitly assuming that non-specified cases are correct without a length parameter
84
+                if ($length === null) {
85
+                    return $type;
86
+                } else {
87
+                    return sprintf('%s(%d)', $type, $length);	
88
+                }
89
+        }
90
+    }
91
+
92
+    /**
93
+     * Builds the part of a MySQL create table statement that corresponds to the supplied column
94
+     * @param string $colName 	Name of the database column
95
+     * @param string $type 		The type of the string
96
+     * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
97
+     * @return string
98
+     */
99
+    public static function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
100
+    {
101
+        $stmnt = sprintf('`%s` %s ', $colName, self::getDatabaseTypeString($colName, $type, $length));
102
+        if ($properties & ColumnProperty::NOT_NULL) {
103
+            $stmnt .= 'NOT NULL ';
104
+        } else {
105
+            $stmnt .= 'NULL ';
106
+        }
107
+
108
+        if ($default !== NULL) {
109
+            $stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
110
+        }
111
+
112
+        if ($properties & ColumnProperty::AUTO_INCREMENT) {
113
+            $stmnt .= 'AUTO_INCREMENT ';
114
+        }
115
+
116
+        if ($properties & ColumnProperty::UNIQUE) {
117
+            $stmnt .= 'UNIQUE ';
118
+        }
119
+
120
+        if ($properties & ColumnProperty::PRIMARY_KEY) {
121
+            $stmnt .= 'PRIMARY KEY ';
122
+        }
123
+
124
+        return $stmnt;
125
+    }
126
+
127
+    /**
128
+     * Sorts the column statement components in the order such that the id appears first, 
129
+     * 		followed by all other columns in alphabetical ascending order
130
+     * @param   Array $colStatements Array of column statements
131
+     * @return  Array
132
+     */
133
+    private static function sortColumnStatements($colStatements)
134
+    {
135
+        // Find ID statement and put it first
136
+        $sortedStatements = [];
137
+
138
+        $sortedStatements[] = $colStatements[AbstractActiveRecord::COLUMN_NAME_ID];
139
+        unset($colStatements[AbstractActiveRecord::COLUMN_NAME_ID]);
140
+
141
+        // Sort remaining columns in alphabetical order
142
+        $columns = array_keys($colStatements);
143
+        sort($columns);
144
+        foreach ($columns as $colName) {
145
+            $sortedStatements[] = $colStatements[$colName];
146
+        }
147
+
148
+        return $sortedStatements;
149
+    }
150
+
151
+    /**
152
+     * Builds the MySQL Create Table statement for the internal table definition
153
+     * @return string
154
+     */
155
+    public static function buildCreateTableSQL($tableName, $tableDefinition)
156
+    {
157
+        $columnStatements = [];
158
+        foreach ($tableDefinition as $colName => $definition) {
159
+            // Destructure column definition
160
+            $type    = $definition['type'] ?? null;
161
+            $default = $definition['default'] ?? null;
162
+            $length  = $definition['length'] ?? null;
163
+            $properties = $definition['properties'] ?? null;
164
+
165
+            if (isset($definition['relation']) && $type !== null) {
166
+                $msg = sprintf("Column \"%s\" on table \"%s\": ", $colName, $tableName);
167
+                $msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
168
+                throw new ActiveRecordException($msg);
169
+            } else if (isset($definition['relation'])) {
170
+                $type = AbstractActiveRecord::COLUMN_TYPE_ID;
171
+            }
172
+
173
+            $columnStatements[$colName] = self::buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
174
+        }
175
+
176
+        // Sort table (first column is id, the remaining are alphabetically sorted)
177
+        $columnStatements = self::sortColumnStatements($columnStatements);
178
+
179
+        $sql = sprintf("CREATE TABLE %s (\n%s\n) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", 
180
+            $tableName, 
181
+            implode(",\n", $columnStatements));
182
+
183
+        return $sql;
184
+    }
185 185
 
186 186
 }
Please login to merge, or discard this patch.
src/Traits/AutoApi.php 1 patch
Indentation   +371 added lines, -371 removed lines patch added patch discarded remove patch
@@ -9,391 +9,391 @@
 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 $createHooks;
16
+    /** @var array A map of column name to functions that hook the insert function */
17
+    protected $createHooks;
18 18
 
19
-	/** @var array A map of column name to functions that hook the read function */
20
-	protected $readHooks;
19
+    /** @var array A map of column name to functions that hook the read function */
20
+    protected $readHooks;
21 21
 
22
-	/** @var array A map of column name to functions that hook the update function */
23
-	protected $updateHooks;
22
+    /** @var array A map of column name to functions that hook the update function */
23
+    protected $updateHooks;
24 24
 
25
-	/** @var array A map of column name to functions that hook the update function */
26
-	protected $deleteHooks;	
25
+    /** @var array A map of column name to functions that hook the update function */
26
+    protected $deleteHooks;	
27 27
 
28
-	/** @var array A map of column name to functions that hook the search function */
29
-	protected $searchHooks;
28
+    /** @var array A map of column name to functions that hook the search function */
29
+    protected $searchHooks;
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
-	 * @return Array an associative array containing the query parameters, and a data field containing an array of search results (associative arrays indexed by the keys in $fieldWhitelist)
42
-	 */
43
-	public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100): Array
44
-	{
45
-		$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
+     * @return Array an associative array containing the query parameters, and a data field containing an array of search results (associative arrays indexed by the keys in $fieldWhitelist)
42
+     */
43
+    public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100): Array
44
+    {
45
+        $query = $this->search();
46 46
 
47
-		// Build query
48
-		$orderColumn = $queryParams['search_order_by'] ?? null;
49
-		if (!in_array($orderColumn, $fieldWhitelist)) {
50
-			$orderColumn = null;
51
-		}
47
+        // Build query
48
+        $orderColumn = $queryParams['search_order_by'] ?? null;
49
+        if (!in_array($orderColumn, $fieldWhitelist)) {
50
+            $orderColumn = null;
51
+        }
52 52
 
53
-		$orderDirection = $queryParams['search_order_direction'] ?? null;
54
-		if ($orderColumn !== null) {
55
-			$query->orderBy($orderColumn, $orderDirection);
56
-		}
53
+        $orderDirection = $queryParams['search_order_direction'] ?? null;
54
+        if ($orderColumn !== null) {
55
+            $query->orderBy($orderColumn, $orderDirection);
56
+        }
57 57
 		
58
-		if ($whereClause !== null) {
59
-			$query->where($whereClause);
60
-		}
61
-
62
-		$limit = min((int) ($queryParams['search_limit'] ?? $maxResultLimit), $maxResultLimit);
63
-		$query->limit($limit);
64
-
65
-		$offset = $queryParams['search_offset'] ?? 0;
66
-		$query->offset($offset);
67
-
68
-		$numPages = $query->getNumberOfPages();
69
-		$currentPage = $query->getCurrentPage();
70
-
71
-		// Fetch results
72
-		$results = $query->fetchAll();
73
-		$resultsArray = [];
74
-		foreach ($results as $result) {
75
-			$resultsArray[] = $result->toArray($fieldWhitelist);
76
-		}
77
-
78
-		return [
79
-			'search_offset' => $offset,
80
-			'search_limit' => $limit,
81
-			'search_order_by' => $orderColumn,
82
-			'search_order_direction' => $orderDirection,
83
-			'search_pages' => $numPages,
84
-			'search_current' => $currentPage,
85
-			'data' => $resultsArray
86
-		];
87
-	}
88
-
89
-	/**
90
-	 * @param string|int $id the id of the current entity
91
-	 * @param Array $fieldWhitelist an array of fields that are allowed to appear in the output
92
-	 * 
93
-	 * @param Array An associative array containing the data for this record, 
94
-	 * 				where the keys are entries in $fieldWhitelist
95
-	 */
96
-	public function apiRead($id, Array $fieldWhitelist): Array
97
-	{
98
-		// @TODO: Should apiRead throw exception or return null on fail?
99
-		$this->read($id);
100
-		return $this->toArray($fieldWhitelist);
101
-	}
102
-
103
-	/* =============================================================
58
+        if ($whereClause !== null) {
59
+            $query->where($whereClause);
60
+        }
61
+
62
+        $limit = min((int) ($queryParams['search_limit'] ?? $maxResultLimit), $maxResultLimit);
63
+        $query->limit($limit);
64
+
65
+        $offset = $queryParams['search_offset'] ?? 0;
66
+        $query->offset($offset);
67
+
68
+        $numPages = $query->getNumberOfPages();
69
+        $currentPage = $query->getCurrentPage();
70
+
71
+        // Fetch results
72
+        $results = $query->fetchAll();
73
+        $resultsArray = [];
74
+        foreach ($results as $result) {
75
+            $resultsArray[] = $result->toArray($fieldWhitelist);
76
+        }
77
+
78
+        return [
79
+            'search_offset' => $offset,
80
+            'search_limit' => $limit,
81
+            'search_order_by' => $orderColumn,
82
+            'search_order_direction' => $orderDirection,
83
+            'search_pages' => $numPages,
84
+            'search_current' => $currentPage,
85
+            'data' => $resultsArray
86
+        ];
87
+    }
88
+
89
+    /**
90
+     * @param string|int $id the id of the current entity
91
+     * @param Array $fieldWhitelist an array of fields that are allowed to appear in the output
92
+     * 
93
+     * @param Array An associative array containing the data for this record, 
94
+     * 				where the keys are entries in $fieldWhitelist
95
+     */
96
+    public function apiRead($id, Array $fieldWhitelist): Array
97
+    {
98
+        // @TODO: Should apiRead throw exception or return null on fail?
99
+        $this->read($id);
100
+        return $this->toArray($fieldWhitelist);
101
+    }
102
+
103
+    /* =============================================================
104 104
 	 * ===================== Constraint validation =================
105 105
 	 * ============================================================= */
106 106
 
107
-	/**
108
-	 * Copy all table variables between two instances
109
-	 */
110
-	public function syncInstanceFrom($from)
111
-	{
112
-		foreach ($this->tableDefinition as $colName => $definition) {
113
-			$this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
114
-		}
115
-	}
116
-
117
-	private function filterInputColumns($input, $whitelist)
118
-	{
119
-		$filteredInput = $input;
120
-		foreach ($input as $colName => $value) {
121
-			if (!in_array($colName, $whitelist)) {
122
-				unset($filteredInput[$colName]);
123
-			}
124
-		}
125
-		return $filteredInput;
126
-	}
127
-
128
-	private function validateExcessKeys($input)
129
-	{
130
-		$errors = [];
131
-		foreach ($input as $colName => $value) {
132
-			if (!array_key_exists($colName, $this->tableDefinition)) {
133
-				$errors[$colName] = "Unknown input field";
134
-				continue;
135
-			}
136
-		}
137
-		return $errors;
138
-	}
139
-
140
-	private function validateImmutableColumns($input)
141
-	{
142
-		$errors = [];
143
-		foreach ($this->tableDefinition as $colName => $definition) {
144
-			$property = $definition['properties'] ?? null;
145
-			if (array_key_exists($colName, $input)
146
-				&& $property & ColumnProperty::IMMUTABLE) {
147
-				$errors[$colName] = "Field cannot be changed";
148
-			}
149
-		}
150
-		return $errors;
151
-	}
152
-
153
-	/**
154
-	 * Checks whether input values are correct:
155
-	 * 1. Checks whether a value passes the validation function for that column
156
-	 * 2. Checks whether a value supplied to a relationship column is a valid value
157
-	 */
158
-	private function validateInputValues($input)
159
-	{
160
-		$errors = [];
161
-		foreach ($this->tableDefinition as $colName => $definition) {
162
-			// Validation check 1: If validate function is present
163
-			if (array_key_exists($colName, $input) 
164
-				&& is_callable($definition['validate'] ?? null)) {
165
-				$inputValue = $input[$colName];
166
-
167
-				// If validation function fails
168
-				[$status, $message] = $definition['validate']($inputValue);
169
-				if (!$status) {
170
-					$errors[$colName] = $message;
171
-				}	
172
-			}
173
-
174
-			// Validation check 2: If relation column, check whether entity exists
175
-			$properties = $definition['properties'] ?? null;
176
-			if (isset($definition['relation'])
177
-				&& ($properties & ColumnProperty::NOT_NULL)) {
178
-				$instance = clone $definition['relation'];
179
-				try {
180
-					$instance->read($input[$colName] ?? $definition['value'] ?? null);
181
-				} catch (ActiveRecordException $e) {
182
-					$errors[$colName] = "Entity for this value doesn't exist";
183
-				}
184
-			}
185
-		}
186
-		return $errors;
187
-	}
188
-
189
-	/**
190
-	 * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
191
-	 * Determines whether there are required columns for which no data is provided
192
-	 */
193
-	private function validateMissingKeys($input)
194
-	{
195
-		$errors = [];
196
-
197
-		foreach ($this->tableDefinition as $colName => $colDefinition) {
198
-			$default = $colDefinition['default'] ?? null;
199
-			$properties = $colDefinition['properties'] ?? null;
200
-			$value = $colDefinition['value'];
201
-
202
-			// If nullable and default not set => null
203
-			// If nullable and default null => default (null)
204
-			// If nullable and default set => default (value)
205
-
206
-			// if not nullable and default not set => error
207
-			// if not nullable and default null => error
208
-			// if not nullable and default st => default (value)
209
-			// => if not nullable and default null and value not set (or null) => error message in this method
210
-			if ($properties & ColumnProperty::NOT_NULL
211
-				&& $default === null
212
-				&& !($properties & ColumnProperty::AUTO_INCREMENT)
213
-				&& (!array_key_exists($colName, $input) 
214
-					|| $input[$colName] === null 
215
-					|| (is_string($input[$colName]) && $input[$colName] === '') )
216
-				&& ($value === null
217
-					|| (is_string($value) && $value === ''))) {
218
-				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
219
-			} 
220
-		}
221
-
222
-		return $errors;
223
-	}
224
-
225
-	/**
226
-	 * Copies the values for entries in the input with matching variable names in the record definition
227
-	 * @param Array $input The input data to be loaded into $this record
228
-	 */
229
-	private function loadData($input)
230
-	{
231
-		foreach ($this->tableDefinition as $colName => $definition) {
232
-			if (array_key_exists($colName, $input)) {
233
-				$definition['value'] = $input[$colName];
234
-			}
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 apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
245
-	{
246
-		// Clone $this to new instance (for restoring if validation goes wrong)
247
-		$transaction = $this->newInstance();
248
-		$errors = [];
249
-
250
-		// Filter out all non-whitelisted input values
251
-		$input = $this->filterInputColumns($input, $createWhitelist);
252
-
253
-		// Validate excess keys
254
-		$errors += $transaction->validateExcessKeys($input);
255
-
256
-		// Validate input values (using validation function)
257
-		$errors += $transaction->validateInputValues($input);
258
-
259
-		// "Copy" data into transaction
260
-		$transaction->loadData($input);
261
-
262
-		// Run create hooks
263
-		foreach ($transaction->createHooks as $colName => $fn) {
264
-			$fn();
265
-		}
266
-
267
-		// Validate missing keys
268
-		$errors += $transaction->validateMissingKeys($input);
269
-
270
-		// If no errors, commit the pending data
271
-		if (empty($errors)) {
272
-			$this->syncInstanceFrom($transaction);
273
-
274
-			// Insert default values for not-null fields
275
-			$this->insertDefaults();
276
-
277
-			try {
278
-				(new Query($this->getPdo(), $this->getTableName()))
279
-					->insert($this->getActiveRecordColumns())
280
-					->execute();
281
-
282
-				$this->setId(intval($this->getPdo()->lastInsertId()));
283
-			} catch (\PDOException $e) {
284
-				// @TODO: Potentially filter and store mysql messages (where possible) in error messages
285
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
286
-			}
287
-
288
-			return [null, $this->toArray($readWhitelist)];
289
-		} else {
290
-			return [$errors, null];
291
-		}
292
-	}
293
-
294
-	/**
295
-	 * @param Array $input Associative array of input values
296
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
297
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
298
-	 * 					of the modified data.
299
-	 */
300
-	public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
301
-	{
302
-		$transaction = $this->newInstance();
303
-		$transaction->syncInstanceFrom($this);
304
-		$errors = [];
305
-
306
-		// Filter out all non-whitelisted input values
307
-		$input = $this->filterInputColumns($input, $updateWhitelist);
308
-
309
-		// Check for excess keys
310
-		$errors += $transaction->validateExcessKeys($input);
311
-
312
-		// Check for immutable keys
313
-		$errors += $transaction->validateImmutableColumns($input);
314
-
315
-		// Validate input values (using validation function)
316
-		$errors += $transaction->validateInputValues($input);
317
-
318
-		// "Copy" data into transaction
319
-		$transaction->loadData($input);
320
-
321
-		// Run create hooks
322
-		foreach ($transaction->updateHooks as $colName => $fn) {
323
-			$fn();
324
-		}
325
-
326
-		// Validate missing keys
327
-		$errors += $transaction->validateMissingKeys($input);
328
-
329
-		// Update database
330
-		if (empty($errors)) {
331
-			$this->syncInstanceFrom($transaction);
332
-
333
-			try {
334
-				(new Query($this->getPdo(), $this->getTableName()))
335
-					->update($this->getActiveRecordColumns())
336
-					->where(Query::Equal('id', $this->getId()))
337
-					->execute();
338
-			} catch (\PDOException $e) {
339
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
340
-			}
341
-
342
-			return [null, $this->toArray($readWhitelist)];
343
-		} else {
344
-			return [$errors, null];
345
-		}
346
-	}
347
-
348
-	/**
349
-	 * Returns this active record after reading the attributes from the entry with the given identifier.
350
-	 *
351
-	 * @param mixed $id
352
-	 * @return $this
353
-	 * @throws ActiveRecordException on failure.
354
-	 */
355
-	abstract public function read($id);
356
-
357
-	/**
358
-	 * Returns the PDO.
359
-	 *
360
-	 * @return \PDO the PDO.
361
-	 */
362
-	abstract public function getPdo();
363
-
364
-	/**
365
-	 * Set the ID.
366
-	 *
367
-	 * @param int $id
368
-	 * @return $this
369
-	 */
370
-	abstract protected function setId($id);
371
-
372
-	/**
373
-	 * Returns the ID.
374
-	 *
375
-	 * @return null|int The ID.
376
-	 */
377
-	abstract protected function getId();
378
-
379
-
380
-	/**
381
-	 * Returns the serialized form of the specified columns
382
-	 * 
383
-	 * @return Array
384
-	 */
385
-	abstract public function toArray(Array $fieldWhitelist);
386
-
387
-	/**
388
-	 * Returns the active record table.
389
-	 *
390
-	 * @return string the active record table name.
391
-	 */
392
-	abstract public function getTableName();
393
-
394
-	/**
395
-	 * Returns the name -> variable mapping for the table definition.
396
-	 * @return Array The mapping
397
-	 */
398
-	abstract protected function getActiveRecordColumns();
107
+    /**
108
+     * Copy all table variables between two instances
109
+     */
110
+    public function syncInstanceFrom($from)
111
+    {
112
+        foreach ($this->tableDefinition as $colName => $definition) {
113
+            $this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
114
+        }
115
+    }
116
+
117
+    private function filterInputColumns($input, $whitelist)
118
+    {
119
+        $filteredInput = $input;
120
+        foreach ($input as $colName => $value) {
121
+            if (!in_array($colName, $whitelist)) {
122
+                unset($filteredInput[$colName]);
123
+            }
124
+        }
125
+        return $filteredInput;
126
+    }
127
+
128
+    private function validateExcessKeys($input)
129
+    {
130
+        $errors = [];
131
+        foreach ($input as $colName => $value) {
132
+            if (!array_key_exists($colName, $this->tableDefinition)) {
133
+                $errors[$colName] = "Unknown input field";
134
+                continue;
135
+            }
136
+        }
137
+        return $errors;
138
+    }
139
+
140
+    private function validateImmutableColumns($input)
141
+    {
142
+        $errors = [];
143
+        foreach ($this->tableDefinition as $colName => $definition) {
144
+            $property = $definition['properties'] ?? null;
145
+            if (array_key_exists($colName, $input)
146
+                && $property & ColumnProperty::IMMUTABLE) {
147
+                $errors[$colName] = "Field cannot be changed";
148
+            }
149
+        }
150
+        return $errors;
151
+    }
152
+
153
+    /**
154
+     * Checks whether input values are correct:
155
+     * 1. Checks whether a value passes the validation function for that column
156
+     * 2. Checks whether a value supplied to a relationship column is a valid value
157
+     */
158
+    private function validateInputValues($input)
159
+    {
160
+        $errors = [];
161
+        foreach ($this->tableDefinition as $colName => $definition) {
162
+            // Validation check 1: If validate function is present
163
+            if (array_key_exists($colName, $input) 
164
+                && is_callable($definition['validate'] ?? null)) {
165
+                $inputValue = $input[$colName];
166
+
167
+                // If validation function fails
168
+                [$status, $message] = $definition['validate']($inputValue);
169
+                if (!$status) {
170
+                    $errors[$colName] = $message;
171
+                }	
172
+            }
173
+
174
+            // Validation check 2: If relation column, check whether entity exists
175
+            $properties = $definition['properties'] ?? null;
176
+            if (isset($definition['relation'])
177
+                && ($properties & ColumnProperty::NOT_NULL)) {
178
+                $instance = clone $definition['relation'];
179
+                try {
180
+                    $instance->read($input[$colName] ?? $definition['value'] ?? null);
181
+                } catch (ActiveRecordException $e) {
182
+                    $errors[$colName] = "Entity for this value doesn't exist";
183
+                }
184
+            }
185
+        }
186
+        return $errors;
187
+    }
188
+
189
+    /**
190
+     * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
191
+     * Determines whether there are required columns for which no data is provided
192
+     */
193
+    private function validateMissingKeys($input)
194
+    {
195
+        $errors = [];
196
+
197
+        foreach ($this->tableDefinition as $colName => $colDefinition) {
198
+            $default = $colDefinition['default'] ?? null;
199
+            $properties = $colDefinition['properties'] ?? null;
200
+            $value = $colDefinition['value'];
201
+
202
+            // If nullable and default not set => null
203
+            // If nullable and default null => default (null)
204
+            // If nullable and default set => default (value)
205
+
206
+            // if not nullable and default not set => error
207
+            // if not nullable and default null => error
208
+            // if not nullable and default st => default (value)
209
+            // => if not nullable and default null and value not set (or null) => error message in this method
210
+            if ($properties & ColumnProperty::NOT_NULL
211
+                && $default === null
212
+                && !($properties & ColumnProperty::AUTO_INCREMENT)
213
+                && (!array_key_exists($colName, $input) 
214
+                    || $input[$colName] === null 
215
+                    || (is_string($input[$colName]) && $input[$colName] === '') )
216
+                && ($value === null
217
+                    || (is_string($value) && $value === ''))) {
218
+                $errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
219
+            } 
220
+        }
221
+
222
+        return $errors;
223
+    }
224
+
225
+    /**
226
+     * Copies the values for entries in the input with matching variable names in the record definition
227
+     * @param Array $input The input data to be loaded into $this record
228
+     */
229
+    private function loadData($input)
230
+    {
231
+        foreach ($this->tableDefinition as $colName => $definition) {
232
+            if (array_key_exists($colName, $input)) {
233
+                $definition['value'] = $input[$colName];
234
+            }
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 apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
245
+    {
246
+        // Clone $this to new instance (for restoring if validation goes wrong)
247
+        $transaction = $this->newInstance();
248
+        $errors = [];
249
+
250
+        // Filter out all non-whitelisted input values
251
+        $input = $this->filterInputColumns($input, $createWhitelist);
252
+
253
+        // Validate excess keys
254
+        $errors += $transaction->validateExcessKeys($input);
255
+
256
+        // Validate input values (using validation function)
257
+        $errors += $transaction->validateInputValues($input);
258
+
259
+        // "Copy" data into transaction
260
+        $transaction->loadData($input);
261
+
262
+        // Run create hooks
263
+        foreach ($transaction->createHooks as $colName => $fn) {
264
+            $fn();
265
+        }
266
+
267
+        // Validate missing keys
268
+        $errors += $transaction->validateMissingKeys($input);
269
+
270
+        // If no errors, commit the pending data
271
+        if (empty($errors)) {
272
+            $this->syncInstanceFrom($transaction);
273
+
274
+            // Insert default values for not-null fields
275
+            $this->insertDefaults();
276
+
277
+            try {
278
+                (new Query($this->getPdo(), $this->getTableName()))
279
+                    ->insert($this->getActiveRecordColumns())
280
+                    ->execute();
281
+
282
+                $this->setId(intval($this->getPdo()->lastInsertId()));
283
+            } catch (\PDOException $e) {
284
+                // @TODO: Potentially filter and store mysql messages (where possible) in error messages
285
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
286
+            }
287
+
288
+            return [null, $this->toArray($readWhitelist)];
289
+        } else {
290
+            return [$errors, null];
291
+        }
292
+    }
293
+
294
+    /**
295
+     * @param Array $input Associative array of input values
296
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
297
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
298
+     * 					of the modified data.
299
+     */
300
+    public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
301
+    {
302
+        $transaction = $this->newInstance();
303
+        $transaction->syncInstanceFrom($this);
304
+        $errors = [];
305
+
306
+        // Filter out all non-whitelisted input values
307
+        $input = $this->filterInputColumns($input, $updateWhitelist);
308
+
309
+        // Check for excess keys
310
+        $errors += $transaction->validateExcessKeys($input);
311
+
312
+        // Check for immutable keys
313
+        $errors += $transaction->validateImmutableColumns($input);
314
+
315
+        // Validate input values (using validation function)
316
+        $errors += $transaction->validateInputValues($input);
317
+
318
+        // "Copy" data into transaction
319
+        $transaction->loadData($input);
320
+
321
+        // Run create hooks
322
+        foreach ($transaction->updateHooks as $colName => $fn) {
323
+            $fn();
324
+        }
325
+
326
+        // Validate missing keys
327
+        $errors += $transaction->validateMissingKeys($input);
328
+
329
+        // Update database
330
+        if (empty($errors)) {
331
+            $this->syncInstanceFrom($transaction);
332
+
333
+            try {
334
+                (new Query($this->getPdo(), $this->getTableName()))
335
+                    ->update($this->getActiveRecordColumns())
336
+                    ->where(Query::Equal('id', $this->getId()))
337
+                    ->execute();
338
+            } catch (\PDOException $e) {
339
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
340
+            }
341
+
342
+            return [null, $this->toArray($readWhitelist)];
343
+        } else {
344
+            return [$errors, null];
345
+        }
346
+    }
347
+
348
+    /**
349
+     * Returns this active record after reading the attributes from the entry with the given identifier.
350
+     *
351
+     * @param mixed $id
352
+     * @return $this
353
+     * @throws ActiveRecordException on failure.
354
+     */
355
+    abstract public function read($id);
356
+
357
+    /**
358
+     * Returns the PDO.
359
+     *
360
+     * @return \PDO the PDO.
361
+     */
362
+    abstract public function getPdo();
363
+
364
+    /**
365
+     * Set the ID.
366
+     *
367
+     * @param int $id
368
+     * @return $this
369
+     */
370
+    abstract protected function setId($id);
371
+
372
+    /**
373
+     * Returns the ID.
374
+     *
375
+     * @return null|int The ID.
376
+     */
377
+    abstract protected function getId();
378
+
379
+
380
+    /**
381
+     * Returns the serialized form of the specified columns
382
+     * 
383
+     * @return Array
384
+     */
385
+    abstract public function toArray(Array $fieldWhitelist);
386
+
387
+    /**
388
+     * Returns the active record table.
389
+     *
390
+     * @return string the active record table name.
391
+     */
392
+    abstract public function getTableName();
393
+
394
+    /**
395
+     * Returns the name -> variable mapping for the table definition.
396
+     * @return Array The mapping
397
+     */
398
+    abstract protected function getActiveRecordColumns();
399 399
 }
Please login to merge, or discard this patch.
src/Traits/ManyToManyRelation.php 1 patch
Indentation   +104 added lines, -104 removed lines patch added patch discarded remove patch
@@ -9,110 +9,110 @@
 block discarded – undo
9 9
 
10 10
 Trait ManyToManyRelation
11 11
 {
12
-	// These variables are relevant for internal bookkeeping (constraint generation etc)
13
-
14
-	/** @var string The name of the left column of the relation. */
15
-	private $_leftColumnName;
16
-
17
-	/** @var string The name of the right column of the relation. */
18
-	private $_rightColumnName;
19
-
20
-	/** @var string The name of the left table of the relation. */
21
-	private $_leftEntityTable;
22
-
23
-	/** @var string The name of the right table of the relation. */
24
-	private $_rightEntityTable;
25
-
26
-	/** @var \PDO The PDO object. */
27
-	protected $pdo;
28
-	/**
29
-	 * Initializes the the ManyToManyRelation trait on the included object
30
-	 * 
31
-	 * @param AbstractActiveRecord $leftEntity The left entity of the relation
32
-	 * @param int $leftVariable The reference to the variable where the id for the left entity will be stored
33
-	 * @param AbstractActiveRecord $rightEntity The left entity of the relation
34
-	 * @param int $leftVariable The reference to the variable where the id for the right entity will be stored
35
-	 * @return void
36
-	 */
37
-	protected function initManyToManyRelation(AbstractActiveRecord $leftEntity, &$leftVariable, AbstractActiveRecord $rightEntity, &$rightVariable)
38
-	{
39
-		$this->_leftEntityTable = $leftEntity->getTableName();
40
-		$this->_rightEntityTable = $rightEntity->getTableName();
41
-
42
-		if (get_class($leftEntity) === get_class($rightEntity)) {
43
-			$this->_leftColumnName = sprintf("id_%s_left", $leftEntity->getTableName());
44
-			$this->_rightColumnName = sprintf("id_%s_right", $rightEntity->getTableName());
45
-		} else {
46
-			$this->_leftColumnName = sprintf("id_%s", $leftEntity->getTableName());
47
-			$this->_rightColumnName = sprintf("id_%s", $rightEntity->getTableName());
48
-		}
49
-
50
-		$this->extendTableDefinition($this->_leftColumnName, [
51
-			'value' => &$leftVariable,
52
-			'validate' => null,
53
-			'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
54
-			'properties' => ColumnProperty::NOT_NULL
55
-		]);
56
-
57
-		$this->extendTableDefinition($this->_rightColumnName, [
58
-			'value' => &$rightVariable,
59
-			'validate' => null,
60
-			'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
61
-			'properties' => ColumnProperty::NOT_NULL
62
-		]);
63
-	}
64
-
65
-	/**
66
-	 * Build the constraints for the many-to-many relation table
67
-	 * @return void
68
-	 */
69
-	public function createTableConstraints()
70
-	{
71
-		$childTable = $this->getTableName();
72
-
73
-		$leftParentTable = $this->_leftEntityTable;
74
-		$rightParentTable = $this->_rightEntityTable;
75
-
76
-		$leftConstraint = SchemaBuilder::buildConstraintOnDeleteCascade($leftParentTable, 'id', $childTable, $this->_leftColumnName);
77
-		$rightConstraint = SchemaBuilder::buildConstraintOnDeleteCascade($rightParentTable, 'id', $childTable, $this->_rightColumnName);
78
-
79
-		$this->pdo->query($leftConstraint);
80
-		$this->pdo->query($rightConstraint);
81
-	}
82
-
83
-	/**
84
-	 * @return void
85
-	 */	
86
-	abstract public function getTableName();
87
-
88
-	/**
89
-	 * @return void
90
-	 */
91
-	abstract protected function extendTableDefinition($columnName, $definition);
12
+    // These variables are relevant for internal bookkeeping (constraint generation etc)
13
+
14
+    /** @var string The name of the left column of the relation. */
15
+    private $_leftColumnName;
16
+
17
+    /** @var string The name of the right column of the relation. */
18
+    private $_rightColumnName;
19
+
20
+    /** @var string The name of the left table of the relation. */
21
+    private $_leftEntityTable;
22
+
23
+    /** @var string The name of the right table of the relation. */
24
+    private $_rightEntityTable;
25
+
26
+    /** @var \PDO The PDO object. */
27
+    protected $pdo;
28
+    /**
29
+     * Initializes the the ManyToManyRelation trait on the included object
30
+     * 
31
+     * @param AbstractActiveRecord $leftEntity The left entity of the relation
32
+     * @param int $leftVariable The reference to the variable where the id for the left entity will be stored
33
+     * @param AbstractActiveRecord $rightEntity The left entity of the relation
34
+     * @param int $leftVariable The reference to the variable where the id for the right entity will be stored
35
+     * @return void
36
+     */
37
+    protected function initManyToManyRelation(AbstractActiveRecord $leftEntity, &$leftVariable, AbstractActiveRecord $rightEntity, &$rightVariable)
38
+    {
39
+        $this->_leftEntityTable = $leftEntity->getTableName();
40
+        $this->_rightEntityTable = $rightEntity->getTableName();
41
+
42
+        if (get_class($leftEntity) === get_class($rightEntity)) {
43
+            $this->_leftColumnName = sprintf("id_%s_left", $leftEntity->getTableName());
44
+            $this->_rightColumnName = sprintf("id_%s_right", $rightEntity->getTableName());
45
+        } else {
46
+            $this->_leftColumnName = sprintf("id_%s", $leftEntity->getTableName());
47
+            $this->_rightColumnName = sprintf("id_%s", $rightEntity->getTableName());
48
+        }
49
+
50
+        $this->extendTableDefinition($this->_leftColumnName, [
51
+            'value' => &$leftVariable,
52
+            'validate' => null,
53
+            'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
54
+            'properties' => ColumnProperty::NOT_NULL
55
+        ]);
56
+
57
+        $this->extendTableDefinition($this->_rightColumnName, [
58
+            'value' => &$rightVariable,
59
+            'validate' => null,
60
+            'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
61
+            'properties' => ColumnProperty::NOT_NULL
62
+        ]);
63
+    }
64
+
65
+    /**
66
+     * Build the constraints for the many-to-many relation table
67
+     * @return void
68
+     */
69
+    public function createTableConstraints()
70
+    {
71
+        $childTable = $this->getTableName();
72
+
73
+        $leftParentTable = $this->_leftEntityTable;
74
+        $rightParentTable = $this->_rightEntityTable;
75
+
76
+        $leftConstraint = SchemaBuilder::buildConstraintOnDeleteCascade($leftParentTable, 'id', $childTable, $this->_leftColumnName);
77
+        $rightConstraint = SchemaBuilder::buildConstraintOnDeleteCascade($rightParentTable, 'id', $childTable, $this->_rightColumnName);
78
+
79
+        $this->pdo->query($leftConstraint);
80
+        $this->pdo->query($rightConstraint);
81
+    }
82
+
83
+    /**
84
+     * @return void
85
+     */	
86
+    abstract public function getTableName();
87
+
88
+    /**
89
+     * @return void
90
+     */
91
+    abstract protected function extendTableDefinition($columnName, $definition);
92 92
 	
93
-	/**
94
-	 * @return void
95
-	 */
96
-	abstract protected function registerSearchHook($columnName, $fn);
97
-
98
-	/**
99
-	 * @return void
100
-	 */
101
-	abstract protected function registerDeleteHook($columnName, $fn);
102
-
103
-	/**
104
-	 * @return void
105
-	 */
106
-	abstract protected function registerUpdateHook($columnName, $fn);
107
-
108
-	/**
109
-	 * @return void
110
-	 */
111
-	abstract protected function registerReadHook($columnName, $fn);
112
-
113
-	/**
114
-	 * @return void
115
-	 */
116
-	abstract protected function registerCreateHook($columnName, $fn);
93
+    /**
94
+     * @return void
95
+     */
96
+    abstract protected function registerSearchHook($columnName, $fn);
97
+
98
+    /**
99
+     * @return void
100
+     */
101
+    abstract protected function registerDeleteHook($columnName, $fn);
102
+
103
+    /**
104
+     * @return void
105
+     */
106
+    abstract protected function registerUpdateHook($columnName, $fn);
107
+
108
+    /**
109
+     * @return void
110
+     */
111
+    abstract protected function registerReadHook($columnName, $fn);
112
+
113
+    /**
114
+     * @return void
115
+     */
116
+    abstract protected function registerCreateHook($columnName, $fn);
117 117
 
118 118
 }
Please login to merge, or discard this patch.
src/AbstractActiveRecord.php 1 patch
Indentation   +507 added lines, -507 removed lines patch added patch discarded remove patch
@@ -18,512 +18,512 @@
 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
-	const CREATE = 'CREATE';
25
-	const READ = 'READ';
26
-	const UPDATE = 'UPDATE';
27
-	const DELETE = 'DELETE';
28
-	const SEARCH = 'SEARCH';
29
-
30
-	/** @var \PDO The PDO object. */
31
-	protected $pdo;
32
-
33
-	/** @var null|int The ID. */
34
-	private $id;
35
-
36
-	/** @var array A map of column name to functions that hook the insert function */
37
-	protected $createHooks;
38
-
39
-	/** @var array A map of column name to functions that hook the read function */
40
-	protected $readHooks;
41
-
42
-	/** @var array A map of column name to functions that hook the update function */
43
-	protected $updateHooks;
44
-
45
-	/** @var array A map of column name to functions that hook the update function */
46
-	protected $deleteHooks;	
47
-
48
-	/** @var array A map of column name to functions that hook the search function */
49
-	protected $searchHooks;
50
-
51
-	/** @var array A list of table column definitions */
52
-	protected $tableDefinition;
53
-
54
-	/**
55
-	 * Construct an abstract active record with the given PDO.
56
-	 *
57
-	 * @param \PDO $pdo
58
-	 */
59
-	public function __construct(\PDO $pdo)
60
-	{
61
-		$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
62
-		$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
63
-
64
-		$this->setPdo($pdo);
65
-
66
-		$this->createHooks = [];
67
-		$this->readHooks = [];
68
-		$this->updateHooks = [];
69
-		$this->deleteHooks = [];
70
-		$this->searchHooks = [];
71
-		$this->tableDefinition = $this->getTableDefinition();
72
-
73
-		// Extend table definition with default ID field, throw exception if field already exists
74
-		if (array_key_exists('id', $this->tableDefinition)) {
75
-			$message = "Table definition in record contains a field with name \"id\"";
76
-			$message .= ", which is a reserved name by ActiveRecord";
77
-			throw new ActiveRecordException($message, 0);
78
-		}
79
-
80
-		$this->tableDefinition[self::COLUMN_NAME_ID] =
81
-		[
82
-			'value' => &$this->id,
83
-			'validate' => null,
84
-			'type' => self::COLUMN_TYPE_ID,
85
-			'properties' =>
86
-				ColumnProperty::NOT_NULL
87
-				| ColumnProperty::IMMUTABLE
88
-				| ColumnProperty::AUTO_INCREMENT
89
-				| ColumnProperty::PRIMARY_KEY
90
-		];
91
-	}
92
-
93
-	private function checkHookConstraints($columnName, $hookMap)
94
-	{
95
-		// Check whether column exists
96
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
97
-		{
98
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
99
-		}
100
-
101
-		// Enforcing 1 hook per table column
102
-		if (array_key_exists($columnName, $hookMap)) {
103
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
104
-			$message .= "do you have conflicting traits?";
105
-			throw new ActiveRecordException($message, 0);
106
-		}
107
-	}
108
-
109
-	public function registerHookOnAction($actionName, $columnName, $fn)
110
-	{
111
-		if (is_string($fn) && is_callable([$this, $fn])) {
112
-			$fn = [$this, $fn];
113
-		}
114
-
115
-		if (!is_callable($fn)) { 
116
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
117
-		}
118
-
119
-		switch ($actionName) {
120
-			case self::CREATE:
121
-				$this->checkHookConstraints($columnName, $this->createHooks);
122
-				$this->createHooks[$columnName] = $fn;
123
-				break;
124
-			case self::READ:
125
-				$this->checkHookConstraints($columnName, $this->readHooks);
126
-				$this->readHooks[$columnName] = $fn;
127
-				break;
128
-			case self::UPDATE:
129
-				$this->checkHookConstraints($columnName, $this->updateHooks);
130
-				$this->updateHooks[$columnName] = $fn;
131
-				break;
132
-			case self::DELETE:
133
-				$this->checkHookConstraints($columnName, $this->deleteHooks);
134
-				$this->deleteHooks[$columnName] = $fn;
135
-				break;
136
-			case self::SEARCH:
137
-				$this->checkHookConstraints($columnName, $this->searchHooks);
138
-				$this->searchHooks[$columnName] = $fn;
139
-				break;
140
-			default:
141
-				throw new ActiveRecordException("Invalid action: Can not register hook on non-existing action");
142
-		}
143
-	}
144
-
145
-	/**
146
-	 * Register a new hook for a specific column that gets called before execution of the create() method
147
-	 * Only one hook per column can be registered at a time
148
-	 * @param string $columnName The name of the column that is registered.
149
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
150
-	 */
151
-	public function registerCreateHook($columnName, $fn)
152
-	{
153
-		$this->registerHookOnAction(self::CREATE, $columnName, $fn);
154
-	}
155
-
156
-	/**
157
-	 * Register a new hook for a specific column that gets called before execution of the read() method
158
-	 * Only one hook per column can be registered at a time
159
-	 * @param string $columnName The name of the column that is registered.
160
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
161
-	 */
162
-	public function registerReadHook($columnName, $fn)
163
-	{
164
-		$this->registerHookOnAction(self::READ, $columnName, $fn);
165
-	}
166
-
167
-	/**
168
-	 * Register a new hook for a specific column that gets called before execution of the update() method
169
-	 * Only one hook per column can be registered at a time
170
-	 * @param string $columnName The name of the column that is registered.
171
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
172
-	 */
173
-	public function registerUpdateHook($columnName, $fn)
174
-	{
175
-		$this->registerHookOnAction(self::UPDATE, $columnName, $fn);
176
-	}
177
-
178
-	/**
179
-	 * Register a new hook for a specific column that gets called before execution of the delete() method
180
-	 * Only one hook per column can be registered at a time
181
-	 * @param string $columnName The name of the column that is registered.
182
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
183
-	 */
184
-	public function registerDeleteHook($columnName, $fn)
185
-	{
186
-		$this->registerHookOnAction(self::DELETE, $columnName, $fn);
187
-	}
188
-
189
-	/**
190
-	 * Register a new hook for a specific column that gets called before execution of the search() method
191
-	 * Only one hook per column can be registered at a time
192
-	 * @param string $columnName The name of the column that is registered.
193
-	 * @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; 
194
-	 */
195
-	public function registerSearchHook($columnName, $fn)
196
-	{
197
-		$this->registerHookOnAction(self::SEARCH, $columnName, $fn);
198
-	}
199
-
200
-	/**
201
-	 * Adds a new column definition to the table.
202
-	 * @param string $columnName The name of the column that is registered.
203
-	 * @param Array $definition The definition of that column.
204
-	 */
205
-	public function extendTableDefinition($columnName, $definition)
206
-	{
207
-		if ($this->tableDefinition === null) {
208
-			throw new ActiveRecordException("tableDefinition is null, has parent been initialized in constructor?");
209
-		}
210
-
211
-		// Enforcing table can only be extended with new columns
212
-		if (array_key_exists($columnName, $this->tableDefinition)) {
213
-			$message = "Table is being extended with a column that already exists, ";
214
-			$message .= "\"$columnName\" conflicts with your table definition";
215
-			throw new ActiveRecordException($message, 0);
216
-		}
217
-
218
-		$this->tableDefinition[$columnName] = $definition;
219
-	}
220
-
221
-	/**
222
-	 * Creates the entity as a table in the database
223
-	 */
224
-	public function createTable()
225
-	{
226
-		$this->pdo->query(SchemaBuilder::buildCreateTableSQL($this->getTableName(), $this->tableDefinition));
227
-	}
228
-
229
-	/**
230
-	 * Iterates over the specified constraints in the table definition, 
231
-	 * 		and applies these to the database.
232
-	 */
233
-	public function createTableConstraints()
234
-	{
235
-		// Iterate over columns, check whether "relation" field exists, if so create constraint
236
-		foreach ($this->tableDefinition as $colName => $definition) {
237
-			if (isset($definition['relation']) && $definition['relation'] instanceof AbstractActiveRecord) {
238
-				// Forge new relation
239
-				$target = $definition['relation'];
240
-				$properties = $definition['properties'] ?? 0;
241
-
242
-				if ($properties & ColumnProperty::NOT_NULL) {
243
-					$constraintSql = SchemaBuilder::buildConstraintOnDeleteCascade($target->getTableName(), 'id', $this->getTableName(), $colName);
244
-				} else {
245
-					$constraintSql = SchemaBuilder::buildConstraintOnDeleteSetNull($target->getTableName(), 'id', $this->getTableName(), $colName);
246
-				}
247
-
248
-				$this->pdo->query($constraintSql);
249
-			} else if (isset($definition['relation'])) {
250
-				$msg = sprintf("Relation constraint on column \"%s\" of table \"%s\" does not contain a valid ActiveRecord instance", 
251
-					$colName,
252
-					$this->getTableName());
253
-				throw new ActiveRecordException($msg);
254
-			}
255
-		}
256
-	}
257
-
258
-	/**
259
-	 * Returns the name -> variable mapping for the table definition.
260
-	 * @return Array The mapping
261
-	 */
262
-	protected function getActiveRecordColumns()
263
-	{
264
-		$bindings = [];
265
-		foreach ($this->tableDefinition as $colName => $definition) {
266
-
267
-			// Ignore the id column (key) when inserting or updating
268
-			if ($colName == self::COLUMN_NAME_ID) {
269
-				continue;
270
-			}
271
-
272
-			$bindings[$colName] = &$definition['value'];
273
-		}
274
-		return $bindings;
275
-	}
276
-
277
-	protected function insertDefaults()
278
-	{
279
-		// Insert default values for not-null fields
280
-		foreach ($this->tableDefinition as $colName => $colDef) {
281
-			if ($colDef['value'] === null
282
-				&& ($colDef['properties'] ?? 0) & ColumnProperty::NOT_NULL
283
-				&& isset($colDef['default'])) {
284
-				$this->tableDefinition[$colName]['value'] = $colDef['default'];
285
-			}
286
-		}		
287
-	}
288
-
289
-	/**
290
-	 * {@inheritdoc}
291
-	 */
292
-	public function create()
293
-	{
294
-		foreach ($this->createHooks as $colName => $fn) {
295
-			$fn();
296
-		}
297
-
298
-		$this->insertDefaults();
299
-
300
-		try {
301
-			(new Query($this->getPdo(), $this->getTableName()))
302
-				->insert($this->getActiveRecordColumns())
303
-				->execute();
304
-
305
-			$this->setId(intval($this->getPdo()->lastInsertId()));
306
-		} catch (\PDOException $e) {
307
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
308
-		}
309
-
310
-		return $this;
311
-	}
312
-
313
-	/**
314
-	 * {@inheritdoc}
315
-	 */
316
-	public function read($id)
317
-	{
318
-		$whereConditions = [
319
-			Query::Equal('id', $id)
320
-		];
321
-		foreach ($this->readHooks as $colName => $fn) {
322
-			$cond = $fn();
323
-			if ($cond !== null) {
324
-				$whereConditions[] = $cond;
325
-			}
326
-		}
327
-
328
-		try {
329
-			$row = (new Query($this->getPdo(), $this->getTableName()))
330
-				->select()
331
-				->where(Query::AndArray($whereConditions))
332
-				->execute()
333
-				->fetch();
21
+    const COLUMN_NAME_ID = 'id';
22
+    const COLUMN_TYPE_ID = 'INT UNSIGNED';
23
+
24
+    const CREATE = 'CREATE';
25
+    const READ = 'READ';
26
+    const UPDATE = 'UPDATE';
27
+    const DELETE = 'DELETE';
28
+    const SEARCH = 'SEARCH';
29
+
30
+    /** @var \PDO The PDO object. */
31
+    protected $pdo;
32
+
33
+    /** @var null|int The ID. */
34
+    private $id;
35
+
36
+    /** @var array A map of column name to functions that hook the insert function */
37
+    protected $createHooks;
38
+
39
+    /** @var array A map of column name to functions that hook the read function */
40
+    protected $readHooks;
41
+
42
+    /** @var array A map of column name to functions that hook the update function */
43
+    protected $updateHooks;
44
+
45
+    /** @var array A map of column name to functions that hook the update function */
46
+    protected $deleteHooks;	
47
+
48
+    /** @var array A map of column name to functions that hook the search function */
49
+    protected $searchHooks;
50
+
51
+    /** @var array A list of table column definitions */
52
+    protected $tableDefinition;
53
+
54
+    /**
55
+     * Construct an abstract active record with the given PDO.
56
+     *
57
+     * @param \PDO $pdo
58
+     */
59
+    public function __construct(\PDO $pdo)
60
+    {
61
+        $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
62
+        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
63
+
64
+        $this->setPdo($pdo);
65
+
66
+        $this->createHooks = [];
67
+        $this->readHooks = [];
68
+        $this->updateHooks = [];
69
+        $this->deleteHooks = [];
70
+        $this->searchHooks = [];
71
+        $this->tableDefinition = $this->getTableDefinition();
72
+
73
+        // Extend table definition with default ID field, throw exception if field already exists
74
+        if (array_key_exists('id', $this->tableDefinition)) {
75
+            $message = "Table definition in record contains a field with name \"id\"";
76
+            $message .= ", which is a reserved name by ActiveRecord";
77
+            throw new ActiveRecordException($message, 0);
78
+        }
79
+
80
+        $this->tableDefinition[self::COLUMN_NAME_ID] =
81
+        [
82
+            'value' => &$this->id,
83
+            'validate' => null,
84
+            'type' => self::COLUMN_TYPE_ID,
85
+            'properties' =>
86
+                ColumnProperty::NOT_NULL
87
+                | ColumnProperty::IMMUTABLE
88
+                | ColumnProperty::AUTO_INCREMENT
89
+                | ColumnProperty::PRIMARY_KEY
90
+        ];
91
+    }
92
+
93
+    private function checkHookConstraints($columnName, $hookMap)
94
+    {
95
+        // Check whether column exists
96
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
97
+        {
98
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
99
+        }
100
+
101
+        // Enforcing 1 hook per table column
102
+        if (array_key_exists($columnName, $hookMap)) {
103
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
104
+            $message .= "do you have conflicting traits?";
105
+            throw new ActiveRecordException($message, 0);
106
+        }
107
+    }
108
+
109
+    public function registerHookOnAction($actionName, $columnName, $fn)
110
+    {
111
+        if (is_string($fn) && is_callable([$this, $fn])) {
112
+            $fn = [$this, $fn];
113
+        }
114
+
115
+        if (!is_callable($fn)) { 
116
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
117
+        }
118
+
119
+        switch ($actionName) {
120
+            case self::CREATE:
121
+                $this->checkHookConstraints($columnName, $this->createHooks);
122
+                $this->createHooks[$columnName] = $fn;
123
+                break;
124
+            case self::READ:
125
+                $this->checkHookConstraints($columnName, $this->readHooks);
126
+                $this->readHooks[$columnName] = $fn;
127
+                break;
128
+            case self::UPDATE:
129
+                $this->checkHookConstraints($columnName, $this->updateHooks);
130
+                $this->updateHooks[$columnName] = $fn;
131
+                break;
132
+            case self::DELETE:
133
+                $this->checkHookConstraints($columnName, $this->deleteHooks);
134
+                $this->deleteHooks[$columnName] = $fn;
135
+                break;
136
+            case self::SEARCH:
137
+                $this->checkHookConstraints($columnName, $this->searchHooks);
138
+                $this->searchHooks[$columnName] = $fn;
139
+                break;
140
+            default:
141
+                throw new ActiveRecordException("Invalid action: Can not register hook on non-existing action");
142
+        }
143
+    }
144
+
145
+    /**
146
+     * Register a new hook for a specific column that gets called before execution of the create() method
147
+     * Only one hook per column can be registered at a time
148
+     * @param string $columnName The name of the column that is registered.
149
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
150
+     */
151
+    public function registerCreateHook($columnName, $fn)
152
+    {
153
+        $this->registerHookOnAction(self::CREATE, $columnName, $fn);
154
+    }
155
+
156
+    /**
157
+     * Register a new hook for a specific column that gets called before execution of the read() method
158
+     * Only one hook per column can be registered at a time
159
+     * @param string $columnName The name of the column that is registered.
160
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
161
+     */
162
+    public function registerReadHook($columnName, $fn)
163
+    {
164
+        $this->registerHookOnAction(self::READ, $columnName, $fn);
165
+    }
166
+
167
+    /**
168
+     * Register a new hook for a specific column that gets called before execution of the update() method
169
+     * Only one hook per column can be registered at a time
170
+     * @param string $columnName The name of the column that is registered.
171
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
172
+     */
173
+    public function registerUpdateHook($columnName, $fn)
174
+    {
175
+        $this->registerHookOnAction(self::UPDATE, $columnName, $fn);
176
+    }
177
+
178
+    /**
179
+     * Register a new hook for a specific column that gets called before execution of the delete() method
180
+     * Only one hook per column can be registered at a time
181
+     * @param string $columnName The name of the column that is registered.
182
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
183
+     */
184
+    public function registerDeleteHook($columnName, $fn)
185
+    {
186
+        $this->registerHookOnAction(self::DELETE, $columnName, $fn);
187
+    }
188
+
189
+    /**
190
+     * Register a new hook for a specific column that gets called before execution of the search() method
191
+     * Only one hook per column can be registered at a time
192
+     * @param string $columnName The name of the column that is registered.
193
+     * @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; 
194
+     */
195
+    public function registerSearchHook($columnName, $fn)
196
+    {
197
+        $this->registerHookOnAction(self::SEARCH, $columnName, $fn);
198
+    }
199
+
200
+    /**
201
+     * Adds a new column definition to the table.
202
+     * @param string $columnName The name of the column that is registered.
203
+     * @param Array $definition The definition of that column.
204
+     */
205
+    public function extendTableDefinition($columnName, $definition)
206
+    {
207
+        if ($this->tableDefinition === null) {
208
+            throw new ActiveRecordException("tableDefinition is null, has parent been initialized in constructor?");
209
+        }
210
+
211
+        // Enforcing table can only be extended with new columns
212
+        if (array_key_exists($columnName, $this->tableDefinition)) {
213
+            $message = "Table is being extended with a column that already exists, ";
214
+            $message .= "\"$columnName\" conflicts with your table definition";
215
+            throw new ActiveRecordException($message, 0);
216
+        }
217
+
218
+        $this->tableDefinition[$columnName] = $definition;
219
+    }
220
+
221
+    /**
222
+     * Creates the entity as a table in the database
223
+     */
224
+    public function createTable()
225
+    {
226
+        $this->pdo->query(SchemaBuilder::buildCreateTableSQL($this->getTableName(), $this->tableDefinition));
227
+    }
228
+
229
+    /**
230
+     * Iterates over the specified constraints in the table definition, 
231
+     * 		and applies these to the database.
232
+     */
233
+    public function createTableConstraints()
234
+    {
235
+        // Iterate over columns, check whether "relation" field exists, if so create constraint
236
+        foreach ($this->tableDefinition as $colName => $definition) {
237
+            if (isset($definition['relation']) && $definition['relation'] instanceof AbstractActiveRecord) {
238
+                // Forge new relation
239
+                $target = $definition['relation'];
240
+                $properties = $definition['properties'] ?? 0;
241
+
242
+                if ($properties & ColumnProperty::NOT_NULL) {
243
+                    $constraintSql = SchemaBuilder::buildConstraintOnDeleteCascade($target->getTableName(), 'id', $this->getTableName(), $colName);
244
+                } else {
245
+                    $constraintSql = SchemaBuilder::buildConstraintOnDeleteSetNull($target->getTableName(), 'id', $this->getTableName(), $colName);
246
+                }
247
+
248
+                $this->pdo->query($constraintSql);
249
+            } else if (isset($definition['relation'])) {
250
+                $msg = sprintf("Relation constraint on column \"%s\" of table \"%s\" does not contain a valid ActiveRecord instance", 
251
+                    $colName,
252
+                    $this->getTableName());
253
+                throw new ActiveRecordException($msg);
254
+            }
255
+        }
256
+    }
257
+
258
+    /**
259
+     * Returns the name -> variable mapping for the table definition.
260
+     * @return Array The mapping
261
+     */
262
+    protected function getActiveRecordColumns()
263
+    {
264
+        $bindings = [];
265
+        foreach ($this->tableDefinition as $colName => $definition) {
266
+
267
+            // Ignore the id column (key) when inserting or updating
268
+            if ($colName == self::COLUMN_NAME_ID) {
269
+                continue;
270
+            }
271
+
272
+            $bindings[$colName] = &$definition['value'];
273
+        }
274
+        return $bindings;
275
+    }
276
+
277
+    protected function insertDefaults()
278
+    {
279
+        // Insert default values for not-null fields
280
+        foreach ($this->tableDefinition as $colName => $colDef) {
281
+            if ($colDef['value'] === null
282
+                && ($colDef['properties'] ?? 0) & ColumnProperty::NOT_NULL
283
+                && isset($colDef['default'])) {
284
+                $this->tableDefinition[$colName]['value'] = $colDef['default'];
285
+            }
286
+        }		
287
+    }
288
+
289
+    /**
290
+     * {@inheritdoc}
291
+     */
292
+    public function create()
293
+    {
294
+        foreach ($this->createHooks as $colName => $fn) {
295
+            $fn();
296
+        }
297
+
298
+        $this->insertDefaults();
299
+
300
+        try {
301
+            (new Query($this->getPdo(), $this->getTableName()))
302
+                ->insert($this->getActiveRecordColumns())
303
+                ->execute();
304
+
305
+            $this->setId(intval($this->getPdo()->lastInsertId()));
306
+        } catch (\PDOException $e) {
307
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
308
+        }
309
+
310
+        return $this;
311
+    }
312
+
313
+    /**
314
+     * {@inheritdoc}
315
+     */
316
+    public function read($id)
317
+    {
318
+        $whereConditions = [
319
+            Query::Equal('id', $id)
320
+        ];
321
+        foreach ($this->readHooks as $colName => $fn) {
322
+            $cond = $fn();
323
+            if ($cond !== null) {
324
+                $whereConditions[] = $cond;
325
+            }
326
+        }
327
+
328
+        try {
329
+            $row = (new Query($this->getPdo(), $this->getTableName()))
330
+                ->select()
331
+                ->where(Query::AndArray($whereConditions))
332
+                ->execute()
333
+                ->fetch();
334 334
 			
335
-			if ($row === false) {
336
-				throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getTableName()));	
337
-			}
338
-
339
-			$this->fill($row)->setId($id);
340
-		} catch (\PDOException $e) {
341
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
342
-		}
343
-
344
-		return $this;
345
-	}
346
-
347
-	/**
348
-	 * {@inheritdoc}
349
-	 */
350
-	public function update()
351
-	{
352
-		foreach ($this->updateHooks as $colName => $fn) {
353
-			$fn();
354
-		}
355
-
356
-		try {
357
-			(new Query($this->getPdo(), $this->getTableName()))
358
-				->update($this->getActiveRecordColumns())
359
-				->where(Query::Equal('id', $this->getId()))
360
-				->execute();
361
-		} catch (\PDOException $e) {
362
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
363
-		}
364
-
365
-		return $this;
366
-	}
367
-
368
-	/**
369
-	 * {@inheritdoc}
370
-	 */
371
-	public function delete()
372
-	{
373
-		foreach ($this->deleteHooks as $colName => $fn) {
374
-			$fn();
375
-		}
376
-
377
-		try {
378
-			(new Query($this->getPdo(), $this->getTableName()))
379
-				->delete()
380
-				->where(Query::Equal('id', $this->getId()))
381
-				->execute();
382
-
383
-			$this->setId(null);
384
-		} catch (\PDOException $e) {
385
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
386
-		}
387
-
388
-		return $this;
389
-	}
390
-
391
-	/**
392
-	 * {@inheritdoc}
393
-	 */
394
-	public function sync()
395
-	{
396
-		if (!$this->exists()) {
397
-			return $this->create();
398
-		}
399
-
400
-		return $this->update();
401
-	}
402
-
403
-	/**
404
-	 * {@inheritdoc}
405
-	 */
406
-	public function exists()
407
-	{
408
-		return $this->getId() !== null;
409
-	}
410
-
411
-	/**
412
-	 * {@inheritdoc}
413
-	 */
414
-	public function fill(array $attributes)
415
-	{
416
-		$columns = $this->getActiveRecordColumns();
417
-		$columns['id'] = &$this->id;
418
-
419
-		foreach ($attributes as $key => $value) {
420
-			if (array_key_exists($key, $columns)) {
421
-				$columns[$key] = $value;
422
-			}
423
-		}
424
-
425
-		return $this;
426
-	}
427
-
428
-	/**
429
-	 * Returns the serialized form of the specified columns
430
-	 * 
431
-	 * @return Array
432
-	 */
433
-	public function toArray(Array $fieldWhitelist)
434
-	{
435
-		$output = [];
436
-		foreach ($this->tableDefinition as $colName => $definition) {
437
-			if (in_array($colName, $fieldWhitelist)) {
438
-				$output[$colName] = $definition['value'];
439
-			}
440
-		}
441
-
442
-		return $output;
443
-	}
444
-
445
-	/**
446
-	 * {@inheritdoc}
447
-	 */
448
-	public function search(array $ignoredTraits = [])
449
-	{
450
-		$clauses = [];
451
-		foreach ($this->searchHooks as $column => $fn) {
452
-			if (!in_array($column, $ignoredTraits)) {
453
-				$clauses[] = $fn();
454
-			}
455
-		}
456
-
457
-		return new ActiveRecordQuery($this, $clauses);
458
-	}
459
-
460
-	/**
461
-	 * Returns the PDO.
462
-	 *
463
-	 * @return \PDO the PDO.
464
-	 */
465
-	public function getPdo()
466
-	{
467
-		return $this->pdo;
468
-	}
469
-
470
-	/**
471
-	 * Set the PDO.
472
-	 *
473
-	 * @param \PDO $pdo
474
-	 * @return $this
475
-	 */
476
-	protected function setPdo($pdo)
477
-	{
478
-		$this->pdo = $pdo;
479
-
480
-		return $this;
481
-	}
482
-
483
-	/**
484
-	 * Returns the ID.
485
-	 *
486
-	 * @return null|int The ID.
487
-	 */
488
-	public function getId()
489
-	{
490
-		return $this->id;
491
-	}
492
-
493
-	/**
494
-	 * Set the ID.
495
-	 *
496
-	 * @param int $id
497
-	 * @return $this
498
-	 */
499
-	protected function setId($id)
500
-	{
501
-		$this->id = $id;
502
-
503
-		return $this;
504
-	}
505
-
506
-	public function getFinalTableDefinition()
507
-	{
508
-		return $this->tableDefinition;
509
-	}
510
-
511
-	public function newInstance()
512
-	{
513
-		return new static($this->pdo);
514
-	}
515
-
516
-	/**
517
-	 * Returns the active record table.
518
-	 *
519
-	 * @return string the active record table name.
520
-	 */
521
-	abstract public function getTableName(): string;
522
-
523
-	/**
524
-	 * Returns the active record columns.
525
-	 *
526
-	 * @return array the active record columns.
527
-	 */
528
-	abstract protected function getTableDefinition(): Array;
335
+            if ($row === false) {
336
+                throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getTableName()));	
337
+            }
338
+
339
+            $this->fill($row)->setId($id);
340
+        } catch (\PDOException $e) {
341
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
342
+        }
343
+
344
+        return $this;
345
+    }
346
+
347
+    /**
348
+     * {@inheritdoc}
349
+     */
350
+    public function update()
351
+    {
352
+        foreach ($this->updateHooks as $colName => $fn) {
353
+            $fn();
354
+        }
355
+
356
+        try {
357
+            (new Query($this->getPdo(), $this->getTableName()))
358
+                ->update($this->getActiveRecordColumns())
359
+                ->where(Query::Equal('id', $this->getId()))
360
+                ->execute();
361
+        } catch (\PDOException $e) {
362
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
363
+        }
364
+
365
+        return $this;
366
+    }
367
+
368
+    /**
369
+     * {@inheritdoc}
370
+     */
371
+    public function delete()
372
+    {
373
+        foreach ($this->deleteHooks as $colName => $fn) {
374
+            $fn();
375
+        }
376
+
377
+        try {
378
+            (new Query($this->getPdo(), $this->getTableName()))
379
+                ->delete()
380
+                ->where(Query::Equal('id', $this->getId()))
381
+                ->execute();
382
+
383
+            $this->setId(null);
384
+        } catch (\PDOException $e) {
385
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
386
+        }
387
+
388
+        return $this;
389
+    }
390
+
391
+    /**
392
+     * {@inheritdoc}
393
+     */
394
+    public function sync()
395
+    {
396
+        if (!$this->exists()) {
397
+            return $this->create();
398
+        }
399
+
400
+        return $this->update();
401
+    }
402
+
403
+    /**
404
+     * {@inheritdoc}
405
+     */
406
+    public function exists()
407
+    {
408
+        return $this->getId() !== null;
409
+    }
410
+
411
+    /**
412
+     * {@inheritdoc}
413
+     */
414
+    public function fill(array $attributes)
415
+    {
416
+        $columns = $this->getActiveRecordColumns();
417
+        $columns['id'] = &$this->id;
418
+
419
+        foreach ($attributes as $key => $value) {
420
+            if (array_key_exists($key, $columns)) {
421
+                $columns[$key] = $value;
422
+            }
423
+        }
424
+
425
+        return $this;
426
+    }
427
+
428
+    /**
429
+     * Returns the serialized form of the specified columns
430
+     * 
431
+     * @return Array
432
+     */
433
+    public function toArray(Array $fieldWhitelist)
434
+    {
435
+        $output = [];
436
+        foreach ($this->tableDefinition as $colName => $definition) {
437
+            if (in_array($colName, $fieldWhitelist)) {
438
+                $output[$colName] = $definition['value'];
439
+            }
440
+        }
441
+
442
+        return $output;
443
+    }
444
+
445
+    /**
446
+     * {@inheritdoc}
447
+     */
448
+    public function search(array $ignoredTraits = [])
449
+    {
450
+        $clauses = [];
451
+        foreach ($this->searchHooks as $column => $fn) {
452
+            if (!in_array($column, $ignoredTraits)) {
453
+                $clauses[] = $fn();
454
+            }
455
+        }
456
+
457
+        return new ActiveRecordQuery($this, $clauses);
458
+    }
459
+
460
+    /**
461
+     * Returns the PDO.
462
+     *
463
+     * @return \PDO the PDO.
464
+     */
465
+    public function getPdo()
466
+    {
467
+        return $this->pdo;
468
+    }
469
+
470
+    /**
471
+     * Set the PDO.
472
+     *
473
+     * @param \PDO $pdo
474
+     * @return $this
475
+     */
476
+    protected function setPdo($pdo)
477
+    {
478
+        $this->pdo = $pdo;
479
+
480
+        return $this;
481
+    }
482
+
483
+    /**
484
+     * Returns the ID.
485
+     *
486
+     * @return null|int The ID.
487
+     */
488
+    public function getId()
489
+    {
490
+        return $this->id;
491
+    }
492
+
493
+    /**
494
+     * Set the ID.
495
+     *
496
+     * @param int $id
497
+     * @return $this
498
+     */
499
+    protected function setId($id)
500
+    {
501
+        $this->id = $id;
502
+
503
+        return $this;
504
+    }
505
+
506
+    public function getFinalTableDefinition()
507
+    {
508
+        return $this->tableDefinition;
509
+    }
510
+
511
+    public function newInstance()
512
+    {
513
+        return new static($this->pdo);
514
+    }
515
+
516
+    /**
517
+     * Returns the active record table.
518
+     *
519
+     * @return string the active record table name.
520
+     */
521
+    abstract public function getTableName(): string;
522
+
523
+    /**
524
+     * Returns the active record columns.
525
+     *
526
+     * @return array the active record columns.
527
+     */
528
+    abstract protected function getTableDefinition(): Array;
529 529
 }
Please login to merge, or discard this patch.