Test Failed
Push — v2 ( 220402...d76e51 )
by Berend
02:43
created
src/Traits/Datefields.php 2 patches
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -65,7 +65,7 @@  discard block
 block discarded – undo
65 65
 
66 66
 	/**
67 67
 	 * Returns the timestamp of last update for this record
68
-	 * @return DateTime
68
+	 * @return \DateTime
69 69
 	 */
70 70
 	public function getLastModifiedDate()
71 71
 	{
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 
75 75
 	/**
76 76
 	 * Returns the timestamp of when this record was created
77
-	 * @return DateTime
77
+	 * @return \DateTime
78 78
 	 */
79 79
 	public function getCreationDate()
80 80
 	{
Please login to merge, or discard this patch.
Indentation   +62 added lines, -62 removed lines patch added patch discarded remove patch
@@ -10,76 +10,76 @@
 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;
13
+    /** @var string The timestamp representing the moment this record was created */
14
+    protected $created;
15 15
 
16
-	/** @var string The timestamp representing the moment this record was last updated */
17
-	protected $lastModified;
16
+    /** @var string The timestamp representing the moment this record was last updated */
17
+    protected $lastModified;
18 18
 
19
-	/**
20
-	 * this method is required to be called in the constructor for each class that uses this trait. 
21
-	 * It adds the datefields to the table definition and registers the callback hooks
22
-	 */
23
-	protected function initDatefields()
24
-	{
25
-		$this->extendTableDefinition(TRAIT_DATEFIELDS_CREATED, [
26
-			'value' => &$this->created,
27
-			'validate' => null,
28
-			'type' => 'DATETIME',
29
-			'default' => 'CURRENT_TIMESTAMP',
30
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
31
-		]);
19
+    /**
20
+     * this method is required to be called in the constructor for each class that uses this trait. 
21
+     * It adds the datefields to the table definition and registers the callback hooks
22
+     */
23
+    protected function initDatefields()
24
+    {
25
+        $this->extendTableDefinition(TRAIT_DATEFIELDS_CREATED, [
26
+            'value' => &$this->created,
27
+            'validate' => null,
28
+            'type' => 'DATETIME',
29
+            'default' => 'CURRENT_TIMESTAMP',
30
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
31
+        ]);
32 32
 
33
-		$this->extendTableDefinition(TRAIT_DATEFIELDS_LAST_MODIFIED, [
34
-			'value' => &$this->lastModified,
35
-			'validate' => null,
36
-			'type' => 'DATETIME',
37
-			'default' => 'CURRENT_TIMESTAMP',
38
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
39
-		]);
33
+        $this->extendTableDefinition(TRAIT_DATEFIELDS_LAST_MODIFIED, [
34
+            'value' => &$this->lastModified,
35
+            'validate' => null,
36
+            'type' => 'DATETIME',
37
+            'default' => 'CURRENT_TIMESTAMP',
38
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE
39
+        ]);
40 40
 		
41
-		$this->registerUpdateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsUpdateHook');
42
-		$this->registerCreateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsCreateHook');
41
+        $this->registerUpdateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsUpdateHook');
42
+        $this->registerCreateHook(TRAIT_DATEFIELDS_LAST_MODIFIED, 'DatefieldsCreateHook');
43 43
 
44
-		$this->created = null;
45
-		$this->lastModified = null;
46
-	}
44
+        $this->created = null;
45
+        $this->lastModified = null;
46
+    }
47 47
 
48
-	/**
49
-	 * The hook that gets called to set the timestamp whenever a new record is created
50
-	 */
51
-	protected function DatefieldsCreateHook()
52
-	{
53
-		// @TODO: Should this be split up to seperate hooks for "last_modified" and "created" for consistency?
54
-		$this->created = (new \DateTime('now'))->format('Y-m-d H:i:s');
55
-		$this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
56
-	}
48
+    /**
49
+     * The hook that gets called to set the timestamp whenever a new record is created
50
+     */
51
+    protected function DatefieldsCreateHook()
52
+    {
53
+        // @TODO: Should this be split up to seperate hooks for "last_modified" and "created" for consistency?
54
+        $this->created = (new \DateTime('now'))->format('Y-m-d H:i:s');
55
+        $this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
56
+    }
57 57
 
58
-	/**
59
-	 * The hook that gets called to set the timestamp whenever a record gets updated
60
-	 */
61
-	protected function DatefieldsUpdateHook()
62
-	{
63
-		$this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
64
-	}
58
+    /**
59
+     * The hook that gets called to set the timestamp whenever a record gets updated
60
+     */
61
+    protected function DatefieldsUpdateHook()
62
+    {
63
+        $this->lastModified = (new \DateTime('now'))->format('Y-m-d H:i:s');
64
+    }
65 65
 
66
-	/**
67
-	 * Returns the timestamp of last update for this record
68
-	 * @return DateTime
69
-	 */
70
-	public function getLastModifiedDate()
71
-	{
72
-		return new \DateTime($this->lastModified);
73
-	}
66
+    /**
67
+     * Returns the timestamp of last update for this record
68
+     * @return DateTime
69
+     */
70
+    public function getLastModifiedDate()
71
+    {
72
+        return new \DateTime($this->lastModified);
73
+    }
74 74
 
75
-	/**
76
-	 * Returns the timestamp of when this record was created
77
-	 * @return DateTime
78
-	 */
79
-	public function getCreationDate()
80
-	{
81
-		return new \DateTime($this->created);
82
-	}
75
+    /**
76
+     * Returns the timestamp of when this record was created
77
+     * @return DateTime
78
+     */
79
+    public function getCreationDate()
80
+    {
81
+        return new \DateTime($this->created);
82
+    }
83 83
 }
84 84
 
85
-	
86 85
\ No newline at end of file
86
+    
87 87
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/Password.php 2 patches
Unused Use Statements   -1 removed lines patch added patch discarded remove patch
@@ -2,7 +2,6 @@
 block discarded – undo
2 2
 
3 3
 namespace miBadger\ActiveRecord\Traits;
4 4
 
5
-use miBadger\ActiveRecord\ColumnProperty;
6 5
 use miBadger\ActiveRecord\ActiveRecordTraitException;
7 6
 
8 7
 const TRAIT_PASSWORD_FIELD_PASSWORD = "password";
Please login to merge, or discard this patch.
Indentation   +119 added lines, -119 removed lines patch added patch discarded remove patch
@@ -13,123 +13,123 @@
 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' => null,
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
-	/**
80
-	 * Set the password.
81
-	 *
82
-	 * @param string $password
83
-	 * @return $this
84
-	 * @throws \Exception
85
-	 */
86
-	public function setPassword($password)
87
-	{
88
-		if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
89
-			throw new ActiveRecordTraitException(sprintf('\'Password\' must be atleast %s characters long. %s characters provied.', self::PASSWORD_MIN_LENGTH, strlen($password)));
90
-		}
91
-
92
-		$passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
93
-
94
-		if ($passwordHash === false) {
95
-			throw new ActiveRecordTraitException('\'Password\' hash failed.');
96
-		}
97
-
98
-		$this->password = $passwordHash;
99
-
100
-		return $this;
101
-	}
102
-
103
-	/**
104
-	 * @return string The Hash of the password
105
-	 */
106
-	public function getPasswordHash()
107
-	{
108
-		return $this->password;
109
-	}
110
-
111
-	/**
112
-	 * Returns the currently set password token for the entity, or null if not set
113
-	 * @return string|null The password reset token
114
-	 */
115
-	public function getPasswordResetToken()
116
-	{
117
-		return $this->passwordResetToken;
118
-	}
119
-
120
-	/**
121
-	 * Generates a new password reset token for the user
122
-	 */
123
-	public function generatePasswordResetToken()
124
-	{
125
-		$this->passwordResetToken = md5(uniqid(mt_rand(), true));
126
-	}
127
-
128
-	/**
129
-	 * Clears the current password reset token
130
-	 */
131
-	public function clearPasswordResetToken()
132
-	{
133
-	$this->passwordResetToken = null;
134
-	}
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' => null,
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
+    /**
80
+     * Set the password.
81
+     *
82
+     * @param string $password
83
+     * @return $this
84
+     * @throws \Exception
85
+     */
86
+    public function setPassword($password)
87
+    {
88
+        if (strlen($password) < TRAIT_PASSWORD_MIN_LENGTH) {
89
+            throw new ActiveRecordTraitException(sprintf('\'Password\' must be atleast %s characters long. %s characters provied.', self::PASSWORD_MIN_LENGTH, strlen($password)));
90
+        }
91
+
92
+        $passwordHash = \password_hash($password, TRAIT_PASSWORD_ENCRYPTION, ['cost' => TRAIT_PASSWORD_STRENTH]);
93
+
94
+        if ($passwordHash === false) {
95
+            throw new ActiveRecordTraitException('\'Password\' hash failed.');
96
+        }
97
+
98
+        $this->password = $passwordHash;
99
+
100
+        return $this;
101
+    }
102
+
103
+    /**
104
+     * @return string The Hash of the password
105
+     */
106
+    public function getPasswordHash()
107
+    {
108
+        return $this->password;
109
+    }
110
+
111
+    /**
112
+     * Returns the currently set password token for the entity, or null if not set
113
+     * @return string|null The password reset token
114
+     */
115
+    public function getPasswordResetToken()
116
+    {
117
+        return $this->passwordResetToken;
118
+    }
119
+
120
+    /**
121
+     * Generates a new password reset token for the user
122
+     */
123
+    public function generatePasswordResetToken()
124
+    {
125
+        $this->passwordResetToken = md5(uniqid(mt_rand(), true));
126
+    }
127
+
128
+    /**
129
+     * Clears the current password reset token
130
+     */
131
+    public function clearPasswordResetToken()
132
+    {
133
+    $this->passwordResetToken = null;
134
+    }
135 135
 }
