Passed
Push — 1.x ( 457fa0...308477 )
by Doug
03:11
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 6
Bugs 3 Features 0
Metric Value
cc 2
eloc 16
c 6
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 43
    public function __construct(Box $box)
39
    {
40 43
        $this->box = $box;
41 43
        $this->logger = new NullLogger();
42 43
    }
43
44 37
    public function setSinglePassMode($singlePassMode)
45
    {
46 37
        $this->singlePassMode = $singlePassMode;
47 37
    }
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 42
    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 42
        $this->logger->debug(
80 42
            "evaluating item {$item->getDescription()} for fit",
81
            [
82 42
                'item' => $item,
83
                'space' => [
84 42
                    'widthLeft' => $widthLeft,
85 42
                    'lengthLeft' => $lengthLeft,
86 42
                    'depthLeft' => $depthLeft,
87
                ],
88
            ]
89
        );
90
91 42
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft, $x, $y, $z, $prevPackedItemList);
92 42
        $usableOrientations = $this->getUsableOrientations($item, $possibleOrientations);
93
94 42
        if (empty($usableOrientations)) {
95 38
            return null;
96
        }
97
98 42
        $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList);
99 42
        $sorter->setLogger($this->logger);
100 42
        usort($usableOrientations, $sorter);
101
102 42
        $this->logger->debug('Selected best fit orientation', ['orientation' => $usableOrientations[0]]);
103
104 42
        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 43
    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 43
        $permutations = $this->generatePermutations($item, $prevItem);
134
135
        //remove any that simply don't fit
136 43
        $orientations = [];
137 43
        foreach ($permutations as $dimensions) {
138 43
            if ($dimensions[0] <= $widthLeft && $dimensions[1] <= $lengthLeft && $dimensions[2] <= $depthLeft) {
139 42
                $orientations[] = new OrientatedItem($item, $dimensions[0], $dimensions[1], $dimensions[2]);
140
            }
141
        }
142
143 43
        if ($item instanceof ConstrainedPlacementItem && !$this->box instanceof WorkingVolume) {
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 43
        return $orientations;
150
    }
151
152
    /**
153
     * @param  Item             $item
154
     * @return OrientatedItem[]
155
     */
156 19
    public function getPossibleOrientationsInEmptyBox(Item $item)
157
    {
158 19
        $cacheKey = $item->getWidth() .
159 19
            '|' .
160 19
            $item->getLength() .
161 19
            '|' .
162 19
            $item->getDepth() .
163 19
            '|' .
164 19
            $this->box->getInnerWidth() .
165 19
            '|' .
166 19
            $this->box->getInnerLength() .
167 19
            '|' .
168 19
            $this->box->getInnerDepth();
169
170 19
        if (isset(static::$emptyBoxCache[$cacheKey])) {
171 14
            $orientations = static::$emptyBoxCache[$cacheKey];
172
        } else {
173 18
            $orientations = $this->getPossibleOrientations(
174 18
                $item,
175 18
                null,
176 18
                $this->box->getInnerWidth(),
177 18
                $this->box->getInnerLength(),
178 18
                $this->box->getInnerDepth(),
179 18
                0,
180 18
                0,
181 18
                0,
182 18
                new PackedItemList()
183
            );
184 18
            static::$emptyBoxCache[$cacheKey] = $orientations;
185
        }
186
187 19
        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...
188
    }
189
190
    /**
191
     * @param Item             $item
192
     * @param OrientatedItem[] $possibleOrientations
193
     * @param bool             $isLastItem
194
     *
195
     * @return OrientatedItem[]
196
     */
197 42
    protected function getUsableOrientations(
198
        Item $item,
199
        array $possibleOrientations
200
    ) {
201 42
        $orientationsToUse = $stableOrientations = $unstableOrientations = [];
202
203
        // Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
204 42
        foreach ($possibleOrientations as $orientation) {
205 42
            if ($orientation->isStable() || $this->box->getInnerDepth() === $orientation->getDepth()) {
206 41
                $stableOrientations[] = $orientation;
207
            } else {
208 1
                $unstableOrientations[] = $orientation;
209
            }
210
        }
211
212
        /*
213
         * We prefer to use stable orientations only, but allow unstable ones if
214
         * the item doesn't fit in the box any other way
215
         */
216 42
        if (count($stableOrientations) > 0) {
217 41
            $orientationsToUse = $stableOrientations;
218 38
        } elseif (count($unstableOrientations) > 0) {
219 1
            $stableOrientationsInEmptyBox = $this->getStableOrientationsInEmptyBox($item);
220
221 1
            if (count($stableOrientationsInEmptyBox) === 0) {
222 1
                $orientationsToUse = $unstableOrientations;
223
            }
224
        }
225
226 42
        return $orientationsToUse;
227
    }
228
229
    /**
230
     * Return the orientations for this item if it were to be placed into the box with nothing else.
231
     *
232
     * @param  Item  $item
233
     * @return array
234
     */
235 1
    protected function getStableOrientationsInEmptyBox(Item $item)
236
    {
237 1
        $orientationsInEmptyBox = $this->getPossibleOrientationsInEmptyBox($item);
238
239 1
        return array_filter(
240 1
            $orientationsInEmptyBox,
241
            function (OrientatedItem $orientation) {
242 1
                return $orientation->isStable();
243 1
            }
244
        );
245
    }
246
247 43
    private function generatePermutations(Item $item, OrientatedItem $prevItem = null)
248
    {
249 43
        $permutations = [];
250
251
        //Special case items that are the same as what we just packed - keep orientation
252 43
        if ($prevItem && $prevItem->isSameDimensions($item)) {
253 35
            $permutations[] = [$prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth()];
254
        } else {
255
            //simple 2D rotation
256 43
            $permutations[] = [$item->getWidth(), $item->getLength(), $item->getDepth()];
257 43
            $permutations[] = [$item->getLength(), $item->getWidth(), $item->getDepth()];
258
        }
259
260 43
        return $permutations;
261
    }
262
}
263