PHP 8 – Key Features & Improvements with Screenshots

PHP 8
23Jul

Technology

Since its inception in the early 90s, PHP has evolved from a fun way to track visits to a website to a powerful, autonomous tool used to develop large-scale, business-critical web systems. With an estimated 80% market share, it’s now the server-side programming language of choice for most web development professionals to create dynamic and interactive websites and applications.

PHP 8

Although PHP 7.x brought significant improvements over PHP 5.x in terms of speed, performance, and lower memory usage, the team has been continuously working to make the language even better, faster, and more feature-rich as the performance improvements in versions 7.1 and 7.2 were modest. As a result of this active development, PHP 8 is all set to release by the end of 2020, with its second alpha released a few weeks ago.

PHP 8

PHP 8 is on the way, and what could be a better way to celebrate its 25th anniversary than getting familiar with all the new goodies coming to the next major release of the language? Let’s dive into some of the most exciting new features and improvements that will make PHP more efficient and reliable.

Highlights:

PHP JIT (Just in Time Compiler)

One of the most promising features coming to PHP 8 is Just-in-time (JIT) compilation. PHP JIT is almost independent of OPcache and is introduced to bring significant improvements to language performance.

To better understand JIT, you first need to be familiar with how PHP executes from the source code to render the final output. The PHP runs the source code in 4 steps:

  • Lexing/Tokenizing: The PHP code will be read and converted into a set of tokens by the interpreter.
  • Parsing: The interpreter matches the syntax rules with the script and utilizes tokes to create an abstract syntax tree (AST) – a hierarchical representation of the source code structure.
  • Compilation: The interpreter travel across the AST and translates nodes into Opcode – the language that Zend virtual machine understands.
  • Interpretation: The Zend VM finally interprets and runs the Opcode to produce the final results.

PHP 8

And to save time in the above process, we have OPCache (Opcode Cache) that improves PHP performance by storing the results of the compilation step in shared memory, thereby eliminating the need for the interpreter to parse, compile, and execute scripts over and over again on each request.

PHP 8

With the implementation of preloading in PHP 7.4, OPcache received significant improvements. However, its performance is still not optimized for typical web-based applications, and PHP JIT attempts to do the same.

The PHP JIT compiler takes advantage of DynASM (Dynamic Assembler) to translate Opcode into machine code and run that code instead of transferring it to the Zend VM. Thus, bypassing compilation or say removing the need for the Zend VM, the PHP JIT optimizes the memory usage and offers remarkable performance improvements for numerical and “typical” PHP web application codes.

PHP 8 New Features & Improvements

Apart from JIT, PHP 8 brings us a whole bunch of powerful features and improvements. Following is our handpicked selection of enhancements made to the world’s most popular server-side scripting language:

#Union Types 2.0

Union types accept values of several different types. Currently, PHP doesn’t support arbitrary union types, but it supports only two special union types:

  • Type or null through the special ?Type syntax
  • array or Traversable via special iterable type.

You can only specify arbitrary union types in phpdoc annotations as shown in the following example:

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

But starting PHP 8, you’ll be able to specify arbitrary union types with a T1|T2|… syntax and use them in all positions where they are accepted.

class Number {
    private int|float $number;
 
    public function setNumber(int|float $number): void {
        $this->number = $number;
    }
 
    public function getNumber(): int|float {
        return $this->number;
    }
}

Learn more about Union Types V2 in the RFC.

#Attributes v2

Also known as Annotations, Attributes are a form of structured metadata that you can use to define properties for files, elements, or objects.

So far, PHP has been allowing you to add an unstructured form of such metadata via doc-comments. PHP 8 introduces Attributes as a new way to add structured, syntactic metadata to declarations of functions, properties, classes, etc.

Similar to doc-block comments, Attributes can be added before the declaration they belong to:

<<ExampleAttribute>>
class Foo
{
    <<ExampleAttribute>>
    public const FOO = 'foo';
 
    <<ExampleAttribute>>
    public $x;
 
    <<ExampleAttribute>>
    public function foo(<<ExampleAttribute>> $bar) { }
}
 
$object = new <<ExampleAttribute>> class () { };
 
<<ExampleAttribute>>
function f1() { }
 
$f2 = <<ExampleAttribute>> function () { };
 
$f3 = <<ExampleAttribute>> fn () => 1;

You can declare Attributes before or after a doc-block comment:

<<ExampleAttribute>>
/** docblock */
<<AnotherExampleAttribute>>
function foo() {}

Each declaration may have one or more attributes and each attribute may have one or more values associated with it:

<<WithoutArgument>>
<<SingleArgument(0)>>
<<FewArguments('Hello', 'World')>>
function foo() {}

You can even use the same attribute name more than once in a declaration. Learn more about Attributes v2 in the RFC.

#Match Expression v2

The match expression is the enhanced version of the switch expression. It has been introduced to address long-standing shortcomings of the switch, namely:

  • Type coercion
  • No return value
  • Fallthrough
  • Inexhaustiveness

