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')); |
||
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) { |
||
0 ignored issues
–
show
|
|||
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); |
||
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; |
||
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 |
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
orexit
statements that have been added for debug purposes.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.