T-CREATOR

PHP 基本文法を 90 分で速習:型宣言・null 合体・スプレッド構文の実践

PHP 基本文法を 90 分で速習:型宣言・null 合体・スプレッド構文の実践

PHP を効率的に学びたい方にとって、モダンな構文の習得は避けて通れません。本記事では、PHP 7.0 以降で導入された重要な基本文法を 90 分で学べるよう、型宣言・null 合体演算子・スプレッド構文に絞って実践的に解説します。

これらの構文は、コードの可読性を高め、バグを減らし、開発効率を大幅に向上させてくれるものばかりです。実務でよく使われるパターンを中心に、すぐに活用できる知識をお届けしますね。

背景

PHP のモダン化の流れ

PHP は 2015 年の 7.0 リリースを境に、大きな進化を遂げました。それまでの「スクリプト言語」としてのイメージから、型安全性やパフォーマンスを重視したモダンな言語へと変貌を遂げたのです。

PHP 7.0 以降で導入された主要機能は次の通りです。

#バージョン主な機能リリース年
1PHP 7.0スカラー型宣言、戻り値の型宣言2015 年
2PHP 7.1null 許容型、void 型2016 年
3PHP 7.3配列・関数へのスプレッド構文2018 年
4PHP 7.4プロパティ型宣言2019 年
5PHP 8.0Union 型、Named Arguments2020 年

これらの機能により、PHP は静的解析ツールとの相性が良くなり、大規模開発にも耐えられる言語へと成長しました。

なぜこれらの構文を学ぶべきか

モダン PHP の構文を学ぶメリットは以下の 3 点に集約されます。

まず、型宣言によって実行前にエラーを検出できるため、デバッグ時間が大幅に短縮されるでしょう。次に、null 合体演算子により null チェックのコードが簡潔になり、可読性が向上します。最後に、スプレッド構文によって配列操作が直感的になり、メモリ効率も改善されますね。

以下の図は、モダン PHP 構文の学習による開発サイクルの改善を示しています。

mermaidflowchart LR
  oldCode["従来の PHP コード"] -->|問題点| issues["実行時エラー<br/>冗長な null チェック<br/>配列操作の複雑さ"]
  issues -->|学習| modernSyntax["モダン構文の習得"]
  modernSyntax -->|適用| features["型宣言<br/>null 合体演算子<br/>スプレッド構文"]
  features -->|効果| benefits["コンパイル時検出<br/>コード簡潔化<br/>パフォーマンス向上"]
  benefits -->|結果| improvement["開発効率の向上"]

このように、モダン構文の習得は開発全体の品質向上につながります。

課題

従来の PHP が抱えていた問題

PHP 5.x 以前のコードベースでは、次のような問題が頻繁に発生していました。

型の不一致によるバグ

型宣言がないため、関数に予期しない型の値が渡されても実行時まで気づけませんでした。

php<?php
// 従来の PHP(型宣言なし)
function calculateTotal($price, $quantity) {
    return $price * $quantity;
}

// 文字列が渡されても実行時までエラーにならない
echo calculateTotal("100円", "5個"); // 予期しない結果: 0

この例では、文字列が渡されても PHP が暗黙的に型変換を試みるため、予期しない結果が返されてしまいます。

null チェックの冗長性

null 値の扱いが煩雑で、三項演算子や isset() を多用する必要がありました。

php<?php
// 従来の null チェック方法
$username = isset($_GET['name']) ? $_GET['name'] : 'ゲスト';

// ネストした配列の場合はさらに複雑
$email = isset($user['profile']['email'])
    ? $user['profile']['email']
    : 'unknown@example.com';

このコードは動作しますが、可読性が低く、書くのも面倒ですよね。

配列操作の不便さ

複数の配列を結合したり、関数の引数として展開したりする際に、array_merge()call_user_func_array() といった関数を使う必要がありました。

php<?php
// 従来の配列結合方法
$arr1 = [1, 2, 3];
$arr2 = [4, 5, 6];
$merged = array_merge($arr1, $arr2);

// 可変長引数の処理も複雑
function sum() {
    $args = func_get_args();
    return array_sum($args);
}

