php 8

La nuova major release di PHP

Matteo Galacci

Software architect & Back-end Developer

Github:  https://github.com/matiux

Slack GRUSP:  matiux

Email:  m.galacci@gmail.com

Linkedin: Matteo Galacci

  • Developer since 2000
  • Consultant since 2008  
  • Software architect
  • Back-end developer
  • Domain Driven Design & Clean Code evangelist 
  • Member of PUG Romagna admins

Grazie a...

History

  • Released: 3 December 2015
  • Last major was PHP 5, 13 July 2004 (11 years)
  • Last release is 7.4.14 (07 January 2021)

PHP 7

  • Released: 26 November 2020
  • Last major was PHP 7, 3 December 2015 (5 years)
  • Last release is 8.0.1 (07 January 2021)

PHP 8

SUPPORTED VERSIONS

PHP 7 IN A NUTSHELL

  • Great performance (thanks to PHPNG)
  • *Return/Scalar Type Declarations (But it is not mandatory like java). 
  • Anonymous classes (Useful in unit test to mock a class)
  • Nullable types (by prefixing the type name with a question mark)
  • Void return type (It was supported but now if esplicited, gives error)
  • New Error hierarchy (e.g. Fatal error, including file parser error, etc). Error and Exception implement both Throwable interface
  • Multiple catch

*Disabled by default. Use declare(strict_types=1); in each file

More info here

7.0| Scalar type declaration

<?php

// Coercive mode

function sumOfInts(int ...$ints)
{
    return array_sum($ints);
}

var_dump(sumOfInts(2, '3', 4.1));

//int(9)
<?php

// strict mode

declare(strict_types=1);

function sumOfInts(int ...$ints)
{
    return array_sum($ints);
}

var_dump(sumOfInts(2, '3', 4.1));

Fatal errors: Uncaught TypeError: Argument 2 passed to sumOfInts() must be of the type integer, string given

 

Next TypeError: Argument 3 passed to sumOfInts() must be of the type integer, float given, called in

7.0| return type declaration

<?php

function arraysSum(array ...$arrays): array
{
    return array_map(function(array $array): int {
        return array_sum($array);
    }, $arrays);
}

print_r(arraysSum([1,2,3], [4,5,6], [7,8,9]));


Array
(
    [0] => 6
    [1] => 15
    [2] => 24
)

7.0| Anonymous classes

<?php

interface Logger {

    public function log(string $msg);
}

class Application {

    private $logger;

    public function getLogger(): Logger {
         return $this->logger;
    }

    public function setLogger(Logger $logger) {
         $this->logger = $logger;
    }
}

$app = new Application();

$app->setLogger(new class implements Logger {

    public function log(string $msg) {
    
        echo $msg;
    }
});

var_dump($app->getLogger());

7.0| Null coalescing operator

<?php

// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';

// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

// Coalescing can be chained: this will return the first
// defined value out of $_GET['user'], $_POST['user'], and
// 'nobody'.
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';

7.1| Nullable types

<?php

function testReturn(?string $string): ?string
{
    return $string;
}

echo testReturn(null); // null
echo testReturn('foo'); // foo

7.1| Void functions

<?php

function testReturn(string $string): void
{
    echo $string;
}

echo testReturn('foo'); // foo

7.1| Class constant visibility

<?php

class ConstDemo
{
    const PUBLIC_CONST_A = 1;
    public const PUBLIC_CONST_B = 2;
    protected const PROTECTED_CONST = 3;
    private const PRIVATE_CONST = 4;
}

7.1| Multi catch exception handling

<?php

try {

    // some code
    
} catch (FirstException | SecondException $e) {

    // handle first and second exceptions
}

7.1| Symmetric array destructuring

<?php
$data = [
    [1, 'Tom'],
    [2, 'Fred'],
];

// list() style
list($id1, $name1) = $data[0];

// [] style
[$id1, $name1] = $data[0];

// list() style
foreach ($data as list($id, $name)) {
    // logic here with $id and $name
}

// [] style
foreach ($data as [$id, $name]) {
    // logic here with $id and $name
}

7.1| Support for keys in list() 

<?php
$data = [
    ["id" => 1, "name" => 'Tom'],
    ["id" => 2, "name" => 'Fred'],
];

// list() style
list("id" => $id1, "name" => $name1) = $data[0];

// [] style
["id" => $id1, "name" => $name1] = $data[0];

// list() style
foreach ($data as list("id" => $id, "name" => $name)) {
    // logic here with $id and $name
}

