PHP 5.6 - 8.2


    // --- symmetric array destructuring, alternative to existing list()
    $data = [ [1, 'Tom'], [2, 'Fred'], ];
    list($id1, $name1) = $data[0]; // list() style
    [$id1, $name1] = $data[0]; // [] style
    foreach ($data as list($id, $name)) { // list() style
      // logic here with $id and $name
    }
    foreach ($data as [$id, $name]) { // [] style
      // logic here with $id and $name
    }
    // - reference assignments, also supported for list()
    [&$a, [$b, &$c]] = $d;
    // - keys in list()
    $data = [ ["id" => 1, "name" => 'Tom'], ["id" => 2, "name" => 'Fred'], ];
    list("id" => $id1, "name" => $name1) = $data[0]; // list() style
    ["id" => $id1, "name" => $name1] = $data[0]; // [] style
    foreach ($data as list("id" => $id, "name" => $name)) { // list() style
      // logic here with $id and $name
    }
    foreach ($data as ["id" => $id, "name" => $name]) { // [] style
      // logic here with $id and $name
    }

    // --- null coalescing - use a ternary in conjunction with isset()
    $username = $_GET['user'] ?? 'nobody'; // if $_GET['user'] does not exist returns 'nobody'
    // equivalent to:
    $username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
    // can be chained, return first defined value
    // out of $_GET['user'], $_POST['user'], and 'nobody'.
    $username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
    // enclose them in parenthesis null coalesce on typecasted properties
    $foo = new StdClass;
    $foo->bas ?? 23; // >>> 23
    $foo = new StdClass;
    $bar = (int) ($foo->bas ?? 23);
    var_dump($bar); // >>> 23
    $foo = new StdClass;
    $bar = (int) $foo->bas ?? 23;
    var_dump($bar); // >>> Notice: Undefined property: stdClass::$bas
    // - null coalescing assignment operator
    $array['key'] ??= computeDefault();
    // roughly equivalent to
    if (!isset($array['key'])) { $array['key'] = computeDefault(); }

    // --- spaceship operator - comparing two expressions
    // returns -1, 0 or 1 when $a is respectively less than, equal to, or greater than $b
    // integers
    echo 1 <=> 1; // 0
    echo 1 <=> 2; // -1
    echo 2 <=> 1; // 1
    // floats
    echo 1.5 <=> 1.5; // 0
    echo 1.5 <=> 2.5; // -1
    echo 2.5 <=> 1.5; // 1
    // strings
    echo "a" <=> "a"; // 0
    echo "a" <=> "b"; // -1
    echo "b" <=> "a"; // 1
    echo 5 <=> 8; // 5 - 8 = -3 >>> -1
    echo 2 <=> 2; // 2 - 2 = 0  >>>  0
    echo 4 <=> 2; // 4 - 2 = 2  >>>  1

    // --- nullsafe methods and properties
    // if the object being dereferenced is null then null will be returned rather than an exception thrown.
    // if the dereference is part of a chain, the rest of the chain is skipped.
    $result = $repository?->getUser(5)?->name;
    // is equivalent to the following code block:
    if (is_null($repository)) {
        $result = null;
    } else {
        $user = $repository->getUser(5);
        if (is_null($user)) {
            $result = null;
        } else {
            $result = $user->name;
        }
    }

    // --- array constants can be defined with define()
    // in PHP 5.6, they could only be defined with const
    define('ANIMALS', [
      'dog',
      'cat',
      'bird'
    ]);
    echo ANIMALS[1]; // outputs "cat"

    // --- WeakMap
    // map (or dictionary) that accepts objects as keys.
    // an object in a key of WeakMap does not contribute toward the object's reference count.
    // if at any point the only remaining reference to an object is the key of a WeakMap,
    // the object will be garbage collected and removed from the WeakMap.
    // primary use case is for building caches of data derived from an object that do not need to live longer than the object
    final class WeakMap implements ArrayAccess, Countable, IteratorAggregate {
      /* Methods */
      public __construct()
      public count(): int // counts the number of live entries in the map
      public getIterator(): Iterator // retrieve an external iterator
      public offsetExists(object $object): bool // hecks whether a certain object is in the map
      public offsetGet(object $object): mixed // checks whether a certain object is in the map
      public offsetSet(object $object, mixed $value): void // updates the map with a new key-value pair
      public offsetUnset(object $object): void // removes an entry from the map
    }
    $wm = new WeakMap();
    $o = new StdClass;
    class A {
      public function __destruct() {
        echo "Dead!\n";
      }
    }
    $wm[$o] = new A;
    var_dump(count($wm));   // int(1)
    echo "Unsetting...\n";  // Unsetting...
    unset($o);              // Dead!
    echo "Done\n";          // Done
    var_dump(count($wm));   // int(0)

    // --- none capturing "catch" blocks
    try {
        $response = $this->sendRequest();
    } catch (RequestException $exception) { // before PHP 8.0
        Log::error('API request failed to send.');
    }
    try {
        $response = $this->sendRequest();
    } catch (RequestException) { // PHP 8.0
        Log::error('API request failed to send.');
    }
  
