These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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\Salutation; |
||
11 | use TheIconic\NameParser\Part\Suffix; |
||
12 | |||
13 | class LastnameMapper extends AbstractMapper |
||
14 | { |
||
15 | protected $prefixes = []; |
||
16 | |||
17 | protected $matchSinglePart = false; |
||
18 | |||
19 | public function __construct(array $prefixes, bool $matchSinglePart = false) |
||
20 | { |
||
21 | $this->prefixes = $prefixes; |
||
22 | $this->matchSinglePart = $matchSinglePart; |
||
23 | } |
||
24 | |||
25 | /** |
||
26 | * map lastnames in the parts array |
||
27 | * |
||
28 | * @param array $parts the name parts |
||
29 | * @return array the mapped parts |
||
30 | */ |
||
31 | public function map(array $parts): array |
||
32 | { |
||
33 | if (!$this->matchSinglePart && count($parts) < 2) { |
||
34 | return $parts; |
||
35 | } |
||
36 | |||
37 | return $this->mapParts($parts); |
||
38 | } |
||
39 | |||
40 | /** |
||
41 | * we map the parts in reverse order because it makes more |
||
42 | * sense to parse for the lastname starting from the end |
||
43 | * |
||
44 | * @param array $parts |
||
45 | * @return array |
||
46 | */ |
||
47 | protected function mapParts(array $parts): array |
||
48 | { |
||
49 | $k = $this->skipIgnoredParts($parts) + 1; |
||
50 | $remapIgnored = true; |
||
51 | |||
52 | while (--$k >= 0) { |
||
53 | $part = $parts[$k]; |
||
54 | |||
55 | if ($part instanceof AbstractPart) { |
||
56 | break; |
||
57 | } |
||
58 | |||
59 | if ($this->isFollowedByLastnamePart($parts, $k)) { |
||
60 | if ($this->isApplicablePrefix($parts, $k)) { |
||
61 | $parts[$k] = new LastnamePrefix($part, $this->prefixes[$this->getKey($part)]); |
||
62 | continue; |
||
63 | } |
||
64 | |||
65 | if ($this->shouldStopMapping($parts, $k)) { |
||
66 | break; |
||
67 | } |
||
68 | } |
||
69 | |||
70 | $parts[$k] = new Lastname($part); |
||
71 | $remapIgnored = false; |
||
72 | } |
||
73 | |||
74 | if ($remapIgnored) { |
||
75 | $parts = $this->remapIgnored($parts); |
||
76 | } |
||
77 | |||
78 | return $parts; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * skip through the parts we want to ignore and return the start index |
||
83 | * |
||
84 | * @param array $parts |
||
85 | * @return int |
||
86 | */ |
||
87 | protected function skipIgnoredParts(array $parts): int |
||
88 | { |
||
89 | $k = count($parts); |
||
90 | |||
91 | while (--$k >= 0) { |
||
92 | if (!$this->isIgnoredPart($parts[$k])) { |
||
93 | break; |
||
94 | } |
||
95 | } |
||
96 | |||
97 | return $k; |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * indicates if we should stop mapping at the give index $k |
||
102 | * |
||
103 | * the assumption is that lastname parts have already been found |
||
104 | * but we want to see if we should add more parts |
||
105 | * |
||
106 | * @param array $parts |
||
107 | * @param int $k |
||
108 | * @return bool |
||
109 | */ |
||
110 | protected function shouldStopMapping(array $parts, int $k): bool |
||
111 | { |
||
112 | if ($k < 1) { |
||
113 | return true; |
||
114 | } |
||
115 | |||
116 | if ($parts[$k + 1] instanceof LastnamePrefix) { |
||
117 | return true; |
||
118 | } |
||
119 | |||
120 | return strlen($parts[$k + 1]->getValue()) >= 3; |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * indicates if the given part should be ignored (skipped) during mapping |
||
125 | * |
||
126 | * @param $part |
||
127 | * @return bool |
||
128 | */ |
||
129 | protected function isIgnoredPart($part) { |
||
130 | return $part instanceof Suffix || $part instanceof Nickname || $part instanceof Salutation; |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * remap ignored parts as lastname |
||
135 | * |
||
136 | * if the mapping did not derive any lastname this is called to transform |
||
137 | * any previously ignored parts into lastname parts |
||
138 | * the parts array is still reversed at this point |
||
139 | * |
||
140 | * @param array $parts |
||
141 | * @return array |
||
142 | */ |
||
143 | protected function remapIgnored(array $parts): array |
||
144 | { |
||
145 | $k = count($parts); |
||
146 | |||
147 | while (--$k >= 0) { |
||
148 | $part = $parts[$k]; |
||
149 | |||
150 | if (!$this->isIgnoredPart($part)) { |
||
151 | break; |
||
152 | } |
||
153 | |||
154 | $parts[$k] = new Lastname($part); |
||
155 | } |
||
156 | |||
157 | return $parts; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * @param array $parts |
||
162 | * @param int $index |
||
163 | * @return bool |
||
164 | */ |
||
165 | protected function isFollowedByLastnamePart(array $parts, int $index): bool |
||
166 | { |
||
167 | $next = $this->skipNicknameParts($parts, $index + 1); |
||
168 | |||
169 | return (isset($parts[$next]) && $parts[$next] instanceof Lastname); |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Assuming that the part at the given index is matched as a prefix, |
||
174 | * determines if the prefix should be applied to the lastname. |
||
175 | * |
||
176 | * We only apply it to the lastname if we already have at least one |
||
177 | * lastname part and there are other parts left in |
||
178 | * the name (this effectively prioritises firstname over prefix matching). |
||
179 | * |
||
180 | * This expects the parts array and index to be in the original order. |
||
181 | * |
||
182 | * @param array $parts |
||
183 | * @param int $index |
||
184 | * @return bool |
||
185 | */ |
||
186 | protected function isApplicablePrefix(array $parts, int $index): bool |
||
187 | { |
||
188 | if (!$this->isPrefix($parts[$index])) { |
||
189 | return false; |
||
190 | } |
||
191 | |||
192 | return $this->hasUnmappedPartsBefore($parts, $index); |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * check if the given word is a lastname prefix |
||
197 | * |
||
198 | * @param string $word the word to check |
||
199 | * @return bool |
||
200 | */ |
||
201 | protected function isPrefix($word): bool |
||
202 | { |
||
203 | return (array_key_exists($this->getKey($word), $this->prefixes)); |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * find the next non-nickname index in parts |
||
208 | * |
||
209 | * @param $parts |
||
210 | * @param $startIndex |
||
211 | * @return int|void |
||
212 | */ |
||
213 | View Code Duplication | protected function skipNicknameParts($parts, $startIndex) |
|
0 ignored issues
–
show
|
|||
214 | { |
||
215 | $total = count($parts); |
||
216 | |||
217 | for ($i = $startIndex; $i < $total; $i++) { |
||
218 | if (!($parts[$i] instanceof Nickname)) { |
||
219 | return $i; |
||
220 | } |
||
221 | } |
||
222 | |||
223 | return $total - 1; |
||
224 | } |
||
225 | } |
||
226 |
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.