AverageExchangeRateProvider::getExchangeRate()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 4
nc 6
nop 3
1
<?php
2
3
namespace BenTools\Currency\Provider;
4
5
use BenTools\Currency\Model\CurrencyInterface;
6
use BenTools\Currency\Model\ExchangeRate;
7
use BenTools\Currency\Model\ExchangeRateFactoryInterface;
8
use BenTools\Currency\Model\ExchangeRateInterface;
9
use BenTools\Currency\Model\ExchangeRateNotFoundException;
10
use BenTools\Currency\Model\NativeExchangeRateFactory;
11
use DateTimeInterface;
12
13
final class AverageExchangeRateProvider implements ExchangeRateProviderInterface
14
{
15
    /**
16
     * @var ExchangeRateProviderInterface[]
17
     */
18
    private $exchangeRateProviders = [];
19
20
    /**
21
     * @var float
22
     */
23
    private $tolerance;
24
25
    /**
26
     * @var ExchangeRateFactoryInterface
27
     */
28
    private $exchangeRateFactory;
29
30
    /**
31
     * AverageExchangeRateProvider constructor.
32
     * @param float|null                        $tolerance
33
     * @param ExchangeRateFactoryInterface|null $exchangeRateFactory
34
     */
35
    public function __construct(float $tolerance = null, ExchangeRateFactoryInterface $exchangeRateFactory = null)
36
    {
37
        $this->tolerance = $tolerance;
38
        $this->exchangeRateFactory = $exchangeRateFactory ?? new NativeExchangeRateFactory();
39
    }
40
41
    /**
42
     * @param ExchangeRateProviderInterface[] ...$exchangeRateProviders
43
     * @return AverageExchangeRateProvider
44
     */
45
    public function withProviders(ExchangeRateProviderInterface ...$exchangeRateProviders): self
46
    {
47
        $clone = clone $this;
48
        foreach ($exchangeRateProviders as $exchangeRateProvider) {
49
            $clone->exchangeRateProviders[] = $exchangeRateProvider;
50
        }
51
        return $clone;
52
    }
53
54
    /**
55
     * @inheritDoc
56
     */
57
    public function getExchangeRate(CurrencyInterface $sourceCurrency, CurrencyInterface $targetCurrency, DateTimeInterface $date = null): ExchangeRateInterface
58
    {
59
        $exchangeRates = [];
60
        foreach ($this->exchangeRateProviders as $e => $exchangeRateProvider) {
61
            $exchangeRates[$e] = $er = $exchangeRateProvider->getExchangeRate($sourceCurrency, $targetCurrency, $date);
0 ignored issues
show
Unused Code introduced by
$er 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...
62
        }
63
64
        if (!$exchangeRates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exchangeRates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
65
            throw new ExchangeRateNotFoundException($sourceCurrency, $targetCurrency);
66
        }
67
68
        $averageRatio = self::getAverageRatio(...$exchangeRates);
69
70
        if (null !== $this->tolerance) {
71
            $this->validate(...$exchangeRates);
72
        }
73
74
        return $this->exchangeRateFactory->create($sourceCurrency, $targetCurrency, $averageRatio);
75
    }
76
77
    /**
78
     * @param ExchangeRateInterface[] ...$exchangeRates
79
     * @throws \RuntimeException
80
     */
81
    private function validate(ExchangeRateInterface ...$exchangeRates): void
82
    {
83
        $ratios = array_map(function (ExchangeRate $exchangeRate) {
84
            return $exchangeRate->getRatio();
85
        }, $exchangeRates);
86
87
        $min = min($ratios);
88
        $max = max($ratios);
89
        $diff = abs($max - $min);
90
91
        if ($diff > $this->tolerance) {
92
            throw new \RuntimeException(sprintf('Tolerance fault: %s difference between minimum and maximum ratio, %s allowed.', $diff, $this->tolerance));
93
        }
94
    }
95
96
    /**
97
     * @param ExchangeRateInterface ...$exchangeRates
98
     * @return float
99
     */
100
    private static function getAverageRatio(ExchangeRateInterface ...$exchangeRates): float
101
    {
102
        return array_sum(
103
            array_map(
104
                function (ExchangeRateInterface $exchangeRate): float {
105
                        return $exchangeRate->getRatio();
106
                },
107
                $exchangeRates
108
            )
109
        ) / count($exchangeRates);
110
    }
111
112
    /**
113
     * @param float|null                        $tolerance
114
     * @param ExchangeRateFactoryInterface|null $exchangeRateFactory
115
     * @return AverageExchangeRateProvider
116
     */
117
    public static function create(float $tolerance = null, ExchangeRateFactoryInterface $exchangeRateFactory = null): self
118
    {
119
        return new self($tolerance, $exchangeRateFactory);
120
    }
121
}
122