これらの問題を解決するために、PHP は新しい構文を導入してきました。

学習者が直面する障壁

モダン PHP を学ぶ際、多くの方が以下のような壁にぶつかります。

#課題具体例
1情報の断片化各機能の説明が別々のドキュメントに散在
2バージョン間の違いPHP 7.1 と 8.0 での型宣言の違いが不明瞭
3実践例の不足理論は分かるが実際のコードへの適用方法が不明
4段階的な学習パスの欠如どの順序で学べば効率的か分からない

本記事では、これらの課題を解決するため、実践的なコード例と段階的な説明を重視しています。

解決策

型宣言による型安全性の確保

型宣言を使うことで、関数の引数と戻り値の型を明示的に指定できます。これにより、実行前に型の不一致を検出できるようになりますね。

スカラー型宣言の基本

PHP 7.0 以降では、関数の引数に intfloatstringbool といったスカラー型を指定できます。

php<?php
// 厳格な型チェックを有効化
declare(strict_types=1);

declare(strict_types=1) は、そのファイル内で厳格な型チェックを有効にする宣言です。これがないと、PHP は自動的に型変換を試みてしまいます。

php<?php
function calculateTotal(int $price, int $quantity): int {
    return $price * $quantity;
}

この関数定義では、$price$quantity が整数型であることを要求し、戻り値も整数型であることを保証しています。

php<?php
// 正しい使用例
echo calculateTotal(100, 5); // 500

// エラーになる例
echo calculateTotal("100", "5");
// TypeError: Argument 1 must be of type int, string given

型宣言により、文字列を渡した時点で TypeError が発生するため、バグの早期発見が可能です。

null 許容型の活用

PHP 7.1 以降では、型の前に ? を付けることで、null も許容する型宣言ができます。

php<?php
function getUserName(?int $userId): ?string {
    if ($userId === null) {
        return null;
    }

    // ユーザー情報を取得する処理
    return "ユーザー" . $userId;
}

この関数は、$userId が null の場合に null を返すことができます。?int は「int または null」を意味しますね。

php<?php
// 使用例
echo getUserName(123);  // "ユーザー123"
echo getUserName(null); // null が返される

null 許容型を使うことで、null を扱うロジックが明確になります。

Union 型による柔軟な型指定

PHP 8.0 以降では、複数の型を受け入れる Union 型が使えるようになりました。

php<?php
function formatValue(int|float $value): string {
    return number_format($value, 2);
}

この関数は整数または浮動小数点数を受け取り、小数点以下 2 桁の文字列にフォーマットします。

php<?php
// 両方の型を受け入れる
echo formatValue(1234);      // "1,234.00"
echo formatValue(1234.56);   // "1,234.56"

Union 型により、型の柔軟性と安全性を両立できるでしょう。

以下の図は、型宣言の進化と適用範囲を示しています。

mermaidflowchart TB
  php70["PHP 7.0<br/>スカラー型宣言"] --> php71["PHP 7.1<br/>null 許容型"]
  php71 --> php74["PHP 7.4<br/>プロパティ型宣言"]
  php74 --> php80["PHP 8.0<br/>Union 型・Mixed 型"]

  php70 -.適用.-> args["関数引数"]
  php70 -.適用.-> returns["戻り値"]
  php74 -.適用.-> props["クラスプロパティ"]
  php80 -.適用.-> complex["複雑な型の組み合わせ"]

このように、PHP の型システムは段階的に強化されてきました。

null 合体演算子による簡潔な null 処理

null 合体演算子(??)を使うことで、null チェックのコードを大幅に簡潔化できます。

基本的な使い方

?? 演算子は、左辺が null または未定義の場合に右辺の値を返します。

php<?php
// null 合体演算子の基本
$username = $_GET['name'] ?? 'ゲスト';

この 1 行で、$_GET['name'] が存在しない、または null の場合に 'ゲスト' を設定できますね。

従来の方法と比較してみましょう。

php<?php
// 従来の方法(冗長)
$username = isset($_GET['name']) ? $_GET['name'] : 'ゲスト';

// null 合体演算子(簡潔)
$username = $_GET['name'] ?? 'ゲスト';

