Modern PHP

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

March 14th 20226 minutes 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

// 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
// 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

// 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

// 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

$publishedPosts = array_filter($posts,
  fn($post) => $post->status === 'Published'
)

Named parameters

Added in v8.0

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

// 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

// our previous example
$data['name'] = $data['name'] ?? 'Guest';
// to
$data['name'] ??= 'Guest';

Null-safe operator

Added in v8.0

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

$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

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.

$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

function getPost(int $postId): Post {
 //
}

Union types

Added in v8.0

function updateTotal(int|float $cost) {
 //
}

Null and Void return types

Added in v7.1

// 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

function notImplemented(): never {
  throw new Exception('Not implemented');
}

Import grouping

Added in v7.0

A small but welcome addition

// 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

// 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

// 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.

🐘