136 136
\ No newline at end of file
Please login to merge, or discard this patch.
src/AbstractActiveRecord.php 1 patch
Indentation   +700 added lines, -700 removed lines patch added patch discarded remove patch
@@ -18,710 +18,710 @@
 block discarded – undo
18 18
  */
19 19
 abstract class AbstractActiveRecord implements ActiveRecordInterface
20 20
 {
21
-	const COLUMN_NAME_ID = 'id';
22
-	const COLUMN_TYPE_ID = 'INT UNSIGNED';
23
-
24
-	/** @var \PDO The PDO object. */
25
-	protected $pdo;
26
-
27
-	/** @var null|int The ID. */
28
-	private $id;
29
-
30
-	/** @var array A map of column name to functions that hook the insert function */
31
-	private $registeredCreateHooks;
32
-
33
-	/** @var array A map of column name to functions that hook the read function */
34
-	private $registeredReadHooks;
35
-
36
-	/** @var array A map of column name to functions that hook the update function */
37
-	private $registeredUpdateHooks;
38
-
39
-	/** @var array A map of column name to functions that hook the update function */
40
-	private $registeredDeleteHooks;	
41
-
42
-	/** @var array A map of column name to functions that hook the search function */
43
-	private $registeredSearchHooks;
44
-
45
-	/** @var array A list of table column definitions */
46
-	private $tableDefinition;
47
-
48
-	/**
49
-	 * Construct an abstract active record with the given PDO.
50
-	 *
51
-	 * @param \PDO $pdo
52
-	 */
53
-	public function __construct(\PDO $pdo)
54
-	{
55
-		$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
56
-		$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
57
-
58
-		$this->setPdo($pdo);
59
-		$this->tableDefinition = $this->getActiveRecordTableDefinition();
60
-		$this->registeredCreateHooks = [];
61
-		$this->registeredReadHooks = [];
62
-		$this->registeredUpdateHooks = [];
63
-		$this->registeredDeleteHooks = [];
64
-		$this->registeredSearchHooks = [];
65
-
66
-		// Extend table definition with default ID field, throw exception if field already exists
67
-		if (array_key_exists('id', $this->tableDefinition)) {
68
-			$message = "Table definition in record contains a field with name \"id\"";
69
-			$message .= ", which is a reserved name by ActiveRecord";
70
-			throw new ActiveRecordException($message, 0);
71
-		}
72
-
73
-		$this->tableDefinition[self::COLUMN_NAME_ID] =
74
-		[
75
-			'value' => &$this->id,
76
-			'validate' => null,
77
-			'type' => self::COLUMN_TYPE_ID,
78
-			'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE | ColumnProperty::AUTO_INCREMENT | ColumnProperty::PRIMARY_KEY
79
-		];
80
-	}
81
-
82
-	/**
83
-	 * Register a new hook for a specific column that gets called before execution of the create() method
84
-	 * Only one hook per column can be registered at a time
85
-	 * @param string $columnName The name of the column that is registered.
86
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
87
-	 */
88
-	public function registerCreateHook($columnName, $fn) 
89
-	{
90
-		// Check whether column exists
91
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
92
-		{
93
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
94
-		}
95
-
96
-		// Enforcing 1 hook per table column
97
-		if (array_key_exists($columnName, $this->registeredCreateHooks)) {
98
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
99
-			$message .= "do you have conflicting traits?";
100
-			throw new ActiveRecordException($message, 0);
101
-		}
102
-
103
-		if (is_string($fn) && is_callable([$this, $fn])) {
104
-			$this->registeredCreateHooks[$columnName] = [$this, $fn];
105
-		} else if (is_callable($fn)) {
106
-			$this->registeredCreateHooks[$columnName] = $fn;
107
-		} else {
108
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
109
-		}
110
-	}
111
-
112
-	/**
113
-	 * Register a new hook for a specific column that gets called before execution of the read() method
114
-	 * Only one hook per column can be registered at a time
115
-	 * @param string $columnName The name of the column that is registered.
116
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
117
-	 */
118
-	public function registerReadHook($columnName, $fn)
119
-	{
120
-		// Check whether column exists
121
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
122
-		{
123
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
124
-		}
125
-
126
-		// Enforcing 1 hook per table column
127
-		if (array_key_exists($columnName, $this->registeredReadHooks)) {
128
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
129
-			$message .= "do you have conflicting traits?";
130
-			throw new ActiveRecordException($message, 0);
131
-		}
132
-
133
-		if (is_string($fn) && is_callable([$this, $fn])) {
134
-			$this->registeredReadHooks[$columnName] = [$this, $fn];
135
-		} else if (is_callable($fn)) {
136
-			$this->registeredReadHooks[$columnName] = $fn;
137
-		} else {
138
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
139
-		}
140
-	}
141
-
142
-	/**
143
-	 * Register a new hook for a specific column that gets called before execution of the update() method
144
-	 * Only one hook per column can be registered at a time
145
-	 * @param string $columnName The name of the column that is registered.
146
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
147
-	 */
148
-	public function registerUpdateHook($columnName, $fn)
149
-	{
150
-		// Check whether column exists
151
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
152
-		{
153
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
154
-		}
155
-
156
-		// Enforcing 1 hook per table column
157
-		if (array_key_exists($columnName, $this->registeredUpdateHooks)) {
158
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
159
-			$message .= "do you have conflicting traits?";
160
-			throw new ActiveRecordException($message, 0);
161
-		}
162
-
163
-		if (is_string($fn) && is_callable([$this, $fn])) {
164
-			$this->registeredUpdateHooks[$columnName] = [$this, $fn];
165
-		} else if (is_callable($fn)) {
166
-			$this->registeredUpdateHooks[$columnName] = $fn;
167
-		} else {
168
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
169
-		}
170
-	}
171
-
172
-	/**
173
-	 * Register a new hook for a specific column that gets called before execution of the delete() method
174
-	 * Only one hook per column can be registered at a time
175
-	 * @param string $columnName The name of the column that is registered.
176
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
177
-	 */
178
-	public function registerDeleteHook($columnName, $fn)
179
-	{
180
-		// Check whether column exists
181
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
182
-		{
183
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
184
-		}
185
-
186
-		// Enforcing 1 hook per table column
187
-		if (array_key_exists($columnName, $this->registeredDeleteHooks)) {
188
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
189
-			$message .= "do you have conflicting traits?";
190
-			throw new ActiveRecordException($message, 0);
191
-		}
192
-
193
-		if (is_string($fn) && is_callable([$this, $fn])) {
194
-			$this->registeredDeleteHooks[$columnName] = [$this, $fn];
195
-		} else if (is_callable($fn)) {
196
-			$this->registeredDeleteHooks[$columnName] = $fn;
197
-		} else {
198
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
199
-		}
200
-	}
201
-
202
-	/**
203
-	 * Register a new hook for a specific column that gets called before execution of the search() method
204
-	 * Only one hook per column can be registered at a time
205
-	 * @param string $columnName The name of the column that is registered.
206
-	 * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. The callable is required to take one argument: an instance of miBadger\Query\Query; 
207
-	 */
208
-	public function registerSearchHook($columnName, $fn)
209
-	{
210
-		// Check whether column exists
211
-		if (!array_key_exists($columnName, $this->tableDefinition)) 
212
-		{
213
-			throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
214
-		}
215
-
216
-		// Enforcing 1 hook per table column
217
-		if (array_key_exists($columnName, $this->registeredSearchHooks)) {
218
-			$message = "Hook is trying to register on an already registered column \"$columnName\", ";
219
-			$message .= "do you have conflicting traits?";
220
-			throw new ActiveRecordException($message, 0);
221
-		}
222
-
223
-		if (is_string($fn) && is_callable([$this, $fn])) {
224
-			$this->registeredSearchHooks[$columnName] = [$this, $fn];
225
-		} else if (is_callable($fn)) {
226
-			$this->registeredSearchHooks[$columnName] = $fn;
227
-		} else {
228
-			throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
229
-		}
230
-	}
231
-
232
-	/**
233
-	 * Adds a new column definition to the table.
234
-	 * @param string $columnName The name of the column that is registered.
235
-	 * @param Array $definition The definition of that column.
236
-	 */
237
-	public function extendTableDefinition($columnName, $definition)
238
-	{
239
-		if ($this->tableDefinition === null) {
240
-			throw new ActiveRecordException("tableDefinition is null, most likely due to parent class not having been initialized in constructor");
241
-		}
242
-
243
-		// Enforcing table can only be extended with new columns
244
-		if (array_key_exists($columnName, $this->tableDefinition)) {
245
-			$message = "Table is being extended with a column that already exists, ";
246
-			$message .= "\"$columnName\" conflicts with your table definition";
247
-			throw new ActiveRecordException($message, 0);
248
-		}
249
-
250
-		$this->tableDefinition[$columnName] = $definition;
251
-	}
252
-
253
-	private function getDatabaseTypeString($colName, $type, $length)
254
-	{
255
-		if ($type === null) 
256
-		{
257
-			throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
258
-		}
259
-
260
-		switch (strtoupper($type)) {
261
-			case 'DATETIME':
262
-			case 'DATE':
263
-			case 'TIME':
264
-			case 'TEXT':
265
-			case 'INT UNSIGNED':
266
-				return $type;
267
-
268
-			case 'VARCHAR':
269
-				return sprintf('%s(%d)', $type, $length);
270
-
271
-			case 'INT':
272
-			case 'TINYINT':
273
-			case 'BIGINT':
274
-			default: 	
275
-			// @TODO(Default): throw exception, or implicitly assume that type is correct? (For when using SQL databases with different types)
276
-				if ($length === null) {
277
-					return $type;
278
-				} else {
279
-					return sprintf('%s(%d)', $type, $length);	
280
-				}
281
-		}
282
-	}
283
-
284
-	private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
285
-	{
286
-
287
-		$stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
288
-		if ($properties & ColumnProperty::NOT_NULL) {
289
-			$stmnt .= 'NOT NULL ';
290
-		} else {
291
-			$stmnt .= 'NULL ';
292
-		}
293
-
294
-		if ($default !== NULL) {
295
-			$stmnt .= ' DEFAULT ' . $default . ' ';
296
-		}
297
-
298
-		if ($properties & ColumnProperty::AUTO_INCREMENT) {
299
-			$stmnt .= 'AUTO_INCREMENT ';
300
-		}
301
-
302
-		if ($properties & ColumnProperty::UNIQUE) {
303
-			$stmnt .= 'UNIQUE ';
304
-		}
305
-
306
-		if ($properties & ColumnProperty::PRIMARY_KEY) {
307
-			$stmnt .= 'PRIMARY KEY ';
308
-		}
309
-
310
-		return $stmnt;
311
-	}
312
-
313
-
314
-	private function sortColumnStatements($colStatements)
315
-	{
316
-		// Find ID statement and put it first
317
-		$sortedStatements = [];
318
-
319
-		$sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
320
-		unset($colStatements[self::COLUMN_NAME_ID]);
321
-
322
-		// Sort remaining columns in alphabetical order
323
-		$columns = array_keys($colStatements);
324
-		sort($columns);
325
-		foreach ($columns as $colName) {
326
-			$sortedStatements[] = $colStatements[$colName];
327
-		}
328
-
329
-		return $sortedStatements;
330
-	}
331
-
332
-
333
-	public function buildCreateTableSQL()
334
-	{
335
-		$columnStatements = [];
336
-		foreach ($this->tableDefinition as $colName => $definition) {
337
-			// Destructure column definition
338
-			$type    = $definition['type'] ?? null;
339
-			$default = $definition['default'] ?? null;
340
-			$length  = $definition['length'] ?? null;
341
-			$properties = $definition['properties'] ?? null;
342
-
343
-			if (isset($definition['relation']) && $type !== null) {
344
-				$msg = "Column \"$colName\": ";
345
-				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
346
-				throw new ActiveRecordException($msg);
347
-			} else if (isset($definition['relation'])) {
348
-				$type = self::COLUMN_TYPE_ID;
349
-			}
350
-
351
-			$columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
352
-		}
353
-
354
-		// Sort table (first column is id, the remaining are alphabetically sorted)
355
-		$columnStatements = $this->sortColumnStatements($columnStatements);
356
-
357
-		$sql = 'CREATE TABLE ' . $this->getActiveRecordTable() . ' ';
358
-		$sql .= "(\n";
359
-		$sql .= join($columnStatements, ",\n");
360
-		$sql .= "\n);";
361
-
362
-		return $sql;
363
-	}
364
-
365
-	public function createTable()
366
-	{
367
-		$this->pdo->query($this->buildCreateTableSQL());
368
-	}
369
-
370
-	protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
371
-	{
372
-		$template = <<<SQL
21
+    const COLUMN_NAME_ID = 'id';
22
+    const COLUMN_TYPE_ID = 'INT UNSIGNED';
23
+
24
+    /** @var \PDO The PDO object. */
25
+    protected $pdo;
26
+
27
+    /** @var null|int The ID. */
28
+    private $id;
29
+
30
+    /** @var array A map of column name to functions that hook the insert function */
31
+    private $registeredCreateHooks;
32
+
33
+    /** @var array A map of column name to functions that hook the read function */
34
+    private $registeredReadHooks;
35
+
36
+    /** @var array A map of column name to functions that hook the update function */
37
+    private $registeredUpdateHooks;
38
+
39
+    /** @var array A map of column name to functions that hook the update function */
40
+    private $registeredDeleteHooks;	
41
+
42
+    /** @var array A map of column name to functions that hook the search function */
43
+    private $registeredSearchHooks;
44
+
45
+    /** @var array A list of table column definitions */
46
+    private $tableDefinition;
47
+
48
+    /**
49
+     * Construct an abstract active record with the given PDO.
50
+     *
51
+     * @param \PDO $pdo
52
+     */
53
+    public function __construct(\PDO $pdo)
54
+    {
55
+        $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
56
+        $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
57
+
58
+        $this->setPdo($pdo);
59
+        $this->tableDefinition = $this->getActiveRecordTableDefinition();
60
+        $this->registeredCreateHooks = [];
61
+        $this->registeredReadHooks = [];
62
+        $this->registeredUpdateHooks = [];
63
+        $this->registeredDeleteHooks = [];
64
+        $this->registeredSearchHooks = [];
65
+
66
+        // Extend table definition with default ID field, throw exception if field already exists
67
+        if (array_key_exists('id', $this->tableDefinition)) {
68
+            $message = "Table definition in record contains a field with name \"id\"";
69
+            $message .= ", which is a reserved name by ActiveRecord";
70
+            throw new ActiveRecordException($message, 0);
71
+        }
72
+
73
+        $this->tableDefinition[self::COLUMN_NAME_ID] =
74
+        [
75
+            'value' => &$this->id,
76
+            'validate' => null,
77
+            'type' => self::COLUMN_TYPE_ID,
78
+            'properties' => ColumnProperty::NOT_NULL | ColumnProperty::IMMUTABLE | ColumnProperty::AUTO_INCREMENT | ColumnProperty::PRIMARY_KEY
79
+        ];
80
+    }
81
+
82
+    /**
83
+     * Register a new hook for a specific column that gets called before execution of the create() method
84
+     * Only one hook per column can be registered at a time
85
+     * @param string $columnName The name of the column that is registered.
86
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
87
+     */
88
+    public function registerCreateHook($columnName, $fn) 
89
+    {
90
+        // Check whether column exists
91
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
92
+        {
93
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
94
+        }
95
+
96
+        // Enforcing 1 hook per table column
97
+        if (array_key_exists($columnName, $this->registeredCreateHooks)) {
98
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
99
+            $message .= "do you have conflicting traits?";
100
+            throw new ActiveRecordException($message, 0);
101
+        }
102
+
103
+        if (is_string($fn) && is_callable([$this, $fn])) {
104
+            $this->registeredCreateHooks[$columnName] = [$this, $fn];
105
+        } else if (is_callable($fn)) {
106
+            $this->registeredCreateHooks[$columnName] = $fn;
107
+        } else {
108
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
109
+        }
110
+    }
111
+
112
+    /**
113
+     * Register a new hook for a specific column that gets called before execution of the read() method
114
+     * Only one hook per column can be registered at a time
115
+     * @param string $columnName The name of the column that is registered.
116
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
117
+     */
118
+    public function registerReadHook($columnName, $fn)
119
+    {
120
+        // Check whether column exists
121
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
122
+        {
123
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
124
+        }
125
+
126
+        // Enforcing 1 hook per table column
127
+        if (array_key_exists($columnName, $this->registeredReadHooks)) {
128
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
129
+            $message .= "do you have conflicting traits?";
130
+            throw new ActiveRecordException($message, 0);
131
+        }
132
+
133
+        if (is_string($fn) && is_callable([$this, $fn])) {
134
+            $this->registeredReadHooks[$columnName] = [$this, $fn];
135
+        } else if (is_callable($fn)) {
136
+            $this->registeredReadHooks[$columnName] = $fn;
137
+        } else {
138
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
139
+        }
140
+    }
141
+
142
+    /**
143
+     * Register a new hook for a specific column that gets called before execution of the update() method
144
+     * Only one hook per column can be registered at a time
145
+     * @param string $columnName The name of the column that is registered.
146
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
147
+     */
148
+    public function registerUpdateHook($columnName, $fn)
149
+    {
150
+        // Check whether column exists
151
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
152
+        {
153
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
154
+        }
155
+
156
+        // Enforcing 1 hook per table column
157
+        if (array_key_exists($columnName, $this->registeredUpdateHooks)) {
158
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
159
+            $message .= "do you have conflicting traits?";
160
+            throw new ActiveRecordException($message, 0);
161
+        }
162
+
163
+        if (is_string($fn) && is_callable([$this, $fn])) {
164
+            $this->registeredUpdateHooks[$columnName] = [$this, $fn];
165
+        } else if (is_callable($fn)) {
166
+            $this->registeredUpdateHooks[$columnName] = $fn;
167
+        } else {
168
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
169
+        }
170
+    }
171
+
172
+    /**
173
+     * Register a new hook for a specific column that gets called before execution of the delete() method
174
+     * Only one hook per column can be registered at a time
175
+     * @param string $columnName The name of the column that is registered.
176
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object.
177
+     */
178
+    public function registerDeleteHook($columnName, $fn)
179
+    {
180
+        // Check whether column exists
181
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
182
+        {
183
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
184
+        }
185
+
186
+        // Enforcing 1 hook per table column
187
+        if (array_key_exists($columnName, $this->registeredDeleteHooks)) {
188
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
189
+            $message .= "do you have conflicting traits?";
190
+            throw new ActiveRecordException($message, 0);
191
+        }
192
+
193
+        if (is_string($fn) && is_callable([$this, $fn])) {
194
+            $this->registeredDeleteHooks[$columnName] = [$this, $fn];
195
+        } else if (is_callable($fn)) {
196
+            $this->registeredDeleteHooks[$columnName] = $fn;
197
+        } else {
198
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
199
+        }
200
+    }
201
+
202
+    /**
203
+     * Register a new hook for a specific column that gets called before execution of the search() method
204
+     * Only one hook per column can be registered at a time
205
+     * @param string $columnName The name of the column that is registered.
206
+     * @param string|callable $fn Either a callable, or the name of a method on the inheriting object. The callable is required to take one argument: an instance of miBadger\Query\Query; 
207
+     */
208
+    public function registerSearchHook($columnName, $fn)
209
+    {
210
+        // Check whether column exists
211
+        if (!array_key_exists($columnName, $this->tableDefinition)) 
212
+        {
213
+            throw new ActiveRecordException("Hook is trying to register on non-existing column \"$columnName\"", 0);
214
+        }
215
+
216
+        // Enforcing 1 hook per table column
217
+        if (array_key_exists($columnName, $this->registeredSearchHooks)) {
218
+            $message = "Hook is trying to register on an already registered column \"$columnName\", ";
219
+            $message .= "do you have conflicting traits?";
220
+            throw new ActiveRecordException($message, 0);
221
+        }
222
+
223
+        if (is_string($fn) && is_callable([$this, $fn])) {
224
+            $this->registeredSearchHooks[$columnName] = [$this, $fn];
225
+        } else if (is_callable($fn)) {
226
+            $this->registeredSearchHooks[$columnName] = $fn;
227
+        } else {
228
+            throw new ActiveRecordException("Provided hook on column \"$columnName\" is not callable", 0);
229
+        }
230
+    }
231
+
232
+    /**
233
+     * Adds a new column definition to the table.
234
+     * @param string $columnName The name of the column that is registered.
235
+     * @param Array $definition The definition of that column.
236
+     */
237
+    public function extendTableDefinition($columnName, $definition)
238
+    {
239
+        if ($this->tableDefinition === null) {
240
+            throw new ActiveRecordException("tableDefinition is null, most likely due to parent class not having been initialized in constructor");
241
+        }
242
+
243
+        // Enforcing table can only be extended with new columns
244
+        if (array_key_exists($columnName, $this->tableDefinition)) {
245
+            $message = "Table is being extended with a column that already exists, ";
246
+            $message .= "\"$columnName\" conflicts with your table definition";
247
+            throw new ActiveRecordException($message, 0);
248
+        }
249
+
250
+        $this->tableDefinition[$columnName] = $definition;
251
+    }
252
+
253
+    private function getDatabaseTypeString($colName, $type, $length)
254
+    {
255
+        if ($type === null) 
256
+        {
257
+            throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
258
+        }
259
+
260
+        switch (strtoupper($type)) {
261
+            case 'DATETIME':
262
+            case 'DATE':
263
+            case 'TIME':
264
+            case 'TEXT':
265
+            case 'INT UNSIGNED':
266
+                return $type;
267
+
268
+            case 'VARCHAR':
269
+                return sprintf('%s(%d)', $type, $length);
270
+
271
+            case 'INT':
272
+            case 'TINYINT':
273
+            case 'BIGINT':
274
+            default: 	
275
+            // @TODO(Default): throw exception, or implicitly assume that type is correct? (For when using SQL databases with different types)
276
+                if ($length === null) {
277
+                    return $type;
278
+                } else {
279
+                    return sprintf('%s(%d)', $type, $length);	
280
+                }
281
+        }
282
+    }
283
+
284
+    private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
285
+    {
286
+
287
+        $stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
288
+        if ($properties & ColumnProperty::NOT_NULL) {
289
+            $stmnt .= 'NOT NULL ';
290
+        } else {
291
+            $stmnt .= 'NULL ';
292
+        }
293
+
294
+        if ($default !== NULL) {
295
+            $stmnt .= ' DEFAULT ' . $default . ' ';
296
+        }
297
+
298
+        if ($properties & ColumnProperty::AUTO_INCREMENT) {
299
+            $stmnt .= 'AUTO_INCREMENT ';
300
+        }
301
+
302
+        if ($properties & ColumnProperty::UNIQUE) {
303
+            $stmnt .= 'UNIQUE ';
304
+        }
305
+
306
+        if ($properties & ColumnProperty::PRIMARY_KEY) {
307
+            $stmnt .= 'PRIMARY KEY ';
308
+        }
309
+
310
+        return $stmnt;
311
+    }
312
+
313
+
314
+    private function sortColumnStatements($colStatements)
315
+    {
316
+        // Find ID statement and put it first
317
+        $sortedStatements = [];
318
+
319
+        $sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
320
+        unset($colStatements[self::COLUMN_NAME_ID]);
321
+
322
+        // Sort remaining columns in alphabetical order
323
+        $columns = array_keys($colStatements);
324
+        sort($columns);
325
+        foreach ($columns as $colName) {
326
+            $sortedStatements[] = $colStatements[$colName];
327
+        }
328
+
329
+        return $sortedStatements;
330
+    }
331
+
332
+
333
+    public function buildCreateTableSQL()
334
+    {
335
+        $columnStatements = [];
336
+        foreach ($this->tableDefinition as $colName => $definition) {
337
+            // Destructure column definition
338
+            $type    = $definition['type'] ?? null;
339
+            $default = $definition['default'] ?? null;
340
+            $length  = $definition['length'] ?? null;
341
+            $properties = $definition['properties'] ?? null;
342
+
343
+            if (isset($definition['relation']) && $type !== null) {
344
+                $msg = "Column \"$colName\": ";
345
+                $msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
346
+                throw new ActiveRecordException($msg);
347
+            } else if (isset($definition['relation'])) {
348
+                $type = self::COLUMN_TYPE_ID;
349
+            }
350
+
351
+            $columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
352
+        }
353
+
354
+        // Sort table (first column is id, the remaining are alphabetically sorted)
355
+        $columnStatements = $this->sortColumnStatements($columnStatements);
356
+
357
+        $sql = 'CREATE TABLE ' . $this->getActiveRecordTable() . ' ';
358
+        $sql .= "(\n";
359
+        $sql .= join($columnStatements, ",\n");
360
+        $sql .= "\n);";
361
+
362
+        return $sql;
363
+    }
364
+
365
+    public function createTable()
366
+    {
367
+        $this->pdo->query($this->buildCreateTableSQL());
368
+    }
369
+
370
+    protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
371
+    {
372
+        $template = <<<SQL
373 373
 ALTER TABLE `%s`
374 374
 ADD CONSTRAINT
375 375
 FOREIGN KEY (`%s`)
376 376
 REFERENCES `%s`(`%s`)
377 377
 ON DELETE CASCADE;
378 378
 SQL;
379
-		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
380
-	}
381
-
382
-	public function createTableConstraints()
383
-	{
384
-		// Iterate over columns, check whether "relation" field exists, if so create constraint
385
-		foreach ($this->tableDefinition as $colName => $definition) {
386
-			if ($definition['relation'] ?? null instanceof AbstractActiveRecord) {
387
-				// Forge new relation
388
-				$target = $definition['relation'];
389
-				$constraintSql = $this->buildConstraint($target->getActiveRecordTable(), 'id', $this->getActiveRecordTable(), $colName);
390
-
391
-				$this->pdo->query($constraintSql);
392
-			}
393
-		}
394
-	}
395
-
396
-	private function getActiveRecordColumns()
397
-	{
398
-		$bindings = [];
399
-		foreach ($this->tableDefinition as $colName => $definition) {
400
-
401
-			// Ignore the id column (key) when inserting or updating
402
-			if ($colName == self::COLUMN_NAME_ID) {
403
-				continue;
404
-			}
405
-
406
-			$bindings[$colName] = &$definition['value'];
407
-		}
408
-		return $bindings;
409
-	}
410
-
411
-	/**
412
-	 * {@inheritdoc}
413
-	 */
414
-	public function create()
415
-	{
416
-		foreach ($this->registeredCreateHooks as $colName => $fn) {
417
-			// @TODO: Would it be better to pass the Query to the function?
418
-			$fn();
419
-		}
420
-
421
-		try {
422
-			$q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
423
-				->insert($this->getActiveRecordColumns())
424
-				->execute();
425
-
426
-			$this->setId(intval($this->getPdo()->lastInsertId()));
427
-		} catch (\PDOException $e) {
428
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
429
-		}
430
-
431
-		return $this;
432
-	}
433
-
434
-	/**
435
-	 * {@inheritdoc}
436
-	 */
437
-	public function read($id)
438
-	{
439
-		foreach ($this->registeredReadHooks as $colName => $fn) {
440
-			// @TODO: Would it be better to pass the Query to the function?
441
-			$fn();
442
-		}
443
-
444
-		try {
445
-			$row = (new Query($this->getPdo(), $this->getActiveRecordTable()))
446
-				->select()
447
-				->where('id', '=', $id)
448
-				->execute()
449
-				->fetch();
450
-
451
-			if ($row === false) {
452
-				throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()));
453
-			}
454
-
455
-			$this->fill($row)->setId($id);
456
-		} catch (\PDOException $e) {
457
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
458
-		}
459
-
460
-		return $this;
461
-	}
462
-
463
-	/**
464
-	 * {@inheritdoc}
465
-	 */
466
-	public function update()
467
-	{
468
-		foreach ($this->registeredUpdateHooks as $colName => $fn) {
469
-			// @TODO: Would it be better to pass the Query to the function?
470
-			$fn();
471
-		}
472
-
473
-		try {
474
-			(new Query($this->getPdo(), $this->getActiveRecordTable()))
475
-				->update($this->getActiveRecordColumns())
476
-				->where('id', '=', $this->getId())
477
-				->execute();
478
-		} catch (\PDOException $e) {
479
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
480
-		}
481
-
482
-		return $this;
483
-	}
484
-
485
-	/**
486
-	 * {@inheritdoc}
487
-	 */
488
-	public function delete()
489
-	{
490
-		foreach ($this->registeredDeleteHooks as $colName => $fn) {
491
-			// @TODO: Would it be better to pass the Query to the function?
492
-			$fn();
493
-		}
494
-
495
-		try {
496
-			(new Query($this->getPdo(), $this->getActiveRecordTable()))
497
-				->delete()
498
-				->where('id', '=', $this->getId())
499
-				->execute();
500
-
501
-			$this->setId(null);
502
-		} catch (\PDOException $e) {
503
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
504
-		}
505
-
506
-		return $this;
507
-	}
508
-
509
-	/**
510
-	 * {@inheritdoc}
511
-	 */
512
-	public function sync()
513
-	{
514
-		if (!$this->exists()) {
515
-			return $this->create();
516
-		}
517
-
518
-		return $this->update();
519
-	}
520
-
521
-	/**
522
-	 * {@inheritdoc}
523
-	 */
524
-	public function exists()
525
-	{
526
-		return $this->getId() !== null;
527
-	}
528
-
529
-	/**
530
-	 * {@inheritdoc}
531
-	 */
532
-	public function fill(array $attributes)
533
-	{
534
-		$columns = $this->getActiveRecordColumns();
535
-		$columns['id'] = &$this->id;
536
-
537
-		foreach ($attributes as $key => $value) {
538
-			if (array_key_exists($key, $columns)) {
539
-				$columns[$key] = $value;
540
-			}
541
-		}
542
-
543
-		return $this;
544
-	}
545
-
546
-	/**
547
-	 * {@inheritdoc}
548
-	 */
549
-	public function searchOne(array $where = [], array $orderBy = [])
550
-	{
551
-		try {
552
-			$row = $this->getSearchQueryResult($where, $orderBy, 1)->fetch();
553
-
554
-			if ($row === false) {
555
-				throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->getActiveRecordTable()));
556
-			}
557
-
558
-			return $this->fill($row)->setId($row['id']);
559
-		} catch (\PDOException $e) {
560
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
561
-		}
562
-	}
563
-
564
-	/**
565
-	 * {@inheritdoc}
566
-	 */
567
-	public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
568
-	{
569
-		try {
570
-			$queryResult = $this->getSearchQueryResult($where, $orderBy, $limit, $offset);
571
-			$result = [];
572
-
573
-			foreach ($queryResult as $row) {
574
-				$new = clone $this;
575
-
576
-				$result[] = $new->fill($row)->setId($row['id']);
577
-			}
578
-
579
-			return $result;
580
-		} catch (\PDOException $e) {
581
-			throw new ActiveRecordException($e->getMessage(), 0, $e);
582
-		}
583
-	}
584
-
585
-	/**
586
-	 * Returns the search query result with the given where, order by, limit and offset clauses.
587
-	 *
588
-	 * @param array $where = []
589
-	 * @param array $orderBy = []
590
-	 * @param int $limit = -1
591
-	 * @param int $offset = 0
592
-	 * @return \miBadger\Query\QueryResult the search query result with the given where, order by, limit and offset clauses.
593
-	 */
594
-	private function getSearchQueryResult(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
595
-	{
596
-		$query = (new Query($this->getPdo(), $this->getActiveRecordTable()))
597
-			->select();
598
-
599
-		$this->getSearchQueryWhere($query, $where);
600
-		$this->getSearchQueryOrderBy($query, $orderBy);
601
-		$this->getSearchQueryLimit($query, $limit, $offset);
602
-
603
-		// Ignore all trait modifiers for which a where clause was specified
604
-		$registeredSearchHooks = $this->registeredSearchHooks;
605
-		foreach ($where as $index => $clause) {
606
-			[$colName, , ] = $clause;
607
-			unset($registeredSearchHooks[$colName]);
608
-		}
609
-
610
-		// Allow traits to modify the query
611
-		foreach ($registeredSearchHooks as $column => $searchFunction) {
612
-			$searchFunction($query);
613
-		}
614
-
615
-		return $query->execute();
616
-	}
617
-
618
-	/**
619
-	 * Returns the given query after adding the given where conditions.
620
-	 *
621
-	 * @param \miBadger\Query\Query $query
622
-	 * @param array $where
623
-	 * @return \miBadger\Query\Query the given query after adding the given where conditions.
624
-	 */
625
-	private function getSearchQueryWhere($query, $where)
626
-	{
627
-		foreach ($where as $key => $value) {
628
-			$query->where($value[0], $value[1], $value[2]);
629
-		}
630
-
631
-		return $query;
632
-	}
633
-
634
-	/**
635
-	 * Returns the given query after adding the given order by conditions.
636
-	 *
637
-	 * @param \miBadger\Query\Query $query
638
-	 * @param array $orderBy
639
-	 * @return \miBadger\Query\Query the given query after adding the given order by conditions.
640
-	 */
641
-	private function getSearchQueryOrderBy($query, $orderBy)
642
-	{
643
-		foreach ($orderBy as $key => $value) {
644
-			$query->orderBy($key, $value);
645
-		}
646
-
647
-		return $query;
648
-	}
649
-
650
-	/**
651
-	 * Returns the given query after adding the given limit and offset conditions.
652
-	 *
653
-	 * @param \miBadger\Query\Query $query
654
-	 * @param int $limit
655
-	 * @param int $offset
656
-	 * @return \miBadger\Query\Query the given query after adding the given limit and offset conditions.
657
-	 */
658
-	private function getSearchQueryLimit($query, $limit, $offset)
659
-	{
660
-		if ($limit > -1) {
661
-			$query->limit($limit);
662
-			$query->offset($offset);
663
-		}
664
-
665
-		return $query;
666
-	}
667
-
668
-	/**
669
-	 * Returns the PDO.
670
-	 *
671
-	 * @return \PDO the PDO.
672
-	 */
673
-	public function getPdo()
674
-	{
675
-		return $this->pdo;
676
-	}
677
-
678
-	/**
679
-	 * Set the PDO.
680
-	 *
681
-	 * @param \PDO $pdo
682
-	 * @return $this
683
-	 */
684
-	protected function setPdo($pdo)
685
-	{
686
-		$this->pdo = $pdo;
687
-
688
-		return $this;
689
-	}
690
-
691
-	/**
692
-	 * Returns the ID.
693
-	 *
694
-	 * @return null|int The ID.
695
-	 */
696
-	public function getId()
697
-	{
698
-		return $this->id;
699
-	}
700
-
701
-	/**
702
-	 * Set the ID.
703
-	 *
704
-	 * @param int $id
705
-	 * @return $this
706
-	 */
707
-	protected function setId($id)
708
-	{
709
-		$this->id = $id;
710
-
711
-		return $this;
712
-	}
713
-
714
-	/**
715
-	 * Returns the active record table.
716
-	 *
717
-	 * @return string the active record table.
718
-	 */
719
-	abstract protected function getActiveRecordTable();
720
-
721
-	/**
722
-	 * Returns the active record columns.
723
-	 *
724
-	 * @return array the active record columns.
725
-	 */
726
-	abstract protected function getActiveRecordTableDefinition();
379
+        return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
380
+    }
381
+
382
+    public function createTableConstraints()
383
+    {
384
+        // Iterate over columns, check whether "relation" field exists, if so create constraint
385
+        foreach ($this->tableDefinition as $colName => $definition) {
386
+            if ($definition['relation'] ?? null instanceof AbstractActiveRecord) {
387
+                // Forge new relation
388
+                $target = $definition['relation'];
389
+                $constraintSql = $this->buildConstraint($target->getActiveRecordTable(), 'id', $this->getActiveRecordTable(), $colName);
390
+
391
+                $this->pdo->query($constraintSql);
392
+            }
393
+        }
394
+    }
395
+
396
+    private function getActiveRecordColumns()
397
+    {
398
+        $bindings = [];
399
+        foreach ($this->tableDefinition as $colName => $definition) {
400
+
401
+            // Ignore the id column (key) when inserting or updating
402
+            if ($colName == self::COLUMN_NAME_ID) {
403
+                continue;
404
+            }
405
+
406
+            $bindings[$colName] = &$definition['value'];
407
+        }
408
+        return $bindings;
409
+    }
410
+
411
+    /**
412
+     * {@inheritdoc}
413
+     */
414
+    public function create()
415
+    {
416
+        foreach ($this->registeredCreateHooks as $colName => $fn) {
417
+            // @TODO: Would it be better to pass the Query to the function?
418
+            $fn();
419
+        }
420
+
421
+        try {
422
+            $q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
423
+                ->insert($this->getActiveRecordColumns())
424
+                ->execute();
425
+
426
+            $this->setId(intval($this->getPdo()->lastInsertId()));
427
+        } catch (\PDOException $e) {
428
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
429
+        }
430
+
431
+        return $this;
432
+    }
433
+
434
+    /**
435
+     * {@inheritdoc}
436
+     */
437
+    public function read($id)
438
+    {
439
+        foreach ($this->registeredReadHooks as $colName => $fn) {
440
+            // @TODO: Would it be better to pass the Query to the function?
441
+            $fn();
442
+        }
443
+
444
+        try {
445
+            $row = (new Query($this->getPdo(), $this->getActiveRecordTable()))
446
+                ->select()
447
+                ->where('id', '=', $id)
448
+                ->execute()
449
+                ->fetch();
450
+
451
+            if ($row === false) {
452
+                throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()));
453
+            }
454
+
455
+            $this->fill($row)->setId($id);
456
+        } catch (\PDOException $e) {
457
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
458
+        }
459
+
460
+        return $this;
461
+    }
462
+
463
+    /**
464
+     * {@inheritdoc}
465
+     */
466
+    public function update()
467
+    {
468
+        foreach ($this->registeredUpdateHooks as $colName => $fn) {
469
+            // @TODO: Would it be better to pass the Query to the function?
470
+            $fn();
471
+        }
472
+
473
+        try {
474
+            (new Query($this->getPdo(), $this->getActiveRecordTable()))
475
+                ->update($this->getActiveRecordColumns())
476
+                ->where('id', '=', $this->getId())
477
+                ->execute();
478
+        } catch (\PDOException $e) {
479
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
480
+        }
481
+
482
+        return $this;
483
+    }
484
+
485
+    /**
486
+     * {@inheritdoc}
487
+     */
488
+    public function delete()
489
+    {
490
+        foreach ($this->registeredDeleteHooks as $colName => $fn) {
491
+            // @TODO: Would it be better to pass the Query to the function?
492
+            $fn();
493
+        }
494
+
495
+        try {
496
+            (new Query($this->getPdo(), $this->getActiveRecordTable()))
497
+                ->delete()
498
+                ->where('id', '=', $this->getId())
499
+                ->execute();
500
+
501
+            $this->setId(null);
502
+        } catch (\PDOException $e) {
503
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
504
+        }
505
+
506
+        return $this;
507
+    }
508
+
509
+    /**
510
+     * {@inheritdoc}
511
+     */
512
+    public function sync()
513
+    {
514
+        if (!$this->exists()) {
515
+            return $this->create();
516
+        }
517
+
518
+        return $this->update();
519
+    }
520
+
521
+    /**
522
+     * {@inheritdoc}
523
+     */
524
+    public function exists()
525
+    {
526
+        return $this->getId() !== null;
527
+    }
528
+
529
+    /**
530
+     * {@inheritdoc}
531
+     */
532
+    public function fill(array $attributes)
533
+    {
534
+        $columns = $this->getActiveRecordColumns();
535
+        $columns['id'] = &$this->id;
536
+
537
+        foreach ($attributes as $key => $value) {
538
+            if (array_key_exists($key, $columns)) {
539
+                $columns[$key] = $value;
540
+            }
541
+        }
542
+
543
+        return $this;
544
+    }
545
+
546
+    /**
547
+     * {@inheritdoc}
548
+     */
549
+    public function searchOne(array $where = [], array $orderBy = [])
550
+    {
551
+        try {
552
+            $row = $this->getSearchQueryResult($where, $orderBy, 1)->fetch();
553
+
554
+            if ($row === false) {
555
+                throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->getActiveRecordTable()));
556
+            }
557
+
558
+            return $this->fill($row)->setId($row['id']);
559
+        } catch (\PDOException $e) {
560
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
561
+        }
562
+    }
563
+
564
+    /**
565
+     * {@inheritdoc}
566
+     */
567
+    public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
568
+    {
569
+        try {
570
+            $queryResult = $this->getSearchQueryResult($where, $orderBy, $limit, $offset);
571
+            $result = [];
572
+
573
+            foreach ($queryResult as $row) {
574
+                $new = clone $this;
575
+
576
+                $result[] = $new->fill($row)->setId($row['id']);
577
+            }
578
+
579
+            return $result;
580
+        } catch (\PDOException $e) {
581
+            throw new ActiveRecordException($e->getMessage(), 0, $e);
582
+        }
583
+    }
584
+
585
+    /**
586
+     * Returns the search query result with the given where, order by, limit and offset clauses.
587
+     *
588
+     * @param array $where = []
589
+     * @param array $orderBy = []
590
+     * @param int $limit = -1
591
+     * @param int $offset = 0
592
+     * @return \miBadger\Query\QueryResult the search query result with the given where, order by, limit and offset clauses.
593
+     */
594
+    private function getSearchQueryResult(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
595
+    {
596
+        $query = (new Query($this->getPdo(), $this->getActiveRecordTable()))
597
+            ->select();
598
+
599
+        $this->getSearchQueryWhere($query, $where);
600
+        $this->getSearchQueryOrderBy($query, $orderBy);
601
+        $this->getSearchQueryLimit($query, $limit, $offset);
602
+
603
+        // Ignore all trait modifiers for which a where clause was specified
604
+        $registeredSearchHooks = $this->registeredSearchHooks;
605
+        foreach ($where as $index => $clause) {
606
+            [$colName, , ] = $clause;
607
+            unset($registeredSearchHooks[$colName]);
608
+        }
609
+
610
+        // Allow traits to modify the query
611
+        foreach ($registeredSearchHooks as $column => $searchFunction) {
612
+            $searchFunction($query);
613
+        }
614
+
615
+        return $query->execute();
616
+    }
617
+
618
+    /**
619
+     * Returns the given query after adding the given where conditions.
620
+     *
621
+     * @param \miBadger\Query\Query $query
622
+     * @param array $where
623
+     * @return \miBadger\Query\Query the given query after adding the given where conditions.
624
+     */
625
+    private function getSearchQueryWhere($query, $where)
626
+    {
627
+        foreach ($where as $key => $value) {
628
+            $query->where($value[0], $value[1], $value[2]);
629
+        }
630
+
631
+        return $query;
632
+    }
633
+
634
+    /**
635
+     * Returns the given query after adding the given order by conditions.
636
+     *
637
+     * @param \miBadger\Query\Query $query
638
+     * @param array $orderBy
639
+     * @return \miBadger\Query\Query the given query after adding the given order by conditions.
640
+     */
641
+    private function getSearchQueryOrderBy($query, $orderBy)
642
+    {
643
+        foreach ($orderBy as $key => $value) {
644
+            $query->orderBy($key, $value);
645
+        }
646
+
647
+        return $query;
648
+    }
649
+
650
+    /**
651
+     * Returns the given query after adding the given limit and offset conditions.
652
+     *
653
+     * @param \miBadger\Query\Query $query
654
+     * @param int $limit
655
+     * @param int $offset
656
+     * @return \miBadger\Query\Query the given query after adding the given limit and offset conditions.
657
+     */
658
+    private function getSearchQueryLimit($query, $limit, $offset)
659
+    {
660
+        if ($limit > -1) {
661
+            $query->limit($limit);
662
+            $query->offset($offset);
663
+        }
664
+
665
+        return $query;
666
+    }
667
+
668
+    /**
669
+     * Returns the PDO.
670
+     *
671
+     * @return \PDO the PDO.
672
+     */
673
+    public function getPdo()
674
+    {
675
+        return $this->pdo;
676
+    }
677
+
678
+    /**
679
+     * Set the PDO.
680
+     *
681
+     * @param \PDO $pdo
682
+     * @return $this
683
+     */
684
+    protected function setPdo($pdo)
685
+    {
686
+        $this->pdo = $pdo;
687
+
688
+        return $this;
689
+    }
690
+
691
+    /**
692
+     * Returns the ID.
693
+     *
694
+     * @return null|int The ID.
695
+     */
696
+    public function getId()
697
+    {
698
+        return $this->id;
699
+    }
700
+
701
+    /**
702
+     * Set the ID.
703
+     *
704
+     * @param int $id
705
+     * @return $this
706
+     */
707
+    protected function setId($id)
708
+    {
709
+        $this->id = $id;
710
+
711
+        return $this;
712
+    }
713
+
714
+    /**
715
+     * Returns the active record table.
716
+     *
717
+     * @return string the active record table.
718
+     */
719
+    abstract protected function getActiveRecordTable();
720
+
721
+    /**
722
+     * Returns the active record columns.
723
+     *
724
+     * @return array the active record columns.
725
+     */
726
+    abstract protected function getActiveRecordTableDefinition();
727 727
 }
