Completed
Push — master ( ca80f9...047a7b )
by Adam
02:47
created

Phone   B

Complexity

Total Complexity 53

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 10

Test Coverage

Coverage 85.11%

Importance

Changes 14
Bugs 4 Features 1
Metric Value
wmc 53
c 14
b 4
f 1
lcom 3
cbo 10
dl 0
loc 421
ccs 120
cts 141
cp 0.8511
rs 7.4757

17 Methods

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

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\Application\UI;
19
use Nette\Bridges;
20
use Nette\Forms;
21
use Nette\Localization;
22
use Nette\Utils;
23
24
use Latte;
25
26
use IPub;
27 1
use IPub\FormPhone;
28 1
use IPub\FormPhone\Exceptions;
29
30
use IPub\Phone\Phone as PhoneUtils;
31
32
use libphonenumber;
33
use libphonenumber\geocoding;
34
35
/**
36
 * Form phone control element
37
 *
38
 * @package        iPublikuj:FormPhone!
39
 * @subpackage     Controls
40
 *
41
 * @author         Adam Kadlec <[email protected]>
42
 */
43
class Phone extends Forms\Controls\TextInput
44 1
{
45
	/**
46
	 * Define filed attributes
47
	 */
48
	const FIELD_COUNTRY = 'country';
49
	const FIELD_NUMBER = 'number';
50
51
	/**
52
	 * @var IPub\Phone\Phone
53
	 */
54
	private $phoneUtils;
55
56
	/**
57
	 * List of allowed countries
58
	 *
59
	 * @var array
60
	 */
61
	private $allowedCountries = [];
62 1
63
	/**
64
	 * List of allowed phone types
65
	 *
66
	 * @var array
67
	 */
68
	private $allowedTypes = [];
69
70
	/**
71
	 * @var string|NULL
72
	 */
73
	private $number = NULL;
74
75
	/**
76
	 * @var string|NULL
77
	 */
78
	private $country = NULL;
79
80
	/**
81
	 * @var string
82
	 */
83
	private $defaultCountry;
84
85
	/**
86
	 * @var bool
87
	 */
88
	private static $registered = FALSE;
89 1
90
	/**
91
	 * @param PhoneUtils $phoneUtils
92
	 * @param string|NULL $label
93
	 * @param int|NULL $maxLength
94
	 */
95
	public function __construct(PhoneUtils $phoneUtils, $label = NULL, $maxLength = NULL)
96
	{
97 1
		parent::__construct($label, $maxLength);
98
99 1
		$this->phoneUtils = $phoneUtils;
100 1
	}
101
102
	/**
103
	 * @param array $countries
104
	 *
105
	 * @return $this
106
	 *
107
	 * @throws Exceptions\NoValidCountryException
108
	 */
109
	public function setAllowedCountries(array $countries = [])
110
	{
111 1
		$this->allowedCountries = [];
112
113 1
		foreach($countries as $country)
114
		{
115 1
			$country = $this->validateCountry($country);
116 1
			$this->allowedCountries[] = strtoupper($country);
117 1
		}
118
119
		// Check for auto country detection
120 1
		if (in_array('AUTO', $this->allowedCountries)) {
121
			$this->allowedCountries = ['AUTO'];
122
		}
123
124
		// Remove duplicities
125 1
		array_unique($this->allowedCountries);
126
127 1
		return $this;
128
	}
129
130
	/**
131
	 * @param string $country
132
	 *
133 1
	 * @return $this
134
	 *
135
	 * @throws Exceptions\NoValidCountryException
136
	 */
137
	public function addAllowedCountry($country)
138
	{
139 1
		$country = $this->validateCountry($country);
140 1
		$this->allowedCountries[] = strtoupper($country);
141
142
		// Remove duplicities
143 1
		array_unique($this->allowedCountries);
144
145 1
		if (strtoupper($country) === 'AUTO') {
146
			$this->allowedCountries = ['AUTO'];
147
148 1
		} else if (($key = array_search('AUTO', $this->allowedCountries)) && $key !== FALSE) {
149
			unset($this->allowedCountries[$key]);
150
		}
151
152 1
		return $this;
153
	}
154
155
	/**
156
	 * @return array
157
	 */
158
	public function getAllowedCountries()
159
	{
160 1
		if (in_array('AUTO', $this->allowedCountries, TRUE) || $this->allowedCountries === []) {
161 1
			return $this->phoneUtils->getSupportedCountries();
162
163
		} else {
164 1
			return $this->allowedCountries;
165
		}
166
	}
167
168
	/**
169
	 * @param string|NULL $country
170
	 *
171
	 * @return $this
172
	 *
173
	 * @throws Exceptions\NoValidCountryException
174
	 */
175
	public function setDefaultCountry($country = NULL)
176
	{
177 1
		if ($country === NULL) {
178 1
			$this->defaultCountry = NULL;
179
180 1
		} else {
181 1
			$country = $this->validateCountry($country);
182
183 1
			$this->defaultCountry = strtoupper($country);
184 1
		}
185
186 1
		return $this;
187
	}
188
189 1
	/**
190 1
	 * @param array $types
191
	 *
192
	 * @return $this
193
	 *
194
	 * @throws Exceptions\NoValidTypeException
195
	 */
196
	public function setAllowedPhoneTypes(array $types = [])
197
	{
198
		$this->allowedTypes = [];
199
200
		foreach($types as $type)
201
		{
202
			$type = $this->validateType($type);
203
			$this->allowedTypes[] = strtoupper($type);
204
		}
205
206
		// Remove duplicities
207
		array_unique($this->allowedTypes);
208
209
		return $this;
210
	}
211
212
	/**
213
	 * @param string $type
214
	 *
215
	 * @return $this
216
	 *
217
	 * @throws Exceptions\NoValidTypeException
218
	 */
219
	public function addAllowedPhoneType($type)
220
	{
221 1
		$type = $this->validateType($type);
222 1
		$this->allowedTypes[] = strtoupper($type);
223
224
		// Remove duplicities
225 1
		array_unique($this->allowedTypes);
226
227 1
		return $this;
228
	}
229
230
	/**
231
	 * @return array
232
	 */
233
	public function getAllowedPhoneTypes()
234
	{
235 1
		return $this->allowedTypes;
236
	}
237
238
	/**
239
	 * @param string
240
	 *
241
	 * @return $this
242
	 *
243
	 * @throws Exceptions\InvalidArgumentException
244
	 */
245
	public function setValue($value)
246
	{
247 1
		if ($value === NULL) {
248 1
			$this->country = NULL;
249 1
			$this->number = NULL;
250
251 1
			return $this;
252
		}
253
254 1
		foreach($this->getAllowedCountries() as $country) {
255 1
			if ($this->phoneUtils->isValid($value, $country)) {
256 1
				$phone = $this->phoneUtils->parse($value, $country);
257
258 1
				$this->country = $phone->getCountry();
259 1
				$this->number = str_replace(' ', '', $phone->getNationalNumber());
260
261 1
				return $this;
262
			}
263 1
		}
264
265 1
		throw new Exceptions\InvalidArgumentException('Provided value is not valid phone number, or is out of list of allowed countries, "' . $value . '" given.');
266
	}
267
268
	/**
269
	 * @return IPub\Phone\Entities\Phone|NULL
270
	 */
271
	public function getValue()
272
	{
273 1
		if ($this->country === NULL || $this->number === NULL) {
274 1
			return NULL;
275
		}
276
277
		try {
278 1
			// Try to parse number & country
279 1
			$number = $this->phoneUtils->parse($this->number, $this->country);
280
281
			return $number === NULL ? NULL : $number;
282 1
283 1
		} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) {
284
			return NULL;
285
286 1
		} catch (IPub\Phone\Exceptions\NoValidPhoneException $ex) {
287
			return NULL;
288
		}
289
	}
