iterable
type, Nullable types, void returnsobject
typePHP 8.0 is the biggest release since...
PHP 7.4
function mangleUsers(string|array $users): array
{
if (is_string($users)) {
$users = [$users];
}
// ...
}
function doMath(int|float $in): int|float
{
// ...
}
function handleRequest(RequestInterface $req):
RequestInterface|ResponseInterface
{
// ...
}
null
is a type, equivalent to ?
false
is a return type only, don't use itmixed
~ untyped, so use mixed
static
returns
interface TaskBuilder
{
public function addStep(Step $s): static;
}
class ImportantTask implements TaskBuilder
{
public function addStep(Step $s): static
{
$this->steps[] = $s;
return $this;
}
}
addStep()
returns ImportantTask
, not TaskBuilder
.
Stringable
interface Stringable {
public function __toString(): string;
}
function show_message(string|Stringable $message): void
{
// ...
}
It's automated!
str_*_with()
$needle = 'hello';
if (str_starts_with('hello world', $needle)) {
print "This must be your first program.";
}
$needle = 'world';
if (str_ends_with('hello world', $needle)) {
print "You say hello to everyone else but not me?";
}
str_contains()
$needle = 'on';
if (str_contains('peace on earth', $needle)) {
print "Yes, let's do that.";
}
Never (screw up) writing this again
$needle = 'on';
if (strpos('peace on earth', $needle) == false) {
print "You have a bug!";
}
A string that is probably close enough to a number that we can
convert it to one automagically and call it a day
"time bomb" by dkshots is licensed under CC BY-NC 2.0
0 == "wait, what?"; // true
0 == ""; // true
99 == '99 bottles of beer'; // true
42 == ' 42'; // true
42 == '42 '; // false, only sometimes
in_array(0, ['a', 'b', 'c']); // true
Numeric string:
trim($string)
Whitespace is ignored at the start and end, consistently
" 42 " < $a_num
=> Compare as numbers
'abc' < $a_num
=> Compare as strings
expects_int(" 42 ");
=> OK in weak mode, error in strict
expects_int("42 is the answer");
=> always type error
Now go enable declare(strict_types=1);
"Burning Match Novoflex adaptor NX NIK 85mm Samsung NX200" by zen whisk is licensed under CC BY-ND 2.0
Have you done this?
$display = $user->isAdmin()
? $user->name() . ' (admin)'
: $user->name() . ' (muggle)'
;
true
/false
:-(match()
expressions
$display = match($user->isAdmin()) {
true => $user->name . ' (admin)',
false => $user->name . ' (muggle)',
};
===
matchThis...
switch ($var) {
case 'a':
$message = "The variable was a.";
break;
case 'b':
$message = "The variable was b.";
break;
case 'c':
$message = "The variable was c.";
break;
default:
$message = "The variable was something else.";
break;
}
Becomes this
$message = match($var) {
'a' => 'The variable was a',
'b' => 'The variable was b',
'c' => 'The variable was c',
default => 'The variable was something else',
};
echo match($operator) {
'+', '-', '*', '/' => 'Basic arithmetic',
'%' => 'Modulus',
'!' => 'Negation',
};
$count = get_count();
$size = match(true) {
$count > 0 && $count <=10 => 'small',
$count <=50 => 'medium',
$count >50 => 'huge',
};
Null
: The billion dollar mistake"100 Billion Dollars" by Peat Bakke is licensed under CC BY 2.0
null
$user = get_user($id);
if (!is_null($user)) {
$address = $user->getAddress();
if (!is_null($address)) {
$state = $address->state;
if (!is_null($state)) {
// And so on.
}
}
}
Eeew
$state = get_user($id)?->getAddress()?->state;
$bestSaleItem = $catalog
?->getProducts(get_seasonal_type())
?->mostPopular(10)[5];
get_seasonal_type()
not called if $catalog
is null
mostPopular()
$not_object->foo = 'bar'
$an_int[] = 4;
foreach()
foreach($an_int as $x) {}
foreach($not_traversable as $x) {}
print $arr[$an_object]
$val = 5/0
$an_int[4]
$a_string->name
print $an_array
$object->does_not_exist
print $does_not_exist
Undefined variables are now Warnings!
RFC: Allow trailing commas in...
implements
clause for interfacesuse
clauses for traitsuse
statement¯\_(ツ)_/¯
RFC: Allow a trailing comma in function arguments
Passed 30:10
¯\_(ツ)_/¯
Trailing commas in function definition:
Trailing commas in closure use
:
¯\_(ツ)_/¯
class Address
{
public function __construct(
string $street,
string $number,
string $city,
string $state,
string $zip, // This is new.
) {
// ...
}
}
$a ='A';
$b = 'B';
$c = 'C';
$dynamic_function = function(
$first,
$second,
$third, // This is new, as before.
) use (
$a,
$b,
$c, // This is also new.
);
class FormRenderer
{
private ThemeSystem $theme;
private UserRepository $users;
private ModuleSystem $modules;
public function __construct(
ThemeSystem $theme,
UserRepository $users,
ModuleSystem $modules
) {
$this->theme = $theme;
$this->users = $users;
$this->modules = $modules;
}
public function render(Form $form): string
{
// ...
}
}
class FormRenderer
{
public function __construct(
private ThemeSystem $theme,
private UserRepository $users,
private ModuleSystem $modules,
) { }
public function renderForm(Form $form): string
{
// ...
}
}
class FormRenderer
{
private User $currentUser;
public function __construct(
private ThemeSystem $theme,
private UserRepository $users,
private ModuleSystem $modules,
) {
$this->currentUser = $this->users->getActiveUser();
}
public function renderForm(Form $form): string
{
// ...
}
}
Class MailMessage
{
public function __construct(
public string $to,
public string $subject,
public string $from,
public string $body,
public array $cc,
public array $bcc,
public string $attachmentPath,
) {}
}
(~Doctrine Annotations but in core)
#[GreedyLoad]
class Product
{
#[Positive]
protected int $id;
#[Admin]
public function refillStock(int $quantity): bool
{
// ...
}
}
function my_listener(MyEvent $event): void { ... }
$provider = new OrderedListenerProvider();
// How you normally add a listener to a provider.
$provider->addListener('my_listener', 5, 'listener_a', MyEvent::class);
namespace Crell\Tukio;
use \Attribute;
#[Attribute]
class Listener implements ListenerAttribute
{
public function __construct(
public ?string $id = null,
public ?string $type = null,
) {}
}
namespace Crell\Tukio;
use \Attribute;
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)]
class Listener implements ListenerAttribute
{
public function __construct(
public ?string $id = null,
public ?string $type = null,
) {}
}
use Crell\Tukio\Listener;
#[Listener('listener_a')]
function my_listener(MyEvent $event): void { ... }
$provider = new OrderedListenerProvider();
$provider->addListener('my_listener', 5);
Use Crell\Tukio\Listener;
/// A string means it's a function name,
// so reflect on it as a function.
if (is_string($listener)) {
$ref = new \ReflectionFunction($listener);
$attribs = $ref->getAttributes(
Listener::class,
\ReflectionAttribute::IS_INSTANCEOF,
);
$attributes = array_map(
fn(\ReflectionAttribute $attrib) => $attrib->newInstance(),
$attribs,
);
}
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
class SomeController
{
#[Route('/path', 'action_name')]
public function index(#[CurrentUser] MyUser $user)
{
// ...
}
}
class Point
{
public function __construct(
#[Positive]
public int $x,
#[Positive]
public int $y,
) {}
}
Applies to parameter and property
What does this do?
$new = array_fill(0, 100, 50);
50
, 100 times?100
, 50 times?That's what this does!
array_fill(start_index: 0, count: 100, value: 50);
array_fill(
value: 50,
count: 100,
start_index: 0,
);
array_fill(
value: 50,
0,
count: 100,
);
// Indexed array, so the values will map to indexed parameters.
$params = [0, 100, 50];
array_fill(...$params);
// Named array, so the values will map to named parameters.
$params = ['count' => 100, 'start_index' => 0, 'value' => 50];
array_fill(...$params);
$args['value'] = $request->get('val') ?? 'a';
$args['start_index'] = 0;
$args['count'] = $config->getSetting('array_size');
$array = array_fill(...$args);
class Url implements UrlInterface
{
public function __construct(
private string $scheme = 'https',
private ?string $authority = null,
private ?string $userInfo = null,
private string $host = '',
private ?int $port = 443,
private string $path = '',
private ?string $query = null,
private ?string $fragment = null,
) {}
}
// PHP <= 7.4
$url = new Url('https', null, null, 'typo3.org', 443, '/blog', null, 'latest');
// PHP 8.0
$url = new Url(host: 'typo3.org', path: '/blog', fragment: 'latest');
// PHP 8.0
$url = new Url(
path: '/blog',
host: 'typo3.org',
fragment: 'latest'
);
class SomeController
{
#[Route(
path: '/path',
name: 'action',
)]
public function someAction()
{
// ...
}
}
if (in_array(haystack: $arr, needle: 'A')) {
// ...
}
¯\_(ツ)_/¯
Parameter names are now part of your interface contract
$needle
/$haystack
is no longer a thing