functions

    // --- arrow functions, same features as anonymous functions
    fn(array $x) => $x;
    static fn(): int => $x;
    fn($x = 42) => $x;
    fn(&$x) => $x;
    fn&($x) => $x;
    fn($x, ...$rest) => $rest;
    // variable used in the expression defined in parent scope will be implicitly captured by-value
    $y = 1;
    $fn1 = fn($x) => $x + $y;
    // equivalent to using $y by value:
    $fn2 = function ($x) use ($y) { return $x + $y; };
    var_export($fn1(3)); // >>> 4
    // values from the outer scope cannot be modified by arrow functions
    $x = 1;
    $fn = fn() => $x++; // no effect
    $fn();
    var_export($x);  // >>> 1
    // nested
    $z = 1;
    $fn = fn($x) => fn($y) => $x * $y + $z;
    var_export($fn(5)(10)); // >>> 51

    // --- chain functions
    function add($a) {
      return function($b) use ($a) {
        return $a + $b;
      };
    }
    // PHP 7
    add(10)(15); // >>> int 25
    // PHP 5.6
    $add10 = add(10);
    $add10(15); // >>> int 25

    // --- variadic functions
    function f($req, $opt = null, ...$params) {
      // $params is an array containing the remaining arguments.
      printf('$req: %d; $opt: %d; number of params: %d'."\n",
            $req, $opt, count($params));
    }
    // f(1, 2, 3, 4, 5) >>> $req: 1; $opt: 2; number of params: 3
    class B extends A {
      public function method(...$everything) {}
    }

    // --- array_is_list(array $array) - determines if the given array is a list
    // if its keys consist of consecutive numbers from 0 to count($array)-1
    array_is_list([]); // true
    array_is_list(['apple', 2, 3]); // true
    array_is_list([0 => 'apple', 'orange']); // true
    array_is_list([1 => 'apple', 'orange']); // false - does not start at 0
    array_is_list([1 => 'apple', 0 => 'orange']); // false - keys are not in the correct order
    array_is_list([0 => 'apple', 'foo' => 'bar']); // false - non-integer keys
    array_is_list([0 => 'apple', 2 => 'bar']); // false - non-consecutive keys

    // --- array unpacking
    // using short array syntax, also, works with array() syntax
    $arr1 = [1, 2, 3];
    $arr2 = [...$arr1]; //[1, 2, 3]
    $arr3 = [0, ...$arr1]; //[0, 1, 2, 3]
    $arr4 = [...$arr1, ...$arr2, 111]; // [1, 2, 3, 1, 2, 3, 111]
    $arr5 = [...$arr1, ...$arr1]; // [1, 2, 3, 1, 2, 3]
    function getArr() {
      return ['a', 'b'];
    }
    $arr6 = [...getArr(), 'c' => 'd']; // ['a', 'b', 'c' => 'd']
    // array unpacking with duplicate key
    // string key
    $arr1 = ["a" => 1];
    $arr2 = ["a" => 2];
    $arr3 = ["a" => 0, ...$arr1, ...$arr2];
    var_dump($arr3); // ["a" => 2]
    // integer key
    $arr4 = [1, 2, 3];
    $arr5 = [4, 5, 6];
    $arr6 = [...$arr4, ...$arr5];
    var_dump($arr6); // [1, 2, 3, 4, 5, 6] , [0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 5, 5 => 6]
    // prior to PHP 8.1, unpacking an array which has a string key is not supported:
    $arr1 = [1, 2, 3];
    $arr2 = ['a' => 4];
    $arr3 = [...$arr1, ...$arr2];

    // --- argument unpacking
    function add($a, $b, $c) {
      return $a + $b + $c;
    }
    // $operators = [2, 3];
    // add(1, ...$operators) >>> 6
    // unpacking inside arrays:
    $parts = ['apple', 'pear'];
    $fruits = ['banana', ...$parts, 'watermelon']; // >>> ['banana', 'apple', 'pear', 'watermelon'];
    array_merge(...$arrays)
    // specifying named arguments after an argument unpack
    foo(...$args, named: $arg)

    // --- named arguments
    // passing arguments to a function based on the parameter name, rather than the parameter position
    // self-documenting arguments, order-independent, allows skipping default values arbitrarily
    // parameter name must be an identifier, specifying dynamically is not allowed
    myFunction(paramName: $value);
    array_foobar(array: $value);
    // using positional arguments:
    array_fill(0, 100, 50);
    // using named arguments:
    array_fill(start_index: 0, count: 100, value: 50);
    // same but with different order of parameters
    array_fill(value: 50, count: 100, start_index: 0);
    // combining named arguments with positional arguments:
    htmlspecialchars($string, double_encode: false);
    // same as:
    htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);
    // use named arguments after unpacking:
    function foo($a, $b, $c = 3, $d = 4) {
      return $a + $b + $c + $d;
    }
    var_dump(foo(...[1, 2], d: 40)); // 46
    var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46
    // error thrown when passing the same parameter multiple times:
    function foo($param) { ... }
    foo(param: 1, param: 2); // Error: Named parameter $param overwrites previous argument
    foo(1, param: 2); // Error: Named parameter $param overwrites previous argument
    class ProcessImage {
      public static function handle(string $path, int $height, int $width, string $type, int $quality, int $compression): void {
          // logic for handling image processing
      }
    }
    ProcessImage::handle('/path/to/image.jpg', 500, 300, 'jpg', 100, 5); // Before PHP 8.0
    ProcessImage::handle( // PHP 8.0
      path: '/path/to/image.jpg',
      height: 500,
      width: 300,
      type: 'jpg',
      quality: 100,
      compression: 5,
    );

    // --- first class callable syntax
    // a way of creating anonymous functions from callable
    // supersedes existing callable syntax using strings and arrays
    // it is accessible to static analysis, and uses the scope at the point where the callable is acquired
    class Foo {
       public function method() {}
       public static function staticmethod() {}
       public function __invoke() {}
    }
    $obj = new Foo();
    $classStr = 'Foo';
    $methodStr = 'method';
    $staticmethodStr = 'staticmethod';

    $f1 = strlen(...);
    $f2 = $obj(...);  // invokable object
    $f3 = $obj->method(...);
    $f4 = $obj->$methodStr(...);
    $f5 = Foo::staticmethod(...);
    $f6 = $classStr::$staticmethodStr(...);
    // traditional callable using string, array
    $f7 = 'strlen'(...);
    $f8 = [$obj, 'method'](...);
    $f9 = [Foo::class, 'staticmethod'](...);
    // ... is part of the syntax, and not an omission
    // CallableExpr(...) has the same semantics as Closure::fromCallable()
    // unlike callable using strings and arrays, CallableExpr(...) respects the scope at the point where it is created
    // CallableExpr(...) and traditional callable:
    class Foo {
      public function getPrivateMethod() {
        return [$this, 'privateMethod'];
      }
      private function privateMethod() {
        echo __METHOD__, "\n";
      }
    }
    $foo = new Foo;
    $privateMethod = $foo->getPrivateMethod();
    $privateMethod(); // Fatal error: Call to private method Foo::privateMethod() from global scope
    // because call is performed outside from Foo and visibility will be checked from this point
    class Foo1 {
        public function getPrivateMethod() {
            // Uses the scope where the callable is acquired.
            return $this->privateMethod(...); // identical to Closure::fromCallable([$this, 'privateMethod']);
        }
        private function privateMethod() {
            echo __METHOD__, "\n";
        }
    }
    $foo1 = new Foo1;
    $privateMethod = $foo1->getPrivateMethod();
    $privateMethod();  // Foo1::privateMethod
    // cannot be combined with the nullsafe operator, compile-time error:
    $obj?->method(...);
    $obj?->prop->method(...);
  
