PHPКонтроль скалярных типов в PHP 5

[Обновлено]: Внесены некоторые изменения в код. Спасибо ithilion и LoneCat

Все уже знают что в PHP 5 в аргументах функций можно указывать их тип, за исключением... скалярных типов, т.е.: integer, string, boolean, float, и т.д.

Однако на странице мануала о контроле типов, в комментариях, Daniel L. Wood приводит достаточно интересное решение этой проблемы с помощью класса-обработчика ошибок. Единственный существенный недостаток этого решения — это его производительность.

Ниже я попытаюсь рассказать, как можно оптимизировать это решение, а также стоит ли им пользоваться, в принципе, в продакшн релизах.

Итак, разберем чем грешит приведенное решение:

  1. Абсолютно ненужные вызовы debug_backtrace. В принципе, для решения задачи достаточно разбора сообщения об ошибке. Явная проверка аргументов попахивает паранойей. Действительно, если мы поймали сообщение вида «Argument N passed to Class::function () must be an instance of string, string given, ...» — это уже дает нам все основания сделать нужный выбор. Обратите внимание ...string, string... В случае ошибки будет, например, ...integer, string... Этого достаточно, чтобы определить, является ли данное сообщение ошибкой на самом деле или нет.
  2. В нем есть опечатка в массиве типов. 'resrouce' => 'is_resource'.
  3. Несколько неоптимальный код в некоторых местах.

Мы попробуем решить все эти проблемы переписав класс следующим образом:

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.

Удачи в девелопменте!

Хорошая статьяПлохая статья +4
   |    Опубликовано: Декабрь, 7 2008г.    |    Автор: Михаил Стадник

Комментарии (6) к статье “Контроль скалярных типов в PHP 5”

Оставить комментарий