Article
PHP 8系の新機能を整理する。trait・enum・パイプ演算子は何が違うのか
PHP 8系で注目したい機能を、PHP 7系以下との違いがわかる具体的なコード付きで整理する。traitの位置づけ、enum、match、Attributes、そしてPHP 8.5で追加されたパイプ演算子までまとめて紹介。
目次
現状最新のPHP 8.5はこんな感じ。
PHP 8.5では、|> パイプ演算子、clone() 時のプロパティ変更、#[\NoDiscard] 属性、定数式でのクロージャや第一級 callable の利用などが追加され、コードをより読みやすく、より安全に書けるようになりました。
なお、よく「PHP 8で導入された機能」として挙げられがちな trait はPHP 5.4で導入済み で、PHP 8系の新機能ではありません。一方で enum はPHP 8.1、パイプ演算子はPHP 8.5 の機能です。
PHP 7系以下と比べると、PHP 8系は「型安全」「表現力」「可読性」がかなり強化された世代だと言えます。
PHP 8系で何が変わったのか
PHP 8系では、単なる高速化だけでなく、コードの書きやすさや保守性に直結する機能が多く追加されました。特に、enum や match、属性(Attributes)、コンストラクタプロパティ昇格、Union Types、そして PHP 8.5 の |> パイプ演算子は、実務コードの見た目と設計を大きく変えています。
その一方で、注意したいのは trait はPHP 8で追加されたものではない という点です。trait は PHP 5.4 から使える仕組みで、クラス間でメソッドを再利用するための機能です。なので、「PHP 8系の新機能」として紹介するなら enum や |> を中心にしつつ、trait は「以前からあるが、今でも重要な機能」として位置付けるのが正確です。
1. trait
これはPHP 8の新機能ではなく、昔からある
trait は、複数のクラスで共通のメソッドを使い回したいときに便利です。継承だと「親クラスは1つしか持てない」という制約がありますが、trait ならメソッド単位で再利用できます。
traitなしだとこうなりがち
<?php
class UserService
{
public function log(string $message): void
{
echo "[LOG] " . $message . PHP_EOL;
}
}
class OrderService
{
public function log(string $message): void
{
echo "[LOG] " . $message . PHP_EOL;
}
}
同じ log() が重複しています。
traitを使うとこうなる
<?php
trait LoggerTrait
{
public function log(string $message): void
{
echo "[LOG] " . $message . PHP_EOL;
}
}
class UserService
{
use LoggerTrait;
}
class OrderService
{
use LoggerTrait;
}
何がうれしいか
- 共通処理をまとめられる
- 継承より柔軟
- Laravel などでもよく使われる
ただし、trait は PHP 5.4から存在 しているため、「PHP 8系で導入された機能」と説明してしまうと誤りです。
2. enum
PHP 8.1で追加された、かなり大きな進化
PHP 7系以下では、状態や種別を文字列や定数で管理することが多く、タイプミスに弱いという問題がありました。PHP 8.1 で enum が入ったことで、その問題をかなり自然に防げるようになりました。
PHP 7系以下の書き方
<?php
class UserStatus
{
public const ACTIVE = 'active';
public const INACTIVE = 'inactive';
public const SUSPENDED = 'suspended';
}
function isActive(string $status): bool
{
return $status === UserStatus::ACTIVE;
}
$status = 'actve'; // タイポしても実行時まで気づきにくい
var_dump(isActive($status)); // false
PHP 8.1以降の enum
<?php
enum UserStatus: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case SUSPENDED = 'suspended';
}
function isActive(UserStatus $status): bool
{
return $status === UserStatus::ACTIVE;
}
$status = UserStatus::ACTIVE;
var_dump(isActive($status)); // true
違い
PHP 7系以下では「ただの文字列」だったものが、PHP 8.1以降では「意味を持った型」になります。
たとえば「active / inactive / suspended しか入らない」という制約を、コード上ではっきり表せます。
3. パイプ演算子 |>
PHP 8.5で追加
PHP 8.5では |> パイプ演算子が追加されました。これにより、処理を左から右へ流れるように書けるようになります。ネストした関数呼び出しが減るので、読みやすさがかなり上がります。
PHP 7系以下や8.4以前の書き方
<?php
$title = ' PHP 8.5 Released ';
$slug = strtolower(
str_replace(
'.',
'',
str_replace(' ', '-', trim($title))
)
);
echo $slug; // php-85-released
ネストが深く、内側から読まないと流れが追いづらいです。
PHP 8.5の書き方
<?php
$title = ' PHP 8.5 Released ';
$slug = $title
|> trim(...)
|> (fn($str) => str_replace(' ', '-', $str))
|> (fn($str) => str_replace('.', '', $str))
|> strtolower(...);
echo $slug; // php-85-released
何がよいか
- 上から下、左から右へ読める
- 中間変数を増やさず処理をつなげられる
- データ変換の連鎖が見やすい
たとえば配列加工でも相性がよいです。
<?php
$numbers = [1, 2, 3, 4, 5];
$result = $numbers
|> array_filter($..., fn($n) => $n % 2 === 1)
|> array_map(fn($n) => $n * 10, $...)
|> array_values(...);
print_r($result); // [10, 30, 50]
4. match式
switchより安全で書きやすい
match は PHP 8.0 で追加されました。switch よりも厳密比較で、値を返せるのが便利です。
PHP 7系以下の switch
<?php
$status = 2;
switch ($status) {
case 1:
$label = '未対応';
break;
case 2:
$label = '対応中';
break;
case 3:
$label = '完了';
break;
default:
$label = '不明';
}
PHP 8の match
<?php
$status = 2;
$label = match ($status) {
1 => '未対応',
2 => '対応中',
3 => '完了',
default => '不明',
};
違い
break不要- 式として使える
==ではなく厳密比較
5. 属性(Attributes)
コメント風アノテーションから正式構文へ
PHP 7系以下では、メタ情報をDocBlockコメントに書いてフレームワーク側で解釈することが多くありました。PHP 8.0 以降は Attributes によって、言語仕様として安全にメタデータを付与できます。
PHP 7系以下
<?php
/**
* @Route("/users")
* @Auth("admin")
*/
class UserController
{
}
PHP 8以降
<?php
#[Route('/users')]
#[Auth('admin')]
class UserController
{
}
何がよいか
- コメントではなく正式な構文
- リファクタしやすい
- IDEや静的解析と相性がよい
6. コンストラクタプロパティ昇格
PHP 8.0で定番になった書き方
PHP 7系以下だと、プロパティ定義と代入を別々に書いていました。PHP 8.0からは、コンストラクタ引数に public や private を書くだけでプロパティ宣言と代入をまとめられます。
PHP 7系以下
<?php
class User
{
private string $name;
private int $age;
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
}
PHP 8以降
<?php
class User
{
public function __construct(
private string $name,
private int $age
) {}
}
違い
かなり短くなります。
DTOや値オブジェクトでは特に恩恵が大きいです。
7. Union Types
型を複数許容できる
PHP 7系でも型宣言はありましたが、「string または null」のような複数型はDocBlock頼りなことが多くありました。PHP 8では型として表現できます。
PHP 7系以下
<?php
/**
* @param int|string $id
*/
function findUser($id)
{
// ...
}
PHP 8以降
<?php
function findUser(int|string $id): array|null
{
// ...
return null;
}
何がよいか
- 型情報が実コードに乗る
- IDE補完が強くなる
- バグを早めに発見しやすい
8. named arguments
引数の意味が読みやすくなる
PHP 8.0から、関数呼び出し時に引数名を指定できます。
PHP 7系以下
<?php
htmlspecialchars($text, ENT_QUOTES, 'UTF-8', false);
何の値かわかりづらいです。
PHP 8以降
<?php
htmlspecialchars(
string: $text,
flags: ENT_QUOTES,
encoding: 'UTF-8',
double_encode: false
);
9. PHP 8.5の追加ポイント
パイプ演算子だけではない
PHP 8.5では |> のほかにも、clone() 時にプロパティを変更できる機能や #[\NoDiscard] 属性などが入っています。
Clone With
8.4以前
<?php
readonly class Color
{
public function __construct(
public int $red,
public int $green,
public int $blue,
public int $alpha = 255,
) {}
public function withAlpha(int $alpha): self
{
return new self($this->red, $this->green, $this->blue, $alpha);
}
}
8.5
<?php
readonly class Color
{
public function __construct(
public int $red,
public int $green,
public int $blue,
public int $alpha = 255,
) {}
public function withAlpha(int $alpha): self
{
return clone($this, ['alpha' => $alpha]);
}
}
#[\NoDiscard]
<?php
#[\NoDiscard]
function buildToken(): string
{
return bin2hex(random_bytes(16));
}
buildToken(); // 戻り値を使わないと警告対象
APIの戻り値をうっかり無視するミスを減らせます。
まとめ
PHP 7系以下とPHP 8系の違いを一言でいうと、「動けばよいコード」から「安全で読みやすいコード」へ進化した という点にあります。
特に次の3つは押さえておくと理解しやすいです。
- trait: 便利だが新機能ではない。PHP 5.4からある
- enum: PHP 8.1で追加。文字列定数管理よりずっと安全
- パイプ演算子
|>: PHP 8.5で追加。処理の流れがかなり読みやすくなる
つまり、PHP 8系は「新しい文法が増えた」だけではなく、型・設計・可読性を言語レベルで支える方向に進化した と捉えるとわかりやすいです。