Passed
Pull Request — master (#452)
by
unknown
05:07 queued 03:31
created

OrientatedItemFactory::getBestOrientation()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 39
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 3

Importance

Changes 14
Bugs 3 Features 0
Metric Value
cc 3
eloc 15
c 14
b 3
f 0
nc 4
nop 12
dl 0
loc 39
ccs 20
cts 20
cp 1
crap 3
rs 9.7666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Box packing (3D bin packing, knapsack problem).
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace DVDoug\BoxPacker;
10
11
use Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
15
use function array_filter;
16
use function count;
17
use function usort;
18
19
/**
20
 * Figure out orientations for an item and a given set of dimensions.
21
 * @internal
22
 */
23
class OrientatedItemFactory implements LoggerAwareInterface
24
{
25
    protected LoggerInterface $logger;
26
27
    protected bool $singlePassMode = false;
28
29
    /**
30
     * @var array<string, bool>
31
     */
32
    protected static array $emptyBoxStableItemOrientationCache = [];
33
34 84
    public function __construct(protected Box $box)
35
    {
36 84
        $this->logger = new NullLogger();
37
    }
38
39 84
    public function setLogger(LoggerInterface $logger): void
40
    {
41 84
        $this->logger = $logger;
42
    }
43
44 4
    public function setSinglePassMode(bool $singlePassMode): void
45
    {
46 4
        $this->singlePassMode = $singlePassMode;
47
    }
48
49
    /**
50
     * Get the best orientation for an item.
51
     */
52 84
    public function getBestOrientation(
53
        Item $item,
54
        ?OrientatedItem $prevItem,
55
        ItemList $nextItems,
56
        int $widthLeft,
57
        int $lengthLeft,
58
        int $depthLeft,
59
        int $rowLength,
60
        int $x,
61
        int $y,
62
        int $z,
63
        PackedItemList $prevPackedItemList,
0 ignored issues
show
Bug introduced by
The type DVDoug\BoxPacker\PackedItemList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
64
        bool $considerStability
65
    ): ?OrientatedItem {
66 84
        $this->logger->debug(
67 84
            "evaluating item {$item->getDescription()} for fit",
68 84
            [
69 84
                'item' => $item,
70 84
                'space' => [
71 84
                    'widthLeft' => $widthLeft,
72 84
                    'lengthLeft' => $lengthLeft,
73 84
                    'depthLeft' => $depthLeft,
74 84
                ],
75 84
            ]
76 84
        );
77
78 84
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft, $x, $y, $z, $prevPackedItemList);
79 84
        $usableOrientations = $considerStability ? $this->getUsableOrientations($item, $possibleOrientations) : $possibleOrientations;
80
81 84
        if (empty($usableOrientations)) {
82 76
            return null;
83
        }
84
85 82
        $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList, $this->logger);
86 82
        usort($usableOrientations, $sorter);
87
88 82
        $this->logger->debug('Selected best fit orientation', ['orientation' => $usableOrientations[0]]);
89
90 82
        return $usableOrientations[0];
91
    }
92
93
    /**
94
     * Find all possible orientations for an item.
95
     *
96
     * @return OrientatedItem[]
97
     */
98 84
    public function getPossibleOrientations(
99
        Item $item,
100
        ?OrientatedItem $prevItem,
101
        int $widthLeft,
102
        int $lengthLeft,
103
        int $depthLeft,
104
        int $x,
105
        int $y,
106
        int $z,
107
        PackedItemList $prevPackedItemList
108
    ): array {
109 84
        $permutations = $this->generatePermutations($item, $prevItem);
110
111
        // remove any that simply don't fit
112 84
        $orientations = [];
113 84
        foreach ($permutations as $dimensions) {
114 84
            if ($dimensions[0] <= $widthLeft && $dimensions[1] <= $lengthLeft && $dimensions[2] <= $depthLeft) {
115 82
                $orientations[] = new OrientatedItem($item, $dimensions[0], $dimensions[1], $dimensions[2]);
116
            }
117
        }
118
119 84
        if ($item instanceof ConstrainedPlacementItem && !$this->box instanceof WorkingVolume) {
120
            $orientations = array_filter($orientations, function (OrientatedItem $i) use ($x, $y, $z, $prevPackedItemList): bool {
121
                /** @var ConstrainedPlacementItem $constrainedItem */
122
                $constrainedItem = $i->getItem();
123
124
                return $constrainedItem->canBePacked(new PackedBox($this->box, $prevPackedItemList), $x, $y, $z, $i->getWidth(), $i->getLength(), $i->getDepth());
125
            });
126
        }
127
128 84
        return $orientations;
129
    }