// [] style
foreach ($data as ["id" => $id, "name" => $name]) {
    // logic here with $id and $name
}

You can now specify keys in list(), or its new shorthand [] syntax. This enables destructuring of arrays with non-integer or non-sequential keys.

7.2| New object type

<?php

function test(object $obj) : object
{
    return new SplQueue();
}

test(new StdClass());

7.2| Abstract method overridinG

<?php

abstract class A
{
    abstract function test(string $s);
}

abstract class B extends A
{
    // overridden - still maintaining contravariance 
    // for parameters and covariance for return
    abstract function test($s) : int;
}

7.4| Typed properties

<?php

class User {
    public int $id;
    public string $name;
}

7.4| Typed properties

array_map(function (User $user) { 
    return $user->id; 
}, $users)

Also called "short closures".


array_map(fn (User $user) => $user->id, $users)
  • Access to parent scope. No need for the use keyword.
  • $this works like a normal closures.
  • Contains only one expression; the return statement.

7.4| ARROW FUNCTIONS

array_map(function (User $user) { 
    return $user->id; 
}, $users)

Also called "short closures".


array_map(fn (User $user) => $user->id, $users)
  • Access to parent scope. No need for the use keyword.
  • $this works like a normal closures.
  • Contains only one expression; the return statement.

7.4| TYPE VARIANCE

class ParentType {}

class ChildType extends ParentType {}

class A
{
    public function covariantReturnTypes(): ParentType
    {
    	/* … */ 
    }
}

class B extends A
{
    public function covariantReturnTypes(): ChildType
    { 
    	/* … */ 
    }
}

Covariance

7.4| TYPE VARIANCE

class ParentType {}

class ChildType extends ParentType {}

class A
{
    public function contraVariantArguments(ChildType $type)
    { 
    	/* … */ 
    }
}

class B extends A
{
    public function contraVariantArguments(ParentType $type)
    { 
    	/* … */ 
    }
}

Contravariance

7.4| Foreign function interface

A foreign function interface (FFI) is a mechanism by which a program written in one programming language can call routines or make use of services written in another.

 

Currently, accessing FFI data structures is significantly (about 2 times) slower than accessing native PHP arrays and objects. Therefore, it makes no sense to use the FFI extension for speed; however, it may make sense to use it to reduce memory consumption.

It was already possible to extend the PHP core. Phalcon framework is an example

7.4| Preloading

Another lower-level feature is preloading. It's is an amazing addition to PHP's core, which can result in some significant performance improvements.

In short: if you're using a framework, its files have to be loaded and linked on every request. Preloading allows the server to load PHP files in memory on startup, and have them permanently available to all subsequent requests.

The performance gain comes of course with a cost: if the source of preloaded files are changed, the server has to be restarted.

7.4| NULL COALESCING ASSIGNMENT OP.


$data['date'] = $data['date'] ?? new DateTime();

$data['date'] ??= new DateTime();

PHP 8

PHP 8 NEW FEATURES

php 8 new features

PHP 8, the new major PHP version, is expected to be released on December 3, 2020. That means there will be no PHP 7.5 version.

  • JIT compiler (RFC)
  • Union types 2.0 (RFC)
  • Constructor Property Promotion (RFC)
  • Static return type (RFC)
  • Allow ::class on objects (RFC)
  • Stringable interface (RFC)
  • Attributes v2 (RFC)
  • Named Arguments (RFC)
  • Nullsafe operator (RFC)
  • Arrays starting with a negative index (RFC)
  • throw expression (RFC)
  • Match expression v2 (RFC)
  • Allow trailing comma in parameter list (RFC)

php 8 | JIT

What's JIT?

A Just-In-Time (JIT) compiler is a feature of the run-time interpreter, that instead of interpreting bytecode every time a method is invoked, will compile the bytecode into machine code, and then invoke this object code instead

PHP 8 | JIT

Ciclo di vita

PHP 8 | JIT

<?php
for ($i=0; $i<100; $i++) {
    echo $i;
}	

Example

Opcode

L0 (2):     ASSIGN CV0($i) int(0)
L1 (2):     JMP L4
L2 (3):     ECHO CV0($i)
L3 (2):     PRE_INC CV0($i)
L4 (2):     T1 = IS_SMALLER CV0($i) int(100)
L5 (2):     JMPNZ T1 L2
L6 (5):     RETURN int(1)

Linguaggio semplice simile ad assembly

PHP 8 | JIT

Opcache

PHP 8 | JIT

Preloading in PHP 7.4

