Test Failed
Push — v2 ( 737a06 )
by Berend
03:55
created

AbstractActiveRecord::read()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 9.52
c 0
b 0
f 0
cc 4
nc 8
nop 1
1
<?php
2
3
/**
4
 * This file is part of the miBadger package.
5
 *
6
 * @author Michael Webbers <[email protected]>
7
 * @license http://opensource.org/licenses/Apache-2.0 Apache v2 License
8
 */
9
10
namespace miBadger\ActiveRecord;
11
12
use miBadger\Query\Query;
13
14
/**
15
 * The abstract active record class.
16
 *
17
 * @since 1.0.0
18
 */
19
abstract class AbstractActiveRecord implements ActiveRecordInterface
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
		// Enforcing table can only be extended with new columns
240
		if (array_key_exists($columnName, $this->tableDefinition)) {
241
			$message = "Table is being extended with a column that already exists, ";
242
			$message .= "\"$columnName\" conflicts with your table definition";
243
			throw new ActiveRecordException($message, 0);
244
		}
245
246
		$this->tableDefinition[$columnName] = $definition;
247
	}
248
249
	private function getDatabaseTypeString($colName, $type, $length)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
250
	{
251
		if ($type === null) 
252
		{
253
			throw new ActiveRecordException(sprintf("Column %s has invalid type \"NULL\"", $colName));
254
		}
255
256
		switch (strtoupper($type)) {
257
			case 'DATETIME':
258
			case 'DATE':
259
			case 'TIME':
260
			case 'TEXT':
261
			case 'INT UNSIGNED':
262
				return $type;
263
264
			case 'VARCHAR':
265
				return sprintf('%s(%d)', $type, $length);
266
267
			case 'INT':
268
			case 'TINYINT':
269
			case 'BIGINT':
270
			default: 	
0 ignored issues
show
Coding Style introduced by
There is some trailing whitespace on this line which should be avoided as per coding-style.
Loading history...
271
			// @TODO(Default): throw exception, or implicitly assume that type is correct? (For when using SQL databases with different types)
272
				if ($length === null) {
273
					return $type;
274
				} else {
275
					return sprintf('%s(%d)', $type, $length);	
276
				}
277
		}
278
	}
279
280
	private function buildCreateTableColumnEntry($colName, $type, $length, $properties, $default)
281
	{
282
283
		$stmnt = sprintf('`%s` %s ', $colName, $this->getDatabaseTypeString($colName, $type, $length));
284
		if ($properties & ColumnProperty::NOT_NULL) {
285
			$stmnt .= 'NOT NULL ';
286
		} else {
287
			$stmnt .= 'NULL ';
288
		}
289
290
		if ($default !== NULL) {
291
			$stmnt .= ' DEFAULT ' . $default . ' ';
292
		}
293
294
		if ($properties & ColumnProperty::AUTO_INCREMENT) {
295
			$stmnt .= 'AUTO_INCREMENT ';
296
		}
297
298
		if ($properties & ColumnProperty::UNIQUE) {
299
			$stmnt .= 'UNIQUE ';
300
		}
301
302
		if ($properties & ColumnProperty::PRIMARY_KEY) {
303
			$stmnt .= 'PRIMARY KEY ';
304
		}
305
306
		return $stmnt;
307
	}
308
309
310
	private function sortColumnStatements($colStatements)
311
	{
312
		// Find ID statement and put it first
313
		$sortedStatements = [];
314
315
		$sortedStatements[] = $colStatements[self::COLUMN_NAME_ID];
316
		unset($colStatements[self::COLUMN_NAME_ID]);
317
318
		// Sort remaining columns in alphabetical order
319
		$columns = array_keys($colStatements);
320
		sort($columns);
321
		foreach ($columns as $colName) {
322
			$sortedStatements[] = $colStatements[$colName];
323
		}
324
325
		return $sortedStatements;
326
	}
327
328
329
	public function buildCreateTableSQL()
