Completed
Push — v2 ( c532d8...5f504a )
by Berend
02:38
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/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/Datefields.php 1 patch
Indentation   +97 added lines, -97 removed lines patch added patch discarded remove patch
@@ -10,104 +10,104 @@
 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
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
30
-		]);
31
-
32
-		$this->extendTableDefinition(TRAIT_DATEFIELDS_LAST_MODIFIED, [
33
-			'value' => &$this->lastModified,
34
-			'validate' => null,
35
-			'type' => 'DATETIME',
36
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
37
-		]);
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
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
30
+        ]);
31
+
32
+        $this->extendTableDefinition(TRAIT_DATEFIELDS_LAST_MODIFIED, [
33
+            'value' => &$this->lastModified,
34
+            'validate' => null,
35
+            'type' => 'DATETIME',
36
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
37
+        ]);
38 38
 		
39
-		$this->registerUpdateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsUpdateHook');
40
-		$this->registerCreateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsCreateHook');
41
-
42
-		$this->created = null;
43
-		$this->lastModified = null;
44
-	}
45
-
46
-	/**
47
-	 * The hook that gets called to set the timestamp whenever a new record is created
48
-	 */
49
-	protected function DatefieldsCreateHook()
50
-	{
51
-		// @TODO: Should this be split up to seperate hooks for "last_modified" and "created" for consistency?
52
-		$this->created = (new \DateTime('now'))->format('Y-m-d H:i:s');
53
-		$this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
54
-	}
55
-
56
-	/**
57
-	 * The hook that gets called to set the timestamp whenever a record gets updated
58
-	 */
59
-	protected function DatefieldsUpdateHook()
60
-	{
61
-		$this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
62
-	}
63
-
64
-	/**
65
-	 * Returns the timestamp of last update for this record
66
-	 * @return \DateTime
67
-	 */
68
-	public function getLastModifiedDate()
69
-	{
70
-		return new \DateTime($this->lastModified);
71
-	}
72
-
73
-	/**
74
-	 * Returns the timestamp of when this record was created
75
-	 * @return \DateTime
76
-	 */
77
-	public function getCreationDate()
78
-	{
79
-		return new \DateTime($this->created);
80
-	}
81
-
82
-	/**
83
-	 * @return void
84
-	 */
85
-	abstract protected function extendTableDefinition($columnName, $definition);
39
+        $this->registerUpdateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsUpdateHook');
40
+        $this->registerCreateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsCreateHook');
41
+
42
+        $this->created = null;
43
+        $this->lastModified = null;
44
+    }
45
+
46
+    /**
47
+     * The hook that gets called to set the timestamp whenever a new record is created
48
+     */
49
+    protected function DatefieldsCreateHook()
50
+    {
51
+        // @TODO: Should this be split up to seperate hooks for "last_modified" and "created" for consistency?
52
+        $this->created = (new \DateTime('now'))->format('Y-m-d H:i:s');
53
+        $this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
54
+    }
55
+
56
+    /**
57
+     * The hook that gets called to set the timestamp whenever a record gets updated
58
+     */
59
+    protected function DatefieldsUpdateHook()
60
+    {
61
+        $this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
62
+    }
63
+
64
+    /**
65
+     * Returns the timestamp of last update for this record
66
+     * @return \DateTime
67
+     */
68
+    public function getLastModifiedDate()
69
+    {
70
+        return new \DateTime($this->lastModified);
71
+    }
72
+
73
+    /**
74
+     * Returns the timestamp of when this record was created
75
+     * @return \DateTime
76
+     */
77
+    public function getCreationDate()
78
+    {
79
+        return new \DateTime($this->created);
80
+    }
81
+
82
+    /**
83
+     * @return void
84
+     */
85
+    abstract protected function extendTableDefinition($columnName, $definition);
86 86
 	
87
-	/**
88
-	 * @return void
89
-	 */
90
-	abstract protected function registerSearchHook($columnName, $fn);
91
-
92
-	/**
93
-	 * @return void
94
-	 */
95
-	abstract protected function registerDeleteHook($columnName, $fn);
96
-
97
-	/**
98
-	 * @return void
99
-	 */
100
-	abstract protected function registerUpdateHook($columnName, $fn);
101
-
102
-	/**
103
-	 * @return void
104
-	 */
105
-	abstract protected function registerReadHook($columnName, $fn);
106
-
107
-	/**
108
-	 * @return void
109
-	 */
110
-	abstract protected function registerCreateHook($columnName, $fn);
87
+    /**
88
+     * @return void
89
+     */
90
+    abstract protected function registerSearchHook($columnName, $fn);
91
+
92
+    /**
93
+     * @return void
94
+     */
95
+    abstract protected function registerDeleteHook($columnName, $fn);
96
+
97
+    /**
98
+     * @return void
99
+     */
100
+    abstract protected function registerUpdateHook($columnName, $fn);
101
+
102
+    /**
103
+     * @return void
104
+     */
105
+    abstract protected function registerReadHook($columnName, $fn);
106
+
107
+    /**
108
+     * @return void
109
+     */
110
+    abstract protected function registerCreateHook($columnName, $fn);
111 111
 }
112 112
 