All’avvio del server, prima dell'esecuzione di ogni script si può caricare in memoria dei file PHP rendendo così il loro contenuto già pronto per le richieste successive che verranno fatto al server.

Classi e funzioni definite nei file in Preloading saranno disponibili nativamente, proprio come le entità interne.

Il Preloading è controllato dalla direttiva opcache.preload che specifica uno script PHP da compilare ed eseguire all'avvio del server. Questo script verrà usato per precaricare file aggiuntivi, includendoli oppure tramite la funzione opcache_compile_file(). 

PHP 8 | JIT

JIT traduce le parti calde del codice intermedio in codice macchina

PHP 8 | JIT

JIT

Previously we said that JIT can decide which portions to translate into machine code and which to leave in Opcode. How do you decide? Through a CRTO configuration, a number of 4 decimal places where each number represents a specific configuration.

CRTO = 1235

 

  • 1 = use AVX instructions (CPU optimization)
  • 2 = Register Allocation
  • 3 = profile on the fly and compile hot functions (Trigger)
  • 5 = type inference & procedure analysis (Optimization level)

https://php.watch/articles/jit-in-depth

PHP 8 | JIT

[C]PU-specific Optimization Flags
0: Disable CPU-specific optimization.
1: Enable use of AVX, if the CPU supports it.

[T]rigger
0: Compile all functions on script load.
1: Compile all functions on first execution.
2: Profile first request and compile the hottest functions afterwards.
3: Profile on the fly and compile hot functions.
4: Currently unused.
5: Use tracing JIT. Profile on the fly and compile traces for hot code segments.

[O]ptimization Level
0: No JIT.
1: Minimal JIT (call standard VM handlers).
2: Inline VM handlers.
3: Use type inference.
4: Use call graph.
5: Optimize whole script.

[R]egister Allocation
0: Don't perform register allocation.
1: Perform block-local register allocation.
2: Perform global register allocation.

CRTO

https://php.watch/articles/jit-in-depth#jit-flags

PHP 8 | JIT

JIT TRIGGER

  • 0 = Compile all functions on script load.
  • 1 = Compile all functions on first execution.
  • 2 = Profile first request and compile the hottest functions afterwards.
  • 3 = Profile on the fly and compile hot functions.
  • 4 = Currently unused. *
  • 5 = Use tracing JIT. Profile on the fly and compile traces for hot code segments.

* The option 4 under Triggers (T=4) did not make it to the final version of JIT implementation. It was trigger JIT on functions declared with @jit DocBlock comment attribute. This is now unused.

PHP 8 | JIT

CRTO default value: opcache.jit=tracing

  • disable = disable JIT
  • off = JIT is turned off but can be started at runtime
  • on = enable JIT to tracing (it should be alias of tracing)
  • tracing = CRTO equal to 1254
  • function = CRTO equal to 1205

In addition to these values you can specify the numerical value of CRTO, for example:
opcache.jit = 1254, equivalent to tracing.

The difference between function and tracing is that the function JIT will only try to optimise code within the scope of a single function, while the tracing JIT can look at the whole stack trace to identify and optimise hot code.

https://stitcher.io/blog/php-8-jit-setup

PHP 8 | JIT on Wordpress

  • PHP 8 no JIT: 190 req/sec
  • PHP 8 + JIT (CTRO 1235): 160 req/sec
  • PHP 8 + JIT (CTRO 1225): 189 req/sec
  • JIT is useful for CPU intensive applications (machine learning - PHP ML, image manipulation, pdf creations etc).
  • JIT does not offer improvement for IO Bound applications

PHP 8 | JIT

PHP 8 | Union types 2.0 (RFC)

  • Variabili con tipi multipli
  • Prima di PHP 8: Annotation oppure ?type
<?php

class Number {

    /**
     * @var int|float $number
     */
    private $number;
	
    /**
     * @param int|float $number
     */
    public function setNumber($number): void {
        $this->number = $number;
    }
	
    /**
     * @return int|float
     */
    public function getNumber() {
        return $this->number;
    }
}

PHP 8 | Union types 2.0 (RFC)

<?php

class Number {

    private int|float $number;
	
    public function setNumber(int|float $number): void {
        $this->number = $number;
    }
	
    public function getNumber(): int|float {
        return $this->number;
    }
}
  • Tipi effettivamente applicati, non suggeriti da phpdoc.
  • Non essendo commenti, non possono non essere aggiornati.
  • I tipi sono soggetti a ereditarietà, viene quindi rispettato il Principio di Sostituzione di Liskov (SOLID).

