Completed
Push — master ( 9610e0...320eee )
by Doug
08:43
created

src/OrientatedItemFactory.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Box packing (3D bin packing, knapsack problem)
4
 * @package BoxPacker
5
 * @author Doug Wright
6
 */
7
namespace DVDoug\BoxPacker;
8
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
12
/**
13
 * Figure out orientations for an item and a given set of dimensions
14
 * @author Doug Wright
15
 * @package BoxPacker
16
 */
17
class OrientatedItemFactory implements LoggerAwareInterface
18
{
19
    use LoggerAwareTrait;
20
21
    /**
22
     * @var OrientatedItem[]
23
     */
24
    static $emptyBoxCache = [];
0 ignored issues
show
The visibility should be declared for property $emptyBoxCache.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
25
26
    /**
27
     * Get the best orientation for an item
28
     * @param Box $box
29
     * @param Item $item
30
     * @param PackedItem|null $prevItem
31
     * @param bool $isLastItem
32
     * @param int $widthLeft
33
     * @param int $lengthLeft
34
     * @param int $depthLeft
35
     * @return OrientatedItem|false
36
     */
37 36
    public function getBestOrientation(Box $box, Item $item, PackedItem $prevItem = null, $isLastItem, $widthLeft, $lengthLeft, $depthLeft) {
38
39 36
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft);
40 36
        $usableOrientations = $this->getUsableOrientations($possibleOrientations, $box, $item, $prevItem, $isLastItem);
41
42 36
        $orientationFits = [];
43
        /** @var OrientatedItem $orientation */
44 36
        foreach ($usableOrientations as $o => $orientation) {
45 36
            $orientationFit = min($widthLeft - $orientation->getWidth(), $lengthLeft - $orientation->getLength());
46 36
            $orientationFits[$o] = $orientationFit;
47
        }
48
49 36
        if (!empty($orientationFits)) {
50 36
            asort($orientationFits);
51 36
            reset($orientationFits);
52 36
            $bestFit = $usableOrientations[key($orientationFits)];
53 36
            $this->logger->debug("Selected best fit orientation", ['orientation' => $bestFit]);
54 36
            return $bestFit;
55
        } else {
56 32
            return false;
57
        }
58
    }
59
60
    /**
61
     * Find all possible orientations for an item
62
     * @param Item $item
63
     * @param PackedItem|null $prevItem
64
     * @param int $widthLeft
65
     * @param int $lengthLeft
66
     * @param int $depthLeft
67
     * @return OrientatedItem[]
68
     */
69 36
    public function getPossibleOrientations(Item $item, PackedItem $prevItem = null, $widthLeft, $lengthLeft, $depthLeft) {
70
71 36
        $orientations = [];
72
73
        //Special case items that are the same as what we just packed - keep orientation
74
        /** @noinspection PhpNonStrictObjectEqualityInspection */
75 36
        if ($prevItem && $prevItem->getItem() == $item) {
76 13
            $orientations[] = new OrientatedItem($item, $prevItem->getWidth(), $prevItem->getLength(), $prevItem->getDepth());
77
        } else {
78
79
            //simple 2D rotation
80 36
            $orientations[] = new OrientatedItem($item, $item->getWidth(), $item->getLength(), $item->getDepth());
81 36
            $orientations[] = new OrientatedItem($item, $item->getLength(), $item->getWidth(), $item->getDepth());
82
83
            //add 3D rotation if we're allowed
84 36
            if (!$item->getKeepFlat()) {
85 13
                $orientations[] = new OrientatedItem($item, $item->getWidth(), $item->getDepth(), $item->getLength());
86 13
                $orientations[] = new OrientatedItem($item, $item->getLength(), $item->getDepth(), $item->getWidth());
87 13
                $orientations[] = new OrientatedItem($item, $item->getDepth(), $item->getWidth(), $item->getLength());
88 13
                $orientations[] = new OrientatedItem($item, $item->getDepth(), $item->getLength(), $item->getWidth());
89
            }
90
        }
91
92
        //remove any that simply don't fit
93 36
        return array_filter($orientations, function(OrientatedItem $i) use ($widthLeft, $lengthLeft, $depthLeft) {
94 36
            return $i->getWidth() <= $widthLeft && $i->getLength() <= $lengthLeft && $i->getDepth() <= $depthLeft;
95 36
        });
96
    }
