int
, string
and float
."123"
to a function that wants an int
, and trust PHP, which will do everything “right”. So why then do we need scalar types? int
, float
, string
and bool
. They will join already existing array
, callable
, classes, and interfaces. Let's complete our previous example with the new feature: interface AddressInterface { public function getStreet() : string; public function getCity() : string; public function getState() : string; public function getZip() : string; }
class EmptyAddress implements AddressInterface { public function getStreet() : string { return ''; } public function getCity() : string { return ''; } public function getState() : string { return ''; } public function getZip() : string { return ''; } }
class Address implements AddressInterface { protected $street; protected $city; protected $state; protected $zip; public function __construct(string $street, string $city, string $state, string $zip) { $this->street = $street; $this->city = $city; $this->state = $state; $this->zip = $zip; } public function getStreet() : string { return $this->street; } public function getCity() : string { return $this->city; } public function getState() : string { return $this->state; } public function getZip() : string { return $this->zip; } }
class Employee { protected $id; protected $address; public function __construct(int $id, AddressInterface $address) { $this->id = $id; $this->address = $address; } public function getId() : int { return $this->id; } public function getAddress() : AddressInterface { return $this->address; } }
class EmployeeRepository { private $data = []; public function __construct() { $this->data[123] = new Employee(123, new Address('123 Main St.', 'Chicago', 'IL', '60614')); $this->data[456] = new Employee(456, new Address('45 Hull St', 'Boston', 'MA', '02113')); } public function findById(int $id) : Employee { if (!isset($this->data[$id])) { throw new InvalidArgumentException('No such Employee: ' . $id); } return $this->data[$id]; } }
$r = new EmployeeRepository(); try { print $r->findById(123)->getAddress()->getStreet() . PHP_EOL; print $r->findById(789)->getAddress()->getStreet() . PHP_EOL; } catch (InvalidArgumentException $e) { print $e->getMessage() . PHP_EOL; }
Address
class are just strings. Previously, one could only assume that they were lines, not Street
objects (consisting of the street number, its name and apartment number) or the city ID from the database. Of course, both of these things are perfectly reasonable in certain circumstances, but they are not considered in this article.findById()
$id
is a value of type int
. Even if it originally came from user input, it will become integer. This means that it cannot contain, for example, SQL injections. Reliance on type checking when working with user input is not the only or even the best defense against attack, but another layer of protection.Address
consists of strings and the employee ID is integer, right? It's true; However, not everyone adheres to fanaticism in the matter of documenting their code, or simply forget to update it. With the "active" information from the language itself, you are guaranteed to know that there is no desynchronization, because PHP will throw an exception if it is not. function loadUser(int $id) : User { return new User($id); }
function findPostsForUser(int $uid) : array { // Obviously something more robust. return [new Post(), new Post()]; }
$u = loadUser(123); $posts = findPostsForUser($u);
loadUser()
always returns an object of type User
, and findPostsForUser()
always returns an integer, there is no way to make this code true. This can be said only by looking at the functions and how to use them. And this, in turn, means that the IDE also knows in advance and can warn us about an error before launch. And since the IDE can track a lot more parts than we do, it can also warn of more errors than you can see for yourself ... without executing the code!int
to a function that expects a string
will work fine, and passing bool
at the expected int
you will get an integer number 0 or 1, because this is natural behavior expected from the language. For an object passed to a function that is expecting a string
, __toString()
will be called, the same will happen with the returned values.int
or float
. Traditionally, when a function expects to get int
/ float
values, and a string
is passed, PHP will simply truncate the string to the first non-numeric character, resulting in possible data loss. In the case of scalar types, the parameter will work fine if the string is indeed numeric, but if the value is truncated, this will result in a call to E_NOTICE
. Everything will work, but at the moment this situation is considered as a minor error in the condition.strict_types
mode. Its use is somewhat subtle and not obvious, but with proper understanding the developer gets an incredibly powerful tool. declare(strict_types=1);
strict_types
works, let's split our code into several separate files and strict_types
it a bit: // EmployeeRespository.php class EmployeeRepository { private $data = []; public function __construct() { $this->data[123] = new Employee(123, new Address('123 Main St.', 'Chicago', 'IL', '60614')); $this->data[456] = new Employee(456, new Address('45 Hull St', 'Boston', 'MA', 02113)); } public function findById(int $id) : Employee { if (!isset($this->data[$id])) { throw new InvalidArgumentException('No such Employee: ' . $id); } return $this->data[$id]; } }
// index.php $r = new EmployeeRepository(); try { $employee_id = get_from_request_query('employee_id'); print $r->findById($employee_id)->getAddress()->getStreet() . PHP_EOL; } catch (InvalidArgumentException $e) { print $e->getMessage() . PHP_EOL; }
$id
inside findById()
is an int
, not a string. No matter what $employee_id
will be, $id
will always take an int
type, even if E_NOTICE
is thrown. If we add the strict_type
declaration to EmployeeRepository.php
, nothing will happen. We will also all have an int
inside findById()
.index.php
, and then use the findById()
call, if $employee_id
is a string, or a float
or something other than int
, this will result in a TypeError
.EntityRespository
constructor where we create our fake data. The second entry sends the ZIP code as an int, not a string. Most of the time it will not matter. However, in the US, postal codes in the northeast begin with the leading zero. If your int
starts with a leading zero, PHP will interpret this as an octal number, that is, a number with a base of 8.EntityRespository.php
to strict mode and immediately catch the type mismatch. If we run the code, we get quite specific errors that will tell us the exact lines where to find them. And good utilities (or IDE) can catch them even before launch!int
to float
. This is safe (except for extremely large or small values ​​when there are consequences of overflow) and logical, since this int
by definition also a floating-point value.int
, then you know (even if you don’t know the IDE) that a numeric string will always be returned from it and, therefore, you can be sure that there is no data loss when passing them to a function that expects an int
.EmployeeRespository
, Employee
, Address
, AddressInterface
and EmptyAddress
should have strict mode enabled. index.php
, on the get_from_request_query()
, interacts with incoming requests (via a call to get_from_request_query()
), and thus it will probably be easier for PHP to deal with types rather than manually manually. As soon as the undefined values ​​from the request are passed to the typed function, then it is possible to switch to the strictly typed work.Source: https://habr.com/ru/post/267799/
All Articles