Ruleset::inflectFirstName()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 14
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 4
1
<?php
2
namespace Staticall\Petrovich\Petrovich;
3
4
use Staticall\Petrovich\Petrovich\Ruleset\Validator;
5
6
class Ruleset
7
{
8
    const ROOT_KEY_FIRSTNAME  = 'firstname';
9
    const ROOT_KEY_LASTNAME   = 'lastname';
10
    const ROOT_KEY_MIDDLENAME = 'middlename';
11
12
    const SECOND_KEY_EXCEPTIONS = 'exceptions';
13
    const SECOND_KEY_SUFFIXES   = 'suffixes';
14
15
    const VALUE_KEY_GENDER = 'gender';
16
    const VALUE_KEY_MODS   = 'mods';
17
    const VALUE_KEY_TEST   = 'test';
18
    const VALUE_KEY_TAGS   = 'tags';
19
20
    const GENDER_ANDROGYNOUS = 'androgynous';
21
    const GENDER_MALE        = 'male';
22
    const GENDER_FEMALE      = 'female';
23
24
    const MOD_INITIAL = '.';
25
26
    const CASE_NOMENATIVE    = -1; //именительный
27
    const CASE_GENITIVE      = 0; //родительный
28
    const CASE_DATIVE        = 1; //дательный
29
    const CASE_ACCUSATIVE    = 2; //винительный
30
    const CASE_INSTRUMENTAL  = 3; //творительный
31
    const CASE_PREPOSITIONAL = 4; //предложный
32
    const DEFAULT_CASE       = self::CASE_NOMENATIVE;
33
34
    const DEFAULT_DELIMITER = '-';
35
36
    /**
37
     * @var array List of parsed Petrovich rules
38
     */
39
    private $rules = [];
40
41
    /**
42
     * @param array $rules
43
     * @param bool  $shouldValidate
44
     *
45
     * @throws ValidationException
46
     */
47
    public function __construct(array $rules = [], bool $shouldValidate = false)
48
    {
49
        if (!empty($rules)) {
50
            $this->setRules($rules, $shouldValidate);
51
        }
52
    }
53
54
    /**
55
     * @param array $rules
56
     * @param bool  $shouldValidate
57
     *
58
     * @return Ruleset
59
     *
60
     * @throws ValidationException
61
     */
62
    public function setRules(array $rules, bool $shouldValidate = false) : Ruleset
63
    {
64
        if ($shouldValidate && $this->validate($rules) === false) {
65
            throw new ValidationException('Input didn\'t pass validation');
66
        }
67
68
        $this->rules = $rules;
69
70
        return $this;
71
    }
72
73
    /**
74
     * @return array
75
     */
76
    public function getRules() : array
77
    {
78
        return $this->rules;
79
    }
80
81
    /**
82
     * Performs basic validation over provided rules
83
     * Useful for testing and/or any format changes
84
     *
85
     * @param array $rules
86
     *
87
     * @return bool
88
     */
89
    public function validate(array $rules) : bool
90
    {
91
        $validator = new Validator();
92
93
        return $validator->validate($rules);
94
    }
95
96
    /**
97
     * Returns all availabe root keys
98
     *
99
     * @return array
100
     */
101
    public static function getAvailableRootKeys() : array
102
    {
103
        return [
104
            static::ROOT_KEY_FIRSTNAME,
105
            static::ROOT_KEY_LASTNAME,
106
            static::ROOT_KEY_MIDDLENAME,
107
        ];
108
    }
109
110
    /**
111
     * Returns all availabe second keys
112
     *
113
     * @return array
114
     */
115
    public static function getAvailableSecondKeys() : array
116
    {
117
        return [
118
            static::SECOND_KEY_EXCEPTIONS,
119
            static::SECOND_KEY_SUFFIXES,
120
        ];
121
    }
122
123
    /**
124
     * Returns all availabe value keys
125
     *
126
     * @return array
127
     */
128
    public static function getAvailableValueKeys() : array
129
    {
130
        return [
131
            static::VALUE_KEY_GENDER,
132
            static::VALUE_KEY_MODS,
133
            static::VALUE_KEY_TEST,
134
            static::VALUE_KEY_TAGS,
135
        ];
136
    }
137
138
    /**
139
     * Returns all available genders
140
     *
141
     * @return array
142
     */
143
    public static function getAvailableGenders() : array
144
    {
145
        return [
146
            static::GENDER_ANDROGYNOUS,
147
            static::GENDER_MALE,
148
            static::GENDER_FEMALE,
149
        ];
150
    }
151
152
    /**
153
     * Returns all available cases
154
     *
155
     * @return array
156
     */
157
    public static function getAvailableCases() : array
158
    {
159
        return [
160
            static::CASE_NOMENATIVE,
161
            static::CASE_GENITIVE,
162
            static::CASE_DATIVE,
163
            static::CASE_ACCUSATIVE,
164
            static::CASE_INSTRUMENTAL,
165
            static::CASE_PREPOSITIONAL,
166
        ];
167
    }
168
169
    /**
170
     * @param string $lastName
171
     * @param int    $case
172
     * @param string $gender
173
     * @param string $delimiter
174
     *
175
     * @return string
176
     *
177
     * @throws RuntimeException
178
     */
179
    public function inflectLastName(
180
        string $lastName,
181
        int $case,
182
        string $gender,
183
        string $delimiter = self::DEFAULT_DELIMITER
184
    ) : string
185
    {
186
        $rules = $this->getRules();
187
188
        if (empty($rules[static::ROOT_KEY_LASTNAME])) {
189
            throw new RuntimeException('Missing key "' . static::ROOT_KEY_LASTNAME . '" for inflection');
190
        }
191
192
        return $this->inflect($lastName, $case, $gender, $rules[static::ROOT_KEY_LASTNAME], $delimiter);
193
    }
194
195
    /**
196
     * @param string $firstName
197
     * @param int    $case
198
     * @param string $gender
199
     * @param string $delimiter
200
     *
201
     * @return string
202
     *
203
     * @throws RuntimeException
204
     */
205
    public function inflectFirstName(
206
        string $firstName,
207
        int $case,
208
        string $gender,
209
        string $delimiter = self::DEFAULT_DELIMITER
210
    ) : string
211
    {
212
        $rules = $this->getRules();
213
214
        if (empty($rules[static::ROOT_KEY_FIRSTNAME])) {
215
            throw new RuntimeException('Missing key "' . static::ROOT_KEY_FIRSTNAME . '" for inflection');
216
        }
217
218
        return $this->inflect($firstName, $case, $gender, $rules[static::ROOT_KEY_FIRSTNAME], $delimiter);
219
    }
220
221
    /**
222
     * @param string $middleName
223
     * @param int    $case
224
     * @param string $gender
225
     * @param string $delimiter
226
     *
227
     * @return string
228
     *
229
     * @throws RuntimeException
230
     */
231
    public function inflectMiddleName(
232
        string $middleName,
233
        int $case,
234
        string $gender,
235
        string $delimiter = self::DEFAULT_DELIMITER
236
    ) : string
237
    {
238
        $rules = $this->getRules();
239
240
        if (empty($rules[static::ROOT_KEY_MIDDLENAME])) {
241
            throw new RuntimeException('Missing key "' . static::ROOT_KEY_MIDDLENAME . '" for inflection');
242
        }
243
244
        return $this->inflect($middleName, $case, $gender, $rules[static::ROOT_KEY_MIDDLENAME], $delimiter);
245
    }
246
247
    /**
248
     * @param string $input
249
     * @param int    $case
250
     * @param string $gender
251
     * @param array  $rule
252
     * @param string $delimiter
253
     *
254
     * @return string
255
     */
256
    public function inflect(
257
        string $input,
258
        int $case,
259
        string $gender,
260
        array $rule,
261
        string $delimiter = self::DEFAULT_DELIMITER
262
    ) : string
263
    {
264
        if ($case === static::CASE_NOMENATIVE) {
265
            // Because Petrovich does not provide a case for nomenative case, because it's useless
266
            return $input;
267
        }
268
269
        $inputParts = \explode($delimiter, $input);
270
        $result     = [];
271
272
        foreach ($inputParts as $inputPart) {
273
            if ($this->isInExceptions($inputPart, $case, $gender, $rule) === true) {
274
                $result[] = $this->getException($inputPart, $case, $gender, $rule);
275
276
                continue;
277
            }
278
279
            $result[] = $this->findMatchingRule($inputPart, $case, $gender, $rule);
280
        }
281
282
        return \implode('-', $result);
283
    }
284
285
    /**
286
     * @param string $input
287
     * @param int    $case
288
     * @param string $gender
289
     * @param array  $rule
290
     *
291
     * @return string
292
     */
293
    protected function findMatchingRule(
294
        string $input,
295
        int $case,
296
        string $gender,
297
        array $rule
298
    ) : string
299
    {
300
        if (empty($rule[static::SECOND_KEY_SUFFIXES])) {
301
            return $input;
302
        }
303
304
        $inputLowercase = \mb_strtolower($input);
305
306
        foreach ($rule[static::SECOND_KEY_SUFFIXES] as $toTest) {
307
            if ($toTest[static::VALUE_KEY_GENDER] !== $gender) {
308
                continue;
309
            }
310
311
            foreach ($toTest[static::VALUE_KEY_TEST] as $ending) {
312
                $inputEnding = \mb_substr(
313
                    $inputLowercase,
314
                    \mb_strlen($input) - \mb_strlen($ending),
315
                    \mb_strlen($ending)
316
                );
317
318
                if ($ending === $inputEnding) {
319
                    if ($toTest[static::VALUE_KEY_MODS][$case] === static::MOD_INITIAL) {
320
                        return $input;
321
                    }
322
323
                    return $this->applyRule($toTest[static::VALUE_KEY_MODS][$case], $input);
324
                }
325
            }
326
        }
327
328
        return $input;
329
    }
330
331
    /**
332
     * Checks if current rule is in exceptions
333
     *
334
     * @param string $input
335
     * @param int    $case
336
     * @param string $gender
337
     * @param array  $rule
338
     *
339
     * @return bool
340
     */
341
    protected function isInExceptions(string $input, int $case, string $gender, array $rule) : bool
342
    {
343
        return $this->getException($input, $case, $gender, $rule) !== null;
344
    }
345
346
    /**
347
     * @param string $input
348
     * @param int    $case
349
     * @param string $gender
350
     * @param array  $rule
351
     *
352
     * @return string|null
353
     */
354
    protected function getException(string $input, int $case, string $gender, array $rule) : ?string
355
    {
356
        if (empty($rule[static::SECOND_KEY_EXCEPTIONS])) {
357
            return null;
358
        }
359
360
        $inputLowercase = \mb_strtolower($input);
361
362
        foreach ($rule[static::SECOND_KEY_EXCEPTIONS] as $exception) {
363
            if ($exception[static::VALUE_KEY_GENDER] !== $gender) {
364
                continue;
365
            }
366
367
            if (\in_array($inputLowercase, $exception[static::VALUE_KEY_TEST], true) === false) {
368
                continue;
369
            }
370
371
            if ($exception[static::VALUE_KEY_MODS][$case] === static::MOD_INITIAL) {
372
                return $input;
373
            }
374
375
            return $this->applyRule($exception[static::VALUE_KEY_MODS][$case], $input);
376
        }
377
378
        return null;
379
    }
380
381
    /**
382
     * @param string $mod
383
     * @param string $input
384
     *
385
     * @return string
386
     */
387
    protected function applyRule(string $mod, string $input) : string
388
    {
389
        $result  = \mb_substr($input, 0, \mb_strlen($input) - \mb_substr_count($mod, '-'));
390
        $result .= \str_replace('-', '', $mod);
391
392
        return $result;
393
    }
394
}
395