Completed
Pull Request — master (#16)
by Andre
01:18
created

LastnameMapper::mapParts()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 8.0675
c 0
b 0
f 0
cc 8
nc 10
nop 1
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 = count($parts);
50
        $remapIgnored = true;
51
52
        while (--$k >= 0) {
53
            $part = $parts[$k];
54
55
            if ($this->isIgnoredPart($part)) {
56
                continue;
57
            }
58
59
            if ($part instanceof AbstractPart) {
60
                break;
61
            }
62
63
            if (!$this->isFollowedByLastnamePart($parts, $k)) {
64
                $parts[$k] = new Lastname($part);
65
                $remapIgnored = false;
66
                continue;
67
            }
68
69
            if ($this->isApplicablePrefix($parts, $k)) {
70
                $parts[$k] = new LastnamePrefix($part, $this->prefixes[$this->getKey($part)]);
71
                continue;
72
            }
73
74
            if ($this->shouldStopMapping($parts, $k)) {
75
                break;
76
            }
77
        }
78
79
        if ($remapIgnored) {
80
            $parts = $this->remapIgnored($parts);
81
        }
82
83
        return $parts;
84
    }
85
86
    /**
87
     * indicates if we should stop mapping at the give index $k
88
     *
89
     * the assumption is that lastname parts have already been found
90
     * but we want to see if we should add more parts
91
     *
92
     * @param array $parts
93
     * @param int $k
94
     * @return bool
95
     */
96
    protected function shouldStopMapping(array $parts, int $k): bool
97
    {
98
        if ($k === 0) {
99
            return true;
100
        }
101
102
        if ($parts[$k + 1] instanceof LastnamePrefix) {
103
            return true;
104
        }
105
106
        return strlen($parts[$k + 1]->getValue()) >= 3;
107
    }
108
109
    /**
110
     * indicates if the given part should be ignored (skipped) during mapping
111
     *
112
     * @param $part
113
     * @return bool
114
     */
115
    protected function isIgnoredPart($part) {
116
        return $part instanceof Suffix || $part instanceof Nickname || $part instanceof Salutation;
117
    }
118
119
    /**
120
     * remap ignored parts as lastname
121
     *
122
     * if the mapping did not derive any lastname this is called to transform
123
     * any previously ignored parts into lastname parts
124
     * the parts array is still reversed at this point
125
     *
126
     * @param array $parts
127
     * @return array
128
     */
129
    protected function remapIgnored(array $parts): array
130
    {
131
        $k = count($parts);
132
133
        while (--$k >= 0) {
134
            $part = $parts[$k];
135
136
            if (!$this->isIgnoredPart($part)) {
137
                break;
138
            }
139
140
            $parts[$k] = new Lastname($part);
141
        }
142
143
        return $parts;
144
    }
145
146
    /**
147
     * @param array $parts
148
     * @param int $index
149
     * @return bool
150
     */
151
    protected function isFollowedByLastnamePart(array $parts, int $index): bool
152
    {
153
        $next = $this->skipNicknameParts($parts, $index + 1);
154
155
        return (isset($parts[$next]) && $parts[$next] instanceof Lastname);
156
    }
157
158
    /**
159
     * Assuming that the part at the given index is matched as a prefix,
160
     * determines if the prefix should be applied to the lastname.
161
     *
162
     * We only apply it to the lastname if we already have at least one
163
     * lastname part and there are other parts left in
164
     * the name (this effectively prioritises firstname over prefix matching).
165
     *
166
     * This expects the parts array and index to be in the original order.
167
     *
168
     * @param array $parts
169
     * @param int $index
170
     * @return bool
171
     */
172
    protected function isApplicablePrefix(array $parts, int $index): bool
173
    {
174
        if (!$this->isPrefix($parts[$index])) {
175
            return false;
176
        }
177
178
        return $this->hasUnmappedPartsBefore($parts, $index);
179
    }
180
181
    /**
182
     * check if the given word is a lastname prefix
183
     *
184
     * @param string $word the word to check
185
     * @return bool
186
     */
187
    protected function isPrefix($word): bool
188
    {
189
        return (array_key_exists($this->getKey($word), $this->prefixes));
190
    }
191
192
    /**
193
     * find the next non-nickname index in parts
194
     *
195
     * @param $parts
196
     * @param $startIndex
197
     * @return int|void
198
     */
199 View Code Duplication
    protected function skipNicknameParts($parts, $startIndex)
0 ignored issues
show
Duplication introduced by
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...
200
    {
201
        $total = count($parts);
202
203
        for ($i = $startIndex; $i < $total; $i++) {
204
            if (!($parts[$i] instanceof Nickname)) {
205
                return $i;
206
            }
207
        }
208
209
        return $total - 1;
210
    }
211
}
212