Completed
Push — v2 ( 6821a7...a375df )
by Berend
02:43
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.
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/ActiveRecordInterface.php 1 patch
Indentation   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -19,72 +19,72 @@
 block discarded – undo
19 19
 interface ActiveRecordInterface
20 20
 {
21 21
 
22
-	public function __construct(\PDO $pdo);
22
+    public function __construct(\PDO $pdo);
23 23
 	
24
-	/**
25
-	 * Returns this active record after creating an entry with the records attributes.
26
-	 *
27
-	 * @return $this
28
-	 * @throws ActiveRecordException on failure.
29
-	 */
30
-	public function create();
24
+    /**
25
+     * Returns this active record after creating an entry with the records attributes.
26
+     *
27
+     * @return $this
28
+     * @throws ActiveRecordException on failure.
29
+     */
30
+    public function create();
31 31
 
32
-	/**
33
-	 * Returns this active record after reading the attributes from the entry with the given identifier.
34
-	 *
35
-	 * @param mixed $id
36
-	 * @return $this
37
-	 * @throws ActiveRecordException on failure.
38
-	 */
39
-	public function read($id);
32
+    /**
33
+     * Returns this active record after reading the attributes from the entry with the given identifier.
34
+     *
35
+     * @param mixed $id
36
+     * @return $this
37
+     * @throws ActiveRecordException on failure.
38
+     */
39
+    public function read($id);
40 40
 
41
-	/**
42
-	 * Returns this active record after updating the attributes to the corresponding entry.
43
-	 *
44
-	 * @return $this
45
-	 * @throws ActiveRecordException on failure.
46
-	 */
47
-	public function update();
41
+    /**
42
+     * Returns this active record after updating the attributes to the corresponding entry.
43
+     *
44
+     * @return $this
45
+     * @throws ActiveRecordException on failure.
46
+     */
47
+    public function update();
48 48
 
49
-	/**
50
-	 * Returns this record after deleting the corresponding entry.
51
-	 *
52
-	 * @return $this
53
-	 * @throws ActiveRecordException on failure.
54
-	 */
55
-	public function delete();
49
+    /**
50
+     * Returns this record after deleting the corresponding entry.
51
+     *
52
+     * @return $this
53
+     * @throws ActiveRecordException on failure.
54
+     */
55
+    public function delete();
56 56
 
57
-	/**
58
-	 * Returns this record after synchronizing it with the corresponding entry.
59
-	 * A new entry is created if this active record does not have a corresponding entry.
60
-	 *
61
-	 * @return $this
62
-	 * @throws ActiveRecordException on failure.
63
-	 */
64
-	public function sync();
57
+    /**
58
+     * Returns this record after synchronizing it with the corresponding entry.
59
+     * A new entry is created if this active record does not have a corresponding entry.
60
+     *
61
+     * @return $this
62
+     * @throws ActiveRecordException on failure.
63
+     */
64
+    public function sync();
65 65
 
66
-	/**
67
-	 * Returns true if this active record has a corresponding entry.
68
-	 *
69
-	 * @return bool true if this active record has a corresponding entry.
70
-	 */
71
-	public function exists();
66
+    /**
67
+     * Returns true if this active record has a corresponding entry.
68
+     *
69
+     * @return bool true if this active record has a corresponding entry.
70
+     */
71
+    public function exists();
72 72
 
73
-	/**
74
-	 * Returns this record after filling it with the given attributes.
75
-	 *
76
-	 * @param array $attributes = []
77
-	 * @return $this
78
-	 * @throws ActiveRecordException on failure.
79
-	 */
80
-	public function fill(array $attributes);
73
+    /**
74
+     * Returns this record after filling it with the given attributes.
75
+     *
76
+     * @param array $attributes = []
77
+     * @return $this
78
+     * @throws ActiveRecordException on failure.
79
+     */
80
+    public function fill(array $attributes);
81 81
 
82
-	/**
83
-	 * Returns the records with the given where, order by, limit and offset clauses.
84
-	 *
85
-	 * @param array $excludedTraits
86
-	 * @return ActiveRecordQuery the query representing the current search.
87
-	 * @throws ActiveRecordException on failure.
88
-	 */
89
-	public function search(Array $excludedTraits);
82
+    /**
83
+     * Returns the records with the given where, order by, limit and offset clauses.
84
+     *
85
+     * @param array $excludedTraits
86
+     * @return ActiveRecordQuery the query representing the current search.
87
+     * @throws ActiveRecordException on failure.
88
+     */
89
+    public function search(Array $excludedTraits);
90 90
 }
Please login to merge, or discard this patch.
src/Traits/SoftDelete.php 1 patch
Indentation   +104 added lines, -104 removed lines patch added patch discarded remove patch
@@ -9,111 +9,111 @@
 block discarded – undo
9 9
 
10 10
 trait SoftDelete
11 11
 {
12
-	/** @var boolean the soft delete status for the entity this trait is embedded into. */
13
-	protected $softDelete;
14
-
15
-	/**
16
-	 * this method is required to be called in the constructor for each class that uses this trait. 
17
-	 * It adds the required fields to the table definition and registers hooks
18
-	 */
19
-	protected function initSoftDelete()
20
-	{
21
-		$this->softDelete = false;
22
-
23
-		$this->extendTableDefinition(TRAIT_SOFT_DELETE_FIELD_KEY, [
24
-			'value' => &$this->softDelete,
25
-			'validate' => null,
26
-			'default' => 0,
27
-			'type' => 'INT',
28
-			'length' => 1,
29
-			'properties' => ColumnProperty::NOT_NULL
30
-		]);
31
-
32
-		$this->registerSearchHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteSearchHook');
33
-		$this->registerReadHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteReadHook');
34
-	}
35
-
36
-	/**
37
-	 * The hook that gets called whenever a query is made
38
-	 */
39
-	protected function softDeleteSearchHook()
40
-	{
41
-		return Query::Equal(TRAIT_SOFT_DELETE_FIELD_KEY, 0);
42
-	}
43
-
44
-	protected function softDeleteReadHook()
45
-	{
46
-		return Query::Equal(TRAIT_SOFT_DELETE_FIELD_KEY, 0);
47
-	}
48
-
49
-	/**
50
-	 * returns the name for the soft delete field in the database
51
-	 * @return string
52
-	 */
53
-	public function getSoftDeleteFieldName()
54
-	{
55
-		return TRAIT_SOFT_DELETE_FIELD_KEY;
56
-	}
12
+    /** @var boolean the soft delete status for the entity this trait is embedded into. */
13
+    protected $softDelete;
14
+
15
+    /**
16
+     * this method is required to be called in the constructor for each class that uses this trait. 
17
+     * It adds the required fields to the table definition and registers hooks
18
+     */
19
+    protected function initSoftDelete()
20
+    {
21
+        $this->softDelete = false;
22
+
23
+        $this->extendTableDefinition(TRAIT_SOFT_DELETE_FIELD_KEY, [
24
+            'value' => &$this->softDelete,
25
+            'validate' => null,
26
+            'default' => 0,
27
+            'type' => 'INT',
28
+            'length' => 1,
29
+            'properties' => ColumnProperty::NOT_NULL
30
+        ]);
31
+
32
+        $this->registerSearchHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteSearchHook');
33
+        $this->registerReadHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteReadHook');
34
+    }
35
+
36
+    /**
37
+     * The hook that gets called whenever a query is made
38
+     */
39
+    protected function softDeleteSearchHook()
40
+    {
41
+        return Query::Equal(TRAIT_SOFT_DELETE_FIELD_KEY, 0);
42
+    }
43
+
44
+    protected function softDeleteReadHook()
45
+    {
46
+        return Query::Equal(TRAIT_SOFT_DELETE_FIELD_KEY, 0);
47
+    }
48
+
49
+    /**
50
+     * returns the name for the soft delete field in the database
51
+     * @return string
52
+     */
53
+    public function getSoftDeleteFieldName()
54
+    {
55
+        return TRAIT_SOFT_DELETE_FIELD_KEY;
56
+    }
57 57
 	
58
-	/**
59
-	 * Mark the current record as soft deleted
60
-	 * @return $this
61
-	 */
62
-	public function softDelete()
63
-	{
64
-		$this->softDelete = true;
65
-		$this->update();
66
-		return $this;
67
-	}
68
-
69
-	/**
70
-	 * Undo the current soft deletion status (mark it as non-soft deleted)
71
-	 * @return $this
72
-	 */
73
-	public function softRestore()
74
-	{
75
-		$this->softDelete = false;
76
-		$this->update();
77
-		return $this;
78
-	}
79
-
80
-	/**
81
-	 * returns the current soft deletion status
82
-	 * @return $this
83
-	 */
84
-	public function getDeletionStatus() 
85
-	{
86
-		return $this->softDelete;
87
-	}
88
-
89
-	/**
90
-	 * @return void
91
-	 */
92
-	abstract protected function extendTableDefinition($columnName, $definition);
58
+    /**
59
+     * Mark the current record as soft deleted
60
+     * @return $this
61
+     */
62
+    public function softDelete()
63
+    {
64
+        $this->softDelete = true;
65
+        $this->update();
66
+        return $this;
67
+    }
68
+
69
+    /**
70
+     * Undo the current soft deletion status (mark it as non-soft deleted)
71
+     * @return $this
72
+     */
73
+    public function softRestore()
74
+    {
75
+        $this->softDelete = false;
76
+        $this->update();
77
+        return $this;
78
+    }
79
+
80
+    /**
81
+     * returns the current soft deletion status
82
+     * @return $this
83
+     */
84
+    public function getDeletionStatus() 
85
+    {
86
+        return $this->softDelete;
87
+    }
88
+
89
+    /**
90
+     * @return void
91
+     */
92
+    abstract protected function extendTableDefinition($columnName, $definition);
93 93
 	
94
-	/**
95
-	 * @return void
96
-	 */
97
-	abstract protected function registerSearchHook($columnName, $fn);
98
-
99
-	/**
100
-	 * @return void
101
-	 */
102
-	abstract protected function registerDeleteHook($columnName, $fn);
103
-
104
-	/**
105
-	 * @return void
106
-	 */
107
-	abstract protected function registerUpdateHook($columnName, $fn);
108
-
109
-	/**
110
-	 * @return void
111
-	 */
112
-	abstract protected function registerReadHook($columnName, $fn);
113
-
114
-	/**
115
-	 * @return void
116
-	 */
117
-	abstract protected function registerCreateHook($columnName, $fn);
94
+    /**
95
+     * @return void
96
+     */
97
+    abstract protected function registerSearchHook($columnName, $fn);
98
+
99
+    /**
100
+     * @return void
101
+     */
102
+    abstract protected function registerDeleteHook($columnName, $fn);
103
+
104
+    /**
105
+     * @return void
106
+     */
107
+    abstract protected function registerUpdateHook($columnName, $fn);
108
+
109
+    /**
110
+     * @return void
111
+     */
112
+    abstract protected function registerReadHook($columnName, $fn);
113
+
114
+    /**
115
+     * @return void
116
+     */
117
+    abstract protected function registerCreateHook($columnName, $fn);
118 118
 	
119 119
 }
