PHP Typecasting – хорошо или плохо?

После некоторой работы на C и Java я все больше и больше раздражался законами дикого запада в PHP. Я действительно чувствую, что PHP не хватает строгих типов данных. Тот факт, что строка ('0') == (int) 0 == (boolean) false является одним из примеров.

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

Является ли хорошей или плохой практикой аргументы типа приемы, полученные для метода? И хорошо ли придумать возвращение?

IE

public function doo($foo, $bar) { $foo = (int)$foo; $bar = (float)$bar; $result = $bar + $foo; return (array)$result; } 

Пример довольно глупый, и я его не тестировал, но я думаю, что все получают эту идею. Есть ли какая-то причина для того, чтобы PHP-бог мог преобразовать тип данных, как он хочет, помимо того, что люди, которые не знают о типах данных, используют PHP?

К лучшему или худшему, свободный ввод текста – это «Путь PHP» . Многие из встроенных модулей и большинство языковых конструкций будут работать на любых типах, которые вы им даете – молча (и часто опасно) бросать их за кулисы, чтобы вещи (вроде) сочетались друг с другом.

Исходя из опыта Java / C / C ++, модель PHP для свободной печати всегда была источником разочарования для меня. Но на протяжении многих лет я обнаружил, что, если мне нужно писать PHP, я могу сделать более эффективную работу (т.е. более чистый, безопасный, более тестируемый код), обнимая «слабость» PHP, а не бороться с ним; и из-за этого я становлюсь более счастливой обезьяной.

Кастинг действительно является основополагающим для моей техники – и (IMHO) это единственный способ последовательно создавать чистый, читаемый PHP-код, который обрабатывает аргументы смешанного типа в хорошо понятном, проверяемом, детерминированном виде.

Основной момент (который вы также четко понимаете) заключается в том, что в PHP вы не можете просто предположить, что аргумент является тем типом, который вы ожидаете от него. Это может привести к серьезным последствиям, которые вы вряд ли поймаете, пока ваше приложение не появится на производстве.

