Completed
Push — master ( e01736...ba6a43 )
by Adam
04:12
created

Phone::getControlPart()   C

Complexity

Conditions 9
Paths 10

Size

Total Lines 67
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 37
CRAP Score 9.3262

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 67
ccs 37
cts 44
cp 0.8409
rs 6.3449
cc 9
eloc 41
nc 10
nop 1
crap 9.3262

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
72
	 */
73
	private $number;
74
75
	/**
76
	 * @var string
77
	 */
78
	private $country;
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 setCountries(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 addCountry($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) AND $key !== FALSE) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
149
			unset($this->allowedCountries[$key]);
150
		}
151
152 1
		return $this;
153
	}
154
155
	/**
156
	 * @return array
157
	 */
158
	public function getCountries()
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 setPhoneTypes(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 addPhoneType($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 getPhoneTypes()
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->getCountries() 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 = $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 string|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
			if (!$this->phoneUtils->isValid($this->number, $this->country)) {
279 1
				return NULL;
280
			}
281
282 1
		} catch (IPub\Phone\Exceptions\NoValidCountryException $ex) {
283 1
			return NULL;
284
		}
285
286 1
		return $this->phoneUtils->format($this->number, $this->country, PhoneUtils::FORMAT_E164);
287
	}
288
289
	/**
290
	 * Loads HTTP data
291
	 *
292
	 * @return void
293
	 */
294
	public function loadHttpData()
295
	{
296 1
		$this->country = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_COUNTRY . ']');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getHttpData(\Nett...c::FIELD_COUNTRY . ']') can also be of type array or object<Nette\Http\FileUpload>. However, the property $country is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
297 1
		$this->number = $this->getHttpData(Forms\Form::DATA_LINE, '[' . static::FIELD_NUMBER . ']');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getHttpData(\Nett...ic::FIELD_NUMBER . ']') can also be of type array or object<Nette\Http\FileUpload>. However, the property $number is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
298 1
	}
299
300
	/**
301
	 * @return string
302
	 */
303
	public function getControl()
304
	{
305 1
		return $this->getControlPart(static::FIELD_COUNTRY) . $this->getControlPart(static::FIELD_NUMBER);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getControl...(static::FIELD_NUMBER); (string) is incompatible with the return type of the parent method Nette\Forms\Controls\TextInput::getControl of type Nette\Utils\Html.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
306 1
	}
307 1
308
	/**
309 1
	 * @param string
310
	 *
311
	 * @return Utils\Html
312
	 *
313
	 * @throws Exceptions\InvalidArgumentException
314
	 */
315
	public function getControlPart($key)
