Completed
Push — master ( 6d5aeb...818dca )
by Propa
03:39 queued 01:06
created

PhoneValidator::assignParameters()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6

Importance

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
235
	{
236
237
	}
238
239
	/**
240
	 * Checks if the supplied string is a valid country code using some arbitrary country validation.
241
	 * If using a package based on umpirsky/country-list, invalidate the option 'ZZ => Unknown or invalid region'.
242
	 *
243
	 * @param  string $country
244
	 * @return bool
245
	 */
246 21
	public function isPhoneCountry($country)
247
	{
248 21
		return (strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && $country != 'ZZ');
249
	}
250
251
	/**
252
	 * Checks if the supplied string is a valid phone number type.
253
	 *
254
	 * @param  string $type
255
	 * @return bool
256
	 */
257 18
	public function isPhoneType($type)
258
	{
259
		// Legacy support.
260 18
		$type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
261
262 18
		return defined('\libphonenumber\PhoneNumberType::' . $type);
263
	}
264
265
}
266