コードの可読性が格段に向上していることが分かります。

連鎖的な null チェック

複数の値を順番にチェックし、最初に null でない値を使用できます。

php<?php
// 複数の候補から最初の有効な値を取得
$config = [
    'database' => [
        'host' => 'localhost',
        // 'port' は未定義
    ]
];

設定ファイルから値を取得する際、環境変数、設定ファイル、デフォルト値の順で優先順位を付けたい場合があります。

php<?php
$dbHost = $_ENV['DB_HOST']
    ?? $config['database']['host']
    ?? 'localhost';

$dbPort = $_ENV['DB_PORT']
    ?? $config['database']['port']
    ?? 3306;

この例では、環境変数が最優先され、次に設定ファイル、最後にデフォルト値が使われます。

null 合体代入演算子

PHP 7.4 以降では、??= 演算子で変数が null の場合のみ値を代入できます。

php<?php
$data = ['name' => 'Taro'];

// name は既に存在するので上書きされない
$data['name'] ??= 'デフォルト名';
echo $data['name']; // "Taro"

name キーは既に存在するため、デフォルト値は適用されません。

php<?php
// email は存在しないので設定される
$data['email'] ??= 'unknown@example.com';
echo $data['email']; // "unknown@example.com"

この演算子は、初期値の設定に便利ですね。

スプレッド構文による配列・関数の柔軟な操作

スプレッド構文(...)は、配列の展開や可変長引数の処理を直感的にします。

配列のスプレッド構文

PHP 7.4 以降では、配列リテラル内でスプレッド構文が使えます。

php<?php
$fruits = ['りんご', 'バナナ'];
$vegetables = ['にんじん', 'トマト'];

複数の配列を結合する際、スプレッド構文を使うと非常に簡潔になります。

php<?php
// スプレッド構文で配列を結合
$foods = [...$fruits, ...$vegetables, 'パン', 'チーズ'];
print_r($foods);
// ['りんご', 'バナナ', 'にんじん', 'トマト', 'パン', 'チーズ']

従来の array_merge() と比べて、より直感的で読みやすいでしょう。

php<?php
// 従来の方法
$foods = array_merge($fruits, $vegetables, ['パン', 'チーズ']);

関数の可変長引数

スプレッド構文を使って、可変長引数を受け取る関数を定義できます。

php<?php
function sum(int ...$numbers): int {
    return array_sum($numbers);
}

...$numbers は、任意の数の整数を配列として受け取ることを意味します。

php<?php
// 任意の数の引数を渡せる
echo sum(1, 2, 3);           // 6
echo sum(10, 20, 30, 40);    // 100
echo sum(100);               // 100

引数の数を気にせず、柔軟に関数を呼び出せますね。

配列を引数として展開

逆に、配列を関数の個別の引数として展開することもできます。

php<?php
function createUser(string $name, string $email, int $age): array {
    return [
        'name' => $name,
        'email' => $email,
        'age' => $age
    ];
}

この関数は 3 つの引数を受け取りますが、配列から値を展開して渡せます。

php<?php
$userParams = ['太郎', 'taro@example.com', 25];

// 配列を引数として展開
$user = createUser(...$userParams);
print_r($user);
// ['name' => '太郎', 'email' => 'taro@example.com', 'age' => 25]

スプレッド構文により、配列と個別の引数を簡単に相互変換できます。

以下の図は、スプレッド構文の 2 つの使用パターンを示しています。

mermaidflowchart LR
  subgraph pattern1["パターン 1: 配列の結合"]
    arr1["配列 A"] --> spread1["..."]
    arr2["配列 B"] --> spread2["..."]
    spread1 --> merged["新しい配列"]
    spread2 --> merged
  end

  subgraph pattern2["パターン 2: 引数の展開"]
    array["配列"] --> spread3["..."]
    spread3 --> func["関数(引数 1, 引数 2, ...)"]
  end

スプレッド構文は配列操作の要となる構文です。

具体例

実践例 1:型安全なユーザー管理クラス

型宣言を活用した、実務で使えるユーザー管理クラスを実装してみましょう。

クラス定義とプロパティ型宣言

