Completed
Push — master ( 55def5...79ff59 )
by Adam
03:10
created

Phone   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 12

Test Coverage

Coverage 84.67%

Importance

Changes 21
Bugs 4 Features 1
Metric Value
wmc 54
c 21
b 4
f 1
lcom 3
cbo 12
dl 0
loc 432
ccs 127
cts 150
cp 0.8467
rs 7.0642

17 Methods

Rating   Name   Duplication   Size   Complexity  
B setValue() 0 22 4
A __construct() 0 6 1
B getValue() 0 19 6
B loadHttpData() 0 8 5
A getControl() 0 5 1
A setAllowedCountries() 0 19 3
A addAllowedCountry() 0 17 4
A getAllowedCountries() 0 9 3
A setDefaultCountry() 0 13 2
A setAllowedPhoneTypes() 0 14 2
A addAllowedPhoneType() 0 10 1
A getAllowedPhoneTypes() 0 4 1
C getControlPart() 0 81 9
A getLabelPart() 0 4 1
B validateCountry() 0 12 6
A validateType() 0 12 2
A register() 0 19 3

How to fix   Complexity   

Complex Class

Complex classes like Phone often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Phone, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Phone.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec <[email protected]>
8
 * @package        iPublikuj:FormPhone!
9
 * @subpackage     Controls
10
 * @since          1.0.0
11
 *
12
 * @date           15.12.15
13
 */
14
15
namespace IPub\FormPhone\Controls;
16
17
use Nette;
18
use Nette\Forms;
19
use Nette\Localization;
20
use Nette\Utils;
21
22
use IPub;
23
use IPub\FormPhone;
24
use IPub\FormPhone\Exceptions;
25
26
use IPub\Phone\Phone as PhoneUtils;
27 1
28 1
use libphonenumber;
29
use libphonenumber\geocoding;
30
31
/**
32
 * Form phone control element
33
 *
34
 * @package        iPublikuj:FormPhone!
35
 * @subpackage     Controls
36
 *
37
 * @author         Adam Kadlec <[email protected]>
38
 *
39
 * @property-read string $emptyValue
40
 * @property-read Nette\Forms\Rules $rules
41
 */
