Skip to content
stoic man
March 14, 2022

Modern PHP

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

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

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

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

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

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

Named parameters

Added in v8.0

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

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

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

Null-safe operator

Added in v8.0

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

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

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.

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

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

Union types

Added in v8.0

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

Null and Void return types

Added in v7.1

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

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

Import grouping

Added in v7.0

A small but welcome addition

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

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

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.

🐘