From 7cd2e21f9f45bad352fe0febaab7c6f98bd55917 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 8 Nov 2020 17:38:18 +0100 Subject: [PATCH 01/38] Created basic recipe entity's getters and setters Signed-off-by: Christian Wolf --- lib/Entity/RecipeEntity.php | 267 ++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 lib/Entity/RecipeEntity.php diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php new file mode 100644 index 000000000..511e0cc52 --- /dev/null +++ b/lib/Entity/RecipeEntity.php @@ -0,0 +1,267 @@ +json); + } + + + //// ************************** Getters and Setters of basic JSON data + + /** + * Get the name of the recipe + * @return string The name of the recipe + */ + public function getName() : string + { + return $this->json['name']; + } + + /** + * Set the name of the recipe + * @param string The new name of the recipe + */ + public function setName(string $name) : void + { + $this->json['name'] = $name; + $this->changed = true; + } + + /** + * Get the description of the recipe + * @return string The description + */ + public function getDescription() : string + { + return $this->json['description']; + } + + /** + * Set the description of the recipe + * @param string The new description + */ + public function setDescription(string $description) : void + { + $this->json['description'] = $description; + $this->changed = true; + } + + /** + * Get the URL of the recipe + * @return string The URL of the recipe + */ + public function getUrl() : string + { + return $this->json['url']; + } + + /** + * Set the URL of the recipe + * @param string The new URL + */ + public function setUrl(string $url) : void + { + $this->json['url'] = $url; + $this->changed = true; + } + + /** + * Get the amount of servings the recipe yields + * @return int The amount of servings + */ + public function getYield() : int + { + return $this->json['recipeYield']; + } + + /** + * Set the amount of portions teh recipe yields + * @param int The amount of portions + */ + public function setYield(int $yield) : void + { + $this->json['recipeYield'] = (int) $yield; + $this->changed = true; + } + + /** + * Get the preparation time for the recipe + * @return string The preparation time + */ + public function getPrepTime() : string + { + return $this->json['prepTime']; + } + + /** + * Set the preparation time of a recipe + * @param string The new preparation time + */ + public function setPrepTime(string $prepTime) : void + { + $this->json['prepTime'] = $prepTime; + $this->changed = true; + } + /** + * Get the cooking time for the recipe + * @return string The cooking time + */ + public function getCookTime() : string + { + return $this->json['cookTime']; + } + + /** + * Set the cooking time of a recipe + * @param string The new cooking time + */ + public function setPrepTime(string $cookTime) : void + { + $this->json['prepTime'] = $cookTime; + $this->changed = true; + } + /** + * Get the total time for the recipe + * @return string The total time + */ + public function getTotalTime() : string + { + return $this->json['totalTime']; + } + + /** + * Set the total time of a recipe + * @param string The new total time + */ + public function setTotalTime(string $totalTime) : void + { + $this->json['prepTime'] = $totalTime; + $this->changed = true; + } + + /** + * Get the ingredients of the recipe + * @return array The ingredients + */ + public function getIngredients() : array + { + return $this->json['recipeIngredient']; + } + + /** + * Set the ingredients of a recipe + * @param array The list of ingredients of the recipe + */ + public function setIngredients(array $ingredients) : void + { + $this->json['recipeIngredient'] = $ingredients; + $this->changed = true; + } + + /** + * Get the tools of the recipe + * @return array The tools + */ + public function getTools() : array + { + return $this->json['tool']; + } + + /** + * Set the tools of a recipe + * @param array The list of tools of the recipe + */ + public function setIngredients(array $tools) : void + { + $this->json['tool'] = $tools; + $this->changed = true; + } + + /** + * Get the instructions of the recipe + * @return array The instructions + */ + public function getInstructions() : array + { + return $this->json['recipeInstructions']; + } + + /** + * Set the instructions of a recipe + * @param array The list of instructions of the recipe + */ + public function setIngredients(array $instructions) : void + { + $this->json['recipeInstructions'] = $instructions; + $this->changed = true; + } + + //// ****************************** Getters and setters for the category and keywords + + /** + * Get the category of a recipe + * @todo Use a category class instead of strings + * @return string The category of the recipe + */ + public function getCategory() : string + { + return $this->json['recipeCategory']; + } + + /** + * Set the category of a recipe + * @todo Use category class instead of string + * @param string The new category + */ + public function setCategory(string $category) : void + { + $this->json['recipeCategory'] = $category; + $this->changed = false; + } + + /** + * Get the keywords of the recipe + * @todo Use keyword call instead of strings + * @return array The keywords of the recipe + */ + public function getKeywords() : array + { + $keywords = $this->json['keywords']; + + if(strlen(trim($keywords)) == 0) + return array(); + + return explode(',', $keywords); + } + + /** + * Set the keywords of the recipe + * @todo Use Keyword call instead of strings + * @param array The keywords as an array of strings + */ + public function setKeywords(array $keywords) : void + { + $this->json['keywords'] = implode(',', $keywords); + $this->changed = true; + } +} From b04bad536f7fdb90f60326bd9e1d292aec3d68a4 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 8 Nov 2020 20:29:42 +0100 Subject: [PATCH 02/38] Corrected RecipeEntity copy&paste errors Signed-off-by: Christian Wolf --- lib/Entity/RecipeEntity.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php index 511e0cc52..24a2c0cef 100644 --- a/lib/Entity/RecipeEntity.php +++ b/lib/Entity/RecipeEntity.php @@ -135,9 +135,9 @@ public function getCookTime() : string * Set the cooking time of a recipe * @param string The new cooking time */ - public function setPrepTime(string $cookTime) : void + public function setCookTime(string $cookTime) : void { - $this->json['prepTime'] = $cookTime; + $this->json['cookTime'] = $cookTime; $this->changed = true; } /** @@ -191,7 +191,7 @@ public function getTools() : array * Set the tools of a recipe * @param array The list of tools of the recipe */ - public function setIngredients(array $tools) : void + public function setTools(array $tools) : void { $this->json['tool'] = $tools; $this->changed = true; @@ -210,7 +210,7 @@ public function getInstructions() : array * Set the instructions of a recipe * @param array The list of instructions of the recipe */ - public function setIngredients(array $instructions) : void + public function setInstructions(array $instructions) : void { $this->json['recipeInstructions'] = $instructions; $this->changed = true; From 90a9241ca1b91aed763ec18470938a06f85df20d Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 19 Nov 2020 13:59:34 +0100 Subject: [PATCH 03/38] Corrected styling of code Signed-off-by: Christian Wolf --- lib/Entity/RecipeEntity.php | 502 +++++++++++++++++------------------- 1 file changed, 239 insertions(+), 263 deletions(-) diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php index 24a2c0cef..d114ec43c 100644 --- a/lib/Entity/RecipeEntity.php +++ b/lib/Entity/RecipeEntity.php @@ -1,267 +1,243 @@ json); - } - - - //// ************************** Getters and Setters of basic JSON data - - /** - * Get the name of the recipe - * @return string The name of the recipe - */ - public function getName() : string - { - return $this->json['name']; - } - - /** - * Set the name of the recipe - * @param string The new name of the recipe - */ - public function setName(string $name) : void - { - $this->json['name'] = $name; - $this->changed = true; - } - - /** - * Get the description of the recipe - * @return string The description - */ - public function getDescription() : string - { - return $this->json['description']; - } - - /** - * Set the description of the recipe - * @param string The new description - */ - public function setDescription(string $description) : void - { - $this->json['description'] = $description; - $this->changed = true; - } - - /** - * Get the URL of the recipe - * @return string The URL of the recipe - */ - public function getUrl() : string - { - return $this->json['url']; - } - - /** - * Set the URL of the recipe - * @param string The new URL - */ - public function setUrl(string $url) : void - { - $this->json['url'] = $url; - $this->changed = true; - } - - /** - * Get the amount of servings the recipe yields - * @return int The amount of servings - */ - public function getYield() : int - { - return $this->json['recipeYield']; - } - - /** - * Set the amount of portions teh recipe yields - * @param int The amount of portions - */ - public function setYield(int $yield) : void - { - $this->json['recipeYield'] = (int) $yield; - $this->changed = true; - } - - /** - * Get the preparation time for the recipe - * @return string The preparation time - */ - public function getPrepTime() : string - { - return $this->json['prepTime']; - } - - /** - * Set the preparation time of a recipe - * @param string The new preparation time - */ - public function setPrepTime(string $prepTime) : void - { - $this->json['prepTime'] = $prepTime; - $this->changed = true; - } - /** - * Get the cooking time for the recipe - * @return string The cooking time - */ - public function getCookTime() : string - { - return $this->json['cookTime']; - } - - /** - * Set the cooking time of a recipe - * @param string The new cooking time - */ - public function setCookTime(string $cookTime) : void - { - $this->json['cookTime'] = $cookTime; - $this->changed = true; - } - /** - * Get the total time for the recipe - * @return string The total time - */ - public function getTotalTime() : string - { - return $this->json['totalTime']; - } - - /** - * Set the total time of a recipe - * @param string The new total time - */ - public function setTotalTime(string $totalTime) : void - { - $this->json['prepTime'] = $totalTime; - $this->changed = true; - } - - /** - * Get the ingredients of the recipe - * @return array The ingredients - */ - public function getIngredients() : array - { - return $this->json['recipeIngredient']; - } - - /** - * Set the ingredients of a recipe - * @param array The list of ingredients of the recipe - */ - public function setIngredients(array $ingredients) : void - { - $this->json['recipeIngredient'] = $ingredients; - $this->changed = true; - } - - /** - * Get the tools of the recipe - * @return array The tools - */ - public function getTools() : array - { - return $this->json['tool']; - } - - /** - * Set the tools of a recipe - * @param array The list of tools of the recipe - */ - public function setTools(array $tools) : void - { - $this->json['tool'] = $tools; - $this->changed = true; - } - - /** - * Get the instructions of the recipe - * @return array The instructions - */ - public function getInstructions() : array - { - return $this->json['recipeInstructions']; - } - - /** - * Set the instructions of a recipe - * @param array The list of instructions of the recipe - */ - public function setInstructions(array $instructions) : void - { - $this->json['recipeInstructions'] = $instructions; - $this->changed = true; - } - - //// ****************************** Getters and setters for the category and keywords - - /** - * Get the category of a recipe - * @todo Use a category class instead of strings - * @return string The category of the recipe - */ - public function getCategory() : string - { - return $this->json['recipeCategory']; - } - - /** - * Set the category of a recipe - * @todo Use category class instead of string - * @param string The new category - */ - public function setCategory(string $category) : void - { - $this->json['recipeCategory'] = $category; - $this->changed = false; - } - - /** - * Get the keywords of the recipe - * @todo Use keyword call instead of strings - * @return array The keywords of the recipe - */ - public function getKeywords() : array - { - $keywords = $this->json['keywords']; - - if(strlen(trim($keywords)) == 0) - return array(); - - return explode(',', $keywords); - } - - /** - * Set the keywords of the recipe - * @todo Use Keyword call instead of strings - * @param array The keywords as an array of strings - */ - public function setKeywords(array $keywords) : void - { - $this->json['keywords'] = implode(',', $keywords); - $this->changed = true; - } +class RecipeEntity { + + /** + * The JSON representation of the recipe + * @var string + */ + private $json; + + /** + * Indicates if the entity has any unsaved changes + * @var bool + */ + private $changed; + + /** + * Get the JSON representing the recipe + * @return string A string representing the internal structure + */ + public function getJSON() : string { + return json_encode($this->json); + } + + + //// ************************** Getters and Setters of basic JSON data + + /** + * Get the name of the recipe + * @return string The name of the recipe + */ + public function getName() : string { + return $this->json['name']; + } + + /** + * Set the name of the recipe + * @param string The new name of the recipe + */ + public function setName(string $name) : void { + $this->json['name'] = $name; + $this->changed = true; + } + + /** + * Get the description of the recipe + * @return string The description + */ + public function getDescription() : string { + return $this->json['description']; + } + + /** + * Set the description of the recipe + * @param string The new description + */ + public function setDescription(string $description) : void { + $this->json['description'] = $description; + $this->changed = true; + } + + /** + * Get the URL of the recipe + * @return string The URL of the recipe + */ + public function getUrl() : string { + return $this->json['url']; + } + + /** + * Set the URL of the recipe + * @param string The new URL + */ + public function setUrl(string $url) : void { + $this->json['url'] = $url; + $this->changed = true; + } + + /** + * Get the amount of servings the recipe yields + * @return int The amount of servings + */ + public function getYield() : int { + return $this->json['recipeYield']; + } + + /** + * Set the amount of portions teh recipe yields + * @param int The amount of portions + */ + public function setYield(int $yield) : void { + $this->json['recipeYield'] = (int) $yield; + $this->changed = true; + } + + /** + * Get the preparation time for the recipe + * @return string The preparation time + */ + public function getPrepTime() : string { + return $this->json['prepTime']; + } + + /** + * Set the preparation time of a recipe + * @param string The new preparation time + */ + public function setPrepTime(string $prepTime) : void { + $this->json['prepTime'] = $prepTime; + $this->changed = true; + } + /** + * Get the cooking time for the recipe + * @return string The cooking time + */ + public function getCookTime() : string { + return $this->json['cookTime']; + } + + /** + * Set the cooking time of a recipe + * @param string The new cooking time + */ + public function setCookTime(string $cookTime) : void { + $this->json['cookTime'] = $cookTime; + $this->changed = true; + } + /** + * Get the total time for the recipe + * @return string The total time + */ + public function getTotalTime() : string { + return $this->json['totalTime']; + } + + /** + * Set the total time of a recipe + * @param string The new total time + */ + public function setTotalTime(string $totalTime) : void { + $this->json['prepTime'] = $totalTime; + $this->changed = true; + } + + /** + * Get the ingredients of the recipe + * @return array The ingredients + */ + public function getIngredients() : array { + return $this->json['recipeIngredient']; + } + + /** + * Set the ingredients of a recipe + * @param array The list of ingredients of the recipe + */ + public function setIngredients(array $ingredients) : void { + $this->json['recipeIngredient'] = $ingredients; + $this->changed = true; + } + + /** + * Get the tools of the recipe + * @return array The tools + */ + public function getTools() : array { + return $this->json['tool']; + } + + /** + * Set the tools of a recipe + * @param array The list of tools of the recipe + */ + public function setTools(array $tools) : void { + $this->json['tool'] = $tools; + $this->changed = true; + } + + /** + * Get the instructions of the recipe + * @return array The instructions + */ + public function getInstructions() : array { + return $this->json['recipeInstructions']; + } + + /** + * Set the instructions of a recipe + * @param array The list of instructions of the recipe + */ + public function setInstructions(array $instructions) : void { + $this->json['recipeInstructions'] = $instructions; + $this->changed = true; + } + + //// ****************************** Getters and setters for the category and keywords + + /** + * Get the category of a recipe + * @todo Use a category class instead of strings + * @return string The category of the recipe + */ + public function getCategory() : string { + return $this->json['recipeCategory']; + } + + /** + * Set the category of a recipe + * @todo Use category class instead of string + * @param string The new category + */ + public function setCategory(string $category) : void { + $this->json['recipeCategory'] = $category; + $this->changed = false; + } + + /** + * Get the keywords of the recipe + * @todo Use keyword call instead of strings + * @return array The keywords of the recipe + */ + public function getKeywords() : array { + $keywords = $this->json['keywords']; + + if (strlen(trim($keywords)) == 0) { + return []; + } + + return explode(',', $keywords); + } + + /** + * Set the keywords of the recipe + * @todo Use Keyword call instead of strings + * @param array The keywords as an array of strings + */ + public function setKeywords(array $keywords) : void { + $this->json['keywords'] = implode(',', $keywords); + $this->changed = true; + } } From 54f777733f3d3578d32b4f5309bd799785be997e Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 19 Nov 2020 16:24:58 +0100 Subject: [PATCH 04/38] Created basic entity structure for categories Signed-off-by: Christian Wolf --- lib/Db/AbstractDbWrapper.php | 83 +++++++++++++++++++++++++++++++++++ lib/Db/CategoryDbWrapper.php | 42 ++++++++++++++++++ lib/Entity/CategoryEntity.php | 45 +++++++++++++++++++ lib/Entity/Entity.php | 10 +++++ 4 files changed, 180 insertions(+) create mode 100644 lib/Db/AbstractDbWrapper.php create mode 100644 lib/Db/CategoryDbWrapper.php create mode 100644 lib/Entity/CategoryEntity.php create mode 100644 lib/Entity/Entity.php diff --git a/lib/Db/AbstractDbWrapper.php b/lib/Db/AbstractDbWrapper.php new file mode 100644 index 000000000..4bd5cb163 --- /dev/null +++ b/lib/Db/AbstractDbWrapper.php @@ -0,0 +1,83 @@ +initialized = false; + $this->db = $db; + } + + /** + * Fetch all elements from the database. + * + * The concrete class must implemnt a way to fetch an array or elements represented by the database. + * @return array The (possible empty) array of entities in the database + */ + abstract protected function fetchDatabase(): array; + + /** + * Fetch the entries in the database + * + * If the cache has already been fetched, the values in the cache are returned. + * + * @return array The entities in the database. + */ + protected function getEntries(): array { + if (! $this->initialized) { + $this->reloadCache(); + } + + return $this->cache; + } + + /** + * Reload the cache from the database. + */ + private function reloadCache(): void { + $this->cache = $this->fetchDatabase(); + $this->initialized = true; + } + + /** + * Invalidate the local cache. + * + * This will cause any access to the entities to refetch the whole cache. + */ + protected function invalidateCache(): void { + $this->initialized = false; + } + + /** + * Set the cache to the given array. + * + * This function will repace the current cache by the given array. + * No further checks are carried out. + * Please be very careful to provide the most up to date data as present in the database. + * Otherwise you will see very strange effects. + * + * @param array $entities The new entities of the cache + */ + protected function setEntites(array $entities): void { + $this->cache = $entities; + $this->initialized = true; + } +} diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php new file mode 100644 index 000000000..bee6acd01 --- /dev/null +++ b/lib/Db/CategoryDbWrapper.php @@ -0,0 +1,42 @@ +db->getQueryBuilder(); + + $qb ->select('name') + ->from('cookbook_categories') + ->where('user_id = :uid') + ->groupBy('name') + ->orderBy('name'); + + $qb->setParameter('uid', $this->userId); + + $res = $qb->execute(); + $ret = []; + while ($row = $res->fetch()) { + $entity = new CategoryEntity($this); + $entity->setName($row['name']); + + $ret[] = $entity; + } + + return $ret; + } +} diff --git a/lib/Entity/CategoryEntity.php b/lib/Entity/CategoryEntity.php new file mode 100644 index 000000000..fa0e24393 --- /dev/null +++ b/lib/Entity/CategoryEntity.php @@ -0,0 +1,45 @@ +wrapper = $wrapper; + } + + /** + * Get the name of the category + * @return string The name of the category + */ + public function getName(): string { + return $this->name; + } + + /** + * Set the name of the category. + * @param string $name The new name of the category + */ + public function setName(string $name): void { + $this->name = $name; + } + + public function persist(): void { + } +} diff --git a/lib/Entity/Entity.php b/lib/Entity/Entity.php new file mode 100644 index 000000000..836260a92 --- /dev/null +++ b/lib/Entity/Entity.php @@ -0,0 +1,10 @@ + Date: Thu, 19 Nov 2020 16:47:38 +0100 Subject: [PATCH 05/38] Added wrapper and entity for keywords Signed-off-by: Christian Wolf --- lib/Db/KeywordDbWrapper.php | 64 ++++++++++++++++++++++++++++++++++++ lib/Entity/KeywordEntity.php | 47 ++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 lib/Db/KeywordDbWrapper.php create mode 100644 lib/Entity/KeywordEntity.php diff --git a/lib/Db/KeywordDbWrapper.php b/lib/Db/KeywordDbWrapper.php new file mode 100644 index 000000000..cce6cf0d1 --- /dev/null +++ b/lib/Db/KeywordDbWrapper.php @@ -0,0 +1,64 @@ +userId = $UserId; + $this->db = $db; + } + + protected function fetchDatabase(): array { + $qb = $this->db->getQueryBuilder(); + + $qb ->select('name') + ->from('cookbook_keywords') + ->where('user_id = :uid') + ->groupBy('name') + ->orderBy('name'); + + $qb->setParameter('uid', $this->userId); + + $res = $qb->execute(); + $arr = []; + while ($row = $res->fetch()) { + $entity = new KeywordEntity($this); + $entity->setName($row['name']); + + $arr[] = $entity; + } + + $res->closeCursor(); + + return $arr; + } + + /** + * Store a single entity back to the database + * @param KeywordEntity $keyword The entity to store + */ + public function store(KeywordEntity $keyword): void { + } + + /** + * Create a new entity and reegister it with this wrapper + * @return KeywordEntity The new entity + */ + public function createEntity(): KeywordEntity { + return new KeywordEntity($this); + } +} diff --git a/lib/Entity/KeywordEntity.php b/lib/Entity/KeywordEntity.php new file mode 100644 index 000000000..7a1e7c9f7 --- /dev/null +++ b/lib/Entity/KeywordEntity.php @@ -0,0 +1,47 @@ +wrapper = $wrapper; + } + + /** + * Get the name of the keyword + * @return string The name of the keyword + */ + public function getName(): string { + return $this->name; + } + + /** + * Set the name of the keyword + * @param string $name The name of the keyword + */ + public function setName($name) { + $this->name = $name; + } + + public function persist(): void { + $this->wrapper->store($this); + } +} From 36b4d87e1e35f3b46246cd3fd53329c0bb1af0a3 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 19 Nov 2020 16:47:50 +0100 Subject: [PATCH 06/38] Updated category interconnection Signed-off-by: Christian Wolf --- lib/Db/CategoryDbWrapper.php | 17 +++++++++++++++++ lib/Entity/CategoryEntity.php | 2 ++ 2 files changed, 19 insertions(+) diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php index bee6acd01..caa8affcc 100644 --- a/lib/Db/CategoryDbWrapper.php +++ b/lib/Db/CategoryDbWrapper.php @@ -37,6 +37,23 @@ protected function fetchDatabase(): array { $ret[] = $entity; } + $res->closeCursor(); + return $ret; } + + /** + * Store a single entity back to the database + * @param CategoryEntity $category The entity to store + */ + public function store(CategoryEntity $category): void { + } + + /** + * Create a new entity and reegister it with this wrapper + * @return CategoryEntity The new entity + */ + public function createEntity(): CategoryEntity { + return new CategoryEntity($this); + } } diff --git a/lib/Entity/CategoryEntity.php b/lib/Entity/CategoryEntity.php index fa0e24393..080e40ee4 100644 --- a/lib/Entity/CategoryEntity.php +++ b/lib/Entity/CategoryEntity.php @@ -18,6 +18,7 @@ class CategoryEntity implements Entity { /** * Create a new category entity + * Do not use this constructor directly but create new entities from the corresponding wrapper. * @param CategoryDbWrapper $wrapper The wrapper to use for DB access */ public function __construct(CategoryDbWrapper $wrapper) { @@ -41,5 +42,6 @@ public function setName(string $name): void { } public function persist(): void { + $this->wrapper->store($this); } } From e7736db932a19a1243dd7cb0ffbd8becd01773c9 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 19 Nov 2020 17:12:55 +0100 Subject: [PATCH 07/38] Moving JSON entity out of the way and extending structure to recipe table Signed-off-by: Christian Wolf --- lib/Db/RecipeDbWrapper.php | 37 +++++ lib/Entity/RecipeEntity.php | 245 ++++---------------------------- lib/Entity/RecipeJSONEntity.php | 243 +++++++++++++++++++++++++++++++ 3 files changed, 311 insertions(+), 214 deletions(-) create mode 100644 lib/Db/RecipeDbWrapper.php create mode 100644 lib/Entity/RecipeJSONEntity.php diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php new file mode 100644 index 000000000..2f2519f64 --- /dev/null +++ b/lib/Db/RecipeDbWrapper.php @@ -0,0 +1,37 @@ +db = $db; + $this->userId = $UserId; + } + + protected function fetchDatabase(): array { + // FIXME + } + + public function store(RecipeEntity $recipe): void { + // FIXME + } + + public function createEntity(): RecipeEntity { + // FIXME + } +} diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php index d114ec43c..28d36b5da 100644 --- a/lib/Entity/RecipeEntity.php +++ b/lib/Entity/RecipeEntity.php @@ -2,242 +2,59 @@ namespace OCA\Cookbook\Entity; -class RecipeEntity { - - /** - * The JSON representation of the recipe - * @var string - */ - private $json; - - /** - * Indicates if the entity has any unsaved changes - * @var bool - */ - private $changed; - - /** - * Get the JSON representing the recipe - * @return string A string representing the internal structure - */ - public function getJSON() : string { - return json_encode($this->json); - } - - - //// ************************** Getters and Setters of basic JSON data - - /** - * Get the name of the recipe - * @return string The name of the recipe - */ - public function getName() : string { - return $this->json['name']; - } - - /** - * Set the name of the recipe - * @param string The new name of the recipe - */ - public function setName(string $name) : void { - $this->json['name'] = $name; - $this->changed = true; - } - - /** - * Get the description of the recipe - * @return string The description - */ - public function getDescription() : string { - return $this->json['description']; - } - - /** - * Set the description of the recipe - * @param string The new description - */ - public function setDescription(string $description) : void { - $this->json['description'] = $description; - $this->changed = true; - } - - /** - * Get the URL of the recipe - * @return string The URL of the recipe - */ - public function getUrl() : string { - return $this->json['url']; - } - - /** - * Set the URL of the recipe - * @param string The new URL - */ - public function setUrl(string $url) : void { - $this->json['url'] = $url; - $this->changed = true; - } - - /** - * Get the amount of servings the recipe yields - * @return int The amount of servings - */ - public function getYield() : int { - return $this->json['recipeYield']; - } - - /** - * Set the amount of portions teh recipe yields - * @param int The amount of portions - */ - public function setYield(int $yield) : void { - $this->json['recipeYield'] = (int) $yield; - $this->changed = true; - } - - /** - * Get the preparation time for the recipe - * @return string The preparation time - */ - public function getPrepTime() : string { - return $this->json['prepTime']; - } - - /** - * Set the preparation time of a recipe - * @param string The new preparation time - */ - public function setPrepTime(string $prepTime) : void { - $this->json['prepTime'] = $prepTime; - $this->changed = true; - } - /** - * Get the cooking time for the recipe - * @return string The cooking time - */ - public function getCookTime() : string { - return $this->json['cookTime']; - } - - /** - * Set the cooking time of a recipe - * @param string The new cooking time - */ - public function setCookTime(string $cookTime) : void { - $this->json['cookTime'] = $cookTime; - $this->changed = true; - } - /** - * Get the total time for the recipe - * @return string The total time - */ - public function getTotalTime() : string { - return $this->json['totalTime']; - } - - /** - * Set the total time of a recipe - * @param string The new total time - */ - public function setTotalTime(string $totalTime) : void { - $this->json['prepTime'] = $totalTime; - $this->changed = true; - } - - /** - * Get the ingredients of the recipe - * @return array The ingredients - */ - public function getIngredients() : array { - return $this->json['recipeIngredient']; - } +use OCA\Cookbook\Db\RecipeDbWrapper; + +class RecipeEntity implements Entity { /** - * Set the ingredients of a recipe - * @param array The list of ingredients of the recipe + * @var RecipeDbWrapper */ - public function setIngredients(array $ingredients) : void { - $this->json['recipeIngredient'] = $ingredients; - $this->changed = true; - } + private $wrapper; /** - * Get the tools of the recipe - * @return array The tools + * @var int */ - public function getTools() : array { - return $this->json['tool']; - } + private $id; /** - * Set the tools of a recipe - * @param array The list of tools of the recipe + * @var string */ - public function setTools(array $tools) : void { - $this->json['tool'] = $tools; - $this->changed = true; - } + private $name; - /** - * Get the instructions of the recipe - * @return array The instructions - */ - public function getInstructions() : array { - return $this->json['recipeInstructions']; + public function persist(): void { + $this->wrapper->store($this); } /** - * Set the instructions of a recipe - * @param array The list of instructions of the recipe + * Obtain the id of the recipe in the database. + * @return number The id of the recipe in the database */ - public function setInstructions(array $instructions) : void { - $this->json['recipeInstructions'] = $instructions; - $this->changed = true; + public function getId(): int { + return $this->id; } - - //// ****************************** Getters and setters for the category and keywords - - /** - * Get the category of a recipe - * @todo Use a category class instead of strings - * @return string The category of the recipe - */ - public function getCategory() : string { - return $this->json['recipeCategory']; - } - + /** - * Set the category of a recipe - * @todo Use category class instead of string - * @param string The new category + * Get the name of the recipe + * @return string The name of the recipe */ - public function setCategory(string $category) : void { - $this->json['recipeCategory'] = $category; - $this->changed = false; + public function getName(): string { + return $this->name; } - + /** - * Get the keywords of the recipe - * @todo Use keyword call instead of strings - * @return array The keywords of the recipe + * Set the id of the recipe in the database. + * Caution: This should be done only by the corresponding wrapper after inserting into the DB + * @param number $id The new id */ - public function getKeywords() : array { - $keywords = $this->json['keywords']; - - if (strlen(trim($keywords)) == 0) { - return []; - } - - return explode(',', $keywords); + public function setId($id): void { + $this->id = $id; } - + /** - * Set the keywords of the recipe - * @todo Use Keyword call instead of strings - * @param array The keywords as an array of strings + * Set the name of the recipe + * @param string $name The new name of the recipe */ - public function setKeywords(array $keywords) : void { - $this->json['keywords'] = implode(',', $keywords); - $this->changed = true; + public function setName($name): void { + $this->name = $name; } } diff --git a/lib/Entity/RecipeJSONEntity.php b/lib/Entity/RecipeJSONEntity.php new file mode 100644 index 000000000..4bdf800c7 --- /dev/null +++ b/lib/Entity/RecipeJSONEntity.php @@ -0,0 +1,243 @@ +json); + } + + + //// ************************** Getters and Setters of basic JSON data + + /** + * Get the name of the recipe + * @return string The name of the recipe + */ + public function getName() : string { + return $this->json['name']; + } + + /** + * Set the name of the recipe + * @param string The new name of the recipe + */ + public function setName(string $name) : void { + $this->json['name'] = $name; + $this->changed = true; + } + + /** + * Get the description of the recipe + * @return string The description + */ + public function getDescription() : string { + return $this->json['description']; + } + + /** + * Set the description of the recipe + * @param string The new description + */ + public function setDescription(string $description) : void { + $this->json['description'] = $description; + $this->changed = true; + } + + /** + * Get the URL of the recipe + * @return string The URL of the recipe + */ + public function getUrl() : string { + return $this->json['url']; + } + + /** + * Set the URL of the recipe + * @param string The new URL + */ + public function setUrl(string $url) : void { + $this->json['url'] = $url; + $this->changed = true; + } + + /** + * Get the amount of servings the recipe yields + * @return int The amount of servings + */ + public function getYield() : int { + return $this->json['recipeYield']; + } + + /** + * Set the amount of portions teh recipe yields + * @param int The amount of portions + */ + public function setYield(int $yield) : void { + $this->json['recipeYield'] = (int) $yield; + $this->changed = true; + } + + /** + * Get the preparation time for the recipe + * @return string The preparation time + */ + public function getPrepTime() : string { + return $this->json['prepTime']; + } + + /** + * Set the preparation time of a recipe + * @param string The new preparation time + */ + public function setPrepTime(string $prepTime) : void { + $this->json['prepTime'] = $prepTime; + $this->changed = true; + } + /** + * Get the cooking time for the recipe + * @return string The cooking time + */ + public function getCookTime() : string { + return $this->json['cookTime']; + } + + /** + * Set the cooking time of a recipe + * @param string The new cooking time + */ + public function setCookTime(string $cookTime) : void { + $this->json['cookTime'] = $cookTime; + $this->changed = true; + } + /** + * Get the total time for the recipe + * @return string The total time + */ + public function getTotalTime() : string { + return $this->json['totalTime']; + } + + /** + * Set the total time of a recipe + * @param string The new total time + */ + public function setTotalTime(string $totalTime) : void { + $this->json['prepTime'] = $totalTime; + $this->changed = true; + } + + /** + * Get the ingredients of the recipe + * @return array The ingredients + */ + public function getIngredients() : array { + return $this->json['recipeIngredient']; + } + + /** + * Set the ingredients of a recipe + * @param array The list of ingredients of the recipe + */ + public function setIngredients(array $ingredients) : void { + $this->json['recipeIngredient'] = $ingredients; + $this->changed = true; + } + + /** + * Get the tools of the recipe + * @return array The tools + */ + public function getTools() : array { + return $this->json['tool']; + } + + /** + * Set the tools of a recipe + * @param array The list of tools of the recipe + */ + public function setTools(array $tools) : void { + $this->json['tool'] = $tools; + $this->changed = true; + } + + /** + * Get the instructions of the recipe + * @return array The instructions + */ + public function getInstructions() : array { + return $this->json['recipeInstructions']; + } + + /** + * Set the instructions of a recipe + * @param array The list of instructions of the recipe + */ + public function setInstructions(array $instructions) : void { + $this->json['recipeInstructions'] = $instructions; + $this->changed = true; + } + + //// ****************************** Getters and setters for the category and keywords + + /** + * Get the category of a recipe + * @todo Use a category class instead of strings + * @return string The category of the recipe + */ + public function getCategory() : string { + return $this->json['recipeCategory']; + } + + /** + * Set the category of a recipe + * @todo Use category class instead of string + * @param string The new category + */ + public function setCategory(string $category) : void { + $this->json['recipeCategory'] = $category; + $this->changed = false; + } + + /** + * Get the keywords of the recipe + * @todo Use keyword call instead of strings + * @return array The keywords of the recipe + */ + public function getKeywords() : array { + $keywords = $this->json['keywords']; + + if (strlen(trim($keywords)) == 0) { + return []; + } + + return explode(',', $keywords); + } + + /** + * Set the keywords of the recipe + * @todo Use Keyword call instead of strings + * @param array The keywords as an array of strings + */ + public function setKeywords(array $keywords) : void { + $this->json['keywords'] = implode(',', $keywords); + $this->changed = true; + } +} From efab4ba0f61d7fd3f8211c1e516ed0acafddd1e8 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 19 Nov 2020 17:13:11 +0100 Subject: [PATCH 08/38] Removed missing parts in other wrappers Signed-off-by: Christian Wolf --- lib/Db/CategoryDbWrapper.php | 7 +++++++ lib/Db/KeywordDbWrapper.php | 1 + 2 files changed, 8 insertions(+) diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php index caa8affcc..99eba854f 100644 --- a/lib/Db/CategoryDbWrapper.php +++ b/lib/Db/CategoryDbWrapper.php @@ -13,8 +13,15 @@ class CategoryDbWrapper extends AbstractDbWrapper { */ private $userId; + /** + * @var IDBConnection + */ + private $db; + public function __construct(string $UserId, IDBConnection $db) { parent::__construct($db); + $this->userId = $UserId; + $this->db = $db; } protected function fetchDatabase(): array { diff --git a/lib/Db/KeywordDbWrapper.php b/lib/Db/KeywordDbWrapper.php index cce6cf0d1..8223aaecf 100644 --- a/lib/Db/KeywordDbWrapper.php +++ b/lib/Db/KeywordDbWrapper.php @@ -18,6 +18,7 @@ class KeywordDbWrapper extends AbstractDbWrapper { private $db; public function __construct(string $UserId, IDBConnection $db) { + parent::__construct($db); $this->userId = $UserId; $this->db = $db; } From b31e30145e7adf677723cd01fa533d93a37cf453 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Thu, 19 Nov 2020 17:49:21 +0100 Subject: [PATCH 09/38] Added mapping structures, not yet implemented Signed-off-by: Christian Wolf --- lib/Db/CategoryMappingDbWrapper.php | 66 ++++++++++++++++++++++++++++ lib/Db/KeywordMappingDbWrapper.php | 66 ++++++++++++++++++++++++++++ lib/Entity/CategoryMappingEntity.php | 32 ++++++++++++++ lib/Entity/KeywordMappingEntity.php | 31 +++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 lib/Db/CategoryMappingDbWrapper.php create mode 100644 lib/Db/KeywordMappingDbWrapper.php create mode 100644 lib/Entity/CategoryMappingEntity.php create mode 100644 lib/Entity/KeywordMappingEntity.php diff --git a/lib/Db/CategoryMappingDbWrapper.php b/lib/Db/CategoryMappingDbWrapper.php new file mode 100644 index 000000000..e194963dc --- /dev/null +++ b/lib/Db/CategoryMappingDbWrapper.php @@ -0,0 +1,66 @@ +userId = $UserId; + $this->db = $db; + } + + protected function fetchDatabase(): array { +// $qb = $this->db->getQueryBuilder(); + +// $qb ->select('name') +// ->from('cookbook_categories') +// ->where('user_id = :uid') +// ->groupBy('name') +// ->orderBy('name'); + +// $qb->setParameter('uid', $this->userId); + +// $res = $qb->execute(); +// $ret = []; +// while ($row = $res->fetch()) { +// $entity = new CategoryEntity($this); +// $entity->setName($row['name']); + +// $ret[] = $entity; +// } + +// $res->closeCursor(); + +// return $ret; + } + + /** + * Store a single entity back to the database + * @param CategoryMappingEntity $category The entity to store + */ + public function store(CategoryMappingEntity $mapping): void { + } + + /** + * Create a new entity and reegister it with this wrapper + * @return CategoryMappingEntity The new entity + */ + public function createEntity(): CategoryMappingEntity { + return new CategoryMappingEntity($this); + } +} diff --git a/lib/Db/KeywordMappingDbWrapper.php b/lib/Db/KeywordMappingDbWrapper.php new file mode 100644 index 000000000..91129b0e2 --- /dev/null +++ b/lib/Db/KeywordMappingDbWrapper.php @@ -0,0 +1,66 @@ +userId = $UserId; + $this->db = $db; + } + + protected function fetchDatabase(): array { +// $qb = $this->db->getQueryBuilder(); + +// $qb ->select('name') +// ->from('cookbook_categories') +// ->where('user_id = :uid') +// ->groupBy('name') +// ->orderBy('name'); + +// $qb->setParameter('uid', $this->userId); + +// $res = $qb->execute(); +// $ret = []; +// while ($row = $res->fetch()) { +// $entity = new CategoryEntity($this); +// $entity->setName($row['name']); + +// $ret[] = $entity; +// } + +// $res->closeCursor(); + +// return $ret; + } + + /** + * Store a single entity back to the database + * @param KeywordMappingEntity $category The entity to store + */ + public function store(KeywordMappingEntity $mapping): void { + } + + /** + * Create a new entity and reegister it with this wrapper + * @return KeywordMappingEntity The new entity + */ + public function createEntity(): KeywordMappingEntity { + return new KeywordMappingEntity($this); + } +} diff --git a/lib/Entity/CategoryMappingEntity.php b/lib/Entity/CategoryMappingEntity.php new file mode 100644 index 000000000..0b10ccac9 --- /dev/null +++ b/lib/Entity/CategoryMappingEntity.php @@ -0,0 +1,32 @@ +wrapper = $wrapper; + } + + public function persist(): void { + $this->wrapper->store($this); + } +} diff --git a/lib/Entity/KeywordMappingEntity.php b/lib/Entity/KeywordMappingEntity.php new file mode 100644 index 000000000..5c918512a --- /dev/null +++ b/lib/Entity/KeywordMappingEntity.php @@ -0,0 +1,31 @@ +wrapper = $wrapper; + } + + public function persist(): void { + $this->wrapper->store($this); + } +} From 8654dba02204f2730e76065db74cc386c73a7fb8 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 11:39:27 +0100 Subject: [PATCH 10/38] Corrected namespacing typos Signed-off-by: Christian Wolf --- lib/Db/CategoryDbWrapper.php | 3 +-- lib/Db/CategoryMappingDbWrapper.php | 3 +-- lib/Db/KeywordMappingDbWrapper.php | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php index 99eba854f..6809e72ac 100644 --- a/lib/Db/CategoryDbWrapper.php +++ b/lib/Db/CategoryDbWrapper.php @@ -1,8 +1,7 @@ Date: Fri, 20 Nov 2020 11:39:57 +0100 Subject: [PATCH 11/38] Created basic service locator for wrappers Signed-off-by: Christian Wolf --- lib/Db/AbstractDbWrapper.php | 13 +++++ lib/Db/DbWrapperServiceLocator.php | 80 ++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 lib/Db/DbWrapperServiceLocator.php diff --git a/lib/Db/AbstractDbWrapper.php b/lib/Db/AbstractDbWrapper.php index 4bd5cb163..a73709b45 100644 --- a/lib/Db/AbstractDbWrapper.php +++ b/lib/Db/AbstractDbWrapper.php @@ -21,6 +21,11 @@ abstract class AbstractDbWrapper { */ private $db; + /** + * @var DbWrapperServiceProvider + */ + protected $wrapperLocator; + public function __construct(IDBConnection $db) { $this->initialized = false; $this->db = $db; @@ -80,4 +85,12 @@ protected function setEntites(array $entities): void { $this->cache = $entities; $this->initialized = true; } + + /** + * Set the central service locator for registering of all wrappers + * @param DbWrapperServiceProvider $locator The locator for the registered wrappers + */ + public function setWrapperServiceLocator(DbWrapperServiceProvider $locator) { + $this->wrapperLocator = $locator; + } } diff --git a/lib/Db/DbWrapperServiceLocator.php b/lib/Db/DbWrapperServiceLocator.php new file mode 100644 index 000000000..d48d55427 --- /dev/null +++ b/lib/Db/DbWrapperServiceLocator.php @@ -0,0 +1,80 @@ +recipeDbWrapper = $recipeWrapper; + $this->categoryDbWrapper = $categoryWrapper; + $this->categoryMappingDbWrapper = $categoryMappingWrapper; + $this->keywordDbWrapper = $keywordWrapper; + $this->keywordMappingDbWrapper = $keywordMappingWrapper; + + // Register the service locator with each + $recipeWrapper->setWrapperServiceLocator($this); + $categoryWrapper->setWrapperServiceLocator($this); + $categoryMappingWrapper->setWrapperServiceLocator($this); + $keywordWrapper->setWrapperServiceLocator($this); + $keywordMappingWrapper->setWrapperServiceLocator($this); + } + /** + * @return \OCA\Cookbook\Db\RecipeDbWrapper + */ + public function getRecipeDbWrapper() { + return $this->recipeDbWrapper; + } + + /** + * @return \OCA\Cookbook\Db\CategoryDbWrapper + */ + public function getCategoryDbWrapper() { + return $this->categoryDbWrapper; + } + + /** + * @return \OCA\Cookbook\Db\CategoryMappingDbWrapper + */ + public function getCategoryMappingDbWrapper() { + return $this->categoryMappingDbWrapper; + } + + /** + * @return \OCA\Cookbook\Db\KeywordDbWrapper + */ + public function getKeywordDbWrapper() { + return $this->keywordDbWrapper; + } + + /** + * @return \OCA\Cookbook\Db\KeywordMappingDbWrapper + */ + public function getKeywordMappingDbWrapper() { + return $this->keywordMappingDbWrapper; + } +} From 3af617f98c7219f47e6aaa9e5c7dc028e0db4797 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 11:52:05 +0100 Subject: [PATCH 12/38] Added missing constructor to RecipeEntity Signed-off-by: Christian Wolf --- lib/Entity/RecipeEntity.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php index 28d36b5da..6cf2a3813 100644 --- a/lib/Entity/RecipeEntity.php +++ b/lib/Entity/RecipeEntity.php @@ -21,6 +21,15 @@ class RecipeEntity implements Entity { */ private $name; + /** + * Creat a new entity object + * Do not use this constructor directly but create new entities from the corresponding wrapper. + * @param RecipeDbWrapper $wrapper The wrapper to use for DB access + */ + public function __construct(RecipeDbWrapper $wrapper){ + $this->wrapper = $wrapper; + } + public function persist(): void { $this->wrapper->store($this); } From b6c6ce12c5e9099b07049f20ea7c59f028f44995 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 15:01:30 +0100 Subject: [PATCH 13/38] Corrected namespaces Signed-off-by: Christian Wolf --- lib/Entity/CategoryEntity.php | 2 +- lib/Entity/CategoryMappingEntity.php | 2 +- lib/Entity/KeywordMappingEntity.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Entity/CategoryEntity.php b/lib/Entity/CategoryEntity.php index 080e40ee4..b4fbf8fa2 100644 --- a/lib/Entity/CategoryEntity.php +++ b/lib/Entity/CategoryEntity.php @@ -2,7 +2,7 @@ namespace OCA\Cookbook\Entity; -use OCAA\Cookbook\Db\CategoryDbWrapper; +use OCA\Cookbook\Db\CategoryDbWrapper; class CategoryEntity implements Entity { diff --git a/lib/Entity/CategoryMappingEntity.php b/lib/Entity/CategoryMappingEntity.php index 0b10ccac9..b1c389f62 100644 --- a/lib/Entity/CategoryMappingEntity.php +++ b/lib/Entity/CategoryMappingEntity.php @@ -2,7 +2,7 @@ namespace OCA\Cookbook\Entity; -use OCAA\Cookbook\Db\CategoryMappingDbWrapper; +use OCA\Cookbook\Db\CategoryMappingDbWrapper; class CategoryMappingEntity implements Entity { diff --git a/lib/Entity/KeywordMappingEntity.php b/lib/Entity/KeywordMappingEntity.php index 5c918512a..1aa63bc6d 100644 --- a/lib/Entity/KeywordMappingEntity.php +++ b/lib/Entity/KeywordMappingEntity.php @@ -2,7 +2,7 @@ namespace OCA\Cookbook\Entity; -use OCAA\Cookbook\Db\KeywordMappingDbWrapper; +use OCA\Cookbook\Db\KeywordMappingDbWrapper; class KeywordMappingEntity implements Entity { From fce1af29ef74cc1d500f3a95887231159172460e Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 17:30:23 +0100 Subject: [PATCH 14/38] Added access to the service locator in the abstract wrapper class Signed-off-by: Christian Wolf --- lib/Db/AbstractDbWrapper.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/Db/AbstractDbWrapper.php b/lib/Db/AbstractDbWrapper.php index a73709b45..2e2cbd219 100644 --- a/lib/Db/AbstractDbWrapper.php +++ b/lib/Db/AbstractDbWrapper.php @@ -93,4 +93,16 @@ protected function setEntites(array $entities): void { public function setWrapperServiceLocator(DbWrapperServiceProvider $locator) { $this->wrapperLocator = $locator; } + + /** + * Get the central service locator for registering of all wrappers + * @return DbWrapperServiceProvider The locator for the registered wrappers + */ + public function getWrapperServiceLocator() + { + return $this->wrapperLocator; + } + + + } From 474580d8c9af10a0e1952a10fd87910c71b148a8 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 17:31:41 +0100 Subject: [PATCH 15/38] Added missing methods to keyword and category classes Signed-off-by: Christian Wolf --- lib/Entity/CategoryMappingEntity.php | 40 ++++++++++++++++++++++++++-- lib/Entity/KeywordMappingEntity.php | 31 +++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/lib/Entity/CategoryMappingEntity.php b/lib/Entity/CategoryMappingEntity.php index b1c389f62..844a8f4a5 100644 --- a/lib/Entity/CategoryMappingEntity.php +++ b/lib/Entity/CategoryMappingEntity.php @@ -1,12 +1,16 @@ wrapper->store($this); } + + /** + * @return \OCA\Cookbook\Entity\RecipeEntity + */ + public function getRecipe() + { + return $this->recipe; + } + + /** + * @return \OCA\Cookbook\Entity\CategoryEntity + */ + public function getCategory() + { + return $this->category; + } + + /** + * @param \OCA\Cookbook\Entity\RecipeEntity $recipe + */ + public function setRecipe($recipe) + { + $this->recipe = $recipe; + } + + /** + * @param \OCA\Cookbook\Entity\CategoryEntity $category + */ + public function setCategory($category) + { + $this->category = $category; + } } diff --git a/lib/Entity/KeywordMappingEntity.php b/lib/Entity/KeywordMappingEntity.php index 1aa63bc6d..8474cad4e 100644 --- a/lib/Entity/KeywordMappingEntity.php +++ b/lib/Entity/KeywordMappingEntity.php @@ -28,4 +28,35 @@ public function __construct(KeywordMappingDbWrapper $wrapper) { public function persist(): void { $this->wrapper->store($this); } + /** + * @return \OCA\Cookbook\Entity\RecipeEntity + */ + public function getRecipe() + { + return $this->recipe; + } + + /** + * @return \OCA\Cookbook\Entity\KeywordEntity + */ + public function getKeyword() + { + return $this->keyword; + } + + /** + * @param \OCA\Cookbook\Entity\RecipeEntity $recipe + */ + public function setRecipe($recipe) + { + $this->recipe = $recipe; + } + + /** + * @param \OCA\Cookbook\Entity\KeywordEntity $keyword + */ + public function setKeyword($keyword) + { + $this->keyword = $keyword; + } } From 4f47af6204a955149d6ec0c1a64fe8fbeb8535ef Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 17:33:03 +0100 Subject: [PATCH 16/38] Added basic structure to remove entities Signed-off-by: Christian Wolf --- lib/Db/CategoryDbWrapper.php | 3 +++ lib/Db/CategoryMappingDbWrapper.php | 3 +++ lib/Db/RecipeDbWrapper.php | 4 ++++ lib/Entity/CategoryEntity.php | 6 ++++++ lib/Entity/CategoryMappingEntity.php | 6 ++++++ lib/Entity/Entity.php | 1 + lib/Entity/RecipeEntity.php | 7 +++++++ 7 files changed, 30 insertions(+) diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php index 6809e72ac..e6faf362d 100644 --- a/lib/Db/CategoryDbWrapper.php +++ b/lib/Db/CategoryDbWrapper.php @@ -62,4 +62,7 @@ public function store(CategoryEntity $category): void { public function createEntity(): CategoryEntity { return new CategoryEntity($this); } + + public function remove(CategoryEntity $category) { + } } diff --git a/lib/Db/CategoryMappingDbWrapper.php b/lib/Db/CategoryMappingDbWrapper.php index 2c3b43c5a..2c2892bcc 100644 --- a/lib/Db/CategoryMappingDbWrapper.php +++ b/lib/Db/CategoryMappingDbWrapper.php @@ -62,4 +62,7 @@ public function store(CategoryMappingEntity $mapping): void { public function createEntity(): CategoryMappingEntity { return new CategoryMappingEntity($this); } + + public function remove(CategoryMappingEntity $mapper): void { + } } diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index 2f2519f64..782fd3b61 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -34,4 +34,8 @@ public function store(RecipeEntity $recipe): void { public function createEntity(): RecipeEntity { // FIXME } + + public function remove(RecipeEntity $recipe): void { + // FIXME + } } diff --git a/lib/Entity/CategoryEntity.php b/lib/Entity/CategoryEntity.php index b4fbf8fa2..6c71149fa 100644 --- a/lib/Entity/CategoryEntity.php +++ b/lib/Entity/CategoryEntity.php @@ -44,4 +44,10 @@ public function setName(string $name): void { public function persist(): void { $this->wrapper->store($this); } + + public function remove(): void + { + $this->wrapper->remove($this); + } + } diff --git a/lib/Entity/CategoryMappingEntity.php b/lib/Entity/CategoryMappingEntity.php index 844a8f4a5..7810cd3de 100644 --- a/lib/Entity/CategoryMappingEntity.php +++ b/lib/Entity/CategoryMappingEntity.php @@ -65,4 +65,10 @@ public function setCategory($category) { $this->category = $category; } + + public function remove(): void + { + $this->wrapper->remove($this); + } + } diff --git a/lib/Entity/Entity.php b/lib/Entity/Entity.php index 836260a92..51475844f 100644 --- a/lib/Entity/Entity.php +++ b/lib/Entity/Entity.php @@ -7,4 +7,5 @@ interface Entity { * Store the Entity to the database */ public function persist(): void; + public function remove(): void; } diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php index 6cf2a3813..c4807ac9a 100644 --- a/lib/Entity/RecipeEntity.php +++ b/lib/Entity/RecipeEntity.php @@ -66,4 +66,11 @@ public function setId($id): void { public function setName($name): void { $this->name = $name; } + + public function remove(): void + { + $this->wrapper->remove($this); + } + + } From 7d734de16a7541934a4c48fe50dcea5f4534e804 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 17:58:00 +0100 Subject: [PATCH 17/38] Implemented Wrapper for CategoryMappers Signed-off-by: Christian Wolf --- lib/Db/CategoryMappingDbWrapper.php | 82 +++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/lib/Db/CategoryMappingDbWrapper.php b/lib/Db/CategoryMappingDbWrapper.php index 2c2892bcc..4a9d0b4a5 100644 --- a/lib/Db/CategoryMappingDbWrapper.php +++ b/lib/Db/CategoryMappingDbWrapper.php @@ -7,6 +7,8 @@ class CategoryMappingDbWrapper extends AbstractDbWrapper { + private const CATEGORIES = 'cookbook_categories'; + /** * @var string */ @@ -24,28 +26,29 @@ public function __construct(string $UserId, IDBConnection $db) { } protected function fetchDatabase(): array { -// $qb = $this->db->getQueryBuilder(); + // FIXME + $qb = $this->db->getQueryBuilder(); + + $qb ->select('name', 'recipe_id') + ->from(self::CATEGORIES) + ->where('user_id = :uid'); -// $qb ->select('name') -// ->from('cookbook_categories') -// ->where('user_id = :uid') -// ->groupBy('name') -// ->orderBy('name'); + $qb->setParameter('uid', $this->userId); -// $qb->setParameter('uid', $this->userId); + $res = $qb->execute(); + $ret = []; -// $res = $qb->execute(); -// $ret = []; -// while ($row = $res->fetch()) { -// $entity = new CategoryEntity($this); -// $entity->setName($row['name']); + while ($row = $res->fetch()) { + $entity = $this->createEntity(); + $entity->setName($row['name']); + $entity->setRecipe($this->getWrapperServiceLocator()->getRecipeDbWrapper()->getRecipeById($row['recipe_id'])); -// $ret[] = $entity; -// } + $ret[] = $entity; + } -// $res->closeCursor(); + $res->closeCursor(); -// return $ret; + return $ret; } /** @@ -53,6 +56,36 @@ protected function fetchDatabase(): array { * @param CategoryMappingEntity $category The entity to store */ public function store(CategoryMappingEntity $mapping): void { + // FIXME + $foundMapping = array_filter($this->getEntries(), function (CategoryMappingEntity $entity) use ($mapping) { + return $entity === $mapping; + }); + + $qb = $this->db->getQueryBuilder(); + + if(count($foundMapping) > 0) { + // We need to update an existing entry + $qb ->update(self::CATEGORIES) + ->set('name', $mapping->getCategory()->getName()) + ->where('recipe_id = :rid', 'user_id = :uid'); + $qb->setParameters([ + 'rid' => $mapping->getRecipe()->getId(), + 'uid' => $this->userId + ]); + } + else { + // We need to create a new entry + $qb ->insert(self::CATEGORIES) + ->values([ + 'recipe_id' => $mapping->getRecipe()->getId(), + 'user_id' => $this->userId, + 'name' => $mapping->getCategory()->getName() + ]); + } + + $qb->execute(); + $this->invalidateCache(); + // TODO Changing of an object will change the cache as well } /** @@ -64,5 +97,22 @@ public function createEntity(): CategoryMappingEntity { } public function remove(CategoryMappingEntity $mapper): void { + $qb = $this->db->getQueryBuilder(); + + $qb ->delete(self::CATEGORIES) + ->where('recipe_id = :rid', 'user_id = :uid'); + $qb->setParameters([ + 'rid' => $mapper->getRecipe()->getId(), + 'uid' => $this->userId + ]); + + $qb->execute(); + $this->invalidateCache(); + +// $this->setEntites(array_filter($this->getEntries(), function (CategoryMappingEntity $entity) use ($mapper) { +// return $entity !== $mapper; +// })); + + // XXX Remove CategoryEntity completely? } } From 73b6584ee8642c72ff579dcecbab7184da8aba95 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Fri, 20 Nov 2020 18:06:17 +0100 Subject: [PATCH 18/38] Implemented search for recipe by id Signed-off-by: Christian Wolf --- lib/Db/RecipeDbWrapper.php | 49 +++++++++++++++++++++-- lib/Exception/EntityNotFoundException.php | 17 ++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 lib/Exception/EntityNotFoundException.php diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index 782fd3b61..4e77070f6 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -4,6 +4,8 @@ use OCA\Cookbook\Entity\RecipeEntity; use OCP\IDBConnection; +use OCA\Cookbook\Exception\EntityNotFoundException; +use OCP\IL10N; class RecipeDbWrapper extends AbstractDbWrapper { @@ -17,25 +19,66 @@ class RecipeDbWrapper extends AbstractDbWrapper { */ private $userId; - public function __construct(string $UserId, IDBConnection $db) { + /** + * @var IL10N + */ + private $l; + + public function __construct(string $UserId, IDBConnection $db, IL10N $l) { parent::__construct($db); $this->db = $db; $this->userId = $UserId; + $this->l = $l; } protected function fetchDatabase(): array { // FIXME + $qb = $this->db->getQueryBuilder(); + + $qb ->select('id', 'name') + ->from('cookbook_names') + ->where('user_id = :uid'); + $qb->setParameter('uid', $this->userId); + + $res = $qb->execute(); + $ret = []; + + while($row = $res->fetch()) + { + $recipe = $this->createEntity(); // XXX Better create directly? + $recipe->setName($row['name']); + $recipe->setId($row['id']); + + $ret[] = $recipe; + } + + return $ret; } - + public function store(RecipeEntity $recipe): void { // FIXME } public function createEntity(): RecipeEntity { - // FIXME + return new RecipeEntity($this); } public function remove(RecipeEntity $recipe): void { // FIXME } + + public function getRecipeById(int $id): RecipeEntity { + $entities = $this->getEntries(); + + foreach($entities as $entry) + { + /** + * @var RecipeEntity $entry + */ + if($entry->getId() == $id) + return $entry; + } + + throw new EntityNotFoundException($this->l->t('Recipe with id %d was not found.', $id)); + } } diff --git a/lib/Exception/EntityNotFoundException.php b/lib/Exception/EntityNotFoundException.php new file mode 100644 index 000000000..17db002f6 --- /dev/null +++ b/lib/Exception/EntityNotFoundException.php @@ -0,0 +1,17 @@ + Date: Fri, 20 Nov 2020 19:30:04 +0100 Subject: [PATCH 19/38] Added some error handling and implemented Wrapper for Recipes Signed-off-by: Christian Wolf --- lib/Db/CategoryMappingDbWrapper.php | 16 +++++++- lib/Db/RecipeDbWrapper.php | 45 ++++++++++++++++++++++- lib/Exception/InvalidDbStateException.php | 9 +++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 lib/Exception/InvalidDbStateException.php diff --git a/lib/Db/CategoryMappingDbWrapper.php b/lib/Db/CategoryMappingDbWrapper.php index 4a9d0b4a5..3a0183b11 100644 --- a/lib/Db/CategoryMappingDbWrapper.php +++ b/lib/Db/CategoryMappingDbWrapper.php @@ -4,6 +4,8 @@ use OCP\IDBConnection; use OCA\Cookbook\Entity\CategoryMappingEntity; +use OCA\Cookbook\Exception\InvalidDbStateException; +use OCP\IL10N; class CategoryMappingDbWrapper extends AbstractDbWrapper { @@ -19,10 +21,16 @@ class CategoryMappingDbWrapper extends AbstractDbWrapper { */ private $db; - public function __construct(string $UserId, IDBConnection $db) { + /** + * @var IL10N + */ + private $l; + + public function __construct(string $UserId, IDBConnection $db, IL10N $l) { parent::__construct($db); $this->userId = $UserId; $this->db = $db; + $this->l = $l; } protected function fetchDatabase(): array { @@ -57,6 +65,12 @@ protected function fetchDatabase(): array { */ public function store(CategoryMappingEntity $mapping): void { // FIXME + if($mapping->getRecipe()->getId() == -1) + { + // Recipe was not saved yet. + throw new InvalidDbStateException($this->l->t('The recipe was not stored to the database yet. No id known.')); + } + $foundMapping = array_filter($this->getEntries(), function (CategoryMappingEntity $entity) use ($mapping) { return $entity === $mapping; }); diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index 4e77070f6..67e9b30f8 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -6,9 +6,12 @@ use OCP\IDBConnection; use OCA\Cookbook\Exception\EntityNotFoundException; use OCP\IL10N; +use OCA\Cookbook\Exception\InvalidDbStateException; class RecipeDbWrapper extends AbstractDbWrapper { + private const NAMES = 'cookbook_names'; + /** * @var IDBConnection */ @@ -57,14 +60,54 @@ protected function fetchDatabase(): array { public function store(RecipeEntity $recipe): void { // FIXME + + $qb = $this->db->getQueryBuilder(); + + if($recipe->getId() == -1) + { + $qb ->insert(self::NAMES) + ->values([ + 'name' => $recipe->getName(), + 'user_id' => $this->userId + ]); + } + else { + $qb ->update(self::NAMES) + ->set('name', ':name') + ->where('recipe_id = :rid'); + $qb->setParameter('rid', $recipe->getId()); + $qb->setParameter('name', $recipe->getName()); + } + + $qb->execute(); + $this->invalidateCache(); + + if($recipe->getId() == -1){ + $recipe->setId($qb->getLastInsertId()); + } } public function createEntity(): RecipeEntity { - return new RecipeEntity($this); + $ret = new RecipeEntity($this); + $ret->setId(-1); + return $ret; } public function remove(RecipeEntity $recipe): void { // FIXME + if($recipe->getId() == -1) + { + throw new InvalidDbStateException($this->l->t('Cannot remove recipe that was not yet saved.')); + } + + $qb = $this->db->getQueryBuilder(); + + $qb ->delete(self::NAMES) + ->where('recipe_id = :rid'); + $qb->setParameter('rid', $recipe->getId()); + + $qb->execute(); + $this->invalidateCache(); } public function getRecipeById(int $id): RecipeEntity { diff --git a/lib/Exception/InvalidDbStateException.php b/lib/Exception/InvalidDbStateException.php new file mode 100644 index 000000000..fdcd300cb --- /dev/null +++ b/lib/Exception/InvalidDbStateException.php @@ -0,0 +1,9 @@ + Date: Sat, 21 Nov 2020 17:12:58 +0100 Subject: [PATCH 20/38] Intermediate UML diagramm added Signed-off-by: Christian Wolf --- documentation/uml/dbwrappers.txt | 169 +++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 documentation/uml/dbwrappers.txt diff --git a/documentation/uml/dbwrappers.txt b/documentation/uml/dbwrappers.txt new file mode 100644 index 000000000..d6439d70c --- /dev/null +++ b/documentation/uml/dbwrappers.txt @@ -0,0 +1,169 @@ +@startuml + +namespace OCA.Cookbook { + + namespace Entity { + + interface Entity { + +persist() + +remove() + } + + interface RecipeEntity { + -- + + getId() : int + + getName() : string + + setName(string) : void + .. + + getCategory() : CategoryEntity + + setCategory(CategoryEntity) : void + + getKeywords() : array + + setKeywords(array) : void + } + + interface CategoryEntity { + -- + + getName() : string + + setName(string) : void + .. + + getRecipes() : array + } + + interface KeywordEntity { + -- + + getName() : string + + setName(string) : void + .. + + getRecipes() : array + } + + + namespace impl { + +' class AbstractEntity {} + + interface ImplEntity { + + isPersisted() : bool + + clone() : ImplEntity + + isSame(ImplEntity) : bool + } + + class RecipeEntityImpl { + #id + #name + #userId + # RecipeDbWrapper wrapper + } + + class CategoryEntityImpl { + #name + # CategoryDbWrapper wrapper + } + + class KeywordEntityImpl { + #name + # KeywordDbWrapper wrapper + } + + class CategoryMappingEntity { + # CategoryMappingDbWrapper wrapper + } + + class KeywordMappingEntity { + # KeywordMappingDbWrapper wrapper + } + +' AbstractEntity <|-- RecipeEntity +' AbstractEntity <|-- CategoryEntity +' AbstractEntity <|-- KeywordEntity +' AbstractEntity <|-- OCA\Cookbook.Entity.impl.KeywordMappingEntity + +/' ImplEntity <|. RecipeEntityImpl + ImplEntity <|. CategoryEntityImpl + ImplEntity <|. KeywordEntityImpl + ImplEntity <|. CategoryMappingEntity + ImplEntity <|. KeywordMappingEntity'/ + + RecipeEntityImpl ..|> ImplEntity + CategoryEntityImpl ..|> ImplEntity + KeywordEntityImpl ..|> ImplEntity + CategoryMappingEntity ..|> ImplEntity + KeywordMappingEntity ..|> ImplEntity + + RecipeEntityImpl "1" *-- "0..1" OCA.Cookbook.Entity.impl.CategoryMappingEntity + CategoryEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.CategoryMappingEntity + + RecipeEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.KeywordMappingEntity + KeywordEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.KeywordMappingEntity + + } + + RecipeEntity <|.. OCA.Cookbook.Entity.impl.RecipeEntityImpl + CategoryEntity <|.. OCA.Cookbook.Entity.impl.CategoryEntityImpl + KeywordEntity <|.. OCA.Cookbook.Entity.impl.KeywordEntityImpl + + Entity <|.. RecipeEntity + Entity <|.. CategoryEntity + Entity <|.. KeywordEntity + + } + + namespace Db { + + abstract class AbstractDbWrapper { + # IDbConnection db + + abstract createEntity() : Entity + - array cache + - bool cacheValid + # abstract fetchDatabase() : array + + getEntites() : array + # setCache(array) : void + } + note right + Cache representys the current state + of the database tables. + endnote + + class RecipeDbWrapper { + + createEntity() : RecipeEntity + + store(RecipeEntityImpl) : void + - storeNew(RecipeEntityImpl) : void + - update(RecipeEntityImpl) : void + } + class CategoryDbWrapper { + + createEntity() : CategoryEntity + + store(CategoryEntityImpl) : void + - storeNew(CategoryEntityImpl) : void + - update(CategoryEntityImpl) : void + } + class KeywordDbWrapper { + + createEntity() : KeywordEntity + + store(KeywordEntityImpl) : void + - storeNew(KeywordEntityImpl) : void + - update(KeywordEntityImpl) : void + } + class CategoryMappingDbWrapper { + + createEntity() : CategoryMappingEntity + } + class KeywordMappingDbWrapper { + + createEntity() : KeywordMappingEntity + } + + class DbWrapperSerciveLocation {} + + AbstractDbWrapper <|-- RecipeDbWrapper + AbstractDbWrapper <|-- CategoryDbWrapper + AbstractDbWrapper <|-- KeywordDbWrapper + AbstractDbWrapper <|-- CategoryMappingDbWrapper + AbstractDbWrapper <|-- KeywordMappingDbWrapper + + RecipeDbWrapper "1" o-- "1" DbWrapperSerciveLocation + CategoryDbWrapper "1" o-- "1" DbWrapperSerciveLocation + KeywordDbWrapper "1" o-- "1" DbWrapperSerciveLocation + CategoryMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocation + KeywordMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocation + } + +} + +@enduml From 0ee944ef697c4ebf6741df76247a2fa8e67d8a58 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sat, 21 Nov 2020 18:12:29 +0100 Subject: [PATCH 21/38] Updated diagramm before bigger changes Signed-off-by: Christian Wolf --- documentation/uml/dbwrappers.txt | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/documentation/uml/dbwrappers.txt b/documentation/uml/dbwrappers.txt index d6439d70c..78897bd1b 100644 --- a/documentation/uml/dbwrappers.txt +++ b/documentation/uml/dbwrappers.txt @@ -18,7 +18,8 @@ namespace OCA.Cookbook { + getCategory() : CategoryEntity + setCategory(CategoryEntity) : void + getKeywords() : array - + setKeywords(array) : void + + addKeyword(KeywordEntity) : void + + removeKeyword(KeywordEntity) : void } interface CategoryEntity { @@ -42,10 +43,12 @@ namespace OCA.Cookbook { ' class AbstractEntity {} - interface ImplEntity { + abstract class AbstractEntity { + - bool persisted + isPersisted() : bool + clone() : ImplEntity - + isSame(ImplEntity) : bool + + isSame(AbstractEntity) : bool + + equals(AbstractEntity) : bool } class RecipeEntityImpl { @@ -84,11 +87,11 @@ namespace OCA.Cookbook { ImplEntity <|. CategoryMappingEntity ImplEntity <|. KeywordMappingEntity'/ - RecipeEntityImpl ..|> ImplEntity - CategoryEntityImpl ..|> ImplEntity - KeywordEntityImpl ..|> ImplEntity - CategoryMappingEntity ..|> ImplEntity - KeywordMappingEntity ..|> ImplEntity + RecipeEntityImpl --|> AbstractEntity + CategoryEntityImpl --|> AbstractEntity + KeywordEntityImpl --|> AbstractEntity + CategoryMappingEntity --|> AbstractEntity + KeywordMappingEntity --|> AbstractEntity RecipeEntityImpl "1" *-- "0..1" OCA.Cookbook.Entity.impl.CategoryMappingEntity CategoryEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.CategoryMappingEntity @@ -118,26 +121,31 @@ namespace OCA.Cookbook { # abstract fetchDatabase() : array + getEntites() : array # setCache(array) : void + + getServiceLocator() : DbWrapperSerciveLocator } note right Cache representys the current state - of the database tables. + of the database tables as clones. endnote class RecipeDbWrapper { - + createEntity() : RecipeEntity + '-- + + createEntity() : RecipeEntityImpl + store(RecipeEntityImpl) : void - storeNew(RecipeEntityImpl) : void - update(RecipeEntityImpl) : void + /'.. + + addKeyword(RecipeEntityImpl, KeywordEntityImpl) : KeywordMappingEntity + + setCategory(RecipeEntityImpl, CategoryEntityImpl) : CategoryMappingEntity'/ } class CategoryDbWrapper { - + createEntity() : CategoryEntity + + createEntity() : CategoryEntityImpl + store(CategoryEntityImpl) : void - storeNew(CategoryEntityImpl) : void - update(CategoryEntityImpl) : void } class KeywordDbWrapper { - + createEntity() : KeywordEntity + + createEntity() : KeywordEntityImpl + store(KeywordEntityImpl) : void - storeNew(KeywordEntityImpl) : void - update(KeywordEntityImpl) : void @@ -149,7 +157,13 @@ namespace OCA.Cookbook { + createEntity() : KeywordMappingEntity } - class DbWrapperSerciveLocation {} + class DbWrapperSerciveLocator { + + getRecipeDbWrapoper() : RecipeDbWrapper + + getCategoryDbWrapper() : CategoryDbWrapper + + getKeywordDbWrapper() : KeywordDbWrapper + + getCategoryMappingDbWrapper() : CategoryMappingDbWrapper + + getKeywordMappingDbWrapper() : KeywordMappingDbWrapper + } AbstractDbWrapper <|-- RecipeDbWrapper AbstractDbWrapper <|-- CategoryDbWrapper @@ -157,11 +171,11 @@ namespace OCA.Cookbook { AbstractDbWrapper <|-- CategoryMappingDbWrapper AbstractDbWrapper <|-- KeywordMappingDbWrapper - RecipeDbWrapper "1" o-- "1" DbWrapperSerciveLocation - CategoryDbWrapper "1" o-- "1" DbWrapperSerciveLocation - KeywordDbWrapper "1" o-- "1" DbWrapperSerciveLocation - CategoryMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocation - KeywordMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocation + RecipeDbWrapper "1" o-- "1" DbWrapperSerciveLocator + CategoryDbWrapper "1" o-- "1" DbWrapperSerciveLocator + KeywordDbWrapper "1" o-- "1" DbWrapperSerciveLocator + CategoryMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocator + KeywordMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocator } } From 49c2ced0dc0713bc96d9fb9ed54414ae5b925c49 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sat, 21 Nov 2020 20:59:17 +0100 Subject: [PATCH 22/38] Removed MappingEntities from structure Signed-off-by: Christian Wolf --- documentation/uml/dbwrappers.txt | 70 +++++++++++--------------------- 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/documentation/uml/dbwrappers.txt b/documentation/uml/dbwrappers.txt index 78897bd1b..2264a62e5 100644 --- a/documentation/uml/dbwrappers.txt +++ b/documentation/uml/dbwrappers.txt @@ -41,14 +41,12 @@ namespace OCA.Cookbook { namespace impl { -' class AbstractEntity {} - abstract class AbstractEntity { - bool persisted + isPersisted() : bool - + clone() : ImplEntity - + isSame(AbstractEntity) : bool - + equals(AbstractEntity) : bool + + abstract clone() : ImplEntity + + abstract isSame(AbstractEntity) : bool + + abstract equals(AbstractEntity) : bool } class RecipeEntityImpl { @@ -56,6 +54,12 @@ namespace OCA.Cookbook { #name #userId # RecipeDbWrapper wrapper + # CategoryEntityImpl newCategory + # array newKeywords + # array removedKeywords + + getNewCategory() : CategoryEntityImpl | null + + getNewKeywords() : array + + getRemovedKeywords() : array } class CategoryEntityImpl { @@ -68,36 +72,10 @@ namespace OCA.Cookbook { # KeywordDbWrapper wrapper } - class CategoryMappingEntity { - # CategoryMappingDbWrapper wrapper - } - - class KeywordMappingEntity { - # KeywordMappingDbWrapper wrapper - } - -' AbstractEntity <|-- RecipeEntity -' AbstractEntity <|-- CategoryEntity -' AbstractEntity <|-- KeywordEntity -' AbstractEntity <|-- OCA\Cookbook.Entity.impl.KeywordMappingEntity - -/' ImplEntity <|. RecipeEntityImpl - ImplEntity <|. CategoryEntityImpl - ImplEntity <|. KeywordEntityImpl - ImplEntity <|. CategoryMappingEntity - ImplEntity <|. KeywordMappingEntity'/ - RecipeEntityImpl --|> AbstractEntity CategoryEntityImpl --|> AbstractEntity KeywordEntityImpl --|> AbstractEntity - CategoryMappingEntity --|> AbstractEntity - KeywordMappingEntity --|> AbstractEntity - - RecipeEntityImpl "1" *-- "0..1" OCA.Cookbook.Entity.impl.CategoryMappingEntity - CategoryEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.CategoryMappingEntity - RecipeEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.KeywordMappingEntity - KeywordEntityImpl "1" *-- "*" OCA.Cookbook.Entity.impl.KeywordMappingEntity } @@ -129,53 +107,53 @@ namespace OCA.Cookbook { endnote class RecipeDbWrapper { - '-- +' - array idMap + -- + createEntity() : RecipeEntityImpl + store(RecipeEntityImpl) : void - storeNew(RecipeEntityImpl) : void - update(RecipeEntityImpl) : void - /'.. - + addKeyword(RecipeEntityImpl, KeywordEntityImpl) : KeywordMappingEntity + + remove(RecipeEntityImpl) : void + .. + /'+ addKeyword(RecipeEntityImpl, KeywordEntityImpl) : KeywordMappingEntity + setCategory(RecipeEntityImpl, CategoryEntityImpl) : CategoryMappingEntity'/ + + getCategory(RecipeEntityImpl) : CategoryEntityImpl + + getKeywords(RecipeEntityImpl) : array } class CategoryDbWrapper { + -- + createEntity() : CategoryEntityImpl + store(CategoryEntityImpl) : void - storeNew(CategoryEntityImpl) : void - update(CategoryEntityImpl) : void + + remove(CategoryEntityImpl) : void + .. + + getRecipes(CategoryEntityImpl) : array } class KeywordDbWrapper { + -- + createEntity() : KeywordEntityImpl + store(KeywordEntityImpl) : void - storeNew(KeywordEntityImpl) : void - update(KeywordEntityImpl) : void - } - class CategoryMappingDbWrapper { - + createEntity() : CategoryMappingEntity - } - class KeywordMappingDbWrapper { - + createEntity() : KeywordMappingEntity + + remove(KeywordEntityImpl) : void + .. + + getRecipes(KeywordEntityImpl) : array } class DbWrapperSerciveLocator { + getRecipeDbWrapoper() : RecipeDbWrapper + getCategoryDbWrapper() : CategoryDbWrapper + getKeywordDbWrapper() : KeywordDbWrapper - + getCategoryMappingDbWrapper() : CategoryMappingDbWrapper - + getKeywordMappingDbWrapper() : KeywordMappingDbWrapper } AbstractDbWrapper <|-- RecipeDbWrapper AbstractDbWrapper <|-- CategoryDbWrapper AbstractDbWrapper <|-- KeywordDbWrapper - AbstractDbWrapper <|-- CategoryMappingDbWrapper - AbstractDbWrapper <|-- KeywordMappingDbWrapper RecipeDbWrapper "1" o-- "1" DbWrapperSerciveLocator CategoryDbWrapper "1" o-- "1" DbWrapperSerciveLocator KeywordDbWrapper "1" o-- "1" DbWrapperSerciveLocator - CategoryMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocator - KeywordMappingDbWrapper "1" o-- "1" DbWrapperSerciveLocator } } From f5e508c34f7fdce5b29a0e29599cd5d5a0afd112 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sat, 21 Nov 2020 21:00:28 +0100 Subject: [PATCH 23/38] Readded MappingEntities in a minimal fashion. Working not yet checked. Signed-off-by: Christian Wolf --- documentation/uml/dbwrappers.txt | 45 ++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/documentation/uml/dbwrappers.txt b/documentation/uml/dbwrappers.txt index 2264a62e5..31e281bc4 100644 --- a/documentation/uml/dbwrappers.txt +++ b/documentation/uml/dbwrappers.txt @@ -5,8 +5,9 @@ namespace OCA.Cookbook { namespace Entity { interface Entity { - +persist() - +remove() + + persist() : void + + remove() : void + + reload() : void } interface RecipeEntity { @@ -72,10 +73,23 @@ namespace OCA.Cookbook { # KeywordDbWrapper wrapper } + class CategoryMappingEntity { + } + + class KeywordMappingEntity { + } + RecipeEntityImpl --|> AbstractEntity CategoryEntityImpl --|> AbstractEntity KeywordEntityImpl --|> AbstractEntity + CategoryMappingEntity -|> AbstractEntity + AbstractEntity <|- KeywordMappingEntity + + RecipeEntityImpl "1" *--x CategoryMappingEntity + RecipeEntityImpl "1" *--x KeywordMappingEntity + CategoryEntityImpl "1" *--x CategoryMappingEntity + KeywordEntityImpl "1" *--x KeywordMappingEntity } @@ -141,10 +155,28 @@ namespace OCA.Cookbook { + getRecipes(KeywordEntityImpl) : array } + class CategoryMappingsDbWrapper { + + createEntity() : CategoryMappingEntity + + store(CategoryMappingEntity) : void + - createNew(CategoryMappingEntity) : void + - update(CategoryMappingEntity) : void + + remove(CategoryMappingEntity) : void + } + + class KeywordMappingsDbWrapper { + + createEntity() : KeywordMappingEntity + + store(KeywordMappingEntity) : void + - createNew(KeywordMappingEntity) : void + - update(KeywordMappingEntity) : void + + remove(KeywordMappingEntity) : void + } + class DbWrapperSerciveLocator { + getRecipeDbWrapoper() : RecipeDbWrapper + getCategoryDbWrapper() : CategoryDbWrapper + getKeywordDbWrapper() : KeywordDbWrapper + + getKeywordMappingDbWrapper() : KeywordMappingsDbWrapper + + getCategoryMappingDbWrapper() : CategoryMappingsDbWrapper } AbstractDbWrapper <|-- RecipeDbWrapper @@ -154,8 +186,17 @@ namespace OCA.Cookbook { RecipeDbWrapper "1" o-- "1" DbWrapperSerciveLocator CategoryDbWrapper "1" o-- "1" DbWrapperSerciveLocator KeywordDbWrapper "1" o-- "1" DbWrapperSerciveLocator + + CategoryMappingsDbWrapper "1" o- "1" DbWrapperSerciveLocator + DbWrapperSerciveLocator "1" -o "1" KeywordMappingsDbWrapper + + AbstractDbWrapper <|--- CategoryMappingsDbWrapper + AbstractDbWrapper <|--- KeywordMappingsDbWrapper + + DbWrapperSerciveLocator -[hidden]-> AbstractDbWrapper } } + @enduml From 2494f98cc9a89ebcc28f4051be22f853f8b88165 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 10:28:02 +0100 Subject: [PATCH 24/38] Usage of generics to simplify abstract DB wrapper Signed-off-by: Christian Wolf --- documentation/uml/dbwrappers.txt | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/documentation/uml/dbwrappers.txt b/documentation/uml/dbwrappers.txt index 31e281bc4..eab50b737 100644 --- a/documentation/uml/dbwrappers.txt +++ b/documentation/uml/dbwrappers.txt @@ -105,18 +105,24 @@ namespace OCA.Cookbook { namespace Db { - abstract class AbstractDbWrapper { + abstract class AbstractDbWrapper { # IDbConnection db - + abstract createEntity() : Entity - array cache - bool cacheValid + -- + + abstract createEntity() : T # abstract fetchDatabase() : array + getEntites() : array # setCache(array) : void + getServiceLocator() : DbWrapperSerciveLocator + .. + + store(T) : void + # abstract storeNew(T) : void + # abstract update(T) : void + + remove(T) : void } note right - Cache representys the current state + Cache represents the current state of the database tables as clones. endnote @@ -125,8 +131,8 @@ namespace OCA.Cookbook { -- + createEntity() : RecipeEntityImpl + store(RecipeEntityImpl) : void - - storeNew(RecipeEntityImpl) : void - - update(RecipeEntityImpl) : void + # storeNew(RecipeEntityImpl) : void + # update(RecipeEntityImpl) : void + remove(RecipeEntityImpl) : void .. /'+ addKeyword(RecipeEntityImpl, KeywordEntityImpl) : KeywordMappingEntity @@ -138,8 +144,8 @@ namespace OCA.Cookbook { -- + createEntity() : CategoryEntityImpl + store(CategoryEntityImpl) : void - - storeNew(CategoryEntityImpl) : void - - update(CategoryEntityImpl) : void + # storeNew(CategoryEntityImpl) : void + # update(CategoryEntityImpl) : void + remove(CategoryEntityImpl) : void .. + getRecipes(CategoryEntityImpl) : array @@ -148,8 +154,8 @@ namespace OCA.Cookbook { -- + createEntity() : KeywordEntityImpl + store(KeywordEntityImpl) : void - - storeNew(KeywordEntityImpl) : void - - update(KeywordEntityImpl) : void + # storeNew(KeywordEntityImpl) : void + # update(KeywordEntityImpl) : void + remove(KeywordEntityImpl) : void .. + getRecipes(KeywordEntityImpl) : array @@ -158,16 +164,16 @@ namespace OCA.Cookbook { class CategoryMappingsDbWrapper { + createEntity() : CategoryMappingEntity + store(CategoryMappingEntity) : void - - createNew(CategoryMappingEntity) : void - - update(CategoryMappingEntity) : void + # storeNew(CategoryMappingEntity) : void + # update(CategoryMappingEntity) : void + remove(CategoryMappingEntity) : void } class KeywordMappingsDbWrapper { + createEntity() : KeywordMappingEntity + store(KeywordMappingEntity) : void - - createNew(KeywordMappingEntity) : void - - update(KeywordMappingEntity) : void + # storeNew(KeywordMappingEntity) : void + # update(KeywordMappingEntity) : void + remove(KeywordMappingEntity) : void } From 8a876c4fafd3e591edbb72a20b6418b3f7a3d507 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 11:46:39 +0100 Subject: [PATCH 25/38] Restrcutured PHP classes according to UML plan Signed-off-by: Christian Wolf --- lib/Entity/CategoryEntity.php | 50 +---- lib/Entity/Entity.php | 9 + lib/Entity/KeywordEntity.php | 42 +---- lib/Entity/RecipeEntity.php | 73 ++------ lib/Entity/impl/AbstractEntity.php | 52 ++++++ lib/Entity/impl/CategoryEntityImpl.php | 82 +++++++++ .../CategoryMappingEntityImpl.php} | 34 +++- lib/Entity/impl/KeywordEntityImpl.php | 79 ++++++++ .../KeywordMappingEntityImpl.php} | 24 ++- lib/Entity/impl/RecipeEntityImpl.php | 173 ++++++++++++++++++ lib/Exception/InvalidComparisionException.php | 17 ++ 11 files changed, 496 insertions(+), 139 deletions(-) create mode 100644 lib/Entity/impl/AbstractEntity.php create mode 100644 lib/Entity/impl/CategoryEntityImpl.php rename lib/Entity/{CategoryMappingEntity.php => impl/CategoryMappingEntityImpl.php} (67%) create mode 100644 lib/Entity/impl/KeywordEntityImpl.php rename lib/Entity/{KeywordMappingEntity.php => impl/KeywordMappingEntityImpl.php} (74%) create mode 100644 lib/Entity/impl/RecipeEntityImpl.php create mode 100644 lib/Exception/InvalidComparisionException.php diff --git a/lib/Entity/CategoryEntity.php b/lib/Entity/CategoryEntity.php index 6c71149fa..9965ebae0 100644 --- a/lib/Entity/CategoryEntity.php +++ b/lib/Entity/CategoryEntity.php @@ -1,53 +1,19 @@ wrapper = $wrapper; - } - - /** - * Get the name of the category * @return string The name of the category */ - public function getName(): string { - return $this->name; - } - + public function getName(): string; /** - * Set the name of the category. * @param string $name The new name of the category */ - public function setName(string $name): void { - $this->name = $name; - } + public function setName(string $name): void; - public function persist(): void { - $this->wrapper->store($this); - } - - public function remove(): void - { - $this->wrapper->remove($this); - } - + /** + * @return RecipeEntity[] The recipes associated with the category + */ + public function getRecipes(): array; } diff --git a/lib/Entity/Entity.php b/lib/Entity/Entity.php index 51475844f..6a02fc5dd 100644 --- a/lib/Entity/Entity.php +++ b/lib/Entity/Entity.php @@ -7,5 +7,14 @@ interface Entity { * Store the Entity to the database */ public function persist(): void; + + /** + * Remove the Entity immeadiately from the database + */ public function remove(): void; + + /** + * Reload all values from the database + */ + public function reload(): void; } diff --git a/lib/Entity/KeywordEntity.php b/lib/Entity/KeywordEntity.php index 7a1e7c9f7..14615c66e 100644 --- a/lib/Entity/KeywordEntity.php +++ b/lib/Entity/KeywordEntity.php @@ -2,46 +2,18 @@ namespace OCA\Cookbook\Entity; -use OCA\Cookbook\Db\KeywordDbWrapper; - -class KeywordEntity implements Entity { - - /** - * @var string - */ - private $name; - - /** - * @var KeywordDbWrapper - */ - private $wrapper; - +interface KeywordEntity extends Entity { /** - * Create a new entity. - * Do not use this constructor directly but create new entities from the corresponding wrapper. - * @param KeywordDbWrapper $wrapper The wrapper to use for DB communication. + * @return string The name of the keyword */ - public function __construct(KeywordDbWrapper $wrapper) { - $this->wrapper = $wrapper; - } - + public function getName(): string; /** - * Get the name of the keyword - * @return string The name of the keyword + * @param string $name The new name of the keyword */ - public function getName(): string { - return $this->name; - } + public function setName(string $name): void; /** - * Set the name of the keyword - * @param string $name The name of the keyword + * @return RecipeEntity[] The recipes associated with the keyword */ - public function setName($name) { - $this->name = $name; - } - - public function persist(): void { - $this->wrapper->store($this); - } + public function getRecipes(): array; } diff --git a/lib/Entity/RecipeEntity.php b/lib/Entity/RecipeEntity.php index c4807ac9a..6f65b9ca1 100644 --- a/lib/Entity/RecipeEntity.php +++ b/lib/Entity/RecipeEntity.php @@ -2,75 +2,40 @@ namespace OCA\Cookbook\Entity; -use OCA\Cookbook\Db\RecipeDbWrapper; - -class RecipeEntity implements Entity { - +interface RecipeEntity extends Entity { /** - * @var RecipeDbWrapper + * @return int The id of the recipe */ - private $wrapper; - + public function getId(): int; + /** - * @var int + * @return string The name of the recipe */ - private $id; - + public function getName(): string; /** - * @var string + * @param string $name The new name of the recipe */ - private $name; + public function setName(string $name): void; /** - * Creat a new entity object - * Do not use this constructor directly but create new entities from the corresponding wrapper. - * @param RecipeDbWrapper $wrapper The wrapper to use for DB access + * @return CategoryEntity The current category */ - public function __construct(RecipeDbWrapper $wrapper){ - $this->wrapper = $wrapper; - } - - public function persist(): void { - $this->wrapper->store($this); - } - + public function getCategory(): CategoryEntity; /** - * Obtain the id of the recipe in the database. - * @return number The id of the recipe in the database + * @param CategoryEntity $category The new category */ - public function getId(): int { - return $this->id; - } - + public function setCategory(CategoryEntity $category): void; + /** - * Get the name of the recipe - * @return string The name of the recipe + * @return KeywordEntity[] The current keywords */ - public function getName(): string { - return $this->name; - } - + public function getKeywords(): array; /** - * Set the id of the recipe in the database. - * Caution: This should be done only by the corresponding wrapper after inserting into the DB - * @param number $id The new id + * @param KeywordEntity $keyword The new keyword to add */ - public function setId($id): void { - $this->id = $id; - } - + public function addKeyword(KeywordEntity $keyword): void; /** - * Set the name of the recipe - * @param string $name The new name of the recipe + * @param KeywordEntity $keyword The keyword to remove */ - public function setName($name): void { - $this->name = $name; - } - - public function remove(): void - { - $this->wrapper->remove($this); - } - - + public function removeKeyword(KeywordEntity $keyword): void; } diff --git a/lib/Entity/impl/AbstractEntity.php b/lib/Entity/impl/AbstractEntity.php new file mode 100644 index 000000000..572c4951f --- /dev/null +++ b/lib/Entity/impl/AbstractEntity.php @@ -0,0 +1,52 @@ +persisted = $persisted; + } + + /** + * Check if the entity has been saved once + * @return bool true, if the element is alreday in the database + */ + public function isPersisted(): bool { + return $this->persisted; + } + + protected function setPersisted(): void { + $this->persisted = true; + } + + abstract function clone(): AbstractEntity; + + public function isSame(AbstractEntity $other): bool { + if(get_class($this) !== get_class($other)) + { + throw new InvalidComponentType(); + } + + return $this->isSameImpl($other); + } + public function equals(AbstractEntity $other): bool { + if(get_class($this) !== get_class($other)) + { + throw new InvalidComponentType(); + } + + return $this->equalsImpl($other); + } + + abstract protected function isSameImpl(AbstractEntity $other): bool; + abstract protected function equalsImpl(AbstractEntity $other): bool; +} diff --git a/lib/Entity/impl/CategoryEntityImpl.php b/lib/Entity/impl/CategoryEntityImpl.php new file mode 100644 index 000000000..708c8e535 --- /dev/null +++ b/lib/Entity/impl/CategoryEntityImpl.php @@ -0,0 +1,82 @@ +wrapper = $wrapper; + } + + /** + * Get the name of the category + * @return string The name of the category + */ + public function getName(): string { + return $this->name; + } + + /** + * Set the name of the category. + * @param string $name The new name of the category + */ + public function setName(string $name): void { + $this->name = $name; + } + + public function persist(): void { + $this->wrapper->store($this); + } + + public function clone(): CategoryEntity + { + $ret = $this->wrapper->createEntity(); + $ret->setName($this->name); + return $ret; + } + + public function remove(): void + { + $this->wrapper->remove($this); + } + + public function reload(): void + { + // FIXME + } + + protected function equalsImpl(AbstractEntity $other): bool + { + // FIXME + } + + protected function isSameImpl(AbstractEntity $other): bool + { + // FIXME + } + + public function getRecipes(): array + { + // FIXME + } + + +} diff --git a/lib/Entity/CategoryMappingEntity.php b/lib/Entity/impl/CategoryMappingEntityImpl.php similarity index 67% rename from lib/Entity/CategoryMappingEntity.php rename to lib/Entity/impl/CategoryMappingEntityImpl.php index 7810cd3de..7e99a3066 100644 --- a/lib/Entity/CategoryMappingEntity.php +++ b/lib/Entity/impl/CategoryMappingEntityImpl.php @@ -1,10 +1,10 @@ wrapper->store($this); + $this->setPersisted(); } /** @@ -65,10 +66,35 @@ public function setCategory($category) { $this->category = $category; } + + public function clone(): CategoryMappingEntityImpl + { + $ret = $this->wrapper->createEntity(); + $ret->setCategory($this->category); + $ret->setRecipe($this->recipe); + if($this->isPersisted()) + { + $ret->setPersisted(); + } + return $ret; + } public function remove(): void { $this->wrapper->remove($this); } + + protected function equalsImpl(AbstractEntity $other): bool + { + // FIXME + } + + protected function isSameImpl(AbstractEntity $other): bool + { + // FIXME + } + + + } diff --git a/lib/Entity/impl/KeywordEntityImpl.php b/lib/Entity/impl/KeywordEntityImpl.php new file mode 100644 index 000000000..0e42550fa --- /dev/null +++ b/lib/Entity/impl/KeywordEntityImpl.php @@ -0,0 +1,79 @@ +wrapper = $wrapper; + } + + /** + * Get the name of the keyword + * @return string The name of the keyword + */ + public function getName(): string { + return $this->name; + } + + /** + * Set the name of the keyword + * @param string $name The name of the keyword + */ + public function setName($name) { + $this->name = $name; + } + + public function persist(): void { + $this->wrapper->store($this); + } + + public function reload(): void + { + // FIXME + } + + public function clone(): AbstractEntity + { + // FIXME + } + + protected function equalsImpl(AbstractEntity $other): bool + { + // FIXME + } + + protected function isSameImpl(AbstractEntity $other): bool + { + // FIXME + } + + public function getRecipes(): array + { + // FIXME + } + + public function remove(): void + { + // FIXME + } + +} diff --git a/lib/Entity/KeywordMappingEntity.php b/lib/Entity/impl/KeywordMappingEntityImpl.php similarity index 74% rename from lib/Entity/KeywordMappingEntity.php rename to lib/Entity/impl/KeywordMappingEntityImpl.php index 8474cad4e..c180fb46c 100644 --- a/lib/Entity/KeywordMappingEntity.php +++ b/lib/Entity/impl/KeywordMappingEntityImpl.php @@ -1,18 +1,18 @@ keyword = $keyword; } + + public function clone(): AbstractEntity + { + // FIXME + } + + protected function equalsImpl(AbstractEntity $other): bool + { + // FIXME + } + + protected function isSameImpl(AbstractEntity $other): bool + { + // FIXME + } + } diff --git a/lib/Entity/impl/RecipeEntityImpl.php b/lib/Entity/impl/RecipeEntityImpl.php new file mode 100644 index 000000000..b09a17f61 --- /dev/null +++ b/lib/Entity/impl/RecipeEntityImpl.php @@ -0,0 +1,173 @@ +wrapper = $wrapper; + } + + public function persist(): void { + $this->wrapper->store($this); + } + + /** + * Obtain the id of the recipe in the database. + * @return number The id of the recipe in the database + */ + public function getId(): int { + return $this->id; + } + + /** + * Get the name of the recipe + * @return string The name of the recipe + */ + public function getName(): string { + return $this->name; + } + + /** + * Set the id of the recipe in the database. + * Caution: This should be done only by the corresponding wrapper after inserting into the DB + * @param number $id The new id + */ + public function setId($id): void { + $this->id = $id; + } + + /** + * Set the name of the recipe + * @param string $name The new name of the recipe + */ + public function setName($name): void { + $this->name = $name; + } + + /** + * @return \OCA\Cookbook\Entity\CategoryEntity + */ + public function getCategory() + { + if(is_null($this->categoryMapper)) { + return null; + } + else { + return $this->categoryMapper->getCategory(); + } + } + + /** + * @return array + */ +// public function getKeywords() +// { +// return array_map(function (KeywordMappingEntity $mapping){ +// return $mapping->getKeyword(); +// }, $this->keywordMappers); +// } + + /** + * @param \OCA\Cookbook\Entity\CategoryEntity $category + */ + public function setCategory($category) + { + if(is_null($category)) + { + if(! is_null($this->categoryMapper)) + { + $this->categoryMapper->remove(); // XXX Good? + } + } + else { + if(is_null($this->categoryMapper)){ + $this->categoryMapper = $this->wrapper->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->createEntity(); + } + + $this->categoryMapper->setRecipe($this); + $this->categoryMapper->setCategory($category); + + $category->persist(); + $this->categoryMapper->persist(); // XXX Good? + } + } + + /** + * @param array $keywords + */ +// public function setKeywords($keywords) +// { +// // $this->keywords = $keywords; +// $oldKeywords = $this->getKeywords(); + + +// } + + public function clone(): RecipeEntity + { + // FIXME + } + + public function remove(): void + { + $this->wrapper->remove($this); + } + + protected function equalsImpl(AbstractEntity $other): bool + { + // FIXME + } + + public function getKeywords(): array + { + // FIXME + } + + public function removeKeyword(KeywordEntity $keyword): void + { + // FIXME + } + + public function reload(): void + { + // FIXME + } + + public function addKeyword(KeywordEntity $keyword): void + { + // FIXME + } + + protected function isSameImpl(AbstractEntity $other): bool + { + // FIXME + } + + + +} diff --git a/lib/Exception/InvalidComparisionException.php b/lib/Exception/InvalidComparisionException.php new file mode 100644 index 000000000..db035a10d --- /dev/null +++ b/lib/Exception/InvalidComparisionException.php @@ -0,0 +1,17 @@ + Date: Sun, 22 Nov 2020 15:16:53 +0100 Subject: [PATCH 26/38] Added some implementation to Entities Signed-off-by: Christian Wolf --- lib/Entity/impl/CategoryEntityImpl.php | 11 +- lib/Entity/impl/CategoryMappingEntityImpl.php | 8 +- lib/Entity/impl/KeywordEntityImpl.php | 19 +- lib/Entity/impl/KeywordMappingEntityImpl.php | 19 +- lib/Entity/impl/RecipeEntityImpl.php | 194 +++++++++++++----- 5 files changed, 181 insertions(+), 70 deletions(-) diff --git a/lib/Entity/impl/CategoryEntityImpl.php b/lib/Entity/impl/CategoryEntityImpl.php index 708c8e535..bc390ec6d 100644 --- a/lib/Entity/impl/CategoryEntityImpl.php +++ b/lib/Entity/impl/CategoryEntityImpl.php @@ -44,12 +44,17 @@ public function setName(string $name): void { public function persist(): void { $this->wrapper->store($this); + $this->setPersisted(); } public function clone(): CategoryEntity { $ret = $this->wrapper->createEntity(); $ret->setName($this->name); + if($this->isPersisted()) + { + $ret->setPersisted(); + } return $ret; } @@ -65,17 +70,17 @@ public function reload(): void protected function equalsImpl(AbstractEntity $other): bool { - // FIXME + return $this->name === $other->name; } protected function isSameImpl(AbstractEntity $other): bool { - // FIXME + return $this->name === $other->name; } public function getRecipes(): array { - // FIXME + return $this->wrapper->getRecipes($this); } diff --git a/lib/Entity/impl/CategoryMappingEntityImpl.php b/lib/Entity/impl/CategoryMappingEntityImpl.php index 7e99a3066..257b74a1c 100644 --- a/lib/Entity/impl/CategoryMappingEntityImpl.php +++ b/lib/Entity/impl/CategoryMappingEntityImpl.php @@ -5,6 +5,7 @@ use OCA\Cookbook\Db\CategoryMappingDbWrapper; class CategoryMappingEntityImpl extends AbstractEntity { + // XXX Add interface? /** * @var CategoryMappingDbWrapper @@ -70,12 +71,15 @@ public function setCategory($category) public function clone(): CategoryMappingEntityImpl { $ret = $this->wrapper->createEntity(); + $ret->setCategory($this->category); $ret->setRecipe($this->recipe); + if($this->isPersisted()) { $ret->setPersisted(); } + return $ret; } @@ -86,12 +90,12 @@ public function remove(): void protected function equalsImpl(AbstractEntity $other): bool { - // FIXME + return $this->category->isSame($other->category) && $this->recipe->isSame($other->recipe); } protected function isSameImpl(AbstractEntity $other): bool { - // FIXME + return $this->equalsImpl($other); } diff --git a/lib/Entity/impl/KeywordEntityImpl.php b/lib/Entity/impl/KeywordEntityImpl.php index 0e42550fa..fa7565053 100644 --- a/lib/Entity/impl/KeywordEntityImpl.php +++ b/lib/Entity/impl/KeywordEntityImpl.php @@ -44,6 +44,7 @@ public function setName($name) { public function persist(): void { $this->wrapper->store($this); + $this->setPersisted(); } public function reload(): void @@ -51,29 +52,35 @@ public function reload(): void // FIXME } - public function clone(): AbstractEntity + public function clone(): KeywordEntityImpl { - // FIXME + $ret = $this->wrapper->createEntity(); + $ret->name = $this->name; + if($this->isPersisted()) + { + $ret->setPersisted(); + } + return $ret; } protected function equalsImpl(AbstractEntity $other): bool { - // FIXME + return $this->name === $other->name; } protected function isSameImpl(AbstractEntity $other): bool { - // FIXME + return $this->name === $other->name; } public function getRecipes(): array { - // FIXME + return $this->wrapper->getRecipes($this); } public function remove(): void { - // FIXME + $this->wrapper->remove($this); } } diff --git a/lib/Entity/impl/KeywordMappingEntityImpl.php b/lib/Entity/impl/KeywordMappingEntityImpl.php index c180fb46c..bfd4c5583 100644 --- a/lib/Entity/impl/KeywordMappingEntityImpl.php +++ b/lib/Entity/impl/KeywordMappingEntityImpl.php @@ -5,6 +5,7 @@ use OCA\Cookbook\Db\KeywordMappingDbWrapper; class KeywordMappingEntityImpl extends AbstractEntity { + // XXX Add interface? /** * @var RecipeEntityImpl @@ -27,7 +28,9 @@ public function __construct(KeywordMappingDbWrapper $wrapper) { public function persist(): void { $this->wrapper->store($this); + $this->setPersisted(); } + /** * @return \OCA\Cookbook\Entity\RecipeEntity */ @@ -62,17 +65,27 @@ public function setKeyword($keyword) public function clone(): AbstractEntity { - // FIXME + $ret = $this->wrapper->createEntity(); + + $ret->setKeyword($this->keyword); + $ret->setRecipe($this->recipe); + + if($this->isPersisted()) + { + $ret->setPersisted(); + } + + return $ret; } protected function equalsImpl(AbstractEntity $other): bool { - // FIXME + return $this->keyword->isSame($other->keyword) && $this->recipe->isSame($other->recipe); } protected function isSameImpl(AbstractEntity $other): bool { - // FIXME + return $this->equalsImpl($other); } } diff --git a/lib/Entity/impl/RecipeEntityImpl.php b/lib/Entity/impl/RecipeEntityImpl.php index b09a17f61..7cb119827 100644 --- a/lib/Entity/impl/RecipeEntityImpl.php +++ b/lib/Entity/impl/RecipeEntityImpl.php @@ -5,6 +5,8 @@ use OCA\Cookbook\Db\RecipeDbWrapper; use OCA\Cookbook\Entity\KeywordEntity; use OCA\Cookbook\Entity\RecipeEntity; +use OCA\Cookbook\Exception\EntityNotFoundException; +use OCP\IL10N; class RecipeEntityImpl extends AbstractEntity implements RecipeEntity { @@ -13,6 +15,11 @@ class RecipeEntityImpl extends AbstractEntity implements RecipeEntity { */ private $wrapper; + /** + * @var IL10N + */ + private $l; + /** * @var int */ @@ -23,17 +30,41 @@ class RecipeEntityImpl extends AbstractEntity implements RecipeEntity { */ private $name; + /** + * @var CategoryEntityImpl + */ + private $newCategory; + + /** + * @var KeywordEntity[] + */ + private $newKeywords; + + /** + * @var KeywordEntity[] + */ + private $removedKeywords; + /** * Creat a new entity object * Do not use this constructor directly but create new entities from the corresponding wrapper. * @param RecipeDbWrapper $wrapper The wrapper to use for DB access */ - public function __construct(RecipeDbWrapper $wrapper){ + public function __construct(RecipeDbWrapper $wrapper, IL10N $l){ $this->wrapper = $wrapper; + + $this->newCategory = null; + $this->newKeywords = []; + $this->removedKeywords = []; } public function persist(): void { $this->wrapper->store($this); + $this->setPersisted(); + + $this->newCategory = null; + $this->newKeywords = []; + $this->removedKeywords = []; } /** @@ -74,65 +105,22 @@ public function setName($name): void { */ public function getCategory() { - if(is_null($this->categoryMapper)) { - return null; - } - else { - return $this->categoryMapper->getCategory(); + if(! is_null($this->newCategory)) + { + return $this->newCategory; } + + return $this->wrapper->getCategory($this); } - /** - * @return array - */ -// public function getKeywords() -// { -// return array_map(function (KeywordMappingEntity $mapping){ -// return $mapping->getKeyword(); -// }, $this->keywordMappers); -// } - /** * @param \OCA\Cookbook\Entity\CategoryEntity $category */ public function setCategory($category) { - if(is_null($category)) - { - if(! is_null($this->categoryMapper)) - { - $this->categoryMapper->remove(); // XXX Good? - } - } - else { - if(is_null($this->categoryMapper)){ - $this->categoryMapper = $this->wrapper->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->createEntity(); - } - - $this->categoryMapper->setRecipe($this); - $this->categoryMapper->setCategory($category); - - $category->persist(); - $this->categoryMapper->persist(); // XXX Good? - } + $this->newCategory = $category; } - /** - * @param array $keywords - */ -// public function setKeywords($keywords) -// { -// // $this->keywords = $keywords; -// $oldKeywords = $this->getKeywords(); - - -// } - - public function clone(): RecipeEntity - { - // FIXME - } - public function remove(): void { $this->wrapper->remove($this); @@ -140,17 +128,59 @@ public function remove(): void protected function equalsImpl(AbstractEntity $other): bool { - // FIXME + if(! $this->isSame($other)) + { + return false; + } + + // Compare internal structures + if($this->name !== $other->name) + { + return false; + } + + // FIXME Compare references as well? } public function getKeywords(): array { - // FIXME + $keywords = $this->wrapper->getKeywords($this); + + // Filter out all removed keywords + foreach($this->removedKeywords as $rkw) { + $keywords = $this->filterOutKeyword($keywords, $rkw); + } + + // Add newly added keywords + $keywords = array_merge($keywords, $this->newKeywords); + + return $keywords; } public function removeKeyword(KeywordEntity $keyword): void { - // FIXME + if($this->isKeywordInList($keyword, $this->removedKeywords)) + { + // The keyword is already mentioned as to be removed. + return; + } + + if($this->isKeywordInList($keyword, $this->newKeywords)){ + // We recently added the keyword. Remove it simply from the adding list + $this->newKeywords = $this->filterOutKeyword($this->newKeywords, $keyword); + } + else { + $dbKeywords = $this->wrapper->getKeywords($this); + + if($this->isKeywordInList($keyword, $dbKeywords)) { + // It is present in the DB, remove it + $this->removedKeywords[] = $keyword; + } + else { + // We have the keyword not in the DB. + throw new EntityNotFoundException($this->l->t('Cannot remove a keyword not been assigned to recipe.')); + } + } } public function reload(): void @@ -160,14 +190,66 @@ public function reload(): void public function addKeyword(KeywordEntity $keyword): void { - // FIXME + if($this->isKeywordInList($keyword, $this->removedKeywords)){ + // If we should remove the keyword previously, just drop the removal + $this->removedKeywords = $this->filterOutKeyword($this->removedKeywords, $keyword); + return; + } + + $dbKeywords = $this->wrapper->getKeywords($this); + if($this->isKeywordInList($keyword, $dbKeywords)){ +// throw new InvalidDbStateException($this->l->t('Cannot add a keyword multiple times.')); + return; + // XXX Better silent ignorance or exception + } + + $this->newKeywords[] = $keyword; } protected function isSameImpl(AbstractEntity $other): bool { - // FIXME + return $this->id == $other->id; } - + public function clone(): RecipeEntity + { + $ret = $this->wrapper->createEntity(); + + $ret->id = $this->id; + $ret->name = $this->name; + $ret->newCategory = $this->newCategory; + $ret->newKeywords = $this->newKeywords; + $ret->removedKeywords = $this->removedKeywords; + + if($this->isPersisted()) + { + $ret->setPersisted(); + } + + return $ret; + } + + /** + * @param KeywordEntityImpl[] $keywords + * @param KeywordEntityImpl $list + * @return KeywordEntityImpl[] + */ + private function filterOutKeyword(array $list, KeywordEntityImpl $keyword): array { + return array_filter($list, function ($kw) use ($keyword) { + if($keyword->equals($kw)) + { + return false; + } + return true; + }); + } + + private function isKeywordInList(KeywordEntityImpl $keyword, array $list): bool { + $oldNumber = count($list); + $list = $this->filterOutKeyword($list, $keyword); + $newNumber = count($list); + + return $oldNumber != $newNumber; + } } From 9d8ab8406a2c5ba6f380e67a481bb4f20b2d98f2 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 17:46:45 +0100 Subject: [PATCH 27/38] Implemented most of the DB backend, foreign links are not yet managed Signed-off-by: Christian Wolf --- lib/Db/AbstractDbWrapper.php | 4 +- lib/Db/CategoryDbWrapper.php | 45 +++++- lib/Db/CategoryMappingDbWrapper.php | 115 +++++++++------ lib/Db/KeywordDbWrapper.php | 43 +++++- lib/Db/KeywordMappingDbWrapper.php | 132 +++++++++++++---- lib/Db/RecipeDbWrapper.php | 139 ++++++++++++++---- lib/Entity/impl/CategoryMappingEntityImpl.php | 16 +- lib/Entity/impl/KeywordMappingEntityImpl.php | 18 +-- 8 files changed, 383 insertions(+), 129 deletions(-) diff --git a/lib/Db/AbstractDbWrapper.php b/lib/Db/AbstractDbWrapper.php index 2e2cbd219..ed2ca1f65 100644 --- a/lib/Db/AbstractDbWrapper.php +++ b/lib/Db/AbstractDbWrapper.php @@ -46,7 +46,7 @@ abstract protected function fetchDatabase(): array; * * @return array The entities in the database. */ - protected function getEntries(): array { + public function getEntries(): array { if (! $this->initialized) { $this->reloadCache(); } @@ -103,6 +103,4 @@ public function getWrapperServiceLocator() return $this->wrapperLocator; } - - } diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php index e6faf362d..e6a7e9eb0 100644 --- a/lib/Db/CategoryDbWrapper.php +++ b/lib/Db/CategoryDbWrapper.php @@ -2,11 +2,16 @@ namespace OCA\Cookbook\Db; -use OCP\IDBConnection; use OCA\Cookbook\Entity\CategoryEntity; +use OCA\Cookbook\Entity\impl\CategoryEntityImpl; +use OCA\Cookbook\Entity\impl\CategoryMappingEntityImpl; +use OCA\Cookbook\Entity\impl\RecipeEntityImpl; +use OCP\IDBConnection; class CategoryDbWrapper extends AbstractDbWrapper { + private const CATEGORIES = 'cookbook_categories'; + /** * @var string */ @@ -27,7 +32,7 @@ protected function fetchDatabase(): array { $qb = $this->db->getQueryBuilder(); $qb ->select('name') - ->from('cookbook_categories') + ->from(self::CATEGORIES) ->where('user_id = :uid') ->groupBy('name') ->orderBy('name'); @@ -35,9 +40,10 @@ protected function fetchDatabase(): array { $qb->setParameter('uid', $this->userId); $res = $qb->execute(); + $ret = []; while ($row = $res->fetch()) { - $entity = new CategoryEntity($this); + $entity = $this->createEntity(); $entity->setName($row['name']); $ret[] = $entity; @@ -53,16 +59,45 @@ protected function fetchDatabase(): array { * @param CategoryEntity $category The entity to store */ public function store(CategoryEntity $category): void { + // We do not need to store anything here. The categories are just virtually generated. } /** * Create a new entity and reegister it with this wrapper * @return CategoryEntity The new entity */ - public function createEntity(): CategoryEntity { - return new CategoryEntity($this); + public function createEntity(): CategoryEntityImpl { + return new CategoryEntityImpl($this); } public function remove(CategoryEntity $category) { + // Remove all foreign links + $recipes = $category->getRecipes(); + + foreach($recipes as $r){ + /** + * @var RecipeEntity $r + */ + $r->setCategory(null); + } + + // We cannot do anything as the categories are purely virtual. + } + + /** + * @param CategoryEntity $category + * @return RecipeEntityImpl[] The recipes associated with the category + */ + public function getRecipes(CategoryEntity $category) : array { + /** + * @var CategoryMappingEntityImpl[] $mappings + */ + $mappings = $this->wrapperLocator->getCategoryMappingDbWrapper()->getEntries(); + $mappings = array_filter($mappings, function (CategoryMappingEntityImpl $mapping) use ($category) { + return $mapping->getCategory()->isSame($category); + }); + return array_map(function (CategoryMappingEntityImpl $mapping) { + return $mapping->getRecipe(); + }, $mappings); } } diff --git a/lib/Db/CategoryMappingDbWrapper.php b/lib/Db/CategoryMappingDbWrapper.php index 3a0183b11..998880621 100644 --- a/lib/Db/CategoryMappingDbWrapper.php +++ b/lib/Db/CategoryMappingDbWrapper.php @@ -2,9 +2,9 @@ namespace OCA\Cookbook\Db; -use OCP\IDBConnection; -use OCA\Cookbook\Entity\CategoryMappingEntity; +use OCA\Cookbook\Entity\impl\CategoryMappingEntityImpl; use OCA\Cookbook\Exception\InvalidDbStateException; +use OCP\IDBConnection; use OCP\IL10N; class CategoryMappingDbWrapper extends AbstractDbWrapper { @@ -33,6 +33,14 @@ public function __construct(string $UserId, IDBConnection $db, IL10N $l) { $this->l = $l; } + /** + * Create a new entity and reegister it with this wrapper + * @return CategoryMappingEntityImpl The new entity + */ + public function createEntity(): CategoryMappingEntityImpl { + return new CategoryMappingEntityImpl($this); + } + protected function fetchDatabase(): array { // FIXME $qb = $this->db->getQueryBuilder(); @@ -47,9 +55,15 @@ protected function fetchDatabase(): array { $ret = []; while ($row = $res->fetch()) { + $recipe = $this->getWrapperServiceLocator()->getRecipeDbWrapper()->getRecipeById($row['recipe_id']); + + $category = $this->getWrapperServiceLocator()->getCategoryDbWrapper()->createEntity(); + $category->setName($row['name']); + $entity = $this->createEntity(); - $entity->setName($row['name']); - $entity->setRecipe($this->getWrapperServiceLocator()->getRecipeDbWrapper()->getRecipeById($row['recipe_id'])); + + $entity->setCategory($category); + $entity->setRecipe($recipe); $ret[] = $entity; } @@ -61,58 +75,71 @@ protected function fetchDatabase(): array { /** * Store a single entity back to the database - * @param CategoryMappingEntity $category The entity to store + * @param CategoryMappingEntityImpl $category The entity to store */ - public function store(CategoryMappingEntity $mapping): void { - // FIXME - if($mapping->getRecipe()->getId() == -1) + public function store(CategoryMappingEntityImpl $mapping): void { + if(! $mapping->getRecipe()->isPersisted()) { - // Recipe was not saved yet. throw new InvalidDbStateException($this->l->t('The recipe was not stored to the database yet. No id known.')); } - $foundMapping = array_filter($this->getEntries(), function (CategoryMappingEntity $entity) use ($mapping) { - return $entity === $mapping; - }); - - $qb = $this->db->getQueryBuilder(); - - if(count($foundMapping) > 0) { - // We need to update an existing entry - $qb ->update(self::CATEGORIES) - ->set('name', $mapping->getCategory()->getName()) - ->where('recipe_id = :rid', 'user_id = :uid'); - $qb->setParameters([ - 'rid' => $mapping->getRecipe()->getId(), - 'uid' => $this->userId - ]); + if($mapping->isPersisted()){ + $this->update($mapping); } else { - // We need to create a new entry - $qb ->insert(self::CATEGORIES) - ->values([ - 'recipe_id' => $mapping->getRecipe()->getId(), - 'user_id' => $this->userId, - 'name' => $mapping->getCategory()->getName() - ]); + $this->storeNew($mapping); } + } + + private function storeNew(CategoryMappingEntityImpl $mapping): void { + $qb = $this->db->getQueryBuilder(); + + $cache = $this->getEntries(); + $qb ->insert(self::CATEGORIES) + ->values([ + 'recipe_id' => $mapping->getRecipe()->getId(), + 'user_id' => $this->userId, + 'name' => $mapping->getCategory()->getName() + ]); $qb->execute(); - $this->invalidateCache(); - // TODO Changing of an object will change the cache as well + + $cache[] = $mapping->clone(); + $this->setEntites($cache); } - /** - * Create a new entity and reegister it with this wrapper - * @return CategoryMappingEntity The new entity - */ - public function createEntity(): CategoryMappingEntity { - return new CategoryMappingEntity($this); + private function update(CategoryMappingEntityImpl $mapping): void { + $qb = $this->db->getQueryBuilder(); + + $cache = $this->getEntries(); + + $qb ->update(self::CATEGORIES) + ->set('name', $mapping->getCategory()->getName()) + ->where('recipe_id = :rid', 'user_id = :uid'); + $qb->setParameters([ + 'rid' => $mapping->getRecipe()->getId(), + 'uid' => $this->userId + ]); + $qb->execute(); + + $cache = array_map(function (CategoryMappingEntityImpl $m) use ($mapping) { + if($m->isSame($mapping)) + { + // We need to update + return $mapping->clone(); + } + else { + return $m; + } + }, $cache); + $this->setEntites($cache); } - public function remove(CategoryMappingEntity $mapper): void { + public function remove(CategoryMappingEntityImpl $mapper): void { $qb = $this->db->getQueryBuilder(); + $cache = $this->getEntries(); + $qb ->delete(self::CATEGORIES) ->where('recipe_id = :rid', 'user_id = :uid'); $qb->setParameters([ @@ -121,11 +148,11 @@ public function remove(CategoryMappingEntity $mapper): void { ]); $qb->execute(); - $this->invalidateCache(); -// $this->setEntites(array_filter($this->getEntries(), function (CategoryMappingEntity $entity) use ($mapper) { -// return $entity !== $mapper; -// })); + $cache = array_filter($cache, function (CategoryMappingEntityImpl $m) use ($mapper) { + return ! $m->isSame($mapper); + }); + $this->setEntites($cache); // XXX Remove CategoryEntity completely? } diff --git a/lib/Db/KeywordDbWrapper.php b/lib/Db/KeywordDbWrapper.php index 8223aaecf..08dbc9775 100644 --- a/lib/Db/KeywordDbWrapper.php +++ b/lib/Db/KeywordDbWrapper.php @@ -4,9 +4,14 @@ use OCP\IDBConnection; use OCA\Cookbook\Entity\KeywordEntity; +use OCA\Cookbook\Entity\impl\KeywordEntityImpl; +use OCA\Cookbook\Entity\impl\KeywordMappingEntityImpl; +use OCA\Cookbook\Entity\impl\RecipeEntityImpl; class KeywordDbWrapper extends AbstractDbWrapper { + private const KEYWORDS = 'cookbook_keywords'; + /** * @var string */ @@ -27,7 +32,7 @@ protected function fetchDatabase(): array { $qb = $this->db->getQueryBuilder(); $qb ->select('name') - ->from('cookbook_keywords') + ->from(self::KEYWORDS) ->where('user_id = :uid') ->groupBy('name') ->orderBy('name'); @@ -37,7 +42,7 @@ protected function fetchDatabase(): array { $res = $qb->execute(); $arr = []; while ($row = $res->fetch()) { - $entity = new KeywordEntity($this); + $entity = $this->createEntity(); $entity->setName($row['name']); $arr[] = $entity; @@ -53,13 +58,43 @@ protected function fetchDatabase(): array { * @param KeywordEntity $keyword The entity to store */ public function store(KeywordEntity $keyword): void { + // We do not need to store anything here. The keywords are just virtually generated. } /** * Create a new entity and reegister it with this wrapper * @return KeywordEntity The new entity */ - public function createEntity(): KeywordEntity { - return new KeywordEntity($this); + public function createEntity(): KeywordEntityImpl { + return new KeywordEntityImpl($this); + } + + /** + * @param KeywordEntity $keyword + * @return RecipeEntityImpl[] The recipes associated with the keyword + */ + public function getRecipes(KeywordEntity $keyword): array{ + $mappings = $this->wrapperLocator->getKeywordMappingDbWrapper()->getEntries(); + $mappings = array_filter($mappings, function (KeywordMappingEntityImpl $mapping) use ($keyword) { + return $mapping->getKeyword()->isSame($keyword); + }); + return array_map(function (KeywordMappingEntityImpl $mapping) { + return $mapping->getRecipe(); + }, $mappings); + } + + public function remove(KeywordEntity $keyword): void + { + // Remove all foreign links + $recipes = $keyword->getRecipes(); + + foreach($recipes as $r){ + /** + * @var RecipeEntity $r + */ + $r->removeKeyword($keyword); + } + + // We cannot do anything as the categories are purely virtual. } } diff --git a/lib/Db/KeywordMappingDbWrapper.php b/lib/Db/KeywordMappingDbWrapper.php index 9f329a6d9..4db928c5e 100644 --- a/lib/Db/KeywordMappingDbWrapper.php +++ b/lib/Db/KeywordMappingDbWrapper.php @@ -2,11 +2,14 @@ namespace OCA\Cookbook\Db; +use OCA\Cookbook\Entity\impl\KeywordMappingEntityImpl; use OCP\IDBConnection; -use OCA\Cookbook\Entity\KeywordMappingEntity; +use OCA\Cookbook\Exception\InvalidDbStateException; class KeywordMappingDbWrapper extends AbstractDbWrapper { + private const KEYWORDS = 'cookbook_keywords'; + /** * @var string */ @@ -23,43 +26,122 @@ public function __construct(string $UserId, IDBConnection $db) { $this->db = $db; } + /** + * Create a new entity and reegister it with this wrapper + * @return KeywordMappingEntityImpl The new entity + */ + public function createEntity(): KeywordMappingEntityImpl { + return new KeywordMappingEntityImpl($this); + } + protected function fetchDatabase(): array { -// $qb = $this->db->getQueryBuilder(); + $qb = $this->db->getQueryBuilder(); -// $qb ->select('name') -// ->from('cookbook_categories') -// ->where('user_id = :uid') -// ->groupBy('name') -// ->orderBy('name'); + $qb ->select('name', 'recipe_id') + ->from(self::KEYWORDS) + ->where('user_id = :uid'); -// $qb->setParameter('uid', $this->userId); + $qb->setParameter('uid', $this->userId); -// $res = $qb->execute(); -// $ret = []; -// while ($row = $res->fetch()) { -// $entity = new CategoryEntity($this); -// $entity->setName($row['name']); + $res = $qb->execute(); + $ret = []; + + while ($row = $res->fetch()) { + $recipe = $this->getWrapperServiceLocator()->getRecipeDbWrapper()->getRecipeById($row['recipe_id']); + + $keyword = $this->getWrapperServiceLocator()->getKeywordDbWrapper()->createEntity(); + $keyword->setName($row['name']); -// $ret[] = $entity; -// } + $entity = $this->createEntity(); + + $entity->setRecipe($recipe); + $entity->setKeyword($keyword); + + $ret[] = $entity; + } -// $res->closeCursor(); + $res->closeCursor(); -// return $ret; + return $ret; } /** * Store a single entity back to the database - * @param KeywordMappingEntity $category The entity to store + * @param KeywordMappingEntityImpl $category The entity to store */ - public function store(KeywordMappingEntity $mapping): void { + public function store(KeywordMappingEntityImpl $mapping): void { + if(! $mapping->getRecipe()->isPersisted()){ + throw new InvalidDbStateException($this->l->t('The recipe was not stored to the database yet. No id known.')); + } + + if($mapping->isPersisted()) + { + $this->update($mapping); + } + else { + $this->storeNew($mapping); + } } - /** - * Create a new entity and reegister it with this wrapper - * @return KeywordMappingEntity The new entity - */ - public function createEntity(): KeywordMappingEntity { - return new KeywordMappingEntity($this); + private function storeNew(KeywordMappingEntityImpl $mapping): void { + $qb = $this->db->getQueryBuilder(); + + $cache = $this->getEntries(); + + $qb ->insert(self::KEYWORDS) + ->values([ + 'recipe_id' => $mapping->getRecipe()->getId(), + 'user_id' => $this->userId, + 'name' => $mapping->getKeyword()->getName() + ]); + + $qb->execute(); + + $cache[] = $mapping; + $this->setEntites($cache); + } + + private function update(KeywordMappingEntityImpl $mapping): void { + $qb = $this->db->getQueryBuilder(); + + $cache = $this->getEntries(); + + $qb ->update(self::KEYWORDS) + ->set('name', $mapping->getKeyword()->getName()) + ->where('recipe_id = :rid', 'user_id = :uid'); + $qb->setParameter('rid', $mapping->getRecipe()->getId()); + $qb->setParameter('uid', $this->user); + + $qb->execute(); + + $cache = array_map(function(KeywordMappingEntityImpl $m) use ($mapping) { + if($m->isSame($mapping)) + { + return $mapping; + } + else { + return $m; + } + }, $cache); + $this->setEntites($cache); } + + public function remove(KeywordMappingEntityImpl $mapping): void { + $qb = $this->db->getQueryBuilder(); + + $cache = $this->getEntries(); + + $qb ->delete(self::KEYWORDS) + ->where('recipe_id = :rid', 'user_id = :uid'); + $qb->setParameter('rid', $mapping->getRecipe()->getId()); + $qb->setParameter('uid', $this->userId); + + $qb->execute(); + + $cache = array_filter($cache, function(KeywordMappingEntityImpl $m) use ($mapping) { + return ! $m->isSame($mapping); + }); + $this->setEntites($cache); + } + } diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index 67e9b30f8..dce3695c1 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -7,6 +7,11 @@ use OCA\Cookbook\Exception\EntityNotFoundException; use OCP\IL10N; use OCA\Cookbook\Exception\InvalidDbStateException; +use OCA\Cookbook\Entity\impl\CategoryEntityImpl; +use OCA\Cookbook\Entity\impl\KeywordEntityImpl; +use OCA\Cookbook\Entity\impl\RecipeEntityImpl; +use OCA\Cookbook\Entity\impl\CategoryMappingEntityImpl; +use OCA\Cookbook\Entity\impl\KeywordMappingEntityImpl; class RecipeDbWrapper extends AbstractDbWrapper { @@ -35,7 +40,6 @@ public function __construct(string $UserId, IDBConnection $db, IL10N $l) { } protected function fetchDatabase(): array { - // FIXME $qb = $this->db->getQueryBuilder(); $qb ->select('id', 'name') @@ -48,7 +52,8 @@ protected function fetchDatabase(): array { while($row = $res->fetch()) { - $recipe = $this->createEntity(); // XXX Better create directly? + $recipe = $this->createEntity(); + $recipe->setName($row['name']); $recipe->setId($row['id']); @@ -58,56 +63,92 @@ protected function fetchDatabase(): array { return $ret; } - public function store(RecipeEntity $recipe): void { - // FIXME - - $qb = $this->db->getQueryBuilder(); - - if($recipe->getId() == -1) + public function createEntity(): RecipeEntityImpl { + $ret = new RecipeEntityImpl($this); + $ret->setId(-1); + return $ret; + } + + public function store(RecipeEntityImpl $recipe): void { + if($recipe->isPersisted()) { - $qb ->insert(self::NAMES) - ->values([ - 'name' => $recipe->getName(), - 'user_id' => $this->userId - ]); + $this->storeNew($recipe); } - else { - $qb ->update(self::NAMES) - ->set('name', ':name') - ->where('recipe_id = :rid'); - $qb->setParameter('rid', $recipe->getId()); - $qb->setParameter('name', $recipe->getName()); + else + { + $this->store($recipe); } + } + + private function storeNew(RecipeEntityImpl $recipe): void { + $qb = $this->db->getQueryBuilder(); + $cache = $this->getEntries(); + + $qb ->insert(self::NAMES) + ->values([ + 'name' => $recipe->getName(), + 'user_id' => $this->userId + ]); + $qb->execute(); - $this->invalidateCache(); + $recipe->setId($qb->getLastInsertId()); - if($recipe->getId() == -1){ - $recipe->setId($qb->getLastInsertId()); - } + $cache[] = $recipe->clone(); + $this->setEntites($cache); + + // FIXME Foreign eleemnts } - public function createEntity(): RecipeEntity { - $ret = new RecipeEntity($this); - $ret->setId(-1); - return $ret; + private function update(RecipeEntityImpl $recipe): void { + $qb = $this->db->getQueryBuilder(); + + $cache = $this->getEntries(); + + $qb ->update(self::NAMES) + ->set('name', ':name') + ->where('recipe_id = :rid'); + $qb->setParameter('rid', $recipe->getId()); + $qb->setParameter('name', $recipe->getName()); + + $qb->execute(); + + $cache = array_map(function(RecipeEntityImpl $r) use ($recipe) { + if($r->isSame($recipe)) + { + return $recipe; + } + else { + return $r; + } + }, $cache); + $this->setEntites($cache); + + // FIXME Foreign elemtns } - public function remove(RecipeEntity $recipe): void { - // FIXME - if($recipe->getId() == -1) + public function remove(RecipeEntityImpl $recipe): void { + if(! $recipe->isPersisted()) { throw new InvalidDbStateException($this->l->t('Cannot remove recipe that was not yet saved.')); } + // FIXME Remove all foreign elements + $qb = $this->db->getQueryBuilder(); + $cache = $this->getEntries(); + $qb ->delete(self::NAMES) ->where('recipe_id = :rid'); $qb->setParameter('rid', $recipe->getId()); $qb->execute(); - $this->invalidateCache(); + + $cache = array_filter($cache, function (RecipeEntityImpl $r) use ($recipe) { + return ! $r->isSame($recipe); + }); + $this->setEntites($cache); } public function getRecipeById(int $id): RecipeEntity { @@ -124,4 +165,40 @@ public function getRecipeById(int $id): RecipeEntity { throw new EntityNotFoundException($this->l->t('Recipe with id %d was not found.', $id)); } + + public function getCategory(RecipeEntity $recipe): ?CategoryEntityImpl { + $mappings = $this->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->getEntries(); + $mappings = array_filter($mappings, function (CategoryMappingEntityImpl $c) use ($recipe) { + return $c->getRecipe()->isSame($recipe); + }); + + if(count($mappings) == 0) + { + return null; + } + if(count($mappings) > 1) + { + throw new InvalidDbStateException($this->l->t('Multiple categopries for a single recipe found.')); + } + + return $mappings[0]->getCategory(); + } + + /** + * @param RecipeEntity $recipe + * @return KeywordEntityImpl[] + */ + public function getKeywords(RecipeEntity $recipe): array { + $mappings = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper()->getEntries(); + $mappings = array_filter($mappings, function (KeywordMappingEntityImpl $m) use ($recipe) { + return $m->getRecipe()->isSame($recipe); + }); + + $keywords = array_map(function(KeywordMappingEntityImpl $m) { + return $m->getKeyword(); + }, $mappings); + + return $keywords; + } + } diff --git a/lib/Entity/impl/CategoryMappingEntityImpl.php b/lib/Entity/impl/CategoryMappingEntityImpl.php index 257b74a1c..cc03425ca 100644 --- a/lib/Entity/impl/CategoryMappingEntityImpl.php +++ b/lib/Entity/impl/CategoryMappingEntityImpl.php @@ -37,33 +37,33 @@ public function persist(): void { } /** - * @return \OCA\Cookbook\Entity\RecipeEntity + * @return RecipeEntityImpl */ - public function getRecipe() + public function getRecipe(): RecipeEntityImpl { return $this->recipe; } /** - * @return \OCA\Cookbook\Entity\CategoryEntity + * @return CategoryEntityImpl */ - public function getCategory() + public function getCategory(): CategoryEntityImpl { return $this->category; } /** - * @param \OCA\Cookbook\Entity\RecipeEntity $recipe + * @param RecipeEntityImpl $recipe */ - public function setRecipe($recipe) + public function setRecipe(RecipeEntityImpl $recipe) { $this->recipe = $recipe; } /** - * @param \OCA\Cookbook\Entity\CategoryEntity $category + * @param CategoryEntityImpl $category */ - public function setCategory($category) + public function setCategory(CategoryEntityImpl $category) { $this->category = $category; } diff --git a/lib/Entity/impl/KeywordMappingEntityImpl.php b/lib/Entity/impl/KeywordMappingEntityImpl.php index bfd4c5583..9deb4a4b9 100644 --- a/lib/Entity/impl/KeywordMappingEntityImpl.php +++ b/lib/Entity/impl/KeywordMappingEntityImpl.php @@ -32,38 +32,38 @@ public function persist(): void { } /** - * @return \OCA\Cookbook\Entity\RecipeEntity + * @return RecipeEntityImpl */ - public function getRecipe() + public function getRecipe(): RecipeEntityImpl { return $this->recipe; } /** - * @return \OCA\Cookbook\Entity\KeywordEntity + * @return KeywordEntityImpl */ - public function getKeyword() + public function getKeyword(): KeywordEntityImpl { return $this->keyword; } /** - * @param \OCA\Cookbook\Entity\RecipeEntity $recipe + * @param RecipeEntityImpl $recipe */ - public function setRecipe($recipe) + public function setRecipe(RecipeEntityImpl $recipe) { $this->recipe = $recipe; } /** - * @param \OCA\Cookbook\Entity\KeywordEntity $keyword + * @param KeywordEntityImpl $keyword */ - public function setKeyword($keyword) + public function setKeyword(KeywordEntityImpl $keyword) { $this->keyword = $keyword; } - public function clone(): AbstractEntity + public function clone(): KeywordMappingEntityImpl { $ret = $this->wrapper->createEntity(); From 6304cbca0de5a5822217c34b8730592e548a4fb4 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 17:59:26 +0100 Subject: [PATCH 28/38] Extracted fetching of mappings into separate methods Signed-off-by: Christian Wolf --- lib/Db/RecipeDbWrapper.php | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index dce3695c1..c2ae5d2ea 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -167,21 +167,28 @@ public function getRecipeById(int $id): RecipeEntity { } public function getCategory(RecipeEntity $recipe): ?CategoryEntityImpl { - $mappings = $this->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->getEntries(); - $mappings = array_filter($mappings, function (CategoryMappingEntityImpl $c) use ($recipe) { - return $c->getRecipe()->isSame($recipe); - }); + $mappings = $this->getRecipeCategoryMappings($recipe); if(count($mappings) == 0) { return null; } + + return $mappings[0]->getCategory(); + } + + private function getRecipeCategoryMappings(RecipeEntityImpl $recipe): array { + $mappings = $this->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->getEntries(); + $mappings = array_filter($mappings, function (CategoryMappingEntityImpl $c) use ($recipe) { + return $c->getRecipe()->isSame($recipe); + }); + if(count($mappings) > 1) { throw new InvalidDbStateException($this->l->t('Multiple categopries for a single recipe found.')); } - return $mappings[0]->getCategory(); + return $mappings; } /** @@ -189,11 +196,7 @@ public function getCategory(RecipeEntity $recipe): ?CategoryEntityImpl { * @return KeywordEntityImpl[] */ public function getKeywords(RecipeEntity $recipe): array { - $mappings = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper()->getEntries(); - $mappings = array_filter($mappings, function (KeywordMappingEntityImpl $m) use ($recipe) { - return $m->getRecipe()->isSame($recipe); - }); - + $mappings = $this->getRecipeKeywordMappings($recipe); $keywords = array_map(function(KeywordMappingEntityImpl $m) { return $m->getKeyword(); }, $mappings); @@ -201,4 +204,13 @@ public function getKeywords(RecipeEntity $recipe): array { return $keywords; } + private function getRecipeKeywordMappings(RecipeEntityImpl $recipe): array { + $mappings = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper()->getEntries(); + $mappings = array_filter($mappings, function (KeywordMappingEntityImpl $m) use ($recipe) { + return $m->getRecipe()->isSame($recipe); + }); + + return $mappings; + } + } From 60d85a4673f5f8a4e4c0327cd85525e68bc37e09 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 19:06:17 +0100 Subject: [PATCH 29/38] Implemented all DB related wrappers, no tests carried out, no checks Signed-off-by: Christian Wolf --- lib/Db/RecipeDbWrapper.php | 102 ++++++++++++++++++- lib/Entity/impl/KeywordMappingEntityImpl.php | 4 + lib/Entity/impl/RecipeEntityImpl.php | 57 +++++++++-- 3 files changed, 151 insertions(+), 12 deletions(-) diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index c2ae5d2ea..165981fd9 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -94,10 +94,33 @@ private function storeNew(RecipeEntityImpl $recipe): void { $qb->execute(); $recipe->setId($qb->getLastInsertId()); + // Update cache $cache[] = $recipe->clone(); $this->setEntites($cache); - // FIXME Foreign eleemnts + // Set category mapping + $cat = $recipe->getCategory(); + $cat->persist(); + + $cm = $this->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->createEntity(); + $cm->setCategory($cat); + $cm->setRecipe($recipe); + $cm->persist(); + + // Set keyword setting + $keywordMapperWrapper = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper(); + foreach($recipe->getKeywords() as $keyword) + { + /** + * @var KeywordEntityImpl $keyword + */ + $keyword->persist(); + + $km = $keywordMapperWrapper->createEntity(); + $km->setRecipe($recipe); + $km->setKeyword($keyword); + $km->persist(); + } } private function update(RecipeEntityImpl $recipe): void { @@ -113,6 +136,7 @@ private function update(RecipeEntityImpl $recipe): void { $qb->execute(); + // Update cache $cache = array_map(function(RecipeEntityImpl $r) use ($recipe) { if($r->isSame($recipe)) { @@ -124,7 +148,64 @@ private function update(RecipeEntityImpl $recipe): void { }, $cache); $this->setEntites($cache); - // FIXME Foreign elemtns + // Update the category if needed + $newCategory = $recipe->getNewCategory(); + if($recipe->newCategoryWasSet() && ! is_null($newCategory)) + { + $oldMappings = $this->getRecipeCategoryMappings($recipe); + + $newCategory->persist(); + + if(count($oldMappings) == 0){ + // We need to insert a new Mapping + $mapping = $this->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->createEntity(); + $mapping->setCategory($newCategory); + $mapping->setRecipe($recipe); + $mapping->persist(); + } + else { + $mapping = $oldMappings[0]; + $mapping->setCategory($newCategory); + $mapping->persist(); + } + } + + // Update the keywords + $kwWrapper = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper(); + + foreach($recipe->getNewKeywords() as $kw) { + /** + * @var KeywordEntityImpl $kw + */ + $kw->persist(); + + $mapping = $kwWrapper->createEntity(); + $mapping->setRecipe($recipe); + $mapping->setKeyword($kw); + $mapping->persist(); + } + + $removedKeywords = $recipe->getRemovedKeywords(); + $currentKwMappings = $this->getRecipeKeywordMappings($recipe); + $removingMappings = array_filter($currentKwMappings, function(KeywordMappingEntityImpl $m) use ($removedKeywords){ + foreach($removedKeywords as $kw){ + /** + * @var KeywordEntityImpl $kw + */ + if($m->getKeyword()->isSame($kw)) + { + return true; + } + } + return false; + }); + + foreach ($removingMappings as $mapping){ + /** + * @var KeywordMappingEntityImpl $mapping + */ + $mapping->remove(); + } } public function remove(RecipeEntityImpl $recipe): void { @@ -133,7 +214,22 @@ public function remove(RecipeEntityImpl $recipe): void { throw new InvalidDbStateException($this->l->t('Cannot remove recipe that was not yet saved.')); } - // FIXME Remove all foreign elements + $catMappings = $this->getRecipeCategoryMappings($recipe); + foreach($catMappings as $catMapping) + { + /** + * @var CategoryMappingEntityImpl $catMapping + */ + $catMapping->remove(); + } + + $keywordMappings = $this->getRecipeKeywordMappings($recipe); + foreach($keywordMappings as $keywordMapping){ + /** + * @var KeywordMappingEntityImpl $keywordMapping + */ + $keywordMapping->remove(); + } $qb = $this->db->getQueryBuilder(); diff --git a/lib/Entity/impl/KeywordMappingEntityImpl.php b/lib/Entity/impl/KeywordMappingEntityImpl.php index 9deb4a4b9..d24b6335c 100644 --- a/lib/Entity/impl/KeywordMappingEntityImpl.php +++ b/lib/Entity/impl/KeywordMappingEntityImpl.php @@ -87,5 +87,9 @@ protected function isSameImpl(AbstractEntity $other): bool { return $this->equalsImpl($other); } + + public function remove(): void { + $this->wrapper->remove($this); + } } diff --git a/lib/Entity/impl/RecipeEntityImpl.php b/lib/Entity/impl/RecipeEntityImpl.php index 7cb119827..3ed06ebf0 100644 --- a/lib/Entity/impl/RecipeEntityImpl.php +++ b/lib/Entity/impl/RecipeEntityImpl.php @@ -3,6 +3,7 @@ namespace OCA\Cookbook\Entity\impl; use OCA\Cookbook\Db\RecipeDbWrapper; +use OCA\Cookbook\Entity\CategoryEntity; use OCA\Cookbook\Entity\KeywordEntity; use OCA\Cookbook\Entity\RecipeEntity; use OCA\Cookbook\Exception\EntityNotFoundException; @@ -35,6 +36,11 @@ class RecipeEntityImpl extends AbstractEntity implements RecipeEntity { */ private $newCategory; + /** + * @var bool + */ + private $setNewCategory; + /** * @var KeywordEntity[] */ @@ -54,6 +60,7 @@ public function __construct(RecipeDbWrapper $wrapper, IL10N $l){ $this->wrapper = $wrapper; $this->newCategory = null; + $this->setNewCategory = false; $this->newKeywords = []; $this->removedKeywords = []; } @@ -101,24 +108,20 @@ public function setName($name): void { } /** - * @return \OCA\Cookbook\Entity\CategoryEntity + * @return CategoryEntityImpl */ public function getCategory() { - if(! is_null($this->newCategory)) - { - return $this->newCategory; - } - - return $this->wrapper->getCategory($this); + return $this->setNewCategory ? $this->newCategory : $this->wrapper->getCategory($this); } /** - * @param \OCA\Cookbook\Entity\CategoryEntity $category + * @param CategoryEntity $category */ - public function setCategory($category) + public function setCategory(CategoryEntity $category) { $this->newCategory = $category; + $this->setNewCategory = true; } public function remove(): void @@ -251,5 +254,41 @@ private function isKeywordInList(KeywordEntityImpl $keyword, array $list): bool return $oldNumber != $newNumber; } + + /** + * @return ?CategoryEntityImpl + */ + public function getNewCategory(): ?CategoryEntityImpl + { + return $this->newCategory; + } + + /** + * @return KeywordEntityImpl[] + */ + public function getNewKeywords(): array + { + return $this->newKeywords; + } + + /** + * @return KeywordEntityImpl[] + */ + public function getRemovedKeywords(): array + { + return $this->removedKeywords; + } + + /** + * @return boolean + */ + public function newCategoryWasSet() + { + return $this->setNewCategory; + } + + + + } From 9884ca7baade00057594e34c140d85a15839f9ac Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 19:41:53 +0100 Subject: [PATCH 30/38] Added entry to changelog Signed-off-by: Christian Wolf --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index abcc1b395..ece2cf9b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## [Unreleased] +### Added +- Database abstraction added for simpler access + [#407](https://github.com/nextcloud/cookbook/pull/407) @christianlupus + ## 0.7.9 - 2021-01-15 ### Changed From 75d8b996f6830dd95cfb35b02c0f241294925c54 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 22 Nov 2020 19:43:44 +0100 Subject: [PATCH 31/38] Corrected style using code styler Signed-off-by: Christian Wolf --- lib/Db/AbstractDbWrapper.php | 12 +- lib/Db/CategoryDbWrapper.php | 3 +- lib/Db/CategoryMappingDbWrapper.php | 17 +-- lib/Db/KeywordDbWrapper.php | 8 +- lib/Db/KeywordMappingDbWrapper.php | 20 +-- lib/Db/RecipeDbWrapper.php | 64 ++++----- lib/Entity/impl/AbstractEntity.php | 11 +- lib/Entity/impl/CategoryEntityImpl.php | 23 +-- lib/Entity/impl/CategoryMappingEntityImpl.php | 57 +++----- lib/Entity/impl/KeywordEntityImpl.php | 22 +-- lib/Entity/impl/KeywordMappingEntityImpl.php | 79 +++++------ lib/Entity/impl/RecipeEntityImpl.php | 134 +++++++----------- lib/Exception/EntityNotFoundException.php | 18 +-- lib/Exception/InvalidComparisionException.php | 18 +-- lib/Exception/InvalidDbStateException.php | 2 +- 15 files changed, 190 insertions(+), 298 deletions(-) diff --git a/lib/Db/AbstractDbWrapper.php b/lib/Db/AbstractDbWrapper.php index ed2ca1f65..7cbf84758 100644 --- a/lib/Db/AbstractDbWrapper.php +++ b/lib/Db/AbstractDbWrapper.php @@ -96,11 +96,9 @@ public function setWrapperServiceLocator(DbWrapperServiceProvider $locator) { /** * Get the central service locator for registering of all wrappers - * @return DbWrapperServiceProvider The locator for the registered wrappers - */ - public function getWrapperServiceLocator() - { - return $this->wrapperLocator; - } - + * @return DbWrapperServiceProvider The locator for the registered wrappers + */ + public function getWrapperServiceLocator() { + return $this->wrapperLocator; + } } diff --git a/lib/Db/CategoryDbWrapper.php b/lib/Db/CategoryDbWrapper.php index e6a7e9eb0..c30936946 100644 --- a/lib/Db/CategoryDbWrapper.php +++ b/lib/Db/CategoryDbWrapper.php @@ -9,7 +9,6 @@ use OCP\IDBConnection; class CategoryDbWrapper extends AbstractDbWrapper { - private const CATEGORIES = 'cookbook_categories'; /** @@ -74,7 +73,7 @@ public function remove(CategoryEntity $category) { // Remove all foreign links $recipes = $category->getRecipes(); - foreach($recipes as $r){ + foreach ($recipes as $r) { /** * @var RecipeEntity $r */ diff --git a/lib/Db/CategoryMappingDbWrapper.php b/lib/Db/CategoryMappingDbWrapper.php index 998880621..9e3263c43 100644 --- a/lib/Db/CategoryMappingDbWrapper.php +++ b/lib/Db/CategoryMappingDbWrapper.php @@ -8,7 +8,6 @@ use OCP\IL10N; class CategoryMappingDbWrapper extends AbstractDbWrapper { - private const CATEGORIES = 'cookbook_categories'; /** @@ -78,15 +77,13 @@ protected function fetchDatabase(): array { * @param CategoryMappingEntityImpl $category The entity to store */ public function store(CategoryMappingEntityImpl $mapping): void { - if(! $mapping->getRecipe()->isPersisted()) - { + if (! $mapping->getRecipe()->isPersisted()) { throw new InvalidDbStateException($this->l->t('The recipe was not stored to the database yet. No id known.')); } - if($mapping->isPersisted()){ + if ($mapping->isPersisted()) { $this->update($mapping); - } - else { + } else { $this->storeNew($mapping); } } @@ -119,16 +116,14 @@ private function update(CategoryMappingEntityImpl $mapping): void { $qb->setParameters([ 'rid' => $mapping->getRecipe()->getId(), 'uid' => $this->userId - ]); + ]); $qb->execute(); $cache = array_map(function (CategoryMappingEntityImpl $m) use ($mapping) { - if($m->isSame($mapping)) - { + if ($m->isSame($mapping)) { // We need to update return $mapping->clone(); - } - else { + } else { return $m; } }, $cache); diff --git a/lib/Db/KeywordDbWrapper.php b/lib/Db/KeywordDbWrapper.php index 08dbc9775..6cd10dd92 100644 --- a/lib/Db/KeywordDbWrapper.php +++ b/lib/Db/KeywordDbWrapper.php @@ -9,7 +9,6 @@ use OCA\Cookbook\Entity\impl\RecipeEntityImpl; class KeywordDbWrapper extends AbstractDbWrapper { - private const KEYWORDS = 'cookbook_keywords'; /** @@ -73,7 +72,7 @@ public function createEntity(): KeywordEntityImpl { * @param KeywordEntity $keyword * @return RecipeEntityImpl[] The recipes associated with the keyword */ - public function getRecipes(KeywordEntity $keyword): array{ + public function getRecipes(KeywordEntity $keyword): array { $mappings = $this->wrapperLocator->getKeywordMappingDbWrapper()->getEntries(); $mappings = array_filter($mappings, function (KeywordMappingEntityImpl $mapping) use ($keyword) { return $mapping->getKeyword()->isSame($keyword); @@ -83,12 +82,11 @@ public function getRecipes(KeywordEntity $keyword): array{ }, $mappings); } - public function remove(KeywordEntity $keyword): void - { + public function remove(KeywordEntity $keyword): void { // Remove all foreign links $recipes = $keyword->getRecipes(); - foreach($recipes as $r){ + foreach ($recipes as $r) { /** * @var RecipeEntity $r */ diff --git a/lib/Db/KeywordMappingDbWrapper.php b/lib/Db/KeywordMappingDbWrapper.php index 4db928c5e..976778441 100644 --- a/lib/Db/KeywordMappingDbWrapper.php +++ b/lib/Db/KeywordMappingDbWrapper.php @@ -7,7 +7,6 @@ use OCA\Cookbook\Exception\InvalidDbStateException; class KeywordMappingDbWrapper extends AbstractDbWrapper { - private const KEYWORDS = 'cookbook_keywords'; /** @@ -70,15 +69,13 @@ protected function fetchDatabase(): array { * @param KeywordMappingEntityImpl $category The entity to store */ public function store(KeywordMappingEntityImpl $mapping): void { - if(! $mapping->getRecipe()->isPersisted()){ + if (! $mapping->getRecipe()->isPersisted()) { throw new InvalidDbStateException($this->l->t('The recipe was not stored to the database yet. No id known.')); } - if($mapping->isPersisted()) - { + if ($mapping->isPersisted()) { $this->update($mapping); - } - else { + } else { $this->storeNew($mapping); } } @@ -114,12 +111,10 @@ private function update(KeywordMappingEntityImpl $mapping): void { $qb->execute(); - $cache = array_map(function(KeywordMappingEntityImpl $m) use ($mapping) { - if($m->isSame($mapping)) - { + $cache = array_map(function (KeywordMappingEntityImpl $m) use ($mapping) { + if ($m->isSame($mapping)) { return $mapping; - } - else { + } else { return $m; } }, $cache); @@ -138,10 +133,9 @@ public function remove(KeywordMappingEntityImpl $mapping): void { $qb->execute(); - $cache = array_filter($cache, function(KeywordMappingEntityImpl $m) use ($mapping) { + $cache = array_filter($cache, function (KeywordMappingEntityImpl $m) use ($mapping) { return ! $m->isSame($mapping); }); $this->setEntites($cache); } - } diff --git a/lib/Db/RecipeDbWrapper.php b/lib/Db/RecipeDbWrapper.php index 165981fd9..7bd2925f3 100644 --- a/lib/Db/RecipeDbWrapper.php +++ b/lib/Db/RecipeDbWrapper.php @@ -14,7 +14,6 @@ use OCA\Cookbook\Entity\impl\KeywordMappingEntityImpl; class RecipeDbWrapper extends AbstractDbWrapper { - private const NAMES = 'cookbook_names'; /** @@ -50,8 +49,7 @@ protected function fetchDatabase(): array { $res = $qb->execute(); $ret = []; - while($row = $res->fetch()) - { + while ($row = $res->fetch()) { $recipe = $this->createEntity(); $recipe->setName($row['name']); @@ -70,12 +68,9 @@ public function createEntity(): RecipeEntityImpl { } public function store(RecipeEntityImpl $recipe): void { - if($recipe->isPersisted()) - { + if ($recipe->isPersisted()) { $this->storeNew($recipe); - } - else - { + } else { $this->store($recipe); } } @@ -109,8 +104,7 @@ private function storeNew(RecipeEntityImpl $recipe): void { // Set keyword setting $keywordMapperWrapper = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper(); - foreach($recipe->getKeywords() as $keyword) - { + foreach ($recipe->getKeywords() as $keyword) { /** * @var KeywordEntityImpl $keyword */ @@ -137,12 +131,10 @@ private function update(RecipeEntityImpl $recipe): void { $qb->execute(); // Update cache - $cache = array_map(function(RecipeEntityImpl $r) use ($recipe) { - if($r->isSame($recipe)) - { + $cache = array_map(function (RecipeEntityImpl $r) use ($recipe) { + if ($r->isSame($recipe)) { return $recipe; - } - else { + } else { return $r; } }, $cache); @@ -150,20 +142,18 @@ private function update(RecipeEntityImpl $recipe): void { // Update the category if needed $newCategory = $recipe->getNewCategory(); - if($recipe->newCategoryWasSet() && ! is_null($newCategory)) - { + if ($recipe->newCategoryWasSet() && ! is_null($newCategory)) { $oldMappings = $this->getRecipeCategoryMappings($recipe); $newCategory->persist(); - if(count($oldMappings) == 0){ + if (count($oldMappings) == 0) { // We need to insert a new Mapping $mapping = $this->getWrapperServiceLocator()->getCategoryMappingDbWrapper()->createEntity(); $mapping->setCategory($newCategory); $mapping->setRecipe($recipe); $mapping->persist(); - } - else { + } else { $mapping = $oldMappings[0]; $mapping->setCategory($newCategory); $mapping->persist(); @@ -173,7 +163,7 @@ private function update(RecipeEntityImpl $recipe): void { // Update the keywords $kwWrapper = $this->getWrapperServiceLocator()->getKeywordMappingDbWrapper(); - foreach($recipe->getNewKeywords() as $kw) { + foreach ($recipe->getNewKeywords() as $kw) { /** * @var KeywordEntityImpl $kw */ @@ -187,20 +177,19 @@ private function update(RecipeEntityImpl $recipe): void { $removedKeywords = $recipe->getRemovedKeywords(); $currentKwMappings = $this->getRecipeKeywordMappings($recipe); - $removingMappings = array_filter($currentKwMappings, function(KeywordMappingEntityImpl $m) use ($removedKeywords){ - foreach($removedKeywords as $kw){ + $removingMappings = array_filter($currentKwMappings, function (KeywordMappingEntityImpl $m) use ($removedKeywords) { + foreach ($removedKeywords as $kw) { /** * @var KeywordEntityImpl $kw */ - if($m->getKeyword()->isSame($kw)) - { + if ($m->getKeyword()->isSame($kw)) { return true; } } return false; }); - foreach ($removingMappings as $mapping){ + foreach ($removingMappings as $mapping) { /** * @var KeywordMappingEntityImpl $mapping */ @@ -209,14 +198,12 @@ private function update(RecipeEntityImpl $recipe): void { } public function remove(RecipeEntityImpl $recipe): void { - if(! $recipe->isPersisted()) - { + if (! $recipe->isPersisted()) { throw new InvalidDbStateException($this->l->t('Cannot remove recipe that was not yet saved.')); } $catMappings = $this->getRecipeCategoryMappings($recipe); - foreach($catMappings as $catMapping) - { + foreach ($catMappings as $catMapping) { /** * @var CategoryMappingEntityImpl $catMapping */ @@ -224,7 +211,7 @@ public function remove(RecipeEntityImpl $recipe): void { } $keywordMappings = $this->getRecipeKeywordMappings($recipe); - foreach($keywordMappings as $keywordMapping){ + foreach ($keywordMappings as $keywordMapping) { /** * @var KeywordMappingEntityImpl $keywordMapping */ @@ -250,13 +237,13 @@ public function remove(RecipeEntityImpl $recipe): void { public function getRecipeById(int $id): RecipeEntity { $entities = $this->getEntries(); - foreach($entities as $entry) - { + foreach ($entities as $entry) { /** * @var RecipeEntity $entry */ - if($entry->getId() == $id) + if ($entry->getId() == $id) { return $entry; + } } throw new EntityNotFoundException($this->l->t('Recipe with id %d was not found.', $id)); @@ -265,8 +252,7 @@ public function getRecipeById(int $id): RecipeEntity { public function getCategory(RecipeEntity $recipe): ?CategoryEntityImpl { $mappings = $this->getRecipeCategoryMappings($recipe); - if(count($mappings) == 0) - { + if (count($mappings) == 0) { return null; } @@ -279,8 +265,7 @@ private function getRecipeCategoryMappings(RecipeEntityImpl $recipe): array { return $c->getRecipe()->isSame($recipe); }); - if(count($mappings) > 1) - { + if (count($mappings) > 1) { throw new InvalidDbStateException($this->l->t('Multiple categopries for a single recipe found.')); } @@ -293,7 +278,7 @@ private function getRecipeCategoryMappings(RecipeEntityImpl $recipe): array { */ public function getKeywords(RecipeEntity $recipe): array { $mappings = $this->getRecipeKeywordMappings($recipe); - $keywords = array_map(function(KeywordMappingEntityImpl $m) { + $keywords = array_map(function (KeywordMappingEntityImpl $m) { return $m->getKeyword(); }, $mappings); @@ -308,5 +293,4 @@ private function getRecipeKeywordMappings(RecipeEntityImpl $recipe): array { return $mappings; } - } diff --git a/lib/Entity/impl/AbstractEntity.php b/lib/Entity/impl/AbstractEntity.php index 572c4951f..9deca2854 100644 --- a/lib/Entity/impl/AbstractEntity.php +++ b/lib/Entity/impl/AbstractEntity.php @@ -11,8 +11,7 @@ abstract class AbstractEntity { */ private $persisted; - public function __construct($persisted = false) - { + public function __construct($persisted = false) { $this->persisted = $persisted; } @@ -28,19 +27,17 @@ protected function setPersisted(): void { $this->persisted = true; } - abstract function clone(): AbstractEntity; + abstract public function clone(): AbstractEntity; public function isSame(AbstractEntity $other): bool { - if(get_class($this) !== get_class($other)) - { + if (get_class($this) !== get_class($other)) { throw new InvalidComponentType(); } return $this->isSameImpl($other); } public function equals(AbstractEntity $other): bool { - if(get_class($this) !== get_class($other)) - { + if (get_class($this) !== get_class($other)) { throw new InvalidComponentType(); } diff --git a/lib/Entity/impl/CategoryEntityImpl.php b/lib/Entity/impl/CategoryEntityImpl.php index bc390ec6d..0aec521c2 100644 --- a/lib/Entity/impl/CategoryEntityImpl.php +++ b/lib/Entity/impl/CategoryEntityImpl.php @@ -47,41 +47,32 @@ public function persist(): void { $this->setPersisted(); } - public function clone(): CategoryEntity - { + public function clone(): CategoryEntity { $ret = $this->wrapper->createEntity(); $ret->setName($this->name); - if($this->isPersisted()) - { + if ($this->isPersisted()) { $ret->setPersisted(); } return $ret; } - public function remove(): void - { + public function remove(): void { $this->wrapper->remove($this); } - public function reload(): void - { + public function reload(): void { // FIXME } - protected function equalsImpl(AbstractEntity $other): bool - { + protected function equalsImpl(AbstractEntity $other): bool { return $this->name === $other->name; } - protected function isSameImpl(AbstractEntity $other): bool - { + protected function isSameImpl(AbstractEntity $other): bool { return $this->name === $other->name; } - public function getRecipes(): array - { + public function getRecipes(): array { return $this->wrapper->getRecipes($this); } - - } diff --git a/lib/Entity/impl/CategoryMappingEntityImpl.php b/lib/Entity/impl/CategoryMappingEntityImpl.php index cc03425ca..4994d8b8d 100644 --- a/lib/Entity/impl/CategoryMappingEntityImpl.php +++ b/lib/Entity/impl/CategoryMappingEntityImpl.php @@ -39,66 +39,53 @@ public function persist(): void { /** * @return RecipeEntityImpl */ - public function getRecipe(): RecipeEntityImpl - { + public function getRecipe(): RecipeEntityImpl { return $this->recipe; } /** - * @return CategoryEntityImpl - */ - public function getCategory(): CategoryEntityImpl - { - return $this->category; - } + * @return CategoryEntityImpl + */ + public function getCategory(): CategoryEntityImpl { + return $this->category; + } /** - * @param RecipeEntityImpl $recipe - */ - public function setRecipe(RecipeEntityImpl $recipe) - { - $this->recipe = $recipe; - } + * @param RecipeEntityImpl $recipe + */ + public function setRecipe(RecipeEntityImpl $recipe) { + $this->recipe = $recipe; + } /** - * @param CategoryEntityImpl $category - */ - public function setCategory(CategoryEntityImpl $category) - { - $this->category = $category; - } - - public function clone(): CategoryMappingEntityImpl - { + * @param CategoryEntityImpl $category + */ + public function setCategory(CategoryEntityImpl $category) { + $this->category = $category; + } + + public function clone(): CategoryMappingEntityImpl { $ret = $this->wrapper->createEntity(); $ret->setCategory($this->category); $ret->setRecipe($this->recipe); - if($this->isPersisted()) - { + if ($this->isPersisted()) { $ret->setPersisted(); } return $ret; } - public function remove(): void - { + public function remove(): void { $this->wrapper->remove($this); } - protected function equalsImpl(AbstractEntity $other): bool - { + protected function equalsImpl(AbstractEntity $other): bool { return $this->category->isSame($other->category) && $this->recipe->isSame($other->recipe); } - protected function isSameImpl(AbstractEntity $other): bool - { + protected function isSameImpl(AbstractEntity $other): bool { return $this->equalsImpl($other); } - - - - } diff --git a/lib/Entity/impl/KeywordEntityImpl.php b/lib/Entity/impl/KeywordEntityImpl.php index fa7565053..56c4a9ce3 100644 --- a/lib/Entity/impl/KeywordEntityImpl.php +++ b/lib/Entity/impl/KeywordEntityImpl.php @@ -47,40 +47,32 @@ public function persist(): void { $this->setPersisted(); } - public function reload(): void - { + public function reload(): void { // FIXME } - public function clone(): KeywordEntityImpl - { + public function clone(): KeywordEntityImpl { $ret = $this->wrapper->createEntity(); $ret->name = $this->name; - if($this->isPersisted()) - { + if ($this->isPersisted()) { $ret->setPersisted(); } return $ret; } - protected function equalsImpl(AbstractEntity $other): bool - { + protected function equalsImpl(AbstractEntity $other): bool { return $this->name === $other->name; } - protected function isSameImpl(AbstractEntity $other): bool - { + protected function isSameImpl(AbstractEntity $other): bool { return $this->name === $other->name; } - public function getRecipes(): array - { + public function getRecipes(): array { return $this->wrapper->getRecipes($this); } - public function remove(): void - { + public function remove(): void { $this->wrapper->remove($this); } - } diff --git a/lib/Entity/impl/KeywordMappingEntityImpl.php b/lib/Entity/impl/KeywordMappingEntityImpl.php index d24b6335c..fe63c8f45 100644 --- a/lib/Entity/impl/KeywordMappingEntityImpl.php +++ b/lib/Entity/impl/KeywordMappingEntityImpl.php @@ -32,64 +32,55 @@ public function persist(): void { } /** - * @return RecipeEntityImpl - */ - public function getRecipe(): RecipeEntityImpl - { - return $this->recipe; - } + * @return RecipeEntityImpl + */ + public function getRecipe(): RecipeEntityImpl { + return $this->recipe; + } /** - * @return KeywordEntityImpl - */ - public function getKeyword(): KeywordEntityImpl - { - return $this->keyword; - } + * @return KeywordEntityImpl + */ + public function getKeyword(): KeywordEntityImpl { + return $this->keyword; + } /** - * @param RecipeEntityImpl $recipe - */ - public function setRecipe(RecipeEntityImpl $recipe) - { - $this->recipe = $recipe; - } + * @param RecipeEntityImpl $recipe + */ + public function setRecipe(RecipeEntityImpl $recipe) { + $this->recipe = $recipe; + } /** - * @param KeywordEntityImpl $keyword - */ - public function setKeyword(KeywordEntityImpl $keyword) - { - $this->keyword = $keyword; - } - - public function clone(): KeywordMappingEntityImpl - { - $ret = $this->wrapper->createEntity(); - - $ret->setKeyword($this->keyword); - $ret->setRecipe($this->recipe); - - if($this->isPersisted()) - { - $ret->setPersisted(); - } - - return $ret; + * @param KeywordEntityImpl $keyword + */ + public function setKeyword(KeywordEntityImpl $keyword) { + $this->keyword = $keyword; + } + + public function clone(): KeywordMappingEntityImpl { + $ret = $this->wrapper->createEntity(); + + $ret->setKeyword($this->keyword); + $ret->setRecipe($this->recipe); + + if ($this->isPersisted()) { + $ret->setPersisted(); + } + + return $ret; } - protected function equalsImpl(AbstractEntity $other): bool - { - return $this->keyword->isSame($other->keyword) && $this->recipe->isSame($other->recipe); + protected function equalsImpl(AbstractEntity $other): bool { + return $this->keyword->isSame($other->keyword) && $this->recipe->isSame($other->recipe); } - protected function isSameImpl(AbstractEntity $other): bool - { + protected function isSameImpl(AbstractEntity $other): bool { return $this->equalsImpl($other); } public function remove(): void { $this->wrapper->remove($this); } - } diff --git a/lib/Entity/impl/RecipeEntityImpl.php b/lib/Entity/impl/RecipeEntityImpl.php index 3ed06ebf0..d2354f388 100644 --- a/lib/Entity/impl/RecipeEntityImpl.php +++ b/lib/Entity/impl/RecipeEntityImpl.php @@ -56,7 +56,7 @@ class RecipeEntityImpl extends AbstractEntity implements RecipeEntity { * Do not use this constructor directly but create new entities from the corresponding wrapper. * @param RecipeDbWrapper $wrapper The wrapper to use for DB access */ - public function __construct(RecipeDbWrapper $wrapper, IL10N $l){ + public function __construct(RecipeDbWrapper $wrapper, IL10N $l) { $this->wrapper = $wrapper; $this->newCategory = null; @@ -108,49 +108,42 @@ public function setName($name): void { } /** - * @return CategoryEntityImpl - */ - public function getCategory() - { - return $this->setNewCategory ? $this->newCategory : $this->wrapper->getCategory($this); - } + * @return CategoryEntityImpl + */ + public function getCategory() { + return $this->setNewCategory ? $this->newCategory : $this->wrapper->getCategory($this); + } /** - * @param CategoryEntity $category - */ - public function setCategory(CategoryEntity $category) - { - $this->newCategory = $category; - $this->setNewCategory = true; - } + * @param CategoryEntity $category + */ + public function setCategory(CategoryEntity $category) { + $this->newCategory = $category; + $this->setNewCategory = true; + } - public function remove(): void - { + public function remove(): void { $this->wrapper->remove($this); } - protected function equalsImpl(AbstractEntity $other): bool - { - if(! $this->isSame($other)) - { + protected function equalsImpl(AbstractEntity $other): bool { + if (! $this->isSame($other)) { return false; } // Compare internal structures - if($this->name !== $other->name) - { + if ($this->name !== $other->name) { return false; } // FIXME Compare references as well? } - public function getKeywords(): array - { + public function getKeywords(): array { $keywords = $this->wrapper->getKeywords($this); // Filter out all removed keywords - foreach($this->removedKeywords as $rkw) { + foreach ($this->removedKeywords as $rkw) { $keywords = $this->filterOutKeyword($keywords, $rkw); } @@ -160,48 +153,42 @@ public function getKeywords(): array return $keywords; } - public function removeKeyword(KeywordEntity $keyword): void - { - if($this->isKeywordInList($keyword, $this->removedKeywords)) - { + public function removeKeyword(KeywordEntity $keyword): void { + if ($this->isKeywordInList($keyword, $this->removedKeywords)) { // The keyword is already mentioned as to be removed. return; } - if($this->isKeywordInList($keyword, $this->newKeywords)){ + if ($this->isKeywordInList($keyword, $this->newKeywords)) { // We recently added the keyword. Remove it simply from the adding list $this->newKeywords = $this->filterOutKeyword($this->newKeywords, $keyword); - } - else { + } else { $dbKeywords = $this->wrapper->getKeywords($this); - if($this->isKeywordInList($keyword, $dbKeywords)) { + if ($this->isKeywordInList($keyword, $dbKeywords)) { // It is present in the DB, remove it $this->removedKeywords[] = $keyword; - } - else { + } else { // We have the keyword not in the DB. throw new EntityNotFoundException($this->l->t('Cannot remove a keyword not been assigned to recipe.')); } } } - public function reload(): void - { + public function reload(): void { // FIXME } - public function addKeyword(KeywordEntity $keyword): void - { - if($this->isKeywordInList($keyword, $this->removedKeywords)){ + public function addKeyword(KeywordEntity $keyword): void { + if ($this->isKeywordInList($keyword, $this->removedKeywords)) { // If we should remove the keyword previously, just drop the removal $this->removedKeywords = $this->filterOutKeyword($this->removedKeywords, $keyword); return; } $dbKeywords = $this->wrapper->getKeywords($this); - if($this->isKeywordInList($keyword, $dbKeywords)){ -// throw new InvalidDbStateException($this->l->t('Cannot add a keyword multiple times.')); + if ($this->isKeywordInList($keyword, $dbKeywords)) { + // throw new InvalidDbStateException($this->l->t('Cannot add a keyword multiple times.')); return; // XXX Better silent ignorance or exception } @@ -209,13 +196,11 @@ public function addKeyword(KeywordEntity $keyword): void $this->newKeywords[] = $keyword; } - protected function isSameImpl(AbstractEntity $other): bool - { + protected function isSameImpl(AbstractEntity $other): bool { return $this->id == $other->id; } - public function clone(): RecipeEntity - { + public function clone(): RecipeEntity { $ret = $this->wrapper->createEntity(); $ret->id = $this->id; @@ -224,8 +209,7 @@ public function clone(): RecipeEntity $ret->newKeywords = $this->newKeywords; $ret->removedKeywords = $this->removedKeywords; - if($this->isPersisted()) - { + if ($this->isPersisted()) { $ret->setPersisted(); } @@ -239,8 +223,7 @@ public function clone(): RecipeEntity */ private function filterOutKeyword(array $list, KeywordEntityImpl $keyword): array { return array_filter($list, function ($kw) use ($keyword) { - if($keyword->equals($kw)) - { + if ($keyword->equals($kw)) { return false; } return true; @@ -256,39 +239,30 @@ private function isKeywordInList(KeywordEntityImpl $keyword, array $list): bool } /** - * @return ?CategoryEntityImpl - */ - public function getNewCategory(): ?CategoryEntityImpl - { - return $this->newCategory; - } + * @return ?CategoryEntityImpl + */ + public function getNewCategory(): ?CategoryEntityImpl { + return $this->newCategory; + } /** - * @return KeywordEntityImpl[] - */ - public function getNewKeywords(): array - { - return $this->newKeywords; - } + * @return KeywordEntityImpl[] + */ + public function getNewKeywords(): array { + return $this->newKeywords; + } /** - * @return KeywordEntityImpl[] - */ - public function getRemovedKeywords(): array - { - return $this->removedKeywords; - } - - /** - * @return boolean - */ - public function newCategoryWasSet() - { - return $this->setNewCategory; - } - - - + * @return KeywordEntityImpl[] + */ + public function getRemovedKeywords(): array { + return $this->removedKeywords; + } - + /** + * @return boolean + */ + public function newCategoryWasSet() { + return $this->setNewCategory; + } } diff --git a/lib/Exception/EntityNotFoundException.php b/lib/Exception/EntityNotFoundException.php index 17db002f6..be1efcba8 100644 --- a/lib/Exception/EntityNotFoundException.php +++ b/lib/Exception/EntityNotFoundException.php @@ -4,14 +4,10 @@ class EntityNotFoundException extends \Exception { /** - * {@inheritDoc} - * @see \Exception::__construct() - */ - public function __construct($message = null, $code = null, $previous = null) - { - parent::__construct($message, $code, $previous); - } - - - -} \ No newline at end of file + * {@inheritDoc} + * @see \Exception::__construct() + */ + public function __construct($message = null, $code = null, $previous = null) { + parent::__construct($message, $code, $previous); + } +} diff --git a/lib/Exception/InvalidComparisionException.php b/lib/Exception/InvalidComparisionException.php index db035a10d..871f012e5 100644 --- a/lib/Exception/InvalidComparisionException.php +++ b/lib/Exception/InvalidComparisionException.php @@ -4,14 +4,10 @@ class InvaludComparisionException extends \Exception { /** - * {@inheritDoc} - * @see \Exception::__construct() - */ - public function __construct($message = null, $code = null, $previous = null) - { - parent::__construct($message, $code, $previous); - } - - - -} \ No newline at end of file + * {@inheritDoc} + * @see \Exception::__construct() + */ + public function __construct($message = null, $code = null, $previous = null) { + parent::__construct($message, $code, $previous); + } +} diff --git a/lib/Exception/InvalidDbStateException.php b/lib/Exception/InvalidDbStateException.php index fdcd300cb..b8d32a892 100644 --- a/lib/Exception/InvalidDbStateException.php +++ b/lib/Exception/InvalidDbStateException.php @@ -2,7 +2,7 @@ namespace OCA\Cookbook\Exception; -class InvalidDbStateException extends \Exception { +class InvalidDbStateException extends \Exception { public function __construct($message = null, $code = null, $previous = null) { parent::__construct($message, $code, $previous); } From df7cbcf0a62abdc00b1e23b9245805d34fd3375a Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 23 Nov 2020 11:41:01 +0100 Subject: [PATCH 32/38] Corrected code to make the unittest run again Signed-off-by: Christian Wolf --- lib/Entity/impl/CategoryEntityImpl.php | 2 +- lib/Entity/impl/KeywordEntityImpl.php | 2 +- lib/Entity/impl/RecipeEntityImpl.php | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Entity/impl/CategoryEntityImpl.php b/lib/Entity/impl/CategoryEntityImpl.php index 0aec521c2..ff26385e6 100644 --- a/lib/Entity/impl/CategoryEntityImpl.php +++ b/lib/Entity/impl/CategoryEntityImpl.php @@ -47,7 +47,7 @@ public function persist(): void { $this->setPersisted(); } - public function clone(): CategoryEntity { + public function clone(): CategoryEntityImpl { $ret = $this->wrapper->createEntity(); $ret->setName($this->name); if ($this->isPersisted()) { diff --git a/lib/Entity/impl/KeywordEntityImpl.php b/lib/Entity/impl/KeywordEntityImpl.php index 56c4a9ce3..c3cce4b41 100644 --- a/lib/Entity/impl/KeywordEntityImpl.php +++ b/lib/Entity/impl/KeywordEntityImpl.php @@ -38,7 +38,7 @@ public function getName(): string { * Set the name of the keyword * @param string $name The name of the keyword */ - public function setName($name) { + public function setName($name): void { $this->name = $name; } diff --git a/lib/Entity/impl/RecipeEntityImpl.php b/lib/Entity/impl/RecipeEntityImpl.php index d2354f388..b59e5d18b 100644 --- a/lib/Entity/impl/RecipeEntityImpl.php +++ b/lib/Entity/impl/RecipeEntityImpl.php @@ -110,14 +110,14 @@ public function setName($name): void { /** * @return CategoryEntityImpl */ - public function getCategory() { + public function getCategory(): CategoryEntityImpl { return $this->setNewCategory ? $this->newCategory : $this->wrapper->getCategory($this); } /** * @param CategoryEntity $category */ - public function setCategory(CategoryEntity $category) { + public function setCategory(CategoryEntity $category): void { $this->newCategory = $category; $this->setNewCategory = true; } @@ -200,7 +200,7 @@ protected function isSameImpl(AbstractEntity $other): bool { return $this->id == $other->id; } - public function clone(): RecipeEntity { + public function clone(): RecipeEntityImpl { $ret = $this->wrapper->createEntity(); $ret->id = $this->id; From de3b3d6e5be1a4ce188d986b3cf122002f680aad Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 23 Nov 2020 12:42:55 +0100 Subject: [PATCH 33/38] Corrected Exception in AbstractEntity Signed-off-by: Christian Wolf --- lib/Entity/impl/AbstractEntity.php | 6 +++--- lib/Exception/InvalidComparisionException.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Entity/impl/AbstractEntity.php b/lib/Entity/impl/AbstractEntity.php index 9deca2854..e783ce613 100644 --- a/lib/Entity/impl/AbstractEntity.php +++ b/lib/Entity/impl/AbstractEntity.php @@ -2,7 +2,7 @@ namespace OCA\Cookbook\Entity\impl; -use Sabre\CalDAV\Exception\InvalidComponentType; +use OCA\Cookbook\Exception\InvalidComparisionException; abstract class AbstractEntity { @@ -31,14 +31,14 @@ abstract public function clone(): AbstractEntity; public function isSame(AbstractEntity $other): bool { if (get_class($this) !== get_class($other)) { - throw new InvalidComponentType(); + throw new InvalidComparisionException(); } return $this->isSameImpl($other); } public function equals(AbstractEntity $other): bool { if (get_class($this) !== get_class($other)) { - throw new InvalidComponentType(); + throw new InvalidComparisionException(); } return $this->equalsImpl($other); diff --git a/lib/Exception/InvalidComparisionException.php b/lib/Exception/InvalidComparisionException.php index 871f012e5..e6dba8207 100644 --- a/lib/Exception/InvalidComparisionException.php +++ b/lib/Exception/InvalidComparisionException.php @@ -2,7 +2,7 @@ namespace OCA\Cookbook\Exception; -class InvaludComparisionException extends \Exception { +class InvalidComparisionException extends \Exception { /** * {@inheritDoc} * @see \Exception::__construct() From 04750d34a5d3590d881ac69d132ab4570680941e Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 23 Nov 2020 13:51:18 +0100 Subject: [PATCH 34/38] Updated GH actions to make support for PHP 7.2 optional Signed-off-by: Christian Wolf --- .github/actions/run-tests/tests/test-entrypoint.sh | 2 +- .github/workflows/tests.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-tests/tests/test-entrypoint.sh b/.github/actions/run-tests/tests/test-entrypoint.sh index ec0ca00f4..9ae04181f 100755 --- a/.github/actions/run-tests/tests/test-entrypoint.sh +++ b/.github/actions/run-tests/tests/test-entrypoint.sh @@ -121,7 +121,7 @@ else echo 'Copying the app code changes' echo 'This might cause trouble when dependencies have changed' - rsync -a /app/ apps/cookbook/ --exclude /.git --exclude /build --exclude /.github + rsync -a /app/ apps/cookbook/ --exclude /.git --exclude /build --exclude /.github --exclude /vendor fi diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 66dccf69a..09b4e9381 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -68,6 +68,10 @@ jobs: coreVersion: stable19 phpVersion: "7.3" mayFail: false + - database: mysql + coreVersion: stable19 + phpVersion: "8" + mayFail: true # Test against master (optionally) - database: mysql From 1aec0374b11fc91296337447b1aaa3f61e74f6f6 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 23 Nov 2020 14:02:42 +0100 Subject: [PATCH 35/38] Corrected types to be compatible with PHP prior to 7.4 Signed-off-by: Christian Wolf --- lib/Entity/impl/CategoryEntityImpl.php | 7 ++++++- lib/Entity/impl/CategoryMappingEntityImpl.php | 7 ++++++- lib/Entity/impl/KeywordEntityImpl.php | 7 ++++++- lib/Entity/impl/KeywordMappingEntityImpl.php | 7 ++++++- lib/Entity/impl/RecipeEntityImpl.php | 9 +++++++-- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/lib/Entity/impl/CategoryEntityImpl.php b/lib/Entity/impl/CategoryEntityImpl.php index ff26385e6..1321821cf 100644 --- a/lib/Entity/impl/CategoryEntityImpl.php +++ b/lib/Entity/impl/CategoryEntityImpl.php @@ -47,7 +47,12 @@ public function persist(): void { $this->setPersisted(); } - public function clone(): CategoryEntityImpl { + /** + * {@inheritDoc} + * @see \OCA\Cookbook\Entity\impl\AbstractEntity::clone() + * @return CategoryEntityImpl + */ + public function clone(): AbstractEntity { $ret = $this->wrapper->createEntity(); $ret->setName($this->name); if ($this->isPersisted()) { diff --git a/lib/Entity/impl/CategoryMappingEntityImpl.php b/lib/Entity/impl/CategoryMappingEntityImpl.php index 4994d8b8d..020101c01 100644 --- a/lib/Entity/impl/CategoryMappingEntityImpl.php +++ b/lib/Entity/impl/CategoryMappingEntityImpl.php @@ -64,7 +64,12 @@ public function setCategory(CategoryEntityImpl $category) { $this->category = $category; } - public function clone(): CategoryMappingEntityImpl { + /** + * {@inheritDoc} + * @see \OCA\Cookbook\Entity\impl\AbstractEntity::clone() + * @return CategoryMappingEntityImpl + */ + public function clone(): AbstractEntity { $ret = $this->wrapper->createEntity(); $ret->setCategory($this->category); diff --git a/lib/Entity/impl/KeywordEntityImpl.php b/lib/Entity/impl/KeywordEntityImpl.php index c3cce4b41..7c44e57a0 100644 --- a/lib/Entity/impl/KeywordEntityImpl.php +++ b/lib/Entity/impl/KeywordEntityImpl.php @@ -51,7 +51,12 @@ public function reload(): void { // FIXME } - public function clone(): KeywordEntityImpl { + /** + * {@inheritDoc} + * @see \OCA\Cookbook\Entity\impl\AbstractEntity::clone() + * @return KeywordEntityImpl + */ + public function clone(): AbstractEntity { $ret = $this->wrapper->createEntity(); $ret->name = $this->name; if ($this->isPersisted()) { diff --git a/lib/Entity/impl/KeywordMappingEntityImpl.php b/lib/Entity/impl/KeywordMappingEntityImpl.php index fe63c8f45..054c2dd5e 100644 --- a/lib/Entity/impl/KeywordMappingEntityImpl.php +++ b/lib/Entity/impl/KeywordMappingEntityImpl.php @@ -59,7 +59,12 @@ public function setKeyword(KeywordEntityImpl $keyword) { $this->keyword = $keyword; } - public function clone(): KeywordMappingEntityImpl { + /** + * {@inheritDoc} + * @see \OCA\Cookbook\Entity\impl\AbstractEntity::clone() + * @return KeywordMappingEntityImpl + */ + public function clone(): AbstractEntity { $ret = $this->wrapper->createEntity(); $ret->setKeyword($this->keyword); diff --git a/lib/Entity/impl/RecipeEntityImpl.php b/lib/Entity/impl/RecipeEntityImpl.php index b59e5d18b..972a9c5e4 100644 --- a/lib/Entity/impl/RecipeEntityImpl.php +++ b/lib/Entity/impl/RecipeEntityImpl.php @@ -110,7 +110,7 @@ public function setName($name): void { /** * @return CategoryEntityImpl */ - public function getCategory(): CategoryEntityImpl { + public function getCategory(): CategoryEntity { return $this->setNewCategory ? $this->newCategory : $this->wrapper->getCategory($this); } @@ -200,7 +200,12 @@ protected function isSameImpl(AbstractEntity $other): bool { return $this->id == $other->id; } - public function clone(): RecipeEntityImpl { + /** + * {@inheritDoc} + * @see \OCA\Cookbook\Entity\impl\AbstractEntity::clone() + * @return RecipeEntityImpl + */ + public function clone(): AbstractEntity { $ret = $this->wrapper->createEntity(); $ret->id = $this->id; From 9a8d9077ce52d12250affb55f11954cd5485c1a0 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 23 Nov 2020 14:09:48 +0100 Subject: [PATCH 36/38] Updated to the most recent rc of PHP for unit testing Signed-off-by: Christian Wolf --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 09b4e9381..0efe07cdd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -70,7 +70,7 @@ jobs: mayFail: false - database: mysql coreVersion: stable19 - phpVersion: "8" + phpVersion: "rc" mayFail: true # Test against master (optionally) From 78f94e60d2c9fa8bb514c54b0577230fb4a11925 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Mon, 23 Nov 2020 14:53:35 +0100 Subject: [PATCH 37/38] Removed rc PHP testing Signed-off-by: Christian Wolf --- .github/workflows/tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0efe07cdd..66dccf69a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -68,10 +68,6 @@ jobs: coreVersion: stable19 phpVersion: "7.3" mayFail: false - - database: mysql - coreVersion: stable19 - phpVersion: "rc" - mayFail: true # Test against master (optionally) - database: mysql From 4083e955d47d5c63483e98bb8bf5552b6ac50f19 Mon Sep 17 00:00:00 2001 From: Christian Wolf Date: Sun, 29 Nov 2020 17:38:29 +0100 Subject: [PATCH 38/38] Added collection classes to UML Signed-off-by: Christian Wolf --- documentation/uml/dbwrappers.txt | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/documentation/uml/dbwrappers.txt b/documentation/uml/dbwrappers.txt index eab50b737..dc7c1a404 100644 --- a/documentation/uml/dbwrappers.txt +++ b/documentation/uml/dbwrappers.txt @@ -101,25 +101,32 @@ namespace OCA.Cookbook { Entity <|.. CategoryEntity Entity <|.. KeywordEntity + namespace Collections { + + abstract class AbstractCollection {} + class Recipes {} + class Categories {} + class Keywords {} + + + AbstractCollection <|-- Recipes + AbstractCollection <|-- Categories + AbstractCollection <|-- Keywords + } + } namespace Db { - abstract class AbstractDbWrapper { + abstract class AbstractDbWrapper { # IDbConnection db - array cache - bool cacheValid - -- + abstract createEntity() : T # abstract fetchDatabase() : array + getEntites() : array # setCache(array) : void + getServiceLocator() : DbWrapperSerciveLocator - .. - + store(T) : void - # abstract storeNew(T) : void - # abstract update(T) : void - + remove(T) : void } note right Cache represents the current state @@ -204,5 +211,6 @@ namespace OCA.Cookbook { } +ArrayIterator <|-- OCA.Cookbook.Entity.Collections.AbstractCollection @enduml