From 46ac56de4e012dc278018e30b93c5706790b86c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 29 Oct 2026 20:10:17 +0100 Subject: [PATCH 1/6] =?UTF-8?q?Add=20=E2=80=9CAn=20Investment=20in=20PHP?= =?UTF-8?q?=E2=80=99s=20Standard=20Library=E2=80=9D=20blog=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/_posts/2026-11-03-stdlib-investment.md | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 source/_posts/2026-11-03-stdlib-investment.md diff --git a/source/_posts/2026-11-03-stdlib-investment.md b/source/_posts/2026-11-03-stdlib-investment.md new file mode 100644 index 00000000..0c57e554 --- /dev/null +++ b/source/_posts/2026-11-03-stdlib-investment.md @@ -0,0 +1,207 @@ +--- +title: 'Designed Once, Usable Everywhere: An Investment in PHP’s Standard Library' +layout: post +tags: + - stories +author: + - tim-dusterhus +published_at: 3 November 2026 +--- + +All PHP projects have one thing in common, regardless of whether they are built +on a full-stack framework, on a microservice framework with carefully selected +standalone libraries, or as an extension for a CMS: the PHP standard library. +Functions such as `str_replace()` or `array_map()` and classes like +`DateTimeImmutable` or `Uri\Rfc3986\Uri` from PHP 8.5 are always available, +always work the same, and always have the same meaning. This makes PHP’s +standard library a “lingua franca”, enabling specialized standalone libraries +to seamlessly integrate into full-stack frameworks and allowing developers to +transfer knowledge and experience across the different parts of PHP’s +ecosystem. + +After the Random extension in PHP 8.2, the new DOM API in PHP 8.4 and the [URI +extension in PHP +8.5](https://thephp.foundation/blog/2025/10/10/php-85-uri-extension/), PHP 8.6 +continues to expand the vocabulary of its standard library by adding a +[`SortDirection` enum](https://wiki.php.net/rfc/sort_direction_enum) and the +[`Time\Duration` class](https://wiki.php.net/rfc/duration_class) amongst +others. Both of these are small and intentionally so. This post explores why +their value greatly exceeds their size. + +# Everyone Reinvents the Wheel + +When a concept as common as measuring elapsed time or sorting things in +different directions has no type in the standard library, every project tends +to invent its own solution to this common problem. Looked at individually each +of these inventions makes sense. But combining them into a coherent application +means that glue code needs to be added whenever crossing a boundary between two +different libraries. It also means that every developer needs to learn which +“dialect” a given library speaks. + +PHP itself is no exception. + +To pass an “amount of time”, PHP’s own standard library uses seconds +(`sleep()`), milliseconds (`CURLOPT_TIMEOUT_MS`) microseconds (`usleep()`), +nanoseconds (`hrtime()`), pairs of integers indicating seconds \+ a number of +fractional seconds (`stream_set_timeout()`), and floats (`microtime(true)`). + +For indicating the direction of sorting the situation is similar. In PHP’s +standard library there are integer constants `SORT_ASC` and +`SCANDIR_SORT_ASCENDING` that only work with specific functions, without any +type level indication. For sorting arrays there are pairs of functions that +only differ in the sort direction. Userland libraries use strings like `'ASC'` +or `bool` parameters to describe the same idea and libraries targeting PHP 8.1 +created their own enum. A GitHub search for `SortDirection` revealed more than +150 different \- incompatible with each other \- hits, not counting differently +named enums such as Doctrine’s `Order` enum. + +# Two New Dictionary Entries + +`SortDirection` is an enum with exactly two cases (`Ascending` and +`Descending`) and no associated behavior. It is fully described by four lines +of PHP code: + +```php +enum SortDirection { + case Ascending; + case Descending; +} +``` + +Implementing it in one’s own library is trivial, but using it across libraries +that each implement their own `SortDirection` enum would require complicated +babelfishes that map between the different enums. Since `SortDirection` is +guaranteed to always be available in PHP 8.6, libraries can use it in their +public APIs, only needing to translate it to a different representation when +serializing it for use in a SQL query or HTTP API. + +```php +$query->orderBy('created_at', SortDirection::Descending); +``` + +`Time\Duration` represents a period of time as measured by a stop-watch, from a +single nanosecond up to more than a hundred years with nanosecond precision +across the entire range. Its named constructors include the desired unit in +their name and do exactly what they say on the tin, so there is no more +guessing whether a `$timeout` parameter expects seconds, milliseconds, or moon +phases. + +Reading the value uses a pair of properties called `->seconds` and +`->nanoseconds` with an explicit `->negative` flag, similarly avoiding the +“magic number” problem. The new polling API that was developed as part of the +“[Streams +Project](https://thephp.foundation/blog/2025/10/30/php-streams-evolution/)” and +that is also included starting with PHP 8.6 is the first one to accept it. + +```php +$events = $pollContext->wait(Time\Duration::fromMilliseconds(250)); +``` + +# A Foundation for More to Come + +`Time\Duration` is already usable by itself, but is only intended to be the +first building block of a new date and time API built with the capabilities of +modern PHP. The next part of this new API is [already under +discussion](https://news-web.php.net/php.internals/132588). The [Time\\Instant +and Time\\Clock RFC](https://wiki.php.net/rfc/time_instant_class) proposes a +`Time\Instant` class representing points in time, together with a `Time\Clock` +interface to make testing time-dependent functionality easier. The distance +between two `Time\Instant`s is a `Time\Duration` and moving the point in time +takes a `Time\Duration` as input, building on the foundation already built and +fitting together like two well-oiled gears. + +# The Standard Library Is Worth the Investment + +An enum that can be written in just four lines of PHP and a class with three +properties and a handful of methods might not look like they deserve a +potentially lengthy RFC discussion on the PHP Internals mailing list when you +could build them yourself in less than an afternoon without coordinating with +anyone else. I think they are exactly the work that pays off the most in the +long run, because they give the ecosystem something new to rely on that +replaces custom implementations, rather than just a different way to write what +already exists. + +The demand for these types was already there. There were more than 150 +different hits for the 4-line enum and there are multiple userland libraries +and extensions that provide a class to measure stop-watch time. They are all +well-intentioned solutions to the same common problem. They are also all +incompatible with each other. A single design discussion on PHP Internals, even +if it comes with a higher initial investment, replaces all these efforts and +gives the ecosystem something to converge on. + +Having the discussion is important, since getting the design right matters more +for the standard library than for any userland package. PHP’s strong backwards +compatibility promise means that there rarely is a second attempt. Once +something has made it into a stable release even the tiniest break in +compatibility means thousands of affected users at PHP’s scale. + +The introduction of namespaces to PHP after a large part of PHP’s existing +standard library was built provided a rare opportunity for a second attempt by +allowing us to build the replacement API in an empty namespace without touching +the behavior of the existing API that PHP developers have come to rely on. +Users are then able to migrate to the new API at their own pace. This +methodology was previously used for the Random extension (`Random\*` replacing +`mt_rand()`, `shuffle()` et al), the DOM extension (`Dom\*` replacing `DOM*`) +and the URI extension (`Uri\*` replacing `parse_url()`) mentioned in the +introduction. + +To make the most of this opportunity the RFCs are deliberately taking small +steps, with a clearly spelled out future scope to allow for long-term planning +without committing to decisions too early. Keeping PHP internally consistent +means that every decision we make today constrains the ones that follow and +potentially rules out some paths entirely. A small API that is correct and +built with future evolution in mind is easy to extend. A large API that is +slightly wrong will be a nuisance for decades to come. + +The `Time\Duration` RFC showed how this looks in practice. Derick Rethans, +ext/date maintainer and a PHP Foundation contractor, and I wanted the new +polling API to take a `Time\Duration` timeout instead of a `$timeoutSeconds` +and `$timeoutMicroseconds` pair before the PHP 8.6 feature freeze made its +signature permanent. This left us with about two months from when we first +proposed the RFC. A sub-optimal API in a single method is a localized problem. +Replacing it with a reusable building block that is not quite right would fix a +small problem and create a big one. That’s why our proposal was limited to two +kinds of functionality: the first being the parts that we were confident about, +erring on the side of caution, the second being functionality that future +additions would rely on, but that could not be added later without breaking +existing expectations, such as support for negative durations. We also clearly +communicated this when starting the discussion, asking the PHP Internals +community to focus on real issues in their review rather than optional +convenience functionality, leaving those as future scope. The result is a new +class representing a generic concept that is useful *today* even if future +parts do not materialize, while leaving room for `Time\Instant` and whatever +follows. + +All this work is not free. Building functionality that unites the ecosystem +requires RFC authors doing research, coming up with a design and managing the +discussion. It also requires reviewers that are willing to spend their time +reading the RFC, providing insight based on their experience and use case and +potentially discussing naming for the third time until an agreement is reached. +The implementation of the discussion results needs to be written, reviewed and +maintained for long after. In the interest of transparency, [I work at +Tideways](https://tideways.com/open-source), which is a founding member and +silver sponsor of The PHP Foundation, and which sponsored part of my time +working on the `SortDirection`, `Time\Duration` and the current `Time\Instant` +RFCs. The PHP Foundation funds developers, like my co-author Derick, to do this +kind of work and it is a large part of why PHP’s standard library has kept +getting better in the last few years. + +Here’s how *you* can help: + +1. Try it: [Test PHP versions during their pre-release +period](https://thephp.foundation/blog/2026/08/17/how-to-contribute-to-php/#1.-test-prereleases), +focusing on new functionality, such as `Time\Duration`, and report any issues +you find, to help the feature make a good first impression on the community at +large after the gold release. +2. Accept it: Where possible, widen APIs in libraries you maintain to accept +the new types. A union type in a parameter allows you to do so without a +breaking change. This allows PHP developers to benefit from the clearer +terminology provided by the new classes, while continuing to rely on their +trusted libraries. +3. Shape it: Participate in the discussion of RFCs, such as [the one for +Time\\Instant](https://news-web.php.net/php.internals/132588). Any opinion, big +or small, provides value. +4. Fund it: If you cannot spend the time for (1) to (3) yourself, you can pay +others to spend the time, for example by convincing your employer to [sponsor +The PHP Foundation](https://thephp.foundation/sponsor/). Any improvement to PHP +itself will be paid back multiple times in improved developer experience. From 1676b9716ef2af8312b6a0f2f66c15be55cdc89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 2 Nov 2026 08:53:20 +0100 Subject: [PATCH 2/6] Fixes for source/_posts/2026-11-03-stdlib-investment.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tim Düsterhus --- source/_posts/2026-11-03-stdlib-investment.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/_posts/2026-11-03-stdlib-investment.md b/source/_posts/2026-11-03-stdlib-investment.md index 0c57e554..d7c703c2 100644 --- a/source/_posts/2026-11-03-stdlib-investment.md +++ b/source/_posts/2026-11-03-stdlib-investment.md @@ -28,7 +28,7 @@ continues to expand the vocabulary of its standard library by adding a others. Both of these are small and intentionally so. This post explores why their value greatly exceeds their size. -# Everyone Reinvents the Wheel +## Everyone Reinvents the Wheel When a concept as common as measuring elapsed time or sorting things in different directions has no type in the standard library, every project tends @@ -41,7 +41,7 @@ different libraries. It also means that every developer needs to learn which PHP itself is no exception. To pass an “amount of time”, PHP’s own standard library uses seconds -(`sleep()`), milliseconds (`CURLOPT_TIMEOUT_MS`) microseconds (`usleep()`), +(`sleep()`), milliseconds (`CURLOPT_TIMEOUT_MS`), microseconds (`usleep()`), nanoseconds (`hrtime()`), pairs of integers indicating seconds \+ a number of fractional seconds (`stream_set_timeout()`), and floats (`microtime(true)`). @@ -55,7 +55,7 @@ created their own enum. A GitHub search for `SortDirection` revealed more than 150 different \- incompatible with each other \- hits, not counting differently named enums such as Doctrine’s `Order` enum. -# Two New Dictionary Entries +## Two New Dictionary Entries `SortDirection` is an enum with exactly two cases (`Ascending` and `Descending`) and no associated behavior. It is fully described by four lines @@ -79,7 +79,7 @@ serializing it for use in a SQL query or HTTP API. $query->orderBy('created_at', SortDirection::Descending); ``` -`Time\Duration` represents a period of time as measured by a stop-watch, from a +`Time\Duration` represents a period of time as measured by a stopwatch, from a single nanosecond up to more than a hundred years with nanosecond precision across the entire range. Its named constructors include the desired unit in their name and do exactly what they say on the tin, so there is no more @@ -97,7 +97,7 @@ that is also included starting with PHP 8.6 is the first one to accept it. $events = $pollContext->wait(Time\Duration::fromMilliseconds(250)); ``` -# A Foundation for More to Come +## A Foundation for More to Come `Time\Duration` is already usable by itself, but is only intended to be the first building block of a new date and time API built with the capabilities of @@ -110,7 +110,7 @@ between two `Time\Instant`s is a `Time\Duration` and moving the point in time takes a `Time\Duration` as input, building on the foundation already built and fitting together like two well-oiled gears. -# The Standard Library Is Worth the Investment +## The Standard Library Is Worth the Investment An enum that can be written in just four lines of PHP and a class with three properties and a handful of methods might not look like they deserve a @@ -123,7 +123,7 @@ already exists. The demand for these types was already there. There were more than 150 different hits for the 4-line enum and there are multiple userland libraries -and extensions that provide a class to measure stop-watch time. They are all +and extensions that provide a class to measure stopwatch time. They are all well-intentioned solutions to the same common problem. They are also all incompatible with each other. A single design discussion on PHP Internals, even if it comes with a higher initial investment, replaces all these efforts and @@ -192,7 +192,7 @@ Here’s how *you* can help: period](https://thephp.foundation/blog/2026/08/17/how-to-contribute-to-php/#1.-test-prereleases), focusing on new functionality, such as `Time\Duration`, and report any issues you find, to help the feature make a good first impression on the community at -large after the gold release. +large after the stable release. 2. Accept it: Where possible, widen APIs in libraries you maintain to accept the new types. A union type in a parameter allows you to do so without a breaking change. This allows PHP developers to benefit from the clearer From 330b3eee7b03782df776fd2a6342060fc1b582c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 2 Nov 2026 18:52:16 +0100 Subject: [PATCH 3/6] Review fixes for source/_posts/2026-11-03-stdlib-investment.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ilija Tovilo Co-authored-by: Tim Düsterhus --- source/_posts/2026-11-03-stdlib-investment.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/_posts/2026-11-03-stdlib-investment.md b/source/_posts/2026-11-03-stdlib-investment.md index d7c703c2..3a8c80fc 100644 --- a/source/_posts/2026-11-03-stdlib-investment.md +++ b/source/_posts/2026-11-03-stdlib-investment.md @@ -32,7 +32,7 @@ their value greatly exceeds their size. When a concept as common as measuring elapsed time or sorting things in different directions has no type in the standard library, every project tends -to invent its own solution to this common problem. Looked at individually each +to invent its own solution to this common problem. Looked at individually, each of these inventions makes sense. But combining them into a coherent application means that glue code needs to be added whenever crossing a boundary between two different libraries. It also means that every developer needs to learn which @@ -52,7 +52,7 @@ type level indication. For sorting arrays there are pairs of functions that only differ in the sort direction. Userland libraries use strings like `'ASC'` or `bool` parameters to describe the same idea and libraries targeting PHP 8.1 created their own enum. A GitHub search for `SortDirection` revealed more than -150 different \- incompatible with each other \- hits, not counting differently +150 different, mutually incompatible hits, not counting differently named enums such as Doctrine’s `Order` enum. ## Two New Dictionary Entries @@ -81,13 +81,13 @@ $query->orderBy('created_at', SortDirection::Descending); `Time\Duration` represents a period of time as measured by a stopwatch, from a single nanosecond up to more than a hundred years with nanosecond precision -across the entire range. Its named constructors include the desired unit in -their name and do exactly what they say on the tin, so there is no more +across the entire range. Its named constructors spell out the desired unit +and do exactly what they say on the tin, so there is no more guessing whether a `$timeout` parameter expects seconds, milliseconds, or moon phases. -Reading the value uses a pair of properties called `->seconds` and -`->nanoseconds` with an explicit `->negative` flag, similarly avoiding the +Similarly, reading the value uses a pair of properties called `->seconds` and +`->nanoseconds` with an explicit `->negative` flag, avoiding the “magic number” problem. The new polling API that was developed as part of the “[Streams Project](https://thephp.foundation/blog/2025/10/30/php-streams-evolution/)” and From 125f52513af297ae40ac6edeb2fa3d5aebf7375c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 2 Nov 2026 18:52:38 +0100 Subject: [PATCH 4/6] Add additional headings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tim Düsterhus --- source/_posts/2026-11-03-stdlib-investment.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/_posts/2026-11-03-stdlib-investment.md b/source/_posts/2026-11-03-stdlib-investment.md index 3a8c80fc..4cf12a38 100644 --- a/source/_posts/2026-11-03-stdlib-investment.md +++ b/source/_posts/2026-11-03-stdlib-investment.md @@ -129,6 +129,9 @@ incompatible with each other. A single design discussion on PHP Internals, even if it comes with a higher initial investment, replaces all these efforts and gives the ecosystem something to converge on. + +## Planning for the Next Decades + Having the discussion is important, since getting the design right matters more for the standard library than for any userland package. PHP’s strong backwards compatibility promise means that there rarely is a second attempt. Once @@ -172,6 +175,9 @@ class representing a generic concept that is useful *today* even if future parts do not materialize, while leaving room for `Time\Instant` and whatever follows. + +## Making It Sustainable + All this work is not free. Building functionality that unites the ecosystem requires RFC authors doing research, coming up with a design and managing the discussion. It also requires reviewers that are willing to spend their time From b500c2b99620bfc56bcc7ec7d8a28d399a1b91b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 2 Nov 2026 19:02:10 +0100 Subject: [PATCH 5/6] Split long sentence in source/_posts/2026-11-03-stdlib-investment.md --- source/_posts/2026-11-03-stdlib-investment.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/_posts/2026-11-03-stdlib-investment.md b/source/_posts/2026-11-03-stdlib-investment.md index 4cf12a38..b45ecebd 100644 --- a/source/_posts/2026-11-03-stdlib-investment.md +++ b/source/_posts/2026-11-03-stdlib-investment.md @@ -138,9 +138,8 @@ compatibility promise means that there rarely is a second attempt. Once something has made it into a stable release even the tiniest break in compatibility means thousands of affected users at PHP’s scale. -The introduction of namespaces to PHP after a large part of PHP’s existing -standard library was built provided a rare opportunity for a second attempt by -allowing us to build the replacement API in an empty namespace without touching +Since namespaces were introduced after a large part of PHP’s existing +standard library was built, they provide a rare opportunity for a second attempt. The replacement API can sit in an as yet empty namespace without touching the behavior of the existing API that PHP developers have come to rely on. Users are then able to migrate to the new API at their own pace. This methodology was previously used for the Random extension (`Random\*` replacing From e0d6c07cea1d9c57e610e7ee7f0f3d98b927f199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 2 Nov 2026 19:05:23 +0100 Subject: [PATCH 6/6] Reflow markdown for source/_posts/2026-11-03-stdlib-investment.md --- source/_posts/2026-11-03-stdlib-investment.md | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/source/_posts/2026-11-03-stdlib-investment.md b/source/_posts/2026-11-03-stdlib-investment.md index b45ecebd..db4e0995 100644 --- a/source/_posts/2026-11-03-stdlib-investment.md +++ b/source/_posts/2026-11-03-stdlib-investment.md @@ -52,8 +52,8 @@ type level indication. For sorting arrays there are pairs of functions that only differ in the sort direction. Userland libraries use strings like `'ASC'` or `bool` parameters to describe the same idea and libraries targeting PHP 8.1 created their own enum. A GitHub search for `SortDirection` revealed more than -150 different, mutually incompatible hits, not counting differently -named enums such as Doctrine’s `Order` enum. +150 different, mutually incompatible hits, not counting differently named enums +such as Doctrine’s `Order` enum. ## Two New Dictionary Entries @@ -81,15 +81,13 @@ $query->orderBy('created_at', SortDirection::Descending); `Time\Duration` represents a period of time as measured by a stopwatch, from a single nanosecond up to more than a hundred years with nanosecond precision -across the entire range. Its named constructors spell out the desired unit -and do exactly what they say on the tin, so there is no more -guessing whether a `$timeout` parameter expects seconds, milliseconds, or moon -phases. +across the entire range. Its named constructors spell out the desired unit and +do exactly what they say on the tin, so there is no more guessing whether a +`$timeout` parameter expects seconds, milliseconds, or moon phases. Similarly, reading the value uses a pair of properties called `->seconds` and -`->nanoseconds` with an explicit `->negative` flag, avoiding the -“magic number” problem. The new polling API that was developed as part of the -“[Streams +`->nanoseconds` with an explicit `->negative` flag, avoiding the “magic number” +problem. The new polling API that was developed as part of the “[Streams Project](https://thephp.foundation/blog/2025/10/30/php-streams-evolution/)” and that is also included starting with PHP 8.6 is the first one to accept it. @@ -138,14 +136,14 @@ compatibility promise means that there rarely is a second attempt. Once something has made it into a stable release even the tiniest break in compatibility means thousands of affected users at PHP’s scale. -Since namespaces were introduced after a large part of PHP’s existing -standard library was built, they provide a rare opportunity for a second attempt. The replacement API can sit in an as yet empty namespace without touching -the behavior of the existing API that PHP developers have come to rely on. -Users are then able to migrate to the new API at their own pace. This -methodology was previously used for the Random extension (`Random\*` replacing -`mt_rand()`, `shuffle()` et al), the DOM extension (`Dom\*` replacing `DOM*`) -and the URI extension (`Uri\*` replacing `parse_url()`) mentioned in the -introduction. +Since namespaces were introduced after a large part of PHP’s existing standard +library was built, they provide a rare opportunity for a second attempt. The +replacement API can sit in an as yet empty namespace without touching the +behavior of the existing API that PHP developers have come to rely on. Users +are then able to migrate to the new API at their own pace. This methodology was +previously used for the Random extension (`Random\*` replacing `mt_rand()`, +`shuffle()` et al), the DOM extension (`Dom\*` replacing `DOM*`) and the URI +extension (`Uri\*` replacing `parse_url()`) mentioned in the introduction. To make the most of this opportunity the RFCs are deliberately taking small steps, with a clearly spelled out future scope to allow for long-term planning