Issues (52)

src/Currency.php (3 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) {
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) {
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