8.0| Constr. Prop. Promotion (RFC)

<?php

class Service
{
   private Dep1 $dep1;
   private array $params;

   public function __construct(
      Dep1 $dep1,
      array $params
   ) {
      $this->dep1 = $dep1;
      $this->params = $params;    
   }

   public function params(): array
   {
       return $this->params;
   }
}
<?php

class Service
{
   public function __construct(
      private Dep1 $dep1,
      private array $params
   ) {}

   public function params(): array
   {
       return $this->params;
   }
}

PHP 8 | Static return type (RFC)

<?php

class A {
 
 private function __construct(
    private int $a
 ){}

 public function create(int $a): self {
  return new self($a);
 }
}

class B extends A {
}

$b = new B();
$result = $b->create(2);

var_dump($result); 

// object(A) {
//   ["a"]=> 2
// }

Useful in fluent interfaces

<?php

class A {
 
 private function __construct(
    private int $a
 ) {}

 public function create($a): static {
  return new static($a);
 }
}

class B extends A {
}

$b = new B();
$result = $b->create(2);

var_dump($result); 

// object(B) {
//   ["a"]=> 2
// }

PHP 8 |Allow ::class on objects (RFC)

<?php

// PHP 5
// Foo\Bar::class

$object = new stdClass;
var_dump($object::class); // "stdClass"
 
$object = null;
var_dump($object::class); // TypeError

Con PHP 8, $object::class === get_class($object)

 

Se $object non è un oggetto viene generata un'eccezione TypeError

8.0| Stringable interface (RFC)

<?php

class Foo
{
    public function __toString(): string
    {
        return 'foo';
    }
}

function bar(string | Stringable $stringable): string
{
    return (string) $stringable;
}

var_dump(bar(new Foo())); //string(3) "foo"
var_dump(bar('abc')); //string(3) "abc"

Stringable interface is automatically added to classes that implement the __toString() method.

 

Ogni volta che una classe implementa __toString(), implementa automaticamente l'interfaccia Stringable e non quindi  necessario implementarla manualmente.

8.0| Attributes v2 (RFC)

First of all, custom attributes are simple classes, annotated themselves with the #[Attribute] attribute

<?php

declare(strict_types=1);

namespace PHP80\Attribute\ExampleA;

use Attribute;

#[Attribute]
class ListensTo
{
    /** @var class-string  */
    public string $event;

    public function __construct(string $event)
    {
        $this->event = $event;
    }
}

PHP's existing Doctrine-esque is widely used, but Attributes in PHP 8 uses the #[ and ] brace syntax. This was debated and changed from the initial <<Attr>> implementation to @@Attr to the final #[Attr] syntax.

8.0| Attributes v2 (RFC)

Most uses of attributes/annotations in PHP today revolve around registration.

Attributes allow to add structured, machine-readable metadata information on declarations in code:  Classes, methods, functions, parameters, properties and class constants can be the target of an attribute.

The metadata defined by attributes can then be inspected at runtime using the Reflection APIs.

Attributes and Annotations provide the same functionality. The word "Annotations" is already being used widely in PHP libraries and frameworks, so the name Attributes help to minimize the confusion with Annotations.

(e.g. Doctrine annotation)

8.0| Attributes v2 (RFC)

Be careful not to use annotations as another tool that takes development away from the Domain.

Attributes could therefore be thought of as a configuration language embedded directly into code.

8.0| Attributes v2 (RFC)

https://php.watch/articles/php-attributes

https://www.php.net/manual/en/language.attributes.overview.php

https://stitcher.io/blog/attributes-in-php-8

https://kinsta.com/it/blog/php-8/#attributes

https://platform.sh/blog/2020/php-8-0-feature-focus-attributes/

They shouldn't — and can't — be used for, for example, argument input validation.

Keep in mind the goal of attributes: they are meant to add meta data to classes and methods, nothing more.

8.0| Attributes v2 (RFC)

#[ArrayShape], #[ExpectedValues], #[NoReturn], #[Pure], #[Deprecated], #[Immutable]

https://blog.jetbrains.com/phpstorm/2020/10/phpstorm-2020-3-eap-4/

  • #[Psalm\Immutable] is equivalent to @psalm-immutable
  • #[Psalm\Pure] is equivalent to @psalm-pure
  • #[Psalm\Readonly] is equivalent to @readonly
  • #[Psalm\Deprecated] is equivalent to @deprecated
  • #[Psalm\Internal] is equivalent to @internal

