Passed
Push — master ( 32425d...98fdb1 )
by Alessandro
03:06
created

CurrencyConverterComponent   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 80
dl 0
loc 254
ccs 89
cts 89
cp 1
rs 10
c 0
b 0
f 0
wmc 23

9 Methods

Rating   Name   Duplication   Size   Complexity  
A _getRateFromDatabase() 0 29 5
A rate() 0 3 1
A convert() 0 6 1
A _getRateFromSession() 0 11 3
A _getRateToUse() 0 15 4
A _formatConvert() 0 14 3
A _storeRateInSession() 0 5 1
A initialize() 0 9 2
A _getRateFromAPI() 0 16 3
1
<?php
2
3
namespace CurrencyConverter\Controller\Component;
4
5
use Cake\Controller\Component;
6
use Cake\Datasource\ConnectionManager;
7
use Cake\ORM\TableRegistry;
8
use Cake\I18n\Time;
9
10
/**
11
 * CurrencyConverter Component to convert currency.
12
 *
13
 * Convert an amount number from one currency to an other currency
14
 * Return currency rate from one currency to an other
15
 * output type from HTML to JSON format.
16
 */
17
class CurrencyConverterComponent extends Component
18
{
19
    /**
20
     * Using database
21
     *
22
     * @var bool
23
     */
24
    private $database;
25
26
    /**
27
     * Time interval for refreshing database
28
     *
29
     * @var int
30
     */
31
    private $refresh;
32
33
    /**
34
     * Number of decimal to use for formatting converted price
35
     *
36
     * @var int
37
     */
38
    private $decimal;
39
40
    /**
41
     * Number to divise 1 and get the sup step to round price to
42
     *
43
     * @var float
44
     */
45
    private $round;
46
47
    /**
48
     * Session
49
     *
50
     * @var \Cake\Http\Session
51
     */
52
    private $session;
53
54
    /**
55
     * CurrencyratesTable Class
56
     * @var \Cake\ORM\Table
57
     */
58
    private $currencyratesTable;
59
60
    /**
61
     * Default CurrencyConverterComponent settings.
62
     *
63
     * When calling CurrencyConverterComponent() these settings will be merged with the configuration
64
     * you provide.
65
     *
66
     * - `database` - Mention if Component have to store currency rate in database
67
     * - `refresh` - Time interval for Component to refresh currency rate in database
68
     * - `decimal` - Number of decimal to use when formatting amount float number
69
     * - `round` - Number to divise 1 and get the sup step to round price to (eg: 4 for 0.25 step)
70
     *
71
     * @var array
72
     */
73
    protected $_defaultConfig = [
74
        'database' => true, // Mention if Component have to store currency rate in database
75
        'refresh' => 24, // Time interval for Component to refresh currency rate in database
76
        'decimal' => 2, // Number of decimal to use when formatting amount float number
77
        'round' => false, // Number to divise 1 and get the sup step to round price to (eg: 4 for 0.25 step)
78
    ];
79
80
    /**
81
     * @param array $config
82
     * @return void
83
     */
84 16
    public function initialize(array $config = [])
85
    {
86 16
        $this->database = $this->getConfig('database');
87 16
        $this->refresh = $this->getConfig('refresh');
88 16
        $this->decimal = $this->getConfig('decimal');
89 16
        $this->round = ($this->getConfig('round') !== 0 ? $this->getConfig('round') : false);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConfig('round'...Config('round') : false can also be of type false. However, the property $round is declared as type double. 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...
90
91 16
        $this->session = $this->request->getSession();
0 ignored issues
show
Deprecated Code introduced by
The property Cake\Controller\Component::$request has been deprecated: 3.4.0 Storing references to the request is deprecated. Use Component::getController() or callback $event->getSubject() to access the controller & request instead. ( Ignorable by Annotation )

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

91
        $this->session = /** @scrutinizer ignore-deprecated */ $this->request->getSession();

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
92 16
        $this->currencyratesTable = TableRegistry::get('CurrencyConverter.Currencyrates');
0 ignored issues
show
Deprecated Code introduced by
The function Cake\ORM\TableRegistry::get() has been deprecated: 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. ( Ignorable by Annotation )

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

92
        $this->currencyratesTable = /** @scrutinizer ignore-deprecated */ TableRegistry::get('CurrencyConverter.Currencyrates');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
93 16
    }
94
95
    /**
96
     * Convert method take an amount as first parameter and convert it using $from currency and $to currency.
97
     *
98
     * @param float|string $amount the amount to convert.
99
     * @param string $from currency to convert from
100
     * @param string $to currency to convert to
101
     * @return string $amount converted
102
     */
103 9
    public function convert($amount, $from, $to)
104
    {
105 9
        $amount = floatval($amount);
106 9
        $rate = $this->_getRateToUse($from, $to);
107
108 9
        return $convert = $this->_formatConvert($rate * $amount);
0 ignored issues
show
Unused Code introduced by
The assignment to $convert is dead and can be removed.
Loading history...
109
    }
110
111
    /**
112
     * Rate method return the rate of two currencies
113
     *
114
     * @param string $from currency to get the rate from
115
     * @param string $to currency to get the rate to
116
     * @return float|null $rate
117
     */
118 6
    public function rate($from, $to)
119
    {
120 6
        return $this->_getRateToUse($from, $to);
121
    }
122
123
    /**
124
     * getRateToUse return rate to use
125
     * Using $from and $to parameters representing currency to deal with and the configuration settings
126
     * This method save or update currencyrates Table if necesseray too.
127
     *
128
     * @param string $from currency to get the rate from
129
     * @param string $to currency to get the rate to
130
     * @return float|null $rate
131
     */
132 15
    private function _getRateToUse($from, $to)
133
    {
134 15
        $rate = 1;
135 15
        if ($from !== $to) {
136 12
            if ($this->database) {
137 10
                $rate = $this->_getRateFromSession($from, $to);
138 10
                if (!$rate) {
139 8
                    $rate = $this->_getRateFromDatabase($from, $to);
140 8
                }
141 10
            } else {
142 2
                $rate = $this->_getRateFromAPI($from, $to);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $rate is correct as $this->_getRateFromAPI($from, $to) targeting CurrencyConverter\Contro...nent::_getRateFromAPI() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
143
            }
144 12
        }
145
146 15
        return $rate;
147
    }
148
149
    /**
150
     * Format number using configuration
151
     *
152
     * @param float number to format
153
     * @return string formatted number
154
     */
155 9
    private function _formatConvert($number)
156
    {
157 9
        if ($this->round) {
158 1
            $n = floor($number);
159 1
            $fraction = ($number - $n);
160 1
            if ($fraction != 0) {
161 1
                $step = 1 / $this->round;
162 1
                $decimal = (((int)($fraction / $step) + 1) * $step);
163 1
                $number = $n + $decimal;
164 1
            }
165 1
        }
166 9
        $number = number_format($number, $this->decimal);
167
168 9
        return number_format($number, $this->decimal);
0 ignored issues
show
Bug introduced by
$number of type string is incompatible with the type double expected by parameter $number of number_format(). ( Ignorable by Annotation )

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

168
        return number_format(/** @scrutinizer ignore-type */ $number, $this->decimal);
Loading history...
169
    }
170
171
    /**
172
     * Check session to see if rate exists in.
173
     *
174
     * @param  string $from currency to get the rate from.
175
     * @param  string $to currency to get the rate to.
176
     * @return float|null $rate.
177
     */
178 10
    private function _getRateFromSession($from, $to)
179
    {
180 10
        $session = $this->session->read('CurrencyConverter.' . $from . '-' . $to);
181 10
        if ($session) {
182 4
            $modified = new Time($session['modified']);
183 4
            if ($modified->wasWithinLast($this->refresh . ' hours')) {
184 2
                return $rate = $session['rate'];
0 ignored issues
show
Unused Code introduced by
The assignment to $rate is dead and can be removed.
Loading history...
185
            }
186 2
        }
187
188 8
        return null;
189
    }
190
191
    /**
192
     * Get a rate from database.
193
     *
194
     * It queries currencyratesTable and ...
195
     * if rate exists and has not to be modified, it returns this rate.
196
     * if rate exists and has to be modified, it call _getRateFromAPI method to get a fresh rate, then update in table and store in session this rate.
197
     * if rate does not exist, it call _getRateFromAPI to get a fresh rate, then create in table and store this rate.
198
     *
199
     * @param  string $from currency to get the rate from
200
     * @param  string $to currency to get the rate to
201
     * @return float|null $rate
202
     */
203 8
    private function _getRateFromDatabase($from, $to)
204
    {
205 8
        $result = $this->currencyratesTable->find('all')->where(['from_currency' => $from, 'to_currency' => $to])->first();
206 8
        if ($result) {
207 6
            if ($result->get('modified')->wasWithinLast($this->refresh . ' hours')) {
208 2
                $rate = $result->get('rate');
209 2
                $this->_storeRateInSession($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type array; however, parameter $entity of CurrencyConverter\Contro...::_storeRateInSession() does only seem to accept Cake\ORM\Entity, 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

209
                $this->_storeRateInSession(/** @scrutinizer ignore-type */ $result);
Loading history...
210 2
            } else {
211 4
                $rate = $this->_getRateFromAPI($from, $to);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $rate is correct as $this->_getRateFromAPI($from, $to) targeting CurrencyConverter\Contro...nent::_getRateFromAPI() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
212 4
                if ($rate) {
0 ignored issues
show
introduced by
$rate is of type null, thus it always evaluated to false.
Loading history...
213 4
                    $result->rate = $rate;
214 4
                    $this->currencyratesTable->save($result);
215 4
                    $this->_storeRateInSession($result);
216 4
                }
217
            }
218 6
        } else {
219 2
            $rate = $this->_getRateFromAPI($from, $to);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $rate is correct as $this->_getRateFromAPI($from, $to) targeting CurrencyConverter\Contro...nent::_getRateFromAPI() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
220 2
            if ($rate) {
0 ignored issues
show
introduced by
$rate is of type null, thus it always evaluated to false.
Loading history...
221 2
                $result = $this->currencyratesTable->newEntity([
222 2
                    'from_currency' => $from,
223 2
                    'to_currency' => $to,
224
                    'rate' => $rate
225 2
                ]);
226 2
                $this->currencyratesTable->save($result);
227 2
                $this->_storeRateInSession($result);
228 2
            }
229
        }
230
231 8
        return $rate;
232
    }
233
234
    /**
235
     * Store in session a rate and his modified datetime
236
     *
237
     * @param  \Cake\ORM\Entity $entity
238
     * @return void
239
     */
240 8
    private function _storeRateInSession($entity)
241
    {
242 8
        $this->session->write('CurrencyConverter.' . $entity->get('from_currency') . '-' . $entity->get('to_currency'), [
243 8
            'rate' => $entity->get('rate'),
244 8
            'modified' => $entity->get('modified')->i18nFormat('yyyy-MM-dd HH:mm:ss')
245 8
        ]);
246 8
    }
247
248
    /**
249
     * Call free.currencyconverterapi.com API to get a rate for one currency to an other one currency.
250
     *
251
     * @param string $from the currency.
252
     * @param string $to the currency.
253
     * @return int|null $rate.
254
     */
255 8
    private function _getRateFromAPI($from, $to)
256
    {
257 8
        $rate = null;
258
259 8
        $url = 'https://free.currencyconverterapi.com/api/v5/convert?q=' . $from . '_' . $to . '&compact=ultra';
260 8
        $request = @fopen($url, 'r');
261 8
        if ($request) {
0 ignored issues
show
introduced by
$request is of type false|resource, thus it always evaluated to false.
Loading history...
262 8
            $response = fgets($request, 4096);
263 8
            fclose($request);
264 8
            $response = json_decode($response, true);
265 8
            if (isset($response[$from . '_' . $to])) {
266 8
                $rate = $response[$from . '_' . $to];
267 8
            }
268 8
        }
269
270 8
        return $rate;
271
    }
272
}
273