Issues (2)

src/Convert.php (2 issues)

Labels
Severity
1
<?php
2
3
/*
4
 * This file is part of the coolert/number_in_chinese.
5
 *
6
 * (c) coolert <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Coolert\NumberInChinese;
13
14
use Coolert\NumberInChinese\Exceptions\DictionarySetException;
15
use Coolert\NumberInChinese\Exceptions\ExtensionException;
16
use Coolert\NumberInChinese\Exceptions\InvalidArgumentException;
17
use Coolert\NumberInChinese\Exceptions\TypeSetException;
18
19
/**
20
 * Class Convert.
21
 */
22
class Convert
23
{
24
    const SIMPLE_DIC = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
25
    const SIMPLE_SPEC_DIC = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
26
    const UPPER_DIC = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '镹', '拾'];
27
    const SIMPLE_UNIT_DIC = ['无量大数', '万', '亿', '兆', '京', '垓', '秭', '穰', '沟', '涧', '正', '载', '极', '恒河沙', '阿僧祇', '那由他', '不可思议'];
28
    const TRADITION_UNIT_DIC = ['無量大數', '萬', '億', '兆', '京', '垓', '秭', '穰', '溝', '澗', '正', '載', '極', '恆河沙', '阿僧祇', '那由他', '不可思議'];
29
    const EXTENSION = ['bcmath', 'mbstring'];
30
31
    public $dic;
32
    public $unit_dic;
33
    public $type;
34
35
    /**
36
     * @throws ExtensionException
37
     */
38 15
    public function __construct()
39
    {
40 15
        $extensions = [];
41 15
        foreach (self::EXTENSION as $name) {
42 15
            $extensions[$name] = true === \extension_loaded($name);
43
        }
44 15
        $this->extensionException($extensions);
45
    }
46
47
    /**
48
     * Check php extension.
49
     *
50
     * @param array $extensions
51
     *
52
     * @throws ExtensionException
53
     */
54 15
    public function extensionException($extensions)
55
    {
56 15
        foreach ($extensions as $name => $state) {
57 15
            if (false === $state) {
58 2
                throw new ExtensionException('Disabled extension '.$name);
59
            }
60
        }
61
    }
62
63
    /**
64
     * Convert numbers into Chinese numbers.
65
     *
66
     * @param int $number
67
     * @param int $character
68
     * @param int $unit
69
     *
70
     * @return string
71
     *
72
     * @throws InvalidArgumentException
73
     * @throws DictionarySetException
74
     * @throws TypeSetException
75
     */
76 2
    public function convertNumbers($number, $character = 1, $unit = 1)
77
    {
78 2
        $number = $this->formatNumber($number);
79 2
        $this->selectDictionaries($character, $unit);
80 2
        if ('float' == $this->type) {
81 1
            $number_arr = \explode('.', $number);
82 1
            $integer_part = $this->convertInteger($number_arr[0]);
83 1
            $decimal_part = $this->convertDecimal($number_arr[1]);
84
85 1
            return $integer_part.'点'.$decimal_part;
86 2
        } elseif ('int' == $this->type) {
87 1
            return $this->convertInteger($number);
88
        } else {
89 1
            throw new TypeSetException('Invalid type set');
90
        }
91
    }
92
93
    /**
94
     * Replace Chinese characters string.
95
     *
96
     * @param string $string
97
     * @param string $replacement
98
     * @param int    $start
99
     * @param int    $length
100
     * @param string $encoding
101
     *
102
     * @return string
103
     */
104 2
    public function mbSubStrReplace($string, $replacement, $start, $length = null, $encoding = null)
105
    {
106 2
        $string_length = (true === \is_null($encoding)) ? \mb_strlen($string) : \mb_strlen($string, $encoding);
107 2
        if ($start < 0) {
108 1
            $start = \max(0, $string_length + $start);
109 2
        } elseif ($start > $string_length) {
110 1
            $start = $string_length;
111
        }
112 2
        if ($length < 0) {
113 1
            $length = \max(0, $string_length - $start + $length);
114 2
        } elseif ((true === \is_null($length)) || ($length > $string_length)) {
115 1
            $length = $string_length;
116
        }
117 2
        if (($start + $length) > $string_length) {
118 1
            $length = $string_length - $start;
119
        }
120 2
        if (true === \is_null($encoding)) {
121 2
            return \mb_substr($string, 0, $start).$replacement.\mb_substr($string, $start + $length, $string_length - $start - $length);
122
        }
123
124 1
        return \mb_substr($string, 0, $start, $encoding).$replacement.\mb_substr($string, $start + $length, $string_length - $start - $length, $encoding);
125
    }
126
127
    /**
128
     * Select character dictionary and unit dictionary.
129
     *
130
     * @param int $character
131
     * @param int $unit
132
     *
133
     * @throws DictionarySetException
134
     */
135 5
    public function selectDictionaries($character, $unit)
136
    {
137
        switch ($character) {
138 5
            case 1:
139 4
                $dic = self::SIMPLE_DIC;
140 4
                break;
141 2
            case 2:
142 1
                $dic = self::SIMPLE_SPEC_DIC;
143 1
                break;
144 2
            case 3:
145 1
                $dic = self::UPPER_DIC;
146 1
                break;
147
            default:
148 1
                throw new DictionarySetException('Invalid dictionary type');
149
        }
150 4
        $this->dic = $dic;
151
        switch ($unit) {
152 4
            case 1:
153 3
                $unit_dic = self::SIMPLE_UNIT_DIC;
154 3
                break;
155 2
            case 2:
156 1
                $unit_dic = self::TRADITION_UNIT_DIC;
157 1
                break;
158
            default:
159 1
                throw new DictionarySetException('Invalid unit dictionary type');
160
        }
161 3
        $this->unit_dic = $unit_dic;
162
    }
