Какой могла бы быть Standard PHP Library

8e333fb82397c35580297c1dd5bcfbd9.jpg?v=1

Каждая статья с критикой PHP, следом за объяснением почему »$» в обозначении переменной портит синтаксис, обязательно расскажет вам о том, как плохо организовано именование функций в ядре и расширениях. И если »$» останется в стандарте языка и наших сердцах навсегда (хотя бы просто как напоминание о старом-добром PHP), то с именованием функций давно пора что-то делать.

TLDR;

Автор предлагает свои правила именования функций ядра PHP и распределение их по пространствам имен.

Standard PHP Library

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

Standard PHP Library (SPL) уже существует в качестве отдельного расширения. SPL предоставляет структуры данных, исключения, итераторы и многое другое, но к базовым функциям, например к функциям для работы со строками, никакого отношения не имеет. Они, на мой далёкий от устройства интерпретатора PHP взгляд, просто существуют где-то в ядре и являются как бы частью стандарта языка.

Я считаю, что базовые функции следует выделить в отдельное расширение и самым подходящим для него названием стало бы именно Standard PHP Library.

Проблемы

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

Наиболее заметные проблемы:

  1. одновременно существуют несколько подходов к именованию: с префиксом »‎str_» (str_split), с префиксом »‎str» (strrev), с префиксом »‎substr_» (substr_compare), без какого-либо префикса (trim);

  2. однородные функции имеют аргументы с разными именами, например, в strncmp два аргумента:»‎str1» и »‎str2», в strnatcasecmp:»‎string1» и »‎string2», а в substr_compare:»‎haystack» и »‎needle»;

  3. однородные функции имеют разный порядок следования однородных же аргументов — все помнят историю про порядок аргументов в implode и explode;

  4. совершенно нечитаемые и непроизносимые имена функций: strtr, strncmp, strpbrk, strrchr и т.д.;

  5. некоторые функции могут менять тип возвращаемого значения в зависимости от типов переданных им аргументов, например, substr_replace непринужденно жонглирует массивами и строками;

  6. зачастую функции возвращают false в случае ошибки или неверно заданных аргументов, например, strrpos;

  7. некоторые функции выглядят довольно экзотично и используются крайне редко: soundex, levenshtein и т.д.

Причины

Не берусь утверждать, что я точно знаю чем это вызвано. Хотя уверен, что мое предположение будет недалеко от истины. Все ошибки на проектах, застигнутых внезапной лавинообразной популярностью, объединяет одна причина — »‎так исторически сложилось».

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

Я прекрасно понимаю, что функции есть и они успешно работают в огромном множестве проектов, а эстетика написания кода — дело десятое. Более того, я понимаю, что взять на себя ответственность за решение, которое, вероятно, оттолкнет от PHP часть старых и новых пользователей, довольно сложно. Так есть ли шансы? Я вижу некоторые робкие шаги в этом направлении, но пока нет ничего, что попало бы в релиз.

Как бы это сделал я.
  1. PHP 8.2 — создать и принять новый регламент об именовании функций и их аргументов;

  2. PHP 8.3 — добавить новые функции согласно регламента, старые оставить в качестве псевдонимов, выпустить инструмент, который преобразует старые вызовы в новые;

  3. PHP 9 — объявить старые функции deprecated, вынести новые и старые функции в отдельные расширения, при этом legacy расширение по умолчанию не будет идти в составе сборки,

  4. PHP 10 — end of life для legacy расширения.

Учитывая, что между 7 и 8 версиями пролетело каких-то 5 лет, я думаю, что 10 лет экосистеме хватит на переход.

Что я предлагаю

Выделить функции из состава ядра в отдельные расширения

Отдельные расширения проще тестировать и их релизы можно выпускать отдельно от больших релизов языка. Здесь я ничего нового не скажу — преимущества модульности давно известны.

Добавить пространства имен для расширений

Да, пространства имен нужны и для ядра, и для расширений. Довольно странно создавать стандарты, которые будут различаться для пользовательского кода и кода, предоставленного создателями языка. И если для ядра я еще могу представить корневое »‎\», то для расширений без них никак не обойтись.

Я не буду выдумывать что-то новое и просто использую PSR-4. В качестве имени вендора очевидно следует использовать »‎Php», а дальше добавить имя расширения. Таким образом, будущие функции стандартной библиотеки будут в пространстве имен »‎Php\Spl».

Договориться о правилах именования функций и аргументов

Снова предлагаю существующие стандарты: частично использовать PSR-12 и частично правила именования функций от дядюшки Боба.

  1. Имена функций не должны содержать какого-либо явного префикса расширения, в котором определены. Иными словами, не должно быть никаких  »‎str_» или »‎str», для этих целей служит пространство имен.

  2. Имена функций и аргументов должны быть заданы в lowerCamelCase.

  3. Имена функций должны быть читаемыми и содержать в себе явное описание выполняемого действия. Уместно использовать для этого некий глагол.

  4. Имена аргументов также должны быть читаемыми и содержать в себе явное описание передаваемых данных. Уместно использовать в качестве имен аргументов существительные.

  5. Порядок и имена аргументов для однородных функций, например для функций сравнения строк, должны совпадать.

Включить строгую проверку типов данных

Тут давно пора навести порядок. Например sscanf может возвращать массив, целочисленный результат или null. Я уверен, что взрослый язык может позволить себе согласовать типы аргументов и возвращаемых значений для функций стандартной библиотеки.

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

  1. Функции стандартной библиотеки должны использовать строгую проверку типов аргументов и возвращаемых значений.

  2. Для аргументов и для возвращаемых значений должны быть явно указаны типы. Использование »‎mixed» недопустимо.

  3. Для аргументов допустимо использовать union-типы и nullable-типы.

  4. Возвращаемое значение должно иметь один явный тип или быть »‎null» там, где это оправданно.

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

Практическая реализация

В качестве proof of concept я создал библиотеку, которая следует всем правилам, указанным выше, и предлагает новые имена функций для работы со строками — https://github.com/marvin255/php-spl-proposal/blob/master/src/StringUtilities.php.

Первый и главный вопрос реализации — использовать отдельные функции в пространстве имен или статические методы класса.

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

Пара примеров для ознакомления.

strnatcmp("test1”, "test2”);
// заменяется на
StringUtilities::compareUsingNaturalOrder("test1”, "test2”);
str_contains(”test”, "t”);
// заменяется на
StringUtilities::contains(”test”, "t”);

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

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

Вместо заключения

Статья не претендует на какую-либо объективность. Более того, очевидно, что я не смог за пару вечеров исправить один из коренных недостатков PHP.

Я вижу как данную проблему из релиза в релиз обходят стороной в погоне за новыми фичами и синтаксическим сахаром. Не подумайте, я не имею ничего против изменений, введенных в PHP 7 и 8, но и для возвращения технического долга нужно найти время.

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

© Habrahabr.ru