aboutsummaryrefslogtreecommitdiffstats
path: root/core/namespace/ORM
diff options
context:
space:
mode:
authorThomas Lange <code@nerdmind.de>2021-07-01 20:11:34 +0200
committerThomas Lange <code@nerdmind.de>2021-07-01 20:11:34 +0200
commite6cef37e0c782fe770db20888d99c17d10e2c479 (patch)
treeaed0790a0fa8eb7cceef7bb32aef4f69b724d619 /core/namespace/ORM
parentf0ea19767d502ec7b5afff7c66c2681292175a3b (diff)
downloadblog-e6cef37e0c782fe770db20888d99c17d10e2c479.tar.gz
blog-e6cef37e0c782fe770db20888d99c17d10e2c479.tar.xz
blog-e6cef37e0c782fe770db20888d99c17d10e2c479.zip
Add category system to categorize posts (readme)
This commit implements a new category system to categorize posts. Each category can have an unlimited number of nested children categories. A single post don't necessarily need to be in a category, but it can. Each category can have a full content body like posts or pages, so you have enough space to describe the content of your categories. Please note that you need to have at least the following MySQL/MariaDB versions to use the category system, because it uses "WITH RECURSIVE" database queries, the so-called "Common-Table-Expressions (CTE)". MariaDB: 10.2.2 MySQL: 8.0 See: https://mariadb.com/kb/en/with/ See: https://dev.mysql.com/doc/refman/8.0/en/with.html
Diffstat (limited to 'core/namespace/ORM')
-rw-r--r--core/namespace/ORM/Entities/Category.php14
-rw-r--r--core/namespace/ORM/Entities/Post.php13
-rw-r--r--core/namespace/ORM/Repositories/Category.php138
-rw-r--r--core/namespace/ORM/Repositories/Post.php12
4 files changed, 171 insertions, 6 deletions
diff --git a/core/namespace/ORM/Entities/Category.php b/core/namespace/ORM/Entities/Category.php
new file mode 100644
index 0000000..17560a7
--- /dev/null
+++ b/core/namespace/ORM/Entities/Category.php
@@ -0,0 +1,14 @@
+<?php
+namespace ORM\Entities;
+use ORM\Entity;
+
+class Category extends Entity {
+ protected $id = FALSE;
+ protected $parent = FALSE;
+ protected $slug = FALSE;
+ protected $name = FALSE;
+ protected $body = FALSE;
+ protected $argv = FALSE;
+ protected $time_insert = FALSE;
+ protected $time_update = FALSE;
+}
diff --git a/core/namespace/ORM/Entities/Post.php b/core/namespace/ORM/Entities/Post.php
index e6bff36..080f01f 100644
--- a/core/namespace/ORM/Entities/Post.php
+++ b/core/namespace/ORM/Entities/Post.php
@@ -3,12 +3,13 @@ namespace ORM\Entities;
use ORM\Entity;
class Post extends Entity {
- protected $id = FALSE;
- protected $user = FALSE;
- protected $slug = FALSE;
- protected $name = FALSE;
- protected $body = FALSE;
- protected $argv = FALSE;
+ protected $id = FALSE;
+ protected $user = FALSE;
+ protected $category = FALSE;
+ protected $slug = FALSE;
+ protected $name = FALSE;
+ protected $body = FALSE;
+ protected $argv = FALSE;
protected $time_insert = FALSE;
protected $time_update = FALSE;
}
diff --git a/core/namespace/ORM/Repositories/Category.php b/core/namespace/ORM/Repositories/Category.php
new file mode 100644
index 0000000..bd7d060
--- /dev/null
+++ b/core/namespace/ORM/Repositories/Category.php
@@ -0,0 +1,138 @@
+<?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
+ if($entities = $Statement->fetchAll($this->Database::FETCH_CLASS, static::getClassName())) {
+ $this->storeMultipleInstances($entities);
+ return $entities;
+ }
+
+ return [];
+ }
+
+ #===============================================================================
+ # 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();
+
+ if($entities = $Statement->fetchAll($this->Database::FETCH_CLASS, static::getClassName())) {
+ $this->storeMultipleInstances($entities);
+ return $entities;
+ }
+
+ return [];
+ }
+
+ #===============================================================================
+ # 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);
+ }
+
+ $query = 'SELECT parent FROM %s WHERE id = ?';
+ $query = sprintf($query, static::getTableName());
+
+ $Statement = $this->Database->prepare($query);
+ $_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);
+ }
+}
diff --git a/core/namespace/ORM/Repositories/Post.php b/core/namespace/ORM/Repositories/Post.php
index 8eac12f..d6328e6 100644
--- a/core/namespace/ORM/Repositories/Post.php
+++ b/core/namespace/ORM/Repositories/Post.php
@@ -2,6 +2,7 @@
namespace ORM\Repositories;
use ORM\Repository;
use ORM\Entities\User;
+use ORM\Entities\Category;
class Post extends Repository {
public static function getTableName(): string { return 'post'; }
@@ -16,4 +17,15 @@ class Post extends Repository {
return $Statement->fetchColumn();
}
+
+ # TODO: This only gets the count of the direct category, not its children
+ public function getCountByCategory(Category $Category): int {
+ $query = 'SELECT COUNT(id) FROM %s WHERE category = ?';
+ $query = sprintf($query, static::getTableName());
+
+ $Statement = $this->Database->prepare($query);
+ $Statement->execute([$Category->getID()]);
+
+ return $Statement->fetchColumn();
+ }
}