Completed
Push — master ( 756960...9fe4f5 )
by Sébastien
02:43
created

NumberFormatter::initWhitespaceSeparator()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4.125

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 4
cts 8
cp 0.5
rs 9.9332
c 0
b 0
f 0
cc 3
nc 2
nop 1
crap 4.125
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
        'force_non_breaking_whitespace' => 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['force_non_breaking_whitespace'] === true
94
        && in_array(bin2hex($formatter->getSymbol(IntlNumberFormatter::GROUPING_SEPARATOR_SYMBOL)), [
95
                self::NARROW_NO_BREAK_SPACE_HEX,
96
                self::NO_BREAK_SPACE_HEX
97 12
            ], true)) {
98
            $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
     * @return NumberFormatter
167
     */
168 17
    public function setLocale($locale)
169
    {
170 17
        $this->params['locale'] = $locale;
171
172 17
        return $this;
173
    }
174
175
    /**
176
     * Get the locale to use.
177
     *
178
     * @return string|null
179
     */
180 6
    public function getLocale()
181
    {
182 6
        return $this->params['locale'];
183
    }
184
185
    /**
186
     * Set decimals.
187
     *
188
     * @param int $decimals
189
     *
190
     * @return self
191
     */
192 15
    public function setDecimals($decimals)
193
    {
194 15
        $this->params['decimals'] = $decimals;
195
196 15
        return $this;
197
    }
198
199
    /**
200
     * @return int
201
     */
202 6
    public function getDecimals()
203
    {
204 6
        return $this->params['decimals'];
205
    }
206
207
    /**
208
     * Set the number pattern, (#,##0.###, ....).
209
     *
210
     * @see http://php.net/manual/en/numberformatter.setpattern.php
211
     *
212
     * @param string $pattern
213
     *
214
     * @return NumberFormatter
215
     */
216 8
    public function setPattern($pattern)
217
    {
218 8
        $this->params['pattern'] = $pattern;
219
220 8
        return $this;
221
    }
222
223
    /**
224
     * Get the number pattern.
225
     *
226
     * @return string|null
227
     */
228 6
    public function getPattern()
229
    {
230 6
        return $this->params['pattern'];
231
    }
232
}
233