Чтобы проиллюстрировать этот момент:

 <?php function displayRoomCount( $numBoys, $numGirls ) { // we'll assume both args are int // check boundary conditions if( ($numBoys < 0) || ($numGirls < 0) ) throw new Exception('argument out of range'); // perform the specified logic $total = $numBoys + $numGirls; print( "{$total} people: {$numBoys} boys, and {$numGirls} girls \n" ); } displayRoomCount(0, 0); // (ok) prints: "0 people: 0 boys, and 0 girls" displayRoomCount(-10, 20); // (ok) throws an exception displayRoomCount("asdf", 10); // (wrong!) prints: "10 people: asdf boys, and 10 girls" 

Один из подходов к решению этого – ограничить типы, которые может принимать функция, бросая исключение, когда обнаружен недопустимый тип. Другие уже упоминали этот подход. Это хорошо отражается на моей Java / C / C ++ -эстетике, и я следовал этому подходу в PHP годами и годами. Короче говоря, в этом нет ничего плохого, но это противоречит «Пути PHP», и через некоторое время это начинает ощущаться как плавание вверх.

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

Используя литье, наш пример теперь становится:

 <?php function displayRoomCount( $numBoys, $numGirls ) { // we cast to ensure that we have the types we expect $numBoys = (int)$numBoys; $numGirls = (int)$numGirls; // check boundary conditions if( ($numBoys < 0) || ($numGirls < 0) ) throw new Exception('argument out of range'); // perform the specified logic $total = $numBoys + $numGirls; print( "{$total} people: {$numBoys} boys, and {$numGirls} girls \n" ); } displayRoomCount("asdf", 10); // (ok now!) prints: "10 people: 0 boys, and 10 girls" 

Теперь функция ведет себя так, как ожидалось. Фактически, легко показать, что поведение функции теперь хорошо определено для всех возможных входов. Это связано с тем, что операция литья хорошо определена для всех возможных входов; броски гарантируют, что мы всегда работаем с целыми числами; а остальная часть функции записывается так, чтобы быть четкой для всех возможных целых чисел.

Правила для ввода типов в PHP описаны здесь (см. Ссылки на конкретные страницы в середине страницы – например: «Преобразование в целое число»).

Этот подход имеет дополнительное преимущество в том, что функция теперь будет вести себя так, как это согласуется с другими встроенными PHP и языковыми конструкциями. Например:

 // assume $db_row read from a database of some sort displayRoomCount( $db_row['boys'], $db_row['girls'] ); 

будет работать нормально, несмотря на то, что $db_row['boys'] и $db_row['girls'] – это строки, содержащие числовые значения. Это согласуется с тем, что средний PHP-разработчик (который не знает C, C ++ или Java) ожидает, что он будет работать.


Что касается значений возвращаемых значений: это очень мало, если вы не знаете, что у вас есть переменная смешанного типа, и вы всегда должны гарантировать, что возвращаемое значение является определенным типом. Это чаще встречается в промежуточных точках кода, а не в точке, откуда вы возвращаетесь от функции.

Практический пример:

 <?php function getParam( $name, $idx=0 ) { $name = (string)$name; $idx = (int)$idx; if($name==='') return null; if($idx<0) $idx=0; // $_REQUEST[$name] could be null, or string, or array // this depends on the web request that came in. Our use of // the array cast here, lets us write generic logic to deal with them all // $param = (array)$_REQUEST[$name]; if( count($param) <= $idx) return null; return $param[$idx]; } // here, the cast is used to ensure that we always get a string // even if "fullName" was missing from the request, the cast will convert // the returned NULL value into an empty string. $full_name = (string)getParam("fullName"); 

Вы поняли эту идею.


Есть пара ошибок, о которых нужно знать

  • Механизм кастования PHP недостаточно умен, чтобы оптимизировать «нет-op». Поэтому кастинг всегда вызывает копирование переменной. В большинстве случаев это не проблема, но если вы регулярно используете этот подход, вы должны держать его в глубине своего сознания. Из-за этого кастинг может вызвать неожиданные проблемы со ссылками и большими массивами. См. Отчет об ошибке PHP # 50894 для получения более подробной информации.

  • В php целое число, которое слишком велико (или слишком мало) для представления в виде целочисленного типа, будет автоматически представлено как float (или, если необходимо, double). Это означает, что результат ($big_int + $big_int) может фактически быть float, и если вы ($big_int + $big_int) его в int, то получившееся число будет тарабарщиной. Итак, если вы создаете функции, которые должны работать на больших целых числах, вы должны помнить об этом и, вероятно, рассмотреть некоторые другие подходы.


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

Следующая версия PHP (вероятно, 5.4) будет поддерживать скалярный тип в аргументах .

Но кроме этого: преобразование динамического типа действительно не то, что вы должны ненавидеть и избегать. В основном это будет работать, как ожидалось. И если это не так, исправьте его, проверив его is_* какого-то типа, используя строгое сравнение, …, …

Вы можете использовать тип hinting для сложных типов. Если вам нужно сравнить значение + типа, вы можете использовать « === » для сравнения.

 (0 === false) => results in false (0 == false) => results in true 

Также вы пишете return (array)$result; что не имеет смысла. В этом случае вам нужен return array($result) если вы хотите, чтобы возвращаемый тип являлся массивом.

Я не думаю, что это плохо, но я хотел бы сделать еще один шаг: использовать тип намека на сложные типы и вызывать исключение, если простой тип не тот, которого вы ожидаете. Таким образом вы делаете клиенты осведомленными о каких-либо затратах / проблемах с литой (например, о потере точности, идущей от int -> float или float -> int).

Ваш приведение к массиву в вышеприведенном коде там, однако, вводит в заблуждение – вы должны просто создать новый массив, содержащий одно значение.

Все сказанное, ваш пример выше:

 public function doo($foo, $bar) { if (!is_int($foo)) throw new InvalidArgumentException(); if (!is_float($bar)) throw new InvalidArgumentException(); $result = $bar + $foo; return array($result); } 

Нет, это не очень хорошо, потому что вы не знаете, что у вас будет в конце. Я лично предложил бы использовать такие функции, как intval() , floatval() и т. Д.