OrderTotalCalculator::calculate()   F
last analyzed

Complexity

Conditions 14
Paths 769

Size

Total Lines 75
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 39
c 1
b 0
f 0
nc 769
nop 0
dl 0
loc 75
rs 2.4208

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Sam Minnée
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
Best Practice introduced by Mark Guinn
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