130
131
    /**
132
     * @param  OrientatedItem[] $possibleOrientations
133
     * @return OrientatedItem[]
134
     */
135 84
    protected function getUsableOrientations(
136
        Item $item,
137
        array $possibleOrientations
138
    ): array {
139 84
        $stableOrientations = $unstableOrientations = [];
140
141
        // Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
142 84
        foreach ($possibleOrientations as $orientation) {
143 82
            if ($orientation->isStable() || $this->box->getInnerDepth() === $orientation->getDepth()) {
144 82
                $stableOrientations[] = $orientation;
145
            } else {
146
                $unstableOrientations[] = $orientation;
147
            }
148
        }
149
150
        /*
151
         * We prefer to use stable orientations only, but allow unstable ones if
152
         * the item doesn't fit in the box any other way
153
         */
154 84
        if (count($stableOrientations) > 0) {
155 82
            return $stableOrientations;
156
        }
157
158 76
        if ((count($unstableOrientations) > 0) && !$this->hasStableOrientationsInEmptyBox($item)) {
159
            return $unstableOrientations;
160
        }
161
162 76
        return [];
163
    }
164
165
    /**
166
     * Return the orientations for this item if it were to be placed into the box with nothing else.
167
     */
168
    protected function hasStableOrientationsInEmptyBox(Item $item): bool
169
    {
170
        $cacheKey = $item->getWidth() .
171
            '|' .
172
            $item->getLength() .
173
            '|' .
174
            $item->getDepth() .
175
            '|' .
176
            $item->getAllowedRotation()->name .
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on DVDoug\BoxPacker\Rotation.
Loading history...
177
            '|' .
178
            $this->box->getInnerWidth() .
179
            '|' .
180
            $this->box->getInnerLength() .
181
            '|' .
182
            $this->box->getInnerDepth();
183
184
        if (isset(static::$emptyBoxStableItemOrientationCache[$cacheKey])) {
185
            return static::$emptyBoxStableItemOrientationCache[$cacheKey];
186
        }
187
188
        $orientations = $this->getPossibleOrientations(
189
            $item,
190
            null,
191
            $this->box->getInnerWidth(),
192
            $this->box->getInnerLength(),
193
            $this->box->getInnerDepth(),
194
            0,
195
            0,
196
            0,
197
            new PackedItemList()
198
        );
199
200
        $stableOrientations = array_filter(
201
            $orientations,
202
            static fn (OrientatedItem $orientation) => $orientation->isStable()
203
        );
204
        static::$emptyBoxStableItemOrientationCache[$cacheKey] = count($stableOrientations) > 0;
205
206
        return static::$emptyBoxStableItemOrientationCache[$cacheKey];
207
    }
208
209
    /**
210
     * @return array<array<int>>
211
     */
212 84
    private function generatePermutations(Item $item, ?OrientatedItem $prevItem): array
213
    {
214
        // Special case items that are the same as what we just packed - keep orientation
215 84
        if ($prevItem && $prevItem->isSameDimensions($item)) {
216 62
            return [[$prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth()]];
217
        }
218
219 84
        $permutations = [];
220 84
        $w = $item->getWidth();
221 84
        $l = $item->getLength();
222 84
        $d = $item->getDepth();
223
224 84
        $permutations[$w . '|' . $l . '|' . $d] = [$w, $l, $d];
225
226 84
        if ($item->getAllowedRotation() !== Rotation::Never) { // simple 2D rotation
227 84
            $permutations[$l . '|' . $w . '|' . $d] = [$l, $w, $d];
228
        }
229
230 84
        if ($item->getAllowedRotation() === Rotation::BestFit) { // add 3D rotation if we're allowed
231 52
            $permutations[$w . '|' . $d . '|' . $l] = [$w, $d, $l];
232 52
            $permutations[$l . '|' . $d . '|' . $w] = [$l, $d, $w];
233 52
            $permutations[$d . '|' . $w . '|' . $l] = [$d, $w, $l];
234 52
            $permutations[$d . '|' . $l . '|' . $w] = [$d, $l, $w];
235
        }
236
237 84
        return $permutations;
238
    }
239
}
240