42
class Phone extends Forms\Controls\TextInput
43 1
{
44
	/**
45
	 * Define filed attributes
46
	 */
47
	const FIELD_COUNTRY	= 'country';
48
	const FIELD_NUMBER	= 'number';
49
50
	/**
51
	 * @var IPub\Phone\Phone
52
	 */
53
	private $phoneUtils;
54
55
	/**
56
	 * List of allowed countries
57
	 *
58
	 * @var array
59
	 */
60
	private $allowedCountries = [];
61
62 1
	/**
63
	 * List of allowed phone types
64
	 *
65
	 * @var array
66
	 */
67
	private $allowedTypes = [];
68
69
	/**
70
	 * @var string|NULL
71
	 */
72
	private $number = NULL;
73
74
	/**
75
	 * @var string|NULL
76
	 */
77
	private $country = NULL;
78
79
	/**
80
	 * @var string
81
	 */
82
	private $defaultCountry;
83
84
	/**
85
	 * @var bool
86
	 */
87
	private static $registered = FALSE;
88
89 1
	/**
90
	 * @param PhoneUtils $phoneUtils
91
	 * @param string|NULL $label
92
	 * @param int|NULL $maxLength
93
	 */
94
	public function __construct(PhoneUtils $phoneUtils, $label = NULL, $maxLength = NULL)
95
	{
96 1
		parent::__construct($label, $maxLength);
97
98 1
		$this->phoneUtils = $phoneUtils;
99 1
	}
100
101
	/**
102
	 * @param array $countries
103
	 *
104
	 * @return $this
105
	 *
106
	 * @throws Exceptions\NoValidCountryException
107
	 */
108
	public function setAllowedCountries(array $countries = [])
109
	{
110 1
		$this->allowedCountries = [];
111
112 1
		foreach ($countries as $country) {
113 1
			$country = $this->validateCountry($country);
114 1
			$this->allowedCountries[] = strtoupper($country);
115 1
		}
116
117
		// Check for auto country detection
118 1
		if (in_array('AUTO', $this->allowedCountries)) {
119
			$this->allowedCountries = ['AUTO'];
120
		}
121
122
		// Remove duplicities
123 1
		array_unique($this->allowedCountries);
124
125 1
		return $this;
126
	}
127
128
	/**
129
	 * @param string $country
130
	 *
131
	 * @return $this
132
	 *
133 1
	 * @throws Exceptions\NoValidCountryException
134
	 */
135
	public function addAllowedCountry($country)
136
	{
137 1
		$country = $this->validateCountry($country);
138 1
		$this->allowedCountries[] = strtoupper($country);
139
140
		// Remove duplicities
141 1
		array_unique($this->allowedCountries);
142
143 1
		if (strtoupper($country) === 'AUTO') {
144
			$this->allowedCountries = ['AUTO'];
145
146 1
		} else if (($key = array_search('AUTO', $this->allowedCountries)) && $key !== FALSE) {
147
			unset($this->allowedCountries[$key]);
148
		}
149
150 1
		return $this;
151
	}
152
153
	/**
154
	 * @return array
155
	 */
156
	public function getAllowedCountries()
157
	{
158 1
		if (in_array('AUTO', $this->allowedCountries, TRUE) || $this->allowedCountries === []) {
159 1
			return $this->phoneUtils->getSupportedCountries();
160
161
		} else {
162 1
			return $this->allowedCountries;
163
		}
164
	}
165
166
	/**
167
	 * @param string|NULL $country
168
	 *
169
	 * @return $this
170
	 *
171
	 * @throws Exceptions\NoValidCountryException
172
	 */
173
	public function setDefaultCountry($country = NULL)
174
	{
175 1
		if ($country === NULL) {
176 1
			$this->defaultCountry = NULL;
177
178 1
		} else {
179 1
			$country = $this->validateCountry($country);
180
181 1
			$this->defaultCountry = strtoupper($country);
182
		}
183
184 1
		return $this;
185
	}
186
187
	/**
188
	 * @param array $types
189 1
	 *
190 1
	 * @return $this
191
	 *
192
	 * @throws Exceptions\NoValidTypeException
193
	 */
194
	public function setAllowedPhoneTypes(array $types = [])
195
	{
196
		$this->allowedTypes = [];
197
198
		foreach ($types as $type) {
199
			$type = $this->validateType($type);
200
			$this->allowedTypes[] = strtoupper($type);
201
		}
202
203
		// Remove duplicities
204
		array_unique($this->allowedTypes);
205
206
		return $this;
207
	}
208
209
	/**
210
	 * @param string $type
211
	 *
212
	 * @return $this
213
	 *
214
	 * @throws Exceptions\NoValidTypeException
215
	 */
216
	public function addAllowedPhoneType($type)
217
	{
218 1
		$type = $this->validateType($type);
219 1
		$this->allowedTypes[] = strtoupper($type);
220
221
		// Remove duplicities
222 1
		array_unique($this->allowedTypes);
223
224 1
		return $this;
225
	}
226
227
	/**
228
	 * @return array
229
	 */
230
	public function getAllowedPhoneTypes()
231
	{
232 1
		return $this->allowedTypes;
233
	}
234
235
	/**
236
	 * @param string
237
	 *
238
	 * @return $this
239
	 *
240
	 * @throws Exceptions\InvalidArgumentException
241
	 * @throws IPub\Phone\Exceptions\NoValidCountryException
242
	 * @throws IPub\Phone\Exceptions\NoValidPhoneException
243
	 */
244
	public function setValue($value)
245
	{
246 1
		if ($value === NULL) {
247 1
			$this->country = NULL;
248 1
			$this->number = NULL;
249
250 1
			return $this;
251
		}
252
253 1
		foreach ($this->getAllowedCountries() as $country) {
254 1
			if ($this->phoneUtils->isValid($value, $country)) {
255 1
				$phone = IPub\Phone\Entities\Phone::fromNumber($value, $country);
256
257 1
				$this->country = $phone->getCountry();
258 1
				$this->number = str_replace(' ', '', $phone->getNationalNumber());
259
260 1
				return $this;
261
			}
262 1
		}
263
264 1
		throw new Exceptions\InvalidArgumentException('Provided value is not valid phone number, or is out of list of allowed countries, "' . $value . '" given.');
265
	}
266
267
	/**
268
	 * @return IPub\Phone\Entities\Phone|NULL
269
	 */
270
	public function getValue()
271
	{
272 1
		if ($this->country === NULL || $this->number === NULL) {
273 1
			return NULL;
274
		}
275
276
		try {
277
			// Try to parse number & country
278 1
			$number = IPub\Phone\Entities\Phone::fromNumber($this->number, $this->country);
279
280 1
			return $number === NULL ? NULL : $number;
281
282 1
		} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) {
283
			return NULL;
284
285 1
		} catch (IPub\Phone\Exceptions\NoValidPhoneException $ex) {
286 1
			return NULL;
287
		}
288
	}
