Подтвердить что ты не робот

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?

4b9b3361

Ответ 1

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

Исходя из опыта Java/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/С++ -эстетике, и я следовал этому подходу в 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, С++ или 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 недостаточно умен, чтобы оптимизировать "no-op". Поэтому кастинг всегда вызывает копирование переменной. В большинстве случаев это не проблема, но если вы регулярно используете этот подход, вы должны держать его в глубине своего сознания. Из-за этого кастинг может вызвать неожиданные проблемы со ссылками и большими массивами. Подробнее см. Отчет об ошибке PHP # 50894.

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


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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

Я не думаю, что это плохо, но я бы сделал еще один шаг: используйте тип hinting для сложных типов и выдайте исключение если простой тип не тот, которого вы ожидаете. Таким образом, вы информируете клиентов о любых издержках/проблемах с литой (например, о потере точности, исходящей из 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);
}

Ответ 5

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