LocalizedStringToNumberTransformer::transform()   C
last analyzed

Complexity

Conditions 16
Paths 23

Size

Total Lines 58
Code Lines 29

Duplication

Lines 6
Ratio 10.34 %

Code Coverage

Tests 33
CRAP Score 16.0065

Importance

Changes 0
Metric Value
dl 6
loc 58
ccs 33
cts 34
cp 0.9706
rs 6.4446
c 0
b 0
f 0
cc 16
eloc 29
nc 23
nop 1
crap 16.0065

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace TreeHouse\Feeder\Modifier\Data\Transformer;
4
5
use TreeHouse\Feeder\Exception\TransformationFailedException;
6
7
/**
8
 * Transforms between a number type and a localized number with grouping (each thousand) and comma separators.
9
 *
10
 * Copied from Symfony's Form component
11
 */
12
class LocalizedStringToNumberTransformer implements TransformerInterface
13
{
14
    /**
15
     * The locale to use.
16
     *
17
     * @var string
18
     */
19
    protected $locale;
20
21
    /**
22
     * Number of fraction digits.
23
     *
24
     * @var int
25
     */
26
    protected $precision;
27
28
    /**
29
     * Whether to use a grouping separator.
30
     *
31
     * @var bool
32
     */
33
    protected $grouping;
34
35
    /**
36
     * The rounding mode to use.
37
     *
38
     * @var int
39
     */
40
    protected $roundingMode;
41
42
    /**
43
     * @param string $locale
44
     * @param int    $precision
45
     * @param bool   $grouping
46
     * @param int    $roundingMode
47
     */
48 224
    public function __construct($locale = null, $precision = null, $grouping = null, $roundingMode = null)
49
    {
50 224
        if (null === $locale) {
51 188
            $locale = \Locale::getDefault();
52 188
        }
53
54 224
        if (null === $grouping) {
55 182
            $grouping = false;
56 182
        }
57
58 224
        if (null === $roundingMode) {
59 70
            $roundingMode = \NumberFormatter::ROUND_HALFUP;
60 70
        }
61
62 224
        $this->locale = $locale;
63 224
        $this->precision = $precision;
64 224
        $this->grouping = $grouping;
65 224
        $this->roundingMode = $roundingMode;
66 224
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71 222
    public function transform($value)
72
    {
73 222
        if (!is_string($value) && !is_numeric($value)) {
74 4
            throw new TransformationFailedException(
75 4
                sprintf('Expected a string to transform, got "%s" instead.', json_encode($value))
76 4
            );
77
        }
78
79 218
        if ('' === $value) {
80 4
            return null;
81
        }
82
83 214
        if ('NaN' === $value) {
84 2
            throw new TransformationFailedException('"NaN" is not a valid number');
85
        }
86
87 212
        $position = 0;
88 212
        $formatter = $this->getNumberFormatter();
89 212
        $groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
90 212
        $decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
91
92 212 View Code Duplication
        if ('.' !== $decSep && (!$this->grouping || '.' !== $groupSep)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
93 182
            $value = str_replace('.', $decSep, $value);
94 182
        }
95
96 212 View Code Duplication
        if (',' !== $decSep && (!$this->grouping || ',' !== $groupSep)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97 2
            $value = str_replace(',', $decSep, $value);
98 2
        }
99
100 212
        $result = $formatter->parse($value, \NumberFormatter::TYPE_DOUBLE, $position);
101
102 212
        if (intl_is_failure($formatter->getErrorCode())) {
103 10
            throw new TransformationFailedException($formatter->getErrorMessage());
104
        }
105
106 202
        if ($result >= PHP_INT_MAX || $result <= -PHP_INT_MAX) {
107 4
            throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like');
108
        }
109
110 198
        $encoding = mb_detect_encoding($value);
111 198
        $length = mb_strlen($value, $encoding);
112
113
        // After parsing, position holds the index of the character where the parsing stopped
114 198
        if ($position < $length) {
115
            // Check if there are unrecognized characters at the end of the
116
            // number (excluding whitespace characters)
117 12
            $remainder = trim(mb_substr($value, $position, $length, $encoding), " \t\n\r\0\x0b\xc2\xa0");
118
119 12
            if ('' !== $remainder) {
120 12
                throw new TransformationFailedException(
121 12
                    sprintf('The number contains unrecognized characters: "%s"', $remainder)
122 12
                );
123
            }
124
        }
125
126
        // Only the format() method in the NumberFormatter rounds, whereas parse() does not
127 186
        return $this->round($result);
128
    }
129
130
    /**
131
     * Returns a preconfigured \NumberFormatter instance.
132
     *
133
     * @return \NumberFormatter
134
     */
135 212
    protected function getNumberFormatter()
136
    {
137 212
        $formatter = new \NumberFormatter($this->locale, \NumberFormatter::DECIMAL);
138
139 212
        if (null !== $this->precision) {
140 152
            $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->precision);
141 152
            $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode);
142 152
        }
143
144 212
        $formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->grouping);
145
146 212
        return $formatter;
147
    }
148
149
    /**
150
     * Rounds a number according to the configured precision and rounding mode.
151
     *
152
     * @param int|float $number A number.
153
     *
154
     * @return int|float The rounded number.
155
     */
156 186
    private function round($number)
157
    {
158 186
        if (null !== $this->precision && null !== $this->roundingMode) {
159
            // shift number to maintain the correct precision during rounding
160 152
            $roundingCoef = pow(10, $this->precision);
161 152
            $number *= $roundingCoef;
162
163 152
            switch ($this->roundingMode) {
164 152
                case \NumberFormatter::ROUND_CEILING:
165 16
                    $number = ceil($number);
166 16
                    break;
167 136
                case \NumberFormatter::ROUND_FLOOR:
168 16
                    $number = floor($number);
169 16
                    break;
170 120 View Code Duplication
                case \NumberFormatter::ROUND_UP:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171 16
                    $number = $number > 0 ? ceil($number) : floor($number);
172 16
                    break;
173 104 View Code Duplication
                case \NumberFormatter::ROUND_DOWN:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174 16
                    $number = $number > 0 ? floor($number) : ceil($number);
175 16
                    break;
176 88
                case \NumberFormatter::ROUND_HALFEVEN:
177 40
                    $number = round($number, 0, PHP_ROUND_HALF_EVEN);
178 40
                    break;
179 48
                case \NumberFormatter::ROUND_HALFUP:
180 24
                    $number = round($number, 0, PHP_ROUND_HALF_UP);
181 24
                    break;
182 24
                case \NumberFormatter::ROUND_HALFDOWN:
183 24
                    $number = round($number, 0, PHP_ROUND_HALF_DOWN);
184 24
                    break;
185 152
            }
186
187 152
            $number /= $roundingCoef;
188 152
        }
189
190 186
        return $number;
191
    }
192
}
193