Completed
Push — master ( 87b10e...1961d1 )
by Andre
16s queued 12s
created

src/Mapper/LastnameMapper.php (1 issue)

Upgrade to new PHP Analysis Engine

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
This method seems to be duplicated in 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...
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