PHP 7.4 以降では、クラスのプロパティにも型を指定できます。

php<?php
declare(strict_types=1);

class User {
    private int $id;
    private string $name;
    private string $email;
    private ?string $phoneNumber;
    private array $roles;
}

各プロパティに型を宣言することで、不正な値の代入を防げます。?string は電話番号が任意項目であることを示していますね。

コンストラクタでの初期化

コンストラクタでも型宣言を使って、安全にオブジェクトを生成します。

php<?php
class User {
    // ... プロパティ宣言

    public function __construct(
        int $id,
        string $name,
        string $email,
        ?string $phoneNumber = null,
        array $roles = []
    ) {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
        $this->phoneNumber = $phoneNumber;
        $this->roles = $roles;
    }
}

コンストラクタの引数にデフォルト値を設定することで、柔軟な初期化が可能です。

メソッドでの型宣言と null 合体演算子の組み合わせ

ユーザー情報を取得するメソッドで、型宣言と null 合体演算子を組み合わせます。

php<?php
class User {
    // ... 前述のコード

    public function getDisplayName(): string {
        return $this->name;
    }

    public function getPhoneNumber(): string {
        // null の場合はデフォルトメッセージを返す
        return $this->phoneNumber ?? '電話番号未登録';
    }
}

getPhoneNumber() メソッドは、電話番号が null の場合に分かりやすいメッセージを返します。

php<?php
    public function hasRole(string $role): bool {
        return in_array($role, $this->roles, true);
    }

    public function addRole(string $role): void {
        if (!$this->hasRole($role)) {
            $this->roles[] = $role;
        }
    }
}

void 型を使うことで、戻り値がないことを明示的に宣言できますね。

使用例

実際にユーザークラスを使ってみましょう。

php<?php
// ユーザーオブジェクトの生成
$user1 = new User(
    id: 1,
    name: '山田太郎',
    email: 'taro@example.com',
    phoneNumber: '090-1234-5678',
    roles: ['member']
);

$user2 = new User(
    id: 2,
    name: '佐藤花子',
    email: 'hanako@example.com'
    // phoneNumber と roles は省略(デフォルト値が使用される)
);

Named Arguments(PHP 8.0 以降)を使うことで、引数の意味が明確になります。

php<?php
// メソッドの利用
echo $user1->getDisplayName();    // "山田太郎"
echo $user1->getPhoneNumber();    // "090-1234-5678"
echo $user2->getPhoneNumber();    // "電話番号未登録"

// ロールの確認と追加
$user1->addRole('admin');
var_dump($user1->hasRole('admin')); // bool(true)

型宣言により、IDE の補完機能も効きやすくなり、開発効率が向上します。

実践例 2:設定管理システム

null 合体演算子とスプレッド構文を組み合わせた設定管理システムを作成しましょう。

設定クラスの定義

複数の設定ソースをマージする機能を持つクラスです。

php<?php
declare(strict_types=1);

class Config {
    private array $config = [];

    public function __construct(array ...$configs) {
        // スプレッド構文で複数の設定配列を受け取る
        $this->config = $this->mergeConfigs(...$configs);
    }
}

コンストラクタで可変長引数を使い、任意の数の設定配列を受け取れるようにしています。

設定のマージ処理

スプレッド構文を使って、複数の設定を再帰的にマージします。

php<?php
class Config {
    // ... 前述のコード

    private function mergeConfigs(array ...$configs): array {
        $merged = [];

        foreach ($configs as $config) {
            $merged = array_merge_recursive($merged, $config);
        }

        return $merged;
    }
}

array_merge_recursive() により、ネストした配列も適切にマージされます。

設定値の取得メソッド

ドット記法で階層化された設定値を取得し、null 合体演算子でデフォルト値を提供します。

php<?php
class Config {
    // ... 前述のコード

    public function get(string $key, mixed $default = null): mixed {
        // ドット記法で階層構造にアクセス (例: "database.host")
        $keys = explode('.', $key);
        $value = $this->config;

        foreach ($keys as $k) {
            if (!isset($value[$k])) {
                return $default;
            }
            $value = $value[$k];
        }

        return $value ?? $default;
    }
}

