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 18 compatible hosts
(Yep, including Platform.sh)
git checkout -b new-hotness
nano .platform.app.yaml
- type: php:7.3
+ type: php:7.4
git commit -am "New hotness" && git push
platform e:activate -y
platform e:merge -y
Director of Developer Experience Platform.sh
The end-to-end web platform for agile teams
Stalk us at @PlatformSH