Issues (27)

Component/CurrencyConverterComponent.php (12 issues)

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
use CurrencyConverter\CurrencyConverter;
10
11
/**
12
 * CurrencyConverter Component to convert currency.
13
 *
14
 * Convert an amount number from one currency to an other currency
15
 * Return currency rate from one currency to an other
16
 * output type from HTML to JSON format.
17
 */
18
class CurrencyConverterComponent extends Component
19
{
20
    /**
21
     * Using database
22
     *
23
     * @var bool
24
     */
25
    private $database;
26
27
    /**
28
     * Time interval for refreshing database
29
     *
30
     * @var int
31
     */
32
    private $refresh;
33
34
    /**
35
     * Number of decimal to use for formatting converted price
36
     *
37
     * @var int
38
     */
39
    private $decimal;
40
41
    /**
42
     * Number to divise 1 and get the sup step to round price to
43
     *
44
     * @var float
45
     */
46
    private $round;
47
48
    /**
49
     * Session
50
     *
51
     * @var \Cake\Http\Session
52
     */
53
    private $session;
54
55
    /**
56
     * CurrencyratesTable Class
57
     * @var \Cake\ORM\Table
58
     */
59
    private $currencyratesTable;
60
61
    /**
62
     * Default CurrencyConverterComponent settings.
63
     *
64
     * When calling CurrencyConverterComponent() these settings will be merged with the configuration
65
     * you provide.
66
     *
67
     * - `database` - Mention if Component have to store currency rate in database
68
     * - `refresh` - Time interval for Component to refresh currency rate in database
69
     * - `decimal` - Number of decimal to use when formatting amount float number
70
     * - `round` - Number to divise 1 and get the sup step to round price to (eg: 4 for 0.25 step)
71
     *
72
     * @var array
73
     */
74
    protected $_defaultConfig = [
75
        'database' => true,
76
        'refresh' => 24,
77
        'decimal' => 2,
78
        'round' => false,
79
        'apikey' => '',
80
    ];
81
82
    private $apiKey;
83
84
    private $currencyConverter;
85
86
    /**
87
     * @param array $config
88
     * @return void
89
     */
90 16
    public function initialize(array $config = [])
91
    {
92 16
        $this->database = $this->getConfig('database');
93 16
        $this->refresh = $this->getConfig('refresh');
94 16
        $this->decimal = $this->getConfig('decimal');
95 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...
96 16
        $this->apiKey = $this->getConfig('apikey');
97
98 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

98
        $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...
99 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

99
        $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...
100 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

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

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

221
                $this->_storeRateInSession(/** @scrutinizer ignore-type */ $result);
Loading history...
222
            } else {
223 4
                $rate = $this->_getRateFromAPI($from, $to);
224 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...
225 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...
226 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

226
                    $this->currencyratesTable->save(/** @scrutinizer ignore-type */ $result);
Loading history...
227 6
                    $this->_storeRateInSession($result);
228
                }
229
            }
230
        } else {
231 2
            $rate = $this->_getRateFromAPI($from, $to);
232 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...
233 2
                $result = $this->currencyratesTable->newEntity([
234 2
                    'from_currency' => $from,
235 2
                    'to_currency' => $to,
236 2
                    'rate' => $rate
237
                ]);
238 2
                $this->currencyratesTable->save($result);
239 2
                $this->_storeRateInSession($result);
240
            }
241
        }
242
243 8
        return $rate;
244
    }
245
246
    /**
247
     * Store in session a rate and his modified datetime
248
     *
249
     * @param  \Cake\ORM\Entity $entity
250
     * @return void
251
     */
252 8
    private function _storeRateInSession($entity)
253
    {
254 8
        $this->session->write('CurrencyConverter.' . $entity->get('from_currency') . '-' . $entity->get('to_currency'), [
255 8
            'rate' => $entity->get('rate'),
256 8
            'modified' => $entity->get('modified')->i18nFormat('yyyy-MM-dd HH:mm:ss')
257
        ]);
258 8
    }
259
260
    /**
261
     * Call free.currencyconverterapi.com API to get a rate for one currency to an other one currency.
262
     *
263
     * @param string $from the currency.
264
     * @param string $to the currency.
265
     * @return int|null $rate.
266
     */
267 8
    private function _getRateFromAPI($from, $to)
268
    {
269 8
        $rate = null;
0 ignored issues
show
The assignment to $rate is dead and can be removed.
Loading history...
270
271 8
        return $this->currencyConverter->getRates($from, $to);
272
    }
273
274 10
    public function setCurrencyConverter(CurrencyConverter $currencyConverter)
275
    {
276 10
        $this->currencyConverter = $currencyConverter;
277 10
    }
278
}
279