2015 has been an important year for PHP. Eleven years after its 5.0 release, a new major version is finally coming our way! PHP 7 is scheduled for release before the end of the year, bringing many new language features and an impressive performance boost.

But how will this impact your current PHP codebase? What really changed? How safe is it to update? This post will answer these questions and give you a taste of what’s to come with PHP 7.

Performance Improvements

Performance is undoubtedly the biggest reason why you should upgrade your servers as soon as a stable version is released. The core refactoring introduced by the phpng RFC makes PHP 7 as fast as (or faster than) HHVM. The official benchmarks are impressive: most real world applications running on PHP 5.6 will run at least twice as fast on PHP 7.

For detailed performance benchmarks, have a look at Rasmus Lerdorf’s presentation at PHP Australia. (You can use the arrow keys to navigate through the slides.) Here are some WordPress benchmarks from that presentation:

 

php7_graph-c863bf78

PHP 7 handles more than twice as many requests per second, which in practical terms will represent a 100% improvement on performance for WordPress websites.

Backwards Compatibility Pitfalls

Let’s talk about the few things that could potentially break a legacy application running on older versions of PHP.

Deprecated Items Removed

A number of deprecated items have been removed. Because they’ve been deprecated for some time now, hopefully you aren’t using them! This might, however, have an impact on legacy applications.

In particular, ASP-style tags ( <%, <%= and %> ), were removed along with script tags ( <script language=”php”> ). Make sure you are using the recommended <?php tag instead. Other functions that were previously deprecated, like split, have also been removed in PHP 7.

The ereg extension (and all ereg_* functions) have been deprecated since PHP 5.3. It should be replaced with the PCRE extension (preg_* functions), which offers many more features. The mysql extension (and the mysql_* functions) have been deprecated since PHP 5.5. For a direct migration, you can use the mysqli extension and the mysqli_* functions instead.

Uniform Variable Syntax

The uniform variable syntax is meant to solve a series of inconsistencies when evaluating variable-variable expressions. Consider the following code:

<?php
class Person
{
   public $name = 'Erika';
   public $job = 'Developer Advocate';
}

$person = new Person();
$property = [ 'first' => 'name', 'second' => 'info' ];
echo "\nMy name is " . $person->$property['first'] . "\n\n";

In PHP 5, the expression $person->$property['first'] is evaluated as $person->{$property['first']}. In practical terms, this will be interpreted as $person->name, giving you the result “My name is Erika”. Even though this is an edge case, it shows clear inconsistencies with the normal expression evaluation order, which is left to right.

In PHP 7, the expression $person->$property['first'] is evaluated as {$person->$property}['first']. The interpreter will evaluate $person->$property first; consequently, the previous code example won’t work in PHP 7 because $property is an array and cannot be converted to a string.

A quick and easy way to fix this problem is by explicitly defining the evaluation order with the help of curly braces (e.g. $person->{$property['first']}), which will guarantee the same behavior on both PHP 5 and PHP 7.

Thanks to the new uniform left-to-right variable syntax, many expressions previously treated as invalid will now become valid. To illustrate this new behavior, consider the following class:

<?php
class Person
{
   public static $company = 'Xylusinfo';
   public function getFriends()
   {
       return [
           'erika' => function () {
               return 'Elephpants and Cats';
           },
           'sammy' => function () {
               return 'Sharks and Penguins';
           }
       ];
   }

   public function getFriendsOf($someone)
   {
       return $this->getFriends()[$someone];
   }

   public static function getNewPerson()
   {
       return new Person();
   }
}

With PHP 7, we can create nested associations and different combinations between operators:

$person = new Person();
echo "\n" . $person->getFriends()['erika']() . "\n\n";
echo "\n" . $person->getFriendsOf('sammy')() . "\n\n";

This snippet would give us a parse error on PHP 5, but works as expected on PHP 7.

Similarly, nested static access is also possible:

echo "\n" . $person::getNewPerson()::$company . "\n\n";

In PHP 5, this would give us the classic T_PAAMAYIM_NEKUDOTAYIM syntax error.

Fatal Error with multiple “default” clauses

This is, again, an edge case and it’s more related to logic errors in your code. There’s no use for multiple default clauses in a switch, but because it never caused any trouble (e.g. no warnings), it can be difficult to detect the mistake. In PHP 5, the last default would be used, but in PHP 7 you will now get a Fatal Error: Switch statements may only contain one default clause.

Engine Exceptions

Engine exceptions are meant to facilitate handling errors in your application. Existing fatal and recoverable fatal errors were replaced by exceptions, making it possible for us to catch said errors and take action — like displaying them in a nicer way, logging them, or performing recovery procedures.

The implementation of engine exceptions was done in such a way to keep backwards compatibility, but there is an edge case that could impact legacy applications when they have a custom error handling function in place. Consider the following code:

