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