LINUX.ORG.RU

Мысли о PHP typecast вообще и про преобразования строк в числа в PHP в частности.

 , ,


0

2

Привет!

Меня всегда напрягало то, как PHP преобразует строки в числа. Классический пример:

$value = (int)"32px"; // 32
$value = (int)"kg32"; // 0

Я понимаю, что такое поведение намеренно и в некоторых случаях бывает полезным.

Но… в результате можно получить вполне корректное целое число, при этом информация о том, что исходная строка содержала что-то ещё (важное или не очень), бесследно теряется.

Я думал над тем, как можно было бы решить эту проблему, и пришёл к одной идее.

Что, если добавить к значениям признак reliable / unreliable (надёжное / ненадёжное)?

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

Например, помечать значение как unreliable, если при его преобразовании возникли сомнения.

Этот флаг будет сохраняться при присваивании и вычислениях:

$a = (int)"32kg";  // 32, unreliable
$b = $a * 2;       // 64, всё ещё unreliable
$c = $b + 10;      // 74, всё ещё unreliable

Флаг можно устанавливать и снимать явно:

$a = (unreliable)42;
$a = (reliable)$a;

В обычном режиме этот флаг ни на что не влияет. Код продолжает работать как прежде. При необходимости можно проверить значение с помощью is_reliable($value) и принять соответствующие меры:

if (!is_reliable($x = (int)$UserInput)) {
 ....
}

Но можно предусмотреть и более строгий режим - с помощью языковой конструкции reliable / catch, по аналогии с try/catch:

$a = (int)"32kg"; // 32, unreliable

reliable {

    echo $a; // Попытка использовать unrel значение - исключение!

} catch (Error $e) {
    // Обрабатываем исключение
}

Или включить строгую проверку для всего последующего кода. Такой режим работает по принципу этакого fail-fast: никаких блоков catch() - просто выбрасывается необработанное исключение, которое прерывает выполнение.

$a = (int)'hello'; // 0, unreliable
$b = $a + 1;

reliable on;  // языковая конструкция, включить строгий режим
...
$c = $b; // Исключение: попытка использовать unrel значение
...

Примеры выше - это все про строки. Но ureliable может происходить и в других сценариях, например, мы конвертим объект в число, а объект не имеет соответствующего магического метода __toInt. Или мы ковертим массив во float, (т.е. (float)[‘a’ => 0, 2]) и так далее.

А ТЕПЕРЬ, ВНИМАНИЕ, ВОПРОС:

Мне было бы интересно узнать, что думают об этом те, кто пишет на PHP.

Как вам такая идея? Кажется ли она полезной? Или это просто добавление сложности там, где на самом деле нет никакой проблемы?

Спасибо!

Меня всегда напрягало то, как PHP преобразует строки в числа. Классический пример:

Я с PHP вообще не знаком и знакомство не планирую. Из этого примера, похоже что под капотом этого преобразования типа лежит функция atoi() из стандартной сишной библиотеки, в которой нет механизма определения ошибки.

Если нужно отслеживать ошибку есть функция strtol(), можете посмотреть как она работает.

Jullyfish ★★
()

Надо чинить преобразование строки в число, а не подпирать костылями.

$value = (int)«32px»; // 32

32px - не число и то, что при преобразовании получается 32 уже ошибка.

hippi90 ★★★★★
()
Ответ на: комментарий от Jullyfish

Да, там исторически был atoi(), и я знаю про strtol().

Наверно, следовало пояснить: я занимаюсь разработкой php-подобного языка. Фича, про которую я написал выше - была легкореализуема, поэтому я ее и реализовал.

Меня интересует мнение сообщества программистов на PHP - была бы такая фича им интересна.

Старый код, который про эту фичу ничего не знает, будет работать как и работал.

vvb333007
() автор топика

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

$v = (int) (preg_match('/\d+/', 'kg32', $matches) ? $matches[0] : 0);
PeleWin ★
()

Зачем оно в движке? Сделай свой класс ReliableIntCast в конструктор суй строку и все возможные нужные тебе методы каста и предикаты

cobold ★★★★★
()
Ответ на: комментарий от cobold

Для поиска ошибок в процессе разработки и как строгий assert() в релизе. Иногда выгоднее написать код, который просто аварийно завершается, вместо того чтобы городить обработку ошибок. Ввел пользователь мусор - скрипт просто упал.

Второй момент - этот (вышеописанный) механизм же работает не только для «string->numeric». Он работает для всех типов: например каст массива в строку (например: $a = (string)[1,2,3,4]; ) или каст экземпляра класса в строку ($a = (string)(new MyClass))

В Zend PHP, если класс не имеет метода __toString(), то каст к строке вернет «Object». Отличить после каста - не возможно: то-ли объект не имел метода __toString() и поэтому «Object» то-ли имел, но вернул «Object».

Где оно может пригодиться:

Например, написали вы код, который работает, все случаи мусорного ввода вы обработали. Теперь можно добавить в самое начало скрипта reliable on; и виртуальная машина вывалит исключение , если где-то ошибка пролезла несмотря на то, что вы обмазались проверками.

вариант reliable {} позволяет включать проверку на конкретный код (или вызов). Исключение можно поймать в блоке catch

Программист может сам помечать значения как unreliable: $a = (unreliable)1; для того, чтобы их потом как-то специально обработать.

