Адаптивная типизация

PHP*
Суть сего паттерна заключается в том, чтобы задавать типы значений через накладываемые на них ограничения. И при возможности изменять это значение так, чтобы оно вписывалось в эти ограничения.

Для чего вообще нужна типизация? Чтобы отлавливать появление ошибки как можно раньше, то есть как можно ближе к тому месту, где программист совершил оплошность, а не к тому, где она вызвала непоправимый сбой. Но вручную приводить типы — это слишком накладно, поэтому необходимо автоматическое приведение для совместимых друг с другом типов. Например, «строка не длиннее Х, содержащая только цифры» может быть однозначно преобразована «целое положительное число», и наоборот.

Многие языки не позволяют определять собственные типы. Некоторые языки это позволяют, но выразительных средств зачастую не хватает. Однако, можно опредлить набор функций, каждая из которых на вход принимает значение и на выходе гарантирует, что возвращённое ею значение удовлетворяет всем ограничениям. При этом она может попытаться преобразовать значение так, чтобы оно соответствовало ограничениям, а если ей это не удаётся — должна бросить исключение.

Определим несколько типовых тайпкастеров:
function aNumber( $val ){
    if( !is_numeric( $val ) ) throw new Exception( 'can not convert to number' );
    return $val= +$val;
}

function aString( $val ){
    return $val= $val . '';
}

function anArray( $val ){
    if( is_object( $val ) ) $val= get_object_vars( $val );
    else if( !is_array( $val ) ) throw new Exception( 'can not convert to array' );
    return $val;
}

Это тайпкастеры общего назвачения. Они делают приведение типов абы как, поэтому лучше вместо них использовать более специфичные типы, соответствующие вашей бизнес-логике:
function anUserId( $val ){
    aNumber( &$val );
    if( $val <= 0 ) throw new Exception( 'user id is not positive' );
    return $val;
}

Заметьте, что тайпкастеры сначала изменяют значение параметра, а потом его возвращают. Это позволяет использовать их в двух формах:
function example( $count ){
    aNumber( &$count ); // передали переменную по ссылке. внутри функции она при необходимости будет изменена
    return aString( $count + 2 ); // передали некоторое выражение и воспользовались возвращаемым функцией результатом.
}
$x= example( '2' ); // вернёт '4'
$y= example( '2x' ); // бросит исключение

Сейчас типовая функция, реализующая паттерн адаптивной типизации, выглядит примерно так:
function xxx( $count= 0, $title= '', $list= array(), $user= 0, $db= null ){
    aNumber( &$count ); aString( &$title ); anArray( &$list ); anUserId( &$user ); DB::anInstance( $db );

    var_dump( $count, $title, $list, $user );

    return aString( $count * 2 );
}

Однако, возможно вскоре в пхп будет добавлена нативная поддержка и запись станет более удобной:
function aString xxx( aNumber $count= 0, aString $title= '', anArray $list= array(), anUserId $user= 0, DB $db= null ){
    var_dump( $count, $title, $list, $user );

    return $count * 2;
}

Фактически мы получим тогда статическую типизацию функций с автоматическим приведением и возможностью самостоятельно определять новые типы данных. А пока разве что можно воспользоваться кодогенерацией или не лениться ;-)
–4
1 июня 2010, 15:46
4
tenshi –27,1

комментарии (18)

+7
remal #
Костылем попахивает. Не видно какой-либо логике в реализации новых фич в PHP. Пихают все подряд, не думая о общей идеологии и стройности языка.
0
tenshi #
а что за идеология такая?
+2
resurection #
Зачем такйо костыль
return aString( $count * 2 );

Если можно
return (string) $cnt * 2; — то же самое

Это от не знаний основного синтаксиса php:
function aString( $val ){
return $val= $val. ''; // Заменить на «return (string) $val;» — тогда и вообще этот самописный костыль не нужен.
}
+2
tenshi #
осспади, ну замени aString на anURIComponentString, коли хочется примеров по сложнее.
+1
Fortop #
Не в этом ведь суть.
А в возможности использовать собственные типы с неявным приведением в сигнатурах функций и методов.
+1
Barttos #
|| // передали переменную по ссылке. внутри функции она при необходимости будет изменена
в PHP 5.3 это не deprecated?
0
tenshi #
угумс
+2
resurection #
Кроме того, генерить ошибки в большинстве случаев не надо.
например user::getByID($_GET['id']); // в некоторых случаях будет генерить Exception внутри aNumber. Это лишняя головная боль. Я тупо внутри ф-ции перевожу всё в integer и в самом худшем случае получится ноль и ф-ция не сможет найти юзера. Но эта ситуация у меня уже обработана. При запросе /profile.php?id=lalal — выводится сообщение «юзер не найден». И никаких Exception-ов.
–1
tenshi #
а у меня при запросе /profile.php?id=lalal будет выводиться «введите численный идентификатор пользователя», а у тебя будет лишний запрос к базе.
+1
resurection #
Ну как бы случайно такой запрос получится не может, поэтому человек обратившийся по такому адресу прекрасно знает что он делает и в таких сообщениях не нуждается. Или нуждается, но только для получения дополнительной информации о внутренностях системы.
0
tenshi #
–1
tenshi #
не стоит всех пользователей считать злостными хакерами
+1
resurection #
В моём случае всё сработало бы без ошибок ибо (integer) вытрет лишнюю точку и юзеры не почувствую дискомфорта.
Юзабилити однако :)
0
tenshi #
это случайное везение habrahabr.ru/blogs/php.
+1
mrmot #
очередной велосипедист
www.php.net/manual/en/book.filter.php
+1
tenshi #
очередной идиот
оно не расширяемо и неудобно в использовании
–1
winbackgo #
И почему-же 2x не может быть правильным числом? (int) '2x' == 2. Полезно при создании seo ссылок /5454-заголовок
+1
tenshi #
потому что «2 миллиона» не равно «2 штуки»

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.