PHP → Контроль скалярных типов в PHP 5
[Обновлено]: Внесены некоторые изменения в код. Спасибо ithilion и LoneCat
Все уже знают что в PHP 5 в аргументах функций можно указывать их тип, за исключением... скалярных типов, т.е.: integer, string, boolean, float, и т.д.
Однако на странице мануала о контроле типов, в комментариях, Daniel L. Wood приводит достаточно интересное решение этой проблемы с помощью класса-обработчика ошибок. Единственный существенный недостаток этого решения — это его производительность.
Ниже я попытаюсь рассказать, как можно оптимизировать это решение, а также стоит ли им пользоваться, в принципе, в продакшн релизах.
Итак, разберем чем грешит приведенное решение:
- Абсолютно ненужные вызовы debug_backtrace. В принципе, для решения задачи достаточно разбора сообщения об ошибке. Явная проверка аргументов попахивает паранойей. Действительно, если мы поймали сообщение вида «Argument N passed to Class::function () must be an instance of string, string given, ...» — это уже дает нам все основания сделать нужный выбор. Обратите внимание ...string, string... В случае ошибки будет, например, ...integer, string... Этого достаточно, чтобы определить, является ли данное сообщение ошибкой на самом деле или нет.
- В нем есть опечатка в массиве типов. 'resrouce' => 'is_resource'.
- Несколько неоптимальный код в некоторых местах.
Мы попробуем решить все эти проблемы переписав класс следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Typehint { private static $_types = array( 'boolean,' => 'boolean', 'bool,' => 'boolean', 'integer,' => 'integer', 'int,' => 'integer', 'float,' => 'float', 'double,' => 'float', 'real,' => 'float', 'string,' => 'string', 'resource,' => 'resource' ); private function __construct() {} public static function init(){ set_error_handler('Typehint::handle'); return true; } public static function handle( $lvl, $msg) { if ($lvl == E_RECOVERABLE_ERROR && strstr($msg, 'must be an instance of') !== false) { $errmsg = explode(' ', $msg, 13); return isset( self::$_types[$errmsg[10]]) && self::$_types[$errmsg[10]] == $errmsg[11]; } return false; } } |
Давайте теперь проведем тесты и посмотрим, что у нас получилось.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | require_once 'Typehint.php'; Typehint::init(); function teststring( string $string) { return $string; } function test( $var) { return $var; } function micro_time() { $timearray = explode(" ", microtime()); return ($timearray[1] + $timearray[0]); } $start = micro_time(); for ($i = 0; $i < 10000; $i++) { teststring( '123'); } $end = micro_time(); echo 'With Typehint: ' . ($end-$start) . ' sec.'; echo "<br />\n"; $start = micro_time(); for ($i = 0; $i < 10000; $i++) { test( '123'); } $end = micro_time(); echo 'Without Typehint: ' . ($end-$start) . ' sec.'; |
Вот, что у меня получилось:
With Typehint: 0.0787329673767 sec.
Without Typehint: 0.00326299667358 sec.
Отмечу, что для оригинального решения от Дэниэля у меня результат получился такой: 0.215523958206 сек. Т.е. мы выиграли в производительности почти в 2,7 раз.
Тем не менее, как видим, без использования Typehint-решения, мы бы выиграли в производительности более чем в 24 раза. Правильнее сказать, используя его, мы проигрываем в 24 раза.
Это наталкивает на мысль о целесообразности его использования. Посмотрите 10 000 вызовов добавляют ко времени выполнения скрипта практически 0,1 секунды. Здесь есть над чем задуматься.
С другой стороны, использование Typehint увеличивает самодокументируемость кода и позволяет в некоторых случаях, когда это особенно необходимо, контролировать тип передаваемых аргументов.
Однако, следует учитывать, что повсеместная строгая типизация в PHP, на самом деле, не даст вам никаких преимуществ, т.к. механизма перегрузки в языке нет, при объявленном типе возникнут проблемы со значениями по-умолчанию у аргументов. Кроме того, за возвращаемые значения в языковых конструкциях также никто не ручается.
Поэтому стоит несколько раз подумать перед тем, стоит ли использовать данное решение, или нет.
Если вы все же видите целесообразность и хотите использовать это в своем проекте, предлагаю вам рассмотреть возможность/необходимость создания некого автоматического билдера, который зарелизит конечный код для продакшн использования, почистив скалярные типы в определениях функций и методов классов.
Сделать это будет, в принципе, несложно, хотя бы с помощью того же PHP или Shell.
Удачи в девелопменте!

08:52:33
Читателям моего блога это будет интересно.Можно, сделаю кросспост у себя на блоге?
22:43:33
Да, пожалуйста, только не забудьте дать ссылку на первоисточник
15:09:59
Я некоторые моменты, на своем блоге размещу, естественно с сылкой на твой блог. Статья интересная
09:43:04
Отличный блог у Вас, и статья интересная
22:44:17
Приятно читать листинги с приведенными тестами)))
«что повсеместная строгая типизация в PHP, на самом деле, >>>де даст<<< вам никаких преимуществ, т.к.»
Помоему у вас тут опечаточка вышла.
20:32:55
Спасибо, исправлено!