Comments 34
У нас была ситуация, из приложения на PHP по запросу получали словарь типа { "key1": "value1"...}
. И всё было хорошо, пока не случилась крайне неприятная ситуация с пустым словарём. Потому что возвращаться стало вот это []
, а не {}
. Запросы выполнялись из приложения на типизированном языке, где подобное в принципе не мыслимо, словарь это всегда словарь, массив это массив (с конкретным типом элементов), а int
это всегда int
. Исправили костылём, но осадочек остался. Подобные решения приветствую, главное чтобы они работали :)
Это прям классический кейс, о который спотыкаются все json API на php при интеграции с приложением под iOS. Ещё ни разу мимо не удалось проскочить, хотя фронт на js и даже андроид достаточно терпимо к такому относятся.
Правда обычно пустой справочник / объект отдаём все же как null, а не {}
Так в этом не PHP виноват, а разработчики, которые не знают что ассоциативный массив кастится в []
, если он пустой. Я сталкивался с такой проблемой когда только начинал знакомиться с Yii, отдавал ему данные и думал будет всё нормально. В итоге пришлось делать что-то вроде return $data === [] ? new \stdObject : $data;
, что, безусловно, тоже костыль. Хорошо было бы указывать тип возвращаемого значения в аннотациях или PHPDoc
Понятно, что это ньюанс, о котором надо знать и помнить, но не хотелось бы погрязнуть в этих ньюансах. Строгая типизация от этого всего защищает. Имея объект типа "словарь", вы никогда не получите из него случайно массив, даже пустой, при любой сериализации, потому что массив это совершенно другой тип, и семантически и физически. Из той же оперы, я бы не хотел даже оказаться в ситуации, когда мне легко удастся сравнить строку и число, хочу стабильную ошибку компиляции в этом месте. Но если сравнение происходит через приведение к общему типу, то результат сравнения должен быть всегда false, независимо от значений. И без знания всяких ньюансов :) Поэтому для меня это немного дико, хотя ньюансы есть везде и порой забористые.
Можно объявлять не как массив, а как ArrayObject, дальше работать как с массивом, но сериализовываться будет как нужно
Через JSON_FORCE_OBJECT?
А зачем везде true ===
пишете? is_numeric и isset, согласно доке, уже bool возвращают.
А вам IDE не подчёркивает нестрогие сравнения? Само по себе нестрогое сравнение - потенциальный источник ошибок, поэтому лучше избавляться от привычки в принципе его использовать. Кроме того, считается, что строгое соавнение срабатывает быстрее, т.к. не пытается приводить типы. И хотя в конкретно данном случае ни ошибки, ни разницы в скорости не будет, но вы подумайте логически: вы предлагаете использовать сравнение с приведением типов там, где это приведение 100% не пригодится.
А причём тут это?
По-вашему «isset(…) === true ? A : B» строже, чем «isset(…) ? A : B» что ли? За счёт чего?
Дельное замечание, спасибо, убрал. Видимо заигрался с PHPStan strict types
Мысли в сторону: Подключу ка я ещё одну библиотечку, чтобы было удобно. Подождите, надо проверить совместимость с другими 15 библиотеками, с которыми мне очень удобно. Завтра выходит новый программер, надо ему ещё поставить пару библиотек, чтобы было ему удобно, потом обсудим за пивом как нам удобно работать ) эй, начальник, нам нужен нужна должность менеджера по удобствам и желательно сервак новый, а то этот тормозит что-то
Предлагаете всё писать руками?
Откровенно говоря, аналогичные велосипеды есть на любом мало-мальски крупном проекте. Любая раздражающая проверка или обработчик, встречающиеся больше одного раза выносятся в хелпер или ещё куда, после чего забываешь про то, что там понаписано и просто вызываешь X::parseNumeric($abc), который вернёт либо число, либо null / default. Да и то такие вещи обычно приходится костылить не от хорошей жизни, на нормальных проектах с хорошей архитектурой за такие вещи по рукам дадут на кодревью. У нас лично самый полезный из таких велосипедов - это DotAccess для массивов, аля JS. Судя по описанию, что-то похожее как раз реализовано и здесь.
Опять вротпрессеры придумали себе проблем и их решают
Вся эта ересь решается использованием дто со строго типизированными полями, а не всяких всратых array $userData
Типизированный DTO надо же тоже где-то создать, разве нет? Потому что из query string вроде приходят только строки. Как вы решаете эту проблему? (при этом я не говорю что решение из статьи мне нравится)
В условной симфе в экшен контроллера можно заинжектить уже готовую дтошку, собранную из пришедшего запроса. Так же и в ларке
В остальных фреймах да, возможно придется создавать дтошку самому, но при этом после валидации ты точно можешь быть уверен, что все пришедшие данные верного типа. Это делается один раз в экшене, зачем делать для этого отдельные функции, тем более отдельную библиотеку и тем более зачем тащить такую библиотеку в проект - я не понимаю
Не, в ларке нет таких инструментов из коробки. Даже все пакеты от каких-либо "шпателей" предоставляют не DTO, а полноценные монструозные VO, хотя называются DTO.
Это надо тащить условные symfony/serializer, jms, type-lang/mapper, valinor, etc. Но экосистемы нормальной в ларе нет под это дело, только недавно завезли атрибуты с костылями (ContextualAttribute интерфейс) для эмуляции аргумент велью резолверов, но всё равно это такое себе.
В ларе это решается для простых случаев через form request, они гарантируют более-менее типы, но никаких преобразований не производят.
не совсем понял; "в ларке нет инструментов из коробки" и "решается через form request" как будто друг другу противоречат
и да, я говорил как раз про form request, в котором для любого поля запроса можно указать желаемый тип и плеваться ошибками валидации при несоответствии; из этого потом влегкую делаются дто через встроенные методы типа integer() или boolean(), потому что ты уже можешь быть уверен, что у тебя все данные верного типа; ну или по крайней мере каст сделается за тебя
не совсем понял; "в ларке нет инструментов из коробки" и "решается через form request" как будто друг другу противоречат
Изначально цитата была следующей:
В условной симфе в экшен контроллера можно заинжектить уже готовую дтошку, собранную из пришедшего запроса. Так же и в ларке
Мой комментарий: Нет, в ларке не так же. Там как в запросе пришла нетипизированная мусорка из запроса, так с ней дальше и работаешь. Просто с некоторым шансом на корректность данных. Никаких реальных операций над типами и тем паче работы с DTO там нет.
почему же с шансом? в ларке конкретные валидаторы на типы переменных есть
да, прямого аналого маппинга как в симфе, но есть достаточно похожий инструмент, позволяющий достичь тех же результатов
Плюс ключи массивов напрямую гонять в принципе плохо, т.к. усложняет поиск мест использования данных.
Хотел тоже самое написать. ArrayToDtoMapper у меня для таких случаев есть.
Подравляю вы изобрели еще один велосипед. Первый такой велосипед изобрел я сам, пятььлет назад.
Я решал аналогичную проблему, написал для этого пару классов, потом код оформил в пакет.
composer require sbwerewolf/language-specific
И статью на Хабр написал.
https://habr.com/ru/articles/522348/
У меня не такое лаконичное решение, мое решение более ООП, в силу мои религиозных убеждений. Статичные методы для меня это прямо крайний случай, процедуры (в php это function) - за гранью добра и зла.
Статичные методы для меня это прямо крайний случай, процедуры (в php это function) - за гранью добра и зла.
Это не процедуры, а функции. Любая процедура всё же содержит сайд-эффекты и не идемпотентна (ну кроме случаев возврата по ссылке, но такое бы я тоже функциями считал, хотя терминологически не знаю как правильно). А функции всё же привычно считать чистыми, как и в данном случае, например.
Так что никаких минусов у такого кода я не вижу, кроме забивания таблицы символов языка. Но даже наличие 100 функций вряд ли вообще скажется на производительности поиска в хешмапе в executor_globals. Всё же коллизии хешей - довольно редкое явление.
Какие разумные (потому что пока что вижу только догматизм) аргументы против вы имеете?
Почему бы не использовать объекты с типизированными свойствами, вместо мап?
Если что, то функции в пыхе принято (в спецификации этого нет, однако 99.9% стандартной библиотеки именно так написана) именовать через нижнее подчёркивание: string_or_null
, а не stringOrNull
. В камел-кейсе методы только ;)
Не принято в пыхе функции писать в snake_case потому что есть яркие примеры: strtotime
, intval
, stripcslashes
и т.д.
Как можно равняться на стандартную библиотеку, если она сама не выдерживает единый стиль.
Вы забыли уточнить версию. То что вы приводите - это времена PHP 4, где функции копипастились из ANSI C 1 в 1. Теперь предлагаю привести пример такого непотребства в PHP 7.x/8.x, найдёте?
Все эти функции и другие подобные я вижу в версии 8.3.
Если вы подразумеваете, что новые функции добавляемые в новые версии php придерживаются этого негласного правила, то опять же нет.
Есть новая версия 8.4 и в нее добавили новые функции bcceil()
, bcdivmod()
, bcfloor()
и bcround()
. snake_case отсутствует.
Для начала - это не функции PHP, а функции внешнего расширения. У каждого расширения может быть своё именование и правила именования. У bcmath - префикс "bc", у mbstring префикс "mb_" (с нижним подчёркиванием). У ffi - отдельный неймспейс FFI в рамках которого класс, внутри которого статические методы. И т.д.
Увы и ах, в bcmath изначально был выбран такой нейминг, так что с этим ничего не поделать. И уж лучше это, нежели bcadd
, bcmul
... и новая функция bc_div_modulo
(вместо bcdivmod
) или bc_round
(вместо bcround
).
Очевидно, что добавление новых функций в расширение должно следовать нейм-конвенции этого расширения, чтобы нейминг был последовательным.
А если мы говорим про функции самого языка (core), то были добавлены array_find
, array_find_key
, array_any
, array_all
, grapheme_str_split
и проч.
PHP Typed: Маленький Composer пакет, который нарушает PHP правила ради вас