Language Features
New Reserved Words
When parsing the source code, most compilers require a few words to be non-ambiguous. Consider the following piece of pseudo code, which just serves the purpose of confusing people (and compilers, for that matter):
for (for=integer to class) {
...
}
Nobody should write code like that, so disallowing to use
keywords like integer
or class
should not
be a serious limitation.
In PHP 7, a few additional words have been reserved, which means you cannot use them as class names, even if you could in PHP 5:
class String
{
}
This code, which works fine on PHP 5, directly leads to a fatal error in PHP 7:
PHP Fatal error:
Cannot use 'String' as class name as it is reserved in ...
It does not matter whether String
is upper or lower
case. PHP has never been case-sensitive about class names. Putting
your class into a namespace will also not help, by the way.
PHP 7 will not allow you to use the following reserved words:
bool
, true
, false
,
null
, object
, string
,
int
, float
If your code uses any of these words as class names, the only thing you can do is to rename those classes.
In addition, the words resource
, mixed
,
and numeric
should be considered as reserved for the
future. This means that some few well-known projects will have to
rename some classes, because terms like Object
, or
Resource
were thought to be good and descriptive names.
Without doubt, they were, but we should not use them anymore to
avoid problems with future PHP versions.
On the upside, quite a few words that could not be used as method names in PHP 5 can now freely be used in PHP 7. We have already discussed this earlier.
Uniform Variable Syntax
What do you get if you divide six by three, and then add one? Simple enough: six divided by three equals two, and two plus one equals three. We can also use PHP to confirm that result:
php -r "var_dump(6/3+1);"
int(3)
However, if we were to add three and one first, and
then divide, the result would be 1.5
. But that
is wrong, you say, point calculation goes before line calculation.
This is called operator precedence. PHP knows all about this, of
course, which makes its calculation results predictable and correct.
The most basic precedence rule is “evaluate from left to right”.
By using brackets, we can force PHP to execute the calculations in a different order:
php -r "var_dump(6/(3+1));"
double(1.5)
With an expression as simple as in the example, all of this is really obvious. We know the basic operator precedence rules, because we learned them in school. But when it comes to more complex expressions, things can quickly get out of hand. Consider the following PHP code, for example:
$$foo['bar']['baz'];
Do you know the operator precedence rules for this one? To try
and figure them out, let us have a look at what we have here. The
example combines the usage of a variable variable,
$$foo
, and multi-dimensional array access. A variable
variable is an “interesting” PHP construct (“interesting” meaning:
you should better not use it) that has been around for a long time.
Maybe it should have never been invented. Anyway: $$foo
refers to the variable with the name contained in the variable
$foo
, as the following example shows:
$foo = 'bar';
$bar = 42;
var_dump($bar, $$foo);
$$foo
refers to $bar
, because
$foo
contains the string bar
. By the way,
if you hate foo/bar examples just like we do, you are in good
company, but nobody should be using features like this, so we really
had a hard time coming up with a more descriptive example.
So what does $$foo['bar']['baz'];
mean after all, or
to be more exact, what does it evaluate to in PHP? Is it
${$foo['bar']['baz']}
(use
$foo['bar']['baz']
to determine the name of the
variable, then access a variable variable), or rather
{$$foo}['bar']['baz']
(determine the variable variable
first, then retrieve the value from the array)?
If your answer is: “Nobody knows, this is why you should never write such code”, then you hit the nail on the head. One could argue that it is okay to write code like that, because PHP knows the precedence rules, and applies them consistently. The problem is that no human being could possibly remember and understand the precedence rules in such cases – at least we can’t.
When evaluating expressions like
$$foo['bar']['baz']
, $foo->$bar['baz']
,
$foo->$bar['baz']()
, or
Foo::$bar['baz']()
(yes, we can add function and method
calls into the mix, to make everybody’s life even more interesting),
PHP 5 would sometimes work from left to right, but not always.
PHP 7 will always evaluate from left to right. This means that some expressions will be evaluated differently in PHP 7:
Expression | PHP 5 interpretation | PHP 7 interpretation |
---|---|---|
$$foo['bar']['baz'] |
${$foo['bar']['baz']} |
($$foo)['bar']['baz'] |
$foo->$bar['baz'] |
$foo->{$bar['baz']} |
($foo->$bar)['baz'] |
$foo->$bar['baz']() |
$foo->{$bar['baz']}() |
($foo->$bar)['baz']() |
Foo::$bar['baz']() |
Foo::{$bar['baz']}() |
(Foo::$bar)['baz']() |
The easy fix is to add braces and brackets as needed. A better fix would be to rewrite the code so that such expressions are not needed anymore. We are sure all of your fellow developers would appreciate that.
Thanks to the uniform variable syntax, PHP 7 now understands more complex expressions than PHP 5. Some of them are:
$foo()['bar']()
[$obj1, $obj2][0]->prop
$foo::$bar::$baz
foo()()
(...)['foo']
We will leave it up to you to decide what the hell all of those mean, and whether it really makes you a good developer to write illegible code like this. We firmly believe that code should be easy to read, so just because you can use even more complex expressions with PHP 7, this does not mean you should.
Parameter Handling
PHP, by design, allows passing of an arbitrary amount of
parameters to any function or method, even if they are not listed in
the signature. To retrieve the parameters, the functions
func_get_arg()
and func_get_args()
can be
used.
Due to internal changes in PHP 7, when modifying those parameters
within the called scope, func_get_arg()
and
func_get_args()
will no longer return the original
value, but the current value:
function doSomething($a)
{
var_dump(func_get_args());
$a = 'modified';
var_dump(func_get_args());
}
doSomething('original');
The first var_dump()
shows the original value, while
the second one shows the modified one:
array(1) {
[0] => string(8) "original"
}
array(1) {
[0] => string(8) "modified"
}
If existing code uses func_get_arg()
or
func_get_args()
after modifying any
parameters, then modify the code to fetch the original parameters
first, and then modifies them. This should be an easy fix.
Please note that in PHP 7, exception backtraces and the output of
debug_backtrace()
will also display the modified values
rather than the original ones.
Calling Functions and Methods with too few Arguments
Before version 7.1, PHP would execute a function or method even when it was called with too few arguments. Consider the following example:
function add($a, $b)
{
return $a + $b;
}
var_dump(add(1));
Executing the code shown above with PHP 7.0 produces the output shown below:
Warning: Missing argument 2 for add(), called in ...
and defined in ...
Notice: Undefined variable: b in ...
int(1)
Executing the code shown above with PHP 7.1 or later produces the output shown below:
Fatal error: Uncaught Error:
Too few arguments to function add(), 1 passed in ...
and exactly 2 expected in ...
Instead of executing the code without all parameters set, PHP since version 7.1 refuses to perform function and method calls with too few arguments. This change breaks backwards compatibility, if only for broken code.
Passing more arguments to a function or method than it expects still works, though:
function f() {
var_dump(func_get_args());
}
f(true);
Executing the code shown above produces the output shown below:
array(1) {
[0]=>
bool(true)
}
Variable
Variables are no Longer Allowed in global
Declarations
Do you still remember the global
keyword? Even
though this relict of times past should have no relevance in the
modern world of object-oriented PHP, it is still around and received
an update restricting it to only simple variables. While this seems
to be a rather logical thing at first glance, it breaks code that
uses variable variables – another construct that should have been
removed from PHP long ago – together with global
. The
following construct no longer works with PHP 7:
global $$foo->bar;
So in case such a construct would still be required by your codebase, it has to get adjusted using curly braces:
global ${$foo->bar};
instanceof
and
Literals
The instanceof
operator is used to determine whether
a variable holds an object of a specified class:
$o = new stdClass;
var_dump($o instanceof stdClass);
var_dump($o instanceof Exception);
Executing the code shown above will print the output shown below:
bool(true)
bool(false)
Previous versions of PHP triggered an error when a literal value
of type bool
, float
, int
, or
string
, for instance, was used with the
instanceof
operator:
var_dump(true instanceof stdClass);
Executing the code shown above used to print the error shown below:
Fatal error: instanceof expects an object instance,
constant given in ...
This behaviour has been changed and instead of triggering an
error the instanceof
operator now returns
false
when it is used with a literal value.
Executing the code shown above will print the output shown below:
bool(false)
Traits with Default Property Values
Introduced in PHP 5.4, traits provide an alternative mechanism for code reuse. A trait is a set of methods that can be embedded into one or more classes. There is no notion of traits at runtime: a trait cannot be instantiated, there is no difference between a method that is declared in a class and a method that comes from an embedded trait, and there is no type relationship between a trait and the classes that embed it.
In addition to methods, traits can also contain properties. Prior
to PHP 7, defining a property in a class with the same name as in a
trait triggered an E_STRICT
notice even if the
declarations used the same visibility and initial value.
trait T
{
private $a = 'bar';
}
class C
{
use T;
private $a = 'bar';
}
Executing the code shown above with PHP 5.6 triggered the violation shown below:
Strict Standards: C and T define the same property ($a)
in the composition of C. This might be incompatible, to improve
maintainability consider using accessor methods in traits instead.
Class was composed in ...
Executing the same code with PHP 7 does not trigger that violation.
When both a trait and a class using that trait declare the same property, both declarations must be compatible with regard to their visibility, type, and default value.
trait T
{
private $a = 'bar';
}
class C
{
use T;
private $a = 'baz';
}
Executing the code shown above with PHP 7 triggers the error shown below:
Fatal error: C and T define the same property ($a)
in the composition of C. However, the definition differs
and is considered incompatible. Class was composed in ...
trait T
{
public $a = 'bar';
}
class C
{
use T;
private $a = 'bar';
}
Executing the code shown above with PHP 7 triggers the error shown below:
Fatal error: C and T define the same property ($a)
in the composition of C. However, the definition differs
and is considered incompatible. Class was composed in ...
Prior to PHP 7.2, a loose comparison with automatic type casting was used to check the type compatibility of default values. However, this was not the intent of the original RFC for traits.
trait T
{
private $a = true;
}
class C
{
use T;
private $a = 1;
}
Executing the code shown above with PHP 7.2 produces the output shown below:
PHP Fatal error: C and T define the same property ($a) in the
composition of C. However, the definition differs and is
considered incompatible. Class was composed in ...
The same code did not trigger an error in earlier versions of PHP.
DateTime
,
DateTimeImmutable
, and Microseconds
Prior to PHP 7.1, objects of the DateTime
and
DateTimeImmutable
classes did not include microsecond
information when constructed for the current time or using a string
such as “first day of next month”. This means that naive comparisons
of two newly created DateTimeImmutable
objects, for
instance, may yield a wrong result.
var_dump(new DateTimeImmutable == new DateTimeImmutable);
When executed with PHP 5.6 and PHP 7.0, the code shown above,
will – at least most of the time – output bool(true)
.
If both objects are not created in the same second, the comparison
will fail and bool(false)
will be printed.
With PHP 7.1, however, the same code usually prints
bool(false)
. If both objects happen to be created with
a microsecond, then bool(true)
would be printed.
This affects your code when you mistakenly assume that two
DateTime
or DateTimeImmutable
objects, one
created after the other, represent the exact point in time with
microsecond resolution.
Even if you got away with this on PHP 5.6 or PHP 7.0, you will run into trouble with PHP 7.1, because the clock now advances a million times faster!
Comparison of
DateInterval
Objects
Trying to compare DateInterval
objects using
operators such as ==
, for example, now triggers a
warning:
$a = new DateInterval('PT1S');
$b = new DateInterval('PT2S');
var_dump($a == $b);
Executing the code shown above prints the warning shown below:
PHP Warning: Cannot compare DateInterval objects in ...
bool(false)
Previous versions of PHP considered all DateInterval
objects to be equal, unless they had dynamically defined
properties:
$a = new DateInterval('PT1S');
$b = new DateInterval('PT2S');
var_dump($a == $b);
$a->foo = 'bar';
var_dump($a == $b);
The code shown above used to generate the following output:
bool(true)
bool(false)
Dynamic Calls to Scope Introspection Functions
There are a couple of functions built into PHP that inspect or
modify the parent scope or stack frame, make unexpected scope
modifications, or generally have unclear behavior when called
dynamically, for example by using call_user_func()
.
Allowing these functions to be called dynamically not only makes it
possible to write code that behaves “interestingly” but also causes
problems for further work on PHP’s bytecode optimizer.
As of PHP 7.1, the functions assert()
(with string
argument), compact()
, extract()
,
func_get_args()
, func_get_arg()
,
func_num_args()
, get_defined_vars()
,
mb_parse_str()
(with one argument), and
parse_str()
(with one argument) can no longer be
dynamically called. The code shown below contains two common
examples for dynamic calls:
$function = 'get_defined_vars';
$function();
call_user_func($function);
Executing the shown code above with PHP 7.1 or newer will result in the errors shown below:
Warning: Cannot call get_defined_vars() dynamically in ...
Warning: Cannot call get_defined_vars() dynamically in ...
Static Methods and
ReflectionMethod::invoke()
When you use ReflectionMethod::invoke()
to invoke a
static method then the first argument passed, $object
,
is ignored. This makes sense because there is no object to invoke
the method on.
Prior to PHP 7.1 you could pass a string containing the name of
the class as the first argument to
ReflectionMethod::invoke()
:
class C
{
public static function m($p)
{
print $p . PHP_EOL;
}
}
$method = new ReflectionMethod(C::class, 'm');
$method->invoke('C', 'example');
Executing the code shown above with PHP 5.6 or PHP 7.0 has the expected result:
example
Executing the code shown above with PHP 7.1 or newer, however, leads to a different result:
Warning:
ReflectionMethod::invoke() expects parameter 1 to be object,
string given in ...
The PHP runtime warns us that the first argument passed to
ReflectionMethod::invoke()
must be an object and does
not invoke the static method.
This breaking change was introduced to make the handling of
arguments for ReflectionMethod::invoke()
consistent
with that of ReflectionMethod::invokeArgs()
. The first
argument passed to both methods must be null
now when
used with static methods.
To make the code shown above compatible with PHP 7.1 and above,
we need to change the first argument passed to the
ReflectionMethod::invoke()
method to
null
:
class C
{
public static function m($p)
{
print $p . PHP_EOL;
}
}
$method = new ReflectionMethod(C::class, 'm');
$method->invoke(null, 'example');
Executing this code prints the same output for all versions of PHP since PHP 5.6:
example
Removed PHP Extensions
As of PHP 7, some extensions are gone. The most notable one is
the mysql
extension, which in the early years of PHP
was the default way of accessing a MySQL database. Years ago, the
mysql
extension has been superseded by the
mysqli
extension, to enable PHP developers to use
features of MySQL database servers that had been introduced after
version 3.
The mysql
extension has been marked as deprecated
for years now. If you have not switched to mysqli
yet,
now is the time. We would recommend against moving to
PDO
, by the way. In many cases, adjusting your code to
use mysqli
is almost as simple as adding an
i
to the mysql_
function calls. However,
you will have to pass the database connection to each
mysqli_
function or method call, which was optional for
the mysql_
calls. If this is something you need to
change, do not use global variables or similar to get to the
database connection, but wrap the calls into an object that receives
the database connection as a constructor parameter, and pass around
this object.
The mssql
extension, which provided access to
Microsoft SQL Server database servers, has also been removed from
PHP. The new sqlsrv
and pdo_sqlsrv
extensions should be used to access Microsoft SQL Server database
servers. The former provides a procedural API whereas the latter
provides a PDO driver. Both extensions are supported on UNIX-based
operating systems as well as on Microsoft Windows and allow
accessing data in all editions of Microsoft SQL Server 2012 and
later. This includes accessing data that is stored in Microsoft
Azure SQL Database instances.
Another extension that has been removed from PHP is
ereg
. It provided regular expression functions that
were not capable of processing strings with binary data. In the C
programming language, a string is stored as a sequence of
characters, terminated by a so-called null character, the ASCII
character encoded as 0. A zero byte. Whenever a string is being
processed in C, it thus knows that it has reached the end when it
encounters a null character, and stops. When binary data, for
example an image file, is being passed as a string, it might contain
zero bytes that are not end-of-string markers. Still, code written
to deal with strings would stop processing. Such code is said to be
not binary safe, as it is not safe to be applied to binary data.
Non-binary safe code usually implies serious security issues.
Should your code still use any ereg_()
function,
replace them by preg_()
functions, and start doing so
as quickly as possible. The so-called Perl Regular Expressions are
binary safe.
In older PHP versions, a sybase
extension existed.
In PHP 5.3, this extension had been renamed to
sybase_ct
. In PHP 7, this extension has been removed.
Researching the web, we were unable to find any substantial
information on either the sybase
or the
sybase_ct
extension. We therefore assume that nobody
was ever really using it.
The PHP core developers have decided to not remove the
imap
extension, even though it depends on an
unmaintained library. You should not rely on this extension anymore,
since it cannot be considered as maintained and might become
incompatible with PHP at any given point in the future.
The mhash
extension is no longer available in PHP 7.
Instead, the hash
extension should be used. If your
application uses mhash_()
functions, namely
mhash_count()
, mhash_get_block_size()
,
mhash_get_hash_name()
, or
mhash_keygen_s2k()
, you have to replace them.
The mcrypt
extension was deprecated in PHP 7.1 and
has been removed from the standard PHP distribution in PHP 7.2. You
can still install it from PECL. Since the
mcrypt
extension usually is not installed by default,
but has to be installed using the operating systems’s package
manager, most PHP users might not even notice. After all, you do not
see where the sources that an additional PHP package was built from
were obtained from (which is, in fact, more of a curse than a
blessing, but that is a different story).
This does not mean, however, that mcrypt
should
still be used. On the contrary: cryptographic software that is
outdated always poses a security risk. And the underlying library,
libmcrypt, has not been updated since 2007. This also means that
modern cryptographic algorithms are not supported, neither by
libmcrypt nor by PHP’s extension for it. Even AES, a widely used
standard, is not available under its name. You have to fall back to
using Rijndael, which was the algorithm’s original name before it
was standardized as AES. Still, performing AES encryption or
decryption has always been cumbersome using the mcrypt
extension.
If you need cryptographic functionality in PHP, use the functions
provided by the sodium
or openssl
extensions instead. There should be no need for PHP to work with old
or exotic cryptographic algorithms. Since most of the world wide web
has finally realized that using HTTPS is a good idea, web servers
“automagically” tend to handle most encryption and decryption for
us. And if you use an encrypted file system (which you probably
should), the filesystem itself will take care of cryptography, again
freeing PHP from the need to deal with it.
Optional Parameter of
mktime()
Removed
Dealing with dates and times is a complex issue. One day, for instance, does not always have 24 hours or 86400 seconds), as is frequently assumed. Let aside the fact that a day in general has a bit less than 24 hours, there are usually two days in a year when a day has 23 or 25 hours, respectively. This happens on the days when the clock is being switched back or forth between regular time and daylight saving time.
Unfortunately, this switch is not being handled consistently across platforms. Some systems switch to daylight saving time between 2am and 3am in the morning, others do so at midnight.
PHP has a built-in function to create a Unix timestamp from a given date:
$timestamp = mktime(12, 0, 0, 7, 1, 2016);
The parameters are $hour
, $minute
,
$seconds
, $month
, $day
, and
$year
. Up to PHP 5, there was a seventh optional
parameter $is_dst
. The name might suggest that the
parameter was boolean, but in fact it was an integer.
When 0
was being passed, no daylight saving time was
assumed, while 1
denoted daylight saving time. The
default value -1
made PHP guess whether daylight saving
time applied or not.
As you can imagine, guessing in combination with inconsistent
behavior between different platforms is never a good idea. Thus, the
seventh parameter has now been removed in PHP 7. If you try to pass
seven parameters to mktime()
in PHP 7, a warning is
being issued:
PHP Warning:
mktime() expects at most 6 parameters, 7 given in ...
When working with time zones, you should use the built-in class
DateTimeZone
, which can tell you about all daylight
saving time transitions in a given time zone:
$timezone = new DateTimeZone('Europe/Berlin');
var_dump(
$timezone->getTransitions(
mktime(0, 0, 0, 1, 1, 2020),
mktime(0, 0, 0, 1, 1, 2021)
)
);
This will output all transitions in 2020:
array(3) {
[0]=>
array(5) {
["ts"] => int(1577833200)
["time"] => string(24) "2019-12-31T23:00:00+0000"
["offset"] => int(3600)
["isdst"] => bool(false)
["abbr"] => string(3) "CET"
}
[1]=>
array(5) {
["ts"] => int(1585443600)
["time"] => string(24) "2020-03-29T01:00:00+0000"
["offset"] => int(7200)
["isdst"] => bool(true)
["abbr"] => string(4) "CEST"
}
[2]=>
array(5) {
["ts"] => int(1603587600)
["time"] => string(24) "2020-10-25T01:00:00+0000"
["offset"] => int(3600)
["isdst"] => bool(false)
["abbr"] => string(3) "CET"
}
}
The first array element tells us that the year started without daylight saving time. On March 29th, daylight saving time had started, ending on October 25th.
stdClass
Objects
and var_export()
$o = new stdClass;
$o->property = 'value';
print var_export($o);
Executing the code shown above printed the output shown below with previous versions of PHP:
stdClass::__set_state(array(
'property' => 'value',
))
As you can see above, var_export()
used to generate
code for stdClass
objects that calls the
__set_state()
method which is not available on these
objects.
Now var_export()
generates code for
stdClass
objects that uses the (object)
operator to cast an array to object instead:
(object) array(
'property' => 'value',
)
<?php
at the End of a
File
If a source file ends with <?php
without a
trailing newline then previous versions of PHP behaved differently
depending on the short_open_tag
configuration
directive. With short_open_tag=1
it was treated as
<? php
which then triggered a syntax error. With
short_open_tag=0
it was treated a literal string.
Now <?php
at the end of a source file is always
treated as an opening PHP tag regardless of the
short_open_tag
configuration directive’s setting.
Heredoc and Nowdoc Syntax
The so-called heredoc syntax allows for in-line definition of multiline content:
<?php
$string = <<<EOT
String
with
indentation
EOT;
var_dump($string);
This will output:
string(35) " String
with
indentation"
Heredoc supports escape sequences and will expand variables just
like a double-quoted string. When enclosing the EOT
identifier in single quotes, the behaviour changes analogous to
a
single-quoted string (this is the so-called nowdoc syntax).
Up to PHP 7.3, the heredoc/nowdoc syntax was very strict, for
example, the closing marker EOT
could not be indented.
Since PHP 7.3, the syntax has been relaxed. The closing marker can
now be indented, which means that you should choose a closing marker
that appears somewhere in your heredoc/nowdoc block, because this
will now be considered a closing marker.
In addition, the indentation of the closing marker will determine how many whitespace characters will be removed from the heredoc/nowdoc content:
$string = <<<EOT
String
with
indentation
EOT;
var_dump($string);
This will take two whitespace off:
string(29) " String
with
indentation"
Previously, a new line character was required after the closing marker. This requirement was also dropped.