この実装により、database.host のような記法で深い階層の設定値にアクセスできますね。

配列全体の取得と更新

スプレッド構文を使って、既存の設定を保持しながら新しい設定を追加します。

php<?php
class Config {
    // ... 前述のコード

    public function all(): array {
        return $this->config;
    }

    public function merge(array $newConfig): void {
        // スプレッド構文で既存の設定を保持しながらマージ
        $this->config = [...$this->config, ...$newConfig];
    }
}

merge() メソッドは、既存の設定を上書きせずに新しい設定を追加できます。

使用例

実際の利用シーンを見てみましょう。

php<?php
// デフォルト設定
$defaultConfig = [
    'database' => [
        'host' => 'localhost',
        'port' => 3306,
        'charset' => 'utf8mb4'
    ],
    'app' => [
        'debug' => false,
        'timezone' => 'Asia/Tokyo'
    ]
];

// 環境固有の設定
$envConfig = [
    'database' => [
        'host' => 'db.production.com',
        'password' => 'secret123'
    ]
];

デフォルト設定と環境固有の設定を分離しています。

php<?php
// 設定オブジェクトの生成(複数の設定をマージ)
$config = new Config($defaultConfig, $envConfig);

// ドット記法で設定値を取得
echo $config->get('database.host');     // "db.production.com"
echo $config->get('database.port');     // 3306
echo $config->get('app.debug');         // false

// 存在しないキーはデフォルト値を返す
echo $config->get('cache.driver', 'file'); // "file"

null 合体演算子により、安全にデフォルト値を提供できていますね。

実践例 3:API レスポンスビルダー

スプレッド構文と型宣言を活用した、柔軟な API レスポンス生成クラスを実装します。

レスポンスビルダークラスの基本構造

php<?php
declare(strict_types=1);

class ApiResponseBuilder {
    private array $data = [];
    private int $statusCode = 200;
    private array $headers = [];
    private ?string $errorMessage = null;
}

各プロパティに適切な型を宣言し、null 許容型も活用しています。

データ設定メソッド

メソッドチェーンを可能にするため、戻り値の型に self を指定します。

php<?php
class ApiResponseBuilder {
    // ... プロパティ宣言

    public function setData(array $data): self {
        $this->data = $data;
        return $this;
    }

    public function addData(string $key, mixed $value): self {
        $this->data[$key] = $value;
        return $this;
    }
}

self を返すことで、$builder->setData()->addData() のようなメソッドチェーンが可能になります。

スプレッド構文を使ったデータマージ

複数のデータソースを柔軟にマージします。

php<?php
class ApiResponseBuilder {
    // ... 前述のコード

    public function mergeData(array ...$datasets): self {
        // 複数の配列をスプレッド構文でマージ
        foreach ($datasets as $dataset) {
            $this->data = [...$this->data, ...$dataset];
        }
        return $this;
    }
}

可変長引数とスプレッド構文の組み合わせにより、任意の数のデータセットをマージできますね。

ステータスコードとヘッダーの設定

php<?php
class ApiResponseBuilder {
    // ... 前述のコード

    public function setStatusCode(int $statusCode): self {
        $this->statusCode = $statusCode;
        return $this;
    }

    public function addHeader(string $name, string $value): self {
        $this->headers[$name] = $value;
        return $this;
    }

    public function setError(string $message, int $statusCode = 400): self {
        $this->errorMessage = $message;
        $this->statusCode = $statusCode;
        return $this;
    }
}

エラー設定メソッドでは、デフォルトのステータスコードを 400 に設定しています。

レスポンスの構築

null 合体演算子を使って、安全にレスポンスを生成します。

php<?php
class ApiResponseBuilder {
    // ... 前述のコード

    public function build(): array {
        $response = [
            'success' => $this->errorMessage === null,
            'statusCode' => $this->statusCode,
        ];

        if ($this->errorMessage !== null) {
            $response['error'] = $this->errorMessage;
        } else {
            $response['data'] = $this->data;
        }

        return $response;
    }
}

エラーの有無によって、レスポンスの構造を変えています。

JSON レスポンスの送信

