あっぽログ
← 記事一覧に戻る

PHPのSplPriorityQueueを使いこなす:優先度付きキューで柔軟なタスク管理を実現する

SplPriorityQueueとは

PHPの標準ライブラリ(SPL)には、さまざまなデータ構造が用意されています。その中の一つが SplPriorityQueue です。

通常のキューは「先入れ先出し(FIFO)」ですが、優先度付きキューは要素に優先度を設定し、優先度の高い順に取り出せるデータ構造です。たとえばタスク管理システムで「緊急」「通常」「低優先度」のようにタスクを分類し、緊急のものから処理するといった用途に適しています。

SplMinHeap / SplMaxHeap は単純な最小・最大ヒープですが、SplPriorityQueue は要素と優先度を別々に指定できる点が特徴です。


基本的な使い方

要素の挿入と取り出し

insert(値, 優先度) で要素を追加し、extract() で優先度の高い順に取り出します。

<?php

$queue = new SplPriorityQueue();

$queue->insert('低優先度タスク', 1);
$queue->insert('高優先度タスク', 10);
$queue->insert('中優先度タスク', 5);

echo "キューのサイズ: " . $queue->count() . PHP_EOL;

while (!$queue->isEmpty()) {
    echo $queue->extract() . PHP_EOL;
}

実行結果:

キューのサイズ: 3
高優先度タスク
中優先度タスク
低優先度タスク

優先度の数値が大きいほど先に取り出されます。これはデフォルトで最大ヒープの動作です。


取り出しモードの変更

setExtractFlags() で取り出す情報を制御できます。

<?php

$queue = new SplPriorityQueue();

$queue->insert('タスクA', 3);
$queue->insert('タスクB', 7);
$queue->insert('タスクC', 1);

// 値と優先度の両方を取得する
$queue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);

while (!$queue->isEmpty()) {
    $item = $queue->extract();
    echo "値: {$item['data']}, 優先度: {$item['priority']}" . PHP_EOL;
}

実行結果:

値: タスクB, 優先度: 7
値: タスクA, 優先度: 3
値: タスクC, 優先度: 1

利用できるフラグは以下の3種類です。

フラグ取得内容
EXTR_DATA値のみ(デフォルト)
EXTR_PRIORITY優先度のみ
EXTR_BOTH値と優先度の両方

実践例:タスクスケジューラの実装

実際のユースケースとして、優先度付きタスクスケジューラを実装してみましょう。

<?php

class Task
{
    public function __construct(
        public readonly string $name,
        public readonly string $description,
        public readonly \DateTimeImmutable $createdAt = new \DateTimeImmutable(),
    ) {}

    public function __toString(): string
    {
        return "[{$this->name}] {$this->description}";
    }
}

class TaskScheduler
{
    private SplPriorityQueue $queue;

    public function __construct()
    {
        $this->queue = new SplPriorityQueue();
        // 同優先度の場合は挿入順を保持するために EXTR_BOTH を使う
        $this->queue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
    }

    public function addTask(Task $task, int $priority): void
    {
        $this->queue->insert($task, $priority);
        echo "追加: {$task->name} (優先度: {$priority})" . PHP_EOL;
    }

    public function process(): void
    {
        echo PHP_EOL . "=== タスク処理開始 ===" . PHP_EOL;

        if ($this->queue->isEmpty()) {
            echo "処理するタスクがありません。" . PHP_EOL;
            return;
        }

        while (!$this->queue->isEmpty()) {
            $item = $this->queue->extract();
            $task = $item['data'];
            $priority = $item['priority'];

            echo "処理中 (優先度: {$priority}): {$task}" . PHP_EOL;
        }
    }

    public function count(): int
    {
        return $this->queue->count();
    }
}

// 使用例
$scheduler = new TaskScheduler();

$scheduler->addTask(new Task('メール送信', 'ユーザーへの確認メールを送る'), 5);
$scheduler->addTask(new Task('DB バックアップ', 'データベースの定期バックアップ'), 2);
$scheduler->addTask(new Task('エラー通知', '本番障害のアラートを通知する'), 10);
$scheduler->addTask(new Task('レポート生成', '月次レポートを作成する'), 3);
$scheduler->addTask(new Task('キャッシュ削除', '不要なキャッシュを削除する'), 2);

echo PHP_EOL . "タスク件数: " . $scheduler->count() . PHP_EOL;

$scheduler->process();

実行結果:

追加: メール送信 (優先度: 5)
追加: DB バックアップ (優先度: 2)
追加: エラー通知 (優先度: 10)
追加: レポート生成 (優先度: 3)
追加: キャッシュ削除 (優先度: 2)

タスク件数: 5

=== タスク処理開始 ===
処理中 (優先度: 10): [エラー通知] 本番障害のアラートを通知する
処理中 (優先度: 5): [メール送信] ユーザーへの確認メールを送る
処理中 (優先度: 3): [レポート生成] 月次レポートを作成する
処理中 (優先度: 2): [DB バックアップ] データベースの定期バックアップ
処理中 (優先度: 2): [キャッシュ削除] 不要なキャッシュを削除する

注意点

同じ優先度の順序は保証されない

SplPriorityQueue は内部的にヒープ構造を使っています。同じ優先度を持つ要素の取り出し順序は保証されません。 厳密に挿入順を保持したい場合は、優先度をタプル(配列)で表現する方法が有効です。

<?php

$queue = new SplPriorityQueue();

// [優先度, 挿入順(降順で大きいほど先)] の形式で指定
$queue->insert('タスク1', [5, 3]);
$queue->insert('タスク2', [5, 2]);
$queue->insert('タスク3', [5, 1]);

while (!$queue->isEmpty()) {
    echo $queue->extract() . PHP_EOL;
}
// タスク1 → タスク2 → タスク3 の順に出力される

PHPの配列比較は辞書順に行われるため、[5, 3] > [5, 2] > [5, 1] となり、挿入順を再現できます。

クローン時の注意

SplPriorityQueue はオブジェクトなので、コピーが必要な場合は clone を使います。ただし、clone してもキューの内部状態はコピーされるため、どちらかで extract() するともう一方には影響しません。


まとめ

SplPriorityQueue のポイントをまとめます。

  • insert(値, 優先度) で要素を追加し、優先度の高い順に取り出せる
  • setExtractFlags() で値・優先度・両方のいずれを取得するか選べる
  • 同一優先度の順序保証が必要な場合は優先度を配列で指定するテクニックが使える
  • タスクスケジューラ・イベント処理・ダイクストラ法など幅広い場面で活用できる

SplMinHeap / SplMaxHeap が単純な値の順序管理に向いているのに対し、SplPriorityQueue値と優先度を分離して管理できる点が強みです。優先度付きの処理が必要なときはぜひ活用してみてください。

← 記事一覧に戻る