Issues (52)

src/Currency.php (5 issues)

1
<?php
2
namespace Turahe\Master;
3
4
use Illuminate\Contracts\Cache\Factory as FactoryContract;
5
use Illuminate\Support\Arr;
6
7
class Currency
8
{
9
    /**
10
     * Currency configuration.
11
     *
12
     * @var array
13
     */
14
    protected $config = [];
15
16
    /**
17
     * Laravel application
18
     *
19
     * @var \Illuminate\Contracts\Cache\Factory
20
     */
21
    protected $cache;
22
23
    /**
24
     * User's currency
25
     *
26
     * @var string
27
     */
28
    protected $user_currency;
29
30
    /**
31
     * Currency driver instance.
32
     *
33
     * @var Contracts\DriverInterface
34
     */
35
    protected $driver;
36
37
    /**
38
     * Formatter instance.
39
     *
40
     * @var Contracts\FormatterInterface
41
     */
42
    protected $formatter;
43
44
    /**
45
     * Cached currencies
46
     *
47
     * @var array
48
     */
49
    protected $currencies_cache;
50
51
    /**
52
     * Create a new instance.
53
     *
54
     * @param array           $config
55
     * @param FactoryContract $cache
56
     */
57
    public function __construct(array $config, FactoryContract $cache)
58
    {
59
        $this->config = $config;
60
        $this->cache = $cache->store($this->config('cache_driver'));
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache->store($this->config('cache_driver')) of type Illuminate\Contracts\Cache\Repository is incompatible with the declared type Illuminate\Contracts\Cache\Factory of property $cache.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61
    }
62
63
    /**
64
     * Format given number.
65
     *
66
     * @param float  $amount
67
     * @param string $from
68
     * @param string $to
69
     * @param bool   $format
70
     *
71
     * @return null|string
72
     */
73
    public function convert(float $amount, string $from = null, string $to = null, bool $format = true)
74
    {
75
        // Get currencies involved
76
        $from = $from ?: $this->config('default');
77
        $to = $to ?: $this->getUserCurrency();
78
79
        // Get exchange rates
80
        (float)$from_rate = $this->getCurrencyProp($from, 'exchange_rate');
81
        (float)$to_rate = $this->getCurrencyProp($to, 'exchange_rate');
82
83
        // Skip invalid to currency rates
84
        if ($to_rate === null) {
0 ignored issues
show
The condition $to_rate === null is always false.
Loading history...
85
            return null;
86
        }
87
88
        try {
89
            // Convert amount
90
            if ($from === $to) {
91
                $value = $amount;
92
            } else {
93
                $value = ($amount * $to_rate) / $from_rate;
94
            }
95
        } catch (\Exception $e) {
0 ignored issues
show
catch (\Exception $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
96
            // Prevent invalid conversion or division by zero errors
97
            return null;
98
        }
99
100
        // Should the result be formatted?
101
        if ($format === true) {
102
            return $this->format($value, $to);
103
        }
104
105
        // Return value
106
        return $value;
107
    }
108
109
    /**
110
     * Format the value into the desired currency.
111
     *
112
     * @param float  $value
113
     * @param string $code
114
     * @param bool   $include_symbol
115
     *
116
     * @return string
117
     */
118
    public function format($value, $code = null, $include_symbol = true)
119
    {
120
        // Get default currency if one is not set
121
        $code = $code ?: $this->config('default');
122
123
        // Remove unnecessary characters
124
        $value = preg_replace('/[\s\',!]/', '', $value);
125
126
        // Check for a custom formatter
127
        if ($formatter = $this->getFormatter()) {
128
            return $formatter->format($value, $code);
129
        }
130
131
        // Get the measurement format
132
        $format = $this->getCurrencyProp($code, 'format');
133
134
        // Value Regex
135
        $valRegex = '/([0-9].*|)[0-9]/';
136
137
        // Match decimal and thousand separators
138
        preg_match_all('/[\s\',.!]/', $format, $separators);
139
140
        if ($thousand = Arr::get($separators, '0.0', null)) {
141
            if ($thousand == '!') {
142
                $thousand = '';
143
            }
144
        }
145
146
        $decimal = Arr::get($separators, '0.1', null);
147
148
        // Match format for decimals count
149
        preg_match($valRegex, $format, $valFormat);
0 ignored issues
show
$format of type array is incompatible with the type string expected by parameter $subject of preg_match(). ( Ignorable by Annotation )

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

149
        preg_match($valRegex, /** @scrutinizer ignore-type */ $format, $valFormat);
Loading history...
150
151
        $valFormat = Arr::get($valFormat, 0, 0);
152
153
        // Count decimals length
154
        $decimals = $decimal ? strlen(substr(strrchr($valFormat, $decimal), 1)) : 0;
155
156
        // Do we have a negative value?
157
        if ($negative = $value < 0 ? '-' : '') {
158
            $value = $value * -1;
159
        }
160
161
        // Format the value
162
        $value = number_format($value, $decimals, $decimal, $thousand);
163
164
        // Apply the formatted measurement
165
        if ($include_symbol) {
166
            $value = preg_replace($valRegex, $value, $format);
167
        }
168
169
        // Return value
170
        return $negative . $value;
0 ignored issues
show
Are you sure $value of type string|string[] can be used in concatenation? ( Ignorable by Annotation )

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

170
        return $negative . /** @scrutinizer ignore-type */ $value;
Loading history...
171
    }
172
173
    /**
174
     * Set user's currency.
175
     *
176
     * @param string $code
177
     */
178
    public function setUserCurrency($code)
179
    {
180
        $this->user_currency = strtoupper($code);
181
    }
182
183
    /**
184
     * Return the user's currency code.
185
     *
186
     * @return string
187
     */
188
    public function getUserCurrency()
189
    {
190
        return $this->user_currency ?: $this->config('default');
191
    }
192
193
    /**
194
     * Determine if the provided currency is valid.
195
     *
196
     * @param string $code
197
     *
198
     * @return null|array
199
     */
200
    public function hasCurrency($code)
201
    {
202
        return array_key_exists(strtoupper($code), $this->getCurrencies());
203
    }
204
205
    /**
206
     * Determine if the provided currency is active.
207
     *
208
     * @param string $code
209
     *
210
     * @return bool
211
     */
212
    public function isActive($code)
213
    {
214
        return $code && (bool) Arr::get($this->getCurrency($code), 'active', false);
215
    }
216
217
    /**
218
     * Return the current currency if the
219
     * one supplied is not valid.
220
     *
221
     * @param string $code
222
     *
223
     * @return null|array
224
     */
225
    public function getCurrency($code = null)
226
    {
227
        $code = $code ?: $this->getUserCurrency();
228
229
        return Arr::get($this->getCurrencies(), strtoupper($code));
230
    }
231
232
    /**
233
     * Return all currencies.
234
     *
235
     * @return array
236
     */
237
    public function getCurrencies()
238
    {
239
        if ($this->currencies_cache === null) {
240
            if (config('app.debug', false) === true) {
241
                $this->currencies_cache = $this->getDriver()->all();
242
            } else {
243
                $this->currencies_cache = $this->cache->rememberForever('torann.currency', function () {
244
                    return $this->getDriver()->all();
245
                });
246
            }
247
        }
248
249
        return $this->currencies_cache;
250
    }
251
252
    /**
253
     * Return all active currencies.
254
     *
255
     * @return array
256
     */
257
    public function getActiveCurrencies()
258
    {
259
        return array_filter($this->getCurrencies(), function ($currency) {
260
            return $currency['active'] == true;
261
        });
262
    }
263
264
    /**
265
     * Get storage driver.
266
     *
267
     */
268
    public function getDriver()
269
    {
270
        if ($this->driver === null) {
271
            // Get driver configuration
272
            $config = $this->config('drivers.' . $this->config('driver'), []);
273
274
            // Get driver class
275
            $driver = Arr::pull($config, 'class');
276
277
            // Create driver instance
278
            $this->driver = new $driver($config);
279
        }
280
281
        return $this->driver;
282
    }
283
284
    /**
285
     * Get formatter driver.
286
     *
287
     */
288
    public function getFormatter()
289
    {
290
        if ($this->formatter === null && $this->config('formatter') !== null) {
291
            // Get formatter configuration
292
            $config = $this->config('formatters.' . $this->config('formatter'), []);
293
294
            // Get formatter class
295
            $class = Arr::pull($config, 'class');
296
297
            // Create formatter instance
298
            $this->formatter = new $class(array_filter($config));
299
        }
300
301
        return $this->formatter;
302
    }
303
304
    /**
305
     * Clear cached currencies.
306
     */
307
    public function clearCache()
308
    {
309
        $this->cache->forget('torann.currency');
310
        $this->currencies_cache = null;
311
    }
312
313
    /**
314
     * Get configuration value.
315
     *
316
     * @param string $key
317
     * @param mixed  $default
318
     *
319
     * @return mixed
320
     */
321
    public function config($key = null, $default = null)
322
    {
323
        if ($key === null) {
324
            return $this->config;
325
        }
326
327
        return Arr::get($this->config, $key, $default);
328
    }
329
330
    /**
331
     * Get the given property value from provided currency.
332
     *
333
     * @param string $code
334
     * @param string $key
335
     * @param mixed  $default
336
     *
337
     * @return array
338
     */
339
    protected function getCurrencyProp($code, $key, $default = null)
340
    {
341
        return Arr::get($this->getCurrency($code), $key, $default);
342
    }
343
344
    /**
345
     * Get a given value from the current currency.
346
     *
347
     * @param string $key
348
     *
349
     * @return mixed
350
     */
351
    public function __get($key)
352
    {
353
        return Arr::get($this->getCurrency(), $key);
354
    }
355
356
    /**
357
     * Dynamically call the default driver instance.
358
     *
359
     * @param string $method
360
     * @param array  $parameters
361
     *
362
     * @return mixed
363
     */
364
    public function __call($method, $parameters)
365
    {
366
        return call_user_func_array([$this->getDriver(), $method], $parameters);
367
    }
368
}
369