Please login to merge, or discard this patch.
src/Traits/SoftDelete.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -9,67 +9,67 @@
 block discarded – undo
9 9
 
10 10
 trait SoftDelete
11 11
 {
12
-	protected $softDelete;
12
+    protected $softDelete;
13 13
 
14
-	/**
15
-	 * this method is required to be called in the constructor for each class that uses this trait. 
16
-	 * It adds the required fields to the table definition and registers hooks
17
-	 */
18
-	protected function initSoftDelete()
19
-	{
20
-		$this->softDelete = false;
14
+    /**
15
+     * this method is required to be called in the constructor for each class that uses this trait. 
16
+     * It adds the required fields to the table definition and registers hooks
17
+     */
18
+    protected function initSoftDelete()
19
+    {
20
+        $this->softDelete = false;
21 21
 
22
-		$this->extendTableDefinition(TRAIT_SOFT_DELETE_FIELD_KEY, [
23
-			'value' => &$this->softDelete,
24
-			'validate' => null,
25
-			'default' => 0,
26
-			'type' => 'INT',
27
-			'length' => 1,
28
-			'properties' => ColumnProperty::NOT_NULL
29
-		]);
22
+        $this->extendTableDefinition(TRAIT_SOFT_DELETE_FIELD_KEY, [
23
+            'value' => &$this->softDelete,
24
+            'validate' => null,
25
+            'default' => 0,
26
+            'type' => 'INT',
27
+            'length' => 1,
28
+            'properties' => ColumnProperty::NOT_NULL
29
+        ]);
30 30
 
31
-		$this->registerSearchHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteSearchHook');
32
-	}
31
+        $this->registerSearchHook(TRAIT_SOFT_DELETE_FIELD_KEY, 'softDeleteSearchHook');
32
+    }
33 33
 
34
-	/**
35
-	 * The hook that gets called whenever a query is made
36
-	 */
37
-	protected function softDeleteSearchHook(Query $query)
38
-	{
39
-		$query->where(TRAIT_SOFT_DELETE_FIELD_KEY, '=', 0);
40
-	}
34
+    /**
35
+     * The hook that gets called whenever a query is made
36
+     */
37
+    protected function softDeleteSearchHook(Query $query)
38
+    {
39
+        $query->where(TRAIT_SOFT_DELETE_FIELD_KEY, '=', 0);
40
+    }
41 41
 
42
-	/**
43
-	 * returns the name for the soft delete field in the database
44
-	 */
45
-	public function getSoftDeleteFieldName()
46
-	{
47
-		return TRAIT_SOFT_DELETE_FIELD_KEY;
48
-	}
42
+    /**
43
+     * returns the name for the soft delete field in the database
44
+     */
45
+    public function getSoftDeleteFieldName()
46
+    {
47
+        return TRAIT_SOFT_DELETE_FIELD_KEY;
48
+    }
49 49
 	
50
-	/**
51
-	 * Mark the current record as soft deleted
52
-	 */
53
-	public function softDelete()
54
-	{
55
-		$this->softDelete = true;
56
-		return $this;
57
-	}
50
+    /**
51
+     * Mark the current record as soft deleted
52
+     */
53
+    public function softDelete()
54
+    {
55
+        $this->softDelete = true;
56
+        return $this;
57
+    }
58 58
 
59
-	/**
60
-	 * Undo the current soft deletion status (mark it as non-soft deleted)
61
-	 */
62
-	public function softRestore()
63
-	{
64
-		$this->softDelete = false;
65
-		return $this;
66
-	}
59
+    /**
60
+     * Undo the current soft deletion status (mark it as non-soft deleted)
61
+     */
62
+    public function softRestore()
63
+    {
64
+        $this->softDelete = false;
65
+        return $this;
66
+    }
67 67
 
68
-	/**
69
-	 * returns the current soft deletion status
70
-	 */
71
-	public function getDeletionStatus() 
72
-	{
73
-		return $this->softDelete;
74
-	}
68
+    /**
69
+     * returns the current soft deletion status
70
+     */
71
+    public function getDeletionStatus() 
72
+    {
73
+        return $this->softDelete;
74
+    }
75 75
 }
