MetadataFilter   F
last analyzed

Complexity

Total Complexity 72

Size/Duplication

Total Lines 365
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 177
dl 0
loc 365
rs 2.64
c 0
b 0
f 0
wmc 72

9 Methods

Rating   Name   Duplication   Size   Complexity  
A forLiteBuild() 0 4 1
A __construct() 0 3 1
A emptyFilter() 0 4 1
A forSpecialBuild() 0 4 1
B computeComplement() 0 28 8
D parseFieldMapFromString() 0 93 25
A shouldDrop() 0 20 6
F filterMetadata() 0 95 24
A getFiltered() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like MetadataFilter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MetadataFilter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace libphonenumber\buildtools;
4
5
use Guzzle\Common\Exception\RuntimeException;
0 ignored issues
show
Bug introduced by
The type Guzzle\Common\Exception\RuntimeException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use libphonenumber\PhoneMetadata;
7
use libphonenumber\PhoneNumberDesc;
8
9
/**
10
 * Class to encapsulate the metadata filtering logic and restrict visibility into raw data
11
 * structures
12
 *
13
 *
14
 * @package libphonenumber\buildtools
15
 * @internal
16
 */
17
class MetadataFilter
18
{
19
    public static $EXCLUDABLE_PARENT_FIELDS = array(
20
        'fixedLine',
21
        'mobile',
22
        'tollFree',
23
        'premiumRate',
24
        'sharedCost',
25
        'personalNumber',
26
        'voip',
27
        'pager',
28
        'uan',
29
        'emergency',
30
        'voicemail',
31
        'shortCode',
32
        'standardRate',
33
        'carrierSpecific',
34
        'smsServices',
35
        'noInternationalDialling'
36
    );
37
38
    public static $EXCLUDABLE_CHILD_FIELDS = array(
39
        'nationalNumberPattern',
40
        'possibleLength',
41
        'possibleLengthLocalOnly',
42
        'exampleNumber'
43
    );
44
45
    public static $EXCLUDABLE_CHILDLESS_FIELDS = array(
46
        'preferredInternationalPrefix',
47
        'nationalPrefix',
48
        'preferredExtnPrefix',
49
        'nationalPrefixTransformRule',
50
        'sameMobileAndFixedLinePattern',
51
        'mainCountryForCode',
52
        'leadingZeroPossible',
53
        'mobileNumberPortableRegion'
54
    );
55
56
    protected $blackList;
57
58
    public function __construct($blackList = array())
59
    {
60
        $this->blackList = $blackList;
61
    }
62
63
    public static function forLiteBuild()
64
    {
65
        // "exampleNumber" is a blacklist.
66
        return new static(self::parseFieldMapFromString('exampleNumber'));
67
    }
68
69
    /**
70
     * The input blacklist or whitelist string is expected to be of the form "a(b,c):d(e):f", where
71
     * b and c are children of a, e is a child of d, and f is either a parent field, a child field, or
72
     * a childless field. Order and whitespace don't matter. We throw RuntimeException for any
73
     * duplicates, malformed strings, or strings where field tokens do not correspond to strings in
74
     * the sets of excludable fields. We also throw RuntimeException for empty strings since such
75
     * strings should be treated as a special case by the flag checking code and not passed here.
76
     * @param string $string
77
     * @return array
78
     */
79
    public static function parseFieldMapFromString($string)
80
    {
81
        if ($string === null) {
0 ignored issues
show
introduced by
The condition $string === null is always false.
Loading history...
82
            throw new \RuntimeException('Null string should not be passed to parseFieldMapFromString');
83
        }
84
85
        // Remove whitespace
86
        $string = \str_replace(' ', '', $string);
87
        if (\strlen($string) === 0) {
88
            throw new \RuntimeException('Empty string should not be passed to parseFieldMapFromString');
89
        }
90
91
        $fieldMap = array();
92
        $wildCardChildren = array();
93
94
        $groups = \explode(':', $string);
95
        foreach ($groups as $group) {
96
            $leftParenIndex = \strpos($group, '(');
97
            $rightParenIndex = \strpos($group, ')');
98
99
            if ($leftParenIndex === false && $rightParenIndex === false) {
100
                if (\in_array($group, self::$EXCLUDABLE_PARENT_FIELDS)) {
101
                    if (\array_key_exists($group, $fieldMap)) {
102
                        throw new \RuntimeException($group . ' given more than once in ' . $string);
103
                    }
104
                    $fieldMap[$group] = self::$EXCLUDABLE_CHILD_FIELDS;
105
                } elseif (\in_array($group, self::$EXCLUDABLE_CHILDLESS_FIELDS)) {
106
                    if (\array_key_exists($group, $fieldMap)) {
107
                        throw new \RuntimeException($group . ' given more than once in ' . $string);
108
                    }
109
                    $fieldMap[$group] = array();
110
                } elseif (\in_array($group, self::$EXCLUDABLE_CHILD_FIELDS)) {
111
                    if (\in_array($group, $wildCardChildren)) {
112
                        throw new \RuntimeException($group . ' given more than once in ' . $string);
113
                    }
114
                    $wildCardChildren[] = $group;
115
                } else {
116
                    throw new \RuntimeException($group . ' is not a valid token');
117
                }
118
            } elseif ($leftParenIndex > 0 && $rightParenIndex == \strlen($group) - 1) {
119
                // We don't check for duplicate parentheses or illegal characters since these will be caught
120
                // as not being part of valid field tokens.
121
                $parent = \substr($group, 0, $leftParenIndex);
122
                if (!\in_array($parent, self::$EXCLUDABLE_PARENT_FIELDS)) {
123
                    throw new \RuntimeException($parent . ' is not a valid parent token');
124
                }
125
126
                if (\array_key_exists($parent, $fieldMap)) {
127
                    throw new \RuntimeException($parent . ' given more than once in ' . $string);
128
                }
129
                $children = array();
130
                $childSearch = \explode(',', \substr($group, $leftParenIndex + 1, $rightParenIndex - $leftParenIndex - 1));
131
                foreach ($childSearch as $child) {
132
                    if (!\in_array($child, self::$EXCLUDABLE_CHILD_FIELDS)) {
133
                        throw new \RuntimeException($child . ' is not a valid child token');
134
                    }
135
                    if (\in_array($child, $children)) {
136
                        throw new \RuntimeException($child . ' given more than once in ' . $group);
137
                    }
138
                    $children[] = $child;
139
                }
140
                $fieldMap[$parent] = $children;
141
            } else {
142
                throw new \RuntimeException('Incorrect location of parentheses in ' . $group);
143
            }
144
        }
145
146
        foreach ($wildCardChildren as $wildCardChild) {
147
            foreach (self::$EXCLUDABLE_PARENT_FIELDS as $parent) {
148
                if (!\array_key_exists($parent, $fieldMap)) {
149
                    $fieldMap[$parent] = array();
150
                }
151
152
                $children = $fieldMap[$parent];
153
154
                if (\in_array($wildCardChild, $children)
155
                    && \count($fieldMap[$parent]) != \count(self::$EXCLUDABLE_CHILD_FIELDS)
156
                ) {
157
                    // The map already contains parent -> wildcardChild but not all possible children.
158
                    // So wildcardChild was given explicitly as a child of parent, which is a duplication
159
                    // since it's also given as a wildcard child.
160
                    throw new \RuntimeException($wildCardChild . ' is present by itself so remove it from ' . $parent . '\'s group');
161
                }
162
163
                if (!\in_array($wildCardChild, $children)) {
164
                    // We don't have an add() that fails if it's a duplicate
165
                    $children[] = $wildCardChild;
166
                }
167
                $fieldMap[$parent] = $children;
168
            }
169
        }
170
171
        return $fieldMap;
172
    }
173
174
    public static function forSpecialBuild()
175
    {
176
        // "mobile" is a whitelist.
177
        return new static(self::computeComplement(self::parseFieldMapFromString('mobile')));
178
    }
179
180
    /**
181
     * Does not check that legal tokens are used, assuming that $fieldMap is constructed using
182
     * parseFieldMapFromString which does check. If $fieldMap contains illegal tokens or parent
183
     * fields with no children or other unexpected state, the behavior of this function is undefined.
184
     * @param $fieldMap
185
     * @return array
186
     */
187
    public static function computeComplement($fieldMap)
188
    {
189
        $complement = array();
190
        foreach (self::$EXCLUDABLE_PARENT_FIELDS as $parent) {
191
            if (!\array_key_exists($parent, $fieldMap)) {
192
                $complement[$parent] = self::$EXCLUDABLE_CHILD_FIELDS;
193
            } else {
194
                $otherChildren = $fieldMap[$parent];
195
                // If the other map has all the children for this parent then we don't want to include the
196
                // parent as a key.
197
                if (\count($otherChildren) != \count(self::$EXCLUDABLE_CHILD_FIELDS)) {
198
                    $children = array();
199
                    foreach (self::$EXCLUDABLE_CHILD_FIELDS as $child) {
200
                        if (!\in_array($child, $otherChildren)) {
201
                            $children[] = $child;
202
                        }
203
                    }
204
                    $complement[$parent] = $children;
205
                }
206
            }
207
        }
208
        foreach (self::$EXCLUDABLE_CHILDLESS_FIELDS as $childlessField) {
209
            if (!\array_key_exists($childlessField, $fieldMap)) {
210
                $complement[$childlessField] = array();
211
            }
212
        }
213
214
        return $complement;
215
    }
216
217
    public static function emptyFilter()
218
    {
219
        // Empty blacklist, meaning we filter nothing.
220
        return new MetadataFilter();
221
    }
222
223
    /**
224
     * Clears certain fields in $metadata as defined by the MetadataFilter instance.
225
     * Note that this changes the mutable $metadata object. If this method does not
226
     * return successfully, do not assume $metadata has not changed.
227
     *
228
     * @param PhoneMetadata $metadata The object to be filtered
229
     */
230
    public function filterMetadata(PhoneMetadata $metadata)
231
    {
232
        if ($metadata->hasFixedLine()) {
233
            $metadata->setFixedLine($this->getFiltered('fixedLine', $metadata->getFixedLine()));
234
        }
235
236
        if ($metadata->hasMobile()) {
237
            $metadata->setMobile($this->getFiltered('mobile', $metadata->getMobile()));
238
        }
239
240
        if ($metadata->hasTollFree()) {
241
            $metadata->setTollFree($this->getFiltered('tollFree', $metadata->getTollFree()));
242
        }
243
244
        if ($metadata->hasPremiumRate()) {
245
            $metadata->setPremiumRate($this->getFiltered('premiumRate', $metadata->getPremiumRate()));
246
        }
247
248
        if ($metadata->hasSharedCost()) {
249
            $metadata->setSharedCost($this->getFiltered('sharedCost', $metadata->getSharedCost()));
250
        }
251
252
        if ($metadata->hasPersonalNumber()) {
253
            $metadata->setPersonalNumber($this->getFiltered('personalNumber', $metadata->getPersonalNumber()));
254
        }
255
256
        if ($metadata->hasVoip()) {
257
            $metadata->setVoip($this->getFiltered('voip', $metadata->getVoip()));
258
        }
259
260
        if ($metadata->hasPager()) {
261
            $metadata->setPager($this->getFiltered('pager', $metadata->getPager()));
262
        }
263
264
        if ($metadata->hasUan()) {
265
            $metadata->setUan($this->getFiltered('uan', $metadata->getUan()));
266
        }
267
268
        if ($metadata->hasEmergency()) {
269
            $metadata->setEmergency($this->getFiltered('emergency', $metadata->getEmergency()));
270
        }
271
272
        if ($metadata->hasVoicemail()) {
273
            $metadata->setVoicemail($this->getFiltered('voicemail', $metadata->getVoicemail()));
274
        }
275
276
        if ($metadata->hasShortCode()) {
277
            $metadata->setShortCode($this->getFiltered('shortCode', $metadata->getShortCode()));
278
        }
279
280
        if ($metadata->hasStandardRate()) {
281
            $metadata->setStandardRate($this->getFiltered('standardRate', $metadata->getStandardRate()));
282
        }
283
284
        if ($metadata->hasCarrierSpecific()) {
285
            $metadata->setCarrierSpecific($this->getFiltered('carrierSpecific', $metadata->getCarrierSpecific()));
286
        }
287
288
        if ($metadata->hasSmsServices()) {
289
            $metadata->setSmsServices($this->getFiltered('smsServices', $metadata->getSmsServices()));
290
        }
291
292
        if ($metadata->hasNoInternationalDialling()) {
293
            $metadata->setNoInternationalDialling($this->getFiltered(
294
                'noInternationalDialling',
295
                $metadata->getNoInternationalDialling()
296
            ));
297
        }
298
299
        if ($this->shouldDrop('preferredInternationalPrefix')) {
300
            $metadata->clearPreferredInternationalPrefix();
301
        }
302
303
        if ($this->shouldDrop('preferredExtnPrefix')) {
304
            $metadata->clearPreferredExtnPrefix();
305
        }
306
307
        if ($this->shouldDrop('nationalPrefixTransformRule')) {
308
            $metadata->clearNationalPrefixTransformRule();
309
        }
310
311
        if ($this->shouldDrop('sameMobileAndFixedLinePattern')) {
312
            $metadata->clearSameMobileAndFixedLinePattern();
313
        }
314
315
        if ($this->shouldDrop('mainCountryForCode')) {
316
            $metadata->clearMainCountryForCode();
317
        }
318
319
        if ($this->shouldDrop('leadingZeroPossible')) {
320
            $metadata->clearLeadingZeroPossible();
321
        }
322
323
        if ($this->shouldDrop('mobileNumberPortableRegion')) {
324
            $metadata->clearMobileNumberPortableRegion();
325
        }
326
    }
327
328
    /**
329
     * @param string $type
330
     * @param PhoneNumberDesc $desc
331
     * @return PhoneNumberDesc
332
     */
333
    private function getFiltered($type, PhoneNumberDesc $desc)
334
    {
335
        $builder = new PhoneNumberDesc();
336
        $builder->mergeFrom($desc);
337
338
        if ($this->shouldDrop($type, 'nationalNumberPattern')) {
339
            $builder->clearNationalNumberPattern();
340
        }
341
342
        if ($this->shouldDrop($type, 'possibleLength')) {
343
            $builder->clearPossibleLength();
344
        }
345
346
        if ($this->shouldDrop($type, 'possibleLengthLocalOnly')) {
347
            $builder->clearPossibleLengthLocalOnly();
348
        }
349
350
        if ($this->shouldDrop($type, 'exampleNumber')) {
351
            $builder->clearExampleNumber();
352
        }
353
354
        return $builder;
355
    }
356
357
    /**
358
     * @param $parent
359
     * @param $child
360
     * @return bool
361
     */
362
    public function shouldDrop($parent, $child = null)
363
    {
364
        if ($child !== null) {
365
            if (!\in_array($parent, self::$EXCLUDABLE_PARENT_FIELDS)) {
366
                throw new RuntimeException($parent . ' is not an excludable parent field');
367
            }
368
369
            if (!\in_array($child, self::$EXCLUDABLE_CHILD_FIELDS)) {
370
                throw new RuntimeException($parent . ' is not an excludable child field');
371
            }
372
373
            return \array_key_exists($parent, $this->blackList) && \in_array($child, $this->blackList[$parent]);
374
        }
375
376
        $childlessField = $parent;
377
        if (!\in_array($childlessField, self::$EXCLUDABLE_CHILDLESS_FIELDS)) {
378
            throw new \RuntimeException($childlessField . ' is not an excludable childless field');
379
        }
380
381
        return \array_key_exists($childlessField, $this->blackList);
382
    }
383
}
384