290
291
	/**
292
	 * Loads HTTP data
293
	 *
294
	 * @return void
295
	 */
296 1
	public function loadHttpData()
297 1
	{
298 1
		$country = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_COUNTRY . ']');
299
		$this->country = ($country === '' || $country === NULL) ? NULL : (string) $country;
300
301
		$number = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_NUMBER . ']');
302
		$this->number = ($number === '' || $number === NULL) ? NULL : (string) $number;
303
	}
304
305 1
	/**
306 1
	 * @return Utils\Html
307 1
	 */
308
	public function getControl()
309 1
	{
310
		return Utils\Html::el()
311
			->add($this->getControlPart(static::FIELD_COUNTRY) . $this->getControlPart(static::FIELD_NUMBER));
312
	}
313
314
	/**
315
	 * @param string
316
	 *
317 1
	 * @return Utils\Html
318
	 *
319 1
	 * @throws Exceptions\InvalidArgumentException
320
	 */
321
	public function getControlPart($key)
322 1
	{
323
		$name = $this->getHtmlName();
324
325 1
		// Try to get translator
326 1
		$translator = $this->getTranslator();
327 1
328 1
		if ($translator instanceof Localization\ITranslator && method_exists($translator, 'getLocale') === TRUE) {
329 1
			$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
		} else {
331 1
			$locale = 'en_US';
332
		}
333 1
334 1
		if ($key === static::FIELD_COUNTRY) {
335 1
			$control = Forms\Helpers::createSelectBox(
336 1
				array_reduce($this->getAllowedCountries(), function (array $result, $row) use ($locale) {
337 1
					$countryName = geocoding\Locale::getDisplayRegion(
338 1
						geocoding\Locale::countryCodeToLocale($row),
339
						$locale
340 1
					);
341 1
342
					$result[$row] = Utils\Html::el('option')
343 1
						->setText('+' . $this->phoneUtils->getCountryCodeForCountry($row) . ' ('. $countryName . ')')
344
						->addAttributes([
345 1
							'data-mask' => preg_replace('/[0-9]/', '9', $this->phoneUtils->getExampleNationalNumber($row)),
346
						])
347
						->value($row);
348 1
349 1
					return $result;
350 1
				}, []),
351 1
				[
352 1
					'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country,
353 1
				]
354
			);
355 1
356
			$control
357
				->name($name . '[' . static::FIELD_COUNTRY . ']')
358
				->id($this->getHtmlId() . '-' . static::FIELD_COUNTRY)
359 1
				->{'data-ipub-forms-phone'}('')
360
				->{'data-settings'}(json_encode([
361 1
					'field' => $name . '[' . static::FIELD_NUMBER . ']'
362 1
				]));
363
364 1
			if ($this->isDisabled()) {
365
				$control->disabled(TRUE);
366
			}
367 1
368 1
			return $control;
369 1
370 1
		} else if ($key === static::FIELD_NUMBER) {
371 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...
372
373 1
			$control = Utils\Html::el('input');
374
375
			$control
376
				->name($name . '[' . static::FIELD_NUMBER . ']')
377 1
				->id($this->getHtmlId() . '-' . static::FIELD_NUMBER)
378
				->value($this->number)
379
				->type('text')
380
				->{'data-nette-rules'}($input->attrs['data-nette-rules']);
381
382
			if ($this->isDisabled()) {
383
				$control->disabled(TRUE);
384
			}
385
386
			return $control;
387
		}
388
389
		throw new Exceptions\InvalidArgumentException('Part ' . $key . ' does not exist.');
390
	}