76 76
\ No newline at end of file
Please login to merge, or discard this patch.
src/Traits/ManyToManyRelation.php 1 patch
Indentation   +54 added lines, -54 removed lines patch added patch discarded remove patch
@@ -8,69 +8,69 @@
 block discarded – undo
8 8
 
9 9
 Trait ManyToManyRelation
10 10
 {
11
-	// These variables are relevant for internal bookkeeping (constraint generation etc)
12
-	/** @var string The name of the left column of the relation. */
13
-	private $_leftColumnName;
11
+    // These variables are relevant for internal bookkeeping (constraint generation etc)
12
+    /** @var string The name of the left column of the relation. */
13
+    private $_leftColumnName;
14 14
 
15
-	/** @var string The name of the right column of the relation. */
16
-	private $_rightColumnName;
15
+    /** @var string The name of the right column of the relation. */
16
+    private $_rightColumnName;
17 17
 
18
-	/** @var string The name of the left table of the relation. */
19
-	private $_leftEntityTable;
18
+    /** @var string The name of the left table of the relation. */
19
+    private $_leftEntityTable;
20 20
 
21
-	/** @var string The name of the right table of the relation. */
22
-	private $_rightEntityTable;
21
+    /** @var string The name of the right table of the relation. */
22
+    private $_rightEntityTable;
23 23
 
24
-	/**
25
-	 * Initializes the the ManyToManyRelation trait on the included object
26
-	 * 
27
-	 * @param AbstractActiveRecord $leftEntity The left entity of the relation
28
-	 * @param &variable $leftVariable The variable where the id for the left entity will be stored
29
-	 * @param AbstractActiveRecord $rightEntity The left entity of the relation
30
-	 * @param &variable $leftVariable The variable where the id for the right entity will be stored
31
-	 */
32
-	protected function initManyToManyRelation(AbstractActiveRecord $leftEntity, &$leftVariable, AbstractActiveRecord $rightEntity, &$rightVariable)
33
-	{
34
-		$this->_leftEntityTable = $leftEntity->getActiveRecordTable();
35
-		$this->_rightEntityTable = $rightEntity->getActiveRecordTable();
24
+    /**
25
+     * Initializes the the ManyToManyRelation trait on the included object
26
+     * 
27
+     * @param AbstractActiveRecord $leftEntity The left entity of the relation
28
+     * @param &variable $leftVariable The variable where the id for the left entity will be stored
29
+     * @param AbstractActiveRecord $rightEntity The left entity of the relation
30
+     * @param &variable $leftVariable The variable where the id for the right entity will be stored
31
+     */
32
+    protected function initManyToManyRelation(AbstractActiveRecord $leftEntity, &$leftVariable, AbstractActiveRecord $rightEntity, &$rightVariable)
33
+    {
34
+        $this->_leftEntityTable = $leftEntity->getActiveRecordTable();
35
+        $this->_rightEntityTable = $rightEntity->getActiveRecordTable();
36 36
 
37
-		if (get_class($leftEntity) === get_class($rightEntity)) {
38
-			$this->_leftColumnName = sprintf("id_%s_left", $leftEntity->getActiveRecordTable());
39
-			$this->_rightColumnName = sprintf("id_%s_right", $rightEntity->getActiveRecordTable());
40
-		} else {
41
-			$this->_leftColumnName = sprintf("id_%s", $leftEntity->getActiveRecordTable());
42
-			$this->_rightColumnName = sprintf("id_%s", $rightEntity->getActiveRecordTable());
43
-		}
37
+        if (get_class($leftEntity) === get_class($rightEntity)) {
38
+            $this->_leftColumnName = sprintf("id_%s_left", $leftEntity->getActiveRecordTable());
39
+            $this->_rightColumnName = sprintf("id_%s_right", $rightEntity->getActiveRecordTable());
40
+        } else {
41
+            $this->_leftColumnName = sprintf("id_%s", $leftEntity->getActiveRecordTable());
42
+            $this->_rightColumnName = sprintf("id_%s", $rightEntity->getActiveRecordTable());
43
+        }
44 44
 
45
-		$this->extendTableDefinition($this->_leftColumnName, [
46
-			'value' => &$leftVariable,
47
-			'validate' => null,
48
-			'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
49
-			'properties' => ColumnProperty::NOT_NULL
50
-		]);
45
+        $this->extendTableDefinition($this->_leftColumnName, [
46
+            'value' => &$leftVariable,
47
+            'validate' => null,
48
+            'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
49
+            'properties' => ColumnProperty::NOT_NULL
50
+        ]);
51 51
 
52
-		$this->extendTableDefinition($this->_rightColumnName, [
53
-			'value' => &$rightVariable,
54
-			'validate' => null,
55
-			'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
56
-			'properties' => ColumnProperty::NOT_NULL
57
-		]);
58
-	}
52
+        $this->extendTableDefinition($this->_rightColumnName, [
53
+            'value' => &$rightVariable,
54
+            'validate' => null,
55
+            'type' => AbstractActiveRecord::COLUMN_TYPE_ID,
56
+            'properties' => ColumnProperty::NOT_NULL
57
+        ]);
58
+    }
59 59
 
60
-	/**
61
-	 * Build the constraints for the many-to-many relation table
62
-	 */
63
-	public function createTableConstraints()
64
-	{
65
-		$childTable = $this->getActiveRecordTable();
60
+    /**
61
+     * Build the constraints for the many-to-many relation table
62
+     */
63
+    public function createTableConstraints()
64
+    {
65
+        $childTable = $this->getActiveRecordTable();
66 66
 
67
-		$leftParentTable = $this->_leftEntityTable;
68
-		$rightParentTable = $this->_rightEntityTable;
67
+        $leftParentTable = $this->_leftEntityTable;
68
+        $rightParentTable = $this->_rightEntityTable;
69 69
 
70
-		$leftConstraint = $this->buildConstraint($leftParentTable, 'id', $childTable, $this->_leftColumnName);
71
-		$rightConstraint = $this->buildConstraint($rightParentTable, 'id', $childTable, $this->_rightColumnName);
70
+        $leftConstraint = $this->buildConstraint($leftParentTable, 'id', $childTable, $this->_leftColumnName);
71
+        $rightConstraint = $this->buildConstraint($rightParentTable, 'id', $childTable, $this->_rightColumnName);
72 72
 
73
-		$this->pdo->query($leftConstraint);
74
-		$this->pdo->query($rightConstraint);
75
-	}
73
+        $this->pdo->query($leftConstraint);
74
+        $this->pdo->query($rightConstraint);
75
+    }
76 76
 }
Please login to merge, or discard this patch.