built-in functions

    // --- str_contains(string $haystack, string $needle), str_starts_with(), str_ends_with()
    // case-sensitive check

    // --- intdiv() - integer division of its operands
    intdiv(10, 3)) // >>> int(3)
    // --- fdiv() - floating-point division under IEEE 754 semantics
    // division by zero is considered well-defined and will return one of Inf, -Inf or NaN

    // --- hash_equals()
    $expected  = crypt('12345', '$2a$07$usesomesillystringforsalt$');
    $correct   = crypt('12345', '$2a$07$usesomesillystringforsalt$');
    $incorrect = crypt('1234',  '$2a$07$usesomesillystringforsalt$');
    var_dump(hash_equals($expected, $correct));   // >>> true
    var_dump(hash_equals($expected, $incorrect)); // >>> false

    // --- unserialize()
    // better security when unserializing objects on untrusted data
    // prevents possible code injections with a whitelist classes that can be unserialized
    // converts all objects into __PHP_Incomplete_Class object
    $data = unserialize($foo, ["allowed_classes" => false]);
    // converts all objects into __PHP_Incomplete_Class object except those of MyClass and MyClass2
    $data = unserialize($foo, ["allowed_classes" => ["MyClass", "MyClass2"]]);
    // default behaviour (same as omitting the second argument) that accepts all classes
    $data = unserialize($foo, ["allowed_classes" => true]);

    // --- preg_replace_callback_array($pattern, string|array $subject, int $limit = -1, int &$count = null, $flags = 0)
    // perform RegEx search and replace using callbacks
    $subject = 'Aaaaaa Bbb';
    preg_replace_callback_array([
      '~[a]+~i' => function ($match) {
        echo strlen($match[0]), ' matches for "a" found', PHP_EOL;
      },
      '~[b]+~i' => function ($match) {
        echo strlen($match[0]), ' matches for "b" found', PHP_EOL;
      }
    ], $subject);
    // >>> 6 matches for "a" found
    // >>> 3 matches for "b" found

    // --- match - branches evaluation based on an identity check of a value
    // similarly to a switch statement.
    // comparison is an identity check (===) rather than a weak equality check (==)
    // if the subject expression is not handled by ANY match arm an UnhandledMatchError is thrown
    $return_value = match (subject_expression) {
      single_conditional_expression => return_expression,
      conditional_expression1, conditional_expression2 => return_expression,
    };
    // basic match usage
    $food = 'cake';
    $return_value = match ($food) {
      'apple' => 'This food is an apple',
      'bar' => 'This food is a bar',
      'cake' => 'This food is a cake',
    }; // string(19) "This food is a cake"
    $result = match ($x) {
      foo() => ...,
      $this->bar() => ..., // $this->bar() isn't called if foo() === $x
      $this->baz => beep(), // beep() isn't called unless $x === $this->baz
      // etc.
      // this match arm:
      $a, $b, $c => 5,
      // is equivalent to these three match arms:
      $a => 5,
      $b => 5,
      $c => 5,
      // default pattern - matches anything that wasn't previously matched
      default => baz(),
    };
    // handle non identity checks - using true as the subject expression
    $age = 23;
    $result = match (true) {
        $age >= 65 => 'senior',
        $age >= 25 => 'adult',
        $age >= 18 => 'young adult',
        default => 'kid',
    }; // string(11) "young adult"
    $text = 'Bienvenue chez nous';
    $result = match (true) {
        str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en',
        str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr',
        // ...
    }; // string(2) "fr"

    // --- Closure::call()
    // temporarily binding an object scope to a closure and invoking it
    class A {private $x = 1;}
    // Pre PHP 7 code
    $getX = function() { return $this->x; };
    $getXCB = $getX->bindTo(new A, 'A'); // intermediate closure
    echo $getXCB(); // >>> 1
    // PHP 7+ code
    $getX = function() { return $this->x; };
    echo $getX->call(new A); // >>> 1

    // --- fsync(resource $stream) - synchronize changes to the file, including its meta-data
    // similar to fflush(), but it also instructs the operating system to write to the storage media
    // stream - must point to a file successfully opened by fopen() or fsockopen() (and not yet closed by fclose())
    $file = 'test.txt';
    $stream = fopen($file, 'w');
    fwrite($stream, 'test data');
    fwrite($stream, "\r\n");
    fwrite($stream, 'additional data');
    fsync($stream);
    fclose($stream);
    // --- fdatasync(resource $stream) — synchronizes data (but not meta-data) to the file

    // --- memory_reset_peak_usage() - resets the peak memory usage returned by the memory_get_peak_usage() function
    var_dump(memory_get_peak_usage()); // int(422440)
    $a = str_repeat("Hello", 424242);
    var_dump(memory_get_peak_usage()); // int(2508672)
    unset($a);
    memory_reset_peak_usage();
    $a = str_repeat("Hello", 2424);
    var_dump(memory_get_peak_usage()); // int(399208)

    // --- "random" extension,  always included when compiling PHP
    // all of the functions continue to reside in the global namespace:
    // functions and constants are now moved to the random extension
    // rand, getrandmax, srand, lcg_value,  mt_rand, mt_getrandmax, mt_srand
    // MT_RAND_PHP, MT_RAND_MT19937
    // random_int($min, $max) - cryptographic random integers
    var_dump(random_int(100, 999)); // >>> int(248)
    var_dump(random_int(-1000, 0)); // >>> int(-898)
    // random_bytes($length) - cryptographically secure pseudo-random bytes
    $bytes = random_bytes(5);
    bin2hex($bytes) // >>> string(10) "385e33f741"
    // Random\Randomizer class- object-oriented API to access all Random Number generation functionality
    // with a choice of Pseudo Random Number Generator algorithm, which can be swapped out with a new implementation
    $r = new Random\Randomizer();
    echo $r->getInt(1, 100); // 42
    echo $r->shuffleBytes('lorem ipsum'); // "ols mpeurim"
    // random-number between 1 and 100
    $randomizer = new Random\Randomizer();
    $randomizer->getInt(1, 100); // 42
    // shuffle a string
    $randomizer = new Random\Randomizer();
    $randomizer->shuffleBytes('abcdef'); // baecfd - does exactly what it says on the name, for multi-byte characters produces distorted/Mojibake text
    // shuffle an array
    $randomizer = new Random\Randomizer();
    $randomizer->shuffleArray(['apple', 'banana', 'orange']); // ['orange', 'apple', 'banana']
    // Mt19937 Engine
    $randomizer = new Random\Randomizer(new Random\Engine\Mt19937());
    $randomizer->getInt(1, 100); // 68
    // Mt19937 Engine with seed
    $randomizer = new Random\Randomizer(new Random\Engine\Mt19937(42));
    $randomizer->getInt(1, 100); // 43
    // Xoshiro256StarStar Engine with seed
    $randomizer = new Random\Randomizer(new Random\Engine\Xoshiro256StarStar(hash("sha256", "some seed value")));
    $randomizer->getInt(1, 100); // 43
    // PcgOneseq128XslRr64 Engine with seed
    $randomizer = new Random\Randomizer(new Random\Engine\PcgOneseq128XslRr64(hash("md5", "some seed value")));
    $randomizer->getInt(1, 100); // 43
    // using a mock Engine that returns the same value, to be used in unit tests
    class XKCDRandomEngine implements \Random\Engine {
      public function generate(): string {
          return \pack('V', 4); // Chosen by fair dice roll, guaranteed to be random
      }
    }
    $randomizer = new Random\Randomizer(new XKCDRandomEngine());
    $randomizer->getInt(0, 100); // 4
    // replacing random_bytes calls
    $randomValue = random_bytes(32); // Retrieves 32 random bytes.
    $randomizer = new Random\Randomizer();
    $randomizer->getBytes(32);
  
