Completed
Pull Request — 2.0 (#506)
by Roman
18:39
created

OrderTotalCalculator::calculate()   F

Complexity

Conditions 14
Paths 961

Size

Total Lines 70
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 14.757

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 70
ccs 43
cts 51
cp 0.8431
rs 2.8571
cc 14
eloc 40
nc 961
nop 0
crap 14.757

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 15
    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 15
        $this->order = $order;
17 15
    }
18
19 15
    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 15
        $runningtotal = $this->order->SubTotal();
22 15
        $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 15
        $sort = 1;
24 15
        $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 15
        $modifierclasses = Order::config()->modifiers;
26
        //check if modifiers are even in use
27 15
        if (!is_array($modifierclasses) || empty($modifierclasses)) {
28 12
            return $runningtotal;
29
        }
30
31 4
        if (DB::get_conn()->supportsTransactions()) {
32 4
            DB::get_conn()->transactionStart();
33 4
        }
34
35 4
        set_error_handler(function ($severity, $message, $file, $line) {
36 1
            throw new ErrorException($message, 0, $severity, $file, $line);
37 4
        }, E_ALL & ~(E_STRICT | E_NOTICE));
38
39
        try {
40 4
            foreach ($modifierclasses as $ClassName) {
41 4
                if ($modifier = $this->getModifier($ClassName)) {
42 4
                    $modifier->Sort = $sort;
43 4
                    $runningtotal = $modifier->modify($runningtotal);
44 4
                    if ($modifier->isChanged()) {
45 4
                        $modifier->write();
46 4
                    }
47 4
                }
48 4
                $sort++;
49 4
            }
50
            //clear old modifiers out
51 4
            if ($existingmodifiers) {
52 4
                foreach ($existingmodifiers as $modifier) {
53 4
                    if (!in_array($modifier->ClassName, $modifierclasses)) {
54
                        $modifier->delete();
55
                        $modifier->destroy();
56
                    }
57 4
                }
58 4
            }
59 4
        } catch (Exception $ex) {
60
            // Rollback the transaction if an error occurred
61 1
            if (DB::get_conn()->supportsTransactions()) {
62 1
                DB::get_conn()->transactionRollback();
63 1
            }
64
            // throw the exception after rollback
65 1
            throw $ex;
66 4
        } finally {
67
            // restore the error handler, no matter what
68 4
            restore_error_handler();
69
        }
70
71
        // Everything went through fine, complete the transaction
72 4
        if (DB::get_conn()->supportsTransactions()) {
73 4
            DB::get_conn()->transactionEnd();
74 4
        }
75
76
        //prevent negative sales from ever occurring
77 4
        if ($runningtotal < 0) {
78 4
            SS_Log::log(
79
                "Order (ID = {$this->order->ID}) was calculated to equal $runningtotal.\n
80
				Order totals should never be negative!\n
81
				The order total was set to $0",
82
                SS_Log::ERR
83
            );
84
            $runningtotal = 0;
85
        }
86
87 4
        return $runningtotal;
88 4
    }
89
90
    /**
91
     * Retrieve a modifier of a given class for the order.
92
     * Modifier will be retrieved from database if it already exists,
93
     * or created if it is always required.
94
     *
95
     * @param string  $className
96
     * @param boolean $forcecreate - force the modifier to be created.
97
     */
98 4
    public function getModifier($className, $forcecreate = false)
99
    {
100 4
        if (!ClassInfo::exists($className)) {
101
            user_error("Modifier class \"$className\" does not exist.");
102
        }
103
        //search for existing
104 4
        $modifier = $className::get()
105 4
            ->filter("OrderID", $this->order->ID)
106 4
            ->first();
107 4
        if ($modifier) {
108
            //remove if no longer valid
109 2
            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 2
            return $modifier;
116
        }
117 4
        $modifier = new $className();
118 4
        if ($modifier->required() || $forcecreate) { //create any modifiers that are required for every order
119 4
            $modifier->OrderID = $this->order->ID;
120 4
            $modifier->write();
121 4
            $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 4
            return $modifier;
124
        }
125
126
        return null;
127
    }
128
}
129