Passed
Push — 2.x-dev ( eccc16...ab1c97 )
by Doug
07:42
created

OrientatedItemFactory::getBestOrientation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 39
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 2

Importance

Changes 8
Bugs 3 Features 0
Metric Value
cc 2
eloc 16
c 8
b 3
f 0
nc 2
nop 11
dl 0
loc 39
ccs 16
cts 16
cp 1
crap 2
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
namespace DVDoug\BoxPacker;
8
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
use Psr\Log\NullLogger;
12
13
/**
14
 * Figure out orientations for an item and a given set of dimensions.
15
 *
16
 * @author Doug Wright
17
 * @internal
18
 */
19
class OrientatedItemFactory implements LoggerAwareInterface
20
{
21
    use LoggerAwareTrait;
22
23
    /** @var Box */
24
    protected $box;
25
26
    /**
27
     * Whether the packer is in single-pass mode.
28
     *
29
     * @var bool
30
     */
31
    protected $singlePassMode = false;
32
33
    /**
34
     * @var OrientatedItem[]
35
     */
36
    protected static $emptyBoxCache = [];
37
38 52
    public function __construct(Box $box)
39
    {
40 52
        $this->box = $box;
41 52
        $this->logger = new NullLogger();
42 52
    }
43
44 46
    public function setSinglePassMode($singlePassMode)
45
    {
46 46
        $this->singlePassMode = $singlePassMode;
47 46
    }
48
49
    /**
50
     * Get the best orientation for an item.
51
     *
52
     * @param Item                $item
53
     * @param OrientatedItem|null $prevItem
54
     * @param ItemList            $nextItems
55
     * @param int                 $widthLeft
56
     * @param int                 $lengthLeft
57
     * @param int                 $depthLeft
58
     * @param int                 $rowLength
59
     * @param int                 $x
60
     * @param int                 $y
61
     * @param int                 $z
62
     * @param PackedItemList      $prevPackedItemList
63
     *
64
     * @return OrientatedItem|null
65
     */
66 51
    public function getBestOrientation(
67
        Item $item,
68
        OrientatedItem $prevItem = null,
69
        ItemList $nextItems,
70
        $widthLeft,
71
        $lengthLeft,
72
        $depthLeft,
73
        $rowLength,
74
        $x,
75
        $y,
76
        $z,
77
        PackedItemList $prevPackedItemList
78
    ) {
79 51
        $this->logger->debug(
80 51
            "evaluating item {$item->getDescription()} for fit",
81
            [
82 51
                'item' => $item,
83
                'space' => [
84 51
                    'widthLeft' => $widthLeft,
85 51
                    'lengthLeft' => $lengthLeft,
86 51
                    'depthLeft' => $depthLeft,
87
                ],
88
            ]
89
        );
90
91 51
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft, $x, $y, $z, $prevPackedItemList);
92 51
        $usableOrientations = $this->getUsableOrientations($item, $possibleOrientations);
93
94 51
        if (empty($usableOrientations)) {
95 46
            return null;
96
        }
97
98 51
        $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList);
99 51
        $sorter->setLogger($this->logger);
100 51
        usort($usableOrientations, $sorter);
101
102 51
        $this->logger->debug('Selected best fit orientation', ['orientation' => $usableOrientations[0]]);
103
104 51
        return $usableOrientations[0];
105
    }
106
107
    /**
108
     * Find all possible orientations for an item.
109
     *
110
     * @param Item                $item
111
     * @param OrientatedItem|null $prevItem
112
     * @param int                 $widthLeft
113
     * @param int                 $lengthLeft
114
     * @param int                 $depthLeft
115
     * @param int                 $x
116
     * @param int                 $y
117
     * @param int                 $z
118
     * @param PackedItemList      $prevPackedItemList
119
     *
120
     * @return OrientatedItem[]
121
     */
122 52
    public function getPossibleOrientations(
123
        Item $item,
124
        OrientatedItem $prevItem = null,
125
        $widthLeft,
126
        $lengthLeft,
127
        $depthLeft,
128
        $x,
129
        $y,
130
        $z,
131
        PackedItemList $prevPackedItemList
132
    ) {
133 52
        $permutations = $this->generatePermutations($item, $prevItem);
134
135
        //remove any that simply don't fit
136 52
        $orientations = [];
137 52
        foreach ($permutations as $dimensions) {
138 52
            if ($dimensions[0] <= $widthLeft && $dimensions[1] <= $lengthLeft && $dimensions[2] <= $depthLeft) {
139 51
                $orientations[] = new OrientatedItem($item, $dimensions[0], $dimensions[1], $dimensions[2]);
140
            }
141
        }
142
143 52
        if ($item instanceof ConstrainedPlacementItem) {
144
            $orientations = array_filter($orientations, function (OrientatedItem $i) use ($x, $y, $z, $prevPackedItemList) {
145 2
                return $i->getItem()->canBePacked($this->box, clone $prevPackedItemList, $x, $y, $z, $i->getWidth(), $i->getLength(), $i->getDepth());
146 2
            });
147
        }
148
149 52
        return $orientations;
150
    }