113
-	
114 113
\ No newline at end of file
114
+    
115 115
\ No newline at end of file
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
Indentation   +145 added lines, -145 removed lines patch added patch discarded remove patch
@@ -18,157 +18,157 @@
 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 buildConstraint($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 buildConstraint($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
-	}
40
-
41
-	/**
42
-	 * Returns the type string as it should appear in the mysql create table statement for the given column
43
-	 * @return string The type string
44
-	 */
45
-	public static function getDatabaseTypeString($colName, $type, $length)
46
-	{
47
-		switch (strtoupper($type)) {
48
-			case '':
49
-				throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
38
+        return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
39
+    }
40
+
41
+    /**
42
+     * Returns the type string as it should appear in the mysql create table statement for the given column
43
+     * @return string The type string
44
+     */
45
+    public static function getDatabaseTypeString($colName, $type, $length)
46
+    {
47
+        switch (strtoupper($type)) {
48
+            case '':
49
+                throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
50 50
 			
51
-			case 'BOOL';
52
-			case 'BOOLEAN':
53
-			case 'DATETIME':
54
-			case 'DATE':
55
-			case 'TIME':
56
-			case 'TEXT':
57
-			case 'INT UNSIGNED':
58
-				return $type;
59
-
60
-			case 'VARCHAR':
61
-				if ($length === null) {
62
-					throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
63
-				} else {
64
-					return sprintf('%s(%d)', $type, $length);	
65
-				}
66
-
67
-			case 'INT':
68
-			case 'TINYINT':
69
-			case 'BIGINT':
70
-			default: 	
71
-				// Implicitly assuming that non-specified cases are correct without a length parameter
72
-				if ($length === null) {
73
-					return $type;
74
-				} else {
75
-					return sprintf('%s(%d)', $type, $length);	
76
-				}
77
-		}
78
-	}
79
-
80
-	/**
81
-	 * Builds the part of a MySQL create table statement that corresponds to the supplied column
82
-	 * @param string $colName 	Name of the database column
83
-	 * @param string $type 		The type of the string
84
-	 * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
85
-	 * @return string
86
-	 */
87
-	public static function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
88
-	{
89
-		$stmnt = sprintf('`%s` %s ', $colName, self::getDatabaseTypeString($colName, $type, $length));
90
-		if ($properties & ColumnProperty::NOT_NULL) {
91
-			$stmnt .= 'NOT NULL ';
92
-		} else {
93
-			$stmnt .= 'NULL ';
94
-		}
95
-
96
-		if ($default !== NULL) {
97
-			$stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
98
-		}
99
-
100
-		if ($properties & ColumnProperty::AUTO_INCREMENT) {
101
-			$stmnt .= 'AUTO_INCREMENT ';
102
-		}
103
-
104
-		if ($properties & ColumnProperty::UNIQUE) {
105
-			$stmnt .= 'UNIQUE ';
106
-		}
107
-
108
-		if ($properties & ColumnProperty::PRIMARY_KEY) {
109
-			$stmnt .= 'PRIMARY KEY ';
110
-		}
111
-
112
-		return $stmnt;
113
-	}
114
-
115
-	/**
116
-	 * Sorts the column statement components in the order such that the id appears first, 
117
-	 * 		followed by all other columns in alphabetical ascending order
118
-	 * @param   Array $colStatements Array of column statements
119
-	 * @return  Array
120
-	 */
121
-	private static function sortColumnStatements($colStatements)
122
-	{
123
-		// Find ID statement and put it first
124
-		$sortedStatements = [];
125
-
126
-		$sortedStatements[] = $colStatements[AbstractActiveRecord::COLUMN_NAME_ID];
127
-		unset($colStatements[AbstractActiveRecord::COLUMN_NAME_ID]);
128
-
129
-		// Sort remaining columns in alphabetical order
130
-		$columns = array_keys($colStatements);
131
-		sort($columns);
132
-		foreach ($columns as $colName) {
133
-			$sortedStatements[] = $colStatements[$colName];
134
-		}
135
-
136
-		return $sortedStatements;
137
-	}
138
-
139
-	/**
140
-	 * Builds the MySQL Create Table statement for the internal table definition
141
-	 * @return string
142
-	 */
143
-	public static function buildCreateTableSQL($tableName, $tableDefinition)
144
-	{
145
-		$columnStatements = [];
146
-		foreach ($tableDefinition as $colName => $definition) {
147
-			// Destructure column definition
148
-			$type    = $definition['type'] ?? null;
149
-			$default = $definition['default'] ?? null;
150
-			$length  = $definition['length'] ?? null;
151
-			$properties = $definition['properties'] ?? null;
152
-
153
-			if (isset($definition['relation']) && $type !== null) {
154
-				$msg = sprintf("Column \"%s\" on table \"%s\": ", $colName, $tableName);
155
-				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
156
-				throw new ActiveRecordException($msg);
157
-			} else if (isset($definition['relation'])) {
158
-				$type = AbstractActiveRecord::COLUMN_TYPE_ID;
159
-			}
160
-
161
-			$columnStatements[$colName] = self::buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
162
-		}
163
-
164
-		// Sort table (first column is id, the remaining are alphabetically sorted)
165
-		$columnStatements = self::sortColumnStatements($columnStatements);
166
-
167
-		$sql = sprintf("CREATE TABLE %s (\n%s\n);", 
168
-			$tableName, 
169
-			implode(",\n", $columnStatements));
170
-
171
-		return $sql;
172
-	}
51
+            case 'BOOL';
52
+            case 'BOOLEAN':
53
+            case 'DATETIME':
54
+            case 'DATE':
55
+            case 'TIME':
56
+            case 'TEXT':
57
+            case 'INT UNSIGNED':
58
+                return $type;
59
+
60
+            case 'VARCHAR':
61
+                if ($length === null) {
62
+                    throw new ActiveRecordException(sprintf("field type %s requires specified column field \"LENGTH\"", $colName));
63
+                } else {
64
+                    return sprintf('%s(%d)', $type, $length);	
65
+                }
66
+
67
+            case 'INT':
68
+            case 'TINYINT':
69
+            case 'BIGINT':
70
+            default: 	
71
+                // Implicitly assuming that non-specified cases are correct without a length parameter
72
+                if ($length === null) {
73
+                    return $type;
74
+                } else {
75
+                    return sprintf('%s(%d)', $type, $length);	
76
+                }
77
+        }
78
+    }
79
+
80
+    /**
81
+     * Builds the part of a MySQL create table statement that corresponds to the supplied column
82
+     * @param string $colName 	Name of the database column
83
+     * @param string $type 		The type of the string
84
+     * @param int $properties 	The set of Column properties that apply to this column (See ColumnProperty for options)
85
+     * @return string
86
+     */
87
+    public static function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
88
+    {
89
+        $stmnt = sprintf('`%s` %s ', $colName, self::getDatabaseTypeString($colName, $type, $length));
90
+        if ($properties & ColumnProperty::NOT_NULL) {
91
+            $stmnt .= 'NOT NULL ';
92
+        } else {
93
+            $stmnt .= 'NULL ';
94
+        }
95
+
96
+        if ($default !== NULL) {
97
+            $stmnt .= 'DEFAULT ' . var_export($default, true) . ' ';
98
+        }
99
+
100
+        if ($properties & ColumnProperty::AUTO_INCREMENT) {
101
+            $stmnt .= 'AUTO_INCREMENT ';
102
+        }
103
+
104
+        if ($properties & ColumnProperty::UNIQUE) {
105
+            $stmnt .= 'UNIQUE ';
106
+        }
107
+
108
+        if ($properties & ColumnProperty::PRIMARY_KEY) {
109
+            $stmnt .= 'PRIMARY KEY ';
110
+        }
111
+
112
+        return $stmnt;
113
+    }
114
+
115
+    /**
116
+     * Sorts the column statement components in the order such that the id appears first, 
117
+     * 		followed by all other columns in alphabetical ascending order
118
+     * @param   Array $colStatements Array of column statements
119
+     * @return  Array
120
+     */
121
+    private static function sortColumnStatements($colStatements)
122
+    {
123
+        // Find ID statement and put it first
124
+        $sortedStatements = [];
125
+
126
+        $sortedStatements[] = $colStatements[AbstractActiveRecord::COLUMN_NAME_ID];
127
+        unset($colStatements[AbstractActiveRecord::COLUMN_NAME_ID]);
128
+
129
+        // Sort remaining columns in alphabetical order
130
+        $columns = array_keys($colStatements);
131
+        sort($columns);
132
+        foreach ($columns as $colName) {
133
+            $sortedStatements[] = $colStatements[$colName];
134
+        }
135
+
136
+        return $sortedStatements;
137
+    }
138
+
139
+    /**
140
+     * Builds the MySQL Create Table statement for the internal table definition
141
+     * @return string
142
+     */
143
+    public static function buildCreateTableSQL($tableName, $tableDefinition)
144
+    {
145
+        $columnStatements = [];
146
+        foreach ($tableDefinition as $colName => $definition) {
147
+            // Destructure column definition
148
+            $type    = $definition['type'] ?? null;
149
+            $default = $definition['default'] ?? null;
150
+            $length  = $definition['length'] ?? null;
151
+            $properties = $definition['properties'] ?? null;
152
+
153
+            if (isset($definition['relation']) && $type !== null) {
154
+                $msg = sprintf("Column \"%s\" on table \"%s\": ", $colName, $tableName);
155
+                $msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
156
+                throw new ActiveRecordException($msg);
157
+            } else if (isset($definition['relation'])) {
158
+                $type = AbstractActiveRecord::COLUMN_TYPE_ID;
159
+            }
160
+
161
+            $columnStatements[$colName] = self::buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
162
+        }
163
+
164
+        // Sort table (first column is id, the remaining are alphabetically sorted)
165
+        $columnStatements = self::sortColumnStatements($columnStatements);
166
+
167
+        $sql = sprintf("CREATE TABLE %s (\n%s\n);", 
168
+            $tableName, 
169
+            implode(",\n", $columnStatements));
170
+
171
+        return $sql;
172
+    }
173 173
 
