1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace TheIconic\NameParser\Mapper; |
4
|
|
|
|
5
|
|
|
use TheIconic\NameParser\LanguageInterface; |
6
|
|
|
use TheIconic\NameParser\Part\AbstractPart; |
7
|
|
|
use TheIconic\NameParser\Part\Lastname; |
8
|
|
|
use TheIconic\NameParser\Part\LastnamePrefix; |
9
|
|
|
use TheIconic\NameParser\Part\Nickname; |
10
|
|
|
use TheIconic\NameParser\Part\Suffix; |
11
|
|
|
|
12
|
|
|
class LastnameMapper extends AbstractMapper |
13
|
|
|
{ |
14
|
|
|
protected $prefixes = []; |
15
|
|
|
|
16
|
|
|
protected $matchSinglePart = false; |
17
|
|
|
|
18
|
|
|
public function __construct(array $prefixes, bool $matchSinglePart = false) |
19
|
|
|
{ |
20
|
|
|
$this->prefixes = $prefixes; |
21
|
|
|
$this->matchSinglePart = $matchSinglePart; |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* map lastnames in the parts array |
26
|
|
|
* |
27
|
|
|
* @param array $parts the name parts |
28
|
|
|
* @return array the mapped parts |
29
|
|
|
*/ |
30
|
|
|
public function map(array $parts): array |
31
|
|
|
{ |
32
|
|
|
if (!$this->matchSinglePart && count($parts) < 2) { |
33
|
|
|
return $parts; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
$parts = array_reverse($parts); |
37
|
|
|
|
38
|
|
|
$parts = $this->mapReversedParts($parts); |
39
|
|
|
|
40
|
|
|
return array_reverse($parts); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @param array $parts |
45
|
|
|
* @return array |
46
|
|
|
*/ |
47
|
|
|
protected function mapReversedParts(array $parts): array |
48
|
|
|
{ |
49
|
|
|
$length = count($parts); |
50
|
|
|
|
51
|
|
|
foreach ($parts as $k => $part) { |
52
|
|
|
if ($part instanceof Suffix) { |
53
|
|
|
continue; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
if ($part instanceof Nickname) { |
57
|
|
|
continue; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
if ($part instanceof AbstractPart) { |
61
|
|
|
break; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$originalIndex = $length - $k - 1; |
65
|
|
|
$originalParts = array_reverse($parts); |
66
|
|
|
|
67
|
|
|
if ($this->isFollowedByLastnamePart($originalParts, $originalIndex)) { |
68
|
|
|
if ($this->isApplicablePrefix($originalParts, $originalIndex)) { |
69
|
|
|
$parts[$k] = new LastnamePrefix($part, $this->prefixes[$this->getKey($part)]); |
70
|
|
|
continue; |
71
|
|
|
} |
72
|
|
|
break; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
$parts[$k] = new Lastname($part); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
return $parts; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @param array $parts |
83
|
|
|
* @param int $index |
84
|
|
|
* @return bool |
85
|
|
|
*/ |
86
|
|
|
protected function isFollowedByLastnamePart(array $parts, int $index): bool |
87
|
|
|
{ |
88
|
|
|
$next = $this->skipNicknameParts($parts, $index + 1); |
89
|
|
|
|
90
|
|
|
return (isset($parts[$next]) && $parts[$next] instanceof Lastname); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Assuming that the part at the given index is matched as a prefix, |
95
|
|
|
* determines if the prefix should be applied to the lastname. |
96
|
|
|
* |
97
|
|
|
* We only apply it to the lastname if we already have at least one |
98
|
|
|
* lastname part and there are other parts left in |
99
|
|
|
* the name (this effectively prioritises firstname over prefix matching). |
100
|
|
|
* |
101
|
|
|
* This expects the parts array and index to be in the original order. |
102
|
|
|
* |
103
|
|
|
* @param array $parts |
104
|
|
|
* @param int $index |
105
|
|
|
* @return bool |
106
|
|
|
*/ |
107
|
|
|
protected function isApplicablePrefix(array $parts, int $index): bool |
108
|
|
|
{ |
109
|
|
|
if (!$this->isPrefix($parts[$index])) { |
110
|
|
|
return false; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
return $this->hasUnmappedPartsBefore($parts, $index); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* check if the given word is a lastname prefix |
118
|
|
|
* |
119
|
|
|
* @param string $word the word to check |
120
|
|
|
* @return bool |
121
|
|
|
*/ |
122
|
|
|
protected function isPrefix($word): bool |
123
|
|
|
{ |
124
|
|
|
return (array_key_exists($this->getKey($word), $this->prefixes)); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* find the next non-nickname index in parts |
129
|
|
|
* |
130
|
|
|
* @param $parts |
131
|
|
|
* @param $startIndex |
132
|
|
|
* @return int|void |
133
|
|
|
*/ |
134
|
|
View Code Duplication |
protected function skipNicknameParts($parts, $startIndex) |
|
|
|
|
135
|
|
|
{ |
136
|
|
|
$total = count($parts); |
137
|
|
|
|
138
|
|
|
for ($i = $startIndex; $i < $total; $i++) { |
139
|
|
|
if (!($parts[$i] instanceof Nickname)) { |
140
|
|
|
return $i; |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
return $total - 1; |
145
|
|
|
} |
146
|
|
|
} |
147
|
|
|
|
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.