====== PHP RFC: $this return type ======
* Date: 2021-09-07
* Author: Nikita Popov
* Status: Inactive
* Target Version: PHP 8.2
* Implementation: https://github.com/php/php-src/pull/7470
===== Introduction =====
The [[rfc:static_return_type|static return type]] introduced in PHP 8.0 allows specifying that a method must return an instance of the late-static-binding (LSB) scope, which is usually the case for named constructors, wither methods and fluent APIs. However, fluent APIs have a more specific contract that requires returning ''$this''. This RFC introduces a ''$this'' return type for this purpose.
The ''$this'' return type follows an established convention from PHPDoc, where ''@return $this'' is commonly used in place of ''@return static'' where it comes to fluent interfaces. On a dataset of 2k composer packages, ''@return $this'' occurs 29k times, while ''@return static'' is used 38k times.
The ''$this'' return type is arguably not a type, in that enforces not just the type of the return value (which is the same as ''static''), but also its object identity (that of ''$this''). The object identity is important to the usage of the API, because it determines whether the return value can be safely discarded:
// Definitely valid if methods return $this type (fluent API).
// May not be valid if they only return static (may be wither API).
$obj->method1()
->method2()
->method3();
// Definitely valid if methods return $this or static type (fluent/wither irrelevant).
$obj = $obj->method1()
->method2()
->method3();
As such, even if a method uses a native ''static'' return type, it currently may still have to specify ''@return $this'' in addition to clarify the API contract. This is especially relevant for interfaces, where this not only determines valid usage, but also valid implementation.
For example, Symfony's cache [[https://github.com/symfony/symfony/blob/8828d94796b8af4160cfc2d78565e8510835d76a/src/Symfony/Contracts/Cache/ItemInterface.php|ItemInterface]] specifies:
interface ItemInterface extends CacheItemInterface {
// Other APIs omitted.
/**
* @return $this
*/
public function tag(string|iterable $tags): static;
}
Under this RFC, it would become:
interface ItemInterface extends CacheItemInterface {
public function tag(string|iterable $tags): $this;
}
===== Proposal =====
The new ''$this'' type can only be used as a return type, not as a property or parameter type, same as ''static''. Additionally, it can only be applied in cases where an instance scope (potentially) exists, which is instance methods and non-static closures. It cannot be applied to free functions, static methods and static closures, as these can never have a ''$this'' value.
A method with ''$this'' return type is required to return ''$this'', though it does not need to do so literally. The following is legal code:
class Test {
public function method(): $this {
$that = $this;
return $that;
}
}
The ''$this'' type is a subtype of ''static''. As such, it is possible to restrict a ''static'' type to ''$this'' in a subclass (but not the other way around):
class A {
public function method1(): static {}
public function method2(): $this {}
}
class B extends A {
// This override is legal.
public function method1(): $this {}
// This override is illegal.
public function method2(): static {}
}
In reflection, the ''$this'' type is represented as a ''ReflectionNamedType'' with ''isBuiltin() == true'' and ''%%getName() == "$this"%%''.
===== Backward Incompatible Changes =====
There are no backwards-compatible changes, as ''$this'' is not a valid class name and could not be used in type position previously.
Code using ''ReflectionType'' may need to be updated to support the new ''$this'' type.
===== Vote =====
Yes/No.