The new match expression uses strict type comparisons, returns values, doesn’t require break statements, and throws an error if a condition isn’t met. It looks like this:

match ($condition) {
    1 => {
        foo();
        bar();
    },
    2 => baz(),
}
 
$expressionResult = match ($condition) {
    1, 2 => foo(),
    3, 4 => bar(),
    default => baz(),
};

Read up on the match in detail over here.

#Mixed Type v2

With the addition of union types in 8.0, PHP development experts may explicitly declare type information for most class properties and function returns or parameters. Keeping this in mind, PHP 8 adds the mixed pseudo-type to PHP’s type system. You can use it when the parameter/return/property are of any type.

public function foo(mixed $value){}

To be more precise, a type of mixed would be equivalent to a Union Type of:

array|bool|callable|int|float|null|object|resource|string

Learn more about Mixed Type v2 in the RFC.

#Validation for Abstract Trait Methods

Traits are a mechanism for code reuse in PHP. They are typically used to declare methods used in more than one class.

Traits can contain abstract methods to impose requirements on the classes using them. However, the signatures of these method implementations are currently only spottily enforced.

The following example relates to not-enforced Abstract trait method signatures:

trait T {
    abstract public function test(int $x);
}
 
class C {
    use T;
 
    // Allowed, but shouldn't be due to invalid type.
    public function test(string $x) {}
}

PHP 8 performs proper method signature validation and always generates a fatal error if the implementing method is incompatible with the abstract trait method.

Fatal error: Declaration of C::test(string $x) must be compatible with T::test(int $x) in /path/to/your/test.php on line 10

#Consistent Type Errors for Internal Functions

For passing a parameter of illegal type in PHP 7.4, user-defined functions throw a TypeError while internal functions typically emit a warning and return null. See the following example:

var_dump(strlen(new stdClass));

The above line of code throws the following warning:

// Warning: strlen () expects parameter 1 to be string, object given
// NULL

To eliminate inconsistencies, PHP 8 consistently throws TypeError for all invalid parameter types, including internal functions. As a result, the code above generates the following error in PHP 8:

Fatal error: Uncaught TypeError: strlen(): Argument #1 ($str) must be of type string, object given in /path/to/your/test.php:4
Stack trace:
#0 { main }
thrown in /path/to/your/test.php on line 4

#Constructor Property Promotion

Currently, when you define a simple value object, you have to repeat all properties multiple times:

class Point {
    public float $x;
    public float $y;
    public float $z;
 
    public function __construct(
        float $x = 0.0,
        float $y = 0.0,
        float $z = 0.0,
    ) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }
}

PHP 8 introduces a short hand syntax to simplify the property declaration. The new and more concise syntax combines class properties and the constructor into a single class.

class Point {
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
    ) {}
}

This short-hand code is equivalent to the previous syntax but more concise.

#Arrays Starting with a Negative Index

In PHP 7.4, if the value of start_index is less than zero, the first index is start_index and the following indices start from zero. Look at the following example:

$a = array_fill(-5, 4, true);
var_dump($a);

This results the following:

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

PHP 8 makes implicit array keys consistent by always using start_index + 1 for the second index, regardless the value of start_index. Consequently, the syntax above would result in the following array:

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

#Fatal Error for Incompatible Method Signatures

Inheritance errors caused by incompatible abstract and non-abstract method signatures either throw a fatal error or a warning, depending on the cause or origin of the error.

For example, in PHP 7.4, the following code generates a fatal error:

interface I {
    public function method(array $a);
}
class C implements I {
    public function method(int $a) {}
}
// Fatal error: Declaration of C::method(int $a) must be compatible with I::method(array $a)

On the other hand, the following code only generates a warning:

class C1 {
    public function method(array $a) {}
}
class C2 extends C1 {
    public function method(int $a) {}
}
// Warning: Declaration of C2::method(int $a) should be compatible with C1::method(array $a)

PHP 8 always generates a fatal error for incompatible method signatures, no matter the origin of the error.

#throw Expression

In the previous version of PHP, throw is a statement, making it impossible to throw exceptions in places where only expressions are accepted.

PHP 8 converts the throw statement into an expression so you can use it in places where expressions are allowed. Look at the following examples:

$callable = fn() => throw new Exception();
 
$value = $nullableValue ?? throw new InvalidArgumentException();
$value = $falsableValue ?: throw new InvalidArgumentException();

#Trailing Comma in Parameter List

Trailing commas are commas added to a list of items in different contexts. PHP currently supports trailing commas in function calls and list syntax.

PHP 8 lets you use a single optional trailing comma in parameter lists for functions, methods, and closures. Here is an example:

class Uri {
    private function __construct(
        ?string $a,
        ?int $b,
        ?float $c, // trailing comma
    ) {
        ...
    }
}

#Weak Maps

