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

LastnameMapper::isIgnoredPart()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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