Facepalms and WTF Moments

If you are into Star Trek and have watched The Next Generation, you will probably remember Q, an omnipotent superior being who appears on the Enterprise every one in a while, usually causing lots of trouble. One episode begins with Q showing up complaining that he has been punished by being turned into a human. This is when Captain Picard does a facepalm, allegedly for the first time in The Next Generation.

The facepalm, especially the one by Captain Picard, has become a famous internet meme, in fact so famous that a facepalm icon has even been added to Unicode. Try var_dump("\u{1F926}"); or better re-watch The Next Generation, it’s still fun.

To get back on topic: some things that exist in PHP, or used to exist in PHP, are definitely worth a facepalm. However, think twice before putting all the blame on the core developers: Allowing weird code constructs in a programming language is one thing, but actually using them is definitely also worth a good, long facepalm.

No More Cheating Using Parentheses

Many PHP core functions require arguments to be passed by reference, disallowing the inline use of return values from other functions. This makes a lot of sense for functions that modify the value of the argument alongside their operation:

$last = array_pop( range(0, 3) );
var_dump($last);

Because array_pop() removes the last element from the given array and effectively shortens it, passing a reference is required. In previous versions of PHP not adhering to this requirement triggered an E_STRICT notice:

PHP Strict standards:
Only variables should be passed by reference in ...
int(3)

Unknown to many developers, wrapping the nested function call into parentheses was a hacky but working way to avoid this notice:

$last = array_pop( (range(0,3)) );
var_dump($last);
int(3)

As this hack does neither address the underlying issue nor fix the logical problem, adding parentheses no longer affects the behavior in PHP 7. Additionally, due to the removal of E_STRICT, the error level got raised to E_NOTICE. Executing the example above with PHP 7 produces the following output:

PHP Notice:
Only variables should be passed by reference in ...
int(3)

Identical Parameter Names are no Longer Allowed

While technically of arguable usefulness, previous versions of PHP allowed function definitions to contain the same parameter name multiple times. With PHP 7 this is no longer supported, and the following declaration is considered invalid:

function f($a, $a)
{
    // ...
}

When executed, the code shown above will trigger the following error:

PHP Fatal error:
Redefinition of parameter $a in ...

Multiple Default Blocks in switch Statements

PHP in previous versions at least syntactically supported multiple default blocks in a given switch statement:

switch ($var) {
    // ...
    default: { var_dump('default #1'); }
    default: { var_dump('default #2'); }
}

Run with PHP 5.6, the following output would be produced, demonstrating that only the last default block would get executed:

string(10) "default #2"

As of PHP 7, this technically pointless construct is considered invalid and trying to run the above example results in a fatal error:

PHP Fatal error:
Switch statements may only contain one default clause in ...

Abusing $this

The special $this variable is used in methods to refer to the object on which they are operating. It was possible to reassign this variable before PHP 7.1 thus breaking a fundamental concept of object-oriented programming. Starting with PHP 7.1, this variable cannot be reassigned or overwritten.

Using the $this variable outside the scope of an object is no longer possible:

function f()
{
    var_dump($this);
}

f();

Executing the code shown above with PHP 7.0 triggered a notice and printed NULL:

Notice: Undefined variable: this in ...
NULL

PHP 7.1 or later triggers a runtime error instead:

Fatal error: Uncaught Error:
Using $this when not in object context in ...

Unsetting the $this variable using unset() is no longer possible:

unset($this);

Starting with PHP 7.1, the code shown above triggers a runtime error :

Fatal error: Cannot unset $this in ...

Using $this as a parameter is no longer possible:

function f($this)
{
}

Since PHP 7.1, the code shown above triggers a compile-time error:

Fatal error: Cannot use $this as parameter in ...

Also, using $this as a static variable is no longer possible:

function f()
{
    static $this;
}

In PHP 7.1 and newer, this is a compile-time error:

Fatal error: Cannot use $this as static variable in ...

You cannot use $this as a global variable:

function f()
{
    global $this;
}

Since PHP 7.1, any attempt to do so will be punished with a compile-time error:

Fatal error: Cannot use $this as global variable in ...

We know that things are getting weirder, but you will have to suffer through this with us. Assigning to $this in a catch statement is no longer possible since PHP 7.1:

try {
} catch (Exception $this) {
}

This does not even compile:

Fatal error: Cannot re-assign $this in ...

You can also not re-assign $this in a foreach statement:

foreach ($a as $this) {
}
Fatal error: Cannot re-assign $this in ...

Furthermore, reassigning $this by using variable variables or indirectly through references also no longer works. Functions such as extract() or parse_str() can also no longer overwrite $this.

If you have code that uses a variable named $this outside the scope of an object, then simply rename it.

Using get_class() on null

The get_class() function returns the name of the class of an object that is passed to it as an argument.

While it does not really make sense, PHP supports calling this function without an argument. When get_class() is used without an argument in the body of a method then it returns the name of the class the method which called get_class() belongs to.

class SomeClass
{
    public function method()
    {
        var_dump(get_class());
    }
}

$o = new SomeClass;
$o->method();

var_dump(get_class(new stdClass));

Executing the code shown above produces the output shown below:

string(9) "SomeClass"
string(8) "stdClass"

Prior to PHP 7.2, it was possible to pass null as an argument to get_class() when the function was used in the body of a method:

class SomeClass
{
    public function method()
    {
        var_dump(get_class(null));
    }
}

$o = new SomeClass;
$o->method();

PHP 7.1 does not complain:

string(9) "SomeClass"

Since version 7.2, PHP will issue a warning:

Warning: get_class() expects parameter 1 to be object,
null given in ...
bool(false)

Prior to PHP 7.2, it was already impossible to pass null as an argument to get_class() outside the context of a class:

var_dump(get_class(null));

This is the warning that PHP 7.1 produced:

Warning: get_class() called without object from
outside a class in ...
bool(false)

In PHP 7.2, the warning message has been updated:

Warning: get_class() expects parameter 1 to be object,
null given in ...
bool(false)

Inheritance Loophole for Static Properties

Static properties are shared between inheriting classes, except for static properties that are explicitly overridden in a child class.

Consider the following example:

class ParentClass
{
    public static $property = 0;
}

class ChildClass extends ParentClass
{
}

var_dump(ParentClass::$property);
var_dump(ChildClass::$property);

ParentClass::$property = 1;

var_dump(ParentClass::$property);
var_dump(ChildClass::$property);

ChildClass::$property = 2;

var_dump(ParentClass::$property);
var_dump(ChildClass::$property);

The parent class declares a public static property that is not explicitly overridden in the child class. Any change made to this property is visible no matter how, whether through ParentClass::$property or ChildClass::$property, it is accessed. Executing the code shown above prints the output shown below:

int(0)
int(0)
int(1)
int(1)
int(2)
int(2)

Previous versions of PHP had a bug that made it possible to separate ParentClass::$property from ChildClass::$property by using references:

class ParentClass
{
    public static $property = 0;
}

class ChildClass extends ParentClass
{
}

ChildClass::$property = &$variable;
$variable = 1;

var_dump(ParentClass::$property);
var_dump(ChildClass::$property);

Executing the code shown above printed the output shown below in previous versions of PHP:

int(0)
int(1)

The bug that allowed this loophole has now been fixed and executing the code shown above now prints the output shown below:

int(1)
int(1)

parent and classes without a parent

Using the parent keyword inside a class that does not have a parent class has been deprecated:

class C
{
    public function m(): void
    {
        parent::m();
    }
}

$o = new C;
$o->m(true);

Executing the code shown above with PHP 7.4 prints the output shown below:

PHP Deprecated: Cannot use "parent" when current class scope
has no parent in ...
PHP Fatal error: Cannot access parent:: when current class scope
has no parent in ...

The deprecation warning is triggered while the code is compiled, and the fatal error when the code is executed.

In PHP 8, the compiler will trigger a fatal error instead of just a deprecation warning:

Fatal error: Cannot use "parent" when current class scope
has no parent in ...

Static versus Non-Static Access

Methods as well as properties of a class can be declared static. Static properties share a common value among all instances of a certain class. Trying to access such a property dynamically will trigger an E_STRICT notice. To demonstrate this, in the following example a dynamic call is used to access a statically defined property. Contrary to common belief, it does not actually change the value of the static property but adds a dynamic property with the same name to the instance:

class Sample
{
    public static $prop = 'default value';
}

$obj1 = new Sample;
var_dump($obj->prop);

$obj1->prop = 'value set #1';
var_dump($obj->prop);

var_dump(Sample::$prop);

When executed with PHP 5.6 and full error reporting, the following output is generated:

Strict Standards:
Accessing static property Sample::$prop as non static in ...

Notice: Undefined property: Sample::$prop in ...
NULL

Strict Standards:
Accessing static property Sample::$prop as non static in ...

Strict Standards:
Accessing static property Sample::$prop as non static in ...
string(12) "value set #1"
string(13) "default value"

Despite complaining about the wrong way of accesssing the property, PHP returns NULL for the first read request to the property – indicating it does not exist – and dynamically adds it for the write request. As can be seen by the last line in the output, the value of the static property was not affected at all.

In PHP 7, the same general behavior is present, yet the E_STRICT message has been raised to an E_NOTICE:

Notice:
Accessing static property Sample::$prop as non static in ...

Notice:
Undefined property: Sample::$prop in ...
NULL

Notice:
Accessing static property Sample::$prop as non static in ...

Notice:
Accessing static property Sample::$prop as non static in ...
string(12) "value set #1"
string(13) "default value"

Funny enough, trying to reverse the approach and accessing a dynamic property statically is considered a fatal mistake in PHP 5 and PHP 7:

class Sample
{
    public $prop = 'default value';
}

Sample::$prop = 'value set #1';
PHP Fatal error:
Uncaught Error: Access to undeclared static property:
Sample::$prop in ...

Static calls to dynamic methods on the other hand are executed without complaint.

For historic reasons, PHP also supports the reverse, namely calling a non-static method statically. While this is currently still supported, static calls to non-static methods will get removed from PHP. To reflect this change, this type of call now emits an E_DEPRECATED.

class Sample {
    public function doSomething() {}
}

Sample::doSomething();

Executing the code shown above prints the output shown below:

Deprecated: Non-static method Sample::doSomething()
should not be called statically in ...

Only Variables should be Assigned or Passed by Reference

Using the ampersand (&), PHP can be forced to use a reference rather than actual values being passed around. While trying to assign a function or method call by reference does not make any practical sense, it still works:

$x =& rand(0, 1);

If encountered, PHP 7 will now emit an E_NOTICE. The same holds true for trying to pass a function by reference:

function reference(&$param)
{
}

reference(rand(0, 1));

setcookie() and Empty Names

The ability to set cookies from within an application is not exactly a new treat for PHP. In previous versions, the function setcookie() allowed the name parameter to be null or an empty string:

setcookie(null, 'abc');

When executed, PHP 5.6 will – among other headers – add the following line to the output:

Set-Cookie: =abc

Since this does not actually work from the HTTP standards perspective, PHP 7 now emits an E_WARNING whenever an empty name was supplied and does not add the nameless cookie to the headers:

PHP Warning:  Cookie names must not be empty in ...

Counting of Non-Countable Objects

How many elements does an array have? Just call count() on the array to find out:

var_dump(count([1, 3, 5, 7, 8]));

Unsurprisingly, the result is:

int(5)

When the array is wrapped into an object, make this object implement the Countable interface, and you can still call count(), which will automatically invoke the object’s count() method:

class Collection implements Countable
{
    private $items = [1, 3, 5, 7, 8];

    public function count()
    {
        return count($this->items);
    }
}

var_dump(count(new Collection));

Again, the result is:

int(5)

Like so often, PHP behaves a bit strangely in some edge cases. What happens, for example, when you call count() on a scalar value, like a string?

var_dump(count('string'));

One might expect the result to be 6, but it is not:

int(1)

You get the same result when trying to count an object that does not implement the Countable interface:

class Collection
{
    private $items = [1, 3, 5, 7, 8];
}

var_dump(count(new Collection));

Since PHP 7.2, PHP will generate a warning when trying to count non-countable objects or scalars:

Warning: count(): Parameter must be an array or an object
that implements Countable in ...

The function sizeof() is an alias to count()

var_dump(sizeof('string'));

… and it behaves just like count(), including the warning:

Warning: sizeof(): Parameter must be an array or an object
that implements Countable in ...

int(1)

Please note that there is no change to the behaviour of count() in PHP 7.2. However, your code will have to deal with the warning, which might be a problem, for example if your application has its own error handler that bails out on warnings.

Bareword (Unquoted) Strings

In PHP, constants are referenced using so-called bareword (or unquoted) strings:

print PHP_VERSION;

PHP tries to resolve such a string to a built-in or user-defined constant. As PHP_VERSION is a built-in constant, executing the code shown above produces the output shown below:

7.4.6

From PHP 3.0 up to and including version 7.1, PHP triggers an E_NOTICE and uses an unquoted string as a string when it cannot be resolved to a constant:

print NOT_A_CONSTANT;

Executing the code shown above with PHP 7.1 produces the output shown below:

Notice: Use of undefined constant NOT_A_CONSTANT -
assumed 'NOT_A_CONSTANT' in ...
NOT_A_CONSTANT

This behavior can lead to serious bugs. This is why, as of PHP 7.2, unquoted strings that do not reference a constant now trigger an E_WARNING instead of an E_NOTICE. Thus executing the code shown above with PHP 7.2 or newer and above produces the output shown below:

Warning: Use of undefined constant NOT_A_CONSTANT -
assumed 'NOT_A_CONSTANT' (this will throw an Error in a future
version of PHP) in ...
NOT_A_CONSTANT

Using unquoted strings that do not reference a constant will trigger an error in PHP 8.

Using Resources as Keys

PHP only supports strings or integer values as a key to specify the index position within an array. Trying to use anything else will lead to an E_WARNING. Or at least almost anything else. Before PHP 7, using a resource as a key when accessing an array element lead not to the expected warning but merely an E_STRICT notice. Even more confusing, the given resource identifier is type-cast to an integer value:

$fp = fopen(__FILE__, 'r');
$array = [];
$array[$fp] = 1;

The above code, when executed with PHP 5, leads to the following E_STRICT notice:

PHP Strict Standards:
Resource ID#5 used as offset, casting to integer (5)

While for backwards compatibility reasons this rather surprising behavior is still present, with PHP 7 it at least triggers a regular notice:

PHP Notice:
Resource ID#5 used as offset, casting to integer (5)

Working with References

Working with explicit, PHP-level references (the & operator), has always been somewhat problematic in PHP. PHP 4 forced us to use lots of & operators, because it would copy objects by default. The “proper” object semantics introduced with PHP 5 made all those & operators superfluous when working with objects, as we have previously pointed out in the chapter Assigning the Result of new by Reference.

Let aside some built-in PHP functions that work with references, for example sort() or preg_match(), there is really no need to work with explicit references in PHP.

In PHP 7.3, internal changes to reference handling change PHP’s behaviour in some edge cases that might be triggered by code that you probably should not have written. The changelog gives the following example:

$arr = [1];
$ref =& $arr[0];
var_dump($arr[0] + ($arr[0] = 2));

Honestly, we would not even know what result to expect, because we would never write such code, for the simple reason that we are not clever enough to figure out what the hell we would expect the result to be.

Accoding to the change log, starting with PHP 7.3, the result is

int(3)

whereas with PHP 5 and every PHP 7 version up to PHP 7.2.31, the result was

int(4)

The change in PHP’s behaviour is that the reference is being unwrapped on access, and that you now cannot modify a reference between accessing it and using its value. The changelog continues on to say “Please note that reading and writing a value inside a single expression remains undefined behavior and may change again in the future.”

Admittedly, we tried hard to come up with a different, more realistic example, but were unable to do so. So anything “regular” you are doing with references is still safe, you should just check your code for very creative constructs like the one shown above.

As with some other facepalms and WTF moments, this one also does also not imply that the PHP developers are incompetent or making irresponsible decisions. On the contrary: it makes perfect sense to clean up PHP, and warn devlopers that obscure code constructs might behave differently. Honestly, the facepalm aspect here is more: you really, really should not write code like this.

Your ultimate litmus test is this: show a piece of code to a number of developers. If they cannot agree on what the code does, the code is not good enough and should be rewritten to express its intent more clearly.