330
	{
331
		$columnStatements = [];
332
		foreach ($this->tableDefinition as $colName => $definition) {
333
			// Destructure column definition
334
			$type    = $definition['type'] ?? null;
335
			$default = $definition['default'] ?? null;
336
			$length  = $definition['length'] ?? null;
337
			$properties = $definition['properties'] ?? null;
338
339
			if (isset($definition['relation']) && $type !== null) {
340
				$msg = "Column \"$colName\": ";
341
				$msg .= "Relationship columns have an automatically inferred type, so type should be omitted";
342
				throw new ActiveRecordException($msg);
343
			} else if (isset($definition['relation'])) {
344
				$type = self::COLUMN_TYPE_ID;
345
			}
346
347
			$columnStatements[$colName] = $this->buildCreateTableColumnEntry($colName, $type, $length, $properties, $default);
348
		}
349
350
		// Sort table (first column is id, the remaining are alphabetically sorted)
351
		$columnStatements = $this->sortColumnStatements($columnStatements);
352
353
		$sql = 'CREATE TABLE ' . $this->getActiveRecordTable() . ' ';
354
		$sql .= "(\n";
355
		$sql .= join($columnStatements, ",\n");
356
		$sql .= "\n);";
357
358
		return $sql;
359
	}
360
361
	public function createTable()
362
	{
363
		$this->pdo->query($this->buildCreateTableSQL());
364
	}
365
366
	protected function buildConstraint($parentTable, $parentColumn, $childTable, $childColumn)
367
	{
368
		$template = <<<SQL
369
ALTER TABLE `%s`
370
ADD CONSTRAINT
371
FOREIGN KEY (`%s`)
372
REFERENCES `%s`(`%s`)
373
ON DELETE CASCADE;
374
SQL;
375
		return sprintf($template, $childTable, $childColumn, $parentTable, $parentColumn);
376
	}
377
378
	public function createTableConstraints()
379
	{
380
		// Iterate over columns, check whether "relation" field exists, if so create constraint
381
		foreach ($this->tableDefinition as $colName => $definition) {
382
			if ($definition['relation'] ?? null instanceof AbstractActiveRecord) {
383
				// Forge new relation
384
				$target = $definition['relation'];
385
				$constraintSql = $this->buildConstraint($target->getActiveRecordTable(), 'id', $this->getActiveRecordTable(), $colName);
386
387
				$this->pdo->query($constraintSql);
388
			}
389
		}
390
	}
391
392
	private function getActiveRecordColumns()
393
	{
394
		$bindings = [];
395
		foreach ($this->tableDefinition as $colName => $definition) {
396
397
			// Ignore the id column (key) when inserting or updating
398
			if ($colName == self::COLUMN_NAME_ID) {
399
				continue;
400
			}
401
402
			$bindings[$colName] = &$definition['value'];
403
		}
404
		return $bindings;
405
	}
406
407
	/**
408
	 * {@inheritdoc}
409
	 */
410
	public function create()
411
	{
412
		foreach ($this->registeredCreateHooks as $colName => $fn) {
413
			// @TODO: Would it be better to pass the Query to the function?
414
			$fn();
415
		}
416
417
		try {
418
			$q = (new Query($this->getPdo(), $this->getActiveRecordTable()))
0 ignored issues
show
Unused Code introduced by
$q is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
419
				->insert($this->getActiveRecordColumns())
420
				->execute();
421
422
			$this->setId(intval($this->getPdo()->lastInsertId()));
423
		} catch (\PDOException $e) {
424
			throw new ActiveRecordException($e->getMessage(), 0, $e);
425
		}
426
427
		return $this;
428
	}
429
430
	/**
431
	 * {@inheritdoc}
432
	 */
433
	public function read($id)
