Completed
Push — master ( 5a3b7b...198f8c )
by Andrii
02:27
created

Discount   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Test Coverage

Coverage 96.43%

Importance

Changes 0
Metric Value
eloc 49
dl 0
loc 120
ccs 54
cts 56
cp 0.9643
rs 9.92
c 0
b 0
f 0
wmc 31

11 Methods

Rating   Name   Duplication   Size   Complexity  
A isRelative() 0 3 1
B ensureValidValue() 0 27 9
A getValue() 0 3 2
A add() 0 14 3
A calculateSum() 0 5 2
A multiply() 0 7 3
A ensureSameType() 0 7 5
A isPercentPoint() 0 3 1
A compare() 0 11 3
A isAbsolute() 0 3 1
A __construct() 0 4 1
1
<?php
2
/**
3
 * PHP Billing Library
4
 *
5
 * @link      https://github.com/hiqdev/php-billing
6
 * @package   php-billing
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2017-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\php\billing\charge\modifiers\addons;
12
13
use hiqdev\php\billing\charge\ChargeInterface;
14
use hiqdev\php\billing\charge\modifiers\AddonInterface;
15
use hiqdev\php\billing\charge\modifiers\PercentPoint;
16
use hiqdev\php\billing\formula\FormulaSemanticsError;
17
use Money\Currencies\ISOCurrencies;
18
use Money\Currency;
19
use Money\Money;
20
use Money\Parser\DecimalMoneyParser;
21
22
/**
23
 * Discount addon.
24
 *
25
 * @author Andrii Vasyliev <[email protected]>
26
 */
27
class Discount implements AddonInterface
28
{
29
    protected static $name = 'discount';
30
31
    /**
32
     * @var string|Money
33
     */
34
    protected $value;
35
36
    protected $moneyParser;
37
38 26
    public function __construct($value)
39
    {
40 26
        $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies());
41 26
        $this->value = $this->ensureValidValue($value);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->ensureValidValue($value) can also be of type hiqdev\php\billing\charge\modifiers\PercentPoint. However, the property $value is declared as type Money\Money|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
42 26
    }
43
44 11
    public function getValue()
45
    {
46 11
        return $this->value instanceof PercentPoint ? $this->value->getNumber() : $this->value;
0 ignored issues
show
introduced by
$this->value is never a sub-type of hiqdev\php\billing\charge\modifiers\PercentPoint.
Loading history...
47
    }
48
49 12
    public function isAbsolute()
50
    {
51 12
        return $this->value instanceof Money;
52
    }
53
54 6
    public function isRelative()
55
    {
56 6
        return !$this->isAbsolute();
57
    }
58
59 2
    public function isPercentPoint(): bool
60
    {
61 2
        return $this->value instanceof PercentPoint;
62
    }
63
64 26
    public function ensureValidValue($value)
65
    {
66 26
        if ($value instanceof self) {
67
            return $value->getValue();
68
        }
69
70 26
        if ($value instanceof Money || $value instanceof PercentPoint) {
71 8
            return $value;
72
        }
73
74 24
        if (is_numeric($value)) {
75 10
            return (string) $value;
76
        }
77
78 20
        if (is_string($value) && preg_match('/^(\d{1,5}(\.\d+)?)(%|pp| [A-Z]{3})$/', $value, $ms)) {
79 20
            if ($ms[3] === '%') {
80 18
                return $ms[1];
81
            }
82 16
            if ($ms[3] === 'pp') {
83
                return new PercentPoint($ms[1]);
84
            }
85
86 16
            return $this->moneyParser->parse($ms[1], new Currency(trim($ms[3])));
87
        }
88
89 4
        $name = static::$name;
90 4
        throw new FormulaSemanticsError("invalid $name value: $value");
91
    }
92
93 6
    public function multiply($multiplier)
94
    {
95 6
        if (!is_numeric($multiplier)) {
96 3
            throw new FormulaSemanticsError('multiplier for discount must be numeric');
97
        }
98
99 3
        return new static($this->isAbsolute() ? $this->value->multiply($multiplier) : $this->getValue()*$multiplier);
100
    }
101
102 9
    public function add($addend)
103
    {
104 9
        if (!$addend instanceof self) {
105 7
            $addend = new self($addend);
106
        }
107 5
        $this->ensureSameType($addend, 'addend');
108
109 3
        if ($this->isAbsolute()) {
110 3
            $sum = $this->getValue()->add($addend->getValue());
0 ignored issues
show
Bug introduced by
It seems like $addend->getValue() can also be of type string; however, parameter $addend of Money\Money::add() does only seem to accept Money\Money, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
            $sum = $this->getValue()->add(/** @scrutinizer ignore-type */ $addend->getValue());
Loading history...
111
        } else {
112 1
            $sum = $this->getValue() + $addend->getValue();
113
        }
114
115 3
        return new static($sum);
116
    }
117
118 1
    public function compare($other)
119
    {
120 1
        if (!$other instanceof self) {
121 1
            $other = new self($other);
122
        }
123 1
        $this->ensureSameType($other, 'comparison argument');
124
125 1
        if ($this->isAbsolute()) {
126 1
            return $this->value->compare($other->getValue());
0 ignored issues
show
Bug introduced by
It seems like $other->getValue() can also be of type string; however, parameter $other of Money\Money::compare() does only seem to accept Money\Money, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

126
            return $this->value->compare(/** @scrutinizer ignore-type */ $other->getValue());
Loading history...
127
        } else {
128 1
            return $this->value - $other->getValue();
129
        }
130
    }
131
132 6
    public function ensureSameType(self $other, $name)
133
    {
134 6
        if ($this->isRelative() && !$other->isRelative()) {
135 1
            throw new FormulaSemanticsError("$name must be relative");
136
        }
137 5
        if ($this->isAbsolute() && !$other->isAbsolute()) {
138 1
            throw new FormulaSemanticsError("$name must be absolute");
139
        }
140 4
    }
141
142 4
    public function calculateSum(ChargeInterface $charge): Money
143
    {
144 4
        return $this->value instanceof Money
145 2
            ? $this->value->multiply($charge->getUsage()->getQuantity())
146 4
            : $charge->getSum()->multiply($this->value * 0.01)
147
        ;
148
    }
149
}
150