implements Huggable
$order = [ 'id' => 345, 'total' => 100.45, 'skus' => [ 123, '5B3', '987'], 'canceled' => false, 'usr' => 8, ];
class Order { public $id; public $total; public $cancelled = false; public $user; public $skus = []; }
class Order { public string $id; public Money $total; public bool $cancelled = false; public User $user; public array $skus = []; }
private ?User $user;
class Test { // Legal default values public bool $a = true; public int $b = 42; public float $c = 42.42; public string $e = "str"; public array $f = [1, 2, 3]; public iterable $g = [1, 2, 3]; public ?int $h = null; public ?Test $j = null; // These have *no* legal default values // so "uninitialized" public object $k; public Test $l; }
class Product { public string $size; } $p = new Product(); print ($p->size); // Throws TypeError: // Typed property Product::$size must not be accessed // before initialization
So when should I use it?
Pretty much always
$username = $username ? $username : 'Anonymous';
$username = $username ?: 'Anonymous';
But what if $username doesn't exist?
Notice: Undefined variable: username on line 3
Fugly...
$username = isset($username) && !is_null($username) ? $username : 'Anonymous';
NULL coalesce! (PHP 7.0)
$username = $username ?? $user->username() ?? 'Anonymous';
NULL coalesce assign! (PHP 7.4)
$username ??= $user->username() ?? 'Anonymous';
?? and ??= check setness, not truthiness
So when should I use it?
$users = [new User(1), new User(2), new User(3), /* ... */]; $field = 'first'; usort($users, function (User $u1, User $u2) use ($field) { return $u1->get($field) <=> $u2->get($field); }); $names = array_map(function(User $user) use ($field) { return $user->get($field); }, $users);
$users = [new User(1), new User(2), new User(3), /* ... */]; $field = 'first'; usort($users, fn($u1, $u2) => $u1->get($field) <=> $u2->get($field)); $names = array_map(fn(User $user) => $user->get($field), $users);
$func = fn() => *expression*; $func = fn($a) => *expression*; $func = fn(string $a): string => *expression*;
For Functional Programming
function volume(float $x, float $y, float $z): float { return $x * $y * $z; }
$x = 5; $volumeWithX = fn(float $y, float $z): float => volume($x, $y, $z);
$volumeWithXY = fn(float $z): float => $volumeWithX(4, $z);
$volumeForLength = fn(float $y): float => volume(5, $y, 8);
class YVolumizer { private float $x; private float $z; public function __construct(float $x, float $z) { $this->x = $x; $this->z = $z; } public function __invoke(float $y): float { return volume($this->x, $y, $this->z); } } $volumizerForLength = new YVolumizer(5, 8); print $volumizerForLength(4);
$volumeForLength = fn(float $y): float => volume(5, $y, 8); print $volumeForLength(4);
function compose(callable ...$fns): callable { return fn($x) => array_reduce($fns, fn($collect, $func) => $func($collect), $x); } $holiday = "Lincoln's Birthday"; $findPromotionsFor = compose( fn($x) => getShoppingList($x, 'wishlist'), fn($x) => mostExpensiveItem($x, ['exclude' => 'onSale']), fn($x) => getPromotions($x, $holiday), ); $findPromotionsFor($someUser);
So when should I use it?
"Subclasses work in inheritance now"
Cannot vary method signature at all
class User {} class Admin extends User {} class Product {} class TShirt extends Product {} class FavoriteFinder { public function stuff(Admin $user): Product {} } class UserFavorite extends FavoriteFinder { public function stuff(Admin $user): Product {} }
Can add return types and remove param types
class User {} class Admin extends User {} class Product {} class TShirt extends Product {} class FavoriteFinder { public function stuff(Admin $user) {} } class UserFavorite extends FavoriteFinder { public function stuff($user): TShirt {} }
Super-types for params, subtypes for returns
class User {} class Admin extends User {} class Product {} class TShirt extends Product {} class FavoriteFinder { public function stuff(Admin $user): Product {} } class UserFavorite extends FavoriteFinder { public function stuff(User $user): TShirt {} }
return self
now works!interface AList { public function add($item): self; } class SpecialList implements AList { public function add($item): self {} }
PHP < 7.4
Fatal error: Declaration of SpecialList::add($item): SpecialList must be compatible with AList::add($item): AList
(But wait, return static
is coming in PHP 8...)
So when should I use it?
Any time you want (especially value objects)!
__sleep
/__wakeup
(The before times)class User { protected int $id; protected string $name; protected DateTime $lastLogin; // ... public function __sleep() { return ['id', 'name']; } public function __wakeup() { $this->lastLogin = UserSystem::getLastLogin($this->id); } }
$s = serialize(new User()); print_r($s); // O:4:"User":2:{s:5:"*id";i:42;s:7:"*name";s:5:"Larry";}
__sleep
/__wakeup
Serializable
(PHP 5.1)class User implements Serializable { protected int $id; protected string $name; protected DateTime $lastLogin; public function serialize(): string { return serialize(['id' => $this->id, 'name' => $this->name]); } public function unserialize($serialized): void { $data = unserialize($serialized); $this->id = $data['id']; $this->name = $data['name']; $this->lastLogin = UserSystem::getLastLogin($this->id); } }
$s = serialize(new User()); print_r($s); // C:4:"User":43:{a:2:{s:2:"id";i:42;s:4:"name";s:5:"Larry";}}
serialize()
-ish__serialize
/__unserialize
(PHP 7.4)class User { protected int $id; protected string $name; protected DateTime $lastLogin; // ... public function __serialize(): array { return ['id' => $this->id, 'name' => $this->name]; } public function __unserialize(array $data): void { $this->id = $data['id']; $this->name = $data['name']; $this->lastLogin = UserSystem::getLastLogin($this->id); } }
$s = serialize(new User()); print_r($s); // O:4:"User":2:{s:2:"id";i:42;s:4:"name";s:5:"Larry";}
__serialize
/__unserialize
__sleep()
__serialize()
> Serializable
> __sleep()
> default
O: __unserialize()
> __wakeup()
> default
C: Serializable
> default
So when should I use it?
$a1 = [1, 2, 3]; $a2 = [4, 5, 6];
$b = $a1 + $a2; // Maybe? var_dump($b);
Wrong! Only for all-associative arrays.
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
$a1 = [1, 2, 3]; $a2 = [4, 5, 6]; $b = array_merge($a1, $a2); // Eew.
$a1 = [1, 2, 3]; $a2 = [4, 5, 6]; $b = [...$a1, ...$a2];
function afunc(string $b, User ...$users): callable { $params = [$a, $b, ...$users]; anotherFunc(...$params); }
... That's amazing!
So when should I use it?
Anytime you would have used array_merge()
Embedding one sequential array inside another
Think fast, what number is this?
$num = 10000000000000;
How about this?
$num = 10_000_000_000_000;
Compile identically
6.674_083e-11; // float 299_792_458; // integer 0xCAFE_F00D; // hexadecimal 0b0101_1111; // binary 0137_041; // octal
So when should I use it?
Q: What is the slowest, avoidable part of a large app?
A: Autoloading
php.ini
opcache.preload = preload-script.php
preload-script.php
$directory = new RecursiveDirectoryIterator(__DIR__ . '/vendor'); $iterator = new RecursiveIteratorIterator($directory); $regex = new RegexIterator($iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH); foreach ($regex as $key => $file) { // This is the important part! opcache_compile_file($file[0]); }
So when should I use it?
points.h
struct point { int x; int y; }; double distance(struct point first, struct point second);
points.c
#include "points.h" #include <math.h> double distance(struct point first, struct point second) { double a_squared = pow((second.x - first.x), 2); double b_squared = pow((second.y - first.y), 2); return sqrt(a_squared + b_squared); }
classes.php
class Point { public int $x; public int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } }
inline.php
require_once 'classes.php'; $ffi = FFI::cdef(file_get_contents('points.h'), 'points.so'); $p1 = new Point(3, 4); $p2 = new Point(7, 9); $cp1 = $ffi->new('struct point'); $cp2 = $ffi->new('struct point'); $cp1->x = $p1->x; $cp1->y = $p1->y; $cp2->x = $p2->x; $cp2->y = $p2->y; $d = $ffi->distance($cp1, $cp2); print "Distance is $d\n";
$ php inline.php Distance is 6.4031242374328
But this is super slow
Running arbitrary C code is stupidly insecure!
ffi.enable=true
(don't do this in prod!)preload
points.h
#define FFI_SCOPE "POINTS" #define FFI_LIB "./points.so" struct point { int x; int y; }; double distance(struct point first, struct point second);
preloader.php
FFI::load(__DIR__ . "/points.h"); opcache_compile_file(__DIR__ . "/classes.php");
preload.php
$ffi = \FFI::scope("POINTS"); $p1 = new Point(3, 4); $p2 = new Point(7, 9); $cp1 = $ffi->new('struct point'); $cp2 = $ffi->new('struct point'); $cp1->x = $p1->x; $cp1->y = $p1->y; $cp2->x = $p2->x; $cp2->y = $p2->y; $d = $ffi->distance($cp1, $cp2); print "Distance is $d\n";
$ php -d opcache.preload="preloader.php" \ -d opcache.enable_cli=true \ preload.php Distance is 6.4031242374328
classes.php
class Point { public int $x; public int $y; public function __construct(int $x, int $y) { $this->x = $x; $this->y = $y; } public function toStruct($ffi) { $cp = $ffi->new('struct point'); $cp->x = $this->x; $cp->y = $this->y; return $cp; } }
classes.php
class PointApi { private static $ffi = null; public function __construct() { static::$ffi ??= \FFI::scope("POINTS"); } public function distance(Point $p1, Point $p2): float { $cp1 = $p1->toStruct(static::$ffi); $cp2 = $p2->toStruct(static::$ffi); return static::$ffi->distance($cp1, $cp2); } }
preload.php
$p1 = new Point(3, 4); $p2 = new Point(7, 9); $api = new PointApi(); $d = $api->distance($p1, $p2); print "Distance is $d\n";
It works with Rust-based .so files, too!
(Though you'll need a manual header file)
So when should I use it?
Beware these deprecations/changes
__toString
can now throw Exceptions
{}
for strings and arrays is deprecated
Again...
$array = [1, 2]; echo $array[1]; // prints 2 echo $array{1}; // also prints 2 - DEPRECATED $string = "foo"; echo $string[0]; // prints "f" echo $string{0}; // also prints "f" - DEPRECATED
Left-associative ternaries deprecated
return $a == 1 ? 'one' : $a == 2 ? 'two' : $a == 3 ? 'three' : $a == 4 ? 'four' : 'other';
Does that check $a == 1
first, or last?
return ((($a == 1 ? 'one' : $a == 2) ? 'two' : $a == 3) ? 'three' : $a == 4) ? 'four' : 'other';
$a == 1
prints four
Parentheses are required when nested, deprecation warning otherwise
Parentheses are required when nested, compile error otherwise
What are you doing nesting ternaries like that???
$a ?: $b ?: $c
is unchanged
// How is this interpreted? echo "sum: " . $a + $b; // PHP 7.3 echo ("sum: " . $a) + $b; // PHP 7.4 (what you probably always meant) echo "sum :" . ($a + $b);
array_key_exists()
with objects is deprecated
implode($pieces, $glue)
is deprecated
money_format()
is deprecated; use NumberFormatter::formatCurrency()
allow_url_include
is deprecated
$closure->bindTo(null)
is deprecated
array access on non-container throws E_WARNING
Thank you, Nikita Popov and JetBrains!
So when should I use it?
Right now!
PHP Versions lists 17 compatible hosts
(Yep, including Platform.sh)
Director of Developer Experience Platform.sh
Idea to Cloud Hosting
Stalk us at @PlatformSH