120 120
\ No newline at end of file
Please login to merge, or discard this patch.
src/SchemaBuilder.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -268,7 +268,7 @@
 block discarded – undo
268 268
 		}
269 269
 
270 270
 		if ($default !== NULL) {
271
-			$stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
271
+			$stmnt .= 'DEFAULT '.var_export($default, true).' ';
272 272
 		}
273 273
 
274 274
 		if ($properties & ColumnProperty::AUTO_INCREMENT) {
Please login to merge, or discard this patch.
Indentation   +150 added lines, -150 removed lines patch added patch discarded remove patch
@@ -18,169 +18,169 @@
 block discarded – undo
18 18
  */
19 19
 class SchemaBuilder
20 20
 {
21
-	/**
22
-	 * builds a MySQL constraint statement for the given parameters
23
-	 * @param string $parentTable
24
-	 * @param string $parentColumn
25
-	 * @param string $childTable
26
-	 * @param string $childColumn
27
-	 * @return string The MySQL table constraint string
28
-	 */
29
-	public static function buildConstraintOnDeleteCascade($parentTable, $parentColumn, $childTable, $childColumn)
30
-	{
31
-		$template = <<<SQL
21
+    /**
22
+     * builds a MySQL constraint statement for the given parameters
23
+     * @param string $parentTable
24
+     * @param string $parentColumn
25
+     * @param string $childTable
26
+     * @param string $childColumn
27
+     * @return string The MySQL table constraint string
28
+     */
29
+    public static function buildConstraintOnDeleteCascade($parentTable, $parentColumn, $childTable, $childColumn)
30
+    {
31
+        $template = <<<SQL
32 32
 ALTER TABLE `%s`
33 33
 ADD CONSTRAINT
34 34
 FOREIGN KEY (`%s`)
35 35
 REFERENCES `%s`(`%s`)
36 36
 ON DELETE CASCADE;
37 37
 SQL;
38
-		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
39
-	}
38
+        return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
39
+    }
40 40
  
41
-	public static function buildConstraintOnDeleteSetNull($parentTable, $parentColumn, $childTable, $childColumn)
42
-	{
43
-		$template = <<<SQL
41
+    public static function buildConstraintOnDeleteSetNull($parentTable, $parentColumn, $childTable, $childColumn)
42
+    {
43
+        $template = <<<SQL
44 44
 ALTER TABLE `%s`
45 45
 ADD CONSTRAINT 
46 46
 FOREIGN KEY (`%s`) 
47 47
 REFERENCES `%s` (`%s`)
48 48
 ON DELETE SET NULL
49 49
 SQL;
50
-  		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
51
-	}
52
-
53
-	/**
54
-	 * Returns the type string as it should appear in the mysql create table statement for the given column
55
-	 * @return string The type string
56
-	 */
57
-	public static function getDatabaseTypeString($colName, $type, $length)
58
-	{
59
-		switch (strtoupper($type)) {
60
-			case '':
61
-				throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
50
+            return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
51
+    }
52
+
53
+    /**
54
+     * Returns the type string as it should appear in the mysql create table statement for the given column
55
+     * @return string The type string
56
+     */
57
+    public static function getDatabaseTypeString($colName, $type, $length)
58
+    {
59
+        switch (strtoupper($type)) {
60
+            case '':
61
+                throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
62 62
 			
63
-			case 'BOOL';
64
-			case 'BOOLEAN':
65
-			case 'DATETIME':
66
-			case 'DATE':
67
-			case 'TIME':
68
-			case 'TEXT':
69
-			case 'INT UNSIGNED':
70
-				return $type;
71
-
72
-			case 'VARCHAR':
73
-				if ($length === null) {
74
-					throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
75
-				} else {
76
-					return sprintf('%s(%d)', $type, $length);	
77
-				}
78
-
79
-			case 'INT':
80
-			case 'TINYINT':
81
-			case 'BIGINT':
82
-			default: 	
83
-				// Implicitly assuming that non-specified cases are correct without a length parameter
84
-				if ($length === null) {
85
-					return $type;
86
-				} else {
87
-					return sprintf('%s(%d)', $type, $length);	
88
-				}
89
-		}
90
-	}
91
-
92
-	/**
93
-	 * Builds the part of a MySQL create table statement that corresponds to the supplied column
94
-	 * @param string $colName 	Name of the database column
95
-	 * @param string $type 		The type of the string
96
-	 * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
97
-	 * @return string
98
-	 */
99
-	public static function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
100
-	{
101
-		$stmnt = sprintf('`%s` %s ', $colName, self::getDatabaseTypeString($colName, $type, $length));
102
-		if ($properties & ColumnProperty::NOT_NULL) {
103
-			$stmnt .= 'NOT NULL ';
104
-		} else {
105
-			$stmnt .= 'NULL ';
106
-		}
107
-
108
-		if ($default !== NULL) {
109
-			$stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
110
-		}
111
-
112
-		if ($properties & ColumnProperty::AUTO_INCREMENT) {
113
-			$stmnt .= 'AUTO_INCREMENT ';
114
-		}
115
-
116
-		if ($properties & ColumnProperty::UNIQUE) {
117
-			$stmnt .= 'UNIQUE ';
118
-		}
119
-
120
-		if ($properties & ColumnProperty::PRIMARY_KEY) {
121
-			$stmnt .= 'PRIMARY KEY ';
122
-		}
123
-
124
-		return $stmnt;
125
-	}
126
-
127
-	/**
128
-	 * Sorts the column statement components in the order such that the id appears first, 
129
-	 * 		followed by all other columns in alphabetical ascending order
130
-	 * @param   Array $colStatements Array of column statements
131
-	 * @return  Array
132
-	 */
133
-	private static function sortColumnStatements($colStatements)
134
-	{
135
-		// Find ID statement and put it first
136
-		$sortedStatements = [];
137
-
138
-		$sortedStatements[] = $colStatements[AbstractActiveRecord::COLUMN_NAME_ID];
139
-		unset($colStatements[AbstractActiveRecord::COLUMN_NAME_ID]);
140
-
141
-		// Sort remaining columns in alphabetical order
142
-		$columns = array_keys($colStatements);
143
-		sort($columns);
144
-		foreach ($columns as $colName) {
145
-			$sortedStatements[] = $colStatements[$colName];
146
-		}
147
-
148
-		return $sortedStatements;
149
-	}
150
-
151
-	/**
152
-	 * Builds the MySQL Create Table statement for the internal table definition
153
-	 * @return string
154
-	 */
155
-	public static function buildCreateTableSQL($tableName, $tableDefinition)
156
-	{
157
-		$columnStatements = [];
158
-		foreach ($tableDefinition as $colName => $definition) {
159
-			// Destructure column definition
160
-			$type    = $definition['type'] ?? null;
161
-			$default = $definition['default'] ?? null;
162
-			$length  = $definition['length'] ?? null;
163
-			$properties = $definition['properties'] ?? null;
164
-
165
-			if (isset($definition['relation']) && $type !== null) {
166
-				$msg = sprintf("Column \"%s\" on table \"%s\": ", $colName, $tableName);
167
-				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
168
-				throw new ActiveRecordException($msg);
169
-			} else if (isset($definition['relation'])) {
170
-				$type = AbstractActiveRecord::COLUMN_TYPE_ID;
171
-			}
172
-
173
-			$columnStatements[$colName] = self::buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
174
-		}
175
-
176
-		// Sort table (first column is id, the remaining are alphabetically sorted)
177
-		$columnStatements = self::sortColumnStatements($columnStatements);
178
-
179
-		$sql = sprintf("CREATE TABLE %s (\n%s\n) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", 
180
-			$tableName, 
181
-			implode(",\n", $columnStatements));
182
-
183
-		return $sql;
184
-	}
63
+            case 'BOOL';
64
+            case 'BOOLEAN':
65
+            case 'DATETIME':
66
+            case 'DATE':
67
+            case 'TIME':
68
+            case 'TEXT':
69
+            case 'INT UNSIGNED':
70
+                return $type;
71
+
72
+            case 'VARCHAR':
73
+                if ($length === null) {
74
+                    throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
75
+                } else {
76
+                    return sprintf('%s(%d)', $type, $length);	
77
+                }
78
+
79
+            case 'INT':
80
+            case 'TINYINT':
81
+            case 'BIGINT':
82
+            default: 	
83
+                // Implicitly assuming that non-specified cases are correct without a length parameter
84
+                if ($length === null) {
85
+                    return $type;
86
+                } else {
87
+                    return sprintf('%s(%d)', $type, $length);	
88
+                }
89
+        }
90
+    }
91
+
92
+    /**
93
+     * Builds the part of a MySQL create table statement that corresponds to the supplied column
94
+     * @param string $colName 	Name of the database column
95
+     * @param string $type 		The type of the string
96
+     * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
97
+     * @return string
98
+     */
99
+    public static function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
100
+    {
101
+        $stmnt = sprintf('`%s` %s ', $colName, self::getDatabaseTypeString($colName, $type, $length));
102
+        if ($properties & ColumnProperty::NOT_NULL) {
103
+            $stmnt .= 'NOT NULL ';
104
+        } else {
105
+            $stmnt .= 'NULL ';
106
+        }
107
+
108
+        if ($default !== NULL) {
109
+            $stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
110
+        }
111
+
112
+        if ($properties & ColumnProperty::AUTO_INCREMENT) {
113
+            $stmnt .= 'AUTO_INCREMENT ';
114
+        }
115
+
116
+        if ($properties & ColumnProperty::UNIQUE) {
117
+            $stmnt .= 'UNIQUE ';
118
+        }
119
+
120
+        if ($properties & ColumnProperty::PRIMARY_KEY) {
121
+            $stmnt .= 'PRIMARY KEY ';
122
+        }
123
+
124
+        return $stmnt;
125
+    }
126
+
127
+    /**
128
+     * Sorts the column statement components in the order such that the id appears first, 
129
+     * 		followed by all other columns in alphabetical ascending order
130
+     * @param   Array $colStatements Array of column statements
131
+     * @return  Array
132
+     */
133
+    private static function sortColumnStatements($colStatements)
134
+    {
135
+        // Find ID statement and put it first
136
+        $sortedStatements = [];
137
+
138
+        $sortedStatements[] = $colStatements[AbstractActiveRecord::COLUMN_NAME_ID];
139
+        unset($colStatements[AbstractActiveRecord::COLUMN_NAME_ID]);
140
+
141
+        // Sort remaining columns in alphabetical order
142
+        $columns = array_keys($colStatements);
143
+        sort($columns);
144
+        foreach ($columns as $colName) {
145
+            $sortedStatements[] = $colStatements[$colName];
146
+        }
147
+
148
+        return $sortedStatements;
149
+    }
150
+
151
+    /**
152
+     * Builds the MySQL Create Table statement for the internal table definition
153
+     * @return string
154
+     */
155
+    public static function buildCreateTableSQL($tableName, $tableDefinition)
156
+    {
157
+        $columnStatements = [];
158
+        foreach ($tableDefinition as $colName => $definition) {
159
+            // Destructure column definition
160
+            $type    = $definition['type'] ?? null;
161
+            $default = $definition['default'] ?? null;
162
+            $length  = $definition['length'] ?? null;
163
+            $properties = $definition['properties'] ?? null;
164
+
165
+            if (isset($definition['relation']) && $type !== null) {
166
+                $msg = sprintf("Column \"%s\" on table \"%s\": ", $colName, $tableName);
167
+                $msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
168
+                throw new ActiveRecordException($msg);
169
+            } else if (isset($definition['relation'])) {
170
+                $type = AbstractActiveRecord::COLUMN_TYPE_ID;
171
+            }
172
+
173
+            $columnStatements[$colName] = self::buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
174
+        }
175
+
176
+        // Sort table (first column is id, the remaining are alphabetically sorted)
177
+        $columnStatements = self::sortColumnStatements($columnStatements);
178
+
179
+        $sql = sprintf("CREATE TABLE %s (\n%s\n) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;", 
180
+            $tableName, 
181
+            implode(",\n", $columnStatements));
182
+
183
+        return $sql;
184
+    }
185 185
 
186 186
 }
Please login to merge, or discard this patch.
src/Traits/AutoApi.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -215,7 +215,7 @@
 block discarded – undo
215 215
 				&& !($properties & ColumnProperty::AUTO_INCREMENT)
216 216
 				&& (!array_key_exists($colName, $input) 
217 217
 					|| $input[$colName] === null 
218
-					|| (is_string($input[$colName]) && $input[$colName] === '') )
218
+					|| (is_string($input[$colName]) && $input[$colName] === ''))
219 219
 				&& ($value === null
220 220
 					|| (is_string($value) && $value === ''))) {
221 221
 				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
Please login to merge, or discard this patch.
Indentation   +371 added lines, -371 removed lines patch added patch discarded remove patch
@@ -9,391 +9,391 @@
 block discarded – undo
9 9
 
10 10
 trait AutoApi
11 11
 {
12
-	/* =======================================================================
12
+    /* =======================================================================
13 13
 	 * ===================== Automatic API Support ===========================
14 14
 	 * ======================================================================= */
15 15
 
16
-	/** @var array A map of column name to functions that hook the insert function */
17
-	protected $createHooks;
16
+    /** @var array A map of column name to functions that hook the insert function */
17
+    protected $createHooks;
18 18
 
19
-	/** @var array A map of column name to functions that hook the read function */
20
-	protected $readHooks;
19
+    /** @var array A map of column name to functions that hook the read function */
20
+    protected $readHooks;
21 21
 
22
-	/** @var array A map of column name to functions that hook the update function */
23
-	protected $updateHooks;
22
+    /** @var array A map of column name to functions that hook the update function */
23
+    protected $updateHooks;
24 24
 
25
-	/** @var array A map of column name to functions that hook the update function */
26
-	protected $deleteHooks;	
25
+    /** @var array A map of column name to functions that hook the update function */
26
+    protected $deleteHooks;	
27 27
 
28
-	/** @var array A map of column name to functions that hook the search function */
29
-	protected $searchHooks;
28
+    /** @var array A map of column name to functions that hook the search function */
29
+    protected $searchHooks;
30 30
 
31
-	/** @var array A list of table column definitions */
32
-	protected $tableDefinition;
31
+    /** @var array A list of table column definitions */
32
+    protected $tableDefinition;
33 33
 
34 34
 
35
-	/**
36
-	 * @param Array $queryparams associative array of query params. Reserved options are
37
-	 *                             "search_order_by", "search_order_direction", "search_limit", "search_offset"
38
-	 *                             or column names corresponding to an instance of miBadger\Query\QueryExpression
39
-	 * @param Array $fieldWhitelist names of the columns that will appear in the output results
40
-	 * 
41
-	 * @return Array an associative array containing the query parameters, and a data field containing an array of search results (associative arrays indexed by the keys in $fieldWhitelist)
42
-	 */
43
-	public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100): Array
44
-	{
45
-		$query = $this->search();
35
+    /**
36
+     * @param Array $queryparams associative array of query params. Reserved options are
37
+     *                             "search_order_by", "search_order_direction", "search_limit", "search_offset"
38
+     *                             or column names corresponding to an instance of miBadger\Query\QueryExpression
39
+     * @param Array $fieldWhitelist names of the columns that will appear in the output results
40
+     * 
41
+     * @return Array an associative array containing the query parameters, and a data field containing an array of search results (associative arrays indexed by the keys in $fieldWhitelist)
42
+     */
43
+    public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100): Array
44
+    {
45
+        $query = $this->search();
46 46
 
47
-		// Build query
48
-		$orderColumn = $queryParams['search_order_by'] ?? null;
49
-		if (!in_array($orderColumn, $fieldWhitelist)) {
50
-			$orderColumn = null;
51
-		}
47
+        // Build query
48
+        $orderColumn = $queryParams['search_order_by'] ?? null;
49
+        if (!in_array($orderColumn, $fieldWhitelist)) {
50
+            $orderColumn = null;
51
+        }
52 52
 
53
-		$orderDirection = $queryParams['search_order_direction'] ?? null;
54
-		if ($orderColumn !== null) {
55
-			$query->orderBy($orderColumn, $orderDirection);
56
-		}
53
+        $orderDirection = $queryParams['search_order_direction'] ?? null;
54
+        if ($orderColumn !== null) {
55
+            $query->orderBy($orderColumn, $orderDirection);
56
+        }
57 57
 		
58
-		if ($whereClause !== null) {
59
-			$query->where($whereClause);
60
-		}
61
-
62
-		$limit = min((int) ($queryParams['search_limit'] ?? $maxResultLimit), $maxResultLimit);
63
-		$query->limit($limit);
64
-
65
-		$offset = $queryParams['search_offset'] ?? 0;
66
-		$query->offset($offset);
67
-
68
-		$numPages = $query->getNumberOfPages();
69
-		$currentPage = $query->getCurrentPage();
70
-
71
-		// Fetch results
72
-		$results = $query->fetchAll();
73
-		$resultsArray = [];
74
-		foreach ($results as $result) {
75
-			$resultsArray[] = $result->toArray($fieldWhitelist);
76
-		}
77
-
78
-		return [
79
-			'search_offset' => $offset,
80
-			'search_limit' => $limit,
81
-			'search_order_by' => $orderColumn,
82
-			'search_order_direction' => $orderDirection,
83
-			'search_pages' => $numPages,
84
-			'search_current' => $currentPage,
85
-			'data' => $resultsArray
86
-		];
87
-	}
88
-
89
-	/**
90
-	 * @param string|int $id the id of the current entity
91
-	 * @param Array $fieldWhitelist an array of fields that are allowed to appear in the output
92
-	 * 
93
-	 * @param Array An associative array containing the data for this record, 
94
-	 * 				where the keys are entries in $fieldWhitelist
95
-	 */
96
-	public function apiRead($id, Array $fieldWhitelist): Array
97
-	{
98
-		// @TODO: Should apiRead throw exception or return null on fail?
99
-		$this->read($id);
100
-		return $this->toArray($fieldWhitelist);
101
-	}
102
-
103
-	/* =============================================================
58
+        if ($whereClause !== null) {
59
+            $query->where($whereClause);
60
+        }
61
+
62
+        $limit = min((int) ($queryParams['search_limit'] ?? $maxResultLimit), $maxResultLimit);
63
+        $query->limit($limit);
64
+
65
+        $offset = $queryParams['search_offset'] ?? 0;
66
+        $query->offset($offset);
67
+
68
+        $numPages = $query->getNumberOfPages();
69
+        $currentPage = $query->getCurrentPage();
70
+
71
+        // Fetch results
72
+        $results = $query->fetchAll();
73
+        $resultsArray = [];
74
+        foreach ($results as $result) {
75
+            $resultsArray[] = $result->toArray($fieldWhitelist);
76
+        }
77
+
78
+        return [
79
+            'search_offset' => $offset,
80
+            'search_limit' => $limit,
81
+            'search_order_by' => $orderColumn,
82
+            'search_order_direction' => $orderDirection,
83
+            'search_pages' => $numPages,
84
+            'search_current' => $currentPage,
85
+            'data' => $resultsArray
86
+        ];
87
+    }
88
+
89
+    /**
90
+     * @param string|int $id the id of the current entity
91
+     * @param Array $fieldWhitelist an array of fields that are allowed to appear in the output
92
+     * 
93
+     * @param Array An associative array containing the data for this record, 
94
+     * 				where the keys are entries in $fieldWhitelist
95
+     */
96
+    public function apiRead($id, Array $fieldWhitelist): Array
97
+    {
98
+        // @TODO: Should apiRead throw exception or return null on fail?
99
+        $this->read($id);
100
+        return $this->toArray($fieldWhitelist);
101
+    }
102
+
103
+    /* =============================================================
104 104
 	 * ===================== Constraint validation =================
105 105
 	 * ============================================================= */
106 106
 
107
-	/**
108
-	 * Copy all table variables between two instances
109
-	 */
110
-	public function syncInstanceFrom($from)
111
-	{
112
-		foreach ($this->tableDefinition as $colName => $definition) {
113
-			$this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
114
-		}
115
-	}
116
-
117
-	private function filterInputColumns($input, $whitelist)
118
-	{
119
-		$filteredInput = $input;
120
-		foreach ($input as $colName => $value) {
121
-			if (!in_array($colName, $whitelist)) {
122
-				unset($filteredInput[$colName]);
123
-			}
124
-		}
125
-		return $filteredInput;
126
-	}
127
-
128
-	private function validateExcessKeys($input)
129
-	{
130
-		$errors = [];
131
-		foreach ($input as $colName => $value) {
132
-			if (!array_key_exists($colName, $this->tableDefinition)) {
133
-				$errors[$colName] = "Unknown input field";
134
-				continue;
135
-			}
136
-		}
137
-		return $errors;
138
-	}
139
-
140
-	private function validateImmutableColumns($input)
141
-	{
142
-		$errors = [];
143
-		foreach ($this->tableDefinition as $colName => $definition) {
144
-			$property = $definition['properties'] ?? null;
145
-			if (array_key_exists($colName, $input)
146
-				&& $property & ColumnProperty::IMMUTABLE) {
147
-				$errors[$colName] = "Field cannot be changed";
148
-			}
149
-		}
150
-		return $errors;
151
-	}
152
-
153
-	/**
154
-	 * Checks whether input values are correct:
155
-	 * 1. Checks whether a value passes the validation function for that column
156
-	 * 2. Checks whether a value supplied to a relationship column is a valid value
157
-	 */
158
-	private function validateInputValues($input)
159
-	{
160
-		$errors = [];
161
-		foreach ($this->tableDefinition as $colName => $definition) {
162
-			// Validation check 1: If validate function is present
163
-			if (array_key_exists($colName, $input) 
164
-				&& is_callable($definition['validate'] ?? null)) {
165
-				$inputValue = $input[$colName];
166
-
167
-				// If validation function fails
168
-				[$status, $message] = $definition['validate']($inputValue);
169
-				if (!$status) {
170
-					$errors[$colName] = $message;
171
-				}	
172
-			}
173
-
174
-			// Validation check 2: If relation column, check whether entity exists
175
-			$properties = $definition['properties'] ?? null;
176
-			if (isset($definition['relation'])
177
-				&& ($properties & ColumnProperty::NOT_NULL)) {
178
-				$instance = clone $definition['relation'];
179
-				try {
180
-					$instance->read($input[$colName] ?? $definition['value'] ?? null);
181
-				} catch (ActiveRecordException $e) {
182
-					$errors[$colName] = "Entity for this value doesn't exist";
183
-				}
184
-			}
185
-		}
186
-		return $errors;
187
-	}
188
-
189
-	/**
190
-	 * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
191
-	 * Determines whether there are required columns for which no data is provided
192
-	 */
193
-	private function validateMissingKeys($input)
194
-	{
195
-		$errors = [];
196
-
197
-		foreach ($this->tableDefinition as $colName => $colDefinition) {
198
-			$default = $colDefinition['default'] ?? null;
199
-			$properties = $colDefinition['properties'] ?? null;
200
-			$value = $colDefinition['value'];
201
-
202
-			// If nullable and default not set => null
203
-			// If nullable and default null => default (null)
204
-			// If nullable and default set => default (value)
205
-
206
-			// if not nullable and default not set => error
207
-			// if not nullable and default null => error
208
-			// if not nullable and default st => default (value)
209
-			// => if not nullable and default null and value not set (or null) => error message in this method
210
-			if ($properties & ColumnProperty::NOT_NULL
211
-				&& $default === null
212
-				&& !($properties & ColumnProperty::AUTO_INCREMENT)
213
-				&& (!array_key_exists($colName, $input) 
214
-					|| $input[$colName] === null 
215
-					|| (is_string($input[$colName]) && $input[$colName] === '') )
216
-				&& ($value === null
217
-					|| (is_string($value) && $value === ''))) {
218
-				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
219
-			} 
220
-		}
221
-
222
-		return $errors;
223
-	}
224
-
225
-	/**
226
-	 * Copies the values for entries in the input with matching variable names in the record definition
227
-	 * @param Array $input The input data to be loaded into $this record
228
-	 */
229
-	private function loadData($input)
230
-	{
231
-		foreach ($this->tableDefinition as $colName => $definition) {
232
-			if (array_key_exists($colName, $input)) {
233
-				$definition['value'] = $input[$colName];
234
-			}
235
-		}
236
-	}
237
-
238
-	/**
239
-	 * @param Array $input Associative array of input values
240
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
241
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
242
-	 * 					of the modified data.
243
-	 */
244
-	public function apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
245
-	{
246
-		// Clone $this to new instance (for restoring if validation goes wrong)
247
-		$transaction = $this->newInstance();
248
-		$errors = [];
249
-
250
-		// Filter out all non-whitelisted input values
251
-		$input = $this->filterInputColumns($input, $createWhitelist);
252
-
253
-		// Validate excess keys
254
-		$errors += $transaction->validateExcessKeys($input);
255
-
256
-		// Validate input values (using validation function)
257
-		$errors += $transaction->validateInputValues($input);
258
-
259
-		// "Copy" data into transaction
260
-		$transaction->loadData($input);
261
-
262
-		// Run create hooks
263
-		foreach ($transaction->createHooks as $colName => $fn) {
264
-			$fn();
265
-		}
266
-
267
-		// Validate missing keys
268
-		$errors += $transaction->validateMissingKeys($input);
269
-
270
-		// If no errors, commit the pending data
271
-		if (empty($errors)) {
272
-			$this->syncInstanceFrom($transaction);
273
-
274
-			// Insert default values for not-null fields
275
-			$this->insertDefaults();
276
-
277
-			try {
278
-				(new Query($this->getPdo(), $this->getTableName()))
279
-					->insert($this->getActiveRecordColumns())
280
-					->execute();
281
-
282
-				$this->setId(intval($this->getPdo()->lastInsertId()));
283
-			} catch (\PDOException $e) {
284
-				// @TODO: Potentially filter and store mysql messages (where possible) in error messages
285
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
286
-			}
287
-
288
-			return [null, $this->toArray($readWhitelist)];
289
-		} else {
290
-			return [$errors, null];
291
-		}
292
-	}
293
-
294
-	/**
295
-	 * @param Array $input Associative array of input values
296
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
297
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
298
-	 * 					of the modified data.
299
-	 */
300
-	public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
301
-	{
302
-		$transaction = $this->newInstance();
303
-		$transaction->syncInstanceFrom($this);
304
-		$errors = [];
305
-
306
-		// Filter out all non-whitelisted input values
307
-		$input = $this->filterInputColumns($input, $updateWhitelist);
308
-
309
-		// Check for excess keys
310
-		$errors += $transaction->validateExcessKeys($input);
311
-
312
-		// Check for immutable keys
313
-		$errors += $transaction->validateImmutableColumns($input);
314
-
315
-		// Validate input values (using validation function)
316
-		$errors += $transaction->validateInputValues($input);
317
-
318
-		// "Copy" data into transaction
319
-		$transaction->loadData($input);
320
-
321
-		// Run create hooks
322
-		foreach ($transaction->updateHooks as $colName => $fn) {
323
-			$fn();
324
-		}
325
-
326
-		// Validate missing keys
327
-		$errors += $transaction->validateMissingKeys($input);
328
-
329
-		// Update database
330
-		if (empty($errors)) {
331
-			$this->syncInstanceFrom($transaction);
332
-
333
-			try {
334
-				(new Query($this->getPdo(), $this->getTableName()))
335
-					->update($this->getActiveRecordColumns())
336
-					->where(Query::Equal('id', $this->getId()))
337
-					->execute();
338
-			} catch (\PDOException $e) {
339
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
340
-			}
341
-
342
-			return [null, $this->toArray($readWhitelist)];
343
-		} else {
344
-			return [$errors, null];
345
-		}
346
-	}
347
-
348
-	/**
349
-	 * Returns this active record after reading the attributes from the entry with the given identifier.
350
-	 *
351
-	 * @param mixed $id
352
-	 * @return $this
353
-	 * @throws ActiveRecordException on failure.
354
-	 */
355
-	abstract public function read($id);
356
-
357
-	/**
358
-	 * Returns the PDO.
359
-	 *
360
-	 * @return \PDO the PDO.
361
-	 */
362
-	abstract public function getPdo();
363
-
364
-	/**
365
-	 * Set the ID.
366
-	 *
367
-	 * @param int $id
368
-	 * @return $this
369
-	 */
370
-	abstract protected function setId($id);
371
-
372
-	/**
373
-	 * Returns the ID.
374
-	 *
375
-	 * @return null|int The ID.
376
-	 */
377
-	abstract protected function getId();
378
-
379
-
380
-	/**
381
-	 * Returns the serialized form of the specified columns
382
-	 * 
383
-	 * @return Array
384
-	 */
385
-	abstract public function toArray(Array $fieldWhitelist);
386
-
387
-	/**
388
-	 * Returns the active record table.
389
-	 *
390
-	 * @return string the active record table name.
391
-	 */
392
-	abstract public function getTableName();
393
-
394
-	/**
395
-	 * Returns the name -> variable mapping for the table definition.
396
-	 * @return Array The mapping
397
-	 */
398
-	abstract protected function getActiveRecordColumns();
107
+    /**
108
+     * Copy all table variables between two instances
109
+     */
110
+    public function syncInstanceFrom($from)
111
+    {
112
+        foreach ($this->tableDefinition as $colName => $definition) {
113
+            $this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
114
+        }
115
+    }
116
+
117
+    private function filterInputColumns($input, $whitelist)
118
+    {
119
+        $filteredInput = $input;
120
+        foreach ($input as $colName => $value) {
121
+            if (!in_array($colName, $whitelist)) {
122
+                unset($filteredInput[$colName]);
123
+            }
124
+        }
125
+        return $filteredInput;
126
+    }
127
+
128
+    private function validateExcessKeys($input)
129
+    {
130
+        $errors = [];
131
+        foreach ($input as $colName => $value) {
132
+            if (!array_key_exists($colName, $this->tableDefinition)) {
133
+                $errors[$colName] = "Unknown input field";
134
+                continue;
135
+            }
136
+        }
137
+        return $errors;
138
+    }
139
+
140
+    private function validateImmutableColumns($input)
141
+    {
142
+        $errors = [];
143
+        foreach ($this->tableDefinition as $colName => $definition) {
144
+            $property = $definition['properties'] ?? null;
145
+            if (array_key_exists($colName, $input)
146
+                && $property & ColumnProperty::IMMUTABLE) {
147
+                $errors[$colName] = "Field cannot be changed";
148
+            }
149
+        }
150
+        return $errors;
151
+    }
152
+
153
+    /**
154
+     * Checks whether input values are correct:
155
+     * 1. Checks whether a value passes the validation function for that column
156
+     * 2. Checks whether a value supplied to a relationship column is a valid value
157
+     */
158
+    private function validateInputValues($input)
159
+    {
160
+        $errors = [];
161
+        foreach ($this->tableDefinition as $colName => $definition) {
162
+            // Validation check 1: If validate function is present
163
+            if (array_key_exists($colName, $input) 
164
+                && is_callable($definition['validate'] ?? null)) {
165
+                $inputValue = $input[$colName];
166
+
167
+                // If validation function fails
168
+                [$status, $message] = $definition['validate']($inputValue);
169
+                if (!$status) {
170
+                    $errors[$colName] = $message;
171
+                }	
172
+            }
173
+
174
+            // Validation check 2: If relation column, check whether entity exists
175
+            $properties = $definition['properties'] ?? null;
176
+            if (isset($definition['relation'])
177
+                && ($properties & ColumnProperty::NOT_NULL)) {
178
+                $instance = clone $definition['relation'];
179
+                try {
180
+                    $instance->read($input[$colName] ?? $definition['value'] ?? null);
181
+                } catch (ActiveRecordException $e) {
182
+                    $errors[$colName] = "Entity for this value doesn't exist";
183
+                }
184
+            }
185
+        }
186
+        return $errors;
187
+    }
188
+
189
+    /**
190
+     * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
191
+     * Determines whether there are required columns for which no data is provided
192
+     */
193
+    private function validateMissingKeys($input)
194
+    {
195
+        $errors = [];
196
+
197
+        foreach ($this->tableDefinition as $colName => $colDefinition) {
198
+            $default = $colDefinition['default'] ?? null;
199
+            $properties = $colDefinition['properties'] ?? null;
200
+            $value = $colDefinition['value'];
201
+
202
+            // If nullable and default not set => null
203
+            // If nullable and default null => default (null)
204
+            // If nullable and default set => default (value)
205
+
206
+            // if not nullable and default not set => error
207
+            // if not nullable and default null => error
208
+            // if not nullable and default st => default (value)
209
+            // => if not nullable and default null and value not set (or null) => error message in this method
210
+            if ($properties & ColumnProperty::NOT_NULL
211
+                && $default === null
212
+                && !($properties & ColumnProperty::AUTO_INCREMENT)
213
+                && (!array_key_exists($colName, $input) 
214
+                    || $input[$colName] === null 
215
+                    || (is_string($input[$colName]) && $input[$colName] === '') )
216
+                && ($value === null
217
+                    || (is_string($value) && $value === ''))) {
218
+                $errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
219
+            } 
220
+        }
221
+
222
+        return $errors;
223
+    }
224
+
225
+    /**
226
+     * Copies the values for entries in the input with matching variable names in the record definition
227
+     * @param Array $input The input data to be loaded into $this record
228
+     */
229
+    private function loadData($input)
230
+    {
231
+        foreach ($this->tableDefinition as $colName => $definition) {
232
+            if (array_key_exists($colName, $input)) {
233
+                $definition['value'] = $input[$colName];
234
+            }
235
+        }
236
+    }
237
+
238
+    /**
239
+     * @param Array $input Associative array of input values
240
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
241
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
242
+     * 					of the modified data.
243
+     */
244
+    public function apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
245
+    {
246
+        // Clone $this to new instance (for restoring if validation goes wrong)
247
+        $transaction = $this->newInstance();
248
+        $errors = [];
249
+
250
+        // Filter out all non-whitelisted input values
251
+        $input = $this->filterInputColumns($input, $createWhitelist);
252
+
253
+        // Validate excess keys
254
+        $errors += $transaction->validateExcessKeys($input);
255
+
256
+        // Validate input values (using validation function)
257
+        $errors += $transaction->validateInputValues($input);
258
+
259
+        // "Copy" data into transaction
260
+        $transaction->loadData($input);
261
+
262
+        // Run create hooks
263
+        foreach ($transaction->createHooks as $colName => $fn) {
264
+            $fn();
265
+        }
266
+
267
+        // Validate missing keys
268
+        $errors += $transaction->validateMissingKeys($input);
269
+
270
+        // If no errors, commit the pending data
271
+        if (empty($errors)) {
272
+            $this->syncInstanceFrom($transaction);
273
+
274
+            // Insert default values for not-null fields
275
+            $this->insertDefaults();
276
+
277
+            try {
278
+                (new Query($this->getPdo(), $this->getTableName()))
279
+                    ->insert($this->getActiveRecordColumns())
280
+                    ->execute();
281
+
282
+                $this->setId(intval($this->getPdo()->lastInsertId()));
283
+            } catch (\PDOException $e) {
284
+                // @TODO: Potentially filter and store mysql messages (where possible) in error messages
285
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
286
+            }
287
+
288
+            return [null, $this->toArray($readWhitelist)];
289
+        } else {
290
+            return [$errors, null];
291
+        }
292
+    }
293
+
294
+    /**
295
+     * @param Array $input Associative array of input values
296
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
297
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
298
+     * 					of the modified data.
299
+     */
300
+    public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
301
+    {
302
+        $transaction = $this->newInstance();
303
+        $transaction->syncInstanceFrom($this);
304
+        $errors = [];
305
+
306
+        // Filter out all non-whitelisted input values
307
+        $input = $this->filterInputColumns($input, $updateWhitelist);
308
+
309
+        // Check for excess keys
310
+        $errors += $transaction->validateExcessKeys($input);
311
+
312
+        // Check for immutable keys
313
+        $errors += $transaction->validateImmutableColumns($input);
314
+
315
+        // Validate input values (using validation function)
316
+        $errors += $transaction->validateInputValues($input);
317
+
318
+        // "Copy" data into transaction
319
+        $transaction->loadData($input);
320
+
321
+        // Run create hooks
322
+        foreach ($transaction->updateHooks as $colName => $fn) {
323
+            $fn();
324
+        }
325
+
326
+        // Validate missing keys
327
+        $errors += $transaction->validateMissingKeys($input);
328
+
329
+        // Update database
330
+        if (empty($errors)) {
331
+            $this->syncInstanceFrom($transaction);
332
+
333
+            try {
334
+                (new Query($this->getPdo(), $this->getTableName()))
335
+                    ->update($this->getActiveRecordColumns())
336
+                    ->where(Query::Equal('id', $this->getId()))
337
+                    ->execute();
338
+            } catch (\PDOException $e) {
339
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
340
+            }
341
+
342
+            return [null, $this->toArray($readWhitelist)];
343
+        } else {
344
+            return [$errors, null];
345
+        }
346
+    }
347
+
348
+    /**
349
+     * Returns this active record after reading the attributes from the entry with the given identifier.
350
+     *
351
+     * @param mixed $id
352
+     * @return $this
353
+     * @throws ActiveRecordException on failure.
354
+     */
355
+    abstract public function read($id);
356
+
357
+    /**
358
+     * Returns the PDO.
359
+     *
360
+     * @return \PDO the PDO.
361
+     */
362
+    abstract public function getPdo();
363
+
364
+    /**
365
+     * Set the ID.
366
+     *
367
+     * @param int $id
368
+     * @return $this
369
+     */
370
+    abstract protected function setId($id);
371
+
372
+    /**
373
+     * Returns the ID.
374
+     *
375
+     * @return null|int The ID.
376
+     */
377
+    abstract protected function getId();
378
+
379
+
380
+    /**
381
+     * Returns the serialized form of the specified columns
382
+     * 
383
+     * @return Array
384
+     */
385
+    abstract public function toArray(Array $fieldWhitelist);
386
+
387
+    /**
388
+     * Returns the active record table.
389
+     *
390
+     * @return string the active record table name.
391
+     */
392
+    abstract public function getTableName();
393
+
394
+    /**
395
+     * Returns the name -> variable mapping for the table definition.
396
+     * @return Array The mapping
397
+     */
398
+    abstract protected function getActiveRecordColumns();
399 399
 }
