Passed
Push — master ( 54e3cc...a9003e )
by Dmitry
14:11
created

ProgressivePriceThresholdList   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 137
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 20
eloc 52
c 1
b 0
f 0
dl 0
loc 137
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A jsonSerialize() 0 4 1
A checkCanBeAdded() 0 4 1
A checkUnit() 0 14 3
A checkCurrency() 0 14 3
A __construct() 0 9 3
A get() 0 5 1
A fromScalarsArray() 0 10 1
A prepareThresholds() 0 7 2
A least() 0 3 1
A __toArray() 0 7 2
A appendThresholds() 0 3 1
A withAdded() 0 8 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace hiqdev\php\billing\price;
6
7
use hiqdev\php\billing\Money\MultipliedMoney;
8
use JsonSerializable;
9
use hiqdev\php\units\Quantity;
10
use InvalidArgumentException;
11
12
final class ProgressivePriceThresholdList implements JsonSerializable
13
{
14
    /** @var ProgressivePriceThreshold[] */
15
    private array $thresholds;
16
17
    /**
18
     * @param ProgressivePriceThreshold[] $thresholds
19
     */
20
    public function __construct(array $thresholds)
21
    {
22
        foreach ($thresholds as $threshold) {
23
            $this->checkCanBeAdded($threshold);
24
            $this->appendThresholds($threshold);
25
        }
26
27
        if (empty($this->thresholds)) {
28
            throw new InvalidArgumentException('Progressive price thresholds must not be empty');
29
        }
30
    }
31
32
    /**
33
     * @param array{
34
     *     price: numeric-string,
35
     *     currency: string,
36
     *     quantity: numeric-string,
37
     *     unit: string
38
     * } $thresholds
39
     */
40
    public static function fromScalarsArray(array $thresholds): self
41
    {
42
        return new self(array_map(function ($threshold) {
43
            return ProgressivePriceThreshold::createFromScalar(
44
                $threshold['price'],
45
                $threshold['currency'],
46
                $threshold['quantity'],
47
                $threshold['unit']
48
            );
49
        }, $thresholds));
50
    }
51
52
    private function checkCanBeAdded(ProgressivePriceThreshold $threshold): void
53
    {
54
        $this->checkCurrency($threshold->price());
55
        $this->checkUnit($threshold->quantity());
56
    }
57
58
    public function withAdded(ProgressivePriceThreshold $threshold): self
59
    {
60
        $this->checkCanBeAdded($threshold);
61
62
        $self = clone $this;
63
        $self->appendThresholds($threshold);
64
65
        return $self;
66
    }
67
68
    /**
69
     * @return ProgressivePriceThreshold[]
70
     */
71
    public function get(): array
72
    {
73
        $this->prepareThresholds();
74
75
        return $this->thresholds;
76
    }
77
78
    public function least(): ProgressivePriceThreshold
79
    {
80
        return $this->thresholds[0];
81
    }
82
83
    private function prepareThresholds(): void
84
    {
85
        usort($this->thresholds, function (ProgressivePriceThreshold $a, ProgressivePriceThreshold $b) {
86
            if ($b->quantity()->convert($a->quantity()->getUnit())->equals($a->quantity())) {
87
                return $b->price()->getAmount() <=> $a->price()->getAmount();
88
            }
89
            return $b->quantity()->convert($a->quantity()->getUnit())->getQuantity() <=> $a->quantity()->getQuantity();
90
        });
91
    }
92
93
    private function checkCurrency(MultipliedMoney $price): void
94
    {
95
        if (empty($this->thresholds)) {
96
            return;
97
        }
98
99
        $last = $this->thresholds[array_key_last($this->thresholds)];
100
101
        if (!$last->price()->getCurrency()->equals($price->getCurrency())) {
102
            throw new InvalidArgumentException(
103
                sprintf(
104
                    "Progressive price thresholds must have the same currency, last is %s, new is %s",
105
                    $last->price()->getCurrency()->getCode(),
106
                    $price->getCurrency()->getCode()
107
                )
108
            );
109
        }
110
    }
111
112
    private function checkUnit(Quantity $prepaid): void
113
    {
114
        if (empty($this->thresholds)) {
115
            return;
116
        }
117
118
        $last = $this->thresholds[array_key_last($this->thresholds)];
119
120
        if (!$last->quantity()->getUnit()->isConvertible($prepaid->getUnit())) {
121
            throw new InvalidArgumentException(
122
                sprintf(
123
                    "Progressive price thresholds must be of the same unit family, last is %s, new is %s",
124
                    $last->quantity()->getUnit()->getName(),
125
                    $prepaid->getUnit()->getName()
126
                )
127
            );
128
        }
129
    }
130
131
    private function appendThresholds(ProgressivePriceThreshold $threshold): void
132
    {
133
        $this->thresholds[] = $threshold;
134
    }
135
136
    public function __toArray(): array
137
    {
138
        $result = [];
139
        foreach ($this->thresholds as $threshold) {
140
            $result[] = $threshold->__toArray();
141
        }
142
        return $result;
143
    }
144
145
    #[\ReturnTypeWillChange]
146
    public function jsonSerialize()
147
    {
148
        return $this->thresholds;
149
    }
150
}
151