[Обновлено]: Внесены некоторые изменения в код. Спасибо 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
