Issues (27)

src/View/Helper/CurrencyConverterHelper.php (11 issues)

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
81
    /**
82
     * @param array $config
83
     * @return void
84
     */
85 16
    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 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 16
        $this->apiKey = $this->getConfig('apikey');
91
92 16
        $this->currencyConverter = new CurrencyConverter($this->apiKey);
0 ignored issues
show
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 16
        $this->session = $this->getView()->getRequest()->getSession();
95 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

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 16
    }
97
98
    /**
99
     * Convert method take an amount as first parameter and convert it using $from currency and $to currency.
100
     *
101
     * @param float|string $amount the amount to convert.
102
     * @param string $from currency to convert from
103
     * @param string $to currency to convert to
104
     * @return string $amount converted
105
     */
106 9
    public function convert($amount, $from, $to)
107
    {
108 9
        if (!isset($this->apiKey)) {
109
            throw new \Exception('Api Key not found');
110
        }
111
112 9
        $amount = floatval($amount);
113 9
        $rate = $this->_getRateToUse($from, $to);
114
115 9
        return $convert = $this->_formatConvert($rate * $amount);
0 ignored issues
show
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 6
    public function rate($from, $to)
126
    {
127 6
        return $this->_getRateToUse($from, $to);
128
    }
129
130
    /**
131
     * getRateToUse return rate to use
132
     * Using $from and $to parameters representing currency to deal with and the configuration settings
133
     * This method save or update currencyrates Table if necesseray too.
134
     *
135
     * @param string $from currency to get the rate from
136
     * @param string $to currency to get the rate to
137
     * @return float|null $rate
138
     */
139 15
    private function _getRateToUse($from, $to)
140
    {
141 15
        $rate = 1;
142 15
        if ($from !== $to) {
143 12
            if ($this->database) {
144 10
                $rate = $this->_getRateFromSession($from, $to);
145 10
                if (!$rate) {
146 10
                    $rate = $this->_getRateFromDatabase($from, $to);
147
                }
148
            } else {
149 2
                $rate = $this->_getRateFromAPI($from, $to);
150
            }
151
        }
152
153 15
        return $rate;
154
    }
155
156
    /**
157
     * Format number using configuration
158
     *
159
     * @param float number to format
160
     * @return string formatted number
161
     */
162 9
    private function _formatConvert($number)
163
    {
164 9
        if ($this->round) {
165 1
            $n = floor($number);
166 1
            $fraction = ($number - $n);
167 1
            if ($fraction != 0) {
168 1
                $step = 1/$this->round;
169 1
                $decimal = (((int)($fraction/$step) + 1) * $step);
170 1
                $number = $n + $decimal;
171
            }
172
        }
173 9
        $number = number_format($number, $this->decimal);
174
175 9
        return number_format($number, $this->decimal);
0 ignored issues
show
$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
    }
177
178
    /**
179
     * Check session to see if rate exists in.
180
     * 
181
     * @param  string $from currency to get the rate from.
182
     * @param  string $to currency to get the rate to.
183
     * @return float|null $rate.
184
     */
185 10
    private function _getRateFromSession($from, $to)
186
    {
187 10
        $session = $this->session->read('CurrencyConverter.' . $from . '-' . $to);        
188 10
        if ($session) {
189 4
            $modified = new Time($session['modified']);
190 4
            if ($modified->wasWithinLast($this->refresh . ' hours')) {
191 2
                return $rate = $session['rate'];
0 ignored issues
show
The assignment to $rate is dead and can be removed.
Loading history...
192
            }
193
        }
194
195 8
        return null;
196
    }
197
198
    /**
199
     * Get a rate from database.
200
     *
201
     * It queries currencyratesTable and ...
202
     * if rate exists and has not to be modified, it returns this rate.
203
     * 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
     * if rate does not exist, it call _getRateFromAPI to get a fresh rate, then create in table and store this rate.
205
     * 
206
     * @param  string $from currency to get the rate from
207
     * @param  string $to currency to get the rate to
208
     * @return float|null $rate
209
     */
210 8
    private function _getRateFromDatabase($from, $to)
211
    {
212 8
        $result = $this->currencyratesTable->find('all')->where(['from_currency' => $from, 'to_currency' => $to])->first();
213 8
        if ($result) {
214 6
            if ($result->get('modified')->wasWithinLast($this->refresh . ' hours')) {
215 2
                $rate = $result->get('rate');
216 2
                $this->_storeRateInSession($result);
0 ignored issues
show
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
            } else {
218 4
                $rate = $this->_getRateFromAPI($from, $to);
219 4
                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 4
                    $result->rate = $rate;
0 ignored issues
show
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 4
                    $this->currencyratesTable->save($result);
0 ignored issues
show
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 6
                    $this->_storeRateInSession($result);
223
                }
224
            }
225
        } else {
226 2
            $rate = $this->_getRateFromAPI($from, $to);
227 2
            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 2
                $result = $this->currencyratesTable->newEntity([
229 2
                    'from_currency' => $from,
230 2
                    'to_currency' => $to,
231 2
                    'rate' => $rate
232
                ]);
233 2
                $this->currencyratesTable->save($result);
234 2
                $this->_storeRateInSession($result);
235
            }
236
        }
237
238 8
        return $rate;
239
    }
240
241
    /**
242
     * Store in session a rate and his modified datetime
243
     * 
244
     * @param  \Cake\ORM\Entity $entity
245
     * @return void
246
     */
247 8
    private function _storeRateInSession($entity)
248
    {
249 8
        $this->session->write('CurrencyConverter.' . $entity->get('from_currency') . '-' . $entity->get('to_currency'), [
250 8
            'rate' => $entity->get('rate'),
251 8
            'modified' => $entity->get('modified')->i18nFormat('yyyy-MM-dd HH:mm:ss')
252
        ]);
253 8
    }
254
255
    /**
256
     * Call free.currencyconverterapi.com API to get a rate for one currency to an other one currency.
257
     *
258
     * @param string $from the currency.
259
     * @param string $to the currency.
260
     * @return int|null $rate.
261
     */
262 8
    private function _getRateFromAPI($from, $to)
263
    {
264 8
        return $this->currencyConverter->getRates($from, $to);
265
    }
266
267 11
    public function setCurrencyConverter(CurrencyConverter $currencyConverter)
268
    {
269 11
        $this->currencyConverter = $currencyConverter;
270 11
    }
271
}
272