133 rivejä
4.6 KiB
PHP
133 rivejä
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);
|
|
}
|
|
}
|
|
}
|
|
} |