PHP RFC: Forbid $this being null in instance methods
- Version: 1.0
- Date: 2016-04-29
- Author: Nikita Popov nikic@php.net
- Status: Withdrawn
- Target version: PHP 7.1
Introduction
Currently it is possible for $this
to be NULL inside a non-static method. This RFC proposes to remove this possibility, while still retaining compatibility with legacy PHP 4 code that performs static calls to non-static methods.
The primary case which may result in $this
being undefined (with an effective NULL value) occurs when a non-static method is called statically:
// Deprecated: Non-static method A::method() should not be called statically A::method(); class A { public function method() { // Uncaught Error: Using $this when not in object context $this->something(); } }
Note that the call throws a deprecation warning, but is allowed. However the use of $this
inside the method then throws an Error
exception. This RFC essentially proposes to throw the Error
exception already when A::method()
is called.
A common pattern for legacy PHP 4 code is to statically call non-static methods that do not use $this
(simply because PHP 4 had no concept of static methods). Under this RFC these calls continue to be allowed (with the existing deprecation warning). Only calls to methods actually using $this
are affected. For example the following remains valid:
// Deprecated: Non-static method A::method() should not be called statically A::method(); class A { public function method() { // No use of $this in here! } }
The goal of this RFC is to tighten guarantees that any reasonable person is expecting anyway, removing one of our largest remaining type-system loopholes. This RFC will ensure that within a non-static method the assertion $this instanceof self
never fails. Furthermore this eliminates a number of hot runtime checks and paves the way for future optimization (like method inlining).
Proposal
This section outlines the precise semantics of the proposal, which covers a slightly larger scope than outlined in the Introduction. In particular this RFC also encompasses rebinding of fake method-closures and also deals with concerns surrounding use of $this
in free functions (or other scope-free contexts), which are implementationally related.
Forbid static calls to non-static methods using $this
The primary change of this RFC is to forbid static calls to non-static methods that use $this
, while calls to non-static methods not using $this
continue to be allowed. The following examples illustrate which calls will be affected and which will behave as currently. The first two examples are repeated from the Introduction.
This A::method()
call is prohibited, because A::method()
uses $this
:
class A { public function method() { $this->foo(); } } // This will throw an Error exception, because A::method() uses $this A::method();
This A::method()
call is allowed, because A::method()
does not use $this
:
class A { public function method() { // No use of $this in here } } // This call is allowed and will only throw a deprecation warnings A::method();
This A::method()
call is allowed, because the $this
access is realized using variable-variables and cannot (generally) be detected at compile-time:
class A { public function method() { $name = 'this'; var_dump($$name); } } // This call is allowed, because the use of $this is not known at compile-time A::method();
This behavior is similar to many other existing restrictions that can be circumvented using variable-variables. For example assignments to $this
are prohibited at compile-time, but this restriction can also be avoided using variable-variables.
The following parent::method()
and A::method()
calls are allowed, because they are not static calls, but rather scoped instance calls. They will have a valid $this
(namely of class B
or a child class):
class A { public function method() { $this->foo(); } } class B extends A { public function method() { // This call is allowed, because it's not a static call (it only looks like one) parent::method(); // This call is also allowed, for the same reason A::method(); } }
Forbid unbinding $this from fake closures for methods using $this
Next to static calls to non-static methods there is another way how $this
may end up being NULL inside a method, namely closure rebinding of fake closures returned by ReflectionMethod::getClosure()
:
class A { public function method() { $this->foo(); } } $closure = (new ReflectionMethod('A', 'method'))->getClosure(new A); $closure = $closure->bindTo(null, 'A'); $closure();
For the case where A::method()
uses $this
, this operation will now be forbidden. Like all other rebinding failures, it will result in a warning and a false being returned.
Note that this does not affect ordinary closures in any way. Only closures obtained using ReflectionMethod::getClosure()
may be affected.
Alternative action: This check could be made more strict, by forbidding unbinding of $this even if the non-static method does not use $this
. As we have no legacy concerns here, it may be preferable to be stricter right away.
Remove special treatment of $this in non-scoped contexts
It is currently possible to use $this
as an ordinary variable in non-scoped contexts (e.g. free functions), however $this
will still be subjected to special treatment in some (but not all) operations. For example:
function test($this) { $this->prop = 42; } test(new stdClass);
This code will not generate compile-time errors and will also allow the function call, however the $this->prop
operation will throw an Error exception with message “Using $this when not in object context”. This is odd in that the $this
variable here does hold an object (which a var_dump would show), but the $this->prop
access still generates an error.
This RFC will allow the shown code to run without errors.
Alternative action: Alternatively we could completely forbid use of $this
in non-scoped contexts. An issue with this is that we would also have to forbid unbinding of $this
from closures that use it, which has unclear backwards compatibility implications.
Backward Incompatible Changes
This RFC is specifically designed to retain compatibility with patterns seen in legacy PHP 4 code. Calls are only prohibited if $this
is used in the method, as such we generally only generate an error in cases that would already error at a later point in time.
There is only one case I can imagine where this change might cause a genuine compatibility issue, namely code checking isset($this)
to determine whether a method was called statically or non-statically:
class A { public function test() { if (isset($this)) { echo "Called non-statically\n"; } else { echo "Called statically\n"; } } } (new A)->test(); // Prints: Called non-statically A::test(); // Prints: Called statically
This pattern will no longer be supported. (Actually it's still possible to do this using variable-variables for people with strong masochistic tendencies.)
Future Scope
In the future static calls to non-static methods may be completely forbidden (as they are already deprecated). However this change is not part of this RFC.
Vote
As this is a language change, a 2/3 majority is required. The vote will be a simple Yes/No vote.
Patches and Tests
Patch: https://github.com/php/php-src/pull/1894
The patch implements the changes described in this proposal and removes a large slew of NULL checks from the Zend VM.