434
	{
435
		foreach ($this->registeredReadHooks as $colName => $fn) {
436
			// @TODO: Would it be better to pass the Query to the function?
437
			$fn();
438
		}
439
440
		try {
441
			$row = (new Query($this->getPdo(), $this->getActiveRecordTable()))
442
				->select()
443
				->where('id', '=', $id)
444
				->execute()
445
				->fetch();
446
447
			if ($row === false) {
448
				throw new ActiveRecordException(sprintf('Can not read the non-existent active record entry %d from the `%s` table.', $id, $this->getActiveRecordTable()));
449
			}
450
451
			$this->fill($row)->setId($id);
452
		} catch (\PDOException $e) {
453
			throw new ActiveRecordException($e->getMessage(), 0, $e);
454
		}
455
456
		return $this;
457
	}
458
459
	/**
460
	 * {@inheritdoc}
461
	 */
462
	public function update()
463
	{
464
		foreach ($this->registeredUpdateHooks as $colName => $fn) {
465
			// @TODO: Would it be better to pass the Query to the function?
466
			$fn();
467
		}
468
469
		try {
470
			(new Query($this->getPdo(), $this->getActiveRecordTable()))
471
				->update($this->getActiveRecordColumns())
472
				->where('id', '=', $this->getId())
473
				->execute();
474
		} catch (\PDOException $e) {
475
			throw new ActiveRecordException($e->getMessage(), 0, $e);
476
		}
477
478
		return $this;
479
	}
480
481
	/**
482
	 * {@inheritdoc}
483
	 */
484
	public function delete()
485
	{
486
		foreach ($this->registeredDeleteHooks as $colName => $fn) {
487
			// @TODO: Would it be better to pass the Query to the function?
488
			$fn();
489
		}
490
491
		try {
492
			(new Query($this->getPdo(), $this->getActiveRecordTable()))
493
				->delete()
494
				->where('id', '=', $this->getId())
495
				->execute();
496
497
			$this->setId(null);
498
		} catch (\PDOException $e) {
499
			throw new ActiveRecordException($e->getMessage(), 0, $e);
500
		}
501
502
		return $this;
503
	}
504
505
	/**
506
	 * {@inheritdoc}
507
	 */
508
	public function sync()
509
	{
510
		if (!$this->exists()) {
511
			return $this->create();
512
		}
513
514
		return $this->update();
515
	}
516
517
	/**
518
	 * {@inheritdoc}
519
	 */
520
	public function exists()
521
	{
522
		return $this->getId() !== null;
523
	}
524
525
	/**
526
	 * {@inheritdoc}
527
	 */
528
	public function fill(array $attributes)
529
	{
530
		$columns = $this->getActiveRecordColumns();
531
		$columns['id'] = &$this->id;
532
533
		foreach ($attributes as $key => $value) {
534
			if (array_key_exists($key, $columns)) {
535
				$columns[$key] = $value;
536
			}
537
		}
538
539
		return $this;
540
	}
541
542
	/**
543
	 * {@inheritdoc}
544
	 */
545
	public function searchOne(array $where = [], array $orderBy = [])
546
	{
547
		try {
548
			$row = $this->getSearchQueryResult($where, $orderBy, 1)->fetch();
549
550
			if ($row === false) {
551
				throw new ActiveRecordException(sprintf('Can not search one non-existent entry from the `%s` table.', $this->getActiveRecordTable()));
552
			}
553
554
			return $this->fill($row)->setId($row['id']);
555
		} catch (\PDOException $e) {
556
			throw new ActiveRecordException($e->getMessage(), 0, $e);
557
		}
558
	}
559
560
	/**
561
	 * {@inheritdoc}
562
	 */
