Introduction to PHP7


PHP

I’m happy to tell you that the wait is over, PHP7 is a thing, and it’s coming this year! With the RFC for the PHP 7.0 Timeline passing almost unanimously (32 to 2), we have now entered into feature freeze, with the first alpha release and we’ll see the first release candidate (RC) appearing between mid June and October.

This post will be updated as soon as a new RFC will be accepted by the PHP group. You can refer to the complete RFC site right here.

But what does this mean for you? We have seen a huge reluctance of web hosts to move towards newer versions of 5.x. Won’t a major version bring huge backwards compatibility breaks and make that move even slower?

Let’s get into the details.

Performance

PHP7 is based on the PHPNG project (PHP Next-Gen), that was led by Zend to speed up PHP applications. PHP7 will now run under Zend Engine 3 who improve a full support for both 32 and 64 bits platforms.

According the official PHP7 page on Zend.com:

The performance gains realized from PHP7 are huge! They vary between 25% and 70% on real-world apps, and all of that just from upgrading PHP, without having to change a single line of code!

As things currently stand, depending on whose benchmark you see, the performance of PHP7 is on par with Facebooks HHVM, which features a Just In Time (JIT) compiler that compiles PHP code all the way down to machine instructions (if it is able).

PHP7 does not feature a JIT, though it has been discussed a lot. It is unclear if there are more performance gains to be had with the addition of one, but it sure will be interesting to see if someone decides to create one!

In addition to performance, there should be substantial memory savings, as optimization of internal data structures is one of the primary ways in which performance improvements have been achieved.

Backwards Incompatible Changes

While the internals developers have tried very hard not to break Backwards Compatibility it is not always possible to do so while moving the language forward.

However, like the BC breaks introduced Uniform Variable Syntax, most of them are minor, such as the catchable fatal errors when trying to call a method on a non-object:

set_error_handler(function($code, $message) {
  var_dump($code, $message);
});

$var = null;
$var->method();
echo $e->getMessage(); // Fatal Error: Call to a member function method() on null
echo "Hello World"; // Still runs

Additionally, ASP tags and script tags have been removed, meaning you can no longer use <% and <%=, or <script language="php"> (and their respective close tags, %>, and </script>).

Other much larger changes however are to be found in the removal of all deprecated functionality.

Most importantly, the removal of the POSIX compatible regular expressions extension, ext/ereg (deprecated in 5.3) and the old ext/mysql extension (deprecated in 5.5).

One other minor BC breaking change is disallowing multiple default cases in a switch. Prior to PHP7, the following was allowed:

switch ($expr) {
    default:
         echo "Hello World";
         break;
    default:
         echo "Goodbye Moon!";
         break;
}

This would result in only the latter being executed. In PHP7, this will result in:

Fatal error: Switch statements may only contain one default clause

Inconsistency Fixes

Unfortunately, the needle/haystack issues have not been fixed. However, two major RFCs have passed that will bring some much-needed internal and userland consistency.

The largest (and most invisible) is the addition of an Abstract Syntax Tree (AST) — an intermediate representation of the code during compilation. With this in place, we are able to clean up some edge case inconsistencies, as well as pave the way for some amazing tooling in the future, such as using the AST to produce more performant opcodes.

The second, which is the introduction of Uniform Variable Syntax, may cause you more issues. This solves numerous inconsistencies in how expressions are evaluated. For example, the ability to call closures assigned to properties using ($object->closureProperty)(), as well as being able to chain static calls, like so:

class foo { static $bar = 'baz'; }
class baz { static $bat = 'Hello World'; }

baz::$bat = function () { echo "Hello World"; };

$foo = 'foo';
($foo::$bar::$bat)();

However, some semantics are also changing. In particular, the semantics when using variable-variables/properties.

Prior to PHP7, $obj->$properties['name'] would access the property whose name is in the name key of the $properties array. With Universal Variable Syntax, it would access the name key of the property whose name resides in $properties.

Or to be more concise, if we take this statement:

$obj->$properties['name']

In PHP5.6, it would be interpreted as:

$obj->{$properties['name']}

And in PHP7 as:

{$obj->$properties}['name']

While the use of variable-variables is generally an edge-case, and frowned upon, variable-properties are much less uncommon in my experience. You can, however, easily work around this with the application of curly braces (as in the illustration above) to ensure the same behavior in PHP 5.6 and 7.

