Completed
Push — master ( e83455...d2af9f )
by Andrii
06:07 queued 04:16
created

src/charge/modifiers/addons/Discount.php (2 issues)

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 hiqdev\php\billing\price\SinglePrice;
18
use hiqdev\php\units\Quantity;
19
use hiqdev\php\units\QuantityInterface;
20
use Money\Currencies\ISOCurrencies;
21
use Money\Currency;
22
use Money\Money;
23
use Money\Parser\DecimalMoneyParser;
24
25
/**
26
 * Discount addon.
27
 *
28
 * @author Andrii Vasyliev <[email protected]>
29
 */
30
class Discount implements AddonInterface
31
{
32
    protected static $name = 'discount';
33
34
    /**
35
     * @var string|Money
36
     */
37
    protected $value;
38
39
    protected $moneyParser;
40
41 26
    public function __construct($value)
42
    {
43 26
        $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies());
44 26
        $this->value = $this->ensureValidValue($value);
45 26
    }
46
47 11
    public function getValue()
48
    {
49 11
        return $this->value instanceof PercentPoint ? $this->value->getNumber() : $this->value;
50
    }
51
52 12
    public function isAbsolute()
53
    {
54 12
        return $this->value instanceof Money;
55
    }
56
57 6
    public function isRelative()
58
    {
59 6
        return !$this->isAbsolute();
60
    }
61
62 2
    public function isPercentPoint(): bool
63
    {
64 2
        return $this->value instanceof PercentPoint;
65
    }
66
67 26
    public function ensureValidValue($value)
68
    {
69 26
        if ($value instanceof self) {
70
            return $value->getValue();
71
        }
72
73 26
        if ($value instanceof Money || $value instanceof PercentPoint || $value instanceof Quantity) {
74 8
            return $value;
75
        }
76
77 25
        if (is_numeric($value)) {
78 10
            return (string)$value;
79
        }
80
81 22
        $name = static::$name;
82 22
        if (\is_string($value)) {
83 22
            if (preg_match('/^(\d{1,5}(\.\d+)?)(%|pp| [A-Z]{3})$/', $value, $ms)) {
84 22
                if ($ms[3] === '%') {
85 20
                    return $ms[1];
86
                }
87 16
                if ($ms[3] === 'pp') {
88
                    return new PercentPoint($ms[1]);
89
                }
90
91 16
                return $this->moneyParser->parse($ms[1], new Currency(trim($ms[3])));
92
            }
93
94 4
            if (preg_match('/^(\d{1,5}(\.\d+)?) ([a-z]{2,})$/', $value, $ms)) {
95
                try {
96
                    return Quantity::create($ms[3], $ms[1]);
97
                } catch (\Exception $e) {
98
                    throw new FormulaSemanticsError("invalid $name value: $value");
99
                }
100
            }
101
        }
102
103 4
        throw new FormulaSemanticsError("invalid $name value: $value");
104
    }
105
106 6
    public function multiply($multiplier)
107
    {
108 6
        if (!is_numeric($multiplier)) {
109 3
            throw new FormulaSemanticsError('multiplier for discount must be numeric');
110
        }
111
112 3
        return new static($this->isAbsolute() ? $this->value->multiply($multiplier) : $this->getValue()*$multiplier);
113
    }
114
115 9
    public function add($addend)
116
    {
117 9
        if (!$addend instanceof self) {
118 7
            $addend = new self($addend);
119
        }
120 5
        $this->ensureSameType($addend, 'addend');
121
122 3
        if ($this->isAbsolute()) {
123 3
            $sum = $this->getValue()->add($addend->getValue());
124
        } else {
125 1
            $sum = $this->getValue() + $addend->getValue();
126
        }
127
128 3
        return new static($sum);
129
    }
130
131 1
    public function compare($other)
132
    {
133 1
        if (!$other instanceof self) {
134 1
            $other = new self($other);
135
        }
136 1
        $this->ensureSameType($other, 'comparison argument');
137
138 1
        if ($this->isAbsolute()) {
139 1
            return $this->value->compare($other->getValue());
140
        } else {
141 1
            return $this->value - $other->getValue();
142
        }
143
    }
144
145 6
    public function ensureSameType(self $other, $name)
146
    {
147 6
        if ($this->isRelative() && !$other->isRelative()) {
148 1
            throw new FormulaSemanticsError("$name must be relative");
149
        }
150 5
        if ($this->isAbsolute() && !$other->isAbsolute()) {
151 1
            throw new FormulaSemanticsError("$name must be absolute");
152
        }
153 4
    }
154
155 4
    public function calculateSum(ChargeInterface $charge): Money
156
    {
157 4
        if ($this->value instanceof Money) {
158 2
            return $this->value->multiply($charge->getUsage()->getQuantity());
159
        }
160
161 2
        if ($this->value instanceof Quantity) {
162
            $price = $charge->getPrice();
163
            if (!$price instanceof SinglePrice) {
164
                throw new \InvalidArgumentException('Discount based on quantity supports only single prices');
165
            }
166
167
            $compensatedQuantity = $this->calculateCompensatedQuantity($charge)->add($price->getPrepaid());
168
            return $price->calculateSum($compensatedQuantity);
169
        }
170
171
        return $charge->getSum()->multiply($this->value * 0.01);
172 2
    }
173
174
    private function calculateCompensatedQuantity(ChargeInterface $charge): QuantityInterface
175
    {
176
        $usage = $charge->getUsage();
177
        $maxCompensatedAmount = $this->getValue();
178
179
        switch ($usage->compare($maxCompensatedAmount)) {
180
            case -1:
181
                return $usage;
182
            case 0:
183
            case 1:
184
                return $maxCompensatedAmount;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $maxCompensatedAmount returns the type Money\Money|string which is incompatible with the type-hinted return hiqdev\php\units\QuantityInterface.
Loading history...
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return hiqdev\php\units\QuantityInterface. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
185
        }
186
    }
187
}
188