151
152
    /**
153
     * @param  Item             $item
154
     * @return OrientatedItem[]
155
     */
156 26
    public function getPossibleOrientationsInEmptyBox(Item $item)
157
    {
158 26
        $cacheKey = $item->getWidth() .
159 26
            '|' .
160 26
            $item->getLength() .
161 26
            '|' .
162 26
            $item->getDepth() .
163 26
            '|' .
164 26
            ($item->getKeepFlat() ? '2D' : '3D') .
165 26
            '|' .
166 26
            $this->box->getInnerWidth() .
167 26
            '|' .
168 26
            $this->box->getInnerLength() .
169 26
            '|' .
170 26
            $this->box->getInnerDepth();
171
172 26
        if (isset(static::$emptyBoxCache[$cacheKey])) {
173 19
            $orientations = static::$emptyBoxCache[$cacheKey];
174
        } else {
175 25
            $orientations = $this->getPossibleOrientations(
176 25
                $item,
177 25
                null,
178 25
                $this->box->getInnerWidth(),
179 25
                $this->box->getInnerLength(),
180 25
                $this->box->getInnerDepth(),
181 25
                0,
182 25
                0,
183 25
                0,
184 25
                new PackedItemList()
185
            );
186 25
            static::$emptyBoxCache[$cacheKey] = $orientations;
187
        }
188
189 26
        return $orientations;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $orientations also could return the type DVDoug\BoxPacker\OrientatedItem which is incompatible with the documented return type DVDoug\BoxPacker\OrientatedItem[].
Loading history...
190
    }
191
192
    /**
193
     * @param Item             $item
194
     * @param OrientatedItem[] $possibleOrientations
195
     * @param bool             $isLastItem
196
     *
197
     * @return OrientatedItem[]
198
     */
199 51
    protected function getUsableOrientations(
200
        Item $item,
201
        array $possibleOrientations
202
    ) {
203 51
        $orientationsToUse = $stableOrientations = $unstableOrientations = [];
204
205
        // Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
206 51
        foreach ($possibleOrientations as $orientation) {
207 51
            if ($orientation->isStable() || $this->box->getInnerDepth() === $orientation->getDepth()) {
208 49
                $stableOrientations[] = $orientation;
209
            } else {
210 8
                $unstableOrientations[] = $orientation;
211
            }
212
        }
213
214
        /*
215
         * We prefer to use stable orientations only, but allow unstable ones if
216
         * the item doesn't fit in the box any other way
217
         */
218 51
        if (count($stableOrientations) > 0) {
219 49
            $orientationsToUse = $stableOrientations;
220 46
        } elseif (count($unstableOrientations) > 0) {
221 8
            $stableOrientationsInEmptyBox = $this->getStableOrientationsInEmptyBox($item);
222
223 8
            if (count($stableOrientationsInEmptyBox) === 0) {
224 6
                $orientationsToUse = $unstableOrientations;
225
            }
226
        }
227
228 51
        return $orientationsToUse;
229
    }
230
231
    /**
232
     * Return the orientations for this item if it were to be placed into the box with nothing else.
233
     *
234
     * @param  Item  $item
235
     * @return array
236
     */
237 8
    protected function getStableOrientationsInEmptyBox(Item $item)
238
    {
239 8
        $orientationsInEmptyBox = $this->getPossibleOrientationsInEmptyBox($item);
240
241 8
        return array_filter(
242 8
            $orientationsInEmptyBox,
243
            function (OrientatedItem $orientation) {
244 8
                return $orientation->isStable();
245 8
            }
246
        );
247
    }
248
249 52
    private function generatePermutations(Item $item, OrientatedItem $prevItem = null)
250
    {
251 52
        $permutations = [];
252
253
        //Special case items that are the same as what we just packed - keep orientation
254 52
        if ($prevItem && $prevItem->isSameDimensions($item)) {
255 41
            $permutations[] = [$prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth()];
256
        } else {
257
            //simple 2D rotation
258 52
            $permutations[] = [$item->getWidth(), $item->getLength(), $item->getDepth()];
259 52
            $permutations[] = [$item->getLength(), $item->getWidth(), $item->getDepth()];
260
261
            //add 3D rotation if we're allowed
262 52
            if (!$item->getKeepFlat()) {
263 34
                $permutations[] = [$item->getWidth(), $item->getDepth(), $item->getLength()];
264 34
                $permutations[] = [$item->getLength(), $item->getDepth(), $item->getWidth()];
265 34
                $permutations[] = [$item->getDepth(), $item->getWidth(), $item->getLength()];
266 34
                $permutations[] = [$item->getDepth(), $item->getLength(), $item->getWidth()];
267
            }
268
        }
269
270 52
        return $permutations;
271
    }
272
}
273