Це переказ колонки Тіма Дюстерхуса (Tim Düsterhus, Tideways) у блозі PHP Foundation від 3 листопада 2026 року. Структура й технічні аргументи - авторові, скорочення - наші. Про самі нововведення PHP 8.6 ми вже писали в огляді «Що нового у PHP 8.6», тут ідеться про мотивацію, дизайн-рішення і про те, що буде далі.
Усі PHP-проєкти, хоч на повноцінному фреймворку, хоч на мікросервісному з ретельно дібраними бібліотеками, хоч у вигляді розширення для CMS, мають одну спільну річ - стандартну бібліотеку PHP. str_replace(), array_map(), DateTimeImmutable чи Uri\Rfc3986\Uri з PHP 8.5 доступні завжди, працюють однаково і означають одне й те саме. Тому стандартна бібліотека - це lingua franca екосистеми: завдяки їй окремі бібліотеки вбудовуються у фреймворки, а розробник переносить досвід з одного проєкту в інший.
Після розширення Random у PHP 8.2, нового DOM API у 8.4 і розширення URI у 8.5 PHP 8.6 поповнює цей словник enum SortDirection і класом Time\Duration. Обидва маленькі, і це навмисно. Автор пояснює, чому їхня цінність набагато більша за розмір.
Кожен винаходить колесо
Коли для поняття на кшталт «скільки минуло часу» чи «в який бік сортувати» немає типу в стандартній бібліотеці, кожен проєкт вигадує власний. Окремо кожне таке рішення має сенс. Але коли їх збирають в одному застосунку, на кожній межі між двома бібліотеками з'являється клей-код, а розробникові доводиться вивчати, яким «діалектом» говорить кожна з них.
Сам PHP тут не виняток. Щоб передати «проміжок часу», стандартна бібліотека використовує:
- секунди (
sleep());
- мілісекунди (
CURLOPT_TIMEOUT_MS);
- мікросекунди (
usleep());
- наносекунди (
hrtime());
- пару цілих чисел - секунди плюс дробова частина (
stream_set_timeout());
- числа з рухомою комою (
microtime(true)).
З напрямком сортування схожа картина. Є цілочисельні константи SORT_ASC і SCANDIR_SORT_ASCENDING, які працюють лише з певними функціями і ніяк не позначені на рівні типів. Для масивів є пари функцій, що відрізняються лише напрямком. Бібліотеки використовують рядки на кшталт 'ASC' або параметри типу bool, а ті, що орієнтуються на PHP 8.1, створили власні enum. Пошук SortDirection на GitHub дав понад 150 різних, взаємно несумісних збігів - і це без enum з іншими назвами, як-от Order у Doctrine.
Два нові слова у словнику
SortDirection - enum рівно з двома варіантами (Ascending і Descending) без жодної поведінки. Він повністю описується чотирма рядками:
enum SortDirection {
case Ascending;
case Descending;
}
Написати такий у своїй бібліотеці - дрібниця. Але щоб поєднати кілька бібліотек, кожна з яких має свій SortDirection, потрібні перехідники між однаковими за змістом enum. Оскільки в PHP 8.6 SortDirection доступний завжди, бібліотеки можуть сміливо використовувати його в публічних API, а в інше подання перетворювати лише тоді, коли треба серіалізувати значення для SQL-запиту чи HTTP API:
$query->orderBy('created_at', SortDirection::Descending);
Time\Duration - це проміжок часу, який показав би секундомір: від однієї наносекунди до понад ста років, з наносекундною точністю на всьому діапазоні. Іменовані конструктори прямо називають одиницю, тож більше не треба гадати, у чому параметр $timeout чекає значення - у секундах, мілісекундах чи фазах Місяця. Для читання є властивості ->seconds і ->nanoseconds та окремий прапорець ->negative, без «магічних чисел».
Першим API, яке приймає Time\Duration, став новий polling API з проєкту Streams, що теж входить у PHP 8.6:
$events = $pollContext->wait(Time\Duration::fromMilliseconds(250));
Фундамент для наступних кроків
Time\Duration корисний і сам по собі, але задуманий як перша цеглина нового API для дати й часу, побудованого на можливостях сучасного PHP. Наступна частина вже обговорюється у PHP Internals: RFC Time\Instant і Time\Clock пропонує клас Time\Instant для моментів у часі та інтерфейс Time\Clock.
Саме інтерфейс Clock найцікавіший для тих, хто пише тести. Код, який сам питає системний час, важко тестувати: результат залежить від того, коли запущено тест. Якщо ж такий код отримує годинник ззовні через інтерфейс, у тесті можна підставити свою реалізацію з потрібним моментом часу. Стандартний інтерфейс означає, що бібліотеки й фреймворки зможуть домовитися про одну точку підміни замість кожен свою.
Дві частини API складаються, як шестерні: відстань між двома Time\Instant - це Time\Duration, а щоб зсунути момент у часі, передають Time\Duration.
Чому це варте інвестиції
Enum на чотири рядки і клас із трьома властивостями та кількома методами можна написати самому за пів дня, ні з ким не домовляючись. Навіщо довге обговорення RFC? Автор вважає, що саме така робота окупається найбільше: вона дає екосистемі спільну опору, яка замінює власні реалізації, а не просто ще один спосіб написати те, що вже існує.
Попит був і раніше: понад 150 варіантів enum, кілька бібліотек і розширень із класом-секундоміром. Усі вони з добрих намірів розв'язують ту саму задачу - і всі несумісні між собою. Одне обговорення дизайну в PHP Internals, хоч і дорожче на старті, замінює всі ці зусилля і дає екосистемі те, до чого можна зійтися.
Планування на десятиліття вперед
Для стандартної бібліотеки правильний дизайн важить більше, ніж для будь-якого пакета. Через жорстку обіцянку зворотної сумісності PHP другої спроби майже не буває: щойно щось потрапило у стабільний випуск, навіть найменша несумісна зміна зачіпає тисячі користувачів.
Простори імен дають рідкісну можливість для другої спроби, бо значна частина стандартної бібліотеки з'явилася раніше за них. Новий API може оселитися в порожньому просторі імен, не чіпаючи поведінки старого, а користувачі переходять на нього у власному темпі. Так уже зробили з Random, DOM і URI (Random\*, Dom\* і Uri\* замість mt_rand(), DOM* і parse_url()).
Тому RFC свідомо роблять маленькі кроки з чітко описаним майбутнім обсягом робіт. Кожне сьогоднішнє рішення обмежує наступні, а деякі шляхи може закрити зовсім. Маленький правильний API, спроєктований з думкою про розвиток, легко розширити. Великий, але трохи хибний API муляє десятиліттями.
На практиці це виглядало так. Тім Дюстерхус і Дерік Ретанс (Derick Rethans), мейнтейнер ext/date і підрядник PHP Foundation, хотіли, щоб новий polling API приймав тайм-аут як Time\Duration, а не парою $timeoutSeconds і $timeoutMicroseconds, і встигнути до feature freeze PHP 8.6, після якого сигнатура стала б остаточною. Від першої пропозиції RFC лишалося близько двох місяців.
Невдалий API в одному методі - локальна проблема. А невдалий будівельний блок, який використовуватимуть усюди, розв'язав би малу проблему ціною великої. Тож пропозиція обмежилася двома видами функціональності:
- тим, у чому автори були впевнені, з перестраховкою;
- тим, на що спиратимуться майбутні доповнення і що не можна додати пізніше, не зламавши наявних очікувань.
Підтримка від'ємних тривалостей належить саме до другого виду. Якби перша версія Time\Duration вважала тривалість завжди невід'ємною, код почав би на це покладатися, і додати від'ємні значення згодом без поломки вже не вдалося б. Тому прапорець ->negative закладено одразу, а все зручне, але необов'язкове, автори прямо попросили спільноту PHP Internals не обговорювати і залишили на майбутнє. У результаті вийшов клас для загального поняття, корисний уже сьогодні, навіть якщо наступні частини так і не з'являться, і з місцем для Time\Instant та всього, що буде далі.
Як зробити це сталим
Така робота не безкоштовна. Авторам RFC треба дослідити тему, запропонувати дизайн і вести обговорення. Рецензентам - читати RFC, ділитися досвідом і, можливо, втретє сперечатися про назви, поки не дійдуть згоди. Реалізацію треба написати, перевірити і підтримувати ще довго. Автор відкрито пише, що працює в Tideways - компанії-засновниці та срібному спонсорі PHP Foundation, яка оплатила частину його часу на RFC SortDirection, Time\Duration і Time\Instant. А PHP Foundation фінансує розробників, як-от Деріка Ретанса, і це значною мірою пояснює, чому стандартна бібліотека PHP останніми роками стає дедалі кращою.
Що може зробити кожен
- Спробувати. Тестуйте PHP у період пре-релізів, зосереджуючись на нових можливостях на кшталт
Time\Duration, і повідомляйте про знайдені проблеми, щоб нові функції добре себе показали після стабільного випуску.
- Прийняти. Якщо ви підтримуєте бібліотеку, розширте її API, щоб воно приймало нові типи. Union type у параметрі дозволяє зробити це без несумісних змін - користувачі отримають зрозумілішу термінологію і лишаться на звичних бібліотеках.
- Вплинути. Долучайтеся до обговорення RFC, наприклад того, що про Time\Instant. Цінна будь-яка думка, велика чи мала.
- Профінансувати. Якщо на перші три пункти бракує часу, можна оплатити час інших - наприклад, переконати роботодавця стати спонсором PHP Foundation. Кожне покращення самого PHP багаторазово окупається зручнішою розробкою.
Для Laravel-розробників це теж не абстракція: щойно SortDirection і Time\Duration з'являться в API фреймворків і пакетів, на межах між ними поменшає перетворень рядків і чисел туди й назад.