Please login to merge, or discard this patch.
src/Traits/Address.php 1 patch
Indentation   +170 added lines, -170 removed lines patch added patch discarded remove patch
@@ -12,181 +12,181 @@
 block discarded – undo
12 12
 
13 13
 trait Address
14 14
 {
15
-	/** @var string the address line */
16
-	protected $address;
17
-
18
-	/** @var string the zipcode */
19
-	protected $zipcode;
20
-
21
-	/** @var string the city */
22
-	protected $city;
23
-
24
-	/** @var string the country */
25
-	protected $country;
26
-
27
-	/** @var string the state */
28
-	protected $state;
29
-
30
-	/**
31
-	 * Registers the Address trait on the including class
32
-	 * @return void
33
-	 */
34
-	protected function initAddress() 
35
-	{
36
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ADDRESS, [
37
-			'value' => &$this->address,
38
-			'validate' => null,
39
-			'type' => 'VARCHAR',
40
-			'length' => 1024,
41
-			'properties' => null
42
-		]);
43
-
44
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ZIPCODE, [
45
-			'value' => &$this->zipcode,
46
-			'validate' => null,
47
-			'type' => 'VARCHAR',
48
-			'length' => 1024,
49
-			'properties' => null
50
-		]);
51
-
52
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_CITY, [
53
-			'value' => &$this->city,
54
-			'validate' => null,
55
-			'type' => 'VARCHAR',
56
-			'length' => 1024,
57
-			'properties' => null
58
-		]);
59
-
60
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_COUNTRY, [
61
-			'value' => &$this->country,
62
-			'validate' => null,
63
-			'type' => 'VARCHAR',
64
-			'length' => 1024,
65
-			'properties' => null
66
-		]);
67
-
68
-		$this->extendTableDefinition(TRAIT_ADDRESS_FIELD_STATE, [
69
-			'value' => &$this->state,
70
-			'validate' => null,
71
-			'type' => 'VARCHAR',
72
-			'length' => 1024,
73
-			'properties' => null
74
-		]);
75
-
76
-		$this->address = null;
77
-		$this->zipcode = null;
78
-		$this->city = null;
79
-		$this->country = null;
80
-		$this->state = null;
81
-	}
82
-
83
-	/**
84
-	 * @return string
85
-	 */
86
-	public function getAddress()
87
-	{
88
-		return $this->address;
89
-	}
15
+    /** @var string the address line */
16
+    protected $address;
17
+
18
+    /** @var string the zipcode */
19
+    protected $zipcode;
20
+
21
+    /** @var string the city */
22
+    protected $city;
23
+
24
+    /** @var string the country */
25
+    protected $country;
26
+
27
+    /** @var string the state */
28
+    protected $state;
29
+
30
+    /**
31
+     * Registers the Address trait on the including class
32
+     * @return void
33
+     */
34
+    protected function initAddress() 
35
+    {
36
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ADDRESS, [
37
+            'value' => &$this->address,
38
+            'validate' => null,
39
+            'type' => 'VARCHAR',
40
+            'length' => 1024,
41
+            'properties' => null
42
+        ]);
43
+
44
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_ZIPCODE, [
45
+            'value' => &$this->zipcode,
46
+            'validate' => null,
47
+            'type' => 'VARCHAR',
48
+            'length' => 1024,
49
+            'properties' => null
50
+        ]);
51
+
52
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_CITY, [
53
+            'value' => &$this->city,
54
+            'validate' => null,
55
+            'type' => 'VARCHAR',
56
+            'length' => 1024,
57
+            'properties' => null
58
+        ]);
59
+
60
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_COUNTRY, [
61
+            'value' => &$this->country,
62
+            'validate' => null,
63
+            'type' => 'VARCHAR',
64
+            'length' => 1024,
65
+            'properties' => null
66
+        ]);
67
+
68
+        $this->extendTableDefinition(TRAIT_ADDRESS_FIELD_STATE, [
69
+            'value' => &$this->state,
70
+            'validate' => null,
71
+            'type' => 'VARCHAR',
72
+            'length' => 1024,
73
+            'properties' => null
74
+        ]);
75
+
76
+        $this->address = null;
77
+        $this->zipcode = null;
78
+        $this->city = null;
79
+        $this->country = null;
80
+        $this->state = null;
81
+    }
82
+
83
+    /**
84
+     * @return string
85
+     */
86
+    public function getAddress()
87
+    {
88
+        return $this->address;
89
+    }
90 90
 	