php<?php
class ApiResponseBuilder {
    // ... 前述のコード

    public function send(): void {
        // ヘッダーの送信
        foreach ($this->headers as $name => $value) {
            header("{$name}: {$value}");
        }

        header('Content-Type: application/json', true, $this->statusCode);

        // JSON レスポンスの出力
        echo json_encode($this->build(), JSON_UNESCAPED_UNICODE);
    }
}

void 型により、このメソッドが値を返さないことが明確になりますね。

使用例:成功レスポンス

実際の API エンドポイントでの使用例を見てみましょう。

php<?php
// ユーザー情報の取得 API
$userData = [
    'id' => 123,
    'name' => '山田太郎'
];

$metaData = [
    'timestamp' => time(),
    'version' => '1.0'
];

$response = (new ApiResponseBuilder())
    ->setData($userData)
    ->mergeData($metaData)
    ->addHeader('X-Api-Version', '1.0')
    ->build();

print_r($response);

メソッドチェーンにより、読みやすく直感的なコードになっています。

出力結果:

phpArray
(
    [success] => 1
    [statusCode] => 200
    [data] => Array
        (
            [id] => 123
            [name] => 山田太郎
            [timestamp] => 1699999999
            [version] => 1.0
        )
)

使用例:エラーレスポンス

エラー時のレスポンス生成も簡潔です。

php<?php
// バリデーションエラーの場合
$response = (new ApiResponseBuilder())
    ->setError('メールアドレスの形式が正しくありません', 422)
    ->addHeader('X-Api-Version', '1.0')
    ->build();

print_r($response);

出力結果:

phpArray
(
    [success] =>
    [statusCode] => 422
    [error] => メールアドレスの形式が正しくありません
)

型宣言により、ステータスコードには必ず整数が設定されることが保証されていますね。

実践例 4:クエリビルダー

型宣言とスプレッド構文を組み合わせた、シンプルなクエリビルダーを実装しましょう。

クエリビルダークラスの定義

php<?php
declare(strict_types=1);

class QueryBuilder {
    private string $table = '';
    private array $columns = ['*'];
    private array $wheres = [];
    private array $bindings = [];
    private ?int $limitValue = null;
    private ?int $offsetValue = null;
}

各プロパティに適切な型を宣言し、null 許容型も活用しています。

テーブルとカラムの指定

php<?php
class QueryBuilder {
    // ... プロパティ宣言

    public function table(string $table): self {
        $this->table = $table;
        return $this;
    }

    public function select(string ...$columns): self {
        // スプレッド構文で任意の数のカラムを受け取る
        $this->columns = $columns;
        return $this;
    }
}

select() メソッドは可変長引数により、select('id', 'name', 'email') のように呼び出せます。

WHERE 句の構築

php<?php
class QueryBuilder {
    // ... 前述のコード

    public function where(string $column, string $operator, mixed $value): self {
        $this->wheres[] = "{$column} {$operator} ?";
        $this->bindings[] = $value;
        return $this;
    }

    public function whereIn(string $column, array $values): self {
        $placeholders = implode(', ', array_fill(0, count($values), '?'));
        $this->wheres[] = "{$column} IN ({$placeholders})";

        // スプレッド構文で配列をマージ
        $this->bindings = [...$this->bindings, ...$values];
        return $this;
    }
}

whereIn() メソッドでは、スプレッド構文を使ってバインディング配列を効率的にマージしていますね。

LIMIT と OFFSET の設定

php<?php
class QueryBuilder {
    // ... 前述のコード

    public function limit(int $limit): self {
        $this->limitValue = $limit;
        return $this;
    }

    public function offset(int $offset): self {
        $this->offsetValue = $offset;
        return $this;
    }
}

型宣言により、limit と offset には必ず整数が渡されることが保証されます。

SQL クエリの生成

null 合体演算子を使って、オプション句を安全に処理します。

php<?php
class QueryBuilder {
    // ... 前述のコード