Improvements

PHP7 is lighter than PHP5 because PHP group members had to remove old features deprecated from many years.

Deprecation of PHP 4-Style Constructors

PHP4 constructors were preserved in PHP5 alongside the new __construct(). Now, PHP 4-style constructors are being deprecated in favour of having only a single method (__construct()) to be invoked on object creation. This is because the conditions upon whether the PHP 4-style constructor was invoked caused additional cognitive overhead to developers that could also be confusing to the inexperienced.

Now in PHP 7, if the class is not in a namespace and there is no __construct() method present, the PHP 4-style constructor will be used as a constructor but an E_DEPRECATED will be removed. In PHP8, the PHP 4-style constructor will always be recognised as a plain method and the E_DEPRECATED notice will disappear.

JSON Extension Replaced with JSOND

The licensing of the old JSON extension was regarded as non-free, causing issues for many Linux-based distributions. The extension has since been replaced with JSOND and comes with some performance gains and backward compatibility breakages.

Here are some changes:

echo json_encode(10.0); // Output 10
echo json_encode(10.1); // Output 10.1
echo json_encode(10.0, JSON_PRESERVE_ZERO_FRACTION); // Output 10.0
echo json_encode(10.1, JSON_PRESERVE_ZERO_FRACTION); // Output 10.1
var_dump(json_decode(json_encode(10.0, JSON_PRESERVE_ZERO_FRACTION))); // Output double(10)
var_dump(10.0 === json_decode(json_encode(10.0, JSON_PRESERVE_ZERO_FRACTION))); // Output bool(true)

Context Sensitive Lexer

Globally reserved words as property, constant, and method names within classes, interfaces, and traits are now allowed. This reduces the surface of BC breaks when new keywords are introduced and avoids naming restrictions on APIs.

This is particularly useful when creating internal DSLs with fluent interfaces:

Finder::for('project')
        ->where('name')->like('%secret%')
        ->and('priority', '>', 9)
        ->or('code')->in(['4', '5', '7'])
        ->and()->not('created_at')->between([$time1, $time2])
        ->list($limit, $offset);

The only limitation is that the class keyword still cannot be used as a constant name, otherwise it would conflict with the class name resolution syntax of ClassName::class.

I think this is the best improvement of PHP7, for the full list of newly reserved and new possibilities, check Context Sensitive Lexer RFC.

Assigining new instance by reference

PHP7 as completly remove support for assiging the result of new by reference. This had already been deprecated since PHP5.0

$obj =& new stdclass(); // Parse error: syntax error, unexpected 'new' (T_NEW)

New Features

We begrudgingly deal with the effects of backwards incompatibility. We appreciate performance. But we revel in new features! New features are what make each release fun — and PHP7 is not short on new features.

Scalar Type Hints & Return Types

I’m going to start with the most controversial change that was added for PHP7: Scalar Type Hints. The addition of this feature involved vote that almost passed. But then the author left PHP developement for good (withdrawing the RFC). This was then followed by multiple RFCs for competing implementations, and a whole lot of public fighting that ultimately ended with (effectively) the original RFC being passed.

For you, as end-users, what this means is that you can now type-hint with scalar types. Specifically: int, float, string, and bool. By default type-hints are non-strict, which means they will coerce the original type to the type specified by the type-hint. This means if you pass int(1) into a function that requires a float, it will be come float(1.0). Passing float(1.5) into a function that requires an int, it will be come int(1).

Here’s an example:

function sendHttpStatus(int $statusCode, string $message) {
     header('HTTP/1.0 ' .$statusCode. ' ' .$message);
}
sendHttpStatus(404, "File Not Found"); // integer and string passed
sendHttpStatus("403", "OK"); // string "403" coerced to int(403)

Additionally, you can enable strict mode by placing declare(strict_types=1); at the top of any given file will ensure that any function calls made in that file strictly adhere to the types specified. Strict is determined by the file in which the call to a function is made, not the file in which the function is defined.

If a type-hint mismatch occurs, a Catchable Fatal Error is thrown:

<?php
declare(strict_types=1); // must be the first line

sendHttpStatus(404, "File Not Found"); // integer and string passed
sendHttpStatus("403", "OK"); // Catchable fatal error: Argument 1 passed to sendHttpStatus() must be of the type integer, string given