type declarations

    // class/interface"
    // name|self|parent|callable|array|bool|float|int|string|iterable|object|mixed|null|false|true
    // never - function either exit(), throws an exception, or doesn't terminate

    // - scalar type - wrong type is coerced into expected !
    function sum($a, $b): float { return $a + $b; }
    sum(1, 2); // >>> float(3)
    // - strict typing for arguments values, only defined for scalar type declarations
    declare(strict_types=1);

    // - return type, return only types: void|static
    function arraysSum(array ...$arrays):array {
    return array_map(function(array $array): int {
        return array_sum($array);
      }, $arrays);
    }
    class C {}
    class D extends C {}
    class E {} // doesnt extend C
    function f(C $c) { echo $c::class."\n"; }
    f(new C); // C
    f(new D); // D
    f(new E); // >>> Uncaught TypeError
    // - void - empty or no return
    function swap(&$left, &$right): void {
      if ($left === $right) { return; }
      $tmp = $left;
      $left = $right;
      $right = $tmp;
      // [ $left, $right ] = [ $right, $left ];
    }
    $a = 1;
    $b = 2;
    var_dump(swap($a, $b), $a, $b); // >>> null >>> int(2) >>> int(1)
    // - nullable - specified type or null
    function f(?C $c) { var_dump($c); }
    f(new C); // >>> object(C)#1 (0) { }
    f(null);  // >>> NULL
    print_r(arraysSum([1,2,3], [4,5,6], [7,8,9])); // >>> [6,15,24]
    // specified type or null can be passed as an argument or returned as a value, respectively
    function testReturn(): ?string { return 'elePHPant'; }
    var_dump(testReturn()); // >>> string(10) "elePHPant"
    function testReturn(): ?string { return null; }
    var_dump(testReturn()); // >>> NULL
    function test(?string $name) { var_dump($name); }
    test('elePHPant'); // >>> string(10) "elePHPant"
    test(null); // >>> NULL
    test(); // >>> Uncaught Error: Too few arguments to function test()

    // - union - T1|T2|... , nullable union: T1|T2|null
    // allows any of the declared types
    class PostService {
      public function all(): mixed { // before PHP 8.0
        if (! Auth::check()) { return []; }
        return Post::query()->get();
      }
    }
    class PostService {
      public function all(): array|Collection { // PHP 8.0
        if (! Auth::check()) { return []; }
        return Post::query()->get();
      }
    }

    class GFG {
      private int|float $CodingScore;
      public function setScore(int|float $CodingScore): void {
          $this->CodingScore = $CodingScore;
      }
      public function getScore(): int|float {
          return $this->CodingScore;
      }
    }
    $a = new GFG();
    $a->setScore(120.8);
    echo  $a->getScore(),"\r\n"; // 120.8
    $a->setScore(100);
    echo $a->getScore(); // 100

    // - mixed - accepts every value
    // equivalent to the union type object|resource|array|string|float|int|bool|null
    // the top type, every other type is a subtype of it

    // - intersection types
    // declaring a type for a parameter, property, or return types
    // and enforce that values belong to all of the declared class/interface types:
    function count_and_iterate(Iterator&\Countable $value) {
      foreach($value as $val) {}
      count($value);
    }
    // $value parameter must be an object from class that implements both Iterator and Countable interfaces
    // passing any other value causes a type error
    class CountableIterator implements \Iterator, \Countable {
        public function current(): mixed {}
        public function key(): mixed {}
        public function next(): void {}
        public function rewind(): void {}
        public function valid(): bool {}

        public function count(): int {}
    }
    count_and_iterate(new CountableIterator()); // OK
    count_and_iterate(new stdClass()); // Fatal error: ... Argument #1 ($value) must be of type Iterator&Countable, stdClass given
    // ! only support class and interface names as intersection members
    // scalar types, array, void, mixed, callable, never, iterable, null, static, parent, self, and other types are not allowed
    class A {
      public function test(Foo&Bar $val) {}
      public function test2(): Foo {}
    }
    class B extends A {
      public function test(Foo $val): Test&dsa {}
      public function test2(): Foo&Bar {}
    }

    // --- Disjunctive Normal Form (DNF) Types - combine union and intersection types,
    // strict rule: when combining union and intersection types, intersection types must be grouped with brackets
    class Foo {
      public function bar(mixed $entity) {
        if ((($entity instanceof A) && ($entity instanceof B)) || ($entity === null)) { // before
          return $entity;
        }
        throw new Exception('Invalid entity');
      }
      public function bar((A&B)|null $entity) { // PHP 8.2
        return $entity;
      }
    }
    (A&B)|D // accept an object that implements both A and B, OR an object that implements D
    C|(X&D)|null // object that implements C, OR a child of X that also implements D, OR null
    (A&B&D)|int|null // object that implements all three of A, B, and D, OR an int, OR null
    // order of types within each AND/OR section is irrelevant, following type declarations are all equivalent:
    (A&B)|(C&D)|(Y&D)|null
    (B&A)|null|(D&Y)|(C&D)
    null|(C&D)|(B&A)|(Y&D)
    // - return co-variance
    // when extending a class, method return type may narrow only,
    // it must be the same or more restrictive as its parent, additional ANDs may be added, but not additional ORs
    interface ITest {
      public function stuff(): (A&B)|D;
    }
    class TestOne implements ITest {
      public function stuff(): (A&B) {} // Acceptable.  A&B is more restrictive
    }
    class TestTwo implements ITest {
      public function stuff(): D {} // Acceptable. D is is a subset of A&B|D
    }
    class TestThree implements ITest {
      public function stuff(): C|D {} // // Acceptable, since C is a subset of A&B, even though it is not identical
    }
    // ! Not acceptable. This would allow an object that implements A but not B, which is wider than the interface
    class TestFour implements ITest {
      public function stuff(): A|D {}
    }
    interface ITestTwo {
      public function things(): C|D {}
    }
    // ! Not acceptable. Although C extends A and B, it's possible
    // for an object to implement A and B without implementing C.
    // Thus this definition is wider, and not allowed.
    class TestFive implements ITestTwo {
      public function things(): (A&B)|D {}
    }
    // - parameter contra-variance
    // when extending a class, a method parameter type may widen only
    // it must be the same or less restrictive as its parent, additional ORs may be added, but not additional ANDs
    interface ITest {
      public function stuff((A&B)|D $arg): void {}
    }
    class TestOne implements ITest {
      public function stuff((A&B)|D|Z $arg): void {} // Acceptable. Everything that ITest accepts is still valid and then some
    }
    class TestOne implements ITest {
      public function stuff(A|D $arg): void {} // Acceptable. This accepts objects that implement just A, which is a super-set of those that implement A&B
    }
    class TestOne implements ITest {
      public function stuff((A&B) $arg): void {} // !  Not acceptable. The interface says D is acceptable, but this class does not
    }
    interface ITestTwo {
      public function things(C|D $arg): void;
    }
    // Acceptable. Anything that implements C implements A&B,
    // but this rule also allows classes that implement A&B
    // directly, and thus is wider.
    class TestFive implements ITestTwo {
      public function things((A&B)|D $arg): void;
    }
  
