Passed
Push — master ( 2a57f9...1b5f50 )
by Will
02:57 queued 22s
created

src/Cart/OrderTotalCalculator.php (2 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
         
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 = new $className();
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