Completed
Push — master ( 3c661d...2a57f9 )
by Will
26s queued 12s
created

src/Cart/OrderTotalCalculator.php (3 issues)

1
<?php
2
3
namespace SilverShop\Cart;
4
5
use ErrorException;
6
use Exception;
7
use Monolog\Logger;
8
use SilverShop\Model\Order;
9
use SilverStripe\Core\ClassInfo;
10
use SilverStripe\Core\Injector\Injectable;
11
use SilverStripe\ORM\DB;
12
13
/**
14
 * Handles the calculation of order totals.
15
 *
16
 * Creates (if necessary) and calculates values for each modifier,
17
 * and subsequently the total of the order.
18
 * Caches to prevent recalculation, unless dirty.
19
 */
20
class OrderTotalCalculator
21
{
22
    use Injectable;
23
24
    private static $dependencies = [
0 ignored issues
show
The private property $dependencies is not used, and could be removed.
Loading history...
25
        'logger' => '%$SilverShop\Logger',
26
    ];
27
28
    /**
29
     * @var Logger
30
     */
31
    public $logger;
32
33
    /**
34
     * @var Order
35
     */
36
    protected $order;
37
38
    function __construct(Order $order)
39
    {
40
        $this->order = $order;
41
    }
42
43
    /**
44
     * @return float
45
     * @throws Exception
46
     * @throws \Psr\Container\NotFoundExceptionInterface
47
     */
48
    function calculate()
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
49
    {
50
        $runningtotal = $this->order->SubTotal();
51
        $sort = 1;
52
        $existingmodifiers = $this->order->Modifiers();
53
        $modifierclasses = array_unique(Order::config()->modifiers);
54
55
        //check if modifiers are even in use
56
        if (!is_array($modifierclasses) || empty($modifierclasses)) {
0 ignored issues
show
The condition is_array($modifierclasses) is always true.
Loading history...
57
            return $runningtotal;
58
        }
59
60
        if (DB::get_conn()->supportsTransactions()) {
61
            DB::get_conn()->transactionStart();
62
        }
63
64
        set_error_handler(
65
            function ($severity, $message, $file, $line) {
66
                throw new ErrorException($message, 0, $severity, $file, $line);
67
            },
68
            E_ALL & ~(E_STRICT | E_NOTICE)
69
        );
70
71
        try {
72
            foreach ($modifierclasses as $ClassName) {
73
                if ($modifier = $this->getModifier($ClassName)) {
74
                    $modifier->Sort = $sort;
75
                    $runningtotal = $modifier->modify($runningtotal);
76
                    if ($modifier->isChanged()) {
77
                        $modifier->write();
78
                    }
79
                }
80
                $sort++;
81
            }
82
            //clear old modifiers out
83
            if ($existingmodifiers) {
84
                foreach ($existingmodifiers as $modifier) {
85
                    if (!in_array($modifier->ClassName, $modifierclasses)) {
86
                        $modifier->delete();
87
                        $modifier->destroy();
88
                    }
89
                }
90
            }
91
        } catch (Exception $ex) {
92
            // Rollback the transaction if an error occurred
93
            if (DB::get_conn()->supportsTransactions()) {
94
                DB::get_conn()->transactionRollback();
95
            }
96
            // throw the exception after rollback
97
            throw $ex;
98
        } finally {
99
            // restore the error handler, no matter what
100
            restore_error_handler();
101
        }
102
103
        // Everything went through fine, complete the transaction
104
        if (DB::get_conn()->supportsTransactions()) {
105
            DB::get_conn()->transactionEnd();
106
        }
107
108
        //prevent negative sales from ever occurring
109
        if ($runningtotal < 0) {
110
            $this->logger->error(
111
                "Order (ID = {$this->order->ID}) was calculated to equal $runningtotal.\n
112
                Order totals should never be negative!\n
113
                The order total was set to $0"
114
            );
115
116
            $runningtotal = 0;
117
        }
118
119
        return $runningtotal;
120
    }
121
122
    /**
123
     * Retrieve a modifier of a given class for the order.
124
     * Modifier will be retrieved from database if it already exists,
125
     * or created if it is always required.
126
     *
127
     * @param string  $className
128
     * @param boolean $forcecreate - force the modifier to be created.
129
     */
130
    public function getModifier($className, $forcecreate = false)
131
    {
132
        if (!ClassInfo::exists($className)) {
133
            user_error("Modifier class \"$className\" does not exist.");
134
        }
135
        //search for existing
136
        $modifier = $className::get()
137
            ->filter("OrderID", $this->order->ID)
138
            ->first();
139
        if ($modifier) {
140
            //remove if no longer valid
141
            if (!$modifier->valid()) {
142
                //TODO: need to provide feedback message - why modifier was removed
143
                $modifier->delete();
144
                $modifier->destroy();
145
                return null;
146
            }
147
            return $modifier;
148
        }
149
        $modifier = new $className();
150
        if ($modifier->required() || $forcecreate) { //create any modifiers that are required for every order
151
            $modifier->OrderID = $this->order->ID;
152
            $modifier->write();
153
            $this->order->Modifiers()->add($modifier);
154
155
            return $modifier;
156
        }
157
158
        return null;
159
    }
160
}
161