Comments 39
Очень давно, холиварщики сообщали, что PHP проще и поэтому лучше. С такими тонкостями, он уже не выглядит таким простым.
Неудачное название для функции, лучше бы strlen назвали datalen, а mb_strlen должна быть strlen
mb = multibyte
Вполне нормально.
Сбивает с толку количество. Интуитивно, хочется один strlen, а проблему кодировок отдать наоткуп какому-нибудь decode()
mb_
в первую очередь значит что это функция из расширения mbstring (а вот оно уже multibyte string), а не встроена в ядро
Нужно очень глубоко влезать, чтобы разобраться во всем этом. Если никогда не пользовался php, вызывает слишком много вопросов.
Это всё вызывает вопросы только у людей, которые не слышали про обратную совместимость и не застали доюникодную эпоху.
Схожие проблемы (причем, зачастую решенные еще хуже) — есть практически во всех языках. Корректно с юникодом умеет работать вообще только эликсир (потому что авторы не изобретали велосипед, а генерируют функции работы со строками на основе последней спецификации консорциума).
Чтобы проиллюстрировать проблему, можно, например, попробовать перевести в верхний регистр название турецкого города «стамбул», или греческое приветствие «яссас»:
iex|🌢|1 ▶ String.capitalize "istanbul", :turkic
"İstanbul" # опа, заглавное «İ» с точкой
iex|🌢|2 ▶ String.upcase "γειά σας", :greek
"ΓΕΙΆ ΣΑΣ" # последняя сигма не отличается от третьей с конца
iex|🌢|3 ▶ String.downcase "ΓΕΙΆ ΣΑΣ", :greek
"γειά σας" # для строчных последняя буква другая
Но это ладно, кого там волнуют греки и турки, правда? Тогда вот:
https://emojipedia.org/family-man-woman-girl-boy (надеюсь, Хабр нарисует этот эмоджик корректно): "👨👩👧👦"
Длина? — Попробуйте угадать:
javascript →
11
ruby →
7
python →
7
elixir →
1
Вероятно, Вы пропустили слова - если никогда не пользовался php
.
На одной чаше весов обратная совместимость, а на другой последние стандарты. В зависимости от ситуации, выбираем что-то одно.
Вероятно, Вы пропустили слова -
если никогда не пользовался php
.
Буквально весь мой комментарий про то, что PHP как таковой — тут вообще ни при чём. Это родовая травма всех языков, включая вполне себе современные го и раст.
Интуитивно, хочется один strlen
Дорого. Надо или каждый раз utf-8 парсить, или хранить в 32-битном UNICODE и придумать, что делать с графемами, которые отображаются как один символ, но состоят из нескольких.
При этом это еще и редко надо. Типичные задачи: понять, сколько места надо зарезервировать для строки, сконкатенировать несколько строк, найти в строке символ '/' - для них для всех простой Сишный strlen - то, что надо. А для отображения на экране нужен не умный strlen, а функция, которая вычислит экранный размер с учётом особенностей фонтов.
Типичные задачи: […]
Отрезать хвост, если не помещается в базу данных. Посчитать, влезет ли в куку. Отправить длину вместе со строкой в другой сервис.
Дорого в данном случае — это смешной аргумент. Медленно будет не здесь (особенно — в PHP). Единственная причина — обратная совместимость.
Для отрезания хвоста вообще нужна функция, которая принимает максимальный размер в байтах а режет по графемам.
принимает максимальный размер в байтах
Это еще почему? VARCHAR(30)
— зависит от кодировки таблицы и это не 30 байт в общем случае.
Смотря в каком диалекте SQL. Например, в MS SQL указывается именно размер в байтах. А уж сколько символов влезет - как получится.
Но по хорошему таки да, нужна ещё функция, принимающая максимальный размер в кодепоинтах.
Это родовая травма микрософтовских битв с юникодом, которые они все как одну проиграли, но в MSSQL есть NCHAR
/NVARCHAR
и они прекрасно подходят для хранения чего угодно по количеству графем.
Да, база распухнет почти вдвое, но на производительности это не скажется.
strlen - наследие Си
когда-то в доисторические времена, когда динозавры свободно бродили по улицам а код писался руками без ИИ-помощников и даже без подглядывания в гугл, это казалось совершенно очевидным...
Наследие C..
Это ж название функции из Си, которую просто прокинули в пых. Как и ещё половина пыховских API. За эргономику эти ребята практически не занимались длительное время и просто прокидывали сишные апишки.
PHP проще для тех, кто нормальных языков не видел.
Вы кстати тоже ошибаетесь, mb_strlen счтиает не в символах, а в кодпоинтах. В символах считает grapheme_strlen
> strlen("sincérité")
= 13
> mb_strlen("sincérité")
= 11
> grapheme_strlen("sincérité")
= 9
Стоит заметить, что и mb_strlen не гарантирует точного результата. Она вернёт не количество символов, а количество UTF-кодепоинтов. Есть ещё одна функция, возвращающая количество графем в строке - grapheme_strlen.
<?php
$emoji = "👩🏼👩🏼👦🏼👦🏼";
printf("strlen('%s') = %d\n", $emoji, strlen($emoji));
printf("mb_strlen('%s') = %d\n", $emoji, mb_strlen($emoji));
printf("grapheme_strlen('%s') = %d\n", $emoji, grapheme_strlen($emoji));
// strlen('👩🏼👩🏼👦🏼👦🏼') = 41
// mb_strlen('👩🏼👩🏼👦🏼👦🏼') = 11
// grapheme_strlen('👩🏼👩🏼👦🏼👦🏼') = 1
<?php
setlocale( LC_ALL, 'ru_RU.CP1251', 'rus_RUS.CP1251', 'Russian_Russia.1251' );
echo strlen('привет!');
файл, естественно, в кодировке 1251
Наиболее популярным ответом оказался «7», что неверно.
Будет ровно 7 - так что сначала нужно научиться четко ставить задачу.
От того, что вы задали локаль консоли, код автоматически не переехал из UTF-8 в cp1251 и по прежнему возвращает 13. Чтобы он вернул 7 надо не локаль менять, а сохранять исходник в cp1251.
Это само собой разумеется, ибо ставить локаль отличную от кодировки файла никто в здравом уме не будет, но я написал на всякий случай)
А задача должна быть сформулирована так "Что выведет echo strlen('привет!') в php, при кодировке файла с этой командой в UTF-8 и настройками локали PHP такими-то?".
Вопрос был на понимание строк. Да, условие не дает 100%-ой конкретики. Но может же быть вариант ответа "зависит от настроек/кодировок". И есть стандартные/распространенные настройки
Он и остался на понимание строк.
Может быть. Как и есть то, что ответ "7 неверно" - неверный ¯\_(ツ)_/¯
Стандартные/распространенные настройки очень от много зависят, например 7-10 лет назад очень много где было 1251 по-умолчанию и есть места, где это до сих пор так.
Поэтому все нужно проговоривать всегда - это снижает кол-во проблем до минимума.
just use the proper locale and have fun
В целом неплохо, но немного сумбурно и местами шероховато.
strlen() считает байты
а только что говорили, что на считает, а показывает уже посчитанное :)
Строки в PHP — не массивы: доступ через [] опасен для Unicode.
Хромает логика: то, что доступ опасен, следует не из того, что строки - это не массивы. А из байтовой натуры оператора []. Вместо двоеточия лучше поставить точку, а ещё лучше разнести в список.
Многие функции, применяемые к массивам, нельзя применить к строкам.
Вот тут даже интересно стало. "Многие" заведомо означает некоторые всё-таки можно. Это какие например?
Отличный коммент :)
Вот тут даже интересно стало. "Многие" заведомо означает некоторые всё-таки можно. Это какие например?
Я не имею ввиду какие-то специфичные только для массивов функции. isset, empty and etc - применимо к любому типу, и к строкам, и к массивам... В json_encode можно передать и то, и другое, и получить преобразование.
PHP - очень высокоуровневый язык с динамической типизацией. Это как бы сразу указывает на его аудиторию. Ещё сильнее делает акцент на аудиторию и скриптовый характер языка, и, особенно, то, что код на PHP - это фактически шаблон для вывода текста.
И что же у нас получается? Глядя на эту основную аудиторию, создатели языка не сделали функцию, само название которой говорит о подсчёте длины строки, универсальной для ASCII и Юникода... По-моему, это провал.
Я не знаю ни одного PHP программиста, который бы на начальном этапе своей карьеры не обжёгся хотя бы раз об юникод. Не говорит ли это о плохом дизайне?
И ведь уже ничего не изменить, ведь у нас "обратная совместимость", чтоб её...
А ведь могли бы при появлении проблемы с юникодом усложнить strlen с целью подсчёта символов, а новую функцию сделать именно для подсчёта байтов. Это же очевидно!!! И так же сделать с остальными подобными функциями. И все 10 человек, которые бы пользовались функцией подсчёта байтов в строке, были бы счастливы.
Подсчёт длины строки — это наименьшее из зол. Реальные приколы начинаются, когда нужно получить подстроку. Например, первые 50 символов текста сохранить в поле анонс. Если использовать substr, то мультибайтный символ может оказаться разрезанным пополам, и далее при записи в базу mysql вылетит с ошибкой.
Ну или ещё прикол:
var_dump(ucfirst('привет'));
выведет "привет" с маленькой буквы.
strlen() vs mb_strlen(): Почему 71% PHP-разработчиков ошибаются