Completed
Push — 2.1.x ( 7b2a9a...8f7c87 )
by f
01:29
created

GeneralDeclension   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 246
Duplicated Lines 4.47 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 11
loc 246
rs 8.5454
c 0
b 0
f 0
wmc 49
lcom 1
cbo 2

12 Methods

Rating   Name   Duplication   Size   Complexity  
A hasForms() 0 6 2
B getDeclension() 0 11 8
B getForms() 0 24 5
C declinateFirstDeclension() 0 43 8
B declinateSecondDeclension() 0 34 2
A declinateThirdDeclension() 0 12 1
C pluralizeAllDeclensions() 11 45 11
A getForm() 0 4 1
A getPrefixOfFirstDeclension() 0 7 2
A getVinitCaseByAnimateness() 0 6 2
A getPredCaseOf12Declensions() 0 10 3
A chooseVowelAfterConsonant() 0 7 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like GeneralDeclension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GeneralDeclension, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace morphos\Russian;
3
4
/**
5
 * Rules are from http://morpher.ru/Russian/Noun.aspx
6
 */
7
class GeneralDeclension extends \morphos\GeneralDeclension implements Cases {
8
	use RussianLanguage;
9
10
	const FIRST_DECLENSION = 1;
11
	const SECOND_DECLENSION = 2;
12
	const THIRD_DECLENSION = 3;
13
14
	const FIRST_SCHOOL_DECLENSION = 2;
15
	const SECOND_SCHOOL_DECLENSION = 1;
16
	const THIRD_SCHOOL_DECLENSION = 3;
17
18
	protected $exceptions = array(
19
		'бремя',
20
		'вымя',
21
		'темя',
22
		'пламя',
23
		'стремя',
24
		'пламя',
25
		'время',
26
		'знамя',
27
		'имя',
28
		'племя',
29
		'семя',
30
	);
31
32
	public function hasForms($word, $animate = false) {
33
		$word = lower($word);
34
		if (in_array(slice($word, -1), array('у', 'и', 'е', 'о', 'ю')))
35
			return false;
36
		return true;
37
	}
38
39
	public function getDeclension($word) {
40
		$word = lower($word);
41
		$last = slice($word, -1);
42
		if ($this->isConsonant($last) || in_array($last, ['о', 'е', 'ё']) || ($last == 'ь' && $this->isConsonant(slice($word, -2, -1)) && !$this->isHissingConsonant(slice($word, -2, -1)))) {
43
			return  1;
44
		} else if (in_array($last, ['а', 'я']) && slice($word, -2) != 'мя') {
45
			return 2;
46
		} else {
47
			return 3;
48
		}
49
	}
50
51
	public function getForms($word, $animate = false) {
52
		$word = lower($word);
53
54
		if (isset($this->exceptions[$word])) {
55
			$prefix = slice($word, -1);
56
			return array(
57
				self::IMENIT => $word,
58
				self::RODIT => $prefix.'и',
59
				self::DAT => $prefix.'и',
60
				self::VINIT => $word,
61
				self::TVORIT => $prefix,
62
				self::PREDLOJ => $this->choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'и',
63
			);
64
		}
65
66
		switch ($this->getDeclension($word)) {
67
			case self::FIRST_DECLENSION:
68
				return $this->declinateFirstDeclension($word, $animate);
69
			case self::SECOND_DECLENSION:
70
				return $this->declinateSecondDeclension($word);
71
			case self::THIRD_DECLENSION:
72
				return $this->declinateThirdDeclension($word);
73
		}
74
	}
75
76
	public function declinateFirstDeclension($word, $animate = false) {
77
		$word = lower($word);
78
		$last = slice($word, -1);
79
		$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && $this->isConsonant(slice($word, -2, -1)));
80
		$prefix = $this->getPrefixOfFirstDeclension($word, $last);
81
		$forms =  array(
82
			Cases::IMENIT_1 => $word,
83
		);
84
85
		// RODIT_2
86
		$forms[Cases::RODIT_2] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
87
88
		// DAT_3
89
		$forms[Cases::DAT_3] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у');
90
91
		// VINIT_4
92
		if (in_array($last, ['о', 'е', 'ё']))
93
			$forms[Cases::VINIT_4] = $word;
94
		else {
95
			$forms[Cases::VINIT_4] = $this->getVinitCaseByAnimateness($forms, $animate);
96
		}
97
98
		// TVORIT_5
99
		// if ($last == 'ь')
100
		// 	$forms[Cases::TVORIT_5] = $prefix.'ом';
101
		// else if ($last == 'й' || ($this->isConsonant($last) && !$this->isHissingConsonant($last)))
102
		// 	$forms[Cases::TVORIT_5] = $prefix.'ем';
103
		// else
104
		// 	$forms[Cases::TVORIT_5] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
105
		if ($this->isHissingConsonant($last) || $last == 'ц') {
106
			$forms[Cases::TVORIT_5] = $prefix.'ем';
107
		} else if (in_array($last, ['й'/*, 'ч', 'щ'*/]) || $soft_last) {
108
			$forms[Cases::TVORIT_5] = $prefix.'ем';
109
		} else {
110
			$forms[Cases::TVORIT_5] = $prefix.'ом';
111
		}
112
113
		// PREDLOJ_6
114
		$forms[Cases::PREDLOJ_6] = $this->getPredCaseOf12Declensions($word, $last, $prefix);
115
		$forms[Cases::PREDLOJ_6] = $this->choosePrepositionByFirstLetter($forms[Cases::PREDLOJ_6], 'об', 'о').' '.$forms[Cases::PREDLOJ_6];
116
117
		return $forms;
118
	}