https://psalm.dev/articles/php-8-attributes
https://psalm.dev/articles/php-8-support

Tip: as well as supporting all the new features outlined below, Psalm 4 can also tell you if your PHP 7 code might break in PHP 8 – just run it with --php-version=8.0

PhpStorm Attributes:

Psalm Attributes:

8.0| Named Arguments (RFC)

array_slice (
   array $array,
   int $offset,
   int|null $length = null,
   bool $preserve_keys = false
): array
<?php

$input = array("a", "b", "c", "d", "e");


// array(1) { [3]=> string(1) "d" }
array_slice($input, -2, 1, true);  
<?php

$input = ['a', 'b', 'c', 'd', 'e'];

// array(1) { [3]=> string(1) "d" }
array_slice(
   array: $input,
   offset: -2,
   length: 1,
   preserve_keys: true
);

8.0| Nullsafe operator (RFC)

<?php

// PHP < 8
if (is_null($repository)) {
    $result = null;
} else {
    $user = $repository->getUser(5);
    if (is_null($user)) {
        $result = null;
    } else {
        $result = $user->name;
    }
}
<?php

// PHP >= 8
$result = $repository?->getUser(5)?->name;

8.0| Arrays negative index (RFC)

Arrays starting with a negative index

<?php

// PHP <8.0
$a = array_fill(-5, 4, true);
var_dump($a);

// array(4) {
//	 [-5]=>
//	 bool(true)
//	 [0]=>
//	 bool(true)
//	 [1]=>
//	 bool(true)
//	 [2]=>
//	 bool(true)
// }
<?php

// PHP >=8.0
$a = array_fill(-5, 4, true);
var_dump($a);

// array(4) {
//	 [-5]=>
//	 bool(true)
//	 [-4]=>
//	 bool(true)
//	 [-3]=>
//	 bool(true)
//	 [-2]=>
//	 bool(true)
// }

8.0| throw expression (RFC)

throw can now be used as an expression. That allows usages like:

<?php

// Arrow function - PHP 7.4
$fn = fn() => throw new Exception('Exception in arrow function');

// Null coalescing operator - PHP 7.0 other than ??= PHP 7.4
$user = $session->user ?? throw new Exception('Must have user');

8.0| Match expression v2 (RFC)

<?php

$foo = 1;
$a = 1;
$b = 2;

$res = 0;

switch ($foo) {

   case $a:
     $res = $a;
     break;
   default:
     $res = $b;
     break;
}
<?php

$foo = 1;
$a = 'a';
$b = 'b';

$res = match ($foo) {
   $a => $a,
   default => $b
};
<?php

$result = match ($x) {
   foo() => ...,
   $this->bar() => ..., // bar() isn't called if foo() === $x
   $this->baz => beep(), // beep() isn't called unless $x === $this->baz
   $a, $b => throw new Exception()
   // etc.
};

8.0| trailing comma (RFC)

Allow trailing comma in parameter list

<?php
// PHP >= 7.3
function functionWithLongSignature(
   string $parameter1,
   string $parameter2
) {
}

functionWithLongSignature(
   'a',
   'b',
);
<?php
// PHP >= 8.0
function functionWithLongSignature(
   string $parameter1,
   string $parameter2, // <-- This comma is now allowed.
) {
}

functionWithLongSignature(
   parameter1: 'a',
   parameter2: 'b',
);

php 8 new functions

See the full list here: Standard library

<?php

// PHP < 8.0
if (strpos('string with lots of words', 'words') !== false) {}

// PHP >= 8.0 - case-sensitive
$string = 'Welcome to the real world';

if (str_contains(haystack: $string, needle: 'real')) {} // True

if (str_starts_with(haystack: $string, needle: 'Welcome')) {} // True

if (str_ends_with(haystack: $string, needle: 'world')) {} // True

php 8 references

OFFICIAL NEW FEATURES

 

PHP RFC official page
PHP RFC Watch
Brent Roose, New in PHP 8
Eli White, What’s in PHP Eight?, php[architect], Vol.19 - Issue 5, May 2020
Arkadiusz Kondas, Compiling PHP 8 from source with JIT support
Níckolas Da Silva, Understanding PHP 8's JIT
Benoit Jacquemont, PHP 8 et Just In Time Compilation, PHPForum 2019
Nikita Popov, PHP 7 Virtual Machine
Anthony Ferrara, A PHP Compiler, aka The FFI Rabbit Hole
James Titcumb, Climbing the Abstract Syntax Tree, PHP UK 2018

CREDITS