aboutsummaryrefslogtreecommitdiffstats
path: root/core/namespace/ORM/Repositories/Category.php
blob: c00fdf3f626db3f71619372737df4651b87e4f49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?php
namespace ORM\Repositories;
use ORM\Repository;
use ORM\EntityInterface;
use ORM\Entities\Category as CategoryEntity;

class Category extends Repository {
	public static function getTableName(): string { return 'category'; }
	public static function getClassName(): string { return 'ORM\Entities\Category'; }

	#===============================================================================
	# Find category with parents based on primary key
	#===============================================================================
	public function findWithParents($id): array {
		return $this->findWithParentsBy('id', $id);
	}

	#===============================================================================
	# Find category with parents based on specific field comparison
	#===============================================================================
	public function findWithParentsBy(string $field, $value): array {
		$query = 'WITH RECURSIVE tree AS (
			SELECT *, 0 AS _depth FROM %s WHERE %s %s UNION
			SELECT c.*, _depth+1 FROM %s c, tree WHERE tree.parent = c.id
		) SELECT * FROM tree ORDER BY _depth DESC';

		$table = static::getTableName();
		$check = is_null($value) ? 'IS NULL': '= ?';
		$query = sprintf($query, $table, $field, $check, $table);

		$Statement = $this->Database->prepare($query);
		$Statement->execute([$value]);

		# TODO: Virtual column _depth shall not be fetched into the entity class
		return $this->fetchEntities($Statement);
	}

	#===============================================================================
	# Get paginated category tree list
	#===============================================================================
	public function getPaginatedTree(int $limit, int $offset = 0): array {
		$query = 'WITH RECURSIVE tree AS (
			SELECT *, name AS _depth FROM %s WHERE parent IS NULL UNION
			SELECT c.*, CONCAT(_depth, "/", c.name) AS _depth FROM %s c INNER JOIN tree ON tree.id = c.parent
		) SELECT * FROM tree ORDER BY _depth %s';

		$_limit = "LIMIT $limit";

		if($offset) {
			$_limit = "LIMIT $offset,$limit";
		}

		$table = static::getTableName();
		$query = sprintf($query, $table, $table, $_limit);

		$Statement = $this->Database->prepare($query);
		$Statement->execute();

		return $this->fetchEntities($Statement);
	}

	#===============================================================================
	# Get children count of $Category
	#===============================================================================
	public function getChildrenCount(CategoryEntity $Category): int {
		$query = 'WITH RECURSIVE tree AS (
			SELECT * FROM %s WHERE id = ? UNION
			SELECT c.* FROM %s c, tree WHERE tree.id = c.parent
		) SELECT COUNT(id) FROM tree WHERE id != ?';

		$query = sprintf($query,
			static::getTableName(),
			static::getTableName()
		);

		$Statement = $this->Database->prepare($query);
		$Statement->execute([$Category->getID(), $Category->getID()]);

		return $Statement->fetchColumn();
	}

	#===============================================================================
	# Update category (and check for parent/child circular loops)
	#===============================================================================
	public function update(EntityInterface $Entity): bool {
		# Entity parent might have changed *in memory*, so we re-fetch the original
		# parent of the entity from the database and save it in a variable.
		# TODO: Repository/Entity class should have a mechanism to detect changes!
		$query = 'SELECT parent FROM %s WHERE id = ?';
		$query = sprintf($query, static::getTableName());

		$Statement = $this->Database->prepare($query);
		$Statement->execute([$Entity->getID()]);

		$parent = $Statement->fetchColumn();

		# If parent is unchanged, circular loop check is not needed.
		if($parent === $Entity->get('parent')) {
			return parent::update($Entity);
		}

		$_parent = $Entity->get('parent');

		# Fetch the parent of the *new* parent category and let the while loop run through
		# the tree until either a parent of "NULL" was found or if the new parent category
		# is a *child* of the *current* category which would cause a circular loop.
		while($Statement->execute([$_parent]) && $_parent = $Statement->fetchColumn()) {
			if($_parent == $Entity->get('id')) {
				# Set parent of the *new* parent category to the *original* parent category
				# of the *current* category (one level up) to prevent a circular loop.
				$query = 'UPDATE %s SET parent = ? WHERE id = ?';
				$query = sprintf($query, static::getTableName());

				$UpdateStatement = $this->Database->prepare($query);
				$UpdateStatement->execute([$parent, $Entity->get('parent')]);
				break;
			} else if($_parent === NULL) {
				break;
			}
		}

		return parent::update($Entity);
	}
}