97
98
    /**
99
     * @param Item $item
100
     * @param Box  $box
101
     * @return OrientatedItem[]
0 ignored issues
show
Should the return type not be OrientatedItem|OrientatedItem[]?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
102
     */
103 36
    public function getPossibleOrientationsInEmptyBox(Item $item, Box $box)
104
    {
105 36
        $cacheKey = $item->getWidth() .
106 36
            '|' .
107 36
            $item->getLength() .
108 36
            '|' .
109 36
            $item->getDepth() .
110 36
            '|' .
111 36
            ($item->getKeepFlat() ? '2D' : '3D') .
112 36
            '|' .
113 36
            $box->getInnerWidth() .
114 36
            '|' .
115 36
            $box->getInnerLength() .
116 36
            '|' .
117 36
            $box->getInnerDepth();
118
119 36
        if (isset(static::$emptyBoxCache[$cacheKey])) {
0 ignored issues
show
Since $emptyBoxCache is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $emptyBoxCache to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
120 31
            $orientations = static::$emptyBoxCache[$cacheKey];
0 ignored issues
show
Since $emptyBoxCache is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $emptyBoxCache to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
121
        } else {
122 33
            $orientations = $this->getPossibleOrientations(
123 33
                $item,
124 33
                null,
125 33
                $box->getInnerWidth(),
126 33
                $box->getInnerLength(),
127 33
                $box->getInnerDepth()
128
            );
129 33
            static::$emptyBoxCache[$cacheKey] = $orientations;
0 ignored issues
show
Since $emptyBoxCache is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $emptyBoxCache to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
130
        }
131 36
        return $orientations;
132
    }
133
134
    /**
135
     * @param OrientatedItem[] $possibleOrientations
136
     * @param Box              $box
137
     * @param Item             $item
138
     * @param PackedItem       $prevItem
0 ignored issues
show
Should the type for parameter $prevItem not be null|PackedItem?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
139
     * @param bool             $isLastItem
140
     *
141
     * @return array
142
     */
143 36
    protected function getUsableOrientations(
144
        $possibleOrientations,
145
        Box $box,
146
        Item $item,
147
        PackedItem $prevItem = null,
0 ignored issues
show
The parameter $prevItem is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
148
        $isLastItem
149
    ) {
150
        /*
151
         * Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
152
         */
153 36
        $stableOrientations = [];
154 36
        $unstableOrientations = [];
155
156 36
        foreach ($possibleOrientations as $o => $orientation) {
157 36
            if ($orientation->isStable()) {
158 31
                $stableOrientations[] = $orientation;
159
            } else {
160 36
                $unstableOrientations[] = $orientation;
161
            }
162
        }
163
164 36
        $orientationsToUse = [];
165
166
        /*
167
         * We prefer to use stable orientations only, but allow unstable ones if either
168
         * the item is the last one left to pack OR
169
         * the item doesn't fit in the box any other way
170
         */
171 36
        if (count($stableOrientations) > 0) {
172 31
            $orientationsToUse = $stableOrientations;
173 35
        } else if (count($unstableOrientations) > 0) {
174 6
            $orientationsInEmptyBox = $this->getPossibleOrientationsInEmptyBox($item, $box);
175
176 6
            $stableOrientationsInEmptyBox = array_filter(
177 6
                $orientationsInEmptyBox,
178 6
                function(OrientatedItem $orientation) {
179 6
                    return $orientation->isStable();
180 6
                }
181
            );
182
183
            if ($isLastItem || count($stableOrientationsInEmptyBox) == 0) {
184
                $orientationsToUse = $unstableOrientations;
185
            }
186
        }
187
188
        return $orientationsToUse;
189
    }
190
}
191
192