classes, namespaces

    // --- properties type declarations
    class User {
      public int $id;
      public string $name;
    }

    // --- use function|const
    namespace Name\Space {
      const FOO = 42;
      function f() { echo __FUNCTION__."\n"; } // >>> 42
    }
    namespace {
      use const Name\Space\FOO;
      use function Name\Space\f;
      echo FOO."\n"; // >>> Name\Space\f
      f();
    }

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

    // --- final constants
    class Foo {
      final public const X = "foo";
    }
    class Bar extends Foo {
      public const X = "bar"; // // Fatal error: Bar::X cannot override final constant Foo::X
    }

    // --- readonly properties - prevents modification of the property after initialization
    // readonly static properties are not supported
    // readonly property can only be initialized once, and only from the scope where it has been declared
    // specifying an explicit default value on readonly properties is not allowed
    // cannot be unset() once they are initialized,
    // it is possible to unset a readonly property prior to initialization, from the scope where the property has been declared
    class Test {
      public readonly string $prop;
      public function __construct(string $prop) {
        $this->prop = $prop; // legal initialization
      }
    }
    $test = new Test("foobar");
    var_dump($test->prop); // string(6) "foobar" - legal read
    // illegal reassignment, does not matter that the assigned value is the same:
    $test->prop = "foobar"; // Error: Cannot modify readonly property Test::$prop
    // illegal initialization of readonly properties
    class Test1 {
      public readonly string $prop;
    }
    $test1 = new Test1; // Illegal initialization outside of private scope.
    $test1->prop = "foobar"; // Error: Cannot initialize readonly property Test1::$prop from global scope
    class Test {
        public readonly int $prop = 42; // Fatal error: Readonly property Test::$prop cannot have default value
    }
    // objects (or resources) stored in readonly properties may still be modified internally:
    class Test {
      public function __construct(public readonly object $obj) {}
    }
    $test = new Test(new stdClass);
    $test->obj->foo = 1; // legal interior mutation
    $test->obj = new stdClass; // illegal reassignment

    // --- group 'use' declarations
    // Pre PHP 7 code
    use some\namespace\ClassA;
    use some\namespace\ClassC as C;
    use function some\namespace\fn_a;
    use function some\namespace\fn_b;
    use const some\namespace\ConstA;
    use const some\namespace\ConstB;
    // PHP 7+ code
    use some\namespace\{ClassA, ClassB, ClassC as C};
    use function some\namespace\{fn_a, fn_b, fn_c};
    use const some\namespace\{ConstA, ConstB, ConstC};

    // --- anonymous classes has been added via new class
    // can be used in place of full class definitions for throwaway objects
    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()); // >>> object(class@anonymous)#2 (0) { }

    // --- constructor promotion - set properties on objects directly in the constructor as an argument.
    // constructor parameters is assigned to a property in the constructor but otherwise not operated upon.
    // when a constructor argument includes a visibility modifier,
    // PHP will interpret it as both an object property and a constructor argument, and assign the argument value to the property.
    // constructor body may then be empty or may contain other statements.
    // any additional statements will be executed after the argument values have been assigned to the corresponding properties.
    // is possible to mix and match promoted and not-promoted arguments, in any order, no impact on code calling the constructor
    class Client {
      private string $url; // before PHP 8.0
      public function __construct(string $url) {
        $this->url = $url;
      }
    }
    class Client {
      public function __construct(
        private string $url, // PHP 8.0
      ) {}
    }
    class Point {
      public function __construct(protected int $x, protected int $y = 0) {
      }
    }

    // --- new() in initializers
    // objects can be used as default parameter values, static variables, and global constants, in attribute arguments
    // allowed:
    static $x = new Foo;
    const C = new Foo;
    function test($param = new Foo) {}
    #[AnAttribute(new Foo)]
    class Test {
      public function __construct(
        public $prop = new Foo,
      ) {}
    }
    class BuyerWorkflow {
      public function __construct(
          private WorkflowStepContract $step = new InitialBuyerStep(),
      ) {}
    }
    // not allowed (compile-time error):
    function test(
      $a = new (CLASS_NAME_CONSTANT)(), // dynamic class name
      $b = new class {}, // anonymous class
      $c = new A(...[]), // argument unpacking
      $d = new B($abc), // unsupported constant expression
    ) {}

    // --- creating an instance using an arbitrary expression
    class ClassA extends \stdClass {}
    class ClassB extends \stdClass {}
    class ClassC extends ClassB {}
    class ClassD extends ClassA {}
    function getSomeClass(): string {
        return 'ClassA';
    }
    var_dump(new (getSomeClass()));
    var_dump(new ('Class' . 'B'));
    var_dump(new ('Class' . 'C'));
    var_dump(new (ClassD::class));

    // --- private methods declared on a parent class
    // no longer enforce any inheritance rules on the methods of a child class
    // (with the exception of final private constructors)
    class ParentClass {
        private function method1() {}
        private function method2() {}
        private static function method3() {}
        // Throws a warning, as "final" no longer has an effect:
        private final function method4() {}
    }
    class ChildClass extends ParentClass {
        // All of the following are now allowed, even though the modifiers aren't
        // the same as for the private methods in the parent class.
        public abstract function method1() {}
        public static function method2() {}
        public function method3() {}
        public function method4() {}
    }

    // --- readonly classes (PHP 8.2.0)
    // add the readonly modifier to every declared property, and prevent the creation of dynamic properties
    // it is impossible to add support for them by using the AllowDynamicProperties attribute.
    #[AllowDynamicProperties]
    readonly class Foo { // Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
    }
    ?>
    // neither untyped, nor static properties can be marked with the readonly modifier, readonly classes cannot declare them either:
    readonly class Foo {
        public $bar; // Fatal error: Readonly property Foo::$bar must have type
    }
    readonly class Foo {
        public static int $bar; // Fatal error: Readonly class Foo cannot declare static properties
    }
    // readonly class can be extended if, and only if, the child class is also a readonly class

    // --- constants in traits
    trait FooBar {
      const FOO = 'foo';
      private const BAR = 'bar';
      final const BAZ = 'baz';
      final protected const QUX = 'qux';
    }
    class Test {
      use FooBar;
    }
    echo Test::BAZ; // 'bar'

    // --- dynamic property deprecation
    class User {
      public $name;
    }
    $user = new User();
    $user->last_name = 'Doe'; // "Deprecated" notice !
    $user = new stdClass();
    $user->last_name = 'Doe'; // Still allowed

    #[AllowDynamicProperties] // allow dynamic properties using the AllowDynamicProperties attribute
    class User() {}
    $user = new User();
    $user->foo = 'bar';
  