PHP 7.4 introduced weak references to allow developers to retain a reference to an object that doesn’t prevent itself from getting destroyed. However, their usefulness is limited.

A weak map holds data (objects) without preventing it from being garbage collected. PHP 8 introduces a WeakMap class with the following prototype:

final class WeakMap implements ArrayAccess, Countable, Traversable {
    public function offsetGet($object);
    public function offsetSet($object, $value): void;
    public function offsetExists($object): bool;
    public function offsetUnset($object): void;
    public function count(): int;
}

As a result, if an object used as a weak map key is garbage collected, the key is destroyed and removed from the weak map. See the following example:

$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 42;
var_dump($map);

In PHP 8, this code will produce the result below:

// object(WeakMap)#1 (1) {
//   [0]=>
//   array(2) {
//     ["key"]=>
//     object(stdClass)#2 (0) {
//     }
//     ["value"]=>
//     int(42)
//   }
// }

Unsetting the object leads to automatically removing the key from the weak map:

unset($obj);
var_dump($map);

Now the result would be:

// object(WeakMap)#1 (0) {

// }

#Allowing ::class on objects

A small, yet useful, new feature! PHP 8 allows you to use to use ::class on objects and get the same result as get_class($object) .

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

If $object is not an object, $object::class throws a TypeError exception.

#New static Return Type

While it was already possible to return self and parent types, PHP 8 allows you to use static as a return type.

class Foo
{
    public function test(): static
    {
        return new static();
    }
}

#Non-capturing Catches

If you want to catch an exception in PHP 7.4, you have to store it in a variable, regardless of whether or not you use it.

On the contrary, PHP 8 allows you to catch exceptions without storing them to variables, so instead of this:

try {
    changeImportantData();
} catch (PermissionException $ex) {
    echo "You don't have permission to do this";
}

You can now do this:

try {
    changeImportantData();
} catch (PermissionException) {
    echo "You don't have permission to do this";
}

#New Stringable interface

PHP 8 introduces a new Stringable interface that is automatically added to anything that is a string or implements the __to String ( ) method. You don’t have to implement it manually. Here is an example:

interface Stringable
{
   public function __toString(): string;
}

#Always Available JSON Extension

A subtle yet healthy change in PHP 8 is the continuous availability of the JSON extension. Currently, you can compile PHP with ./configure –disable-json. However, this is not possible anymore with PHP 8.

Given that JSON is widely used for many use cases, PHP 8 makes it impossible to disable it through configuration or build options. Now developers have the guarantee that JSON is always enabled.

#Stable Sorting

Sorting functions are currently unstable in PHP, resulting in no guarantee of the order of equal elements. PHP 8 makes all sorting functions stable by default.

#Remove Inappropriate Inheritance Signature Checks

In the current PHP version, a method with a similar name to a parent’s method is still checked against some inheritance rules regardless of the parent’s method is public or private.

Given that private methods cannot be called outside of their scope, PHP 8 removes inappropriate inheritance checks for the cases when the parent method is private.

#Object-based token_get_all() Alternative

Currently, the token_get_all() returns tokens either as a single-character string, or an array of values.

PHP 8 introduces a new PhpToken class with a PhpToken::getAll() method, which acts as an alternative to token_get_all() method. Instead of a mix of strings and arrays, PhpToken::getAll() returns an array of PhpToken objects.

New Functions in PHP 8

In addition to the features and improvements mentioned above, PHP 8 also adds several new functions to the language:

#str_contains: This new function checks if a string is contained within another string and returns a Boolean value, true or false, depending on the situation.

str_contains ( string $haystack , string $needle ) : bool

#get_debug_type: This new PHP function returns the true type name of a variable and resolves class names. get_debug_type works in the same way as the gettype function, but it returns a more useful output for classes, objects, arrays, and strings, so instead of this:

$bar = $arr['key'];
if (!($bar instanceof Foo)) { 
  throw new TypeError('Expected ' . Foo::class . ' got ' . 
(is_object($bar) ? get_class($bar) : gettype($bar)));
}

You can now use this:

if (!($bar instanceof Foo)) { 
  throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}

You can see a full list of differences between get_debug_type() and gettype() here.

#str_starts_with() and str_ends_with(): These new PHP functions check if a string starts or ends with a certain substring.

str_starts_with(string $haystack, string $needle): bool

str_ends_with(string $haystack, string $needle): bool

Both functions return false if $needle is longer than $haystack.

Backward Compatibility

Since PHP 8 is a major update to the language, you can expect it to introduce some breaking changes that may break your existing code. In other words, chances are very high that you’ll have to make significant changes to your code to get it running on PHP 8.

If you have kept yourself up-to-date with the recent releases, upgrading to PHP 8 shouldn’t be that difficult as most breaking changes were deprecated in version 7.x. As always, you can take help from the upgrading document that serves as an excellent guide for upgradation. However, if you still face any problems upgrading to PHP 8, you can always hire certified PHP developers.

Recent Articles