91
-	/**
92
-	 * @param string $address
93
-	 */
94
-	public function setAddress($address)
95
-	{
96
-		$this->address = $address;
97
-		return $this;
98
-	}
99
-
100
-	/**
101
-	 * @return string
102
-	 */
103
-	public function getZipcode()
104
-	{
105
-		return $this->zipcode;
106
-	}
91
+    /**
92
+     * @param string $address
93
+     */
94
+    public function setAddress($address)
95
+    {
96
+        $this->address = $address;
97
+        return $this;
98
+    }
99
+
100
+    /**
101
+     * @return string
102
+     */
103
+    public function getZipcode()
104
+    {
105
+        return $this->zipcode;
106
+    }
107 107
 	
108
-	/**
109
-	 * @param string $zipcode
110
-	 */
111
-	public function setZipcode($zipcode)
112
-	{
113
-		$this->zipcode = $zipcode;
114
-		return $this;
115
-	}
116
-
117
-	/**
118
-	 * @return string
119
-	 */
120
-	public function getCity()
121
-	{
122
-		return $this->city;
123
-	}
108
+    /**
109
+     * @param string $zipcode
110
+     */
111
+    public function setZipcode($zipcode)
112
+    {
113
+        $this->zipcode = $zipcode;
114
+        return $this;
115
+    }
116
+
117
+    /**
118
+     * @return string
119
+     */
120
+    public function getCity()
121
+    {
122
+        return $this->city;
123
+    }
124 124
 	
