Passed
Push — master ( a5379f...f484f2 )
by Christian
12:11 queued 10s
created

CartRuleLoader::loadRules()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 3
nop 1
dl 0
loc 20
rs 9.9332
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Checkout\Cart;
4
5
use Psr\Log\LoggerInterface;
6
use Shopware\Core\Checkout\Cart\Exception\CartTokenNotFoundException;
7
use Shopware\Core\Content\Rule\RuleCollection;
8
use Shopware\Core\Framework\Context;
9
use Shopware\Core\System\SalesChannel\SalesChannelContext;
10
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
11
12
class CartRuleLoader
13
{
14
    public const CHECKOUT_RULE_LOADER_CACHE_KEY = 'all-rules';
15
    private const MAX_ITERATION = 5;
16
17
    /**
18
     * @var CartPersisterInterface
19
     */
20
    private $cartPersister;
21
22
    /**
23
     * @var null|RuleCollection
24
     */
25
    private $rules;
26
27
    /**
28
     * @var Processor
29
     */
30
    private $processor;
31
32
    /**
33
     * @var LoggerInterface
34
     */
35
    private $logger;
36
37
    /**
38
     * @var TagAwareAdapterInterface
39
     */
40
    private $cache;
41
42
    /**
43
     * @var RuleLoader
44
     */
45
    private $ruleLoader;
46
47
    public function __construct(
48
        CartPersisterInterface $cartPersister,
49
        Processor $processor,
50
        LoggerInterface $logger,
51
        TagAwareAdapterInterface $cache,
52
        RuleLoader $loader
53
    ) {
54
        $this->cartPersister = $cartPersister;
55
        $this->processor = $processor;
56
        $this->logger = $logger;
57
        $this->cache = $cache;
58
        $this->ruleLoader = $loader;
59
    }
60
61
    public function loadByToken(SalesChannelContext $context, string $cartToken): RuleLoaderResult
62
    {
63
        try {
64
            $cart = $this->cartPersister->load($cartToken, $context);
65
        } catch (CartTokenNotFoundException $e) {
66
            $cart = new Cart($context->getSalesChannel()->getTypeId(), $cartToken);
67
        }
68
69
        return $this->loadByCart($context, $cart, new CartBehavior($context->getPermissions()));
70
    }
71
72
    public function loadByCart(SalesChannelContext $context, Cart $cart, CartBehavior $behaviorContext): RuleLoaderResult
73
    {
74
        return $this->load($context, $cart, $behaviorContext);
75
    }
76
77
    public function reset(): void
78
    {
79
        $this->rules = null;
80
        $this->cache->deleteItem(self::CHECKOUT_RULE_LOADER_CACHE_KEY);
81
    }
82
83
    private function load(SalesChannelContext $context, Cart $cart, CartBehavior $behaviorContext): RuleLoaderResult
84
    {
85
        $rules = $this->loadRules($context->getContext());
86
87
        $context->setRuleIds($rules->getIds());
88
89
        $iteration = 1;
90
91
        $cart = $this->processor->process($cart, $context, $behaviorContext);
92
93
        do {
94
            if ($iteration > self::MAX_ITERATION) {
95
                break;
96
            }
97
98
            //find rules which matching current cart and context state
99
            $rules = $rules->filterMatchingRules($cart, $context);
100
101
            //place rules into context for further usages
102
            $context->setRuleIds($rules->getIds());
103
104
            //recalculate cart for new context rules
105
            $new = $this->processor->process($cart, $context, $behaviorContext);
106
107
            $recalculate = $this->cartChanged($cart, $new);
108
109
            $cart = $new;
110
111
            ++$iteration;
112
        } while ($recalculate);
113
114
        $index = 0;
115
        foreach ($rules as $rule) {
116
            ++$index;
117
            $this->logger->info(
118
                sprintf('#%s Rule detection: %s with priority %s (id: %s)', $index, $rule->getName(), $rule->getPriority(), $rule->getId())
119
            );
120
        }
121
122
        $context->setRuleIds($rules->getIds());
123
124
        // save the cart if errors exist, so the errors get persisted
125
        if ($cart->getErrors()->count() > 0) {
126
            $this->cartPersister->save($cart, $context);
127
        }
128
129
        return new RuleLoaderResult($cart, $rules);
130
    }
131
132
    private function loadRules(Context $context): RuleCollection
133
    {
134
        if ($this->rules !== null) {
135
            return $this->rules;
136
        }
137
138
        $item = $this->cache->getItem(self::CHECKOUT_RULE_LOADER_CACHE_KEY);
139
140
        $rules = $item->get();
141
        if ($item->isHit() && $rules) {
142
            return $this->rules = $rules;
143
        }
144
145
        $rules = $this->ruleLoader->load($context);
146
147
        $item->set($rules);
148
149
        $this->cache->save($item);
150
151
        return $this->rules = $rules;
152
    }
153
154
    private function cartChanged(Cart $previous, Cart $current): bool
155
    {
156
        $previousLineItems = $previous->getLineItems();
157
        $currentLineItems = $current->getLineItems();
158
159
        return $previousLineItems->count() !== $currentLineItems->count()
160
            || $previous->getPrice()->getTotalPrice() !== $current->getPrice()->getTotalPrice()
161
            || $previousLineItems->getKeys() !== $currentLineItems->getKeys()
162
            || $previousLineItems->getTypes() !== $currentLineItems->getTypes()
163
        ;
164
    }
165
}
166