generators

    // https://www.php.net/manual/en/language.generators.syntax.php
    // implement simple iterators
    // write code that uses foreach to iterate over a set of data without in memory array
    // instead of returning a value, yields as many values as it needs to
    // - current - get yielded value
    // - getReturn - get return value of a generator
      $gen = (function() { yield 1; yield 2; return 3; })();
      foreach ($gen as $val) { echo $val, PHP_EOL; }
      echo $gen->getReturn(), PHP_EOL;
      // >>> 1 >>> 2 >>> 3
    // - key - get yielded key
      function Gen(){ yield 'key' => 'value'; }
      $gen = Gen();
      echo "{$gen->key()} => {$gen->current()}";
      // >>> key => value
    // - next - resume execution of the generator
    // - rewind - rewind the iterator
    // - send - send a value to the generator and resumes execution
      function printer() {
        echo "I'm printer!".PHP_EOL;
        while (true) { $string = yield; echo $string.PHP_EOL; }
      }
      $printer = printer();
      $printer->send('Hello world!');
      $printer->send('Bye world!');
      // >>> I'm printer! >>> Hello world! >>> Bye world!
    // - throw - throw an exception into the generator and resumes execution
      function gen() {
        echo "Foo\n";
        try { yield; }
        catch (Exception $e) { echo "Exception: {$e->getMessage()}\n"; }
        echo "Bar\n";
      }
      $gen = gen();
      $gen->rewind();
      $gen->throw(new Exception('Test'));
      // >>> Foo >>> Exception: Test >>> Bar
    // - valid - check if the iterator has been closed
    // - __wakeup - serialize callback
    function xrange($start, $limit, $step = 1) {
      if ($start <= $limit) {
        if ($step <= 0) { throw new LogicException('Step must be positive'); }
        for ($i = $start; $i <= $limit; $i += $step) { yield $i; }
      } else {
        if ($step >= 0) { throw new LogicException('Step must be negative'); }
        for ($i = $start; $i >= $limit; $i += $step) { yield $i; }
      }
    }
    foreach (range(1, 9, 2) as $number) { echo "$number "; }
    foreach (xrange(1, 9, 2) as $number) { echo "$number "; }
    // range() and xrange() result is the same >>> 1 3 5 7 9
    function fib($n) {
      $cur = 1;
      $prev = 0;
      for ($i = 0; $i < $n; $i++) {
        yield $cur;
        $temp = $cur;
        $cur = $prev + $cur;
        $prev = $temp;
      }
    }
    $fibs = fib(9); // >>> 1 1 2 3 5 8 13 21 34

    // generator delegation
    function gen() { yield 1; yield 2; yield from gen2(); }
    function gen2() { yield 3; yield 4; }
    foreach (gen() as $val){ echo $val, PHP_EOL; } // >>> 1 >>> 2 >>> 3 >>> 4
  