125
-	/**
126
-	 * @param string $city
127
-	 */
128
-	public function setCity($city)
129
-	{
130
-		$this->city = $city;
131
-		return $this;
132
-	}
133
-
134
-	/**
135
-	 * @return string
136
-	 */
137
-	public function getCountry()
138
-	{
139
-		return $this->country;
140
-	}
125
+    /**
126
+     * @param string $city
127
+     */
128
+    public function setCity($city)
129
+    {
130
+        $this->city = $city;
131
+        return $this;
132
+    }
133
+
134
+    /**
135
+     * @return string
136
+     */
137
+    public function getCountry()
138
+    {
139
+        return $this->country;
140
+    }
141 141
 	
142
-	/**
143
-	 * @param string $country
144
-	 */
145
-	public function setCountry($country)
146
-	{
147
-		$this->country = $country;
148
-		return $this;
149
-	}
150
-
151
-	public function getState()
152
-	{
153
-		return $this->state;
154
-	}
142
+    /**
143
+     * @param string $country
144
+     */
145
+    public function setCountry($country)
146
+    {
147
+        $this->country = $country;
148
+        return $this;
149
+    }
150
+
151
+    public function getState()
152
+    {
153
+        return $this->state;
154
+    }
155 155
 	
156
-	public function setState($state)
157
-	{
158
-		$this->state = $state;
159
-		return $this;
160
-	}
161
-
162
-	/**
163
-	 * @return void
164
-	 */
165
-	abstract protected function extendTableDefinition($columnName, $definition);
156
+    public function setState($state)
157
+    {
158
+        $this->state = $state;
159
+        return $this;
160
+    }
161
+
162
+    /**
163
+     * @return void
164
+     */
165
+    abstract protected function extendTableDefinition($columnName, $definition);
166 166
 	
