What’s New in
By Joshua Ray Copeland
TALK Outline
01
02
03
04
05
06
Release Schedule
Features
Breaking Changes
PHP 8.1 FEATURES
RESOURCES
SHOUTOUTS
Things to lookout for in PHP 8.1
Current & Upcoming 8.x Releases
Tons of updates and added features
Not too bad if you’ve kept up with releases
Resources for PHP 8 and this talk
The People Behind PHP 8
Joshua Ray Copeland
Remote Dev Force
Hire elite software developers for your projects
Looking for Part-time Freelance Laravel/Symfony Developers�
Josh@RemoteDevForce.com
Release Schedule
01
“My hour for tea is half-past five, and my buttered toast waits for nobody.”
PHP 8 Lifecycle
2020
2022
2023
Release
End-of-life
Security patches
https://www.php.net/supported-versions.php
UPGRADE OFTEN
Major.Minor.Patch�7.4.9
Check your logs for deprecations
error_reporting=-1
PHP Beta Downloads
https://www.php.net/downloads
PHP GitHub
FEATURES
02
“That's the thing about people who think they hate computers. What they really hate is lousy programmers.”
PHP 8 Big Upgrades
https://www.php.net/releases/8.0/en.php
PHP 8 Other Updates
PHP 8 Other Updates
The JIT Compiler
The JIT Compiler
Downsides �
Attributes
Attributes, commonly known as annotations in other languages, offers a way to add metadata to classes, without having to parse docblocks.
use App\Attributes\ExampleAttribute;
@@ExampleAttribute
class Foo
{
@@ExampleAttribute
public const FOO = 'foo';
@@ExampleAttribute
public $x;
@@ExampleAttribute
public function foo(@@ExampleAttribute $bar) { }
}
@@Attribute
class ExampleAttribute
{
public $value;
public function __construct($value)
{
$this->value = $value;
}
}
Attribute Drama
@@
RE-VOTE on ATTRIBUTE SYNTAX
FINAL Attribute SYNTAX
#[
ORM\Entity,
ORM\Table("user")
]
class User
{
#[ORM\Id, ORM\Column("integer"), ORM\GeneratedValue]
private $id;
#[ORM\Column("string", ORM\Column::UNIQUE)]
#[Assert\Email(["message" => "The email '{{ value }}' is not a valid email."])]
private $email;
}
DECLARING ATTRIBUTES
CONFIGURATIONS
Flags
#[Attribute(Attribute::TARGET_CLASS)]
class ClassAttribute
{
}
Attribute::TARGET_CLASS
Attribute::TARGET_FUNCTION
Attribute::TARGET_METHOD
Attribute::TARGET_PROPERTY
Attribute::TARGET_CLASS_CONSTANT
Attribute::TARGET_PARAMETER
Attribute::TARGET_ALL
Attribute::IS_REPEATABLE
#[Attribute(� Attribute::TARGET_METHOD |
Attribute::TARGET_FUNCTION
)]
class ClassAttribute { }
Creating Attribute Classes
ATTRIBUTES PARAMETER TYPES ALLOWED
zend_bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */
{
return kind == ZEND_AST_ZVAL || kind == ZEND_AST_BINARY_OP
|| kind == ZEND_AST_GREATER || kind == ZEND_AST_GREATER_EQUAL
|| kind == ZEND_AST_AND || kind == ZEND_AST_OR
|| kind == ZEND_AST_UNARY_OP
|| kind == ZEND_AST_UNARY_PLUS || kind == ZEND_AST_UNARY_MINUS
|| kind == ZEND_AST_CONDITIONAL || kind == ZEND_AST_DIM
|| kind == ZEND_AST_ARRAY || kind == ZEND_AST_ARRAY_ELEM
|| kind == ZEND_AST_UNPACK
|| kind == ZEND_AST_CONST || kind == ZEND_AST_CLASS_CONST
|| kind == ZEND_AST_CLASS_NAME
|| kind == ZEND_AST_MAGIC_CONST || kind == ZEND_AST_COALESCE;
}
/* }}} */
Built-in Attributes
Attributes Event Listeners Example
class CartsProjector implements Projector
{
use ProjectsEvents;
protected array $handlesEvents = [
CartStartedEvent::class => 'onCartStarted',
CartItemAddedEvent::class => 'onCartItemAdded',
CartItemRemovedEvent::class => 'onCartItemRemoved',
];
public function onCartStarted(CartStartedEvent $event): void
{ /* … */ }
public function onCartItemAdded(CartItemAddedEvent $event): void
{ /* … */ }
public function onCartItemRemoved(CartItemRemovedEvent $event): void
{ /* … */ }
}
Before
Attributes Event Listeners Example
class CartsProjector implements Projector
{
use ProjectsEvents;
#[SubscribesTo(CartStartedEvent::class)]
public function onCartStarted(CartStartedEvent $event): void
{ /* … */ }
#[SubscribesTo(CartItemAddedEvent::class)]
public function onCartItemAdded(CartItemAddedEvent $event): void
{ /* … */ }
#[SubscribesTo(CartItemRemovedEvent::class)]
public function onCartItemRemoved(CartItemRemovedEvent $event): void
{ /* … */ }
}
After
Named Arguments
Can specify the value name and no consideration to the order.
function foo(string $a, string $b, ?string $c = null, ?string $d = null)
{ /* … */ }
foo(
b: 'value b',
a: 'value a',
d: 'value d',
);
// NEW
setcookie(
name: 'test',
expires: time() + 60 * 60 * 2,
);
// OLD
setcookie(
'test',
'',
time() + 60 * 60 * 2,
);
setcookie (
string $name,
string $value = "",
int $expires = 0,
string $path = "",
string $domain = "",
bool $secure = false,
bool $httponly = false,
) : bool
Named Arguments
class CustomerData
{
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}
$data = new CustomerData(
name: $input['name'],
email: $input['email'],
age: $input['age'],
);
$input = [
'age' => 25,
'name' => 'Brent',
'email' => 'brent@stitcher.io',
];
$data = new CustomerData(...$input);
$input = [
'age' => 25,
'name' => 'Brent',
'email' => 'brent@stitcher.io',
'unknownProperty' => 'This is not allowed',
];
$data = new CustomerData(...$input);
Valid
Throws an Error
$input = [
'Brent',
'age' => 25,
'email' => 'brent@stitcher.io',
];
$data = new CustomerData(...$input);
Named Arguments Cont.
class CustomerData
{
public static function new(...$args): self
{
return new self(...$args);
}
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}
$data = CustomerData::new(
email: 'brent@stitcher.io',
age: 25,
name: 'Brent',
);
[
'age' => 25,
'email' => 'brent@stitcher.io',
'name' => 'Brent',
]
Variadic functions
Named Arguments Cont.
$field = 'age';
$data = CustomerData::new(
$field: 25,
);
Not Valid
Named Arguments Cont.
interface EventListener {
public function on($event, $handler);
}
class MyListener implements EventListener
{
public function on($myEvent, $myHandler)
{
// …
}
}
Name is flexible for inheritance
public function register(EventListener $listener)
{
$listener->on(
event: $this->event,
handler: $this->handler,
);
}
Match Expression
A Cleaner Switch Expression
$result = match($input) {
0 => "hello",
'1', '2', '3' => "world",
Default => "mars"
};
Throw Expression
php 7.4
public function (array $input): void
{
if (! isset($input['bar'])) {
throw new BarIsMissing();
}
$bar = $input['bar'];
}
public function (array $input): void
{
$bar = $input['bar'] ?? throw new BarIsMissing();
}
php 8+
Static return type
php 7.4
/**
* @return static
*/
public static function newInstance()
{
return new static();
}
public static function newInstance(): static
{
return new static();
}
php 8+
Union Type
php 7.4
/**
* @param string|int $input
*
* @return string
*/
public function validate($input): string;
php 8+
public function validate(string|int $input): string;
Mixed Type
/**
* @param mixed $input
*
* @return bool
*/
public function isValid($input): bool;
/**
* @param mixed $input
*
* @return bool
*/
public function isValid(mixed $input): bool;
public function isValid(mixed $input): bool;
Constructor Property Promotion
class Point {
public float $x;
public float $y;
public float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0,
) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
Inheritance with Private Methods
Warning: Private methods cannot be final as they are never overridden by other classes in ...
Weakmap Implementation
class Foo
{
private WeakMap $cache;
public function getSomethingWithCaching(object $obj): object
{
return $this->cache[$obj]
??= $this->computeSomethingExpensive($obj);
}
}
::class on objects
$foo = new Foo();
var_dump($foo::class);
NUllsafe Operator
$startDate = $booking->getStartDate();
$dateAsString = $startDate ? $startDate->asDateTimeString() : null;
Before
After
$dateAsString = $booking->getStartDate()?->asDateTimeString();
Non-capturing Catches
try {
// Something goes wrong
} catch (Throwable $exception) {
Log::error("Something went wrong");
}
php < 8
php > 8
try {
// Something goes wrong
} catch (Throwable) {
Log::error("Something went wrong");
}
Trailing comma in parameter list
$longArgs_longVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument, // Trailing commas were allowed in parameter lists in PHP 8.0
) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
$longArgs_longVars(
$longArgumentValue,
$obj->longMethodCall(),
$obj->longPropertyName ?? $longDefault,
);
Create Datetime from INTERFACE
DateTime::createFromInterface()
DatetimeImmutable::createFromInterface()
Stringable Interface
class Foo
{
public function __toString(): string
{
return 'foo';
}
}
function bar(string|Stringable $stringable) { /* … */ }
bar(new Foo());
bar('abc');
str_contains() function
OLD
NEW
if (strpos('string with lots of words', 'words') !== false) { /* … */ }
if (str_contains('string with lots of words', 'words')) { /* … */ }
str_starts/ends_with() functions
str_starts_with('haystack', 'hay'); // true
str_ends_with('haystack', 'stack'); // true
fdiv() function
fdiv(100, 0); // NAN/INF/-INF
get_debug_type() function
$bar = $arr['key'];
if (!($bar instanceof Foo)) {
// this shows the most simple of patterns, to get the real type an assoc array
// must be present to convert long-form "integer" into int etc.
throw new TypeError('Expected ' . Foo::class . ' got ' . (is_object($bar) ? get_class($bar) : gettype($bar)));
}
// would become
if (!($bar instanceof Foo)) {
throw new TypeError('Expected ' . Foo::class . ' got ' . get_debug_type($bar));
}
$bar->someFooMethod();
get_resource_id() function
$resourceId = (int) $mysqlConnection;
$resourceId = get_resource_id($mysqlConnection);
$resourceId = (int) $fileHandle;
$resourceId = get_resource_id($fileHandle);
PhpToken objects with get_token_all()
class PhpToken {
/** One of the T_* constants, or an integer < 256 representing a single-char token. */
public int $id;
/** The textual content of the token. */
public string $text;
/** The starting line number (1-based) of the token. */
public int $line;
/** The starting position (0-based) in the tokenized string. */
public int $pos;
/**
* Same as token_get_all(), but returning array of PhpToken.
* @return static[]
*/
public static function getAll(string $code, int $flags = 0): array;
final public function __construct(int $id, string $text, int $line = -1, int $pos = -1);
/** Get the name of the token. */
public function getTokenName(): ?string;
/**
* Whether the token has the given ID, the given text,
* or has an ID/text part of the given array.
*
* @param int|string|array $kind
*/
public function is($kind): bool;
/** Whether this token would be ignored by the PHP parser. */
public function isIgnorable(): bool;
}
Variable syntax tweaks
The Uniform Variable Syntax RFC resolved a number of inconsistencies in PHP's variable syntax. This RFC intends to address a small handful of cases that were overlooked.
Type annotation for internal functions
Ext-json always compiled in
PHP 8.1 Big Upgrades
ENUMS
enum Status {
case Pending;
case Active;
case Archived;
}
class User
{
public function __construct(
private Status $status = Status::Pending;
) {}
public function setStatus(Status $status): void
{ // … }
}
$user->setStatus(Status::Active);
Performance Improvements
Dmitry Stogov has added some improvements to opcache��"inheritance cache"��Reports between a 5% and 8% performance increase
Array Unpacking w/ String Keys
$array1 = ["a" => 1];
$array2 = ["b" => 2];
$array = ["a" => 0, ...$array1, ...$array2];
var_dump($array); // ["a" => 1, "b" => 2]
Already had array unpacking with Int keys��Now can use String keys w/ array unpacking
New “array_is_list” Function
$list = ["a", "b", "c"];
array_is_list($list); // true
$notAList = [1 => "a", 2 => "b", 3 => "c"];
array_is_list($notAList); // false
$alsoNotAList = ["a" => "a", "b" => "b", "c" => "c"];
array_is_list($alsoNotAList); // false
Determine if array is in numeric order starting at index 0
Explicit octal integer literal notation
016 === 0o16; // true
016 === 0O16; // true
Can now use 0o and 0O to denote octal numbers.
Breaking CHanges
03
“If you want to make an omelet, you gotta break some eggs”
Abstract methods in traits type fix
trait Test {
abstract public function test(int $input): int;
}
class UsesTrait
{
use Test;
public function test($input)
{
return $input;
}
}
php < 8 was valid
php > 8
class UsesTrait
{
use Test;
public function test(int $input): int
{
return $input;
}
}
Php 4 style constructor
class AnExampleObject
{
public function AnExampleObject() { }
}
class AnExampleObject
{
public function __construct() { }
}
OLD
NEW
Calling non-static methods with static call
class AnExampleObject
{
public function someMethod() { }
}
AnExampleObject::someMethod();
Don’t do this
UNSET CASTING IS GONE
$value = “value”;
(unset) $value;
OLD
NEW
$value = “value”;
unset($value);
Error Tracking
CASE-INSENSITIVE CONSTANTS
UNDEFINED CONSTANTS
Autoload function
public function __autoload($class) { }
OLD
NEW
�spl_autoload_register() {
function($class) { }
}
Create function
$func = create_function(
‘$x’, ‘$y’, ‘return $x * $y’;
)
OLD
NEW
�$func = function($x, $y) {
Return $x * $y;
};
Each function
$array = [1, 2, 3, 4];
reset($array);
while(list($key, $value) = each($array)) {
// ...
}
OLD
NEW
$array = [1, 2, 3, 4];
foreach($array as $key => $value) {
// ...
}
ARRAY INDEXES with NEGATIVE INTEGERS
array(3) = {
[-5] => string(3) ‘foo‘
[0] => string(3) ‘bar‘
[1] => string(3) ‘buzz‘
}
php < 8
php => 8
array(3) = {
[-5] => string(3) ‘foo‘
[-4] => string(3) ‘bar‘
[-3] => string(3) ‘buzz‘
}
$array = [ -5 => ‘foo‘ ];
$array[] = ‘bar‘;
$array[] = ‘buzz‘;
var_dump($array);
e_ALL and display_startup_errors
Php 8.1 Breaking Changes
// Run-time error
$GLOBALS = [];
$GLOBALS += [];
$GLOBALS =& $x;
$x =& $GLOBALS;
unset($GLOBALS);
by_ref($GLOBALS);
Php 8.1 Breaking Changes
These no longer work with *resources, you have to use the Object versions of these when calling the related functions.
Php 8.1 Breaking Changes
PHP 8.1 these will generate deprecation warnings and then TypeErrors for PHP 9
Php 8.1 Breaking Changes
Autovivification on false
autovivification (auto-creation of arrays from falsey values).
$array = false;
$array[] = 2;
Automatic conversion of false to array is deprecated
MINOR Php 8.1 Breaking Changes
MINOR Php 8.1 Breaking Changes
deprecationS
Default param 8.0 deprecation
<?php
function test($a = [], $b) {} // Before
function test($a, $b) {} // After
?>
<?php
function test(A $a = null, $b) {} // Still allowed
function test(?A $a, $b) {} // Recommended
?>
Misc 8.0 Deprecations
Misc 8.0 Deprecations Cont.
Misc 8.0 Deprecations Cont.
Sort comparison functions that return true or false will now throw a deprecation warning, and should be replaced with an implementation that returns an integer less than, equal to, or greater than zero.
<?php
// Replace
usort($array, fn($a, $b) => $a > $b);
// With
usort($array, fn($a, $b) => $a <=> $b);
?>
Misc 8.0 Deprecations Cont.
Using an empty file as ZipArchive is deprecated. Libzip 1.6.0 does not accept empty files as valid zip archives any longer. The existing workaround will be removed in the next version.
The procedural API of Zip is deprecated. Use ZipArchive instead. Iteration over all entries can be accomplished using ZipArchive::statIndex() and a for loop:
<?php
// iterate using the procedural API
assert(is_resource($zip));
while ($entry = zip_read($zip)) {
echo zip_entry_name($entry);
}
// iterate using the object-oriented API
assert($zip instanceof ZipArchive);
for ($i = 0; $entry = $zip->statIndex($i); $i++) {
echo $entry['name'];
}
?>
Misc 8.0 Deprecations Cont.
ReflectionFunction::isDisabled() is deprecated, as it is no longer possible to create a ReflectionFunction for a disabled function. This method now always returns false.
ReflectionParameter::getClass(), ReflectionParameter::isArray(), and ReflectionParameter::isCallable() are deprecated. ReflectionParameter::getType() and the ReflectionType APIs should be used instead.
Targeting 8.1 for listing as deprecated
(Under Discussion)
PHP 8.1 Features
04
“Out with the old, in with the new”
PHP 8.1 Release date
NOVEMBER 25, 2021
Php 8.1 - enums
enum Status {
case Pending;
case Active;
case Archived;
}
Php 8.1 - ENUMS
class Post
{
public function __construct(
private Status $status = Status::Pending;
) {}
public function setStatus(Status $status): void
{
// …
}
}
$post->setStatus(Status::Active);
Php 8.1 - Fibers
$fiber = new Fiber(function (): void {
$valueAfterResuming = Fiber::suspend('after suspending');
// …
});
$valueAfterSuspending = $fiber->start();
$fiber->resume('after resuming');
Php 8.1 - Performance boost
“Inheritance cache”
5% - 8% performance increase thanks for dmitry
Php 8.1 - Array unpacking
$array1 = ["a" => 1];
$array2 = ["b" => 2];
$array = ["a" => 0, ...$array1, ...$array2];
var_dump($array); // ["a" => 1, "b" => 2]
Php 8.1 - Array unpacking
class MyController {
public function __construct(
private Logger $logger = new NullLogger(),
) {}
}
Php 8.1 - READONLY PROPERTIES
class PostData {
public function __construct(
public readonly string $title,
public readonly DateTimeImmutable $date,
) {}
}
Php 8.1 - READONLY PROPERTIES
class PostData {
public function __construct(
public readonly string $title,
public readonly DateTimeImmutable $date,
) {}
}
$post = new Post('Title', /* … */);
$post->title = 'Other';
Error: Cannot modify readonly property Post::$title
Php 8.1 - first-class callable syntax
function foo(int $a, int $b) { /* … */ }
$foo = foo(...);
$foo(a: 1, b: 2);
Php 8.1 - Pure Intersection Types
function generateSlug(HasTitle&HasId $post) {
return strtolower($post->getTitle()) . $post->getId();
}
Php 8.1 - Never Type
function dd(mixed $input): never
{
// dump
exit;
}
Php 8.1 - ARRAY_IS_LIST Function
$list = ["a", "b", "c"];
array_is_list($list); // true
$notAList = [1 => "a", 2 => "b", 3 => "c"];
array_is_list($notAList); // false
$alsoNotAList = ["a" => "a", "b" => "b", "c" => "c"];
array_is_list($alsoNotAList); // false
Php 8.1 - final class constants
class Foo
{
public const X = "foo";
}
class Bar extends Foo
{
public const X = "bar";
}
Php 8.1 - final class 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
}
Php 8.1 - fsync function
$file = fopen("sample.txt", "w");
fwrite($file, "Some content");
if (fsync($file)) {
echo "File has been successfully persisted to disk.";
}
fclose($file);
Php 8.1 - explicit octal integer literal notation
016 === 0o16; // true
016 === 0O16; // true
RESOURCES
05
“Time is the single most important resource that we have. Every single minute we lose is never coming back”
Migration Guide
https://www.php.net/manual/en/migration80.php
Helpful Packages
Type Safety
Research some shim code if you want to start using new functions
Video & Articles & DOCS
I built this talk with the following resources:
Sebastian Bergmann Talk on How to get ready for PHP 8�Thank him for PHPUnit!
Brent’s articles on stitcher.io�Give a follow on Twitter!
Great way to see what is coming down the pipeline.
shoutouts
06
“Time is the single most important resource that we have. Every single minute we lose is never coming back”
PHP 8 is brought to you by...
Derick rethans
Sara Golemon
Dmitry & ZeEV
Gabriel Caruso
Nikita Popov
Internals
Contributor�Podcasting
PHP 8 Release Manager
JIT Compiler
PHP 8 Release Manager
OS Contributor�Submitted RFCs
PHP 8 Elephpant
https://inphpinity.elephpant.com
And thanks to all the others I forgot to mention
Thank You
Let’s Connect!
+1 702 381 3269