attributes

    // 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.
    // metadata defined by attributes can then be inspected at runtime using the Reflection APIs
    // attributes could therefore be thought of as a configuration language embedded directly into code.
    // generic implementation of a feature and its concrete use in an application can be decoupled..
    // comparable to interfaces and their implementations.
    // interfaces and implementations are about code,
    // attributes are about annotating extra information and configuration.
    // interfaces can be implemented by classes,
    // yet attributes can also be declared on methods, functions, parameters, properties and class constants.
    // more flexible than interfaces.
    // simple example of attribute usage is to convert an interface that has optional methods to use attributes.
    // ActionHandler interface representing an operation in an application,
    // where some implementations of an action handler require setup and others do not.
    // instead of requiring all classes that implement ActionHandler to implement a method setUp(), an attribute can be used.
    // benefit of this approach is that we can use the attribute several times:
    interface ActionHandler {
        public function execute();
    }

    #[Attribute]
    class SetUp {}

    class CopyFile implements ActionHandler {
        public string $fileName;
        public string $targetDirectory;

        #[SetUp]
        public function fileExists() {
            if (!file_exists($this->fileName)) {
                throw new RuntimeException("File does not exist");
            }
        }

        #[SetUp]
        public function targetDirectoryExists() {
            if (!file_exists($this->targetDirectory)) {
                mkdir($this->targetDirectory);
            } elseif (!is_dir($this->targetDirectory)) {
                throw new RuntimeException("Target directory $this->targetDirectory is not a directory");
            }
        }

        public function execute() {
            copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName));
        }
    }

    function executeAction(ActionHandler $actionHandler)    {
        $reflection = new ReflectionObject($actionHandler);
        foreach ($reflection->getMethods() as $method) {
            $attributes = $method->getAttributes(SetUp::class);
            if (count($attributes) > 0) {
                $methodName = $method->getName();
                $actionHandler->$methodName();
            }
        }
        $actionHandler->execute();
    }

    $copyAction = new CopyFile();
    $copyAction->fileName = "/tmp/foo.jpg";
    $copyAction->targetDirectory = "/home/user";
    executeAction($copyAction);

    // --- syntax
    // a.php
    namespace MyExample;
    use Attribute;
    #[Attribute]
    class MyAttribute {
      const VALUE = 'value';
      private $value;
      public function __construct($value = null) {
          $this->value = $value;
      }
    }
    // b.php
    namespace Another;
    use MyExample\MyAttribute;
    #[MyAttribute]
    #[\MyExample\MyAttribute]
    #[MyAttribute(1234)]
    #[MyAttribute(value: 1234)]
    #[MyAttribute(MyAttribute::VALUE)]
    #[MyAttribute(array("key" => "value"))]
    #[MyAttribute(100 + 200)]
    class Thing {
    }
    #[MyAttribute(1234), MyAttribute(5678)]
    class AnotherThing {
    }

    // --- reading Attributes with the Reflection API with getAttributes() method
    // returns an array of ReflectionAttribute instances that can be queried for
    // attribute name, arguments and to instantiate an instance of the represented attribute.
    // increases control to handle errors regarding missing attribute classes, mistyped or missing arguments
    // oly after calling ReflectionAttribute::newInstance(),
    // objects of the attribute class are instantiated and the correct matching of arguments is validated, not earlier.
    #[Attribute]
    class MyAttribute {
      public $value;
      public function __construct($value) {
        $this->value = $value;
      }
    }
    #[MyAttribute(value: 1234)]
    class Thing {
    }
    function dumpAttributeData($reflection) {
      $attributes = $reflection->getAttributes();
      foreach ($attributes as $attribute) {
        var_dump($attribute->getName());
        var_dump($attribute->getArguments());
        var_dump($attribute->newInstance());
      }
    }
    dumpAttributeData(new ReflectionClass(Thing::class));
    /*
      string(11) "MyAttribute"
      array(1) {
        ["value"]=>
        int(1234)
      }
      object(MyAttribute)#3 (1) {
        ["value"]=>
        int(1234)
      }
    */
    // reading specific attributes
    function dumpMyAttributeData($reflection) {
        $attributes = $reflection->getAttributes(MyAttribute::class);
        foreach ($attributes as $attribute) {
           var_dump($attribute->getName());
           var_dump($attribute->getArguments());
           var_dump($attribute->newInstance());
        }
    }
    dumpMyAttributeData(new ReflectionClass(Thing::class));
  
fibers

    // full-stack, interruptible functions
    // may be suspended from anywhere in the call-stack, pausing execution within the fiber until the fiber is resumed at a later time.
    // pause the entire execution stack, so the direct caller of the function does not need to change how it invokes the function.
    // Fiber::suspend() - may be in a deeply nested function or not even exist at all
    //  resumed with any value using Fiber::resume()
    // or by throwing an exception into the fiber using Fiber::throw()
    // currently it is not possible to switch fibers in the destructor of an object
    $fiber = new Fiber(function (): void {
       $value = Fiber::suspend('fiber');
       echo "Value used to resume fiber: ", $value, PHP_EOL;
    });
    $value = $fiber->start();
    echo "Value from fiber suspending: ", $value, PHP_EOL; // Value from fiber suspending: fiber
    $fiber->resume('test'); // Value used to resume fiber: test
  
