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.