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

LastnameMapper::isPrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
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
        $parts = array_reverse($parts);
38
39
        $parts = $this->mapReversedParts($parts);
40
41
        return array_reverse($parts);
42
    }
43
44
    /**
45
     * we map the parts in reverse order because it makes more
46
     * sense to parse for the lastname starting from the end
47
     *
48
     * @param array $parts
49
     * @return array
50
     */
51
    protected function mapReversedParts(array $parts): array
52
    {
53
        $length = count($parts);
54
        $remapIgnored = true;
55
56
        foreach ($parts as $k => $part) {
57
            if ($this->isIgnoredPart($part)) {
58
                continue;
59
            }
60
61
            if ($part instanceof AbstractPart) {
62
                break;
63
            }
64
65
            $originalIndex = $length - $k - 1;
66
            $originalParts = array_reverse($parts);
67
68
            if ($this->isFollowedByLastnamePart($originalParts, $originalIndex)) {
69
                if ($this->isApplicablePrefix($originalParts, $originalIndex)) {
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
            $parts[$k] = new Lastname($part);
80
            $remapIgnored = false;
81
        }
82
83
        if ($remapIgnored) {
84
            $parts = $this->remapIgnored($parts);
85
        }
86
87
        return $parts;
88
    }
89
90
    /**
91
     * indicates if we should stop mapping at the give index $k
92
     *
93
     * the assumption is that lastname parts have already been found
94
     * but we want to see if we should add more parts
95
     *
96
     * @param array $parts
97
     * @param int $k
98
     * @return bool
99
     */
100
    protected function shouldStopMapping(array $parts, int $k): bool
101
    {
102
        if ($k + 2 > count($parts)) {
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
        foreach ($parts as $k => $part) {
132
            if (!$this->isIgnoredPart($part)) {
133
                break;
134
            }
135
136
            $parts[$k] = new Lastname($part);
137
        }
138
139
        return $parts;
140
    }
141
142
    /**
143
     * @param array $parts
144
     * @param int $index
145
     * @return bool
146
     */
147
    protected function isFollowedByLastnamePart(array $parts, int $index): bool
148
    {
149
        $next = $this->skipNicknameParts($parts, $index + 1);
150
151
        return (isset($parts[$next]) && $parts[$next] instanceof Lastname);
152
    }
153
154
    /**
155
     * Assuming that the part at the given index is matched as a prefix,
156
     * determines if the prefix should be applied to the lastname.
157
     *
158
     * We only apply it to the lastname if we already have at least one
159
     * lastname part and there are other parts left in
160
     * the name (this effectively prioritises firstname over prefix matching).
161
     *
162
     * This expects the parts array and index to be in the original order.
163
     *
164
     * @param array $parts
165
     * @param int $index
166
     * @return bool
167
     */
168
    protected function isApplicablePrefix(array $parts, int $index): bool
169
    {
170
        if (!$this->isPrefix($parts[$index])) {
171
            return false;
172
        }
173
174
        return $this->hasUnmappedPartsBefore($parts, $index);
175
    }
176
177
    /**
178
     * check if the given word is a lastname prefix
179
     *
180
     * @param string $word the word to check
181
     * @return bool
182
     */
183
    protected function isPrefix($word): bool
184
    {
185
        return (array_key_exists($this->getKey($word), $this->prefixes));
186
    }
187
188
    /**
189
     * find the next non-nickname index in parts
190
     *
191
     * @param $parts
192
     * @param $startIndex
193
     * @return int|void
194
     */
195 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...
196
    {
197
        $total = count($parts);
198
199
        for ($i = $startIndex; $i < $total; $i++) {
200
            if (!($parts[$i] instanceof Nickname)) {
201
                return $i;
202
            }
203
        }
204
205
        return $total - 1;
206
    }
207
}
208