563
	public function search(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
564
	{
565
		try {
566
			$queryResult = $this->getSearchQueryResult($where, $orderBy, $limit, $offset);
567
			$result = [];
568
569
			foreach ($queryResult as $row) {
570
				$new = clone $this;
571
572
				$result[] = $new->fill($row)->setId($row['id']);
573
			}
574
575
			return $result;
576
		} catch (\PDOException $e) {
577
			throw new ActiveRecordException($e->getMessage(), 0, $e);
578
		}
579
	}
580
581
	/**
582
	 * Returns the search query result with the given where, order by, limit and offset clauses.
583
	 *
584
	 * @param array $where = []
585
	 * @param array $orderBy = []
586
	 * @param int $limit = -1
587
	 * @param int $offset = 0
588
	 * @return \miBadger\Query\QueryResult the search query result with the given where, order by, limit and offset clauses.
589
	 */
590
	private function getSearchQueryResult(array $where = [], array $orderBy = [], $limit = -1, $offset = 0)
591
	{
592
		$query = (new Query($this->getPdo(), $this->getActiveRecordTable()))
593
			->select();
594
595
		$this->getSearchQueryWhere($query, $where);
596
		$this->getSearchQueryOrderBy($query, $orderBy);
597
		$this->getSearchQueryLimit($query, $limit, $offset);
598
599
		// Ignore all trait modifiers for which a where clause was specified
600
		$registeredSearchHooks = $this->registeredSearchHooks;
601
		foreach ($where as $index => $clause) {
602
			[$colName, , ] = $clause;
0 ignored issues
show
Bug introduced by
The variable $colName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
603
			unset($registeredSearchHooks[$colName]);
604
		}
605
606
		// Allow traits to modify the query
607
		foreach ($registeredSearchHooks as $column => $searchFunction) {
608
			$searchFunction($query);
609
		}
610
611
		return $query->execute();
612
	}
613
614
	/**
615
	 * Returns the given query after adding the given where conditions.
616
	 *
617
	 * @param \miBadger\Query\Query $query
618
	 * @param array $where
619
	 * @return \miBadger\Query\Query the given query after adding the given where conditions.
620
	 */
621
	private function getSearchQueryWhere($query, $where)
622
	{
623
		foreach ($where as $key => $value) {
624
			$query->where($value[0], $value[1], $value[2]);
625
		}
626
627
		return $query;
628
	}
629
630
	/**
631
	 * Returns the given query after adding the given order by conditions.
632
	 *
633
	 * @param \miBadger\Query\Query $query
634
	 * @param array $orderBy
635
	 * @return \miBadger\Query\Query the given query after adding the given order by conditions.
636
	 */
637
	private function getSearchQueryOrderBy($query, $orderBy)
638
	{
639
		foreach ($orderBy as $key => $value) {
640
			$query->orderBy($key, $value);
641
		}
642
643
		return $query;
644
	}
645
646
	/**
647
	 * Returns the given query after adding the given limit and offset conditions.
648
	 *
649
	 * @param \miBadger\Query\Query $query
650
	 * @param int $limit
651
	 * @param int $offset
652
	 * @return \miBadger\Query\Query the given query after adding the given limit and offset conditions.
653
	 */
654
	private function getSearchQueryLimit($query, $limit, $offset)
655
	{
656
		if ($limit > -1) {
657
			$query->limit($limit);
658
			$query->offset($offset);
659
		}
660
661
		return $query;
662
	}
663
664
	/**
665
	 * Returns the PDO.
666
	 *
667
	 * @return \PDO the PDO.
668
	 */
669
	public function getPdo()
670
	{
671
		return $this->pdo;
672
	}
673
674
	/**
675
	 * Set the PDO.
676
	 *
677
	 * @param \PDO $pdo
678
	 * @return $this
679
	 */
680
	protected function setPdo($pdo)
681
	{
682
		$this->pdo = $pdo;
683
684
		return $this;
685
	}
686
687
	/**
688
	 * Returns the ID.
689
	 *
690
	 * @return null|int The ID.
691
	 */
692
	public function getId()
693
	{
694
		return $this->id;
695
	}
696
697
	/**
698
	 * Set the ID.
699
	 *
700
	 * @param int $id
701
	 * @return $this
702
	 */
703
	protected function setId($id)
704
	{
705
		$this->id = $id;
706
707
		return $this;
708
	}
709
710
	/**
711
	 * Returns the active record table.
712
	 *
713
	 * @return string the active record table.
714
	 */
715
	abstract protected function getActiveRecordTable();
716
717
	/**
718
	 * Returns the active record columns.
719
	 *
720
	 * @return array the active record columns.
721
	 */
722
	abstract protected function getActiveRecordTableDefinition();
723
}
724