    public function toSql(): string {
        $columns = implode(', ', $this->columns);
        $sql = "SELECT {$columns} FROM {$this->table}";

        // WHERE 句の追加
        if (count($this->wheres) > 0) {
            $sql .= ' WHERE ' . implode(' AND ', $this->wheres);
        }

        // LIMIT 句の追加(null の場合は追加しない)
        if ($this->limitValue !== null) {
            $sql .= " LIMIT {$this->limitValue}";
        }

        // OFFSET 句の追加(null の場合は追加しない)
        if ($this->offsetValue !== null) {
            $sql .= " OFFSET {$this->offsetValue}";
        }

        return $sql;
    }
}

null チェックにより、オプション句を柔軟に制御できていますね。

バインディングパラメータの取得

php<?php
class QueryBuilder {
    // ... 前述のコード

    public function getBindings(): array {
        return $this->bindings;
    }
}

使用例

実際にクエリビルダーを使ってみましょう。

php<?php
// 基本的なクエリ
$query = (new QueryBuilder())
    ->table('users')
    ->select('id', 'name', 'email')
    ->where('age', '>=', 20)
    ->where('status', '=', 'active')
    ->limit(10)
    ->offset(0);

echo $query->toSql();
// SELECT id, name, email FROM users WHERE age >= ? AND status = ? LIMIT 10 OFFSET 0

print_r($query->getBindings());
// [20, 'active']

メソッドチェーンにより、SQL の構造が直感的に理解できますね。

php<?php
// IN 句を使ったクエリ
$query = (new QueryBuilder())
    ->table('products')
    ->select('id', 'name', 'price')
    ->whereIn('category_id', [1, 2, 3])
    ->limit(20);

echo $query->toSql();
// SELECT id, name, price FROM products WHERE category_id IN (?, ?, ?) LIMIT 20

print_r($query->getBindings());
// [1, 2, 3]

スプレッド構文により、配列の値が適切にバインディングパラメータに展開されています。

以下の図は、クエリビルダーのメソッド呼び出しフローを示しています。

mermaidflowchart LR
  start["new QueryBuilder()"] --> table["table('users')"]
  table --> select["select('id', 'name')"]
  select --> where["where('age', '>=', 20)"]
  where --> limit["limit(10)"]
  limit --> build["toSql()"]
  build --> result["SQL クエリ文字列"]

  bindings["getBindings()"] --> params["バインディングパラメータ"]

これらの実践例を通じて、型宣言・null 合体演算子・スプレッド構文の組み合わせが、いかに強力であるかが分かりますね。

まとめ

本記事では、PHP のモダン構文である型宣言・null 合体演算子・スプレッド構文について、90 分で習得できるよう実践的に解説しました。

学習した内容の要点

型宣言による型安全性の確保

スカラー型宣言、null 許容型、Union 型を使うことで、実行前に型エラーを検出できるようになります。これにより、バグの早期発見と IDE の補完機能の向上が実現されますね。

null 合体演算子による簡潔な null 処理

????= 演算子を使うことで、冗長な null チェックから解放されます。コードの可読性が大幅に向上し、メンテナンス性も改善されるでしょう。

スプレッド構文による柔軟な配列・引数操作

... 構文により、配列の結合や可変長引数の処理が直感的になります。従来の関数を使うよりも、コードの意図が明確になりますね。

実務での活用ポイント

#ポイント効果
1新規コードは必ず型宣言を使用バグの早期発見、IDE 補完の向上
2設定値取得には null 合体演算子デフォルト値の簡潔な指定
3配列操作はスプレッド構文を優先コードの可読性と直感性の向上
4既存コードも段階的にリファクタリングレガシーコードの品質改善

これらの構文は、単独で使うよりも組み合わせることで、より大きな効果を発揮します。実務では、これら 3 つの構文を意識的に活用し、コード品質を継続的に改善していくことが重要ですね。

次のステップ

本記事で学んだ基本構文をベースに、以下のような発展的なトピックにも挑戦してみてください。

PHP 8.0 以降の Named Arguments や Attributes を学ぶことで、さらにモダンな PHP コードが書けるようになるでしょう。また、静的解析ツール(PHPStan、Psalm)を導入することで、型宣言の恩恵を最大限に受けられます。

ぜひ、今日から実践的なコードに取り入れて、PHP 開発の生産性を向上させていきましょう。

関連リンク