316
	{
317 1
		$name = $this->getHtmlName();
318
319 1
		if ($translator = $this->getTranslator() AND $translator instanceof Localization\ITranslator AND method_exists($translator, 'getLocale')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
320
			$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...
321
		} else {
322 1
			$locale = 'en_US';
323
		}
324
325 1
		if ($key === static::FIELD_COUNTRY) {
326 1
			$control = Forms\Helpers::createSelectBox(
327 1
				array_reduce($this->getCountries(), function (array $result, $row) use ($locale) {
328 1
					$countryName = geocoding\Locale::getDisplayRegion(
329 1
						geocoding\Locale::countryCodeToLocale($row),
330
						$locale
331 1
					);
332
333 1
					$result[$row] = Utils\Html::el('option')
334 1
						->setText('+' . $this->phoneUtils->getCountryCodeForCountry($row) . ' ('. $countryName . ')')
335 1
						->addAttributes([
336 1
							'data-mask' => preg_replace('/[0-9]/', '9', $this->phoneUtils->getExampleNationalNumber($row)),
337 1
						])
338 1
						->value($row);
339
340 1
					return $result;
341 1
				}, []),
342
				[
343 1
					'selected?' => $this->country === NULL ? $this->defaultCountry : $this->country,
344
				]
345 1
			);
346
347
			$control
348 1
				->name($name . '[' . static::FIELD_COUNTRY . ']')
349 1
				->id($this->getHtmlId() . '-' . static::FIELD_COUNTRY)
350 1
				->{'data-ipub-forms-phone'}('')
351 1
				->{'data-settings'}(json_encode([
352 1
					'field' => $name . '[' . static::FIELD_NUMBER . ']'
353 1
				]));
354
355 1
			if ($this->isDisabled()) {
356
				$control->disabled(TRUE);
357
			}
358
359 1
			return $control;
360
361 1
		} else if ($key === static::FIELD_NUMBER) {
362 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...
363
364 1
			$control = Utils\Html::el('input');
365
366
			$control
367 1
				->name($name . '[' . static::FIELD_NUMBER . ']')
368 1
				->id($this->getHtmlId() . '-' . static::FIELD_NUMBER)
369 1
				->value($this->number)
370 1
				->type('text')
371 1
				->{'data-nette-rules'}($input->attrs['data-nette-rules']);
372
373 1
			if ($this->isDisabled()) {
374
				$control->disabled(TRUE);
375
			}
376
377 1
			return $control;
378
		}
379
380
		throw new Exceptions\InvalidArgumentException('Part ' . $key . ' does not exist.');
381
	}
382
383
	/**
384
	 * @return NULL
385
	 */
386
	public function getLabelPart()
387
	{
388
		return NULL;
389
	}
390
391
	/**
392
	 * @param string $country
393
	 *
394
	 * @return string
395
	 *
396
	 * @throws Exceptions\NoValidCountryException
397
	 */
398
	protected function validateCountry($country)
399
	{
400
		// Country code have to be upper-cased
401 1
		$country = strtoupper($country);
402
403 1
		if ((strlen($country) === 2 && ctype_alpha($country) && ctype_upper($country) && in_array($country, $this->phoneUtils->getSupportedCountries())) || $country === 'AUTO') {
404 1
			return $country;
405
406
		} else {
407 1
			throw new Exceptions\NoValidCountryException('Provided country code "' . $country . '" is not valid. Provide valid country code or AUTO for automatic detection.');
408
		}
409
	}
410
411
	/**
412
	 * @param string $type
413
	 *
414
	 * @return string
415
	 *
416
	 * @throws Exceptions\NoValidTypeException
417
	 */
418
	protected function validateType($type)
419
	{
420
		// Phone type have to be upper-cased
421 1
		$type = strtoupper($type);
422
423 1
		if (defined('\IPub\Phone\Phone::TYPE_' . $type)) {
424 1
			return $type;
425
426
		} else {
427
			throw new Exceptions\NoValidTypeException('Provided phone type "' . $type . '" is not valid. Provide valid phone type.');
428
		}
429
	}
430
431
	/**
432
	 * @param PhoneUtils $phoneUtils
433
	 * @param string $method
434
	 */
435
	public static function register(PhoneUtils $phoneUtils, $method = 'addPhone')
436
	{
437
		// Check for multiple registration
438 1
		if (static::$registered) {
0 ignored issues
show
Bug introduced by
Since $registered is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $registered to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
439 1
			throw new Nette\InvalidStateException('Phone control already registered.');
440
		}
441
442 1
		static::$registered = TRUE;
0 ignored issues
show
Bug introduced by
Since $registered is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $registered to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
443
444 1
		$class = function_exists('get_called_class') ? get_called_class() : __CLASS__;
445 1
		Forms\Container::extensionMethod(
446 1
			$method, function (Forms\Container $form, $name, $label = NULL, $maxLength = NULL) use ($class, $phoneUtils) {
447 1
			$component = new $class($phoneUtils, $label, $maxLength);
448 1
			$form->addComponent($component, $name);
449
450 1
			return $component;
451
		}
452 1
		);
453 1
	}
454
}
455