167
-	/**
168
-	 * @return void
169
-	 */
170
-	abstract protected function registerSearchHook($columnName, $fn);
171
-
172
-	/**
173
-	 * @return void
174
-	 */
175
-	abstract protected function registerDeleteHook($columnName, $fn);
176
-
177
-	/**
178
-	 * @return void
179
-	 */
180
-	abstract protected function registerUpdateHook($columnName, $fn);
181
-
182
-	/**
183
-	 * @return void
184
-	 */
185
-	abstract protected function registerReadHook($columnName, $fn);
186
-
187
-	/**
188
-	 * @return void
189
-	 */
190
-	abstract protected function registerCreateHook($columnName, $fn);
167
+    /**
168
+     * @return void
169
+     */
170
+    abstract protected function registerSearchHook($columnName, $fn);
171
+
172
+    /**
173
+     * @return void
174
+     */
175
+    abstract protected function registerDeleteHook($columnName, $fn);
176
+
177
+    /**
178
+     * @return void
179
+     */
180
+    abstract protected function registerUpdateHook($columnName, $fn);
181
+
182
+    /**
183
+     * @return void
184
+     */
185
+    abstract protected function registerReadHook($columnName, $fn);
186
+
187
+    /**
188
+     * @return void
189
+     */
190
+    abstract protected function registerCreateHook($columnName, $fn);
191 191
 	
192 192
 }
193 193
\ No newline at end of file
Please login to merge, or discard this patch.
src/ActiveRecordQuery.php 1 patch
Indentation   +266 added lines, -266 removed lines patch added patch discarded remove patch
@@ -20,276 +20,276 @@
 block discarded – undo
20 20
  */
21 21
 class ActiveRecordQuery implements \IteratorAggregate
