Table of Contents

PHP RFC: Scalar Extension Methods

Introduction

The Extension Methods RFC lets code declare additional methods for existing classes and interfaces, resolved only where PHP would otherwise raise an undefined-method error. This RFC extends the same construct to the built-in value types:

extension string $s {
    public function length(): int { return strlen($s); }
}
 
var_dump("hello"->length());   // int(5)

Every previous attempt at “scalar methods” for PHP has foundered on the same rock: designing the API. Which methods does string get, what are they named, how do the legacy argument orders map — a bikeshed with no exit. This RFC dissolves that problem rather than solving it: PHP ships the mechanism and zero methods. Userland defines, names, versions, and ships the scalar APIs as ordinary Composer packages, and with the lexical visibility RFC competing scalar vocabularies cannot collide — each file imports the string API it wants. The language never has to bless anyone's length().

Why a separate RFC. The base RFC's implementation is contained to the method-resolution miss path: if it is buggy, only code calling extension methods is affected. Value receivers still touch more of the engine than that — dispatch on the non-object call path and a brief handoff through the call frame — even though the base RFC's declared-receiver model (adopted because of this RFC; see the base RFC's Rejected Features) reduced the footprint to a fraction of earlier designs. It is co-proposed and fully implemented, but it deserves to be reviewed and voted on its own merits rather than riding on (or dragging down) the base proposal.

Proposal

Targets

The five built-in value types become valid extension targets: string, int, float, bool (covering true and false receivers), and array. Scalar targets are matched on the raw unqualified name, never namespace-prefixed: extension string inside namespace App targets the built-in type, not App\string. All other reserved type names (iterable, mixed, object, null, ...) remain compile errors, as in the base RFC. null is deliberately not extendable — calls on null keep their existing error, and the nullsafe operator's short-circuit is unaffected.

Named extensions and use extension compose unchanged: extension Str on string $s { } is lexically gated exactly like a class-targeted named extension — which is precisely what lets competing userland scalar APIs coexist.

Semantics

  1. Dispatch. A method call on a scalar is unconditionally fatal in PHP today, so this is the base RFC's error-path-only principle in its purest form: resolution sits exactly where “Call to a member function on string” is raised, keyed by the receiver's value type. No successful program changes behavior.
  2. The declared receiver holds the value. extension string $s binds each method's receiver into $s — an ordinary by-value local, exactly like a parameter: mutation is local and does not propagate to the caller, which is PHP's value-type semantics stated in syntax. Chained calls — $s->shout() inside another string extension method — work naturally, and generators work (the receiver variable is populated before a generator's frame is captured).
  3. One restriction remains: first-class callables of scalar extension methods throw an Error for now — a closure cannot yet carry a by-value receiver binding. Object-targeted extension methods support them; extending that to scalars is Future Scope. ($this, static::, magic methods, and abstract methods are already rejected family-wide by the base RFC.)
  4. Direct calls only, by construction. Callable arrays require objects, so call_user_func and friends can never reach a scalar extension method: the base RFC's open question about indirect invocation does not arise for scalars.

Backward Incompatible Changes

None. Every call this RFC makes resolvable is a fatal Error today, and extension string { } is a compile error under the base RFC.

Proposed PHP Version(s)

Same release as the base Extension Methods RFC.

RFC Impact

To the Ecosystem

Tooling that learned the base RFC's blocks needs one addition: the five type names as targets, with methods surfaced in completion on receivers of the matching static type. The same static analysis that resolves extension calls on classes applies; there is no runtime discovery to model.

To Existing Extensions

The engine surface beyond the base RFC is small and narrow, and this section states it plainly:

To SAPIs

None beyond the engine changes.

To Opcache

Nothing new beyond the base RFC: the flag is compile-time state persisted with the op_array; the registry lanes reuse the base registry. Verified under opcache.protect_memory=1, file_cache, and both JIT modes with aggressive hotness settings (the configuration that exposed — and now regression-tests — a JIT'd FETCH_THIS unsoundness during development).

Open Issues

  1. Should int methods apply to float receivers (numeric unification), or stay strictly type-keyed as proposed?

Unaffected PHP Functionality

All existing scalar behavior: casts, operators, string functions, array functions, null handling, and the nullsafe operator. Objects and the base RFC's semantics are untouched. Programs that never declare a scalar extension cannot observe this RFC.

Future Scope

Voting Choices

Primary vote requiring a 2/3 majority (per the php/policies voting guidelines); void if the base Extension Methods RFC is declined:

Allow extension methods on the built-in value types as outlined in the RFC? Yes / No

Patches and Tests

Implemented on the extension-methods-scalars branch, stacked directly on the base RFC's extension-methods-phase1 branch — the diff between the two branches is exactly this proposal. Tests cover all five targets, literal/temporary/dynamic-name receivers, chained calls, value semantics, generators, namespaced scalar targets, and the first-class-callable restriction; the lexical-gating interaction ships with the visibility RFC's branch. Verified: full Zend suite green plain, under opcache.protect_memory=1, and under tracing and function JIT with jit_hot_*=1.

Implementation

After acceptance: link to the merged commit(s), PHP version, and documentation.

References

Rejected Features

Changelog