Passed
Push — master ( 5ac991...5f315b )
by Christian
11:49 queued 24s
created

LineItemGroupBuilder::adjustRestOfCart()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 67
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 27
nc 24
nop 2
dl 0
loc 67
rs 8.5546
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Checkout\Cart\LineItem\Group;
4
5
use Shopware\Core\Checkout\Cart\Cart;
6
use Shopware\Core\Checkout\Cart\Exception\InvalidQuantityException;
7
use Shopware\Core\Checkout\Cart\Exception\LineItemNotStackableException;
8
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
9
use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection;
10
use Shopware\Core\Checkout\Cart\LineItem\LineItemFlatCollection;
11
use Shopware\Core\Checkout\Cart\LineItem\LineItemQuantitySplitter;
12
use Shopware\Core\System\SalesChannel\SalesChannelContext;
13
14
class LineItemGroupBuilder
15
{
16
    /**
17
     * @var LineItemGroupServiceRegistry
18
     */
19
    private $registry;
20
21
    /**
22
     * @var LineItemGroupRuleMatcherInterface
23
     */
24
    private $ruleMatcher;
25
26
    /**
27
     * @var LineItemQuantitySplitter
28
     */
29
    private $quantitySplitter;
30
31
    public function __construct(LineItemGroupServiceRegistry $registry, LineItemGroupRuleMatcherInterface $ruleMatcher, LineItemQuantitySplitter $lineItemQuantitySplitter)
32
    {
33
        $this->registry = $registry;
34
        $this->ruleMatcher = $ruleMatcher;
35
        $this->quantitySplitter = $lineItemQuantitySplitter;
36
    }
37
38
    /**
39
     * Searches for all packages that can be built from the provided list of groups.
40
     * Every line item will be taken from the cart and only the ones that are left will
41
     * be checked for upcoming groups.
42
     *
43
     * @param LineItemGroupDefinition[] $groupDefinitions
44
     */
45
    public function findGroupPackages(array $groupDefinitions, Cart $cart, SalesChannelContext $context): LineItemGroupBuilderResult
46
    {
47
        $result = new LineItemGroupBuilderResult();
48
49
        // filter out all promotion items
50
        $cartProducts = $this->getCartProducts($cart);
51
52
        // split quantities into separate line items
53
        // so we have a real list of products like we would have
54
        // them when holding it in our actual hands.
55
        $restOfCart = $this->splitQuantities($cartProducts, $context);
56
57
        foreach ($groupDefinitions as $groupDefinition) {
58
            $sorter = $this->registry->getSorter($groupDefinition->getSorterKey());
59
            $packager = $this->registry->getPackager($groupDefinition->getPackagerKey());
60
61
            // we have to sort our items first
62
            // otherwise it would be a "random" order when
63
            // adjusting the rest of our cart...
64
            $restOfCart = $sorter->sort($restOfCart);
65
66
            // try as long as groups can be
67
            // found for the current definition
68
            while (true) {
69
                $itemsToConsider = $this->ruleMatcher->getMatchingItems($groupDefinition, $restOfCart, $context);
70
71
                // now build a package with our packager
72
                $group = $packager->buildGroupPackage($groupDefinition->getValue(), $itemsToConsider, $context);
73
74
                // if we have no found items in our group, quit
75
                if (!$group->hasItems()) {
76
                    break;
77
                }
78
79
                // append the currently found group of items
80
                // to our group definition inside our result object
81
                $result->addGroup($groupDefinition, $group);
82
83
                // decrease rest of cart items for next search
84
                $restOfCart = $this->adjustRestOfCart($group->getItems(), $restOfCart);
85
            }
86
        }
87
88
        return $result;
89
    }
90
91
    /**
92
     * This is a very important function.
93
     * It removes our line items that are found in the group and returns the rest of the cart items.
94
     * So if we have 4 line items of 2 products with each quantity 1, and want to remove a product with qt 2,
95
     * then 2 line items will be removed and the new rest of the cart is being returned.
96
     *
97
     * @param LineItemQuantity[] $foundItems
98
     */
99
    private function adjustRestOfCart(array $foundItems, LineItemFlatCollection $restOfCart): LineItemFlatCollection
100
    {
101
        // a holder for all foundItems indexed by lineItemId
102
        /** @var LineItemQuantity[] $lineItemsToRemove */
103
        $lineItemsToRemove = [];
104
105
        // we prepare the removeLineItemIds array with all LineItemQuantity objects indexed by lineItemId
106
        foreach ($foundItems as $itemToRemove) {
107
            if (isset($lineItemsToRemove[$itemToRemove->getLineItemId()])) {
108
                $quantity = $lineItemsToRemove[$itemToRemove->getLineItemId()];
109
                $lineItemsToRemove[$itemToRemove->getLineItemId()]->setQuantity($quantity->getQuantity() + $itemToRemove->getQuantity());
110
111
                continue;
112
            }
113
            $lineItemsToRemove[$itemToRemove->getLineItemId()] = $itemToRemove;
114
        }
115
116
        /** @var array $lineItemsToRemoveIDs */
117
        $lineItemsToRemoveIDs = array_keys($lineItemsToRemove);
118
119
        $newRestOfCart = new LineItemFlatCollection();
120
121
        // this is our running buffer
122
        // for the items that need to be removed
123
        $deleteBuffer = [];
124
125
        // make sure we have an ID index for
126
        // all our delete-items with a qty of 0
127
        foreach (array_keys($lineItemsToRemove) as $id) {
128
            $deleteBuffer[$id] = 0;
129
        }
130
131
        foreach ($restOfCart as $item) {
132
            // if its a totally different item
133
            // just add it to the rest of our cart
134
            if (!in_array($item->getId(), $lineItemsToRemoveIDs, true)) {
135
                $newRestOfCart->add($item);
136
            } else {
137
                // we have an item that should be removed
138
                // now we have to calculate how many of the item position (qty diff)
139
                // or if we have even reached our max amount of quantities to remove for this item
140
                $maxRemoveMeta = $lineItemsToRemove[$item->getId()]->getQuantity();
141
142
                $alreadyDeletedCount = $deleteBuffer[$item->getId()];
143
144
                // now check if we can remove our current item completely
145
                // or if we have a sub quantity that still needs to be
146
                // added to the rest of the cart
147
                if ($alreadyDeletedCount + $item->getQuantity() <= $maxRemoveMeta) {
148
                    // remove completely
149
                    $deleteBuffer[$item->getId()] += $item->getQuantity();
150
                } else {
151
                    $toDeleteCount = $maxRemoveMeta - $alreadyDeletedCount;
152
                    $keepCount = $item->getQuantity() - $toDeleteCount;
153
154
                    // mark our diff as "deleted"
155
                    $deleteBuffer[$item->getId()] += $toDeleteCount;
156
157
                    // add the keep count to our item
158
                    // and the item to the rest of our cart
159
                    $item->setQuantity($keepCount);
160
                    $newRestOfCart->add($item);
161
                }
162
            }
163
        }
164
165
        return $newRestOfCart;
166
    }
167
168
    private function getCartProducts(Cart $cart): LineItemCollection
169
    {
170
        return $cart->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE);
171
    }
172
173
    /**
174
     * @throws InvalidQuantityException
175
     * @throws LineItemNotStackableException
176
     */
177
    private function splitQuantities(LineItemCollection $cartItems, SalesChannelContext $context): LineItemFlatCollection
178
    {
179
        $items = [];
180
181
        foreach ($cartItems as $item) {
182
            for ($i = 1; $i <= $item->getQuantity(); ++$i) {
183
                $tmpItem = $this->quantitySplitter->split($item, 1, $context);
184
185
                $items[] = $tmpItem;
186
            }
187
        }
188
189
        return new LineItemFlatCollection($items);
190
    }
191
}
192