174 174
 }
Please login to merge, or discard this patch.
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.
src/Traits/AutoApi.php 2 patches
Indentation   +366 added lines, -366 removed lines patch added patch discarded remove patch
@@ -9,386 +9,386 @@
 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
-	public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100)
42
-	{
43
-		$query = $this->search();
35
+    /**
36
+     * @param Array $queryparams associative array of query params. Reserved options are
37
+     *                             "search_order_by", "search_order_direction", "search_limit", "search_offset"
38
+     *                             or column names corresponding to an instance of miBadger\Query\QueryExpression
39
+     * @param Array $fieldWhitelist names of the columns that will appear in the output results
40
+     */
41
+    public function apiSearch(Array $queryParams, Array $fieldWhitelist, ?QueryExpression $whereClause = null, int $maxResultLimit = 100)
42
+    {
43
+        $query = $this->search();
44 44
 
45
-		// Build query
46
-		$orderColumn = $queryParams['search_order_by'] ?? null;
47
-		if (!in_array($orderColumn, $fieldWhitelist)) {
48
-			$orderColumn = null;
49
-		}
45
+        // Build query
46
+        $orderColumn = $queryParams['search_order_by'] ?? null;
47
+        if (!in_array($orderColumn, $fieldWhitelist)) {
48
+            $orderColumn = null;
49
+        }
50 50
 
51
-		$orderDirection = $queryParams['search_order_direction'] ?? null;
52
-		if ($orderColumn !== null) {
53
-			$query->orderBy($orderColumn, $orderDirection);
54
-		}
51
+        $orderDirection = $queryParams['search_order_direction'] ?? null;
52
+        if ($orderColumn !== null) {
53
+            $query->orderBy($orderColumn, $orderDirection);
54
+        }
55 55
 		
56
-		if ($whereClause !== null) {
57
-			$query->where($whereClause);
58
-		}
59
-
60
-		$limit = min((int) ($queryParams['search_limit'] ?? $maxResultLimit), $maxResultLimit);
61
-		$query->limit($limit);
62
-
63
-		$offset = $queryParams['search_offset'] ?? 0;
64
-		$query->offset($offset);
65
-
66
-		$numPages = $query->getNumberOfPages();
67
-		$currentPage = $query->getCurrentPage();
68
-
69
-		// Fetch results
70
-		$results = $query->fetchAll();
71
-		$resultsArray = [];
72
-		foreach ($results as $result) {
73
-			$resultsArray[] = $result->toArray($fieldWhitelist);
74
-		}
75
-
76
-		return [
77
-			'search_offset' => $offset,
78
-			'search_limit' => $limit,
79
-			'search_order_by' => $orderColumn,
80
-			'search_order_direction' => $orderDirection,
81
-			'search_pages' => $numPages,
82
-			'search_current' => $currentPage,
83
-			'data' => $resultsArray
84
-		];
85
-	}
86
-
87
-	public function toArray($fieldWhitelist)
88
-	{
89
-		$output = [];
90
-		foreach ($this->tableDefinition as $colName => $definition) {
91
-			if (in_array($colName, $fieldWhitelist)) {
92
-				$output[$colName] = $definition['value'];
93
-			}
94
-		}
95
-
96
-		return $output;
97
-	}
98
-
99
-	public function apiRead($id, Array $fieldWhitelist)
100
-	{
101
-		// @TODO: Should apiRead throw exception or return null on fail?
102
-		$this->read($id);
103
-		return $this->toArray($fieldWhitelist);
104
-	}
105
-
106
-	/* =============================================================
56
+        if ($whereClause !== null) {
57
+            $query->where($whereClause);
58
+        }
59
+
60
+        $limit = min((int) ($queryParams['search_limit'] ?? $maxResultLimit), $maxResultLimit);
61
+        $query->limit($limit);
62
+
63
+        $offset = $queryParams['search_offset'] ?? 0;
64
+        $query->offset($offset);
65
+
66
+        $numPages = $query->getNumberOfPages();
67
+        $currentPage = $query->getCurrentPage();
68
+
69
+        // Fetch results
70
+        $results = $query->fetchAll();
71
+        $resultsArray = [];
72
+        foreach ($results as $result) {
73
+            $resultsArray[] = $result->toArray($fieldWhitelist);
74
+        }
75
+
76
+        return [
77
+            'search_offset' => $offset,
78
+            'search_limit' => $limit,
79
+            'search_order_by' => $orderColumn,
80
+            'search_order_direction' => $orderDirection,
81
+            'search_pages' => $numPages,
82
+            'search_current' => $currentPage,
83
+            'data' => $resultsArray
84
+        ];
85
+    }
86
+
87
+    public function toArray($fieldWhitelist)
88
+    {
89
+        $output = [];
90
+        foreach ($this->tableDefinition as $colName => $definition) {
91
+            if (in_array($colName, $fieldWhitelist)) {
92
+                $output[$colName] = $definition['value'];
93
+            }
94
+        }
95
+
96
+        return $output;
97
+    }
98
+
99
+    public function apiRead($id, Array $fieldWhitelist)
100
+    {
101
+        // @TODO: Should apiRead throw exception or return null on fail?
102
+        $this->read($id);
103
+        return $this->toArray($fieldWhitelist);
104
+    }
105
+
106
+    /* =============================================================
107 107
 	 * ===================== Constraint validation =================
108 108
 	 * ============================================================= */
109 109
 
110
-	/**
111
-	 * Copy all table variables between two instances
112
-	 */
113
-	public function syncInstanceFrom($from)
114
-	{
115
-		foreach ($this->tableDefinition as $colName => $definition) {
116
-			$this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
117
-		}
118
-	}
119
-
120
-	private function filterInputColumns($input, $whitelist)
121
-	{
122
-		$filteredInput = $input;
123
-		foreach ($input as $colName => $value) {
124
-			if (!in_array($colName, $whitelist)) {
125
-				unset($filteredInput[$colName]);
126
-			}
127
-		}
128
-		return $filteredInput;
129
-	}
130
-
131
-	private function validateExcessKeys($input)
132
-	{
133
-		$errors = [];
134
-		foreach ($input as $colName => $value) {
135
-			if (!array_key_exists($colName, $this->tableDefinition)) {
136
-				$errors[$colName] = "Unknown input field";
137
-				continue;
138
-			}
139
-		}
140
-		return $errors;
141
-	}
142
-
143
-	private function validateImmutableColumns($input)
144
-	{
145
-		$errors = [];
146
-		foreach ($this->tableDefinition as $colName => $definition) {
147
-			$property = $definition['properties'] ?? null;
148
-			if (array_key_exists($colName, $input)
149
-				&& $property & ColumnProperty::IMMUTABLE) {
150
-				$errors[$colName] = "Field cannot be changed";
151
-			}
152
-		}
153
-		return $errors;
154
-	}
155
-
156
-	/**
157
-	 * Checks whether input values are correct:
158
-	 * 1. Checks whether a value passes the validation function for that column
159
-	 * 2. Checks whether a value supplied to a relationship column is a valid value
160
-	 */
161
-	private function validateInputValues($input)
162
-	{
163
-		$errors = [];
164
-		foreach ($this->tableDefinition as $colName => $definition) {
165
-			// Validation check 1: If validate function is present
166
-			if (array_key_exists($colName, $input) 
167
-				&& is_callable($definition['validate'] ?? null)) {
168
-				$inputValue = $input[$colName];
169
-
170
-				// If validation function fails
171
-				[$status, $message] = $definition['validate']($inputValue);
172
-				if (!$status) {
173
-					$errors[$colName] = $message;
174
-				}	
175
-			}
176
-
177
-			// Validation check 2: If relation column, check whether entity exists
178
-			$properties = $definition['properties'] ?? null;
179
-			if (isset($definition['relation'])
180
-				&& ($properties & ColumnProperty::NOT_NULL)) {
181
-				$instance = clone $definition['relation'];
182
-				try {
183
-					$instance->read($input[$colName] ?? $definition['value'] ?? null);
184
-				} catch (ActiveRecordException $e) {
185
-					$errors[$colName] = "Entity for this value doesn't exist";
186
-				}
187
-			}
188
-		}
189
-		return $errors;
190
-	}
191
-
192
-	/**
193
-	 * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
194
-	 * Determines whether there are required columns for which no data is provided
195
-	 */
196
-	private function validateMissingKeys($input)
197
-	{
198
-		$errors = [];
199
-
200
-		foreach ($this->tableDefinition as $colName => $colDefinition) {
201
-			$default = $colDefinition['default'] ?? null;
202
-			$properties = $colDefinition['properties'] ?? null;
203
-			$value = $colDefinition['value'];
204
-
205
-			// If nullable and default not set => null
206
-			// If nullable and default null => default (null)
207
-			// If nullable and default set => default (value)
208
-
209
-			// if not nullable and default not set => error
210
-			// if not nullable and default null => error
211
-			// if not nullable and default st => default (value)
212
-			// => if not nullable and default null and value not set (or null) => error message in this method
213
-			if ($properties & ColumnProperty::NOT_NULL
214
-				&& $default === null
215
-				&& !($properties & ColumnProperty::AUTO_INCREMENT)
216
-				&& (!array_key_exists($colName, $input) 
217
-					|| $input[$colName] === null 
218
-					|| (is_string($input[$colName]) && $input[$colName] === '') )
219
-				&& ($value === null
220
-					|| (is_string($value) && $value === ''))) {
221
-				$errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
222
-			} 
223
-		}
224
-
225
-		return $errors;
226
-	}
227
-
228
-	/**
229
-	 * Copies the values for entries in the input with matching variable names in the record definition
230
-	 * @param Array $input The input data to be loaded into $this record
231
-	 */
232
-	private function loadData($input)
233
-	{
234
-		foreach ($this->tableDefinition as $colName => $definition) {
235
-			if (array_key_exists($colName, $input)) {
236
-				$definition['value'] = $input[$colName];
237
-			}
238
-		}
239
-	}
240
-
241
-	/**
242
-	 * @param Array $input Associative array of input values
243
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
244
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
245
-	 * 					of the modified data.
246
-	 */
247
-	public function apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
248
-	{
249
-		// Clone $this to new instance (for restoring if validation goes wrong)
250
-		$transaction = $this->newInstance();
251
-		$errors = [];
252
-
253
-		// Filter out all non-whitelisted input values
254
-		$input = $this->filterInputColumns($input, $createWhitelist);
255
-
256
-		// Validate excess keys
257
-		$errors += $transaction->validateExcessKeys($input);
258
-
259
-		// Validate input values (using validation function)
260
-		$errors += $transaction->validateInputValues($input);
261
-
262
-		// "Copy" data into transaction
263
-		$transaction->loadData($input);
264
-
265
-		// Run create hooks
266
-		foreach ($transaction->createHooks as $colName => $fn) {
267
-			$fn();
268
-		}
269
-
270
-		// Validate missing keys
271
-		$errors += $transaction->validateMissingKeys($input);
272
-
273
-		// If no errors, commit the pending data
274
-		if (empty($errors)) {
275
-			$this->syncInstanceFrom($transaction);
276
-
277
-			// Insert default values for not-null fields
278
-			$this->insertDefaults();
279
-
280
-			try {
281
-				(new Query($this->getPdo(), $this->getTableName()))
282
-					->insert($this->getActiveRecordColumns())
283
-					->execute();
284
-
285
-				$this->setId(intval($this->getPdo()->lastInsertId()));
286
-			} catch (\PDOException $e) {
287
-				// @TODO: Potentially filter and store mysql messages (where possible) in error messages
288
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
289
-			}
290
-
291
-			return [null, $this->toArray($readWhitelist)];
292
-		} else {
293
-			return [$errors, null];
294
-		}
295
-	}
296
-
297
-	/**
298
-	 * @param Array $input Associative array of input values
299
-	 * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
300
-	 * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
301
-	 * 					of the modified data.
302
-	 */
303
-	public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
304
-	{
305
-		$transaction = $this->newInstance();
306
-		$transaction->syncInstanceFrom($this);
307
-		$errors = [];
308
-
309
-		// Filter out all non-whitelisted input values
310
-		$input = $this->filterInputColumns($input, $updateWhitelist);
311
-
312
-		// Check for excess keys
313
-		$errors += $transaction->validateExcessKeys($input);
314
-
315
-		// Check for immutable keys
316
-		$errors += $transaction->validateImmutableColumns($input);
317
-
318
-		// Validate input values (using validation function)
319
-		$errors += $transaction->validateInputValues($input);
320
-
321
-		// "Copy" data into transaction
322
-		$transaction->loadData($input);
323
-
324
-		// Run create hooks
325
-		foreach ($transaction->updateHooks as $colName => $fn) {
326
-			$fn();
327
-		}
328
-
329
-		// Validate missing keys
330
-		$errors += $transaction->validateMissingKeys($input);
331
-
332
-		// Update database
333
-		if (empty($errors)) {
334
-			$this->syncInstanceFrom($transaction);
335
-
336
-			try {
337
-				(new Query($this->getPdo(), $this->getTableName()))
338
-					->update($this->getActiveRecordColumns())
339
-					->where(Query::Equal('id', $this->getId()))
340
-					->execute();
341
-			} catch (\PDOException $e) {
342
-				throw new ActiveRecordException($e->getMessage(), 0, $e);
343
-			}
344
-
345
-			return [null, $this->toArray($readWhitelist)];
346
-		} else {
347
-			return [$errors, null];
348
-		}
349
-	}
350
-
351
-	/**
352
-	 * Returns this active record after reading the attributes from the entry with the given identifier.
353
-	 *
354
-	 * @param mixed $id
355
-	 * @return $this
356
-	 * @throws ActiveRecordException on failure.
357
-	 */
358
-	abstract public function read($id);
359
-
360
-	/**
361
-	 * Returns the PDO.
362
-	 *
363
-	 * @return \PDO the PDO.
364
-	 */
365
-	abstract public function getPdo();
366
-
367
-	/**
368
-	 * Set the ID.
369
-	 *
370
-	 * @param int $id
371
-	 * @return $this
372
-	 */
373
-	abstract protected function setId($id);
374
-
375
-	/**
376
-	 * Returns the ID.
377
-	 *
378
-	 * @return null|int The ID.
379
-	 */
380
-	abstract protected function getId();
381
-
382
-	/**
383
-	 * Returns the active record table.
384
-	 *
385
-	 * @return string the active record table name.
386
-	 */
387
-	abstract public function getTableName();
388
-
389
-	/**
390
-	 * Returns the name -> variable mapping for the table definition.
391
-	 * @return Array The mapping
392
-	 */
393
-	abstract protected function getActiveRecordColumns();
110
+    /**
111
+     * Copy all table variables between two instances
112
+     */
113
+    public function syncInstanceFrom($from)
114
+    {
115
+        foreach ($this->tableDefinition as $colName => $definition) {
116
+            $this->tableDefinition[$colName]['value'] = $from->tableDefinition[$colName]['value'];
117
+        }
118
+    }
119
+
120
+    private function filterInputColumns($input, $whitelist)
121
+    {
122
+        $filteredInput = $input;
123
+        foreach ($input as $colName => $value) {
124
+            if (!in_array($colName, $whitelist)) {
125
+                unset($filteredInput[$colName]);
126
+            }
127
+        }
128
+        return $filteredInput;
129
+    }
130
+
131
+    private function validateExcessKeys($input)
132
+    {
133
+        $errors = [];
134
+        foreach ($input as $colName => $value) {
135
+            if (!array_key_exists($colName, $this->tableDefinition)) {
136
+                $errors[$colName] = "Unknown input field";
137
+                continue;
138
+            }
139
+        }
140
+        return $errors;
141
+    }
142
+
143
+    private function validateImmutableColumns($input)
144
+    {
145
+        $errors = [];
146
+        foreach ($this->tableDefinition as $colName => $definition) {
147
+            $property = $definition['properties'] ?? null;
148
+            if (array_key_exists($colName, $input)
149
+                && $property & ColumnProperty::IMMUTABLE) {
150
+                $errors[$colName] = "Field cannot be changed";
151
+            }
152
+        }
153
+        return $errors;
154
+    }
155
+
156
+    /**
157
+     * Checks whether input values are correct:
158
+     * 1. Checks whether a value passes the validation function for that column
159
+     * 2. Checks whether a value supplied to a relationship column is a valid value
160
+     */
161
+    private function validateInputValues($input)
162
+    {
163
+        $errors = [];
164
+        foreach ($this->tableDefinition as $colName => $definition) {
165
+            // Validation check 1: If validate function is present
166
+            if (array_key_exists($colName, $input) 
167
+                && is_callable($definition['validate'] ?? null)) {
168
+                $inputValue = $input[$colName];
169
+
170
+                // If validation function fails
171
+                [$status, $message] = $definition['validate']($inputValue);
172
+                if (!$status) {
173
+                    $errors[$colName] = $message;
174
+                }	
175
+            }
176
+
177
+            // Validation check 2: If relation column, check whether entity exists
178
+            $properties = $definition['properties'] ?? null;
179
+            if (isset($definition['relation'])
180
+                && ($properties & ColumnProperty::NOT_NULL)) {
181
+                $instance = clone $definition['relation'];
182
+                try {
183
+                    $instance->read($input[$colName] ?? $definition['value'] ?? null);
184
+                } catch (ActiveRecordException $e) {
185
+                    $errors[$colName] = "Entity for this value doesn't exist";
186
+                }
187
+            }
188
+        }
189
+        return $errors;
190
+    }
191
+
192
+    /**
193
+     * This function is only used for API Update calls (direct getter/setter functions are unconstrained)
194
+     * Determines whether there are required columns for which no data is provided
195
+     */
196
+    private function validateMissingKeys($input)
197
+    {
198
+        $errors = [];
199
+
200
+        foreach ($this->tableDefinition as $colName => $colDefinition) {
201
+            $default = $colDefinition['default'] ?? null;
202
+            $properties = $colDefinition['properties'] ?? null;
203
+            $value = $colDefinition['value'];
204
+
205
+            // If nullable and default not set => null
206
+            // If nullable and default null => default (null)
207
+            // If nullable and default set => default (value)
208
+
209
+            // if not nullable and default not set => error
210
+            // if not nullable and default null => error
211
+            // if not nullable and default st => default (value)
212
+            // => if not nullable and default null and value not set (or null) => error message in this method
213
+            if ($properties & ColumnProperty::NOT_NULL
214
+                && $default === null
215
+                && !($properties & ColumnProperty::AUTO_INCREMENT)
216
+                && (!array_key_exists($colName, $input) 
217
+                    || $input[$colName] === null 
218
+                    || (is_string($input[$colName]) && $input[$colName] === '') )
219
+                && ($value === null
220
+                    || (is_string($value) && $value === ''))) {
221
+                $errors[$colName] = sprintf("The required field \"%s\" is missing", $colName);
222
+            } 
223
+        }
224
+
225
+        return $errors;
226
+    }
227
+
228
+    /**
229
+     * Copies the values for entries in the input with matching variable names in the record definition
230
+     * @param Array $input The input data to be loaded into $this record
231
+     */
232
+    private function loadData($input)
233
+    {
234
+        foreach ($this->tableDefinition as $colName => $definition) {
235
+            if (array_key_exists($colName, $input)) {
236
+                $definition['value'] = $input[$colName];
237
+            }
238
+        }
239
+    }
240
+
241
+    /**
242
+     * @param Array $input Associative array of input values
243
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
244
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
245
+     * 					of the modified data.
246
+     */
247
+    public function apiCreate(Array $input, Array $createWhitelist, Array $readWhitelist)
248
+    {
249
+        // Clone $this to new instance (for restoring if validation goes wrong)
250
+        $transaction = $this->newInstance();
251
+        $errors = [];
252
+
253
+        // Filter out all non-whitelisted input values
254
+        $input = $this->filterInputColumns($input, $createWhitelist);
255
+
256
+        // Validate excess keys
257
+        $errors += $transaction->validateExcessKeys($input);
258
+
259
+        // Validate input values (using validation function)
260
+        $errors += $transaction->validateInputValues($input);
261
+
262
+        // "Copy" data into transaction
263
+        $transaction->loadData($input);
264
+
265
+        // Run create hooks
266
+        foreach ($transaction->createHooks as $colName => $fn) {
267
+            $fn();
268
+        }
269
+
270
+        // Validate missing keys
271
+        $errors += $transaction->validateMissingKeys($input);
272
+
273
+        // If no errors, commit the pending data
274
+        if (empty($errors)) {
275
+            $this->syncInstanceFrom($transaction);
276
+
277
+            // Insert default values for not-null fields
278
+            $this->insertDefaults();
279
+
280
+            try {
281
+                (new Query($this->getPdo(), $this->getTableName()))
282
+                    ->insert($this->getActiveRecordColumns())
283
+                    ->execute();
284
+
285
+                $this->setId(intval($this->getPdo()->lastInsertId()));
286
+            } catch (\PDOException $e) {
287
+                // @TODO: Potentially filter and store mysql messages (where possible) in error messages
288
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
289
+            }
290
+
291
+            return [null, $this->toArray($readWhitelist)];
292
+        } else {
293
+            return [$errors, null];
294
+        }
295
+    }
296
+
297
+    /**
298
+     * @param Array $input Associative array of input values
299
+     * @param Array $fieldWhitelist array of column names that are allowed to be filled by the input array 
300
+     * @return Array Array containing the set of optional errors (associative array) and an optional array representation (associative)
301
+     * 					of the modified data.
302
+     */
303
+    public function apiUpdate(Array $input, Array $updateWhitelist, Array $readWhitelist)
304
+    {
305
+        $transaction = $this->newInstance();
306
+        $transaction->syncInstanceFrom($this);
307
+        $errors = [];
308
+
309
+        // Filter out all non-whitelisted input values
310
+        $input = $this->filterInputColumns($input, $updateWhitelist);
311
+
312
+        // Check for excess keys
313
+        $errors += $transaction->validateExcessKeys($input);
314
+
315
+        // Check for immutable keys
316
+        $errors += $transaction->validateImmutableColumns($input);
317
+
318
+        // Validate input values (using validation function)
319
+        $errors += $transaction->validateInputValues($input);
320
+
321
+        // "Copy" data into transaction
322
+        $transaction->loadData($input);
323
+
324
+        // Run create hooks
325
+        foreach ($transaction->updateHooks as $colName => $fn) {
326
+            $fn();
327
+        }
328
+
329
+        // Validate missing keys
330
+        $errors += $transaction->validateMissingKeys($input);
331
+
332
+        // Update database
333
+        if (empty($errors)) {
334
+            $this->syncInstanceFrom($transaction);
335
+
336
+            try {
337
+                (new Query($this->getPdo(), $this->getTableName()))
338
+                    ->update($this->getActiveRecordColumns())
339
+                    ->where(Query::Equal('id', $this->getId()))
340
+                    ->execute();
341
+            } catch (\PDOException $e) {
342
+                throw new ActiveRecordException($e->getMessage(), 0, $e);
343
+            }
344
+
345
+            return [null, $this->toArray($readWhitelist)];
346
+        } else {
347
+            return [$errors, null];
348
+        }
349
+    }
350
+
351
+    /**
352
+     * Returns this active record after reading the attributes from the entry with the given identifier.
353
+     *
354
+     * @param mixed $id
355
+     * @return $this
356
+     * @throws ActiveRecordException on failure.
357
+     */
358
+    abstract public function read($id);
359
+
360
+    /**
361
+     * Returns the PDO.
362
+     *
363
+     * @return \PDO the PDO.
364
+     */
365
+    abstract public function getPdo();
366
+
367
+    /**
368
+     * Set the ID.
369
+     *
370
+     * @param int $id
371
+     * @return $this
372
+     */
373
+    abstract protected function setId($id);
374
+
375
+    /**
376
+     * Returns the ID.
377
+     *
378
+     * @return null|int The ID.
379
+     */
380
+    abstract protected function getId();
381
+
382
+    /**
383
+     * Returns the active record table.
384
+     *
385
+     * @return string the active record table name.
386
+     */
387
+    abstract public function getTableName();
388
+
389
+    /**
390
+     * Returns the name -> variable mapping for the table definition.
391
+     * @return Array The mapping
392
+     */
393
+    abstract protected function getActiveRecordColumns();
394 394
 }
