Thursday, May 27, 2010

php type hinting

Навеяно http://habrahabr.ru/blogs/php/94714/


В статье обсуждаются предлагаемые в будущих реализациях механизмы проверки типов, в комментариях задевается текущая. А я хочу рассказать о том как я боролся с проверкой типов, в том числе и примитивных, в php средствами php.



Идеология. Проверка типов должна быть легко отключаема, или поведение на несоотвестие типов должно легко подменяться.



Набросок использования предполагаемого решения для проверки типов данных на примере Yii framework. (В принципе от yii тут только component, что например в Zend может быть сделано на Registry, если там ничего не появилось нового, или на Toolkit в limb, ну или уж в крайнем случае singleton, глобальные переменные)



class AnyClass
{
/**
* anyFunction description
*
* @param integer $arg1
* @param string $arg2
*/
public function anyFunction($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs($this,'anyFunction' get_funciton_args());
//orYii::app()->typeHintingComponent->assertFunctionArgs(__CLASS__,__METHOD__, get_funciton_args());
}


Ожидаемое поведение : в случае если тип $arg1 не integer или тип $arg2 не string или полученное количество аргументов не совпадает, мы получаем например RuntimeException.



Реализация :


  • По классу и имени метода получаем Reflection Method

  • У Reflection Method получаем docsting

  • Из dostring вытаскиваем типы данных

  • Проверяем

  • Делаем что душа пожелает, тут можно использовать либо стратегию (оно же вызывать call_back) , либо наследников с переопределением обработки ошибок, чтобы менять поведение.




В моем примере поведение меняется заданием component в конфиге.



В результате получаем отключаемую проверку типов, и можем управлять поведением.



Основным недостатком мне видится необходимость каждый раз явно указывать объект, метод, передавать полученные аргументы

Мое решение это написать функцию обертку что берет эти данные из debug_backtrace


//docstring
public function func($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs();
}

//...
public function assertFunctionArgs()
{

$backTrace = debug_backtrace();
if ( ! isset($backTrace[1]['object'], $backTrace[1]['function'], $backtrace[1]['args']))
throw new RCAssertException(/* Details */);

$obj = $backTrace[1]['object'];
$function = $backTrace[1]['function'];
$args = $backTrace[1]['args'];
//далее по предыдущему алгоритму

}



Еще один недостаток, на этапе разработки можно получить несоотвествие документации и ождаемых параметров, лично я это к недостаткам не отношу, т.к. позволит поддерживать актуальность документации как минимум.



Ну а самый большой недостаток такой реализации, это все же необходимость в методе постоянно вызывать ....->assertFunctionArgs(), если этого не делать, то и схема получится не работающая, это уже нужно насаждать на уровне review кода.

Зато самое большое преимущество это возможность проверять даже не существующие типы

/**
* @param mixed $var ну и нафига проверка типов если используются такие переменные? :)
* @param string|integer $var2 тоже смешно, но на промежуточном этапе рефакторинга может пригодится
*/



На практике у меня это не прижилось т.к. пытался насадить это вместе с кучей других требований и это не было приоритетным, возможно теперь перейдя на yii framework и реализовав это в виде компоненты у меня получится это сделать со второй попытки. Но тем не менее мне пришлось при рефакторинге одного запутанного кода использовать эту наработку активно, просто в том проекте одни и те же функции/методы использовались для разных целей, и соотвественно передаваемые типы данных были непредсказуемыми.

 
Каталог сайтов, Добавить сайт