Completed
Push — travis-test ( a8e23e...09200d )
by Propa
04:41
created

PhoneValidator::isValidNumber()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 35
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 11
nop 2
dl 0
loc 35
ccs 11
cts 11
cp 1
crap 7
rs 6.7272
c 0
b 0
f 0
1
<?php namespace Propaganistas\LaravelPhone;
2
3
use Iso3166\Codes as ISO3166;
4
use libphonenumber\NumberParseException;
5
use libphonenumber\PhoneNumberType;
6
use libphonenumber\PhoneNumberUtil;
7
use Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException;
8
use Propaganistas\LaravelPhone\Exceptions\InvalidParameterException;
9
10
class PhoneValidator
11
{
12
13
    /**
14
     * @var \libphonenumber\PhoneNumberUtil
15
     */
16
    protected $lib;
17
18
    /**
19
     * Dotted validator data.
20
     *
21
     * @var array
22
     */
23
    protected $data;
24
25
    /**
26
     * Whether the country should be auto-detected.
27
     *
28
     * @var bool
29
     */
30
    protected $autodetect = false;
31
32
    /**
33
     * Whether to allow lenient checking of numbers (i.e. landline without area codes).
34
     *
35
     * @var bool
36
     */
37
    protected $lenient = false;
38
39
    /**
40
     * The field to use for country if not passed as a parameter.
41
     *
42
     * @var string|null
43
     */
44
    protected $countryField = null;
45
46
    /**
47
     * Countries to validate against.
48
     *
49
     * @var array
50
     */
51
    protected $countries = [];
52
53
    /**
54
     * Transformed phone number types to validate against.
55
     *
56
     * @var array
57
     */
58
    protected $types = [];
59
60
    /**
61
     * PhoneValidator constructor.
62
     */
63 27
    public function __construct(PhoneNumberUtil $lib)
64
    {
65 27
        $this->lib = $lib;
66 27
    }
67
68
    /**
69
     * Validates a phone number.
70
     *
71
     * @param  string $attribute
72
     * @param  mixed  $value
73
     * @param  array  $parameters
74
     * @param  object $validator
75
     * @return bool
76
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
77
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
78
     */
79 27
    public function validatePhone($attribute, $value, array $parameters, $validator)
80
    {
81 27
        $this->data = array_dot($validator->getData());
82
83 27
        $this->assignParameters($parameters);
84
85 24
        $this->checkCountries($attribute);
86
87
        // If autodetecting, let's first try without a country.
88
        // Otherwise use provided countries as default.
89 21
        if ($this->autodetect || ($this->lenient && empty($this->countries))) {
90 6
            array_unshift($this->countries, null);
91
        }
92
93 21
        foreach ($this->countries as $country) {
94 21
            if ($this->isValidNumber($value, $country)) {
95 21
                return true;
96
            }
97
        }
98
99
        // All specified country validations have failed.
100 21
        return false;
101
    }
102
103
    /**
104
     * Parses the supplied validator parameters.
105
     *
106
     * @param array $parameters
107
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
108
     */
109 27
    protected function assignParameters(array $parameters)
110
    {
111 27
        $types = [];
112
113 27
        foreach ($parameters as $parameter) {
114
            // First check if the parameter is some phone type configuration.
115 21
            if ($this->isPhoneType($parameter)) {
116 12
                $types[] = strtoupper($parameter);
117 18
            } elseif ($this->isPhoneCountry($parameter)) {
118 15
                $this->countries[] = strtoupper($parameter);
119 12
            } elseif ($parameter == 'AUTO') {
120 3
                $this->autodetect = true;
121 9
            } elseif ($parameter == 'LENIENT') {
122 3
                $this->lenient = true;
123
            } // Lastly check if it is an input field containing the country.
124 6
            elseif ($this->isInputField($parameter)) {
125 3
                $this->countryField = $parameter;
126
            } else {
127
                // Force developers to write proper code.
128 21
                throw new InvalidParameterException($parameter);
129
            }
130
        }
131
132 24
        $this->types = $this->parseTypes($types);
133
134 24
    }
135
136
    /**
137
     * Checks the detected countries. Overrides countries if a country field is present.
138
     * When using a country field, we should validate to false if country is empty so no exception
139
     * will be thrown.
140
     *
141
     * @param string $attribute
142
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
143
     */
144 24
    protected function checkCountries($attribute)
145
    {
146 24
        $countryField = (is_null($this->countryField) ? $attribute . '_country' : $this->countryField);
147
148 24
        if ($this->isInputField($countryField)) {
149 12
            $this->countries = [array_get($this->data, $countryField)];
150 15
        } elseif (! $this->autodetect && ! $this->lenient && empty($this->countries)) {
151 3
            throw new NoValidCountryFoundException;
152
        }
153 21
    }
154
155
    /**
156
     * Performs the actual validation of the phone number.
157
     *
158
     * @param mixed $number
159
     * @param null  $country
160
     * @return bool
161
     */
162 21
    protected function isValidNumber($number, $country = null)
163
    {
164
        try {
165
            // Throws NumberParseException if not parsed correctly against the supplied country.
166
            // If no country was given, tries to discover the country code from the number itself.
167 21
            $phoneNumber = $this->lib->parse($number, $country);
168
169
            // Check if type is allowed.
170 21
            if (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types)) {
171
172
                // Lenient validation.
173 21
                if ($this->lenient) {
174 3
                    return $this->lib->isPossibleNumber($phoneNumber, $country);
175
                }
176
177
                // For automatic detection, the number should have a country code.
178 21
                if ($phoneNumber->hasCountryCode()) {
179
180
                    // Automatic detection:
181 21
                    if ($this->autodetect) {
182
                        // Validate if the international phone number is valid for its contained country.
183 3
                        return $this->lib->isValidNumber($phoneNumber);
184
                    }
185
186
                    // Validate number against the specified country.
187 18
                    return $this->lib->isValidNumberForRegion($phoneNumber, $country);
188
                }
189
            }
190
191 6
        } catch (NumberParseException $e) {
192
            // Proceed to default validation error.
193
        }
194
195 15
        return false;
196
    }
197
198
    /**
199
     * Parses the supplied phone number types.
200
     *
201
     * @param array $types
202
     * @return array
203
     */
204
    protected function parseTypes(array $types)
205
    {
206
        // Transform types to their namespaced class constant.
207 24
        array_walk($types, function (&$type) {
208 9
            $type = constant('\libphonenumber\PhoneNumberType::' . $type);
209 24
        });
210
211
        // Add in the unsure number type if applicable.
212 24
        if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
213 9
            $types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
214
        }
215
216 24
        return $types;
217
    }
218
219
    /**
220
     * Checks if the given field is an actual input field.
221
     *
222
     * @param string $field
223
     * @return bool
224
     */
225 27
    public function isInputField($field)
226
    {
227 27
        return ! is_null(array_get($this->data, $field));
228
    }
229
230
    /**
231
     * Checks if the supplied string is a valid country code.
232
     *
233
     * @param  string $country
234
     * @return bool
235
     */
236 18
    public function isPhoneCountry($country)
237
    {
238 18
        return ISO3166::isValid(strtoupper($country));
239
    }
240
241
    /**
242
     * Checks if the supplied string is a valid phone number type.
243
     *
244
     * @param  string $type
245
     * @return bool
246
     */
247 21
    public function isPhoneType($type)
248
    {
249
        // Legacy support.
250 21
        $type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
251
252 21
        return defined('\libphonenumber\PhoneNumberType::' . strtoupper($type));
253
    }
254
255
}
256