289
290
	/**
291
	 * Loads HTTP data
292
	 *
293
	 * @return void
294
	 */
295
	public function loadHttpData()
296
	{
297 1
		$country = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_COUNTRY . ']');
298 1
		$this->country = ($country === '' || $country === NULL) ? NULL : (string) $country;
299
300 1
		$number = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_NUMBER . ']');
301 1
		$this->number = ($number === '' || $number === NULL) ? NULL : (string) $number;
302 1
	}
303
304
	/**
305
	 * @return Utils\Html
306 1
	 */
307 1
	public function getControl()
308
	{
309 1
		return Utils\Html::el()
310 1
			->add($this->getControlPart(static::FIELD_COUNTRY) . $this->getControlPart(static::FIELD_NUMBER));
311
	}
312
313
	/**
314
	 * @param string $key
315
	 *
316
	 * @return Utils\Html
317
	 *
318
	 * @throws Exceptions\InvalidArgumentException
319
	 */
320
	public function getControlPart($key)
321
	{
322 1
		$name = $this->getHtmlName();
323
324
		// Try to get translator
325 1
		$translator = $this->getTranslator();
326
327 1
		if ($translator instanceof Localization\ITranslator && method_exists($translator, 'getLocale') === TRUE) {
328
			try {
329
				$locale = $translator->getLocale();
0 ignored issues
show
Bug introduced by
The method getLocale() does not seem to exist on object<Nette\Localization\ITranslator>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
330
331
			} catch (\Exception $ex) {
332
				$locale = 'en_US';
333
			}
334
335
		} else {
336 1
			$locale = 'en_US';
337
		}
338
339 1
		if ($key === static::FIELD_COUNTRY) {
340 1
			$control = Forms\Helpers::createSelectBox(
341 1
				array_reduce($this->getAllowedCountries(), function (array $result, $row) use ($locale) {
342 1
					$countryName = geocoding\Locale::getDisplayRegion(
343 1
						geocoding\Locale::countryCodeToLocale($row),
344
						$locale
345 1
					);
346
347 1
					$result[$row] = Utils\Html::el('option')
348 1
						->setText('+' . $this->phoneUtils->getCountryCodeForCountry($row) . ' (' . $countryName . ')')
349 1
						->addAttributes([
350 1
							'data-mask' => preg_replace('/[0-9]/', '9', $this->phoneUtils->getExampleNationalNumber($row)),
351 1
						])
352 1
						->value($row);
353
354 1
					return $result;
355 1
				}, []),
356
				[
357 1
					'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country,
358
				]
359 1
			);
360
361 1
			$control->addAttributes([
362 1
				'name' => $name . '[' . static::FIELD_COUNTRY . ']',
363 1
				'id'   => $this->getHtmlId() . '-' . static::FIELD_COUNTRY,
364
365 1
				'data-ipub-forms-phone' => '',
366 1
				'data-settings'         => json_encode([
367 1
					'field' => $name . '[' . static::FIELD_NUMBER . ']'
368 1
				])
369 1
			]);
370
371 1
			if ($this->isDisabled()) {
372
				$control->disabled(TRUE);
373
			}
374
375 1
			return $control;
376
377 1
		} else if ($key === static::FIELD_NUMBER) {
378 1
			$input = parent::getControl();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (getControl() instead of getControlPart()). Are you sure this is correct? If so, you might want to change this to $this->getControl().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
379
380 1
			$control = Utils\Html::el('input');
381
382 1
			$control->addAttributes([
383 1
				'name'  => $name . '[' . static::FIELD_NUMBER . ']',
384 1
				'id'    => $this->getHtmlId() . '-' . static::FIELD_NUMBER,
385 1
				'value' => $this->number,
386 1
				'type'  => 'text',
387
388 1
				'data-nette-empty-value' => Utils\Strings::trim($this->translate($this->emptyValue)),
389 1
				'data-nette-rules'       => $input->{'data-nette-rules'},
390 1
			]);
391
392 1
			if ($this->isDisabled()) {
393
				$control->disabled(TRUE);
394
			}
395
396 1
			return $control;
397
		}
398
399
		throw new Exceptions\InvalidArgumentException('Part ' . $key . ' does not exist.');
400
	}
