Completed
Pull Request — master (#22)
by Ryan
02:22
created

PhoneValidator   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 215
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 98.41%

Importance

Changes 18
Bugs 4 Features 5
Metric Value
wmc 30
c 18
b 4
f 5
lcom 1
cbo 4
dl 0
loc 215
ccs 62
cts 63
cp 0.9841
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A isPhoneCountry() 0 4 4
A __construct() 0 4 1
A validatePhone() 0 21 4
B assignParameters() 0 22 6
A checkCountries() 0 11 4
C isValidNumber() 0 32 7
A parseTypes() 0 14 2
A isPhoneType() 0 7 2
1
<?php namespace Propaganistas\LaravelPhone;
2
3
use libphonenumber\NumberParseException;
4
use libphonenumber\PhoneNumberType;
5
use libphonenumber\PhoneNumberUtil;
6
use Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException;
7
use Propaganistas\LaravelPhone\Exceptions\InvalidParameterException;
8
9
class PhoneValidator
10
{
11
12
    /**
13
     * @var \libphonenumber\PhoneNumberUtil
14
     */
15
    protected $lib;
16
17
    /**
18
     * Whether the country should be auto-detected.
19
     *
20
     * @var bool
21
     */
22
    protected $autodetect = false;
23
24
    /**
25
     * Whether to allow lenient checking of numbers (i.e. landline without area codes)
26
     *
27
     * @var object
28
     */
29
    protected $lenient = false;
30
31
    /**
32
     * Countries to validate against.
33
     *
34
     * @var array
35
     */
36
    protected $countries = [];
37
38
    /**
39
     * Transformed phone number types to validate against.
40
     *
41
     * @var array
42
     */
43
    protected $types = [];
44
45
    /**
46
     * PhoneValidator constructor.
47
     */
48 18
    public function __construct(PhoneNumberUtil $lib)
49
    {
50 18
        $this->lib = $lib;
51 16
    }
52
53
    /**
54
     * Validates a phone number.
55
     *
56
     * @param  string $attribute
57
     * @param  mixed  $value
58
     * @param  array  $parameters
59
     * @param  object $validator
60
     * @return bool
61
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
62
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
63
     */
64 19
    public function validatePhone($attribute, $value, array $parameters, $validator)
65
    {
66 16
        $this->assignParameters(array_map('strtoupper', $parameters));
67
68 15
        $this->checkCountries($attribute, $validator);
69
70
        // If autodetecting, let's first try without a country.
71
        // Otherwise use provided countries as default.
72 12
        if ($this->autodetect) {
73 2
            array_unshift($this->countries, null);
74 1
        }
75
76 12
        foreach ($this->countries as $country) {
77 12
            if ($this->isValidNumber($value, $country)) {
78 15
                return true;
79
            }
80 6
        }
81
82
        // All specified country validations have failed.
83 14
        return false;
84 2
    }
85
86
    /**
87
     * Parses the supplied validator parameters.
88
     *
89
     * @param array $parameters
90
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
91
     */
92 18
    protected function assignParameters(array $parameters)
93
    {
94 18
        $types = array();
95
96 16
        foreach ($parameters as $parameter) {
97 12
            if ($this->isPhoneCountry($parameter)) {
98 11
                $this->countries[] = $parameter;
99 11
            } elseif ($this->isPhoneType($parameter)) {
100 7
                $types[] = $parameter;
101 10
            } elseif ($parameter == 'AUTO') {
102 2
                $this->autodetect = true;
103 7
            } elseif ($parameter == 'LENIENT') {
104 3
                $this->lenient = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type boolean is incompatible with the declared type object of property $lenient.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
105 1
            } else {
106
                // Force developers to write proper code.
107 7
                throw new InvalidParameterException($parameter);
108
            }
109 9
        }
110
111 14
        $this->types = $this->parseTypes($types);
112
113 14
    }
114
115
    /**
116
     * Checks the detected countries. Overrides countries if a country field is present.
117
     * When using a country field, we should validate to false if country is empty so no exception
118
     * will be thrown.
119
     *
120
     * @param $attribute
121
     * @param $validator
122
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
123
     */
124 15
    protected function checkCountries($attribute, $validator)
125
    {
126 14
        $data = $validator->getData();
127 15
        $countryField = $attribute . '_country';
128
129 15
        if (isset($data[$countryField])) {
130 4
            $this->countries = array($data[$countryField]);
131 13
        } elseif (!$this->autodetect && empty($this->countries)) {
132 2
            throw new NoValidCountryFoundException;
133
        }
134 12
    }
135
136
    /**
137
     * Performs the actual validation of the phone number.
138
     *
139
     * @param mixed $number
140
     * @param null  $country
141
     * @return bool
142
     */
143 14
    protected function isValidNumber($number, $country = null)
144
    {
145
        try {
146
            // Throws NumberParseException if not parsed correctly against the supplied country.
147
            // If no country was given, tries to discover the country code from the number itself.
148 12
            $phoneNumber = $this->lib->parse($number, $country);
149
150
            // Lenient validation; doesn't need a country code.
151 12
            if ($this->lenient) {
152 2
                return $this->lib->isPossibleNumber($phoneNumber);
153
            }
154
155
            // For automatic detection, the number should have a country code.
156
            // Check if type is allowed.
157 12
            if ($phoneNumber->hasCountryCode() && (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types))) {
158
159
                // Automatic detection:
160 14
                if ($this->autodetect) {
161
                    // Validate if the international phone number is valid for its contained country.
162 2
                    return $this->lib->isValidNumber($phoneNumber);
163
                }
164
165
                // Validate number against the specified country.
166 10
                return $this->lib->isValidNumberForRegion($phoneNumber, $country);
167
            }
168
169 4
        } catch (NumberParseException $e) {
170
            // Proceed to default validation error.
171
        }
172
173 6
        return false;
174
    }
175
176
    /**
177
     * Parses the supplied phone number types.
178
     *
179
     * @param array $types
180
     * @return array
181
     */
182
    protected function parseTypes(array $types)
183
    {
184
        // Transform types to their namespaced class constant.
185 14
        array_walk($types, function (&$type) {
186 4
            $type = constant('\libphonenumber\PhoneNumberType::' . $type);
187 14
        });
188
189
        // Add in the unsure number type if applicable.
190 14
        if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
191 4
            $types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
192 2
        }
193
194 15
        return $types;
195 1
    }
196
197
    /**
198
     * Checks if the supplied string is a valid country code using some arbitrary country validation.
199
     * If using a package based on umpirsky/country-list, invalidate the option 'ZZ => Unknown or invalid region'.
200
     *
201
     * @param  string $country
202
     * @return bool
203
     */
204 12
    public function isPhoneCountry($country)
205
    {
206 12
        return (strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && $country != 'ZZ');
207
    }
208
209
    /**
210
     * Checks if the supplied string is a valid phone number type.
211
     *
212
     * @param  string $type
213
     * @return bool
214
     */
215 11
    public function isPhoneType($type)
216
    {
217
        // Legacy support.
218 11
        $type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
219
220 10
        return defined('\libphonenumber\PhoneNumberType::' . $type);
221
    }
222
223
}
224