cookidoo-shopping-advanced/src/Rules/MergeSame.php

133 lines
4.6 KiB
PHP

<?php
namespace CookidooShoppingAdvanced\Rules;
use CookidooShoppingAdvanced\Models\Ingredient;
use CookidooShoppingAdvanced\Models\ShoppingList;
use Exception;
use Nadar\Stemming\Stemm;
class MergeSame extends AbstractRule {
private const STRATEGY_WITHOUT_AMOUNT = 'withoutAmount';
private const STRATEGY_BY_UNIT = 'byUnit';
public function filter(ShoppingList &$shoppingList): void {
if ($shoppingList->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);
}
}
}
}