Modern PHP

While I wasn't paying attention, PHP got quite good

2022-03-145 min read

The last time I've used PHP was probably around 2017, although it was just in the context of supporting some WordPress sites. By that time 7.2 had already been released, but I had no idea. I wanted to avoid working with PHP at all costs.

This month I took some time to check what good things have been added to the language that I was unaware of. To be honest, things are looking great.

The list below is not exhaustive, I only reference what I'll be using, or find notable.

Array destructuring

Added in v7.1

PHP
default.php
// arrays
$posts = [[1, 2, 3, 4], [5, 6, 7]];
[$publishedPosts, $draftPosts] = $posts;
// or skip
[, $draftPosts] = $posts;
 
// associative arrays
$post = [
  'title' => 'Modern PHP',
  'description' => '...',
  'status' => 'Published'
];
 
['title' => $title, 'description' => $description] = $post;

Spread operator within arrays

  • Initial support for arrays in v7.4
  • Support for string-keyed (associative) arrays in v8.1
PHP
default.php
// arrays
$userPosts = [1, 2, 3, 4, 5];
$userPosts = [...$userPosts, 6];
 
// associative arrays
$userPosts = [
  'id-a' => 'Published',
  'id-b' => 'Draft',
];
$userPosts = [...$userPosts, 'id-c' => 'Draft'];

Match expressions

Added in v8.1

The one thing I'm most jealous it's missing in JavaScript

PHP
default.php
// using a switch statement
switch ($status) {
 case 'Published':
  $message = 'The post has been published';
  break;
 case 'Draft':
  $message = 'The post is in draft state';
  break;
}
 
// as a match expression
$message = match($status) {
  'Published' => 'The post has been published',
  'Draft' => 'The post is in draft state',
};

Enumerations (enums)

Added in v8.1

PHP
default.php
// previously using a class
class Status {
  const DRAFT = 'Draft';
  const PUBLISHED = 'Published';
  const ARCHIVED = 'Archived';
}
 
// using an enum
enum PostStatus {
  case Draft;
  case Published;
  case Archived;
}
 
$status = PostStatus::Draft;

Arrow functions

Added in v7.4

fn (args) => expression

(!) Arrow functions can't be multi-line

PHP
default.php
$publishedPosts = array_filter($posts,
  fn($post) => $post->status === 'Published'
)

Named parameters

Added in v8.0

PHP
default.php
function enableThisConfig($optionA, $optionB, $somethingElse, $anotherOne) {
 //
}
 
// 6 months later reading this.. ??
enableThisConfig(true, true, false, 'DEV');
 
// using named params
enableThisConfig(
  optionA: true,
  optionB: true,
  somethingElse: false,
  anotherOne: 'DEV',
);

Null coalescing operator

Added in v7.0

PHP
default.php
// this annoying thing
$data['name'] = isset($data['name']) ? $data['name'] : 'Guest';
// to
$data['name'] = $data['name'] ?? 'Guest';

Null coalescing assignment operator

Added in v7.4

PHP
default.php
// our previous example
$data['name'] = $data['name'] ?? 'Guest';
// to
$data['name'] ??= 'Guest';

Null-safe operator

Added in v8.0

PHP
default.php
class User {
  public function getPreferences() {}
}
class Preferences {
  public function getColorScheme() {}
}
 
$user = new User;
 
//  preferences could be null
$colorScheme = $user->getPreferences()->getColorScheme();
 
// alternative
$preferences = $user->getPreferences();
$colorScheme = $preferences ? $preferences->getColorScheme() : null;
 
// using null-safe operator
$colorScheme = $user->getPreferences()?->getColorScheme();
 

Spaceship operator

Added in v7.0

PHP
default.php
$result = 1 <=> 1 // 0
$result = 1 <=> 2 // -1
$result = 2 <=> 1 // 1
 
$array = [1, 4, 5, 6, 7, 8 ,9 ,2];
usort($array, fn($a, $b) => $a <=> $b);

Multi-catch exception handling

Added in v7.1

PHP
default.php
try {
 // ...
} catch(ErrorA | ErrorB $e) {
 //
} catch(Exception $e) {
 // general case
}

New string utils

Added in v8.0

Previously we would use strpos or some other creative solution.

PHP
default.php
$string = 'Modern PHP';
 
if (str_starts_with($string, 'Modern')) {}
if (str_ends_with($string, 'PHP')) {}
if (str_contains($string, 'Modern')) {}

Return types

Added in v7.0

PHP
default.php
function getPost(int $postId): Post {
 //
}

Union types

Added in v8.0

PHP
default.php
function updateTotal(int|float $cost) {
 //
}

Null and Void return types

Added in v7.1

PHP
default.php
// Notice the `?` before the type
function getPost(int $postId): ?Post {
 // can return null
}
 
function setPostTitle(int $postId, string $title): void {
  persistInDB($postId, $postTitle);
 // won't return anything, or optionally can do:
 // `return;`
}

Never return type

Added in v8.1

PHP
default.php
function notImplemented(): never {
  throw new Exception('Not implemented');
}

Import grouping

Added in v7.0

A small but welcome addition

PHP
default.php
// all in separate lines
use App\Entity\Task;
use App\Entity\Reminder;
use App\Entity\Todo;
 
// now
use App\Entity\{Task, Reminder, Todo};

Constructor property promotion

Added in v8.0

PHP
default.php
// from this
class User {
  public string $name;
  public Address $address;
 
  public function __construct(string $name, Address $address) {
    $this->name = $name;
    $this->address = $address;
 }
}
 
// to this
class User {
  public function __construct(protected string $name, protected Address $address) {
  // nothing else needed
  }
}

Weakmaps

Not really using Weakmaps, not even in JavaScript, but I find it a great addition

PHP
default.php
// code taken from https://php.watch/versions/8.0/weakmap
class CommentRepository {
  private WeakMap $comments_cache;
  public function getCommentsByPost(Post $post): ?array {
    if (!isset($this->comments_cache[$post])) {
      $this->comments_cache[$post] = $this->loadComments($post);
    }
 
    return $this->comments_cache[$post]
  }
}

I have to say I'm very happy to see these features, and I look forward to the next PHP releases with great excitement. Although I'll be mostly working with Laravel and its extensive library (collections & helpers) watching the language grow is lovely.

🐘