isEmpty()) { return; } $mergableIngredientsByStrategy = $this->groupByMergeStrategy( $this->extractMergableGroups($shoppingList) ); $this->merge($shoppingList, $mergableIngredientsByStrategy); } private function extractMergableGroups(ShoppingList $shoppingList): array { $groupedByNames = []; foreach ($shoppingList as $ingredient) { $key = Stemm::stem($ingredient->getCleanName(), 'de'); if (!isset($groupedByNames[$key])) { $groupedByNames[$key] = []; } $groupedByNames[$key][] = $ingredient; } $mergableIngredients = array_filter($groupedByNames, static function(array $group): bool { return count($group) > 1; }); return $mergableIngredients; } private function groupByMergeStrategy(array $mergableIngredients): array { $strategyGrouped = []; foreach($mergableIngredients as $name => $ingredientGroup) { if (!isset($strategyGrouped[$name])) { $strategyGrouped[$name] = []; } foreach($ingredientGroup as $ingredient) { $strategy = $this->detectStrategy($ingredient); $strategyGrouped[$name][$strategy][] = $ingredient; } } return $strategyGrouped; } private function detectStrategy(Ingredient $ingredient): string { if (empty($ingredient->getAmount())) { return self::STRATEGY_WITHOUT_AMOUNT; } return self::STRATEGY_BY_UNIT; } private function merge(ShoppingList &$shoppingList, array $mergableIngredientsByStrategy) { foreach($mergableIngredientsByStrategy as $ingredientGroup) { foreach($ingredientGroup as $strategy => $ingredientList) { switch($strategy) { case self::STRATEGY_WITHOUT_AMOUNT: $this->applyStrategyWithoutAmount($shoppingList, $ingredientList); break; case self::STRATEGY_BY_UNIT: $this->applyStrategyByUnit($shoppingList, $ingredientList); break; } } } } /** * WithoutAmount Strategy * Keep the first entry only, remove the others */ private function applyStrategyWithoutAmount(ShoppingList &$shoppingList, array $ingredientGroup): void { $keep = reset($ingredientGroup); $ingredientsToRemove = array_slice($ingredientGroup, 1); foreach($ingredientsToRemove as $ingredient) { $keep->addMerged($ingredient); $shoppingList->remove($ingredient); } } /** * By Unit Strategy * Group ingredients with the same unit */ private function applyStrategyByUnit(ShoppingList &$shoppingList, array $ingredientGroup): void { $byUnit = []; foreach($ingredientGroup as $ingredient) { $key = Stemm::stem($ingredient->getUnit(), 'de'); if (!isset($byUnit[$key])) { $byUnit[$key] = []; } $byUnit[$key][] = $ingredient; } foreach($byUnit as $ingredients) { $baseIngredient = $ingredients[0]; $ingredientsToRemove = array_slice($ingredients, 1); foreach($ingredientsToRemove as $ingredientToRemove) { $amount = $ingredientToRemove->getAmount(); // check for ranges if (strpos($amount, '-') !== false) { $amounts = array_map('trim', explode('-', $amount)); $amount = array_pop($amounts); } if (!is_numeric($amount)) { throw new Exception(sprintf('Cannot merge non numeric amounts (%s) of ingredients: %s', $amount, $ingredientToRemove)); } $newAmount = (float)$baseIngredient->getAmount() + (float)$ingredientToRemove->getAmount(); $baseIngredient->setAmount($newAmount); $baseIngredient->addMerged($ingredientToRemove); $shoppingList->remove($ingredientToRemove); } } } }