Test Setup Failed
Pull Request — master (#20)
by Alessandro
04:01
created

CurrencyConverterHelper::setCurrencyConverter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
namespace CurrencyConverter\View\Helper;
3
4
use Cake\View\Helper;
5
use Cake\ORM\TableRegistry;
6
use Cake\I18n\Time;
7
use CurrencyConverter\CurrencyConverter;
8
9
/**
10
 * @property \Cake\View\Helper\HtmlHelper $Html
11
 * @property \Cake\View\Helper\FormHelper $Form
12
 * @property \Cake\View\Helper\NumberHelper $Number
13
 */
14
class CurrencyConverterHelper extends Helper
15
{
16
    /**
17
     * Using database
18
     *
19
     * @var bool
20
     */
21
    private $database;
22
23
    /**
24
     * Time interval for refreshing database
25
     *
26
     * @var int
27
     */
28
    private $refresh;
29
30
    /**
31
     * Number of decimal to use for formatting converted price
32
     *
33
     * @var int
34
     */
35
    private $decimal;
36
37
    /**
38
     * Number to divise 1 and get the sup step to round price to
39
     *
40
     * @var float
41
     */
42
    private $round;
43
44
    /**
45
     * Session
46
     *
47
     * @var \Cake\Http\Session
48
     */
49
    private $session;
50
51
    /**
52
     * CurrencyratesTable Class
53
     * @var \Cake\ORM\Table
54
     */
55
    private $currencyratesTable;
56
57
    /**
58
     * Default CurrencyConverterComponent settings.
59
     *
60
     * When calling CurrencyConverterComponent() these settings will be merged with the configuration
61
     * you provide.
62
     *
63
     * - `database` - Mention if Component have to store currency rate in database
64
     * - `refresh` - Time interval for Component to refresh currency rate in database
65
     * - `decimal` - Number of decimal to use when formatting amount float number
66
     * - `round` - Number to divise 1 and get the sup step to round price to (eg: 4 for 0.25 step)
67
     *
68
     * @var array
69
     */
70
    protected $_defaultConfig = [
71
        'database' => true,
72
        'refresh' => 24,
73
        'decimal' => 2,
74
        'round' => false,
75
        'apikey' => '',
76
    ];
77
78
    private $apiKey;
79
    private $currencyConverter;
80 16
81 16
    /**
82 16
     * @param array $config
83 16
     * @return void
84 16
     */
85
    public function initialize(array $config = []) {
86 16
        $this->database = $this->getConfig('database');
87 16
        $this->refresh = $this->getConfig('refresh');
88 16
        $this->decimal = $this->getConfig('decimal');
89
        $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
        $this->apiKey = $this->getConfig('apikey');
91
92
        $this->currencyConverter = new CurrencyConverter($this->apiKey);
0 ignored issues
show
Bug introduced by
It seems like $this->apiKey can also be of type null; however, parameter $apiKey of CurrencyConverter\CurrencyConverter::__construct() does only seem to accept string, 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

92
        $this->currencyConverter = new CurrencyConverter(/** @scrutinizer ignore-type */ $this->apiKey);
Loading history...
93
94
        $this->session = $this->getView()->getRequest()->getSession();
95
        $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

95
        $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...
96
    }
97
98 9
    /**
99
     * Convert method take an amount as first parameter and convert it using $from currency and $to currency.
100 9
     *
101 9
     * @param float|string $amount the amount to convert.
102
     * @param string $from currency to convert from
103 9
     * @param string $to currency to convert to
104
     * @return string $amount converted
105
     */
106
    public function convert($amount, $from, $to)
107
    {
108
        if (!isset($this->apiKey)) {
109
            throw new \Exception('Api Key not found');
110
        }
111
112
        $amount = floatval($amount);
113 6
        $rate = $this->_getRateToUse($from, $to);
114
115 6
        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...
116
    }
117
118
    /**
119
     * Rate method return the rate of two currencies
120
     *
121
     * @param string $from currency to get the rate from
122
     * @param string $to currency to get the rate to
123
     * @return float|null $rate
124
     */
125
    public function rate($from, $to)
126
    {
127 15
        return $this->_getRateToUse($from, $to);
128
    }
129 15
130 15
    /**
131 12
     * getRateToUse return rate to use
132 10
     * Using $from and $to parameters representing currency to deal with and the configuration settings
133 10
     * This method save or update currencyrates Table if necesseray too.
134 8
     *
135 8
     * @param string $from currency to get the rate from
136 10
     * @param string $to currency to get the rate to
137 2
     * @return float|null $rate
138
     */
139 12
    private function _getRateToUse($from, $to)
140
    {
141 15
        $rate = 1;
142
        if ($from !== $to) {
143
            if ($this->database) {
144
                $rate = $this->_getRateFromSession($from, $to);
145
                if (!$rate) {
146
                    $rate = $this->_getRateFromDatabase($from, $to);
147
                }
148
            } else {
149
                $rate = $this->_getRateFromAPI($from, $to);
150 9
            }
151
        }
152 9
153 1
        return $rate;
154 1
    }
155 1
156 1
    /**
157 1
     * Format number using configuration
158 1
     *
159 1
     * @param float number to format
160 1
     * @return string formatted number
161 9
     */
162
    private function _formatConvert($number)
163 9
    {
164
        if ($this->round) {
165
            $n = floor($number);
166
            $fraction = ($number - $n);
167
            if ($fraction != 0) {
168
                $step = 1/$this->round;
169
                $decimal = (((int)($fraction/$step) + 1) * $step);
170
                $number = $n + $decimal;
171
            }
172
        }
173 10
        $number = number_format($number, $this->decimal);
174
175 10
        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

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

216
                $this->_storeRateInSession(/** @scrutinizer ignore-type */ $result);
Loading history...
217 2
            } else {
218 2
                $rate = $this->_getRateFromAPI($from, $to);
219
                if ($rate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rate of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
220 2
                    $result->rate = $rate;
0 ignored issues
show
Bug introduced by
Accessing rate on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
221 2
                    $this->currencyratesTable->save($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type array; however, parameter $entity of Cake\ORM\Table::save() does only seem to accept Cake\Datasource\EntityInterface, 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

221
                    $this->currencyratesTable->save(/** @scrutinizer ignore-type */ $result);
Loading history...
222 2
                    $this->_storeRateInSession($result);
223 2
                }
224
            }
225
        } else {
226 8
            $rate = $this->_getRateFromAPI($from, $to);
227
            if ($rate) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rate of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
228
                $result = $this->currencyratesTable->newEntity([
229
                    'from_currency' => $from,
230
                    'to_currency' => $to,
231
                    'rate' => $rate
232
                ]);
233
                $this->currencyratesTable->save($result);
234
                $this->_storeRateInSession($result);
235 8
            }
236
        }
237 8
238 8
        return $rate;
239 8
    }
240 8
241 8
    /**
242
     * Store in session a rate and his modified datetime
243
     * 
244
     * @param  \Cake\ORM\Entity $entity
245
     * @return void
246
     */
247
    private function _storeRateInSession($entity)
248
    {
249
        $this->session->write('CurrencyConverter.' . $entity->get('from_currency') . '-' . $entity->get('to_currency'), [
250 8
            'rate' => $entity->get('rate'),
251
            'modified' => $entity->get('modified')->i18nFormat('yyyy-MM-dd HH:mm:ss')
252 8
        ]);
253
    }
254 8
255 8
    /**
256 8
     * Call free.currencyconverterapi.com API to get a rate for one currency to an other one currency.
257 8
     *
258 8
     * @param string $from the currency.
259 8
     * @param string $to the currency.
260 8
     * @return int|null $rate.
261 8
     */
262 8
    private function _getRateFromAPI($from, $to)
263 8
    {
264
        return $this->currencyConverter->getRates($from, $to);
265 8
    }
266
267
    public function setCurrencyConverter(CurrencyConverter $currencyConverter)
268
    {
269
        $this->currencyConverter = $currencyConverter;
270
    }
271
}
272