Passed
Push — 3.x ( fece24...05a09a )
by Doug
02:30
created

OrientatedItemFactory::getBestOrientation()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 40
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 14
Bugs 3 Features 0
Metric Value
cc 3
eloc 16
c 14
b 3
f 0
nc 4
nop 12
dl 0
loc 40
ccs 16
cts 16
cp 1
crap 3
rs 9.7333

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 function array_filter;
12
use function count;
13
use Psr\Log\LoggerAwareInterface;
14
use Psr\Log\LoggerAwareTrait;
15
use Psr\Log\NullLogger;
16
use function usort;
17
18
/**
19
 * Figure out orientations for an item and a given set of dimensions.
20
 *
21
 * @author Doug Wright
22
 * @internal
23
 */
24
class OrientatedItemFactory implements LoggerAwareInterface
25
{
26
    use LoggerAwareTrait;
27
28
    /** @var Box */
29
    protected $box;
30
31
    /**
32
     * Whether the packer is in single-pass mode.
33
     *
34
     * @var bool
35
     */
36
    protected $singlePassMode = false;
37
38
    /**
39
     * @var bool[]
40
     */
41
    protected static $emptyBoxStableItemOrientationCache = [];
42
43 82
    public function __construct(Box $box)
44
    {
45 82
        $this->box = $box;
46 82
        $this->logger = new NullLogger();
47 82
    }
48
49 48
    public function setSinglePassMode(bool $singlePassMode): void
50
    {
51 48
        $this->singlePassMode = $singlePassMode;
52 48
    }
53
54
    /**
55
     * Get the best orientation for an item.
56
     */
57 80
    public function getBestOrientation(
58
        Item $item,
59
        ?OrientatedItem $prevItem,
60
        ItemList $nextItems,
61
        int $widthLeft,
62
        int $lengthLeft,
63
        int $depthLeft,
64
        int $rowLength,
65
        int $x,
66
        int $y,
67
        int $z,
68
        PackedItemList $prevPackedItemList,
69
        bool $considerStability
70
    ): ?OrientatedItem {
71 80
        $this->logger->debug(
0 ignored issues
show
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

71
        $this->logger->/** @scrutinizer ignore-call */ 
72
                       debug(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
72 80
            "evaluating item {$item->getDescription()} for fit",
73
            [
74 80
                'item' => $item,
75
                'space' => [
76 80
                    'widthLeft' => $widthLeft,
77 80
                    'lengthLeft' => $lengthLeft,
78 80
                    'depthLeft' => $depthLeft,
79
                ],
80
            ]
81
        );
82
83 80
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft, $x, $y, $z, $prevPackedItemList);
84 80
        $usableOrientations = $considerStability ? $this->getUsableOrientations($item, $possibleOrientations) : $possibleOrientations;
85
86 80
        if (empty($usableOrientations)) {
87 71
            return null;
88
        }
89
90 80
        $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList);
91 80
        $sorter->setLogger($this->logger);
0 ignored issues
show
Bug introduced by
It seems like $this->logger can also be of type null; however, parameter $logger of DVDoug\BoxPacker\OrientatedItemSorter::setLogger() does only seem to accept Psr\Log\LoggerInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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