NumberFormatter::loadFormatterId()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 11
cts 11
cp 1
rs 9.7333
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * soluble-flexstore library
7
 *
8
 * @author    Vanvelthem Sébastien
9
 * @link      https://github.com/belgattitude/soluble-flexstore
10
 * @copyright Copyright (c) 2016-2017 Vanvelthem Sébastien
11
 * @license   MIT License https://github.com/belgattitude/soluble-flexstore/blob/master/LICENSE.md
12
 *
13
 */
14
15
namespace Soluble\FlexStore\Formatter;
16
17
use Soluble\FlexStore\Exception;
18
use Soluble\FlexStore\I18n\LocalizableInterface;
19
use ArrayObject;
20
use Locale;
21
use NumberFormatter as IntlNumberFormatter;
22
23
class NumberFormatter implements FormatterInterface, LocalizableInterface, FormatterNumberInterface
24
{
25
    public const NO_BREAK_SPACE_HEX = 'c2a0';
26
    public const NARROW_NO_BREAK_SPACE_HEX = 'e280af';
27
28
    /**
29
     * Formatter instances.
30
     *
31
     * @var array
32
     */
33
    protected $formatters = [];
34
35
    /**
36
     * @var array
37
     */
38
    protected $params = [];
39
40
    /**
41
     * @var array
42
     */
43
    protected $default_params = [
44
        'decimals' => 2,
45
        'locale' => null,
46
        'pattern' => null,
47
        'disableUseOfNonBreakingSpaces' => false
48
    ];
49
50
    /**
51
     * @param array $params
52
     *
53
     * @throws Exception\ExtensionNotLoadedException if ext/intl is not present
54
     * @throws Exception\InvalidArgumentException
55
     */
56 24
    public function __construct(array $params = [])
57
    {
58 24
        if (!extension_loaded('intl')) {
59
            throw new Exception\ExtensionNotLoadedException(sprintf(
60
                '%s component requires the intl PHP extension',
61
                __NAMESPACE__
62
            ));
63
        }
64
65
        // As default locale may include unsupported
66
        // variants (like 'en_US_POSIX' for example),
67
        // only the 5 chars will be taken into consideration
68
69 24
        $default_locale = Locale::getDefault();
70 24
        $this->default_params['locale'] = substr($default_locale, 0, 5);
71 24
        $this->setParams($params);
72 24
    }
73
74
    /**
75
     * @throws Exception\InvalidArgumentException
76
     *
77
     * @param array $params
78
     */
79 24
    protected function setParams($params)
80
    {
81 24
        $this->params = $this->default_params;
82 24
        foreach ($params as $name => $value) {
83 17
            $method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($name))));
84 17
            if (!method_exists($this, $method)) {
85 3
                throw new Exception\InvalidArgumentException(__METHOD__ . " Parameter '$name' does not exists.");
86
            }
87 14
            $this->$method($value);
88
        }
89 24
    }
90
91 12
    protected function initWhitespaceSeparator(IntlNumberFormatter $formatter): void
92
    {
93 12
        if ($this->params['disableUseOfNonBreakingSpaces'] === true
94 3
        && in_array(bin2hex($formatter->getSymbol(IntlNumberFormatter::GROUPING_SEPARATOR_SYMBOL)), [
95 3
                self::NARROW_NO_BREAK_SPACE_HEX,
96 3
                self::NO_BREAK_SPACE_HEX
97 12
            ], true)) {
98 3
            $formatter->setSymbol(IntlNumberFormatter::GROUPING_SEPARATOR_SYMBOL, ' ');
99
        }
100 12
    }
101
102 6
    protected function loadFormatterId(string $formatterId): void
103
    {
104 6
        $locale = $this->params['locale'];
105 6
        $formatter = new IntlNumberFormatter(
106 6
            $locale,
107 6
            IntlNumberFormatter::DECIMAL
108
        );
109 6
        $formatter->setAttribute(IntlNumberFormatter::FRACTION_DIGITS, $this->params['decimals']);
110 6
        if ($this->params['pattern'] !== null) {
111 2
            $formatter->setPattern($this->params['pattern']);
112
        }
113
114 6
        $this->initWhitespaceSeparator($formatter);
115
116 6
        $this->formatters[$formatterId] = $formatter;
117 6
    }
118
119
    /**
120
     * Format a number.
121
     *
122
     * @param float $number
123
     *
124
     * @return string
125
     */
126 3
    public function format($number, ArrayObject $row = null): string
127
    {
128 3
        $locale = $this->params['locale'];
129
        //$formatterId = md5($locale);
130 3
        $formatterId = $locale . (string) $this->params['pattern'];
131 3
        if (!array_key_exists($formatterId, $this->formatters)) {
132 3
            $this->loadFormatterId($formatterId);
133
        }
134
135 3
        if (!is_numeric($number)) {
136 2
            $this->throwNumberFormatterException($this->formatters[$formatterId], $number);
137
        }
138
139 1
        return $this->formatters[$formatterId]->format($number);
140
    }
141
142
    /**
143
     * Throws an Exception when number cannot be formatted.
144
     *
145
     * @param IntlNumberFormatter $intlFormatter
146
     * @param int|string|float    $number
147
     *
148
     * @throws Exception\RuntimeException
149
     */
150 4
    protected function throwNumberFormatterException(IntlNumberFormatter $intlFormatter, $number): void
151
    {
152 4
        $error_code = $intlFormatter->getErrorCode();
153 4
        if (is_scalar($number)) {
154 2
            $val = (string) $number;
155
        } else {
156 2
            $val = 'type: ' . gettype($number);
157
        }
158 4
        throw new Exception\RuntimeException(__METHOD__ . " Cannot format value '$val', Intl/NumberFormatter error code: $error_code.");
159
    }
160
161
    /**
162
     * Set locale to use instead of the default.
163
     *
164
     * @param string $locale
165
     */
166 17
    public function setLocale(?string $locale): self
167
    {
168 17
        $this->params['locale'] = $locale;
169
170 17
        return $this;
171
    }
172
173
    /**
174
     * Get the locale to use.
175
     *
176
     * @return string|null
177
     */
178 6
    public function getLocale(): ?string
179
    {
180 6
        return $this->params['locale'];
181
    }
182
183
    /**
184
     * Set decimals.
185
     *
186
     * @param int $decimals
187
     */
188 15
    public function setDecimals($decimals): self
189
    {
190 15
        $this->params['decimals'] = $decimals;
191
192 15
        return $this;
193
    }
194
195
    /**
196
     * @return int
197
     */
198 6
    public function getDecimals(): int
199
    {
200 6
        return $this->params['decimals'];
201
    }
202
203
    /**
204
     * Set the number pattern, (#,##0.###, ....).
205
     *
206
     * @see http://php.net/manual/en/numberformatter.setpattern.php
207
     *
208
     * @param string $pattern
209
     */
210 8
    public function setPattern($pattern): self
211
    {
212 8
        $this->params['pattern'] = $pattern;
213
214 8
        return $this;
215
    }
216
217
    /**
218
     * Get the number pattern.
219
     *
220
     * @return string|null
221
     */
222 6
    public function getPattern(): ?string
223
    {
224 6
        return $this->params['pattern'];
225
    }
226
227 3
    public function setDisableUseOfNonBreakingSpaces(bool $disable = true): self
228
    {
229 3
        $this->params['disableUseOfNonBreakingSpaces'] = $disable;
230
231 3
        return $this;
232
    }
233
}
234