<?php
set_error_handler(function ($code, $message) {
   echo "ERROR $code: " . $message . "\n\n";
});

function a(ArrayObject $b){
   return $b;
}

a("test");

echo "Hello World";

This code generates a recoverable error caused by the type mismatch when calling the function a() using a string as parameter. In PHP 5, it generates an E_RECOVERABLE that is caught by the custom error handler, so this is the output you get:

ERROR 4096: Argument 1 passed to a() must be an instance of ArrayObject, string given, called in /data/Projects/php7dev/tests/test04.php on line 12 and defined(...)

Hello World

Notice that the execution continues because the error was handled. In PHP 7, this code generates a TypeError exception (not an error!) so the custom error handler won’t be called. This is the output you get:

Fatal error: Uncaught TypeError: Argument 1 passed to a() must be an instance of ArrayObject, string given, called in /vagrant/tests/test04.php on line 12 and defined in /vagrant/tests/test04.php:7
Stack trace:
#0 /vagrant/tests/test04.php(12): a('test')
#1 {main}
  thrown in /vagrant/tests/test04.php on line 7

The execution is stopped because the exception was not caught. To solve this problem, you should catch the exceptions using try/catch blocks. It’s important to notice that the Exception hierarchy had to change to accommodate the new engine exceptions with minimal impact on legacy code:

  • Throwable interface
    • Exception implements Throwable
      • ErrorException extends Exception
      • RuntimeException extends Exception
    • Error implements Throwable
      • TypeError extends Error
      • ParseError extends Error
      • AssertionError extends Error

This basically means that the new catch-all Exception is now Throwable instead of Exception. This should not impact any legacy code, but keep it in mind when handling the new engine exceptions in PHP 7.

New Language Features

Now the fun part — let’s talk about the most exciting new features that will be available when you upgrade to PHP 7.

New Operators

PHP 7 comes with two shiny new operators: the spaceship (or combined comparison operator) and the null coalesce operator.

The spaceship operator ( <=> ), also known as combined comparison operator, can be used to make your chained comparison more concise. Consider the following expression:

$a <=> $b

This expression will evaluate to -1 if $a is smaller than $b, 0 if $a equals $b, and 1 if $b is greater than $a. It’s basically a shortcut for the following expression:

($a < $b) ? -1 : (($a > $b) ? 1 : 0)

The null coalesce operator ( ?? ) also works as a shortcut for a common use case: a conditional attribution that checks if a value is set before using it. In PHP 5, you would usually do something like this:

$a = isset($b) ? $b : "default";

With the null coalesce operator in PHP 7, we can simply use:

$a = $b ?? "default";

Scalar Type Hints

One of the most debated new features coming with PHP 7, scalar type hints, will finally make it possible to use integers, floats, strings, and booleans as type hints for functions and methods. By default, scalar type hints are non-restrictive, which means that if you pass a float value to an integer parameter, it will just coerce it to int without generating any errors or warnings.

It is possible, however, to enable a strict mode that will throw errors when the wrong type is passed as an argument. Consider the following code:

<?php
function double(int $value)
{
   return 2 * $value;
}
$a = double("5");
var_dump($a);

This code won’t generate any errors because we are not using strict mode. The only thing that will happen is a type conversion, so the string “5” passed as an argument will be coerced into integer inside the double function.

If we want to make sure only integers are allowed to be passed to the double function, we can enable strict mode by including the directive declare(strict_types = 1) as the very first line in our script:

<?php
declare(strict_types = 1);
function double(int $value)
{
   return 2 * $value;
}
$a = double("5");
var_dump($a);

This code will generate a Fatal error: Uncaught TypeError: Argument 1 passed to double() must be of the type integer, string given.

Return Type Hints

Another important new feature coming with PHP 7 is the ability to define the return type of methods and functions, and it behaves in the same fashion as scalar type hints in regards of coercion and strict mode:

<?php
function a() : bool
{
   return 1;
}
var_dump(a());

This snippet will run without warnings and the returned value will be converted to bool automatically. If you enable strict mode (just the same as with scalar type hints), you will get a fatal error instead:

Fatal error: Uncaught TypeError: Return value of a() must be of the type boolean, integer returned

Once again, notice that these errors are actually Exceptions that can be caught and handled using try/catch blocks. It’s also important to highlight that you can use any valid type hint, not only scalar types.

What’s Next

The PHP 7 timeline indicates a stable release in mid-October, subject to quality. We are currently on release candidate cycles, and an beta version is already available for tests. Check out the RFC with all changes coming with PHP 7 for more information.

Note that even though no new features will be included, some changes might still happen before the final release, so please don’t use PHP 7 in production just yet! There’s a Vagrant VM created and shared by Rasmus Lerdorf that you can use to test your current code on PHP 7. You are strongly encouraged to test your applications and report any problems you might find.

Happy coding!

Leave a Comment