Furthermore, PHP7 also supports Return Type Hints which support all the same types as arguments. These follow the same syntax as hack, suffixing the argument parenthesis with a colon followed by the type:

function isValidStatusCode(int $statusCode): bool {
    return isset($this->statuses[$statusCode]);
}

In this example the : bool indicates that the function will return a boolean. The same rules that apply to type-hints apply to returned type hints for strict mode.

Combined Comparison (Spaceship) operator

The first new operator added in PHP7 is the addition of the Combined Comparison Operator, <=>,otherwise known as the spaceship operator. This operator is still a nice addition to the language, complementing the greater-than and less-than operators.

It effectively works like strcmp(), or version_compare(), returning -1 if the left operand is smaller than the right, 0 if they are equal, and 1 if the left is greater than the right. The major difference being that it can be used on any two operands, not just strings, but also integers, floats, arrays, etc.

The most common usage for this operator is in sorting callbacks:

// Pre Spacefaring PHP 7
function order_func($a, $b) {
    return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}

// Post PHP 7
function order_func($a, $b) {
    return $a <=> $b;
}

Null Coalesce operator

Another new operator, the Null Coalesce Operator, ??, is effectively the fabled ifsetor. It will return the left operand if it is not NULL, otherwise it will return the right.

The important thing is that it will not raise a notice if the left operand is a non-existent variable. This is like isset() and unlike the ?: short ternary operator.

You can also chain the operators to return the first non-null of a given set:

// Without null coalesce
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

// With null coalesce
$username = $_GET['user'] ?? 'nobody';

Unicode Codepoint Escape Syntax

The addition of a new escape character, \u, allows us to specify Unicode character code points (in hexidecimal) unambiguously inside PHP strings. This means that a string composed by U+ will now not be parsed as special characters

The syntax used is \u{CODEPOINT}, for example the green heart, ?, can be expressed as the PHP string: \u{1F49A}.

Bind Closure on Call

With PHP5.4 we saw the addition of Closure->bindTo() and Closure::bind() which allows you change the binding of $this and the calling scope, together, or separately, creating a duplicate closure.

PHP7 now adds an easy way to do this at call time, binding both $this and the calling scope to the same object with the addition of Closure->call(). This method takes the object as it's first argument, followed by any arguments to pass into the closure, like so:

class HelloWorld {
     private $greeting = "Hello";
}

$closure = function($whom) { echo $this->greeting . ' ' . $whom; }

$obj = new HelloWorld();
$closure->call($obj, 'World'); // Hello World

Group Use Declarations

If you’ve ever had to import many classes from the same namespace, you’ve probably been very happy if your IDE will auto-complete them for you. For others, and for brevity, PHP7 now has Group Use Declarations. This allows you to quickly specify many similar imports with better clarity:

// Before PHP7
use Framework\Component\SubComponent\ClassA;
use Framework\Component\SubComponent\ClassB as ClassC;
use Framework\Component\OtherComponent\ClassD;

// With Group Use
use Framework\Component\{
     SubComponent\ClassA,
     SubComponent\ClassB as ClassC,
     OtherComponent\ClassD
};

It can also be used with constant and function imports with use function, and use const, as well as supporting mixed imports:

use Framework\Component\{
     SubComponent\ClassA,
     function OtherComponent\someFunction,
     const OtherComponent\SOME_CONSTANT
};

Anonymous Classes support

For some time PHP has featured anonymous function support in the shape of Closures. This new feature introduces the same kind of functionality for objects of an anonymous class.

