[Обновлено]: Внесены некоторые изменения в код. Спасибо ithilion и LoneCat
Все уже знают что в PHP 5 в аргументах функций можно указывать их тип, за исключением… скалярных типов, т.е.: integer, string, boolean, float, и т.д.
Однако на странице мануала о контроле типов, в комментариях, Daniel L. Wood приводит достаточно интересное решение этой проблемы с помощью класса-обработчика ошибок. Единственный существенный недостаток этого решения — это его производительность.
Ниже я попытаюсь рассказать, как можно оптимизировать это решение, а также стоит ли им пользоваться, в принципе, в продакшн релизах.
Итак, разберем чем грешит приведенное решение:
Мы попробуем решить все эти проблемы переписав класс следующим образом:
Давайте теперь проведем тесты и посмотрим, что у нас получилось.
Вот, что у меня получилось:
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.
Удачи в девелопменте!
P.S. Кросс-пост с моего блога: mikhailstadnik.com/php5-types-control
Все уже знают что в 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'.
- Несколько неоптимальный код в некоторых местах.
Мы попробуем решить все эти проблемы переписав класс следующим образом:
<?php 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; } } ?>
Давайте теперь проведем тесты и посмотрим, что у нас получилось.
<?php 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.
Удачи в девелопменте!
P.S. Кросс-пост с моего блога: mikhailstadnik.com/php5-types-control