Completed
Push — dev-replacer ( 8b41f0...6cfa56 )
by Propa
02:49
created

PhoneValidator::replacePhone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 4
dl 0
loc 4
rs 10
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
    public function __construct(PhoneNumberUtil $lib)
64
    {
65
        $this->lib = $lib;
66
    }
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
    public function validatePhone($attribute, $value, array $parameters, $validator)
80
    {
81
        $this->data = array_dot($validator->getData());
82
83
        $this->assignParameters($parameters);
84
85
        $this->checkCountries($attribute);
86
87
        // If autodetecting, let's first try without a country.
88
        // Otherwise use provided countries as default.
89
        if ($this->autodetect || ($this->lenient && empty($this->countries))) {
90
            array_unshift($this->countries, null);
91
        }
92
93
        foreach ($this->countries as $country) {
94
            if ($this->isValidNumber($value, $country)) {
95
                return true;
96
            }
97
        }
98
99
        // All specified country validations have failed.
100
        return false;
101
    }
102
    
103
    /**
104
     * Replace all place-holders for the phone rule.
105
     *
106
     * @param  string  $message
107
     * @param  string  $attribute
108
     * @param  string  $rule
109
     * @param  array   $parameters
110
     * @return string
111
     */
112
    public function replacePhone($message, $attribute, $rule, $parameters)
0 ignored issues
show
Unused Code introduced by
The parameter $attribute 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...
Unused Code introduced by
The parameter $rule 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...
113
    {
114
        return str_replace(':value', $parameters[0], $message);
115
    }
116
117
    /**
118
     * Parses the supplied validator parameters.
119
     *
120
     * @param array $parameters
121
     * @throws \Propaganistas\LaravelPhone\Exceptions\InvalidParameterException
122
     */
123
    protected function assignParameters(array $parameters)
124
    {
125
        $types = [];
126
127
        foreach ($parameters as $parameter) {
128
            // First check if the parameter is some phone type configuration.
129
            if ($this->isPhoneType($parameter)) {
130
                $types[] = strtoupper($parameter);
131
            } elseif ($this->isPhoneCountry($parameter)) {
132
                $this->countries[] = strtoupper($parameter);
133
            } elseif ($parameter == 'AUTO') {
134
                $this->autodetect = true;
135
            } elseif ($parameter == 'LENIENT') {
136
                $this->lenient = true;
137
            } // Lastly check if it is an input field containing the country.
138
            elseif ($this->isInputField($parameter)) {
139
                $this->countryField = $parameter;
140
            } else {
141
                // Force developers to write proper code.
142
                throw new InvalidParameterException($parameter);
143
            }
144
        }
145
146
        $this->types = $this->parseTypes($types);
147
148
    }
149
150
    /**
151
     * Checks the detected countries. Overrides countries if a country field is present.
152
     * When using a country field, we should validate to false if country is empty so no exception
153
     * will be thrown.
154
     *
155
     * @param string $attribute
156
     * @throws \Propaganistas\LaravelPhone\Exceptions\NoValidCountryFoundException
157
     */
158
    protected function checkCountries($attribute)
159
    {
160
        $countryField = (is_null($this->countryField) ? $attribute . '_country' : $this->countryField);
161
162
        if ($this->isInputField($countryField)) {
163
            $this->countries = [array_get($this->data, $countryField)];
164
        } elseif (! $this->autodetect && ! $this->lenient && empty($this->countries)) {
165
            throw new NoValidCountryFoundException;
166
        }
167
    }
168
169
    /**
170
     * Performs the actual validation of the phone number.
171
     *
172
     * @param mixed $number
173
     * @param null  $country
174
     * @return bool
175
     */
176
    protected function isValidNumber($number, $country = null)
177
    {
178
        try {
179
            // Throws NumberParseException if not parsed correctly against the supplied country.
180
            // If no country was given, tries to discover the country code from the number itself.
181
            $phoneNumber = $this->lib->parse($number, $country);
182
183
            // Check if type is allowed.
184
            if (empty($this->types) || in_array($this->lib->getNumberType($phoneNumber), $this->types)) {
185
186
                // Lenient validation.
187
                if ($this->lenient) {
188
                    return $this->lib->isPossibleNumber($phoneNumber, $country);
189
                }
190
191
                // For automatic detection, the number should have a country code.
192
                if ($phoneNumber->hasCountryCode()) {
193
194
                    // Automatic detection:
195
                    if ($this->autodetect) {
196
                        // Validate if the international phone number is valid for its contained country.
197
                        return $this->lib->isValidNumber($phoneNumber);
198
                    }
199
200
                    // Validate number against the specified country.
201
                    return $this->lib->isValidNumberForRegion($phoneNumber, $country);
202
                }
203
            }
204
205
        } catch (NumberParseException $e) {
206
            // Proceed to default validation error.
207
        }
208
209
        return false;
210
    }
211
212
    /**
213
     * Parses the supplied phone number types.
214
     *
215
     * @param array $types
216
     * @return array
217
     */
218
    protected function parseTypes(array $types)
219
    {
220
        // Transform types to their namespaced class constant.
221
        array_walk($types, function (&$type) {
222
            $type = constant('\libphonenumber\PhoneNumberType::' . $type);
223
        });
224
225
        // Add in the unsure number type if applicable.
226
        if (array_intersect([PhoneNumberType::FIXED_LINE, PhoneNumberType::MOBILE], $types)) {
227
            $types[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
228
        }
229
230
        return $types;
231
    }
232
233
    /**
234
     * Checks if the given field is an actual input field.
235
     *
236
     * @param string $field
237
     * @return bool
238
     */
239
    public function isInputField($field)
240
    {
241
        return ! is_null(array_get($this->data, $field));
242
    }
243
244
    /**
245
     * Checks if the supplied string is a valid country code.
246
     *
247
     * @param  string $country
248
     * @return bool
249
     */
250
    public function isPhoneCountry($country)
251
    {
252
        return ISO3166::isValid(strtoupper($country));
253
    }
254
255
    /**
256
     * Checks if the supplied string is a valid phone number type.
257
     *
258
     * @param  string $type
259
     * @return bool
260
     */
261
    public function isPhoneType($type)
262
    {
263
        // Legacy support.
264
        $type = ($type == 'LANDLINE' ? 'FIXED_LINE' : $type);
265
266
        return defined('\libphonenumber\PhoneNumberType::' . strtoupper($type));
267
    }
268
269
}
270