Completed
Pull Request — 2.0 (#506)
by Roman
40:49 queued 14:31
created

OrderTotalCalculator::calculate()   F

Complexity

Conditions 14
Paths 961

Size

Total Lines 70
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 19.1816

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 70
ccs 33
cts 47
cp 0.702
rs 2.8571
cc 14
eloc 40
nc 961
nop 0
crap 19.1816

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
/**
4
 * Handles the calculation of order totals.
5
 *
6
 * Creates (if necessary) and calculates values for each modifier,
7
 * and subsequently the total of the order.
8
 * Caches to prevent recalculation, unless dirty.
9
 */
10
class OrderTotalCalculator
11
{
12
    protected $order;
13
14 13
    function __construct(Order $order)
0 ignored issues
show
Best Practice introduced by
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...
15
    {
16 13
        $this->order = $order;
17 13
    }
18
19 13
    function calculate()
0 ignored issues
show
Best Practice introduced by
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...
Complexity introduced by
This operation has 456 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
20
    {
21 13
        $runningtotal = $this->order->SubTotal();
22 13
        $modifiertotal = 0;
0 ignored issues
show
Unused Code introduced by
$modifiertotal is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
23 13
        $sort = 1;
24 13
        $existingmodifiers = $this->order->Modifiers();
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
25 13
        $modifierclasses = Order::config()->modifiers;
26
        //check if modifiers are even in use
27 13
        if (!is_array($modifierclasses) || empty($modifierclasses)) {
28 11
            return $runningtotal;
29
        }
30 3
31 3
        if (DB::get_conn()->supportsTransactions()) {
32 3
            DB::get_conn()->transactionStart();
33 3
        }
34 3
35 3
        set_error_handler(function ($severity, $message, $file, $line) {
36 3
            throw new ErrorException($message, 0, $severity, $file, $line);
37 3
        }, E_ALL & ~(E_STRICT | E_NOTICE));
38 3
39 3
        try {
40
            foreach ($modifierclasses as $ClassName) {
41 3
                if ($modifier = $this->getModifier($ClassName)) {
42 3
                    $modifier->Sort = $sort;
43 3
                    $runningtotal = $modifier->modify($runningtotal);
44
                    if ($modifier->isChanged()) {
45
                        $modifier->write();
46
                    }
47 3
                }
48 3
                $sort++;
49
            }
50 3
            //clear old modifiers out
51
            if ($existingmodifiers) {
52
                foreach ($existingmodifiers as $modifier) {
53
                    if (!in_array($modifier->ClassName, $modifierclasses)) {
54
                        $modifier->delete();
55
                        $modifier->destroy();
56
                    }
57
                }
58
            }
59
        } catch (Exception $ex) {
60 3
            // Rollback the transaction if an error occurred
61
            if (DB::get_conn()->supportsTransactions()) {
62
                DB::get_conn()->transactionRollback();
63
            }
64
            // throw the exception after rollback
65
            throw $ex;
66
        } finally {
67
            // restore the error handler, no matter what
68
            restore_error_handler();
69
        }
70
71 3
        // Everything went through fine, complete the transaction
72
        if (DB::get_conn()->supportsTransactions()) {
73 3
            DB::get_conn()->transactionEnd();
74
        }
75
76
        //prevent negative sales from ever occurring
77 3
        if ($runningtotal < 0) {
78 3
            SS_Log::log(
79 3
                "Order (ID = {$this->order->ID}) was calculated to equal $runningtotal.\n
80 3
				Order totals should never be negative!\n
81
				The order total was set to $0",
82 1
                SS_Log::ERR
83
            );
84
            $runningtotal = 0;
85
        }
86
87
        return $runningtotal;
88 3
    }
89 3
90 3
    /**
91 3
     * Retrieve a modifier of a given class for the order.
92 3
     * Modifier will be retrieved from database if it already exists,
93 3
     * or created if it is always required.
94 3
     *
95
     * @param string  $className
96 3
     * @param boolean $forcecreate - force the modifier to be created.
97
     */
98
    public function getModifier($className, $forcecreate = false)
99
    {
100
        if (!ClassInfo::exists($className)) {
101
            user_error("Modifier class \"$className\" does not exist.");
102 1
        }
103
        //search for existing
104
        $modifier = $className::get()
105
            ->filter("OrderID", $this->order->ID)
106
            ->first();
107
        if ($modifier) {
108
            //remove if no longer valid
109
            if (!$modifier->valid()) {
110
                //TODO: need to provide feedback message - why modifier was removed
111
                $modifier->delete();
112
                $modifier->destroy();
113
                return null;
114
            }
115
            return $modifier;
116
        }
117
        $modifier = new $className();
118
        if ($modifier->required() || $forcecreate) { //create any modifiers that are required for every order
119
            $modifier->OrderID = $this->order->ID;
120
            $modifier->write();
121
            $this->order->Modifiers()->add($modifier);
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
122
123
            return $modifier;
124
        }
125
126
        return null;
127
    }
128
}
129