22 22
 {
23
-	private $instance;
23
+    private $instance;
24 24
 
25
-	private $query;
25
+    private $query;
26 26
 
27
-	private $type;
27
+    private $type;
28 28
 
29
-	private $clauses = [];
29
+    private $clauses = [];
30 30
 
31
-	private $maxresultCount;
31
+    private $maxresultCount;
32 32
 
33
-	private $results;
33
+    private $results;
34 34
 	
35
-	private $whereExpression = null;
36
-
37
-	private $limit;
38
-
39
-	private $offset;
40
-
41
-	private $orderBy;
42
-
43
-	private $orderDirection;
44
-
45
-	/**
46
-	 * Constructs a new Active Record Query
47
-	 */
48
-	public function __construct(AbstractActiveRecord $instance, Array $additionalWhereClauses)
49
-	{
50
-		$this->instance = $instance;
51
-		$this->query = new Query($instance->getPdo(), $instance->getTableName());
52
-		$this->type = $instance;
53
-		$this->clauses = $additionalWhereClauses;
54
-		$this->maxResultCount = null;
55
-		$this->results = null;
56
-		$this->limit = null;
57
-		$this->offset = null;
58
-	}
59
-
60
-	private function getWhereCondition()
61
-	{
62
-		$clauses = $this->clauses;
63
-
64
-		// Optionally add user concatenated where expression
65
-		if ($this->whereExpression !== null) {
66
-			$clauses[] = $this->whereExpression;
67
-		}
68
-
69
-		// Construct where clause
70
-		if (count($clauses) > 0) {
71
-			return Query::AndArray($clauses);
72
-		}
73
-		return null;
74
-	}
75
-
76
-	/**
77
-	 * Executes the query
78
-	 */
79
-	public function execute()
80
-	{
81
-		$whereCondition = $this->getWhereCondition();
82
-		if ($whereCondition !== null) {
83
-			$this->query->where($whereCondition);
84
-		}
85
-
86
-		$this->query->select();
87
-
88
-		$this->results = $this->query->execute();
89
-
90
-		return $this;
91
-	}
92
-
93
-	/**
94
-	 * Returns an iterator for the result set
95
-	 * @return ArrayIterator
96
-	 */
97
-	public function getIterator()
98
-	{
99
-		return new \ArrayIterator($this->fetchAll());
100
-	}
101
-
102
-	/**
103
-	 * returns the result set of ActiveRecord instances for this query
104
-	 * @return Array
105
-	 */
106
-	public function fetchAll()
107
-	{
108
-		try {
109
-			if ($this->results === null) {
110
-				$this->execute();	
111
-			}
112
-
113
-			$entries = $this->results->fetchAll();
114
-			if ($entries === false) {
115
-				return [];
116
-			}
117
-
118
-			$typedResults = [];
119
-			foreach ($entries as $entry) {
120
-				$typedEntry = $this->type->newInstance();
121
-				$typedEntry->fill($entry);
122
-				$typedResults[] = $typedEntry;
123
-			}
124
-
125
-			return $typedResults;
126
-		} catch (\PDOException $e) {
127
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
128
-		}
129
-	}
130
-
131
-	public function fetchAllAsArray($readWhitelist)
132
-	{
133
-		$data = $this->fetchAll();
134
-		$output = [];
135
-		foreach ($data as $entry) {
136
-			$output[] = $entry->toArray($readWhitelist);
137
-		}
138
-		return $output;
139
-	}
140
-
141
-	/**
142
-	 * Fetch one record from the database
143
-	 * @return AbstractActiveRecord 
144
-	 */
145
-	public function fetch()
146
-	{
147
-		try {
148
-			if ($this->results === null) {
149
-				$this->execute();
150
-			}
151
-
152
-			$typedResult = $this->type->newInstance();
153
-
154
-			$entry = $this->results->fetch();
155
-			if ($entry === false) {
156
-				return null;
157
-			}
158
-
159
-			$typedResult->fill($entry);
160
-
161
-			return $typedResult;
162
-		} catch (\PDOException $e) {
163
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
164
-		}
165
-	}
166
-
167
-	/**
168
-	 * Fetch one record from the database and format it as an associative array, 
169
-	 * 	 filtered by the entries in $readwhitelist
170
-	 * @param Array $readWhitelist Array of whitelisted database column keys to be returned in the result
171
-	 * @return Array|Null
172
-	 */
173
-	public function fetchAsArray($readWhitelist)
174
-	{
175
-		$res = $this->fetch();
176
-		if ($res !== null) {
177
-			return $res->toArray($readWhitelist);
178
-		}
179
-		return null;
180
-	}
181
-
182
-	public function countMaxResults()
183
-	{
184
-		if ($this->maxResultCount === null) {
185
-			$query = new Query($this->instance->getPdo(), $this->instance->getTableName());
186
-			$query->select(['count(*) as count'], false);
187
-
188
-			$whereCondition = $this->getWhereCondition();
189
-			if ($whereCondition !== null) {
190
-				$query->where($whereCondition);
191
-			}
192
-
193
-			$this->maxResultCount = $query->execute()->fetch()['count'];
194
-		}
195
-		return $this->maxResultCount;
196
-	}
197
-
198
-	public function getNumberOfPages()
199
-	{
200
-		if ($this->limit === null) {
201
-			return 1;
202
-		}
203
-
204
-		if ($this->limit === 0) {
205
-			return 0;
206
-		}
207
-
208
-		$resultCount = $this->countMaxResults();
209
-		if ($resultCount % $this->limit > 0) {
210
-			return (int) floor($resultCount / $this->limit) + 1;
211
-		}
212
-		return (int) floor($resultCount / $this->limit);
213
-	}
214
-
215
-	public function getCurrentPage()
216
-	{
217
-		if ($this->offset === null || $this->offset === 0) {
218
-			return 1;
219
-		}
220
-
221
-		if ($this->limit === null || $this->limit === 0) {
222
-			return 1;
223
-		}
224
-
225
-		return (int) floor($this->offset / $this->limit);
226
-	}
227
-
228
-	/**
229
-	 * Set the where condition
230
-	 *
231
-	 * @param QueryExpression $expression the query expression
232
-	 * @return $this
233
-	 * @see https://en.wikipedia.org/wiki/SQL#Operators
234
-	 * @see https://en.wikipedia.org/wiki/Where_(SQL)
235
-	 */
236
-	public function where(QueryExpression $expression)
237
-	{
238
-		$this->whereExpression = $expression;
239
-		return $this;
240
-	}
241
-
242
-	/**
243
-	 * Set an additional group by.
244
-	 *
245
-	 * @param string $column
246
-	 * @return $this
247
-	 * @see https://en.wikipedia.org/wiki/SQL#Queries
248
-	 */
249
-	public function groupBy($column)
250
-	{
251
-		$this->query->groupBy($column);
252
-		return $this;
253
-	}
254
-
255
-	/**
256
-	 * Set an additional order condition.
257
-	 *
258
-	 * @param string $column
259
-	 * @param string|null $order
260
-	 * @return $this
261
-	 * @see https://en.wikipedia.org/wiki/SQL#Queries
262
-	 * @see https://en.wikipedia.org/wiki/Order_by
263
-	 */
264
-	public function orderBy($column, $order = null)
265
-	{
266
-		$this->query->orderBy($column, $order);	
267
-		return $this;
268
-	}
269
-
270
-	/**
271
-	 * Set the limit.
272
-	 *
273
-	 * @param mixed $limit
274
-	 * @return $this
275
-	 */
276
-	public function limit($limit)
277
-	{
278
-		$this->limit = $limit;
279
-		$this->query->limit($limit);
280
-		return $this;
281
-	}
282
-
283
-	/**
284
-	 * Set the offset.
285
-	 *
286
-	 * @param mixed $offset
287
- 	 * @return $this
288
-	 */
289
-	public function offset($offset)
290
-	{
291
-		$this->offset = $offset;
292
-		$this->query->offset($offset);
293
-		return $this;
294
-	}
35
+    private $whereExpression = null;
36
+
37
+    private $limit;
38
+
39
+    private $offset;
40
+
41
+    private $orderBy;
42
+
43
+    private $orderDirection;
44
+
45
+    /**
46
+     * Constructs a new Active Record Query
47
+     */
48
+    public function __construct(AbstractActiveRecord $instance, Array $additionalWhereClauses)
49
+    {
50
+        $this->instance = $instance;
51
+        $this->query = new Query($instance->getPdo(), $instance->getTableName());
52
+        $this->type = $instance;
53
+        $this->clauses = $additionalWhereClauses;
54
+        $this->maxResultCount = null;
55
+        $this->results = null;
56
+        $this->limit = null;
57
+        $this->offset = null;
58
+    }
59
+
60
+    private function getWhereCondition()
61
+    {
62
+        $clauses = $this->clauses;
63
+
64
+        // Optionally add user concatenated where expression
65
+        if ($this->whereExpression !== null) {
66
+            $clauses[] = $this->whereExpression;
67
+        }
68
+
69
+        // Construct where clause
70
+        if (count($clauses) > 0) {
71
+            return Query::AndArray($clauses);
72
+        }
73
+        return null;
74
+    }
75
+
76
+    /**
77
+     * Executes the query
78
+     */
79
+    public function execute()
80
+    {
81
+        $whereCondition = $this->getWhereCondition();
82
+        if ($whereCondition !== null) {
83
+            $this->query->where($whereCondition);
84
+        }
85
+
86
+        $this->query->select();
87
+
88
+        $this->results = $this->query->execute();
89
+
90
+        return $this;
91
+    }
92
+
93
+    /**
94
+     * Returns an iterator for the result set
95
+     * @return ArrayIterator
96
+     */
97
+    public function getIterator()
98
+    {
99
+        return new \ArrayIterator($this->fetchAll());
100
+    }
101
+
102
+    /**
103
+     * returns the result set of ActiveRecord instances for this query
104
+     * @return Array
105
+     */
106
+    public function fetchAll()
107
+    {
108
+        try {
109
+            if ($this->results === null) {
110
+                $this->execute();	
111
+            }
112
+
113
+            $entries = $this->results->fetchAll();
114
+            if ($entries === false) {
115
+                return [];
116
+            }
117
+
118
+            $typedResults = [];
119
+            foreach ($entries as $entry) {
120
+                $typedEntry = $this->type->newInstance();
121
+                $typedEntry->fill($entry);
122
+                $typedResults[] = $typedEntry;
123
+            }
124
+
125
+            return $typedResults;
126
+        } catch (\PDOException $e) {
127
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
128
+        }
129
+    }
130
+
131
+    public function fetchAllAsArray($readWhitelist)
132
+    {
133
+        $data = $this->fetchAll();
134
+        $output = [];
135
+        foreach ($data as $entry) {
136
+            $output[] = $entry->toArray($readWhitelist);
137
+        }
138
+        return $output;
139
+    }
140
+
141
+    /**
142
+     * Fetch one record from the database
143
+     * @return AbstractActiveRecord 
144
+     */
145
+    public function fetch()
146
+    {
147
+        try {
148
+            if ($this->results === null) {
149
+                $this->execute();
150
+            }
151
+
152
+            $typedResult = $this->type->newInstance();
153
+
154
+            $entry = $this->results->fetch();
155
+            if ($entry === false) {
156
+                return null;
157
+            }
158
+
159
+            $typedResult->fill($entry);
160
+
161
+            return $typedResult;
162
+        } catch (\PDOException $e) {
163
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
164
+        }
165
+    }
166
+
167
+    /**
168
+     * Fetch one record from the database and format it as an associative array, 
169
+     * 	 filtered by the entries in $readwhitelist
170
+     * @param Array $readWhitelist Array of whitelisted database column keys to be returned in the result
171
+     * @return Array|Null
172
+     */
173
+    public function fetchAsArray($readWhitelist)
174
+    {
175
+        $res = $this->fetch();
176
+        if ($res !== null) {
177
+            return $res->toArray($readWhitelist);
178
+        }
179
+        return null;
180
+    }
181
+
182
+    public function countMaxResults()
183
+    {
184
+        if ($this->maxResultCount === null) {
185
+            $query = new Query($this->instance->getPdo(), $this->instance->getTableName());
186
+            $query->select(['count(*) as count'], false);
187
+
188
+            $whereCondition = $this->getWhereCondition();
189
+            if ($whereCondition !== null) {
190
+                $query->where($whereCondition);
191
+            }
192
+
193
+            $this->maxResultCount = $query->execute()->fetch()['count'];
194
+        }
195
+        return $this->maxResultCount;
196
+    }
197
+
198
+    public function getNumberOfPages()
199
+    {
200
+        if ($this->limit === null) {
201
+            return 1;
202
+        }
203
+
204
+        if ($this->limit === 0) {
205
+            return 0;
206
+        }
207
+
208
+        $resultCount = $this->countMaxResults();
209
+        if ($resultCount % $this->limit > 0) {
210
+            return (int) floor($resultCount / $this->limit) + 1;
211
+        }
212
+        return (int) floor($resultCount / $this->limit);
213
+    }
214
+
215
+    public function getCurrentPage()
216
+    {
217
+        if ($this->offset === null || $this->offset === 0) {
218
+            return 1;
219
+        }
220
+
221
+        if ($this->limit === null || $this->limit === 0) {
222
+            return 1;
223
+        }
224
+
225
+        return (int) floor($this->offset / $this->limit);
226
+    }
227
+
228
+    /**
229
+     * Set the where condition
230
+     *
231
+     * @param QueryExpression $expression the query expression
232
+     * @return $this
233
+     * @see https://en.wikipedia.org/wiki/SQL#Operators
234
+     * @see https://en.wikipedia.org/wiki/Where_(SQL)
235
+     */
236
+    public function where(QueryExpression $expression)
237
+    {
238
+        $this->whereExpression = $expression;
239
+        return $this;
240
+    }
241
+
242
+    /**
243
+     * Set an additional group by.
244
+     *
245
+     * @param string $column
246
+     * @return $this
247
+     * @see https://en.wikipedia.org/wiki/SQL#Queries
248
+     */
249
+    public function groupBy($column)
250
+    {
251
+        $this->query->groupBy($column);
252
+        return $this;
253
+    }
254
+
255
+    /**
256
+     * Set an additional order condition.
257
+     *
258
+     * @param string $column
259
+     * @param string|null $order
260
+     * @return $this
261
+     * @see https://en.wikipedia.org/wiki/SQL#Queries
262
+     * @see https://en.wikipedia.org/wiki/Order_by
263
+     */
264
+    public function orderBy($column, $order = null)
265
+    {
266
+        $this->query->orderBy($column, $order);	
267
+        return $this;
268
+    }
269
+
270
+    /**
271
+     * Set the limit.
272
+     *
273
+     * @param mixed $limit
274
+     * @return $this
275
+     */
276
+    public function limit($limit)
277
+    {
278
+        $this->limit = $limit;
279
+        $this->query->limit($limit);
280
+        return $this;
281
+    }
282
+
283
+    /**
284
+     * Set the offset.
285
+     *
286
+     * @param mixed $offset
287
+     * @return $this
288
+     */
289
+    public function offset($offset)
290
+    {
291
+        $this->offset = $offset;
292
+        $this->query->offset($offset);
293
+        return $this;
294
+    }
295 295
 }
Please login to merge, or discard this patch.
src/Traits/Password.php 1 patch
Indentation   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -14,186 +14,186 @@
 block discarded – undo
14 14
 
15 15
 trait Password