163
164
    /**
165
     * Check input data and format data into usable numbers.
166
     *
167
     * @param $number
168
     *
169
     * @return string
170
     *
171
     * @throws InvalidArgumentException
172
     */
173 4
    public function formatNumber($number)
174
    {
175 4
        if (!\is_string($number)) {
176 1
            throw new InvalidArgumentException('Invalid number type, must be a string');
177
        }
178 3
        $number = \trim(\str_replace(' ', '', $number), ' \t\n\r');
179 3
        if (0 !== $number) {
180 3
            $number = \ltrim($number, '\0\x0B');
181
        }
182 3
        $pos_dot = \strpos($number, '.');
183 3
        if (false !== $pos_dot) {
184 2
            if (0 === $pos_dot) {
185 1
                $number = '0'.$number;
186
            }
187 2
            $number = \rtrim(\rtrim($number, '0'), '.');
188
        }
189 3
        if (0 === \preg_match('/^\d+(\.{0,1}\d+){0,1}$/', $number)) {
190 1
            throw new InvalidArgumentException('Invalid value number: '.$number);
191
        }
192 2
        if (false === \strpos($number, '.')) {
193 2
            $this->type = 'int';
194
        } else {
195 2
            $this->type = 'float';
196
        }
197
198 2
        return $number;
199
    }
200
201
    /**
202
     * Convert integer part.
203
     *
204
     * @param $integer
205
     *
206
     * @return string
207
     *
208
     * @throws DictionarySetException
209
     */
210 4
    public function convertInteger($integer)
211
    {
212 4
        if (empty($this->dic)) {
213 1
            throw new DictionarySetException('Dictionary is not set');
214
        }
215 3
        if (empty($this->unit_dic)) {
216 1
            throw new DictionarySetException('Unit dictionary is not set');
217
        }
218 2
        $num_arr_chunk = \array_chunk(\array_reverse(\str_split($integer)), 68);
0 ignored issues
show
It seems like str_split($integer) can also be of type true; however, parameter $array of array_reverse() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

218
        $num_arr_chunk = \array_chunk(\array_reverse(/** @scrutinizer ignore-type */ \str_split($integer)), 68);
Loading history...
219 2
        $complete_str = '';
220 2
        foreach ($num_arr_chunk as $cycle => $num_arr) {
221 2
            $length = \count($num_arr);
222 2
            $match_unit_digits = 0 == $length % 4 ? \bcdiv($length, 4) : \bcdiv($length, 4) + 1;
223 2
            $chunk_unit_dic = \array_slice($this->unit_dic, 0, $match_unit_digits);
0 ignored issues
show
It seems like $match_unit_digits can also be of type string; however, parameter $length of array_slice() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

223
            $chunk_unit_dic = \array_slice($this->unit_dic, 0, /** @scrutinizer ignore-type */ $match_unit_digits);
Loading history...
224 2
            if (0 == $cycle) {
225 2
                $chunk_unit_dic[0] = '';
226
            }
227 2
            for ($a = 0; $a < $match_unit_digits; ++$a) {
228 2
                if ($this->dic == $this::UPPER_DIC) {
229 1
                    \array_splice($chunk_unit_dic, $a * 4 + 1, 0, ['拾', '百', '千']);
230
                } else {
231 2
                    \array_splice($chunk_unit_dic, $a * 4 + 1, 0, ['十', '百', '千']);
232
                }
233
            }
234 2
            $chinese_num = '';
235 2
            foreach ($num_arr as $key => $value) {
236 2
                if (0 == $key % 4) {
237 2
                    if ($key > 4 && $chunk_unit_dic[$key - 4] == \mb_substr($chinese_num, 0, 1)) {
238 1
                        $chinese_num = $this->mbSubStrReplace($chinese_num, '', 0, 1);
239 1
                        if (\mb_substr($chinese_num, 0, 1) != $this->dic[0]) {
240 1
                            $chinese_num = $this->dic[0].$chinese_num;
241
                        }
242
                    }
243 2
                    $chinese_num = (0 == $value && 1 != $length ? '' : $this->dic[$value]).$chunk_unit_dic[$key].$chinese_num;
244
                } else {
245 2
                    if (0 == $value && '' == $chinese_num) {
246 1
                        continue;
247 2
                    } elseif (0 == $value && '' != $chinese_num) {
248 1
                        if (0 != $num_arr[$key - 1]) {
249 1
                            $chinese_num = $this->dic[0].$chinese_num;
250
                        }
251
                    } else {
252 2
                        if (1 == $value && 2 == $length % 4 && $length == $key + 1) {
253 1
                            $chinese_num = $chunk_unit_dic[$key].$chinese_num;
254
                        } else {
255 2
                            $chinese_num = $this->dic[$value].$chunk_unit_dic[$key].$chinese_num;
256
                        }
257
                    }
258
                }
259
            }
260 2
            $complete_str = $chinese_num.$complete_str;
261
        }
262
263 2
        return $complete_str;
264
    }
265
266
    /**
267
     * Convert decimal part.
268
     *
269
     * @param $decimal
270
     *
271
     * @return string
272
     *
273
     * @throws DictionarySetException
274
     */
275 3
    public function convertDecimal($decimal)
276
    {
277 3
        if (empty($this->dic)) {
278 1
            throw new DictionarySetException('Dictionary is not set');
279
        }
280 2
        $num_arr = \str_split($decimal);
281 2
        $converted_str = '';
282 2
        foreach ($num_arr as $v) {
283 2
            $converted_str .= $this->dic[$v];
284
        }
285
286 2
        return $converted_str;
287
    }
288
}
289