Please login to merge, or discard this patch.
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.
src/Traits/Password.php 1 patch
Indentation   +158 added lines, -158 removed lines patch added patch discarded remove patch
@@ -13,165 +13,165 @@
 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
-		return $this;
136
-	}
137
-
138
-	/**
139
-	 * Clears the current password reset token
140
-	 */
141
-	public function clearPasswordResetToken()
142
-	{
143
-		$this->passwordResetToken = null;
144
-		return $this;
145
-	}
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
+        return $this;
136
+    }
137
+
138
+    /**
139
+     * Clears the current password reset token
140
+     */
141
+    public function clearPasswordResetToken()
142
+    {
143
+        $this->passwordResetToken = null;
144
+        return $this;
145
+    }
146 146
 	
147
-	/**
148
-	 * @return void
149
-	 */
150
-	abstract protected function extendTableDefinition($columnName, $definition);
147
+    /**
148
+     * @return void
149
+     */
150
+    abstract protected function extendTableDefinition($columnName, $definition);
151 151
 	
152
-	/**
153
-	 * @return void
154
-	 */
155
-	abstract protected function registerSearchHook($columnName, $fn);
156
-
157
-	/**
158
-	 * @return void
159
-	 */
160
-	abstract protected function registerDeleteHook($columnName, $fn);
161
-
162
-	/**
163
-	 * @return void
164
-	 */
165
-	abstract protected function registerUpdateHook($columnName, $fn);
166
-
167
-	/**
168
-	 * @return void
169
-	 */
170
-	abstract protected function registerReadHook($columnName, $fn);
171
-
172
-	/**
173
-	 * @return void
174
-	 */
175
-	abstract protected function registerCreateHook($columnName, $fn);
152
+    /**
153
+     * @return void
154
+     */
155
+    abstract protected function registerSearchHook($columnName, $fn);
156
+
157
+    /**
158
+     * @return void
159
+     */
160
+    abstract protected function registerDeleteHook($columnName, $fn);
161
+
162
+    /**
163
+     * @return void
164
+     */
165
+    abstract protected function registerUpdateHook($columnName, $fn);
166
+
167
+    /**
168
+     * @return void
169
+     */
170
+    abstract protected function registerReadHook($columnName, $fn);
171
+
172
+    /**
173
+     * @return void
174
+     */
175
+    abstract protected function registerCreateHook($columnName, $fn);
176 176
 
177 177
 }
178 178
\ No newline at end of file
Please login to merge, or discard this patch.