vvb333007
() автор топика

Единственный случай когда оно меня напрягало это что при валидации значений приходящих в виде строки приходится городить странные конструкции вида if ((string)$a === (string)(int)$a) ... а с float-ми оно вообще не будет работать корректно на некоторых значениях. Так что решаемая задача не вполне ясна. Если мы хотим строгую проверку, то логично сделать ее там где получаем на вход потенциально не валидные данные. Для чего может потребоваться проделать ряд вычислений и потом в каком-то случайном месте упасть, мол «ба! да мы же тут с некорректными входными данными работаем!»? В некотором смысле это напоминает runtime-проверку тайп хинтов в каком-нибудь старом проекте, в котором есть как новый код с хинтами так и старый без хинтов

cobold ★★★★★
()

$value = (int)«32px»; // 32

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

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

theanonymous ★
()

Есть два подходу к дизайну ЯП. Один позволяет писать короткий выразительный код, который «просто работает», но иногда даёт сбой. Другой позволяет обеспечивать корректность, но ценой выразительности.

Нужна корректность — не пишите на PHP. Деваться некуда — обмазывайтесь костылями.

kaldeon ★★
()

Чем это лучше запрета на преобразования невалидных строк при запуске пхп движка?

ya-betmen ★★★★★
()

зачем «путать соленое с зеленым» и «натягивать ежа на ужа».
создай отдельный тип данных где к int значению будет прикладываться стринг с размерностью и «будет тибе щастья» :) всё станет простым и логичным без сущностей сверх необходимого как завещал великий Оккама.

pfg ★★★★★
()
Последнее исправление: pfg (всего исправлений: 1)

PHP головного мозга.

$UserInput никогда не должен каститься. Он должен парситься в соответствии с определёнными правилами (которые могут быть разными для 42, 69kg и $420), при этом каким-то образом должен возращаться признак успеха (не важно, через исключения, указатели или возвращаемое значения). Вызывающий код должен при этом адекватно реагировать на ошибки.

Кастинг в стиле PHP/JavaScript - это зло, которое должно быть искоренено. Единственный допустимый сценарий - это кастинг из более «узкого» типа в более «широкий» (например, из u32 в u64), но только для совместимых типов. Каст строк в число должен быть запрещён.

anonymous
()
Ответ на: комментарий от cobold

Если мы хотим строгую проверку, то логично сделать ее там где получаем на вход потенциально не валидные данные. Для чего может потребоваться проделать ряд вычислений и потом в каком-то случайном месте упасть, мол «ба! да мы же тут с некорректными входными данными работаем!»?

Да. Есть термин (вроде бы, гугл в основном возвращает какой-то React.js-мусор) «error boundary». Суть в том, что у тебя есть слой, на котором выполняется какая-то проверка/валидация/etc, и код разветвляется на два пути:

  • happy path, при обработке которого на каждом слое ниже мы можем быть чётко уверены, что значения, с которыми мы работаем, валидны (не содержат null или какого-то мусора);
  • unhappy path, при обработке которого мы ругаемся в логе, возвращаем пользователю ошибку или делаем какое-то другое осмысленное действие.

Если у тебя на каждом слое обработки есть «защитный» код, который проверяет, что переданное по стеку не содержит мусор — это говнокод. На практике (в большинстве случаев) чем выше уровнем располагается error boundary, тем более простым, поддерживаемым и тестируемым получается код.

anonymous
()
Ответ на: комментарий от PeleWin

is_numeric

Оно флоаты в себя включает. Или ты про задачу проверки что строка является флоатом?

cobold ★★★★★
()

Но… в результате можно получить вполне корректное целое число, при этом информация о том, что исходная строка содержала что-то ещё (важное или не очень), бесследно теряется.

Поздравляю, ты на своём опыте понял почему языки с нестрогой типизацией - говно.

Мне было бы интересно узнать, что думают об этом те, кто пишет на PHP.

Нужно переходить с него на полноценные языки программирования, там не надо костылить значения никакими признаками - они просто не надут тебе сконвертить строку в число если строка не представляет собой число, причём ещё на этапе сборки, поэтому и catch не нужно будет писать. Круто, да?

anonymous
()
Ответ на: комментарий от anonymous

Каст строк в число должен быть запрещён.

Пишите в ООН! Или на Rust. Там много чего запрещено

vvb333007
() автор топика
Последнее исправление: vvb333007 (всего исправлений: 1)
Ответ на: комментарий от vvb333007

Теперь можно добавить в самое начало скрипта reliable on; и виртуальная машина вывалит исключение , если где-то ошибка пролезла несмотря на то, что вы обмазались проверками.

Кажется что достаточно лишь этого механизма: вываливать исключения по добавляемым новой директивеэой проверке. Остальная часть предложения с метаданными в рантайме станет неактуальна.

На php никогда не писал.

GPFault ★★★★
()
Ответ на: комментарий от vvb333007

Никто не запрещает делать так, как нравится

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

cobold ★★★★★
()

Абсолютное ненужно, проблема решается нормальной функцией преобразования с валидацией.

Лучше сделай нормальный switch-оператор в шаблонах, а то сейчас он неюзабелен из за требования break в одном php-куске с выводом: https://www.php.net/manual/en/control-structures.alternative-syntax.php

anonymous
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария