The elevator pitch for the RFC. The first paragraph of this section will be rendered slightly larger to give it emphasis.
Please write an introduction that helps people by providing concise context and include a representative small code snippet.
We propose allowing a named parameter this: to be combined with the “scope resolution operator” syntax of referring to a method to be used as an alternative way of calling an instance method. The value passed to the this: parameter will effectively be used as the left side of the -> operator:
Since the this: parameter does not refer to a parameter in the underlying argument list and thus there is no ambiguity with regard to positional parameters it may be placed at any position of the parameter list. In particular it may appear at the first position, before all positional parameters, mirroring the left-most position for a regular method call.
$now = new DateTimeImmutable(); $formatted = DateTimeImmutable::format(this: $now, "c"); // is effectively equivalent to $formatted = $now->format('c');
The semantics follow that of a “wrapper function” copying all parameter names, parameter types and the return type of the method specified in the method reference. The parameter type of the this: parameter is the class name that is specified in the method reference. If the method is overridden in a child class, the overridden method will be called, not the method that is specified.
This also means that it is legal to specify an interface method and provide any object implementing the interface.
$formatted = DateTimeInterface::format(this: new DateTimeImmutable(), "c");
Since the semantics follow that of a “wrapper function”, the signature does not adapt to the object that is actually passed. If a parameter is widened in a child class only the original types remain valid. If a parameter is renamed, the parameter name of the specified method must be used.
interface MyInterface { public function method(string $foo): int|string|bool; } class ParentClass implements MyInterface { public function method(string|array $foo): string { return 'parent'; } } class ChildClass extends ParentClass { public function method(string|array $bar): string { return 'child'; } } // Throws TypeError, since an array is not a valid parameter on the interface, // despite being valid on ChildClass::method(). MyInterface::method(this: new ChildClass(), []); // Throws an Error about an unknown parameter 'bar', because the parameter // is called 'foo' on ParentClass::method(). ParentClass::method(this: new ChildClass(), bar: []); // Calls ChildClass::method(). var_dump(ParentClass::method(this: new ChildClass(), foo: 'bar')); // string(5) "child"
It is however not allowed to provide an object that is not contravariant with the specified class name, even if it implements a method with a compatible signature:
final class NotDateTimeImmutable { public function format(string $format): string { } } // Throws a TypeError. $formatted = DateTimeImmutable::format(this: new NotDateTimeImmutable(), "c");
Default values are taken from the method that is actually being executed, not the method that is specified in the method reference. There are two reasons for these semantics:
class ParentClass { public function dump(string $x = 'ParentClass') { var_dump($x); } } class ChildClass extends ParentClass { public function dump(string $x = 'ChildClass', string $y = 'extra parameter') { parent::dump($x); var_dump($y); } } ParentClass::dump(this: new ChildClass()); // string(10) "ChildClass" // string(15) "extra parameter"
There is one exception from the rule that the child class' method will be called if it is overridden. For a call ClassName::method(this: $object) if:
ClassName matches that of a parent (or grandparent, grandgrandparent, …) class of the current class (is_subclass_of(self::class, ClassName::class)).$object is an instance of the current class ($object instanceof self).
The method() defined in ClassName will be called, not $object->method().
class GrandparentClass { public function method() { echo "Grandparent"; } } class ParentClass extends GrandparentClass { public function method() { echo "Parent"; } } class Example extends ParentClass { public function method() { echo "Example"; } public function sameClass() { // Existing behavior: Echos "Grandparent". GrandparentClass::method(); // this: parameter with $this as value. Also echos "Grandparent", // mirroring the existing behavior, but making it clear that // GrandparentClass::method() is not a static method call by // explicitly providing $this. GrandparentClass::method(this: $this); // The same happens when specifying a different object instance // of MyClass. This also echos "Grandparent". GrandparentClass::method(this: new MyClass()); } public function childClass() { // Existing behavior: Echos "Grandparent". (new ChildClass())->bypassOverridden(); // this: parameter with an instance of ChildClass as value. // Also echos "Grandparent", mirroring the existing capabilities. GrandparentClass::method(this: new ChildClass()); } private function bypassOverridden() { GrandparentClass::method(); } public function parentClass() { // It however does not happen when specifying an object instance // of a parent class. // This echos "Parent". GrandparentClass::method(this: new ParentClass()); } } class ChildClass extends Example { public function method() { echo "Child"; } }
For the this: parameter to be recognized it needs to be explicitly specified. Dynamically passing it using argument-unpacking of an array or Traversable with a key 'this' will not result in an instance call. The same applies for dynamic calls using call_user_func() and call_user_func_array(). There are two reasons for this:
'this' parameter name to be passed into a variadic parameter.final class Example { public function instanceCall(...$args) { var_dump($args); } public static function staticCall(...$args) { var_dump($args); } } Example::instanceCall(this: new Example(), ...['this' => 'this', 'foo' => 'bar']); // array(2) { // ["this"]=> // string(4) "this" // ["foo"]=> // string(3) "bar" // } Example::staticCall(...['this' => new Example(), 'foo' => 'bar']); // array(2) { // ["this"]=> // object(Example)#1 (0) { // } // ["foo"]=> // string(3) "bar" // } call_user_func_array(['Example', 'staticCall'], ['this' => new Example(), 'foo' => 'bar']); // array(2) { // ["this"]=> // object(Example)#1 (0) { // } // ["foo"]=> // string(3) "bar" // }
Virtual methods using __call() are supported as expected.
Free-standing functions however may not be called with this:.
function example() { } example(this: new stdClass()); // Not allowed.
Although not particularly useful, static methods are supported, similarly to how it is legal to call static methods using the -> operator. The static::class value matches the class of the passed object:
class ParentClass { public static function staticMethod() { var_dump(static::class); } } class ChildClass extends ParentClass { } ParentClass::staticMethod(this: new ChildClass()); // string(10) "ChildClass"
It remains illegal to define a parameter called $this:
final class Example { public static function staticMethod( $this, // Not allowed. ) { } }
The this: parameter may be combined with partial function application to create a partial function where the “object instance” will be filled in at call time. Specifying the this: parameter outside the left-most position is particularly useful for partial function application, since it allows to specify the position of the object instance parameter relative to regular parameters. If a variadic placeholder is specified it must come last.
$c = DateTimeImmutable::format(this: ?, "c"); $c = DateTimeImmutable::format(this: ?, format: "c"); // are both effectively equivalent to: $c = static fn (DateTimeImmutable $__this): string => $__this->format('c');
$c = DateTimeImmutable::setTimestamp(this: ?, ?); $c = DateTimeImmutable::setTimestamp(this: ?, timestamp: ?); // are both effectively equivalent to: $c = static fn (DateTimeImmutable $__this, int $timestamp): DateTimeImmutable => $__this->setTimestamp($timestamp); // and $c = DateTimeImmutable::setTimestamp(this: ?, ...); // is effectively equivalent to: $c = static fn (DateTimeImmutable $__this, int $timestamp): DateTimeImmutable => $__this->setTimestamp($timestamp, ...array_slice(func_get_args(), 2)); // but $c = DateTimeImmutable::setTimestamp(?, this: ?); $c = DateTimeImmutable::setTimestamp(timestamp: ?, this: ?); // are both effectively equivalent to: $c = static fn (int $timestamp, DateTimeImmutable $__this): DateTimeImmutable => $__this->setTimestamp($timestamp);
If the this: parameter is placed after optional parameters, they will become required.
$c = DateTimeImmutable::setTime(this: ?, ?, ?, ?, ?); // is effectively equivalent to: $c = static fn (DateTimeImmutable $__this, int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTimeImmutable => $__this->setTime($hour, $minute, $second, $microsecond); // but $c = DateTimeImmutable::setTime(?, ?, ?, ?, this: ?); // is effectively equivalent to: $c = static fn (int $hour, int $minute, int $second, int $microsecond, DateTimeImmutable $__this): DateTimeImmutable => $__this->setTime($hour, $minute, $second, $microsecond);
The method lookup rules for direct calls apply equally to partial function application. The self at the time of the PFA declaration is relevant.
class GrandparentClass { public function method() { return "Grandparent"; } } class ParentClass extends GrandparentClass { public function method() { return "Parent"; } } class Example extends ParentClass { public function method() { return "Example"; } public static function run($objects) { return array_map(GrandparentClass::method(this: ?), $objects); } public static function runWithPfa($pfa, $objects) { return array_map($pfa, $objects); } public static function getPfa() { return GrandparentClass::method(this: ?); } } class ChildClass extends Example { public function method() { return "Child"; } } $objects = [ new GrandparentClass(), new ParentClass(), new Example(), new ChildClass(), ]; // PFA is defined and called within Example. // Echos "Grandparent, Parent, Grandparent, Grandparent" echo implode(", ", Example::run($objects)); // PFA is defined within Example and called within the global scope. // Echos "Grandparent, Parent, Grandparent, Grandparent" echo implode(", ", array_map(Example::getPfa(), $objects)); // PFA is defined and called within the global scope. // Echos "Grandparent, Parent, Example, Child" echo implode(", ", array_map(GrandparentClass::method(this: ?), $objects)); // PFA is defined within the global scope and called within Example // Echos "Grandparent, Parent, Example, Child" echo implode(", ", Example::runWithPfa(GrandparentClass::method(this: ?), $objects));
There are two main use cases. The primary use case is enabling partial function application for the object instance:
<?php $dates = [ new DateTimeImmutable('now'), ]; $formattedDates = array_map(DateTimeImmutable::format(this: ?, "c"), $dates); echo implode(", ", $formattedDates), PHP_EOL; // 2026-02-08T19:00:00+00:00
// Partially applying $this for the 'choice_label' with Symfony Form’s ChoiceType // https://symfony.com/doc/current/reference/forms/types/choice.html#advanced-example-with-objects $builder->add('category', ChoiceType::class, [ 'placeholder' => false, 'choices' => [ new Category('Cat1'), new Category('Cat2'), new Category('Cat3'), new Category('Cat4'), ], 'choice_label' => Category::getName(this: ?), /* … */ ]);
The other is making calls to grandparent methods explicit:
<?php class GrandparentClass { public function method() { echo "Grandparent"; } } class ParentClass extends GrandparentClass { public function method() { echo "Parent"; } } class Example extends ParentClass { public function method() { // Call instance method on grandparent. GrandparentClass::method(this: $this); } }
While $this is a reserved variable name, thus is guaranteed to never be a valid parameter name, it may nevertheless be used as a named parameter name to use as a key for a variadic parameter:
<?php function dump(...$args) { var_dump($args); } dump(this: 'this'); // array(1) { ["this"]=> string(4) "this" }
If this RFC is accepted, this would fail with a clear compile-time error for non-methods. For methods it will fail at runtime, unless the value passed to this: happens to be of the correct type. Users are still able to use "this" as a key in the resulting array by using the array unpacking syntax to pass parameters:
dump(...['this' => 'test']);
Seifeddine Gmati scanned 18975 Packagist packages containing 626044 files with Mago without finding any usage of this: as a named parameter. For reference, self: also had zero usages, object: had 32 and static: had 6.
Given that the backwards compatibility break has a direct workaround that does not require complex tooling combined with the non-existent usage in public libraries, we expect the impact to be minimal in practice.
Next PHP 8.x (8.6).
IDEs and Static Analyzers need to understand the special semantics of the this: parameter to provide proper diagnostics:
$this value in an instance callIt does now introduce any new syntax, thus files making use of the feature continue to parse correctly and incorrect diagnostics are localized to the calls making use of the feature.
None.
None.
None.
This section should outline areas that you are not planning to work on in the scope of this RFC, but that might be iterated upon in the future by yourself or another contributor.
This helps with long-term planning and ensuring this RFC does not prevent future work.
Primary Vote requiring a 2/3 majority to accept the RFC:
tbd
After the RFC is implemented, this section should contain:
None.