401
402
	/**
403
	 * @return NULL
404
	 */
405
	public function getLabelPart()
406
	{
407 1
		return NULL;
408
	}
409
410
	/**
411
	 * @param string $country
412
	 *
413
	 * @return string
414
	 *
415
	 * @throws Exceptions\NoValidCountryException
416
	 */
417
	protected function validateCountry($country)
418
	{
419
		// Country code have to be upper-cased
420 1
		$country = strtoupper($country);
421
422 1
		if ((strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && in_array($country, $this->phoneUtils->getSupportedCountries())) || $country === 'AUTO') {
423 1
			return $country;
424
425
		} else {
426 1
			throw new Exceptions\NoValidCountryException('Provided country code "' . $country . '" is not valid. Provide valid country code or AUTO for automatic detection.');
427
		}
428
	}
429
430
	/**
431
	 * @param string $type
432
	 *
433
	 * @return string
434
	 *
435
	 * @throws Exceptions\NoValidTypeException
436
	 */
437
	protected function validateType($type)
438
	{
439
		// Phone type have to be upper-cased
440 1
		$type = strtoupper($type);
441
442 1
		if (defined('\IPub\Phone\Phone::TYPE_' . $type)) {
443 1
			return $type;
444
445
		} else {
446
			throw new Exceptions\NoValidTypeException('Provided phone type "' . $type . '" is not valid. Provide valid phone type.');
447
		}
448
	}
449
450
	/**
451
	 * @param PhoneUtils $phoneUtils
452
	 * @param string $method
453
	 */
454
	public static function register(PhoneUtils $phoneUtils, $method = 'addPhone')
455
	{
456
		// Check for multiple registration
457 1
		if (self::$registered) {
458 1
			throw new Nette\InvalidStateException('Phone control already registered.');
459
		}
460
461 1
		self::$registered = TRUE;
462
463 1
		$class = function_exists('get_called_class') ? get_called_class() : __CLASS__;
464 1
		Forms\Container::extensionMethod(
465 1
			$method, function (Forms\Container $form, $name, $label = NULL, $maxLength = NULL) use ($class, $phoneUtils) {
466 1
			$component = new $class($phoneUtils, $label, $maxLength);
467 1
			$form->addComponent($component, $name);
468
469 1
			return $component;
470
		}
471 1
		);
472 1
	}
473
}
474