PHP 5.6 - 8.2
- Migration Guide
session_start()
, session_commit()
, session_reset()
- re-initialize session array with original values, session_abort()
- finishes session without saving data
- foreach by-value operates on a copy of the array, changes to the array made during iteration will not affect the values that are iterated
session_abort()
supports async connections and queries
- types comparisons
// --- 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