Completed
Push — master ( dd1828...b8149d )
by Propa
02:30
created

PhoneValidator   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 201
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 16
Bugs 3 Features 3
Metric Value
wmc 28
c 16
b 3
f 3
lcom 1
cbo 4
dl 0
loc 201
ccs 56
cts 56
cp 1
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A validatePhone() 0 21 4
A isPhoneCountry() 0 4 4
A __construct() 0 4 1
A checkCountries() 0 11 4
B isValidNumber() 0 27 6
A isPhoneType() 0 7 2
B assignParameters() 0 20 5
A parseTypes() 0 14 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
     * Countries to validate against.
26
     *
27
     * @var array
28
     */
29
    protected $countries = [];
30
31
    /**
32
     * Transformed phone number types to validate against.
33
     *
34
     * @var array
35
     */
36
    protected $types = [];
37
38
    /**
39
     * PhoneValidator constructor.
40
     */
41 21
    public function __construct(PhoneNumberUtil $lib)
42
    {
43 21
        $this->lib = $lib;
44 21
    }
45
46
    /**
47
     * Validates a phone number.
48
     *
49
     * @param  string $attribute
50
     * @param  mixed  $value
51
     * @param  array  $parameters
52
     * @param  object $validator
53
     * @return bool
54
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
55
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
56
     */
57 21
    public function validatePhone($attribute, $value, array $parameters, $validator)
58
    {
59 21
        $this->assignParameters(array_map('strtoupper', $parameters));
60
61 18
        $this->checkCountries($attribute, $validator);
62
63
        // If autodetecting, let's first try without a country.
64
        // Otherwise use provided countries as default.
65 15
        if ($this->autodetect) {
66 3
            array_unshift($this->countries, null);
67 2
        }
68
69 15
        foreach ($this->countries as $country) {
70 15
            if ($this->isValidNumber($value, $country)) {
71 15
                return true;
72
            }
73 10
        }
74
75
        // All specified country validations have failed.
76 15
        return false;
77
    }
78
79
    /**
80
     * Parses the supplied validator parameters.
81
     *
82
     * @param array $parameters
83
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
84
     */
85 21
    protected function assignParameters(array $parameters)
86
    {
87 21
        $types = array();
88
89 21
        foreach ($parameters as $parameter) {
90 15
            if ($this->isPhoneCountry($parameter)) {
91 12
                $this->countries[] = $parameter;
92 14
            } elseif ($this->isPhoneType($parameter)) {
93 9
                $types[] = $parameter;
94 10
            } elseif ($parameter == 'AUTO') {
95 3
                $this->autodetect = true;
96 2
            } else {
97
                // Force developers to write proper code.
98 7
                throw new InvalidParameterException($parameter);
99
            }
100 14
        }
101
102 18
        $this->types = $this->parseTypes($types);
103
104 18
    }
105
106
    /**
107
     * Checks the detected countries. Overrides countries if a country field is present.
108
     * When using a country field, we should validate to false if country is empty so no exception
109
     * will be thrown.
110
     *
111
     * @param $attribute
112
     * @param $validator
113
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
114
     */
115 18
    protected function checkCountries($attribute, $validator)
116
    {
117 18
        $data = $validator->getData();
118 18
        $countryField = $attribute . '_country';
119
120 18
        if (isset($data[$countryField])) {
121 6
            $this->countries = array($data[$countryField]);
122 16
        } elseif (!$this->autodetect && empty($this->countries)) {
123 3
            throw new NoValidCountryFoundException;
124
        }
125 15
    }
126
127
    /**
128
     * Performs the actual validation of the phone number.
129
     *
130
     * @param mixed $number
131
     * @param null  $country
132
     * @return bool
133
     */
134 15
    protected function isValidNumber($number, $country = null)
135
    {
136
        try {
137
            // Throws NumberParseException if not parsed correctly against the supplied country.
138
            // If no country was given, tries to discover the country code from the number itself.
139 15
            $phoneNumber = $this->lib->parse($number, $country);
140
141
            // For automatic detection, the number should have a country code.
142
            // Check if type is allowed.
143 15
            if ($phoneNumber->hasCountryCode() && (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types))) {
144
145
                // Automatic detection:
146 15
                if ($this->autodetect) {
147
                    // Validate if the international phone number is valid for its contained country.
148 3
                    return $this->lib->isValidNumber($phoneNumber);
149
                }
150
151
                // Validate number against the specified country.
152 12
                return $this->lib->isValidNumberForRegion($phoneNumber, $country);
153
            }
154
155 7
        } catch (NumberParseException $e) {
156
            // Proceed to default validation error.
157
        }
158
159 9
        return false;
160
    }
161
162
    /**
163
     * Parses the supplied phone number types.
164
     *
165
     * @param array $types
166
     * @return array
167
     */
168
    protected function parseTypes(array $types)
169
    {
170
        // Transform types to their namespaced class constant.
171 18
        array_walk($types, function (&$type) {
172 6
            $type = constant('\libphonenumber\PhoneNumberType::' . $type);
173 18
        });
174
175
        // Add in the unsure number type if applicable.
176 18
        if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
177 6
            $types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
178 4
        }
179
180 18
        return $types;
181
    }
182
183
    /**
184
     * Checks if the supplied string is a valid country code using some arbitrary country validation.
185
     * If using a package based on umpirsky/country-list, invalidate the option 'ZZ => Unknown or invalid region'.
186
     *
187
     * @param  string $country
188
     * @return bool
189
     */
190 15
    public function isPhoneCountry($country)
191
    {
192 15
        return (strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && $country != 'ZZ');
193
    }
194
195
    /**
196
     * Checks if the supplied string is a valid phone number type.
197
     *
198
     * @param  string $type
199
     * @return bool
200
     */
201 12
    public function isPhoneType($type)
202
    {
203
        // Legacy support.
204 12
        $type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
205
206 12
        return defined('\libphonenumber\PhoneNumberType::' . $type);
207
    }
208
209
}
210