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
     * @return array
103
     */
104 34
    public static function getCases($number, $gender = self::MALE)
105
    {
106
        // simple numeral
107 34
        if (isset(self::$words[$number]) || isset(self::$exponents[$number])) {
108 34
            $word = isset(self::$words[$number]) ? self::$words[$number] : self::$exponents[$number];
109
            // special rules for 3
110 34
            if ($number == 3) {
111 2
                $prefix = S::slice($word, 0, -2);
112
                return [
113 2
                    self::IMENIT => $prefix.($gender == self::MALE ? 'ий' : ($gender == self::FEMALE ? 'ья' : 'ье')),
114 2
                    self::RODIT => $prefix.($gender == self::FEMALE ? 'ьей' : 'ьего'),
115 2
                    self::DAT => $prefix.($gender == self::FEMALE ? 'ьей' : 'ьему'),
116 2
                    self::VINIT => $prefix.($gender == self::FEMALE ? 'ью' : 'ьего'),
117 2
                    self::TVORIT => $prefix.($gender == self::FEMALE ? 'ьей' : 'ьим'),
118 2
                    self::PREDLOJ => $prefix.($gender == self::FEMALE ? 'ьей' : 'ьем'),
119
                ];
120
            } else {
121
                switch ($gender) {
122 32 View Code Duplication
                    case self::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...
123 28
                        $prefix = S::slice($word, 0, $number == 40 ? -1 : -2);
124
                        return [
125 28
                            self::IMENIT => $word,
126 28
                            self::RODIT => $prefix.'ого',
127 28
                            self::DAT => $prefix.'ому',
128 28
                            self::VINIT => $word,
129 28
                            self::TVORIT => $prefix.'ым',
130 28
                            self::PREDLOJ => $prefix.'ом',
131
                        ];
132
133 4 View Code Duplication
                    case self::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...
134 2
                        $prefix = S::slice($word, 0, $number == 40 ? -1 : -2);
135
                        return [
136 2
                            self::IMENIT => $prefix.'ая',
137 2
                            self::RODIT => $prefix.'ой',
138 2
                            self::DAT => $prefix.'ой',
139 2
                            self::VINIT => $prefix.'ую',
140 2
                            self::TVORIT => $prefix.'ой',
141 2
                            self::PREDLOJ => $prefix.'ой',
142
                        ];
143
144 2 View Code Duplication
                    case self::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...
145 2
                        $prefix = S::slice($word, 0, $number == 40 ? -1 : -2);
146
                        return [
147 2
                            self::IMENIT => $prefix.'ое',
148 2
                            self::RODIT => $prefix.'ого',
149 2
                            self::DAT => $prefix.'ому',
150 2
                            self::VINIT => $prefix.'ое',
151 2
                            self::TVORIT => $prefix.'ым',
152 2
                            self::PREDLOJ => $prefix.'ом',
153
                        ];
154
                }
155
            }
156
        }
157
        // compound numeral
158
        else {
159 20
            $ordinal_part = null;
160 20
            $ordinal_prefix = null;
161 20
            $result = [];
162
163
            // test for exponents. If smaller summand of number is an exponent, declinate it
164 20
            foreach (array_reverse(self::$exponents, true) as $word_number => $word) {
165 20
                if ($number >= $word_number && ($number % $word_number) == 0) {
166
                    $count = floor($number / $word_number) % 1000;
167
                    $number -= ($count * $word_number);
168
                    foreach (array_reverse(self::$multipliers, true) as $multiplier => $multipliers_word) {
169
                        if ($count >= $multiplier) {
170
                            $ordinal_prefix .= $multipliers_word;
171
                            $count -= $multiplier;
172
                        }
173
                    }
174
                    $ordinal_part = self::getCases($word_number, $gender);
175
                    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...
176
                        $ordinal_part[$case] = $ordinal_prefix.$ordinal_part[$case];
177
                    }
178
179 20
                    break;
180
                }
181
            }
182
183
            // otherwise, test if smaller summand is just a number with it's own name
184 20
            if (empty($ordinal_part)) {
185
                // get the smallest number with it's own name
186 20
                foreach (self::$words as $word_number => $word) {
187 20
                    if ($number >= $word_number) {
188 20
                        if ($word_number <= 9) {
189 20
                            if ($number % 10 == 0) {
190 4
                                continue;
191
                            }
192
                            // check for case when word_number smaller than should be used (e.g. 1,2,3 when it can be 4 (number: 344))
193 16
                            if (($number % 10) > $word_number) {
194 12
                                continue;
195
                            }
196
                            // check that there is no two-digits number with it's own name (e.g. 13 for 113)
197 16
                            if (isset(self::$words[$number % 100]) && $number % 100 > $word_number) {
198 16
                                continue;
199
                            }
200 10
                        } elseif ($word_number <= 90) {
201
                            // check for case when word_number smaller than should be used (e.g. 10, 11, 12 when it can be 13)
202 10
                            if (($number % 100) > $word_number) {
203 10
                                continue;
204
                            }
205
                        }
206 20
                        $ordinal_part = self::getCases($word_number, $gender);
207 20
                        $number -= $word_number;
208 20
                        break;
209
                    }
210
                }
211
            }
212
213
            // if number has second summand, get cardinal form of it
214 20
            if ($number > 0) {
215 20
                $cardinal_part = CardinalNumeralGenerator::getCase($number, self::IMENIT, $gender);
216
217
                // make one array with cases and delete 'o/об' prepositional from all parts except the last one
218 20
                foreach ([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ] as $case) {
219 20
                    $result[$case] = $cardinal_part.' '.$ordinal_part[$case];
220
                }
221
            } else {
222
                $result = $ordinal_part;
223
            }
224
225 20
            return $result;
226
        }
227
    }
228
229
    /**
230
     * @param $number
231
     * @param $case
232
     * @param string $gender
233
     * @return mixed
234
     * @throws \Exception
235
     */
236 17
    public static function getCase($number, $case, $gender = self::MALE)
237
    {
238 17
        $case = self::canonizeCase($case);
239 17
        $forms = self::getCases($number, $gender);
240 17
        return $forms[$case];
241
    }
242
}
243