391
392
	/**
393
	 * @return NULL
394
	 */
395
	public function getLabelPart()
396
	{
397
		return NULL;
398
	}
399
400
	/**
401 1
	 * @param string $country
402
	 *
403 1
	 * @return string
404 1
	 *
405
	 * @throws Exceptions\NoValidCountryException
406
	 */
407 1
	protected function validateCountry($country)
408
	{
409
		// Country code have to be upper-cased
410
		$country = strtoupper($country);
411
412
		if ((strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && in_array($country, $this->phoneUtils->getSupportedCountries())) || $country === 'AUTO') {
413
			return $country;
414
415
		} else {
416
			throw new Exceptions\NoValidCountryException('Provided country code "' . $country . '" is not valid. Provide valid country code or AUTO for automatic detection.');
417
		}
418
	}
419
420
	/**
421 1
	 * @param string $type
422
	 *
423 1
	 * @return string
424 1
	 *
425
	 * @throws Exceptions\NoValidTypeException
426
	 */
427
	protected function validateType($type)
428
	{
429
		// Phone type have to be upper-cased
430
		$type = strtoupper($type);
431
432
		if (defined('\IPub\Phone\Phone::TYPE_' . $type)) {
433
			return $type;
434
435
		} else {
436
			throw new Exceptions\NoValidTypeException('Provided phone type "' . $type . '" is not valid. Provide valid phone type.');
437
		}
438 1
	}
439 1
440
	/**
441
	 * @param PhoneUtils $phoneUtils
442 1
	 * @param string $method
443
	 */
444 1
	public static function register(PhoneUtils $phoneUtils, $method = 'addPhone')
445 1
	{
446 1
		// Check for multiple registration
447 1
		if (self::$registered) {
448 1
			throw new Nette\InvalidStateException('Phone control already registered.');
449
		}
450 1
451
		self::$registered = TRUE;
452 1
453 1
		$class = function_exists('get_called_class') ? get_called_class() : __CLASS__;
454
		Forms\Container::extensionMethod(
455 1
			$method, function (Forms\Container $form, $name, $label = NULL, $maxLength = NULL) use ($class, $phoneUtils) {
456
			$component = new $class($phoneUtils, $label, $maxLength);
457
			$form->addComponent($component, $name);
458
459
			return $component;
460
		}
461
		);
462
	}
463
}
464