16 16
 {
17
-	/** @var string The password hash. */
18
-	protected $password;
19
-
20
-	/** @var string|null The password reset token. */
21
-	protected $passwordResetToken;
22
-
23
-	/** @var string|null The password expiry date */
24
-	protected $passwordExpiryDate;
25
-
26
-	/**
27
-	 * this method is required to be called in the constructor for each class that uses this trait. 
28
-	 * It adds the fields necessary for the passwords struct to the table definition
29
-	 */
30
-	protected function initPassword()
31
-	{
32
-		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD, [
33
-			'value' => &$this->password,
34
-			'validate' => [$this, 'validatePassword'],
35
-			'type' => 'VARCHAR',
36
-			'length' => 1024,
37
-			'properties' => null
38
-		]);
39
-
40
-		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN, [
41
-			'value' => &$this->passwordResetToken,
42
-			'validate' => null,
43
-			'default' => 0,
44
-			'type' => 'VARCHAR',
45
-			'length' => 1024
46
-		]);
47
-
48
-		$this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN_EXPIRY, [
49
-			'value' => &$this->passwordExpiryDate,
50
-			'validate' => null,
51
-			'type' => 'DATETIME',
52
-		]);
53
-	}
54
-
55
-
56
-	/**
57
-	 * Returns whether the users password has been set
58
-	 * @return boolean true if the user has a password
59
-	 */
60
-	public function hasPasswordBeenSet()
61
-	{
62
-		return $this->password !== null;
63
-	}
64
-
65
-	/**
66
-	 * Returns true if the credentials are correct.
67
-	 *
68
-	 * @param string $password
69
-	 * @return boolean true if the credentials are correct
70
-	 */
71
-	public function isPassword($password)
72
-	{ 
73
-		if (!$this->hasPasswordBeenSet())
74
-		{
75
-			throw new ActiveRecordTraitException("Password field has not been set");
76
-		}
77
-
78
-		if (!password_verify($password, $this->password)) {
79
-			return false;
80
-		}
81
-
82
-		if (password_needs_rehash($this->password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH])) {
83
-			$this->setPassword($password)->sync();
84
-		}
85
-
86
-		return true;
87
-	}
88
-
89
-	public function validatePassword($password) {
90
-		if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
91
-			$message = sprintf('\'Password\' must be atleast %s characters long. %s characters provided.', TRAIT_PASSWORD_MIN_LENGTH, strlen($password));
92
-			return [false, $message];
93
-		}
94
-		return [true, ''];
95
-	}
96
-
97
-	/**
98
-	 * Set the password.
99
-	 *
100
-	 * @param string $password
101
-	 * @return $this
102
-	 * @throws \Exception
103
-	 */
104
-	public function setPassword($password)
105
-	{
106
-		[$status, $error] = $this->validatePassword($password);
107
-		if (!$status) {
108
-			throw new ActiveRecordTraitException($error);
109
-		}
110
-
111
-		$passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
112
-
113
-		if ($passwordHash === false) {
114
-			throw new ActiveRecordTraitException('\'Password\' hash failed.');
115
-		}
116
-
117
-		$this->password = $passwordHash;
118
-
119
-		return $this;
120
-	}
121
-
122
-	/**
123
-	 * @return string The Hash of the password
124
-	 */
125
-	public function getPasswordHash()
126
-	{
127
-		return $this->password;
128
-	}
129
-
130
-	/**
131
-	 * Returns the currently set password token for the entity, or null if not set
132
-	 * @return string|null The password reset token
133
-	 */
134
-	public function getPasswordResetToken()
135
-	{
136
-		return $this->passwordResetToken;
137
-	}
138
-
139
-	/**
140
-	 * Generates a new password reset token for the user
141
-	 */
142
-	public function generatePasswordResetToken()
143
-	{
144
-		$this->passwordResetToken = md5(uniqid(mt_rand(), true));
145
-
146
-		$validityDuration = new \DateInterval('PT24H');
147
-
148
-		$this->passwordExpiryDate = (new \DateTime('now'))->add($validityDuration)->format('Y-m-d H:i:s');
149
-		return $this;
150
-	}
151
-
152
-	/**
153
-	 * Clears the current password reset token
154
-	 */
155
-	public function clearPasswordResetToken()
156
-	{
157
-		$this->passwordResetToken = null;
158
-		$this->passwordExpiryDate = null;
159
-		return $this;
160
-	}
161
-
162
-	public function validatePasswordResetToken(string $token)
163
-	{
164
-		return $this->passwordResetToken !== null
165
-			&& $token === $this->passwordResetToken
166
-			&& (new \DateTime('now')) < (new \DateTime($this->passwordExpiryDate));
167
-	}
17
+    /** @var string The password hash. */
18
+    protected $password;
19
+
20
+    /** @var string|null The password reset token. */
21
+    protected $passwordResetToken;
22
+
23
+    /** @var string|null The password expiry date */
24
+    protected $passwordExpiryDate;
25
+
26
+    /**
27
+     * this method is required to be called in the constructor for each class that uses this trait. 
28
+     * It adds the fields necessary for the passwords struct to the table definition
29
+     */
30
+    protected function initPassword()
31
+    {
32
+        $this->extendTableDefinition(TRAIT_PASSWORD_FIELD_PASSWORD, [
33
+            'value' => &$this->password,
34
+            'validate' => [$this, 'validatePassword'],
35
+            'type' => 'VARCHAR',
36
+            'length' => 1024,
37
+            'properties' => null
38
+        ]);
39
+
40
+        $this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN, [
41
+            'value' => &$this->passwordResetToken,
42
+            'validate' => null,
43
+            'default' => 0,
44
+            'type' => 'VARCHAR',
45
+            'length' => 1024
46
+        ]);
47
+
48
+        $this->extendTableDefinition(TRAIT_PASSWORD_FIELD_RESET_TOKEN_EXPIRY, [
49
+            'value' => &$this->passwordExpiryDate,
50
+            'validate' => null,
51
+            'type' => 'DATETIME',
52
+        ]);
53
+    }
54
+
55
+
56
+    /**
57
+     * Returns whether the users password has been set
58
+     * @return boolean true if the user has a password
59
+     */
60
+    public function hasPasswordBeenSet()
61
+    {
62
+        return $this->password !== null;
63
+    }
64
+
65
+    /**
66
+     * Returns true if the credentials are correct.
67
+     *
68
+     * @param string $password
69
+     * @return boolean true if the credentials are correct
70
+     */
71
+    public function isPassword($password)
72
+    { 
73
+        if (!$this->hasPasswordBeenSet())
74
+        {
75
+            throw new ActiveRecordTraitException("Password field has not been set");
76
+        }
77
+
78
+        if (!password_verify($password, $this->password)) {
79
+            return false;
80
+        }
81
+
82
+        if (password_needs_rehash($this->password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH])) {
83
+            $this->setPassword($password)->sync();
84
+        }
85
+
86
+        return true;
87
+    }
88
+
89
+    public function validatePassword($password) {
90
+        if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
91
+            $message = sprintf('\'Password\' must be atleast %s characters long. %s characters provided.', TRAIT_PASSWORD_MIN_LENGTH, strlen($password));
92
+            return [false, $message];
93
+        }
94
+        return [true, ''];
95
+    }
96
+
97
+    /**
98
+     * Set the password.
99
+     *
100
+     * @param string $password
101
+     * @return $this
102
+     * @throws \Exception
103
+     */
104
+    public function setPassword($password)
105
+    {
106
+        [$status, $error] = $this->validatePassword($password);
107
+        if (!$status) {
108
+            throw new ActiveRecordTraitException($error);
109
+        }
110
+
111
+        $passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
112
+
113
+        if ($passwordHash === false) {
114
+            throw new ActiveRecordTraitException('\'Password\' hash failed.');
115
+        }
116
+
117
+        $this->password = $passwordHash;
118
+
119
+        return $this;
120
+    }
121
+
122
+    /**
123
+     * @return string The Hash of the password
124
+     */
125
+    public function getPasswordHash()
126
+    {
127
+        return $this->password;
128
+    }
129
+
130
+    /**
131
+     * Returns the currently set password token for the entity, or null if not set
132
+     * @return string|null The password reset token
133
+     */
134
+    public function getPasswordResetToken()
135
+    {
136
+        return $this->passwordResetToken;
137
+    }
138
+
139
+    /**
140
+     * Generates a new password reset token for the user
141
+     */
142
+    public function generatePasswordResetToken()
143
+    {
144
+        $this->passwordResetToken = md5(uniqid(mt_rand(), true));
145
+
146
+        $validityDuration = new \DateInterval('PT24H');
147
+
148
+        $this->passwordExpiryDate = (new \DateTime('now'))->add($validityDuration)->format('Y-m-d H:i:s');
149
+        return $this;
150
+    }
151
+
152
+    /**
153
+     * Clears the current password reset token
154
+     */
155
+    public function clearPasswordResetToken()
156
+    {
157
+        $this->passwordResetToken = null;
158
+        $this->passwordExpiryDate = null;
159
+        return $this;
160
+    }
161
+
162
+    public function validatePasswordResetToken(string $token)
163
+    {
164
+        return $this->passwordResetToken !== null
165
+            && $token === $this->passwordResetToken
166
+            && (new \DateTime('now')) < (new \DateTime($this->passwordExpiryDate));
167
+    }
168 168
 	
169
-	/**
170
-	 * @return void
171
-	 */
172
-	abstract protected function extendTableDefinition($columnName, $definition);
169
+    /**
170
+     * @return void
171
+     */
172
+    abstract protected function extendTableDefinition($columnName, $definition);
173 173
 	
174
-	/**
175
-	 * @return void
176
-	 */
177
-	abstract protected function registerSearchHook($columnName, $fn);
178
-
179
-	/**
180
-	 * @return void
181
-	 */
182
-	abstract protected function registerDeleteHook($columnName, $fn);
183
-
184
-	/**
185
-	 * @return void
186
-	 */
187
-	abstract protected function registerUpdateHook($columnName, $fn);
188
-
189
-	/**
190
-	 * @return void
191
-	 */
192
-	abstract protected function registerReadHook($columnName, $fn);
193
-
194
-	/**
195
-	 * @return void
196
-	 */
197
-	abstract protected function registerCreateHook($columnName, $fn);
174
+    /**
175
+     * @return void
176
+     */
177
+    abstract protected function registerSearchHook($columnName, $fn);
178
+
179
+    /**
180
+     * @return void
181
+     */
182
+    abstract protected function registerDeleteHook($columnName, $fn);
183
+
184
+    /**
185
+     * @return void
186
+     */
187
+    abstract protected function registerUpdateHook($columnName, $fn);
188
+
189
+    /**
190
+     * @return void
191
+     */
192
+    abstract protected function registerReadHook($columnName, $fn);
193
+
194
+    /**
195
+     * @return void
196
+     */
197
+    abstract protected function registerCreateHook($columnName, $fn);
198 198
 
199 199
 }
200 200
\ No newline at end of file
Please login to merge, or discard this patch.