OrderTotalCalculator::getModifier()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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