Можем ли мы иметь статические типы в PHP без PHP 7 или HHVM?
Декабрь 27, 2018
Теперь, когда PHP 7 уже давно вышел с интересными функциями, такими как обработка ошибок, оператор объединения нулей, объявления скалярных типов и т. Д., Мы часто слышим, как люди по-прежнему придерживаются PHP 5, говоря, что у него слабая система типирования, и что все быстро стать непредсказуемым.
Хотя это отчасти верно, PHP позволяет вам контролировать свое приложение, когда вы знаете, что делаете. Давайте посмотрим несколько примеров кода, чтобы проиллюстрировать это:
Наша функция будет увеличивать число, переданное в качестве аргумента, на единицу. Тем не менее, второй и третий вызовы передают строку, и функция по-прежнему возвращает целочисленные значения. Это называется преобразованием строк . Мы можем убедиться, что пользователь проходит числовое значение через проверку.
functionplusone($a){if(!is_numeric($a)){thrownewInvalidArgumentException("I can only increment numbers!",1);}return$a+1;}
Это вызовет InvalidArgumentException Если мы указываем желаемый тип в прототипе функции…
// 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-системам.
Мы должны указать 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.inivim /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 filezend_extension=/usr/lib/php5/20100525/augmented_types.so
Расширение можно включить для каждого файла с помощью функции ini_set
ini_set("augmented_types.enforce_by_default",1);
Или непосредственно в файле конфигурации php.ini Обязательно ознакомьтесь с документацией для получения более подробной информации о процессе установки.
использование
Ранее мы упоминали, что расширение использует phpDoc для прототипов функций / методов. Большая часть его функциональности может быть объяснена с помощью примеров кода.
/**
* Add one
*
* @paramint $a
* @return int
*/functionplusone($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
*
* @paramint|float $a
* @return int
*/functionplusone($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.
Расширение дает нам возможность работать с типами массивов . Предыдущий пример выдаст фатальную ошибку, как и ожидалось.
/**
* Calculate sum
*
* @paramint[] $nums
* @return int
*/functionsum($nums){$sum=0;foreach($numsas$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
*/functionsum($nums){$args=func_get_args();$sum=0;foreach($argsas$value){$sum+=$value;}return$sum;}var_dump(sum(10,12,76));
Определение типа *int Мы можем объединить определения типов *intint[]
/**
* Calculate sum
*
* @param *int|int[] $nums
* @return int
*/functionsum($nums){if(!is_array($nums)){$nums=func_get_args();}$sum=0;foreach($numsas$value){$sum+=$value;}return$sum;}var_dump(sum(10,12,76));var_dump(sum([10,12,76]));
Теперь оба вызова функций вернут одно и то же значение ( int(98)
Аргументы по умолчанию
В большинстве случаев функции содержат необязательные аргументы, которые могут быть инициализированы с использованием значений по умолчанию. Мы используем определение типа void
Примечание . Если аргумент имеет значение по умолчанию, он не будет применять указанный тип. Это означает, что передача строки в аргументе порта не приведет к ошибке.
Типы возврата
То же самое, что мы говорили ранее о типах аргументов, применимо к определениям возвращаемых типов. Мы можем указать нормальные скалярные типы, классы, составные типы и типы массивов. Каждая функция должна иметь определение @return <type>должна использовать @return void
classUser{protected$id;protected$name;/**
* Constructor
* @paramint $id
* @paramstring $name
* @return void
*/publicfunction__construct($id,$name){$this->id=$id;$this->name=$name;}/**
* Return the user info as a string.
*
* @return string
*/publicfunction__toString(){return$this->name;}}
Игнорирование файлов
В большинстве случаев наше приложение содержит пакеты других людей, и мы не хотим, чтобы расширение дополненных типов генерировало ошибки и останавливало наше приложение из-за пропущенных определений. К счастью, расширение обеспечивает чистый способ для внесения в черный и белый список файлов и папок.
augmented_types_blacklist([__DIR__."/vendor"]);
Расширение теперь будет игнорировать наш каталог поставщиков. Если у нас есть некоторые из наших собственных проверяемых каталогов внутри каталога vendor, мы можем использовать функцию белого списка, чтобы добавить их обратно. Вы можете прочитать больше об этом в документации .
В PHP 7 введены объявления скалярных типов и возвращаемые типы для функций / методов, что исключает необходимость дальнейшей проверки аргументов и возвращаемых типов. Проверьте список изменений на официальном сайте PHP.
Если PHP 7 уже поддерживает это, что может быть полезным при установке нового расширения?
Ну, расширение предоставляет широкий спектр опций, которых нет в PHP 7, например:
Составные типы ( @param string|int $var
Типы массивов ( @param int[] $var
Принуждение функций к возврату значения (по крайней мере, void
Вывод
Это всегда хорошая идея, чтобы перейти на PHP 7 и воспользоваться всеми новыми замечательными функциями. Однако мы не всегда можем позволить себе роскошь использовать передовые технологии в наших проектах.
Расширение расширенных типов абсолютно замедляет работу приложения (как и следовало ожидать), поэтому его следует использовать только во время разработки и тестирования, чтобы поддерживать производственный сервер в чистоте и быстро.
Что вы думаете о расширении? Это полезно? У вас есть другие идеи о том, как это сделать? Нам любопытно узнать, что вы думаете, дайте нам знать!