Once PHP 7.3 version has been released, I decided to take a detailed look into PHP development: what is being developed and what is the direction towards better understand its new potential and optimisations of this widely popular programming language.
After looking around for a condensed list of features that PHP implemented during development ofPHP v7.x, I have decided to compile the list by myself — a nice catch up that I believe someone may find useful as well.
We will start at PHP 5.6 as a baseline and will look into what’s been added, changed. I’ve as well added links to official documentation for each of the mentioned things, so if you are interested into in depth reading — feel free.
Anonymous Class Support
An anonymous class might be used over a named class:
new class($i) {
public function __construct($i) {
$this->i = $i;
}
}
The Integer division function — safe way to divide (even by 0)
It returns the integer division of the first operand by the second. If the divisor (the second operand) is zero, it throws an E_WARNING and returns FALSE.
intdiv(int $numerator, int $divisor)
Added new null coalesce operator — the “??”
$x = NULL;
$y = NULL;
$z = 3;
var_dump($x ?? $y ?? $z); // int(3)
$x = ["c" => "meaningful_value"];
var_dump($x["a"] ?? $x["b"] ?? $x["c"]); // string(16) "meaningful_value"
Added new operator — space ship (<=>)
Used to optimise and simplify comparison operations.
// Before
f unction order_func($a, $b) {
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}
// Using <=> operator
function order_func($a, $b) {
return $a <=> $b;
}
Scalar type declarations
This is just the first step towards achieving stronger typed programming language benefits in PHP — v0.5.
function add(float $a, float $b): float {
return $a + $b;
}
add(1, 2); // float(3)
Return type declarations
Added ability to return types beyond scalar — classes including inheritance. Heh, still somehow totally missed ability to make it optional (introduced in v7.1 :) )
interface A {
static function make(): A;
}
class B implements A {
static function make(): A {
return new B();
}
}
// Explicit use syntax:
use FooLibrary\Bar\Baz\ClassA;
use FooLibrary\Bar\Baz\ClassB;
use FooLibrary\Bar\Baz\ClassC;
use FooLibrary\Bar\Baz\ClassD as Fizbo;
// Grouped use syntax:
use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo };
Generator DelegationThe following new syntax is allowed in the body of generator functions:
yield from <expr>
PHP 7 is up to twice as fast as PHP 5.6.
As you can see from the charts, PHP 7.0 is a huge improvement in terms of performance and memory usage. For the page with the database queries, version 7.0.0 is more than 3 times faster than 5.6 with opcache enabled and 2.7 times faster without opcache! In terms of memory usage, the difference is also significant!
Throwable interfaceRestructured exception classes to have a naming scheme that is unintuitive and will lead to less confusion, especially for newer users.
Errors
and Exceptions
are now implementing Throwable
.
Here is Throwable
hierarchy:
interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException
⚠ Caution! You can only implement _Throwable_
through _Error_
and _Exception_
.
Unicode Codepoint Escape Syntax — “\u{xxxxx}”
echo "\u{202E}Reversed text"; // outputs Reversed text
echo "mañana"; // "ma\u{00F1}ana"
echo "mañana"; // "man\u{0303}ana" "n" with combining ~ character (U+0303)
Context Sensitive LexerWith this globally reserved words became semi-reserved:
callable class trait extends implements static abstract final public protected private const
enddeclare endfor endforeach endif endwhile and global goto instanceof insteadof interface
namespace new or xor try use var exit list clone include include_once throw array
print echo require require_once return else elseif default break continue switch yield
function if endswitch finally for foreach declare case do while as catch die self parent
Except it’s still forbidden to define a class constant named as class
because of the class name resolution ::class
.
Level support for the dirname() function
function answer(): ?int {
return null; //ok
}
function answer(): ?int {
return 42; // ok
}
function answer(): ?int {
return new stdclass(); // error
}
function say(?string $msg) {
if ($msg) {
echo $msg;
}
}
say('hello'); // ok -- prints hello
say(null); // ok -- does not print
say(); // error -- missing parameter
say(new stdclass); //error -- bad type
function should_return_nothing(): void {
return 1; // Fatal error: A void function must not return a value
}
Unlike other return types which are enforced when a function is called, this type is checked at compile-time, which means that an error is produced without the function needing to be called.
A function with a void
return type, or void function, may either return implicitly, or have a return statement without a value:
function lacks_return(): void {
// valid
}
Iterable pseudo typeIt is common for a function to accept or return either an array
or an object implementing Traversable
to be used with foreach
. However, because array
is a primitive type and Traversable
is an interface, there currently is no way to use a type declaration on a parameter or return type to indicate that the value is iterable.
function foo(iterable $iterable) {
foreach ($iterable as $value) {
// ...
}
}
iterable
can also be used as a return type to indicate a function will return an iterable value. If the returned value is not an array or instance of Traversable
, a TypeError
will be thrown.
function bar(): iterable {
return [1, 2, 3];
}
Parameters declared as iterable
may use null
or an array as a default value.
function foo(iterable $iterable = []) {
// ...
}
class Closure {
...
public static function fromCallable(callable $callable) : Closure {...}
...
}
Square bracket syntax for array destructuring assignment
$array = [1, 2, 3];
// Assigns to $a, $b and $c the values of their respective array elements in $array with keys numbered from zero
[$a, $b, $c] = $array;
// Assigns to $a, $b and $c the values of the array elements in $array with the keys "a", "b" and "c", respectively
["a" => $a, "b" => $b, "c" => $c] = $array;
Square bracket syntax for list()
$powersOfTwo = [1 => 2, 2 => 4, 3 => 8];
list(1 => $oneBit, 2 => $twoBit, 3 => $threeBit) = $powersOfTwo;
class Token {
// Constants default to public
const PUBLIC_CONST = 0;
// Constants then also can have a defined visibility
private const PRIVATE_CONST = 0;
protected const PROTECTED_CONST = 0;
public const PUBLIC_CONST_TWO = 0;
//Constants can only have one visibility declaration list
private const FOO = 1, BAR = 2;
}
Catching Multiple Exception Types
try {
// Some code...
} catch (ExceptionType1 | ExceptionType2 $e) {
// Code to handle the exception
} catch (\Exception $e) {
// ...
}
<?php
class ArrayClass {
public function foo(array $foo) { /* ... */ }
}
// This RFC proposes allowing the type to be widened to be untyped aka any
// type can be passed as the parameter.
// Any type restrictions can be done via user code in the method body.
class EverythingClass extends ArrayClass {
public function foo($foo) { /* ... */ }
}
Counting of non-countable objects
Calling count()
on a scalar or object that doesn’t implement the Countable interface (http://php.net/manual/en/class.countable.php) returns 1 (illogical).
In this version added a warning when calling count()
with a parameter that is a scalar, null, or an object that doesn’t implement Countable.
Trailing Commas In List Syntax in namespace uses
use Foo\Bar\{ Foo, Bar, Baz, };
Argon2 Password Hash
The existing password_ functions provided a forward compatible, simplified interface for hashing passwords. This RFC proposes the implementation of Argon2i (v1.3) within the password_ functions for use as a secure alternative to Bcrypt.
Debugging PDO Prepared Statement Emulation
$db = new PDO(...);
// works with statements without bound values
$stmt = $db->query('SELECT 1');
var_dump($stmt->activeQueryString()); // => string(8) "SELECT 1"
$stmt = $db->prepare('SELECT :string');
$stmt->bindValue(':string', 'foo');
// returns unparsed query before execution
var_dump($stmt->activeQueryString()); // => string(14) "SELECT :string"
// returns parsed query after execution
$stmt->execute();
var_dump($stmt->activeQueryString()); // => string(11) "SELECT 'foo'"
JSON_THROW_ON_ERRORNot having an adequate way to handle errors when using JSON has been a problem for quite a long time, and web developers all over the world have seen this as a huge downside of the language.
Until PHP v7.2 we needed to use a workaround to get an error from JSON and it was neither reliable, nor proficient in its job;
Here is an example:
json_decode("{");
json_last_error() === JSON_ERROR_NONE // the result is false
json_last_error_msg() // The result is "Syntax error"
So let’s see how we could employ this new sugar:
use JsonException;
try {
$json = json_encode("{", JSON_THROW_ON_ERROR);
return base64_encode($json);
} catch (JsonException $e) {
throw new EncryptException('Could not encrypt the data.', 0, $e);
}
As you can see from the previous code the json_encode function has now an optional parameter JSON_THROW_ON_ERROR — this will catch the error and display it using the following exception methods:
$e->getMessage(); // like json_last_error_msg()
$e->getCode(); // like json_last_error()
// Before:
if (is_array($foo) || $foo instanceof Countable) {
// $foo is countable
}
// After
if (is_countable($foo)) {
// $foo is countable
}
Added array functions array_key_first(), array_key_last()
$firstKey = array_key_first($array);
$lastKey = array_key_last($array);
Added native “same site” cookie supportThere are now two possibilities for a cookie that is using the samesite flag: “Lax” and “Strict”. The difference between Lax and Strict is the accessibility of the cookie in requests originating from another registrable domain using the HTTP GET method. Cookies using Lax will be accessible in a GET request originated from another registrable domain, whereas cookies using Strict will not be accessible in a GET request originated from another registrable domain. There is no difference with POST methods: the browser should not allow the cookie to be accessed in a POST request originating from another registrable domain.
Set-Cookie: key=value; path=/; domain=example.org; HttpOnly; SameSite=Lax|Strict
Argon2 Password Hash EnhancementsThe existing password_ functions provided a forward compatible, simplified interface for hashing passwords. This RFC proposes the implementation of Argon2id within the password_ functions for use as a secure alternative to the originally proposed Argon2i.
Allow a Trailing Comma in Function Calls
$newArray = array_merge(
$arrayOne,
$arrayTwo,
['foo', 'bar'], // comma is allowed in function calls
);
$array = [1, 2];
list($a, &$b) = $array;
This is equivalent to the following:
$array = [1, 2];
$a = $array[0];
$b =& $array[1];
Deprecated case-insensitive constants
class User {
public int $id;
public string $name;
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
}
Foreign Function InterfaceFFI is one of the features that made Python and LuaJIT very useful for fast prototyping. It allows calling C functions and using C data types from pure scripting language and therefore develop “system code” more productively. For PHP, FFI opens a way to write PHP extensions and bindings to C libraries in pure PHP.
Null Coalescing Assignment Operator
// The folloving lines are doing the same
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';
// Instead of repeating variables with long names, the equal coalesce operator is used
$this->request->data['comments']['user_id'] ??= 'value';
PreloadingPHP has been using opcode caches for ages (APC, Turck MMCache, Zend OpCache). They achieve significant performance boost by ALMOST completely eliminating the overhead of PHP code recompilation. Preloading is going to be controlled by just a single new php.ini directive — opcache.preload. Using this directive we will specify a single PHP file — which will perform the preloading task. Once loaded, this file is then fully executed — and may preload other files, either by including them or by using the opcache_compile_file() function.
Always available hash extensionThis will make hash extension (ext/hash
) always available, similar to the date
. The hash extension provides a very rich utility with many hashing algorithms which is extremely useful in modern day applications, not only in userland code but also very much in internals.
In short. When you start a PHP program, the Zend Engine will parse the code into an abstract syntax tree (AST) and translate it to opcodes. The opcodes are execution units for the Zend Virtual Machine (Zend VM). Opcodes are pretty low-level, so much faster to translate to machine code than the original PHP code. PHP has an extension named OPcache in core, to cache these opcodes.
“JIT” is a technique that will compile parts of the code at runtime, so that the compiled version can be used instead.
This is one of the last and biggest PHP optimisations strategies still on the table. PHP engineers are looking forwards to seeing how much this new approach can squeeze out of their applications. Really keen on seeing this myself.
Consistent type errors for internal functions
Make the internal parameter parsing APIs always generate a TypeError
if parameter parsing fails. It should be noted that this also includes ArgumentCountError
(a child of TypeError
) for cases where too few/many arguments were passed.
I have composed a simple test to help easily compare performance of different PHP versions (using Docker). This would even allow to easily check performance of new PHP versions just by adding new container names.
Running on Macbook pro, 2,5 GHz Intel Core i7.
PHP version : 5.6.40
--------------------------------------
test_math : 1.101 sec.
test_stringmanipulation : 1.144 sec.
test_loops : 1.736 sec.
test_ifelse : 1.122 sec.
Mem: 429.4609375 kb Peak mem: 687.65625 kb
--------------------------------------
Total time: : 5.103
PHP version : 7.0.33
--------------------------------------
test_math : 0.344 sec.
test_stringmanipulation : 0.516 sec.
test_loops : 0.477 sec.
test_ifelse : 0.373 sec.
Mem: 421.0859375 kb Peak mem: 422.2109375 kb
--------------------------------------
Total time: : 1.71
PHP version : 7.1.28
--------------------------------------
test_math : 0.389 sec.
test_stringmanipulation : 0.514 sec.
test_loops : 0.501 sec.
test_ifelse : 0.464 sec.
Mem: 420.9375 kb Peak mem: 421.3828125 kb
--------------------------------------
Total time: : 1.868
PHP version : 7.2.17
--------------------------------------
test_math : 0.264 sec.
test_stringmanipulation : 0.391 sec.
test_loops : 0.182 sec.
test_ifelse : 0.252 sec.
Mem: 456.578125 kb Peak mem: 457.0234375 kb
--------------------------------------
Total time: : 1.089
PHP version : 7.3.4
--------------------------------------
test_math : 0.233 sec.
test_stringmanipulation : 0.317 sec.
test_loops : 0.171 sec.
test_ifelse : 0.263 sec.
Mem: 459.953125 kb Peak mem: 460.3984375 kb
--------------------------------------
Total time: : 0.984
PHP version : 7.4.0-dev
--------------------------------------
test_math : 0.212 sec.
test_stringmanipulation : 0.358 sec.
test_loops : 0.205 sec.
test_ifelse : 0.228 sec.
Mem: 459.6640625 kb Peak mem: 460.109375 kb
--------------------------------------
Total time: : 1.003
If interested in testing yourself, you can find code in repository below.
I really liked visual performance compilation from servebolt.com of all major versions from 5.6 and up. See the results in the tables below.
PHP 7.0.0 was a major milestone with significantly improved performance and lower memory usage but PHP maintainers are simply running out of ideas to improve it. One of the remaining points is JIT (Just in time) compilation. And it’s coming with PHP 8.0.
Throughout PHP versions 7.x there is a visible path towards more typed (and a bit more objective) and modern programming language. Still PHP likes to adopt neat and useful features from other programming languages.
Soon we may see some more nice features, like:
With these in the pipeline, PHP developers would join the pool of modern language adopters. No language is perfect, but PHP is paving it’s way to the future.
#php #laravel #programming