119
120
	public function declinateSecondDeclension($word) {
121
		$word = lower($word);
122
		$prefix = slice($word, 0, -1);
123
		$last = slice($word, -1);
124
		$soft_last = $this->checkLastConsonantSoftness($word);
125
		$forms =  array(
126
			Cases::IMENIT_1 => $word,
127
		);
128
129
		// RODIT_2
130
		$forms[Cases::RODIT_2] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'и', $prefix.'ы');
131
132
		// DAT_3
133
		$forms[Cases::DAT_3] = $this->getPredCaseOf12Declensions($word, $last, $prefix);
134
135
		// VINIT_4
136
		$forms[Cases::VINIT_4] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у');
137
138
		// TVORIT_5
139
		if ($last == 'ь')
140
			$forms[Cases::TVORIT_5] = $prefix.'ой';
141
		else {
142
			$forms[Cases::TVORIT_5] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой');
143
		}
144
145
		// 	if ($last == 'й' || ($this->isConsonant($last) && !$this->isHissingConsonant($last)) || $this->checkLastConsonantSoftness($word))
146
		// 	$forms[Cases::TVORIT_5] = $prefix.'ей';
147
		// else
148
		// 	$forms[Cases::TVORIT_5] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
149
150
		// PREDLOJ_6 the same as DAT_3
151
		$forms[Cases::PREDLOJ_6] = $this->choosePrepositionByFirstLetter($forms[Cases::DAT_3], 'об', 'о').' '.$forms[Cases::DAT_3];
152
		return $forms;
153
	}
154
155
	public function declinateThirdDeclension($word) {
156
		$word = lower($word);
157
		$prefix = slice($word, 0, -1);
158
		return array(
159
			Cases::IMENIT_1 => $word,
160
			Cases::RODIT_2 => $prefix.'и',
161
			Cases::DAT_3 => $prefix.'и',
162
			Cases::VINIT_4 => $word,
163
			Cases::TVORIT_5 => $prefix.'ью',
164
			Cases::PREDLOJ_6 => $this->choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'и',
165
		);
166
	}
167
168
	public function pluralizeAllDeclensions($word, $animate = false) {
169
		$word = lower($word);
170
		$prefix = slice($word, 0, -1);
171
		$last = slice($word, -1);
172
173
		if (($declension = $this->getDeclension($word)) == self::FIRST_DECLENSION) {
174
			$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && $this->isConsonant(slice($word, -2, -1)));
175
			$prefix = $this->getPrefixOfFirstDeclension($word, $last);
176
		} else if ($declension == self::SECOND_DECLENSION) {
177
			 $soft_last = $this->checkLastConsonantSoftness($word);
178
		} else {
179
			$soft_last = false;
180
		}
181
182
		$forms =  array(
183
			Cases::IMENIT_1 => $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'),
184
		);
185
186
		// RODIT_2
187 View Code Duplication
		if ($this->isHissingConsonant($last) || ($soft_last && $last != 'й'))
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...
188
			$forms[Cases::RODIT_2] = $prefix.'ей';
189
		else if ($last == 'й')
190
			$forms[Cases::RODIT_2] = $prefix.'ев';
191
		else // ($this->isConsonant($last) && !$this->isHissingConsonant($last))
192
			$forms[Cases::RODIT_2] = $prefix.'ов';
193
194
		// DAT_3
195
		$forms[Cases::DAT_3] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ям', $prefix.'ам');
196
197
		// VINIT_4
198
		$forms[Cases::VINIT_4] = $this->getVinitCaseByAnimateness($forms, $animate);
199
200
		// TVORIT_5
201
		// my personal rule
202 View Code Duplication
		if ($last == 'ь' && $declension == self::THIRD_DECLENSION) {
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...
203
			$forms[Cases::TVORIT_5] = $prefix.'ми';
204
		} else {
205
			$forms[Cases::TVORIT_5] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ями', $prefix.'ами');
206
		}
207
208
		// PREDLOJ_6
209
		$forms[Cases::PREDLOJ_6] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ях', $prefix.'ах');
210
		$forms[Cases::PREDLOJ_6] = $this->choosePrepositionByFirstLetter($forms[Cases::PREDLOJ_6], 'об', 'о').' '.$forms[Cases::PREDLOJ_6];
211
		return $forms;
212
	}
213
214
	public function getForm($word, $animate = false, $form) {
215
		$forms = $this->getForms($word, $animate);
216
		return $forms[$form];
217
	}
218
219
	protected function getPrefixOfFirstDeclension($word, $last) {
220
		if (in_array($last, ['о', 'е', 'ё', 'ь', 'й']))
221
			$prefix = slice($word, 0, -1);
222
		else
223
			$prefix = $word;
224
		return $prefix;
225
	}
226
227
	protected function getVinitCaseByAnimateness(array $forms, $animate) {
228
		if ($animate)
229
			return $forms[Cases::RODIT_2];
230
		else
231
			return $forms[Cases::IMENIT_1];
232
	}
233
234
	protected function getPredCaseOf12Declensions($word, $last, $prefix) {
235
		if (slice($word, -2) == 'ий') {
236
			if ($last == 'ё')
237
				return $prefix.'е';
238
			else
239
				return $prefix.'и';
240
		} else {
241
			return $prefix.'е';
242
		}
243
	}
244
245
	protected function chooseVowelAfterConsonant($last, $soft_last, $after_soft, $after_hard) {
246
		if ($this->isHissingConsonant($last) || $this->isVelarConsonant($last) || $soft_last) {
247
			return $after_soft;
248
		} else {
249
			return $after_hard;
250
		}
251
	}
252
}
253