Теперь, когда PHP 7 уже давно вышел с интересными функциями, такими как обработка ошибок, оператор объединения нулей, объявления скалярных типов и т. Д., Мы часто слышим, как люди по-прежнему придерживаются PHP 5, говоря, что у него слабая система типирования, и что все быстро стать непредсказуемым.
Хотя это отчасти верно, PHP позволяет вам контролировать свое приложение, когда вы знаете, что делаете. Давайте посмотрим несколько примеров кода, чтобы проиллюстрировать это:
function plusone($a)
{
return $a + 1;
}
var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));
// output
int(2)
int(2)
int(2)
Наша функция будет увеличивать число, переданное в качестве аргумента, на единицу. Тем не менее, второй и третий вызовы передают строку, и функция по-прежнему возвращает целочисленные значения. Это называется преобразованием строк . Мы можем убедиться, что пользователь проходит числовое значение через проверку.
function plusone($a)
{
if ( !is_numeric($a) )
{
throw new InvalidArgumentException("I can only increment numbers!", 1);
}
return $a + 1;
}
Это вызовет InvalidArgumentException
Если мы указываем желаемый тип в прототипе функции…
function plusone(int $a)
{
return $a + 1;
}
var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));
// output
PHP Catchable fatal error: Argument 1 passed to plusone() must be an instance of int, integer given, called in /vagrant/test_at/test.php on line 7 and defined in /vagrant/test_at/test.php on line 2
Эта ошибка поначалу кажется немного странной, потому что первый вызов нашей функции использует целое число!
Если мы внимательно прочитаем сообщение, то увидим, что в сообщении об ошибке указано «должен быть экземпляр типа int» — предполагается, что целое число является классом, поскольку PHP до версии 7 поддерживал только подсказки типов классов!
Вещи становятся еще более неловкими с возвращаемыми аргументами функций в PHP 5. Короче говоря, мы не можем автоматически блокировать их типы, и мы должны проверить ожидаемое значение после того, как вызов функции вернет значение.
Расширенные типы
Перед выпуском PHP 7 команда Box предложила отличную идею для решения проблемы безопасности при печати в своем приложении PHP 5. После использования утверждений, подсказок и т. Д. Они решили поработать над более чистым решением этой проблемы.
Мы видели, как Facebook продвинул PHP немного вперед, запустив HHVM и Hack , но команда Box не хотела разветвлять исходный код PHP или что-либо изменять в ядре. Их решение состояло в том, чтобы создать отдельное расширение, называемое расширенными типами, для анализа типов phpDoc и assert метода во время выполнения.
Установка
Приведенное ниже расширение предназначено для использования с PHP 5 — если вы используете PHP 7, просто расслабьтесь и наслаждайтесь поездкой!
Процесс установки не очень сложен и не требует какой-либо конкретной настройки. Приведенные ниже инструкции относятся к Ubuntu, но легко применимы и к другим * nix-системам.
# update system
sudo apt-get update
# install required dependencies
sudo apt-get install php5-dev bison flex
# clone the repo
git clone [email protected]:box/augmented_types.git
# install extension
phpize
./configure --enable-augmented_types
make
make test
sudo make install
Мы должны указать PHP загрузить наше расширение, отредактировав файл php.ini
# Get php.ini location from PHP info. This will print the cli configuration file, you may need to edit /etc/php5/fpm/php.ini )
php -i | grep 'Loaded Configuration File'
# output: /etc/php5/cli/php.ini
vim /etc/php5/cli/php.ini
# Get `extension_dir` the PHP info
php -i | grep extension_dir
# output: extension_dir => /usr/lib/php5/20100525 => /usr/lib/php5/20100525
# Add this line at the end of the file
zend_extension=/usr/lib/php5/20100525/augmented_types.so
Расширение можно включить для каждого файла с помощью функции ini_set
ini_set("augmented_types.enforce_by_default",1);
Или непосредственно в файле конфигурации php.ini
Обязательно ознакомьтесь с документацией для получения более подробной информации о процессе установки.
использование
Ранее мы упоминали, что расширение использует phpDoc для прототипов функций / методов. Большая часть его функциональности может быть объяснена с помощью примеров кода.
/**
* Add one
*
* @param int $a
* @return int
*/
function plusone($a)
{
return $a + 1;
}
var_dump(plusone(1));
var_dump(plusone("1"));
var_dump(plusone("1 apple"));
Вы можете подумать, что сможете угадать вывод приведенного выше кода, но вы, вероятно, ошибаетесь!
int(2)
PHP Fatal error: Wrong type encountered for argument 1 of function plusone, was expecting a integer but got a (string) '1' in /vagrant/test_at/test.php on line 15
Даже второй звонок не проходит! Это происходит потому, что мы не выполняем преобразование PHP, о котором мы говорили ранее, функция требует от нас строго передавать целое число. А как насчет того, чтобы вызывать его с помощью float?
var_dump(plusone(1.5));
PHP Fatal error: Wrong type encountered for argument 1 of function plusone, was expecting a integer but got a (float) 1.500000 in /vagrant/test_at/test.php on line 14
Мы заставили нашу функцию принимать два типа аргументов (int и float), используя определение составных типов .
/**
* Add one
*
* @param int|float $a
* @return int
*/
function plusone($a)
{
return $a + 1;
}
var_dump(plusone(1));
var_dump(plusone(1.5));
Теперь наша функция должна работать как положено.
int(2)
PHP Fatal error: Wrong type returned by function plusone, was expecting a integer but got a (float) 2.500000 in /vagrant/test_at/test.php on line 0
К сожалению! Мы также должны определить тип возвращаемого значения для нашей функции или привести приведенное значение в соответствие с phpDoc.
/**
* Add one
*
* @param int|float $a
* @return int|float
*/
function plusone($a)
{
return $a + 1;
}
Мы также можем работать с составными типами: в следующем примере суммируются элементы массива.
/**
* Calculate sum
*
* @param array $nums
* @return int
*/
function sum($nums)
{
$sum = 0;
foreach ($nums as $value) {
$sum += $value;
}
return $sum;
}
var_dump(sum([10, 12, 76]));
// output
int(98)
Функция возвращает ожидаемое значение. Что если массив содержит что-то кроме целых чисел?
var_dump(sum([10, 12, "something"]));
// output
int(22)
Расширение дает нам возможность работать с типами массивов . Предыдущий пример выдаст фатальную ошибку, как и ожидалось.
/**
* Calculate sum
*
* @param int[] $nums
* @return int
*/
function sum($nums)
{
$sum = 0;
foreach ($nums as $value) {
$sum += $value;
}
return $sum;
}
var_dump(sum([10, 12, 76]));
var_dump(sum([10, 12, "something"]));
int(98)
PHP Fatal error: Wrong type encountered for argument 1 of function sum, was expecting a (integer)[] but got a array in /vagrant/test_at/test.php on line 20
Интересный случай, когда у нас есть функция, которая принимает произвольное количество аргументов. Мы можем заставить нашу предыдущую функцию возвращать сумму всех переданных аргументов.
/**
* Calculate sum
*
* @param *int $nums
* @return int
*/
function sum($nums)
{
$args = func_get_args();
$sum = 0;
foreach ($args as $value) {
$sum += $value;
}
return $sum;
}
var_dump(sum(10, 12, 76));
Определение типа *int
Мы можем объединить определения типов *int
int[]
/**
* Calculate sum
*
* @param *int|int[] $nums
* @return int
*/
function sum($nums)
{
if ( !is_array($nums) )
{
$nums = func_get_args();
}
$sum = 0;
foreach ($nums as $value) {
$sum += $value;
}
return $sum;
}
var_dump(sum(10, 12, 76));
var_dump(sum([10, 12, 76]));
Теперь оба вызова функций вернут одно и то же значение ( int(98)
Аргументы по умолчанию
В большинстве случаев функции содержат необязательные аргументы, которые могут быть инициализированы с использованием значений по умолчанию. Мы используем определение типа void
/**
* SSH to server.
*
* @param string $url
* @param int|void $port
* @return bool
*/
function ssh($url, $port = 2222)
{
return true;
}
Примечание . Если аргумент имеет значение по умолчанию, он не будет применять указанный тип. Это означает, что передача строки в аргументе порта не приведет к ошибке.
Типы возврата
То же самое, что мы говорили ранее о типах аргументов, применимо к определениям возвращаемых типов. Мы можем указать нормальные скалярные типы, классы, составные типы и типы массивов. Каждая функция должна иметь определение @return <type>
должна использовать @return void
class User
{
protected $id;
protected $name;
/**
* Constructor
* @param int $id
* @param string $name
* @return void
*/
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
/**
* Return the user info as a string.
*
* @return string
*/
public function __toString()
{
return $this->name;
}
}
Игнорирование файлов
В большинстве случаев наше приложение содержит пакеты других людей, и мы не хотим, чтобы расширение дополненных типов генерировало ошибки и останавливало наше приложение из-за пропущенных определений. К счастью, расширение обеспечивает чистый способ для внесения в черный и белый список файлов и папок.
augmented_types_blacklist([__DIR__."/vendor"]);
Расширение теперь будет игнорировать наш каталог поставщиков. Если у нас есть некоторые из наших собственных проверяемых каталогов внутри каталога vendor, мы можем использовать функцию белого списка, чтобы добавить их обратно. Вы можете прочитать больше об этом в документации .
augmented_types_whitelist([__DIR__."/vendor/mylib"]);
Мы также можем достичь того же результата, используя файл php.ini
# php.ini
augmented_types.whitelist=./vendor
augmented_types.blacklist=./vendor/mylib
PHP 7 Изменения
В PHP 7 введены объявления скалярных типов и возвращаемые типы для функций / методов, что исключает необходимость дальнейшей проверки аргументов и возвращаемых типов. Проверьте список изменений на официальном сайте PHP.
Если PHP 7 уже поддерживает это, что может быть полезным при установке нового расширения?
Ну, расширение предоставляет широкий спектр опций, которых нет в PHP 7, например:
- Составные типы (
@param string|int $var
- Типы массивов (
@param int[] $var
- Принуждение функций к возврату значения (по крайней мере,
void
Вывод
Это всегда хорошая идея, чтобы перейти на PHP 7 и воспользоваться всеми новыми замечательными функциями. Однако мы не всегда можем позволить себе роскошь использовать передовые технологии в наших проектах.
Расширение расширенных типов абсолютно замедляет работу приложения (как и следовало ожидать), поэтому его следует использовать только во время разработки и тестирования, чтобы поддерживать производственный сервер в чистоте и быстро.
Что вы думаете о расширении? Это полезно? У вас есть другие идеи о том, как это сделать? Нам любопытно узнать, что вы думаете, дайте нам знать!