The ability to create objects of an anonymous class is an established and well used part of Object Orientated programming in other languages (namely C# and Java).

/ Pre PHP 7 code
class Logger
{
    public function log($msg)
    {
        echo $msg;
    }
}

$util->setLogger(new Logger());

// PHP 7+ code
$util->setLogger(new class {
    public function log($msg)
    {
        echo $msg;
    }
});

Nesting an anonymous class within another class does not give it access to any private or protected methods or properties of that outer class. In order to use the outer class' protected properties or methods, the anonymous class can extend the outer class. To use the private or protected properties of the outer class in the anonymous class, they must be passed through its constructor:

class Outer
{
    private $prop = 1;
    protected $prop2 = 2;

    protected function func1()
    {
        return 3;
    }

    public function func2()
    {
        return new class($this->prop) extends Outer {
            private $prop3;

            public function __construct($prop)
            {
                $this->prop3 = $prop;
            }

            public function func3()
            {
                return $this->prop2 + $this->prop3 + $this->func1();
            }
        };
    }
}

echo (new Outer)->func2()->func3(); // 6

Generators Improvements

There are two new features added to generators.

Generator return expressions

The first feature added is Generator Return Expressions, which allows you to now return a value upon (successful) completion of a generator.

Prior to PHP7, if you tried to return anything, this would result in an error. However, now you can call $generator->getReturn() to retrieve the return value.

If the generator has not yet returned, or has thrown an uncaught exception, calling $generator->getReturn() will throw an exception.

If the generator has completed but there was no return, null is returned. Here’s an example:

function gen() {
    yield "Hello";
    yield " ";
    yield "World!";

    return "Goodbye Moon!";
}

$gen = gen();

foreach ($gen as $value) {
    echo $value; 
}

// Outputs "Hello" on iteration 1, " " on iterator 2, and "World!" on iteration 3

echo $gen->getReturn(); // Goodbye Moon!

Generator delegation

The second feature is much more exciting: Generator Delegation. This allows you to return another iterable structure that can itself be traversed — whether that is an array, an iterator, or another generator.

It is important to understand that iteration of sub-structures is done by the outer-most original loop as if it were a single flat structure, rather than a recursive one.

This is also true when sending data or exceptions into a generator. They are passed directly onto the sub-structure as if it were being controlled directly by the call.

This is done using the yield from syntax, like so:

function hello() {
     yield "Hello";
     yield " ";
     yield "World!";

     yield from goodbye();
}

function goodbye() {
     yield "Goodbye";
     yield " ";
     yield "Moon!";
}

$gen = hello();
foreach ($gen as $value) {
     echo $value;
}

On each iteration, this will output:

"Hello"
" "
"World!"
"Goodbye"
" "
"Moon!"

One other caveat worth mentioning is that because the sub-structure can yield it’s own keys, it is entirely possibly that the same key will be returned for multiple iterations — it is your responsibility to avoid that, if it matters to you.

Engine Exceptions

Handling of fatal and catchable fatal errors has traditionally been impossible, or at least difficult, in PHP. But with the addition of Engine Exceptions, many of these errors will now throw exceptions instead.

Now, when a fatal or catchable fatal error occurs, it will throw an exception, allowing you to handle it gracefully. If you do not handle it at all, it will result in a traditional fatal error as an uncaught exception.

These exceptions are \EngineException objects, and unlike all userland exceptions, do not extend the base \Exception class. This is to ensure that existing code that catches the \Exception class does not start catching fatal errors moving forward. It thus maintains backwards compatibility.

In the future, if you wish to catch both traditional exceptions and engine exceptions, you will need to catch their new shared parent class, \BaseException.

Additionally, parse errors in eval()'ed code will throw a \ParseException, while type mismatches will throw a \TypeException.

Here’s an example:

try {
    nonExistentFunction();
}
catch (\EngineException $e) {
     var_dump($e);
}

object(EngineException)#1 (7) {
  ["message":protected]=>
  string(32) "Call to undefined function nonExistantFunction()"
  ["string":"BaseException":private]=>
  string(0) ""
  ["code":protected]=>
  int(1)
  ["file":protected]=>
  string(17) "engine-exceptions.php"
  ["line":protected]=>
  int(1)
  ["trace":"BaseException":private]=>
  array(0) {
  }
  ["previous":"BaseException":private]=>
  NULL
}

The Exception hierarchy is changed like:

BaseException(abstract)
+- EngineException
+- TypeException
+- ParseException
+- Exception
+- ErrorException
+- RuntimeException
+- ...

Throwable Interface

As explain in the previous section, PHP7 has introduced exceptions as a replacement for fatal or recoverable fatal errors. These exceptions do not extend Exception, but instead extend a new class BaseException and are named EngineException, TypeException, and ParseException. These classes have a naming scheme that is unintuitive and will lead to confusion, especially for newer users.

For example, you can now catch exceptions like:

function add(int $left, int $right) {
    return $left + $right;
}
 
try {
    echo add('left', 'right');
} catch (Exception $e) {
    // Handle exception
} catch (TypeException $e) {
    // Log error and end gracefully
}

For more informations about Throwable interface, go to: https://wiki.php.net/rfc/throwable-interface