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

src/Cart/OrderTotalCalculator.php (1 issue)

Severity
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 = [
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()
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