enumerations

    // or "Enums" allow to define a custom type that is limited to one of a discrete number of possible values
    // itself is a class, and its possible cases are all single-instance objects of that class
    // Enum cases are valid objects and may be used anywhere an object may be used, including type checks.
    enum Suit
    {
      case Hearts;
      case Diamonds;
      case Clubs;
      case Spades;
    }
    function pick_a_card(Suit $suit) { ... }
    $val = Suit::Diamonds; // OK
    pick_a_card($val); // OK
    pick_a_card(Suit::Clubs); // TypeError: pick_a_card(): Argument #1 ($suit) must be of type Suit, string given
    pick_a_card('Spades');
    $a = Suit::Spades;
    $b = Suit::Spades;
    $a === $b; // true
    $a instanceof Suit;  // true
    // read-only property "name", case-sensitive name of the case itself
    Suit::Spades->name; // prints "Spades"
    // backed enumerations - scalar equivalent for an Enumeration, the syntax is as follows:
    enum Suit: string {
      case Hearts = 'H';
      case Diamonds = 'D';
      case Clubs = 'C';
      case Spades = 'S';
    }
    // backed Cases have an additional read-only property "value"
    print Suit::Clubs->value; // prints "C"
    // to add some constants in Enum cases, using class constants:
    Here's an example :
    class Something {
      const PARENT = 'parent';
    }
    enum MyEnum:string {
      case firstChild = Something::PARENT . '_child1';
      case secondChild = Something::PARENT . '_child2';
    }
    echo MyEnum::firstChild->value; //print"parent_child1"

    // --- may contain methods, and may implement interfaces
    // if an Enum implements an interface, then any type check for that interface will also accept all cases of that Enum
    interface Colorful {
        public function color(): string;
    }
    enum Suit implements Colorful {
      case Hearts;
      case Diamonds;
      case Clubs;
      case Spades;
      public function color(): string { // ulfills the interface contract
        return match($this) {
          Suit::Hearts, Suit::Diamonds => 'Red',
          Suit::Clubs, Suit::Spades => 'Black',
        };
      }
      public function shape(): string { // not part of an interface; that's fine
        return "Rectangle";
      }
    }
    function paint(Colorful $c) { ... }
    paint(Suit::Clubs);  // Works
    print Suit::Diamonds->shape(); // prints "Rectangle"
    interface Colorful {
      public function color(): string;
    }
    enum Suit: string implements Colorful {
      case Hearts = 'H';
      case Diamonds = 'D';
      case Clubs = 'C';
      case Spades = 'S';
      public function color(): string { // fulfills the interface contract
        return match($this) {
          Suit::Hearts, Suit::Diamonds => 'Red',
          Suit::Clubs, Suit::Spades => 'Black',
        };
      }
    }
    // inside a method, the $this variable is defined and refers to the Case instance
    // above hierarchy is logically similar to the following class structure (although this is not the actual code that runs):
    interface Colorful {
      public function color(): string;
    }
    final class Suit implements UnitEnum, Colorful {
      public const Hearts = new self('Hearts');
      public const Diamonds = new self('Diamonds');
      public const Clubs = new self('Clubs');
      public const Spades = new self('Spades');
      private function __construct(public readonly string $name) {}
      public function color(): string {
        return match($this) {
          Suit::Hearts, Suit::Diamonds => 'Red',
          Suit::Clubs, Suit::Spades => 'Black',
        };
      }
      public function shape(): string {
        return "Rectangle";
      }
      public static function cases(): array {
        // Illegal method, because manually defining a cases() method on an Enum is disallowed.
        // See also "Value listing" section.
      }
    }
    // methods may be public, private, or protected
    // in practice private and protected are equivalent as inheritance is not allowed

    // --- static methods - primarily for alternative constructors:
    enum Size {
      case Small;
      case Medium;
      case Large;
      public static function fromLength(int $cm): static {
        return match(true) {
          $cm < 50 => static::Small,
          $cm < 100 => static::Medium,
          default => static::Large,
        };
      }
    }

    // --- constants - may be public, private, or protected,
    // in practice private and protected are equivalent as inheritance is not allowed
    enum Size {
        case Small;
        case Medium;
        case Large;

        public const Huge = self::Large;
    }

    // before PHP 8.1
    class Method {
      public const GET = 'GET';
      public const POST = 'POST';
      public const PUT = 'PUT';
      public const PATCH = 'PATCH';
      public const DELETE = 'DELETE';
    }
    // PHP 8.1
    enum Method: string {
      case GET = 'GET';
      case POST = 'POST';
      case PUT = 'PUT';
      case PATCH = 'PATCH';
      case DELETE = 'DELETE';
    }
    trait SendsRequests {
      // before PHP 8.1
      public function send(string $method, string $uri, array $options = []): Response {
        if (! in_array($method, ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'])) {
          throw new InvalidArgumentException(
            message: "Method [$method] is not supported.",
          );
        }
        return $this->buildRequest()->send(
          method: $method,
          uri: $uri,
          options: $options,
        );
      }
      // PHP 8.1
      public function send(Method $method, string $uri, array $options = []): Response {
        return $this->buildRequest()->send(
          method: $method->value,
          uri: $uri,
          options: $options,
        );
      }
    }

    // ... Traits, using in constants, ...
  
old and new evaluation of indirect expressions
left-to-right vs. right-to-left
Expression PHP 5 interpretation PHP 7 interpretation
$$foo['bar']['baz'] ${$foo['bar']['baz']} ($$foo)['bar']['baz']
$foo->$bar['baz'] $foo->{$bar['baz']} ($foo->$bar)['baz']
$foo->$bar['baz']() $foo->{$bar['baz']}() ($foo->$bar)['baz']()
Foo::$bar['baz']() Foo::{$bar['baz']}() (Foo::$bar)['baz']()

Back to Main Page