Language Features

Global __autoload() Function

Autoloading may be one of the most important features added in PHP 5. Before autoloading, we would have to make sure all classes and interfaces that we would use were defined. Since the order of execution of multiple PHP scripts can be different, this effectively made us write loads of require_once statements on top of each PHP file, just to make sure everything that we needed had already been loaded and was available.

With autoloading, PHP 5.0 introduced a functionality that would allow us to get rid of all those require_once statements. When a global function named __autoload was defined, whenever a certain class or interface was used for the first time, the __autoload() function would be called with the name of the class or interface. The autoload function was then responsible for loading the appropriate code.

Of course, there can be only one global function named __autoload, meaning that there could be only one function responsible for autoloading. This design limitation of autoloading was overcome in PHP 5.1 with the introduction of spl_autoload_register(). This function allows the registration of multiple autoloaders, which makes perfect sense if you integrate your code with a third-party library that has its own autoloader.

In PHP 7.2, the original __autoload() mechanism has been deprecated, and it will be removed in PHP 8. If your application still uses an __autoload() function, just rename it, and use spl_autoload_register() to register this function as an autoloader.

create_function()

The function create_function() has been deprecated in PHP 7.2 and will be removed in PHP 8. Since PHP 5 introduced anonymous functions, the create_function() has not really been relevant anymore, but may still be used in legacy code bases. Before PHP 5.3, create_function() was the only way to define an anonymous function. You had to pass a string which contained the source code of the function. This, of course, is error-prone, because it prevents your IDE from inspecting the code.

To get your code ready for the future, just replace all create_function() calls by contemporary anonymous function definitions. If your code needs to build PHP source code on the fly, you should probably rethink your design, because you are effectively using eval(), which makes code impossible to test, and can open up gigantic security holes.

assert() with String Argument

Before PHP 7, the language has never had a very strong support for design-by-contract. There was an assert() function, but you had to pass a string containing source code that would get evaluated at runtime. This was tedious and very error-prone, because, among other things, it prevented your IDE from inspecting the code, warn you about errors, or offer auto-completion.

In PHP 7, assertions can contain “proper” PHP code; we cover this in the “Assertions” section in greater depth. As of PHP 7.2, passing a string to assert() is deprecated, which means that this feature will be removed in PHP 8. Make sure to start converting your assertions –if you use any– now, to get your code ready for PHP 8.

Luckily, in most cases this change is really trivial, it can literally be as simple as removing the quotes.

PHP 4-Style Constructors

In PHP 4 a method that had the same name as the class in which it was declared was the constructor of that class. PHP 5 introduced __construct as a unified name for constructors, but still allowed using the naming convention from PHP 4.

As of PHP 7, PHP 4-style constructors are deprecated:

class C
{
    public function C()
    {
        // ...
    }
}
Deprecated:
Methods with the same name as their class will not be
constructors in a future version of PHP; C has a deprecated
constructor in ...

One of the reasons why the old naming convention was a bad idea is that when renaming the class, you also need to rename its constructors. This is tedious and error-prone.

You should start to rename any old-style constructors to __construct().

track_errors Configuration Directive

Today, PHP is a multi-paradigm programming language that supports procedural as well as object-oriented programming. Even functional programming is supported to some extent. But this was not always the case. Originally, PHP was a procedural language that at some point happened to have a class keyword. Support for “real” object-orientation was added in PHP 5.0, along with exceptions for error handling. Before PHP 7, built-in functions never threw exceptions. As we have explained in the “Error Handling” chapter, nowadays PHP uses exceptions rather consistently.

Back when PHP did not support exceptions and you tried to connect to a database, the result would either be a resource that allowed you to communicate with that database, or false to signal that an error had occurred. But what went wrong? To enable developers to find out, PHP used a feature called error tracking. This feature had to be enabled in php.ini by setting the track_errors directive to true. This made PHP store the last error in the special variable $php_errormsg. You were still left to parsing that error message to find out what had gone wrong, though.

Although PHP supports proper error handling through exceptions since version 5, this error tracking mechanism was never removed. As of PHP 7.2, it is now finally deprecated. You should use exceptions and try/catch statements as described in the “Error Handling” chapter instead of relying on $php_errormsg.

Non-Static Closures and $this Unbinding

The unbinding of $this in a non-static closure that uses $this is deprecated:

class C
{
    public function m()
    {
        $f = function()
        {
            var_dump($this);
        };

        $f->bindTo(null);
    }
}

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

Executing the code shown above prints the deprecation warning shown below:

PHP Deprecated: Unbinding $this of closure is deprecated in ...

Allegedly, there is a dirty trick which still allows you to do the same, using a combination of ReflectionMethod::getClosure() and Closure::bindTo(). Attemtping this in PHP 7.4 now also prints a deprecation warning.

break and continue Outside Loops

The break statement can no longer be used outside of a loop or switch structure:

function f(string $string)
{
    if (empty($string)) {
        break;
    }

    // ...
}

Executing the code shown above prints the error shown below:

PHP Fatal error: 'break' not in the 'loop' or 'switch' context in ...

continue statements targeting switch control flow structures now generate a warning:

$a = true;
$b = 'c';

while ($a) {
    switch ($b) {
        case 'c':
            $a = false;

            continue;
    }
}

Executing the code shown above prints the warning shown below:

PHP Warning: "continue" targeting switch is equivalent to "break".
Did you mean to use "continue 2"? in ...

A continue statement that targets a switch is equivalent to break in PHP whereas other programming languages treat such a statement as continue 2.

Default Object Creation and Empty Values

A warning is now triggered when an object is automatically created because a property is set on a variable that does not yet exist:

$unexisting->property = true;

var_dump($unexisting);

Executing the code shown above prints the warning shown below:

PHP Warning: Creating default object from empty value in ...
object(stdClass)#1 (1) {
  ["property"]=>
  bool(true)
}

Serialization of Reflection Objects

Trying to serialize a reflection object such as ReflectionClass or ReflectionMethod, for instance, will now raise an exception:

serialize(new ReflectionClass(stdClass::class));

Executing the code shown above prints the error shown below:

PHP Fatal error:  Uncaught Exception: Serialization of
'ReflectionClass' is not allowed in ...

Serialization for reflection objects was never supported and resulted in corrupted reflection objects.

Traversable and Argument Unpacking

Traversable objects with non-integer keys can no longer be unpacked using the ... operator:

function f(...$parameters)
{
    var_dump($parameters);
}

function g()
{
    yield 1.23 => 123;
}

f(...g());

Executing the code shown above prints the error shown below:

PHP Fatal error: Uncaught Error: Cannot unpack Traversable
with non-integer keys in ...

Argument unpacking of Traversable objects “worked” by accident in previous versions of PHP.

ArrayAccess and Type Conversion of Keys

Array accesses of type $o["123"], where $o is an object that implements ArrayAccess and "123" is an integer string literal will no longer result in an implicit conversion of the array to integer:

class C implements ArrayAccess
{
    private $a = [];

    public function offsetExists($offset): bool
    {
        foreach (array_keys($this->a) as $key) {
            var_dump($key);
            var_dump($offset);

            if ($key === $offset) {
                return true;
            }

            return false;
        }
    }

    // ...
}

$o = new C;

$o['123'] = 'value';

var_dump(isset($o['123']));

Executing the code shown above prints the output shown below:

int(123)
string(3) "123"
bool(false)

Previous versions of PHP printed the following instead:

int(123)
int(123)
bool(true)

Option to configure remote includes now deprecated

Due to its flexible I/O subsystem, PHP could technically require or include a file from anywhere, using any protocol. At least as long as an implementation of that protocol exists for PHP’s stream subsystem, that is.

Considering the fact that including a file from a remote location is a security nightmare and could easily be abused, this option has been disabled by default for a very long time. It could still be explicitly enabled for legacy applications that require this functionality.

As this feature will be removed in PHP 8, the associated configuration option, allow_url_include, has been marked as deprecated and you should remove it from your php.ini configuration file.

is_real() and (real)

Before PHP introduced scalar type declarations and the float keyword, the terms “float” and “real” were used interchangeably for floating-point numbers. This is why the is_real() function is an alias for the is_float() function and why the (real) operator can be used to cast a value to the float type.

Now, both the is_real() function as well as the (real) cast operator have been deprecated. is_float() and (float) should be used instead, respectively.

Deprecate restore_include_path()

Most PHP developers today use Composer to manage their runtime dependencies. Before Composer was widely adopted, loading classes, libraries, or code dependencies in general, happened in a less standardized way. One of the mechanisms PHP has always made available is an include path that can be configured in php.ini. When trying to load code from a non-absolute path, PHP will try all directories listed in the include path, and load whatever file it finds first. In fact this means that you don’t really know what will be loaded, and since the include directories are usually system-wide, the whole include path thing usually turns into a big mess pretty quickly. Relying on PHP’s include path has never been a good idea, and should be avoided altogether.

To make things worse, you can even change the include path at runtime using set_include_path():

var_dump(get_include_path());

set_include_path('/some/other/path');

var_dump(get_include_path());

restore_include_path();

var_dump(get_include_path());

While the include path on your system might be different, the output will look something like this:

string(32) ".:/usr/share/pear:/usr/share/php"
string(16) "/some/other/path"
PHP Deprecated:
Function restore_include_path() is deprecated in ...
string(32) ".:/usr/share/pear:/usr/share/php"

As you can see, the function restore_include_path() has been deprecated. Ignoring the fact that you should not rely on the include path, let alone change it at runtime, there is rarely a reason to change it back, given the fact that your PHP script will probably terminate soon. And, honestly, if you write long-running PHP processes that modify the include path at runtime, then we probably will need to have a serious conversation.

Nevertheless, here is what to replace restore_include_path() with if you absolutely feel that you cannot live without it:

$old = get_include_path();

set_include_path('/some/other/path');

var_dump(get_include_path());

set_include_path($old);

var_dump(get_include_path());

Works like a charm, and without any deprecation message:

string(16) "/some/other/path"
string(32) ".:/usr/share/pear:/usr/share/php"

Still, you should not change the include path at runtime. You really should not.