OrdinalNumeralGenerator::getCases()   F
last analyzed

Complexity

Conditions 36
Paths 300

Size

Total Lines 124

Duplication

Lines 30
Ratio 24.19 %

Code Coverage

Tests 62
CRAP Score 40.4355

Importance

Changes 0
Metric Value
cc 36
nc 300
nop 2
dl 30
loc 124
ccs 62
cts 73
cp 0.8493
crap 40.4355
rs 1.6666
c 0
b 0
f 0

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
namespace morphos\Russian;
3
4
use morphos\NumeralGenerator;
5
use morphos\S;
6
7
/**
8
 * Rules are from http://www.fio.ru/pravila/grammatika/sklonenie-imen-chislitelnykh/
9
 *            and http://school-collection.edu.ru/dlrstore-wrapper/ebbc76cf-46b6-4d51-ad92-d9a84db35cfa/[I-RUS_06-05]_[TE_16-SU]/[I-RUS_06-05]_[TE_16-SU].html
10
 */
11
class OrdinalNumeralGenerator extends NumeralGenerator implements Cases
12
{
13
    use RussianLanguage, CasesHelper;
14
15
    protected static $words = [
16
        1 => 'первый',
17
        2 => 'второй',
18
        3 => 'третий',
19
        4 => 'четвертый',
20
        5 => 'пятый',
21
        6 => 'шестой',
22
        7 => 'седьмой',
23
        8 => 'восьмой',
24
        9 => 'девятый',
25
        10 => 'десятый',
26
        11 => 'одиннадцатый',
27
        12 => 'двенадцатый',
28
        13 => 'тринадцатый',
29
        14 => 'четырнадцатый',
30
        15 => 'пятнадцатый',
31
        16 => 'шестнадцатый',
32
        17 => 'семнадцатый',
33
        18 => 'восемнадцатый',
34
        19 => 'девятнадцатый',
35
        20 => 'двадцатый',
36
        30 => 'тридцатый',
37
        40 => 'сороковой',
38
        50 => 'пятьдесятый',
39
        60 => 'шестьдесятый',
40
        70 => 'семьдесятый',
41
        80 => 'восемьдесятый',
42
        90 => 'девяностый',
43
        100 => 'сотый',
44
        200 => 'двухсотый',
45
        300 => 'трехсотый',
46
        400 => 'четырехсотый',
47
        500 => 'пятисотый',
48
        600 => 'шестисотый',
49
        700 => 'семисотый',
50
        800 => 'восемисотый',
51
        900 => 'девятисотый',
52
    ];
53
54
    protected static $exponents = [
55
        '1000' => 'тысячный',
56
        '1000000' => 'миллионный',
57
        '1000000000' => 'миллиардный',
58
        '1000000000000' => 'триллионный',
59
    ];
60
61
    protected static $multipliers = [
62
        2 => 'двух',
63
        3 => 'трех',
64
        4 => 'четырех',
65
        5 => 'пяти',
66
        6 => 'шести',
67
        7 => 'седьми',
68
        8 => 'восьми',
69
        9 => 'девяти',
70
        10 => 'десяти',
71
        11 => 'одиннадцати',
72
        12 => 'двенадцати',
73
        13 => 'тринадцати',
74
        14 => 'четырнадцати',
75
        15 => 'пятнадцати',
76
        16 => 'шестнадцати',
77
        17 => 'семнадцати',
78
        18 => 'восемнадцати',
79
        19 => 'девятнадцати',
80
        20 => 'двадцати',
81
        30 => 'тридцати',
82
        40 => 'сорока',
83
        50 => 'пятьдесяти',
84
        60 => 'шестьдесяти',
85
        70 => 'семьдесяти',
86
        80 => 'восемьдесяти',
87
        90 => 'девяности',
88
        100 => 'сто',
89
        200 => 'двухста',
90
        300 => 'трехста',
91
        400 => 'четырехста',
92
        500 => 'пятиста',
93
        600 => 'шестиста',
94
        700 => 'семиста',
95
        800 => 'восемиста',
96
        900 => 'девятиста',
97
    ];
98
99
    /**
100
     * @param        $number
101
     * @param string $gender
102
     *
103
     * @return array
104
     * @throws \Exception
105
     */
106 34
    public static function getCases($number, $gender = self::MALE)
107
    {
108
        // simple numeral
109 34
        if (isset(static::$words[$number]) || isset(static::$exponents[$number])) {
110 34
            $word = isset(static::$words[$number]) ? static::$words[$number] : static::$exponents[$number];
111
            // special rules for 3
112 34
            if ($number == 3) {
113 2
                $prefix = S::slice($word, 0, -2);
114
                return [
115 2
                    static::IMENIT => $prefix.($gender == static::MALE ? 'ий' : ($gender == static::FEMALE ? 'ья' : 'ье')),
116 2
                    static::RODIT => $prefix.($gender == static::FEMALE ? 'ьей' : 'ьего'),
117 2
                    static::DAT => $prefix.($gender == static::FEMALE ? 'ьей' : 'ьему'),
118 2
                    static::VINIT => $prefix.($gender == static::FEMALE ? 'ью' : 'ьего'),
119 2
                    static::TVORIT => $prefix.($gender == static::FEMALE ? 'ьей' : 'ьим'),
120 2
                    static::PREDLOJ => $prefix.($gender == static::FEMALE ? 'ьей' : 'ьем'),
121
                ];
122
            } else {
123
                switch ($gender) {
124 32 View Code Duplication
                    case static::MALE:
0 ignored issues
show
Duplication introduced by wapmorgan
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...
125 28
                        $prefix = S::slice($word, 0, $number == 40 ? -1 : -2);
126
                        return [
127 28
                            static::IMENIT => $word,
128 28
                            static::RODIT => $prefix.'ого',
129 28
                            static::DAT => $prefix.'ому',
130 28
                            static::VINIT => $word,
131 28
                            static::TVORIT => $prefix.'ым',
132 28
                            static::PREDLOJ => $prefix.'ом',
133
                        ];
134
135 4 View Code Duplication
                    case static::FEMALE:
0 ignored issues
show
Duplication introduced by wapmorgan
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...
136 2
                        $prefix = S::slice($word, 0, $number == 40 ? -1 : -2);
137
                        return [
138 2
                            static::IMENIT => $prefix.'ая',
139 2
                            static::RODIT => $prefix.'ой',
140 2
                            static::DAT => $prefix.'ой',
141 2
                            static::VINIT => $prefix.'ую',
142 2
                            static::TVORIT => $prefix.'ой',
143 2
                            static::PREDLOJ => $prefix.'ой',
144
                        ];
145
146 2 View Code Duplication
                    case static::NEUTER:
0 ignored issues
show
Duplication introduced by wapmorgan
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...
147 2
                        $prefix = S::slice($word, 0, $number == 40 ? -1 : -2);
148
                        return [
149 2
                            static::IMENIT => $prefix.'ое',
150 2
                            static::RODIT => $prefix.'ого',
151 2
                            static::DAT => $prefix.'ому',
152 2
                            static::VINIT => $prefix.'ое',
153 2
                            static::TVORIT => $prefix.'ым',
154 2
                            static::PREDLOJ => $prefix.'ом',
155
                        ];
156
                }
157
            }
158
        }
159
        // compound numeral
160
        else {
161 20
            $ordinal_part = null;
162 20
            $ordinal_prefix = null;
163 20
            $result = [];
164
165
            // test for exponents. If smaller summand of number is an exponent, declinate it
166 20
            foreach (array_reverse(static::$exponents, true) as $word_number => $word) {
167 20
                if ($number >= $word_number && ($number % $word_number) == 0) {
168
                    $count = floor($number / $word_number) % 1000;
169
                    $number -= ($count * $word_number);
170
                    foreach (array_reverse(static::$multipliers, true) as $multiplier => $multipliers_word) {
171
                        if ($count >= $multiplier) {
172
                            $ordinal_prefix .= $multipliers_word;
173
                            $count -= $multiplier;
174
                        }
175
                    }
176
                    $ordinal_part = static::getCases($word_number, $gender);
177
                    foreach ($ordinal_part as $case => $ordinal_word) {
0 ignored issues
show
Bug introduced by wapmorgan
The expression $ordinal_part of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
178
                        $ordinal_part[$case] = $ordinal_prefix.$ordinal_part[$case];
179
                    }
180
181 20
                    break;
182
                }
183
            }
184
185
            // otherwise, test if smaller summand is just a number with it's own name
186 20
            if (empty($ordinal_part)) {
187
                // get the smallest number with it's own name
188 20
                foreach (static::$words as $word_number => $word) {
189 20
                    if ($number >= $word_number) {
190 20
                        if ($word_number <= 9) {
191 20
                            if ($number % 10 == 0) {
192 4
                                continue;
193
                            }
194
                            // check for case when word_number smaller than should be used (e.g. 1,2,3 when it can be 4 (number: 344))
195 16
                            if (($number % 10) > $word_number) {
196 12
                                continue;
197
                            }
198
                            // check that there is no two-digits number with it's own name (e.g. 13 for 113)
199 16
                            if (isset(static::$words[$number % 100]) && $number % 100 > $word_number) {
200 16
                                continue;
201
                            }
202 10
                        } elseif ($word_number <= 90) {
203
                            // check for case when word_number smaller than should be used (e.g. 10, 11, 12 when it can be 13)
204 10
                            if (($number % 100) > $word_number) {
205 10
                                continue;
206
                            }
207
                        }
208 20
                        $ordinal_part = static::getCases($word_number, $gender);
209 20
                        $number -= $word_number;
210 20
                        break;
211
                    }
212
                }
213
            }
214
215
            // if number has second summand, get cardinal form of it
216 20
            if ($number > 0) {
217 20
                $cardinal_part = CardinalNumeralGenerator::getCase($number, static::IMENIT, $gender);
218
219
                // make one array with cases and delete 'o/об' prepositional from all parts except the last one
220 20
                foreach ([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ] as $case) {
221 20
                    $result[$case] = $cardinal_part.' '.$ordinal_part[$case];
222
                }
223
            } else {
224
                $result = $ordinal_part;
225
            }
226
227 20
            return $result;
228
        }
229
    }
230
231
    /**
232
     * @param $number
233
     * @param $case
234
     * @param string $gender
235
     * @return mixed
236
     * @throws \Exception
237
     */
238 17
    public static function getCase($number, $case, $gender = self::MALE)
239
    {
240 17
        $case = static::canonizeCase($case);
241 17
        $forms = static::getCases($number, $gender);
242 17
        return $forms[$case];
243
    }
244
}
245