【PHP8.x】SplSubject::notify()メソッドの使い方
notifyメソッドの使い方について、初心者にもわかりやすく解説します。
基本的な使い方
notifyメソッドは、SplSubjectインターフェースを実装するクラスにおいて、そのオブジェクトの状態が変化した際に、登録されている全てのオブザーバーに通知を行うメソッドです。このメソッドは、GoFデザインパターンのObserverパターンにおけるSubject(被監視者)の役割を果たすオブジェクトが、自身を監視しているObserver(監視者)へ状態の変更を伝えるために利用されます。
具体的には、SplSubjectを実装したクラスのインスタンスで何らかの重要なイベントが発生したり、保持しているデータが更新されたりしたときに、開発者が明示的にnotifyメソッドを呼び出します。notifyメソッドが実行されると、事前にこのSubjectにattachメソッドで登録されていた全てのSplObserverオブジェクトのupdateメソッドが自動的に順次呼び出されます。これにより、Subjectは自身の状態変化をObserverに一斉に伝え、Observerはそれを受けて適切な処理を実行することができます。
この仕組みは、SubjectとObserverの間を疎結合に保ちながら、イベント発生時の連携を可能にします。ObserverはSubjectの内部状態を直接監視することなく、通知を介して必要な情報を受け取ることができるため、システムの柔軟性や拡張性が向上します。主にGUIイベント処理、データベースの状態変更通知、または複数のコンポーネントが同一のオブジェクトの状態に依存するような設計において、その真価を発揮します。
構文(syntax)
1abstract public function notify(): void;
引数(parameters)
引数なし
引数はありません
戻り値(return)
戻り値なし
戻り値はありません
サンプルコード
PHP SplSubject notifyでLINE通知する
1<?php 2 3declare(strict_types=1); 4 5/** 6 * 監視される側(Subject)を表現するクラスです。 7 * SplSubjectインターフェースを実装し、Observer(監視者)の登録・解除・通知を行います。 8 * 状態が変更された際に、登録されているすべてのObserverにnotify()メソッドを通じて通知します。 9 */ 10class EventMonitor implements SplSubject 11{ 12 /** 13 * @var SplObserver[] 登録されているObserverのリスト 14 */ 15 private array $observers = []; 16 17 /** 18 * @var string 現在の監視対象の状態 19 */ 20 private string $currentState = '初期状態'; 21 22 /** 23 * Observerを登録します。 24 * 25 * @param SplObserver $observer 登録するObserverインスタンス 26 */ 27 public function attach(SplObserver $observer): void 28 { 29 $this->observers[] = $observer; 30 echo "[EventMonitor] Observerが登録されました。\n"; 31 } 32 33 /** 34 * Observerの登録を解除します。 35 * 36 * @param SplObserver $observer 解除するObserverインスタンス 37 */ 38 public function detach(SplObserver $observer): void 39 { 40 foreach ($this->observers as $key => $obs) { 41 if ($obs === $observer) { 42 unset($this->observers[$key]); 43 echo "[EventMonitor] Observerが解除されました。\n"; 44 return; 45 } 46 } 47 } 48 49 /** 50 * 登録されているすべてのObserverに通知します。 51 * SplSubject::notify() の要件通り、引数なし、戻り値なしです。 52 * Observerのupdate()メソッドを呼び出し、自身のインスタンスを渡します。 53 */ 54 public function notify(): void 55 { 56 echo "[EventMonitor] 全てのObserverに通知しています...\n"; 57 foreach ($this->observers as $observer) { 58 $observer->update($this); 59 } 60 } 61 62 /** 63 * 監視対象の状態を変更し、Observerに通知します。 64 * このメソッド内でnotify()が呼び出されることで、状態変化がObserverに伝わります。 65 * 66 * @param string $newState 新しい状態 67 */ 68 public function changeState(string $newState): void 69 { 70 $this->currentState = $newState; 71 echo "[EventMonitor] 状態が「" . $this->currentState . "」に変更されました。\n"; 72 $this->notify(); // 状態変更後に通知を発行 73 } 74 75 /** 76 * 現在の状態を取得します。 77 * Observerが通知を受け取った際に、Subjectのどの状態変化かを判断するために利用されます。 78 * 79 * @return string 現在の状態 80 */ 81 public function getCurrentState(): string 82 { 83 return $this->currentState; 84 } 85} 86 87/** 88 * 監視する側(Observer)を表現し、LINE通知を模倣するクラスです。 89 * SplObserverインターフェースを実装し、Subjectからの通知を受け取ります。 90 */ 91class LineNotifierObserver implements SplObserver 92{ 93 /** 94 * Subjectからの通知を受け取った際に実行されるメソッドです。 95 * このメソッド内で、LINEに通知を送る処理を模倣します。 96 * 97 * @param SplSubject $subject 通知を発行したSubjectインスタンス 98 */ 99 public function update(SplSubject $subject): void 100 { 101 // 実際のLINE通知API呼び出しの代わりに、メッセージを出力します。 102 // ここにLINE Notify APIを呼び出すHTTPリクエストなどのコードを記述します。 103 // 例: 104 // $message = "システムイベント発生: " . $subject->getCurrentState(); 105 // sendLineNotification($message); // 独自のLINE通知関数を呼び出す 106 107 if ($subject instanceof EventMonitor) { 108 echo "[LINE Notify] 通知を受信しました!システム状態が「" . $subject->getCurrentState() . "」になりました。\n"; 109 } else { 110 echo "[LINE Notify] 不明なSubjectからの通知です。\n"; 111 } 112 } 113} 114 115// --- ここからサンプルコードの実行部分 --- 116 117// イベントを監視するSubjectのインスタンスを作成 118$monitor = new EventMonitor(); 119 120// LINE通知を行うObserverのインスタンスを作成 121$lineNotifier = new LineNotifierObserver(); 122 123// ObserverをSubjectに登録 124$monitor->attach($lineNotifier); 125 126echo "\n--- システムの状態を変化させます ---\n"; 127 128// Subjectの状態を変更すると、登録されたObserverに自動的に通知されます。 129$monitor->changeState("サーバー起動"); 130 131echo "\n--- さらに状態を変化させます ---\n"; 132 133$monitor->changeState("処理実行中"); 134 135echo "\n--- 異常が発生したと仮定します ---\n"; 136 137$monitor->changeState("エラー発生!要確認"); 138 139// Observerの登録解除も可能です 140// $monitor->detach($lineNotifier); 141// echo "\n--- Observerを解除しました ---\n"; 142// $monitor->changeState("復旧"); // この状態変化ではLINE通知は行われません
SplSubject::notify()メソッドは、PHPの標準機能として提供されるObserver(監視者)パターンの一部で、監視される側(Subject)が自身の状態変化を監視者たちに通知するために利用されます。このメソッドは引数を一切取らず、また特定の値を返すこともありません。
サンプルコードでは、システムイベントを監視するEventMonitorクラスがSplSubjectインターフェースを実装し、notify()メソッドを実装しています。EventMonitorの状態がchangeState()メソッドによって「サーバー起動」や「エラー発生」といった新しい状態に変わるたびに、内部でnotify()メソッドが呼び出されます。
notify()が実行されると、EventMonitorに事前に登録されているすべての監視者(Observer)に対し、それぞれのupdate()メソッドが自動的に実行されます。今回の例では、LINE通知を模倣するLineNotifierObserverが監視者として登録されており、update()メソッドを通じて「システム状態が『〇〇』になりました」といったメッセージが出力されます。
このように、notify()メソッドを使うことで、システムの状態変化に応じて、LINE通知のような外部サービスへの連携処理を柔軟にトリガーできる仕組みを構築できます。
SplSubject::notifyメソッドは、監視対象(Subject)の状態が変化した際に、登録されている全ての監視者(Observer)にその変化を伝える役割を担います。このメソッドは引数も戻り値も持たず、インターフェースの規約を厳守する必要があります。実際に通知を受け取り、具体的な処理を行うのはObserver側のupdateメソッドです。サンプルコードのLineNotifierObserverはLINE通知を模倣していますが、実際にLINEへメッセージを送る場合は、updateメソッド内にLINE Notify APIを呼び出すHTTPリクエストなどの実装が必要です。その際、APIキーの安全な管理や、ネットワーク障害などに対するエラーハンドリングも適切に考慮してください。この設計は、システムの特定イベント発生時に複数の処理を柔軟に連携させたい場合に有効です。
PHPでPostgreSQLのLISTEN/NOTIFYをSplSubject::notify()で処理する
1<?php 2 3/** 4 * プログラミング言語リファレンス情報: SplSubject::notify() 5 * キーワード: php postgresql listen notify 6 * 7 * このサンプルコードは、PostgreSQLのLISTEN/NOTIFY機能を使用して 8 * データベースからの通知をPHPアプリケーションで受け取り、 9 * その通知をアプリケーション内部のオブザーバーパターン(SplSubject/SplObserver)で処理する方法を示します。 10 * 11 * 想定されるシナリオ: 12 * 1. PHPアプリケーションがPostgreSQLデータベースに接続し、特定のチャネルを「LISTEN」する。 13 * 2. データベース側で何らかのイベント(例: 新しいデータが挿入されたトリガーなど)が発生し、 14 * 「NOTIFY」コマンドでそのチャネルに通知を送る。 15 * 3. PHPアプリケーションがその通知を受け取り、アプリケーション内部で登録されたオブザーバー(監視者)に 16 * そのイベントをさらに「通知」する(SplSubject::notify() の役割)。 17 * 18 * 動作にはPostgreSQLデータベースと、PHPのpg_connectなどのPostgreSQL拡張が必要です。 19 * 以下の環境変数を設定するか、直接コード内の接続情報 (`$dsn`) を実際のデータベース情報に変更してください。 20 * PG_HOST, PG_PORT, PG_DBNAME, PG_USER, PG_PASSWORD 21 * 22 * PostgreSQL側でのNOTIFYコマンドの発行例: 23 * SELECT pg_notify('my_channel', '{"event": "データ更新", "id": 123}'); 24 * 25 */ 26 27// ------------------------------------------------------------------------- 28// 1. オブザーバー(監視者)の定義 29// => Subjectからの通知を受け取り、具体的な処理を実行する役割 30// ------------------------------------------------------------------------- 31// SplObserverインターフェースを実装することで、Subjectからの通知を受け取れるようになります。 32class MyPostgresObserver implements SplObserver 33{ 34 private string $name; 35 36 public function __construct(string $name) 37 { 38 $this->name = $name; 39 } 40 41 /** 42 * Subjectから通知を受け取った際に実行されるメソッドです。 43 * SplSubjectインスタンスを通じて、通知元の情報を取得できます。 44 * 45 * @param SplSubject $subject 通知を発行したSubjectのインスタンス 46 */ 47 public function update(SplSubject $subject): void 48 { 49 // $subjectがPostgresEventSubjectのインスタンスであることを前提とする 50 if ($subject instanceof PostgresEventSubject) { 51 $notificationData = $subject->getLastNotificationData(); 52 echo sprintf( 53 "[%s] Observer '%s' received notification: %s\n", 54 date('Y-m-d H:i:s'), 55 $this->name, 56 json_encode($notificationData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) 57 ); 58 // ここで通知されたデータ ($notificationData) を使用して、 59 // ログ記録、キャッシュクリア、他のシステムへの連携などの具体的な処理を行います。 60 } else { 61 echo sprintf( 62 "[%s] Observer '%s' received a notification from an unknown subject.\n", 63 date('Y-m-d H:i:s'), 64 $this->name 65 ); 66 } 67 } 68} 69 70// ------------------------------------------------------------------------- 71// 2. サブジェクト(被監視者・通知元)の定義 72// => Observerを登録・解除し、状態変化があった際にObserverに通知する役割 73// ------------------------------------------------------------------------- 74// SplSubjectインターフェースを実装することで、Observerを登録・解除し、通知を発行できます。 75class PostgresEventSubject implements SplSubject 76{ 77 /** @var SplObserver[] 登録されているオブザーバーのリスト */ 78 private array $observers = []; 79 80 /** @var array|null 最後に受け取ったPostgreSQLからの通知データ */ 81 private ?array $lastNotificationData = null; 82 83 /** 84 * オブザーバーを登録します。 85 * 86 * @param SplObserver $observer 登録するオブザーバーのインスタンス 87 */ 88 public function attach(SplObserver $observer): void 89 { 90 $hash = spl_object_hash($observer); 91 if (!isset($this->observers[$hash])) { 92 $this->observers[$hash] = $observer; 93 echo "Observer attached.\n"; 94 } 95 } 96 97 /** 98 * オブザーバーの登録を解除します。 99 * 100 * @param SplObserver $observer 解除するオブザーバーのインスタンス 101 */ 102 public function detach(SplObserver $observer): void 103 { 104 $hash = spl_object_hash($observer); 105 if (isset($this->observers[$hash])) { 106 unset($this->observers[$hash]); 107 echo "Observer detached.\n"; 108 } 109 } 110 111 /** 112 * 登録されている全てのオブザーバーに通知します。 113 * これがリファレンス情報である `SplSubject::notify()` メソッドの実装です。 114 * オブザーバーパターンにおける「通知」の核心となる部分です。 115 */ 116 public function notify(): void 117 { 118 echo "Notifying observers...\n"; 119 foreach ($this->observers as $observer) { 120 // 各オブザーバーの update() メソッドを呼び出し、自分自身 (Subject) を渡します。 121 $observer->update($this); 122 } 123 } 124 125 /** 126 * PostgreSQLから受け取った最新の通知データを設定します。 127 * このデータは、notify() メソッドを通じてオブザーバーに間接的に渡されます。 128 * 129 * @param array $data PostgreSQLのNOTIFYコマンドのペイロード 130 */ 131 public function setLastNotificationData(array $data): void 132 { 133 $this->lastNotificationData = $data; 134 } 135 136 /** 137 * 最新の通知データを取得します。 138 * オブザーバーは自身の update() メソッド内でこのデータを参照します。 139 * 140 * @return array|null 最新の通知データ、またはデータがない場合はnull 141 */ 142 public function getLastNotificationData(): ?array 143 { 144 return $this->lastNotificationData; 145 } 146} 147 148// ------------------------------------------------------------------------- 149// 3. メイン処理:PostgreSQLとの連携とオブザーバーパターンの利用 150// ------------------------------------------------------------------------- 151 152// PostgreSQL接続設定を環境変数から読み込むか、直接設定します。 153$host = getenv('PG_HOST') ?: 'localhost'; 154$port = getenv('PG_PORT') ?: '5432'; 155$dbname = getenv('PG_DBNAME') ?: 'testdb'; // 実際のデータベース名に変更してください 156$user = getenv('PG_USER') ?: 'testuser'; // 実際のユーザー名に変更してください 157$password = getenv('PG_PASSWORD') ?: 'testpass'; // 実際のパスワードに変更してください 158$channel = 'my_channel'; // PostgreSQLでLISTENするチャンネル名 159 160$dsn = sprintf("host=%s port=%s dbname=%s user=%s password=%s", 161 $host, $port, $dbname, $user, $password); 162 163// データベースへの接続を試みる 164$connection = pg_connect($dsn); 165 166if (!$connection) { 167 die("エラー: PostgreSQLデータベースに接続できませんでした。接続設定を確認してください。\n"); 168} 169 170echo "PostgreSQLデータベースに接続しました。\n"; 171 172// LISTENコマンドを発行 173// これにより、指定されたチャンネルでのNOTIFYイベントをリッスンします。 174// pg_query() で SQL を実行します。 175$listenResult = pg_query($connection, "LISTEN {$channel}"); 176if (!$listenResult) { 177 die("エラー: チャンネル '{$channel}' のリッスンを開始できませんでした。\n"); 178} 179echo "チャンネル '{$channel}' で通知を待機しています...\n"; 180 181// サブジェクトとオブザーバーを初期化 182$subject = new PostgresEventSubject(); 183$observer1 = new MyPostgresObserver('LoggerObserver'); 184$observer2 = new MyPostgresObserver('CacheManagerObserver'); 185 186// オブザーバーをサブジェクトに登録 187$subject->attach($observer1); 188$subject->attach($observer2); 189 190echo "\n--- PostgreSQLからの通知を待っています ---\n"; 191echo "PostgreSQLクライアントで以下のコマンドを実行してテストできます:\n"; 192echo " SELECT pg_notify('{$channel}', '{\"message\": \"hello from db\", \"timestamp\": \"" . date('Y-m-d H:i:s') . "\"}');\n"; 193echo " SELECT pg_notify('{$channel}', '{\"event_type\": \"user_registered\", \"user_id\": 1, \"username\": \"Alice\"}');\n"; 194echo "----------------------------------------\n"; 195 196// 無限ループでPostgreSQLからの通知をポーリングします。 197// pg_get_notify() は、通知があるまで処理をブロックします。 198while (true) { 199 // pg_get_notify() は、キューに通知がある場合にそれを返します。 200 // 通知がない場合は、新しい通知が来るまでプログラムの実行をブロックします。 201 $notification = pg_get_notify($connection); 202 203 if ($notification) { 204 echo "\n>>> PostgreSQL通知を受け取りました! <<< \n"; 205 206 // 通知のペイロード(データ部分)をJSONとしてパースを試みます。 207 $payload = json_decode($notification['payload'], true); 208 209 if (json_last_error() === JSON_ERROR_NONE) { 210 // 正常にJSONとしてパースできた場合 211 $subject->setLastNotificationData($payload); 212 } else { 213 // JSONでない、またはパースエラーの場合、生のペイロードをデータとして扱う 214 $subject->setLastNotificationData(['raw_payload' => $notification['payload']]); 215 echo "警告: 通知ペイロードが有効なJSONではありませんでした。生のデータで処理します。\n"; 216 } 217 218 // サブジェクトのnotify()メソッドを呼び出し、登録されている全てのオブザーバーに通知を転送します。 219 // これが `SplSubject::notify()` の具体的な利用例です。 220 $subject->notify(); 221 echo "----------------------------------------\n"; 222 // exit(0); // デバッグやテスト目的で、1回通知を受け取ったら終了したい場合はコメント解除 223 } 224 225 // pg_get_notify() はブロッキング関数なので、通常は追加のスリープは不要です。 226 // 通知が来ない間は、この関数がOSレベルで待機します。 227} 228 229// このサンプルコードは無限ループのため、通常はこの行には到達しませんが、 230// 実際のアプリケーションでは、プログラム終了時にデータベース接続を閉じることが重要です。 231pg_close($connection); 232 233?>
このサンプルコードは、PHPの標準機能であるSplSubjectインターフェースが提供するnotify()メソッドの利用方法を、PostgreSQLデータベースのLISTEN/NOTIFY機能との連携を通じて解説しています。
SplSubject::notify()メソッドは、デザインパターンの一つであるオブザーバーパターンにおいて中心的な役割を果たすメソッドです。これは、SplSubjectを実装したオブジェクト(被監視者)の状態に変化があった際、自身に登録されているすべてのオブザーバー(監視者)に対してその変化を通知するために呼び出されます。このメソッドは引数を一切受け取らず、また、戻り値もありません。その主な機能は、登録された各オブザーバーのupdate()メソッドを順次実行し、通知元であるSplSubject自身のインスタンスを引数として渡すことにあります。
具体的には、このコードではPostgreSQLデータベースから特定のチャネルへの通知を受け取ると、その通知内容がPostgresEventSubjectクラス(SplSubjectを実装)に設定されます。その後、$subject->notify()が呼び出されることで、PostgresEventSubjectに事前に登録された複数のMyPostgresObserverインスタンスのupdate()メソッドが実行されます。これにより、各オブザーバーはデータベースからのイベントに応じた独自の処理(例: ログ記録、キャッシュ更新)を実行し、外部からの通知をアプリケーション内部で柔軟に処理できる仕組みを実現しています。
このサンプルコードは、PostgreSQLからの通知をPHPのオブザーバーパターンで処理する、常駐プロセス(デーモン)としての利用を想定しています。データベース接続情報は、本番環境では環境変数などで安全に管理し、直接コードに書き込まないようご注意ください。SplSubject::notify()は、PostgreSQLから受け取ったデータを元に、登録された全てのオブザーバー(監視者)へ一斉に通知を転送し、具体的な処理を促す中心的な役割を担います。通知ペイロードはJSON形式を期待していますが、形式が異なる場合の堅牢なエラーハンドリングも重要です。Webリクエストの都度実行する通常のPHP処理とは異なり、while (true)による無限ループで継続的に動作する点に特にご留意ください。