Have you done this?
| $display = $user->isAdmin() |
| ? $user->name() . ' (admin)' |
| : $user->name() . ' (muggle)' |
| ; |
- Clean branching expression
- Encourages simple expressions
- Encourages factoring out to functions
- Only works for
true/false :-(
match() expressions
| $display = match($user->isAdmin()) { |
| true => $user->name . ' (admin)', |
| false => $user->name . ' (muggle)', |
| }; |
=== match
- Returns expression
- No fall-through
- Must be exhaustive
This...
| 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', |
| }; |
Compound arms
| echo match($operator) { |
| '+', '-', '*', '/' => 'Basic arithmetic', |
| '%' => 'Modulus', |
| '!' => 'Negation', |
| }; |
Complex matches
| $count = get_count(); |
| $size = match(true) { |
| $count > 0 && $count <=10 => 'small', |
| $count <=50 => 'medium', |
| $count >50 => 'huge', |
| }; |
2017
RFC: Allow trailing commas in...
- function arguments
- function declaration
- grouped namespaces
implements clause for interfaces
use clauses for traits
- class property list
- closure
use statement
¯\_(ツ)_/¯
PHP 7.3
RFC: Allow a trailing comma in function arguments
Passed 30:10
¯\_(ツ)_/¯
PHP 8.0
Trailing commas in function definition: 
Trailing commas in closure use: 
¯\_(ツ)_/¯
Commas!
| class Address |
| { |
| public function __construct( |
| string $street, |
| string $number, |
| string $city, |
| string $state, |
| string $zip, // This is new. |
| ) { |
| |
| } |
| } |
| class Address |
| { |
| public function __construct( |
| string $street, |
| string $number, |
| string $city, |
| string $state, |
| string $zip, // This is new. |
| ) { |
| |
| } |
| } |
More Commas!
| $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. |
| ); |
| $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. |
| ); |
Meta(meta(data))
(~Doctrine Annotations but in core)
Attributes
| |
| class Product |
| { |
| |
| protected int $id; |
| |
| |
| public function refillStock(int $quantity): bool |
| { |
| |
| } |
| } |
Example: Tukio
| function my_listener(MyEvent $event): void { ... } |
| |
| $provider = new OrderedListenerProvider(); |
| |
| |
| $provider->addListener('my_listener', 5, 'listener_a', MyEvent::class); |
Step 1: Define attribute
| namespace Crell\Tukio; |
| |
| use \Attribute; |
| |
| |
| class Listener implements ListenerAttribute |
| { |
| public function __construct( |
| public ?string $id = null, |
| public ?string $type = null, |
| ) {} |
| } |
Step 1.5: Restrict attribute
| namespace Crell\Tukio; |
| |
| use \Attribute; |
| |
| |
| class Listener implements ListenerAttribute |
| { |
| public function __construct( |
| public ?string $id = null, |
| public ?string $type = null, |
| ) {} |
| } |
Step 2: Tag it
| use Crell\Tukio\Listener; |
| |
| |
| function my_listener(MyEvent $event): void { ... } |
| |
| $provider = new OrderedListenerProvider(); |
| |
| $provider->addListener('my_listener', 5); |
Step 3: Interpret it
| Use Crell\Tukio\Listener; |
| |
| |
| |
| 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, |
| ); |
| } |
Symfony 5.2
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
| use Symfony\Component\Routing\Annotation\Route; |
| use Symfony\Component\Security\Http\Attribute\CurrentUser; |
| |
| class SomeController |
| { |
| |
| public function index(#[CurrentUser] MyUser $user) |
| { |
| |
| } |
| } |
Promoted parameter attributes
| 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); |
Any order
| array_fill( |
| value: 50, |
| count: 100, |
| start_index: 0, |
| ); |
Not allowed
| array_fill( |
| value: 50, |
| 0, |
| count: 100, |
| ); |
Variadics
| |
| $params = [0, 100, 50]; |
| array_fill(...$params); |
| |
| |
| $params = ['count' => 100, 'start_index' => 0, 'value' => 50]; |
| array_fill(...$params); |
Dynamic arguments
| $args['value'] = $request->get('val') ?? 'a'; |
| $args['start_index'] = 0; |
| $args['count'] = $config->getSetting('array_size'); |
| |
| $array = array_fill(...$args); |
Value objects
| 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, |
| ) {} |
| } |
How are you going to call?
| |
| $url = new Url('https', null, null, 'typo3.org', 443, '/blog', null, 'latest'); |
| |
| $url = new Url(host: 'typo3.org', path: '/blog', fragment: 'latest'); |
| |
| $url = new Url( |
| path: '/blog', |
| host: 'typo3.org', |
| fragment: 'latest' |
| ); |
cf: Improving PHP's object ergonomics
Attributes
| class SomeController |
| { |
| |
| path: '/path', |
| name: 'action', |
| )] |
| public function someAction() |
| { |
| |
| } |
| } |
The most important use case...
| if (in_array(haystack: $arr, needle: 'A')) { |
| |
| } |
¯\_(ツ)_/¯
Caveat coder
Parameter names are now part of your interface contract