PHPでライクサイクルと稼働中バージョンを表示する

(1/1)
Supported Versions によると、現在、PHPの各リリースブランチ(小数第1位のバージョン:例 PHP 8.5)は、最初の安定版から2年間の完全なサポート(アクティブサポート)を提供し、さらに2年間(合計4年間)のセキュリティサポートを受けられるというライフサイクルになっています。また、各リリースブランチについては、毎月、パッチバージョン(小数第2位のバージョン:例 8.5.4)がリリースされます。
そこで今回は、PHPを使って、公式サイトの Supported Versions や最新パッチバージョンのリリース情報をスクレイピングし、ライフサイクルを一覧に整理して表示するプログラムを作ってみます。公式サイトにアクセス不可を書けないようキャッシュシステムを利用し、また、自分自身のバージョンも表示させるようにする。取得した情報は、ローカル(サーバ)にある XMLデータファイルに保存する。

サンプル・プログラムの実行例

バージョン 安定版
リリース
最新
バージョン
最新リリース日 アクティブ
サポート終了
セキュリティ
サポート終了
8.5 2025-11-20 8.5.9 2026-07-30 2027-12-31 2029-12-31
8.4 2024-11-21 8.4.24 2026-07-30 2026-12-31 2028-12-31
8.3 2023-11-23 8.3.33 2026-07-30 2025-12-31 2027-12-31
8.2 2022-12-08 8.2.33 2026-07-30 2024-12-31 2026-12-31
8.1 2021-11-25 8.4.34 2025-12-18 2023-11-25 2025-12-31
8.0 2020-11-26 8.0.30 2023-08-03 2022-11-26 2023-11-26
7.4 2019-11-28 7.4.33 2022-11-03 2021-11-28 2022-11-28
7.3 2018-12-06 7.3.33 2021-11-18 2020-12-06 2021-12-06
7.2 2017-11-30 7.2.34 2020-10-01 2019-11-30 2020-11-30
7.1 2016-12-01 7.1.33 2019-10-24 2018-12-01 2019-12-01
7.0 2015-12-03 7.0.33 2018-12-06 2017-12-03 2019-12-03
5.6 2014-08-28 5.6.40 2019-01-10 2017-01-19 2018-12-31
5.5 2013-06-20 5.5.38 2016-07-21 2015-07-10 2016-07-10
5.4 2012-03-01 5.4.45 2015-09-03 2014-09-14 2015-09-14
5.3 2009-06-30 5.3.29 2014-08-14 - 2014-08-14
5.2 2006-11-02 5.2.17 2011-01-06 - 2011-01-06
5.1 2005-11-24 5.1.6 2006-08-24 - 2006-08-24
5.0 2004-07-13 5.0.5 2005-09-05 - 2005-09-05
4.4 2005-07-11 4.4.9 2008-08-07 - 2007-08-07
4.3 2002-12-27 4.3.11 2005-03-31 - 2005-03-31
4.2 2002-04-22 4.2.3 2002-09-06 - 2002-09-06
4.1 2001-12-10 4.1.2 2002-02-27 - 2002-03-12
4.0 2000-05-22 4.0.6 2001-06-23 - 2001-06-23
3.0 1998-06-06 3.0.18 2000-10-20 - 2000-01-20
2.0 1997-11-01 - - - -
1.0 1995-06-08 - - - -

目次

サンプル・プログラムのダウンロード

圧縮ファイルの内容
lifecyclePHP.phpサンプル・プログラム本体
phpVersion.xmlPHPのライフサイクルを格納したデータベース・ファイル
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooScraping.phpスクレイピング処理に関わるクラス pahooScraping。
スクレイピング処理に関わるクラスの使い方は「PHPで作るRSSビューア」を参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
lifecyclePHP.php 更新履歴
バージョン 更新日 内容
1.0.1 2026/03/30 修正:

1.0.0 2026/03/22 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加
pahooScraping.php 更新履歴
バージョン 更新日 内容
1.2.1 2024/10/31 __construct() 文字化け対策
1.2.0 2024/09/29 getValueFistrXPath() 属性値でない指定に対応
1.1.0 2023/10/15 getValueFistrXPath() 追加
1.0.1 2023/09/29 __construct() bug-fix
1.0.0 2023/09/18 初版
pahooCache.php 更新履歴
バージョン 更新日 内容
1.3.0 2025/12/06 PHP8.5対応:curl_closeを使わない
1.2.0 2025/09/06 cLoad() HTTPヘッダを送信できるようにした
1.1.3 2025/08/10 var→public
1.1.2 2023/07/22 bug-fix
1.1.1 2023/02/11 コメント追記

サンプル・プログラムの流れ

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

準備:pahooCache クラス

pahooCache.php

  13: class pahooCache {
  14:     const LIFE_CACHE = (2 * 60);        // キャッシュ保持時間(デフォルト;分)
  15:     const DEF_DIRCACHE = './pcache/';   // キャッシュ・ディレクトリ(デフォルト)
  16: 
  17:     public $lifeCache;      // キャッシュ保持時間(分)(0:キャッシュしない)
  18:     public $dirCache;       // キャッシュ用ディレクトリ
  19:     public $httpHeader;     // HTTPヘッダ(空文字の時は何も送らない)
  20:     public $error;          // エラーフラグ
  21:     public $errmsg;         // エラーメッセージ
  22:     public $debug;          // デバッグ用ファイル名
  23: 
  24: /**
  25:  * コンストラクタ
  26:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  27:  * @param   int    $life キャッシュ保持時間(分)(省略可能)
  28:  * @param   string $dir キャッシュ・ディレクトリ(省略可能)
  29:  * @param   array  $httpHeader httpヘッダに渡す配列(省略可能)
  30:  *          USER AGENT偽装に用いることを想定
  31:  *          (例)
  32:  *           array(
  33:  *              'User-Agent: Mozilla/5.0(Windows NT 10.0; Win64; x64) pahooAppy/pahoo.org AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
  34:  *              'Accept-Language: ja-JP'
  35:  *          );
  36:  * @return  なし
  37: */
  38: function __construct($life=self::LIFE_CACHE, $dir=self::DEF_DIRCACHE, $httpHeader=NULL) {
  39:     if ($life < 0) {
  40:         $life = 0;
  41:     }
  42:     if (preg_match('/\/$/ui', $dir) == 0) {
  43:         $dir = $dir . '/';
  44:     }
  45:     $this->error      = FALSE;
  46:     $this->errmsg     = '';
  47:     $this->debug      = '';
  48:     $this->lifeCache  = $life;
  49:     $this->dirCache   = $dir;
  50:     $this->httpHeader = $httpHeader;
  51: 
  52:     // PHP5以上であることを調べる.
  53:     if (! $this->isphp5over()) {
  54:         $this->error   = TRUE;
  55:         $this->errmsg  = '動作にはPHP5以上が必要です';
  56:         return;
  57:     }
  58: 
  59:     // キャッシュ・ディレクトリが無ければ作成する.
  60:     if (! is_dir($this->dirCache)) {
  61:         $res = mkdir($this->dirCache, 0744);
  62:         if ($res == FALSE) {
  63:             $this->error   = TRUE;
  64:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
  65:             return;
  66:         }
  67:     }
  68: }

クラウド連携や相手先サイトの公開データを利用する際、こちらのアプリを起動する都度、APIを呼び出したりデータをダウンロードするのでは、相手サーバに負荷をかけてしまう。
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが pahooCacheクラス である。同梱のクラス・ファイル "pahooCache.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooCacheクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

pahooCacheクラス の注意ポイントは、キャッシュ時間(単位:分)とキャッシュを保存するディレクトリをコンストラクタで指定している点だ。これらはプログラムによって変わるものである。インスタンス化するときの値は、pahooCacheクラス を利用するメイン・プログラムの方で解説する。

サイトによっては、User-Agent などを必要とすることがあるだろう。そこで、第3引数に HTTPヘッダ として送信するデータを配列で渡すことができるようにした。配列の構造はコメントを参照していただきたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooScraping クラス

pahooScraping.php

  13: class pahooScraping {
  14:     private $dom;           //DOMDocument
  15:     private $xpath;         //DOMXPath
  16:     private $language;      //使用言語(初期値は日本語)
  17:     private $error;         //エラー・メッセージ
  18:     private $errorTable;    //エラー番号 => エラー・メッセージ 一覧
  19: 
  20: /**
  21:  * コンストラクタ
  22:  * @param   string $contents 解析対象テキスト(UTF-8)
  23:  * @return  object /NULL:PHP8未満のため実行できない.
  24: */
  25: function __construct($contents) {
  26:     //PHPのバージョンをチェックする.
  27:     if (! $this->isphp8over()) {
  28:         $this->error = 111;
  29:         throw new Exception($this->getErrorMessage());
  30:         return NULL;
  31:     }
  32:     //プロパティの初期値を設定する.
  33:     try {
  34:         // Unicodeの範囲を指定
  35:         $map = [ 0x80, 0xFFFF, 0, 0xFFFF ];
  36:         // DOMの文字化け対策:UTF-8文字を数値エンティティに変換
  37:         $contents = mb_encode_numericentity($contents, $map, 'UTF-8');
  38:         // DOMDocument クラス
  39:         $this->dom = new DOMDocument();
  40:         libxml_use_internal_errors(TRUE);
  41:         @$this->dom->loadHTML($contents);
  42:         libxml_clear_errors();
  43:         $this->xpath = new DOMXPath($this->dom);
  44:     } catch (Exception $e) {
  45:         $this->error = 121;
  46:         throw new Exception($e->getMessage());
  47:         return FALSE;
  48:     }
  49: 
  50:     //エラー番号 => エラー・メッセージ 一覧
  51:     $this->errorTable = array(
  52:         111 => array('PHP8以上が必要です', 'PHP8 or higher is required'),
  53:         121 => array('DOMエラーです', 'DOM error'),
  54:         999 => array('不明のエラー', 'unknown error'),
  55:     );
  56: }

Webサイトのスクレイピング処理を行うためのクラスが pahooScraping である。同梱のクラス・ファイル "pahooScraping.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooScrapingクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

解説:各種定数など

初期値などは、あらかじめ定数で定義しておく。
変更不可となっている定数以外は、自由に変更できる。

lifecyclePHP.php

  66: // 各種定数(START) ===========================================================
  67: 
  68: // 表示幅(ピクセル)
  69: define('WIDTH', 640);
  70: 
  71: // ライフサイクルを保存するXMLデータ・ファイル名(フルパス)
  72: define('FILE_XML_VERSION', './phpVersion.xml');
  73: 
  74: // サポート終了のCSSスタイル(クラス)
  75: define('CLASS_END', 'end');
  76: 
  77: // 間もなくサポート終了のCSSスタイル(クラス)
  78: define('CLASS_NEAREND', 'near-end');
  79: 
  80: // 間もなくサポート終了までの期間(日)
  81: define('LIFE_NEAR', 180);
  82: 
  83: // キャッシュ保持時間(分) 0:キャッシュしない
  84: // PHP公式サイトへのアクセス負荷軽減のため.
  85: define('LIFE_CACHE_DATA', (60 * 24));
  86: 
  87: // キャッシュ・ディレクトリ
  88: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
  89: define('DIR_CACHE_DATA',   './pcacheLifecycle/');
  90: 
  91: // 公式サイトアクセスのためのUSER AGENT
  92: $HttpHeaders = array(
  93:     'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36'
  94: );
  95: 
  96: // 各種定数(END) ===============================================================

解説:XMLデータファイル

lifecyclePHP.php

 173: /**
 174:  * XMLファイルからライフサイクルを読み込む.
 175:  * @param   string $fname XMLファイル名
 176:  * @param   array  $items ライフサイクルを格納する配列
 177:  * @return  int 取得数/FALSE:失敗
 178: */
 179: function loadXMLfile($fname, &$items) {
 180:     global $FlagUpdate;
 181: 
 182:     $xml = @simplexml_load_file($fname);
 183: 
 184:     // エラー・チェック
 185:     if ($xml == FALSE)              return FALSE;
 186:     if (! isset($xml->product))     return FALSE;
 187: 
 188:     $cnt = 0;
 189:     foreach ($xml->product as $product) {
 190:         $branchVersion  = (string)$product->branchVersion;
 191:         $initialRelease = (string)$product->initialRelease;
 192:         $latestVersion  = (string)$product->latestVersion;
 193:         $latestRelease  = (string)$product->latestRelease;
 194:         $activeEnd      = (string)$product->activeEnd;
 195:         $securityEnd    = (string)$product->securityEnd;
 196:         $items[$branchVersion]['initialRelease'] = $initialRelease;
 197:         $items[$branchVersion]['latestVersion']  = $latestVersion;
 198:         $items[$branchVersion]['latestRelease']  = $latestRelease;
 199:         $items[$branchVersion]['activeEnd']      = $activeEnd;
 200:         $items[$branchVersion]['securityEnd']    = $securityEnd;
 201:         $cnt++;
 202:     }
 203: 
 204:     $FlagUpdate = FALSE;
 205:     return $cnt;
 206: }

ユーザー定義関数 loadXMLfile は、XMLデータファイルに保存したライフサイクル情報を配列に読み込む。

lifecyclePHP.php

 208: /**
 209:  * XMLファイルにライフサイクルを書き戻す.
 210:  * @param   string $fname XMLファイル名
 211:  * @param   array  $items ライフサイクルを格納する配列
 212:  * @return  int 保存したバイト数/0:更新無し/FALSE:保存失敗
 213: */
 214: function saveXMLfile($fname, $items) {
 215:     global $FlagUpdate;
 216: 
 217:     // XMLデータベースが更新されていなければスキップ
 218:     if (! $FlagUpdate)      return 0;
 219: 
 220:     // 配列をキー値で降順ソート
 221:     krsort($items);
 222: 
 223:     // 現在の日時
 224:     $now = date('Y-m-d H:i:s');
 225: 
 226:     $xml =<<< EOT
 227: <?xml version="1.0" encoding="utf-8" ?>
 228: <!-- PHPライフサイクル -->
 229: <atit:database xmlns:atit="urn:incidentList">
 230: <update>{$now}</update>
 231: 
 232: EOT;
 233:     foreach ($items as $branchVersion=>$item) {
 234:         $xml .=<<< EOT
 235: <product>
 236:     <branchVersion>{$branchVersion}</branchVersion>
 237:     <initialRelease>{$item['initialRelease']}</initialRelease>
 238:     <latestVersion>{$item['latestVersion']}</latestVersion>
 239:     <latestRelease>{$item['latestRelease']}</latestRelease>
 240:     <activeEnd>{$item['activeEnd']}</activeEnd>
 241:     <securityEnd>{$item['securityEnd']}</securityEnd>
 242: </product>
 243: 
 244: EOT;
 245:     }
 246: 
 247:     $xml .=<<< EOT
 248: </atit:database>
 249: 
 250: EOT;
 251:     $n = @file_put_contents($fname, $xml);
 252: 
 253:     return $n;
 254: }

また、ユーザー定義関数 saveXMLfile は、配列に格納してあるライフサイクル上方を XMLデータファイルに書き込む。本プログラム処理内でライフサイクル情報が更新されていなければ、グローバル変数 $FlagUpdate は FALSE のままなので、XMLファイルの上書きは行わない。

なお、XMLデータファイルは、下記に示すような構造をしている。
ライフサイクル・データ構造(xml) database update ファイル更新日時 product branchVersion ブランチのバージョン initialRelease 安定版リリース年月日 latestVersion 最新のバッチバージョン latestRelease 最新バージョンのリリース年月日 activeEnd アクティブサポート期限の年月日 securityEnd セキュリティサポート期限の年月日 product branchVersion ブランチのバージョン initialRelease 安定版リリース年月日 latestVersion 最新のバッチバージョン latestRelease 最新バージョンのリリース年月日 activeEnd アクティブサポート期限の年月日 securityEnd セキュリティサポート期限の年月日

解説:ブランチバージョンを取得する

lifecyclePHP.php

 273: /**
 274:  * 公式サイトを参照し,ブランチバージョンを取得する.
 275:  * @param   array  $items  ライフサイクルを格納する配列
 276:  * @param   string $errmsg エラー・メッセージを格納する(正常なら空文字)
 277:  * @return  int 更新件数/FALSE:更新失敗
 278: */
 279: function getLifecycle(&$items, &$errmsg) {
 280:     global $HttpHeaders, $FlagUpdate;
 281: 
 282:     // 公式サイト
 283:     $url = 'https://www.php.net/supported-versions.php';
 284: 
 285:     // インスタンスを生成する.
 286:     $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA, $HttpHeaders);
 287: 
 288:     // コンテンツを読み込む
 289:     $errmsg = '';
 290:     $contents = $pcc->cLoad($url);
 291:     if ($contents === FALSE) {
 292:         $errmsg = $pcc->geterror();
 293:         // インスタンスを解放する.
 294:         $pcc = NULL;
 295:         return FALSE;
 296:     }
 297:     // インスタンスを解放する.
 298:     $pcc = NULL;
 299: 
 300:     // インスタンスを生成する.
 301:     $psc = new pahooScraping($contents);
 302: 
 303:     // 対象テーブルの行を取得
 304:     // テーブル tbody 内の tr
 305:     $rows = $psc->queryXPath('//table[contains(@class,"standard")]//tr[td]');
 306: 
 307:     $cnt = 0;
 308:     foreach ($rows as $row) {
 309:         $cols = $row->getElementsByTagName('td');
 310:         if ($cols->length < 5) {
 311:             continue;
 312:         }
 313: 
 314:         // Branch(例:8.3)
 315:         $branchVersion = trim($cols->item(0)->textContent);
 316:         // バージョン行だけ残す.
 317:         if (! preg_match('/^\d+\.\d+$/', $branchVersion)) {
 318:             continue;
 319:         }
 320: 
 321:         // 日付(yyyy-mm-dd 形式)
 322:         $initialRelease = normalizeDate($cols->item(1)->textContent);
 323:         $activeEnd      = normalizeDate($cols->item(3)->textContent);
 324:         $securityEnd    = normalizeDate($cols->item(5)->textContent);
 325: 
 326:         // 既存の $items を更新
 327:         foreach ($items as $branch=>$item) {
 328:             // ブランチバージョンがあればスキップ
 329:             if ($branchVersion === $branch)     break;
 330:             // ブランチを登録する.
 331:             $items[$branchVersion]['initialRelease'] = $initialRelease;
 332:             $items[$branchVersion]['activeEnd']      = $activeEnd;
 333:             $items[$branchVersion]['securityEnd']    = $securityEnd;
 334:         }
 335:     }
 336: 
 337:     // インスタンスを解放する.
 338:     $psc = NULL;
 339: 
 340:     return TRUE;
 341: }

冒頭の公式サイトの Supported Versions を参照し、ブランチバージョンを取得するのがユーザー関数 getLifecycle である。

キャッシュシステム pahooCache を利用し、公式サイトへのアクセスは定数 LIFE_CACHE_DATA の間隔(デフォルトでは1日1回)に制限することで、アクセス負荷をかけないようにしている。

Supported Versions のHTML構造(DOM構造)を解析すると、ブランチバージョンの一覧は、class="standard" の table タグに書かれていることが分かる。そこで、pahooScrapingクラスと、XPath式を利用し、行(trタグ)に分解してスクレイピングしていく。
各行の中の情報(ブランチバージョン、安定版リリース年月日、アクティブサポート年月日、セキュリティサポート年月日)を取得するには、XPath式よりも DOM を使った方が簡便にできるので、そうしている。
ブランチバージョンを取得するには、正規表現を用いている。
安定版リリース年月日、アクティブサポート年月日、セキュリティサポート年月日は、tdタグで囲まれた中を丸ごと取り込んでいるが、年月日表記が英語であるため、後述するユーザー定義関数 normalizeDate を使って、yyyy-mm-dd形式に変換する。

取得した情報は、ブランチバージョンをキーにした連想配列 $items に格納していく。ここで、すでにブランチバージョンが登録済み(XMLデータファイルから読み込まれた)ものについては、配列の内容を更新せずにスキップする。配列の内容を更新したときだけ、グローバル変数 $FlagUpdate を TRUE にする。

lifecyclePHP.php

 257: /**
 258:  * 年月日を表す文字列を yyyy-mm-dd 形式に正規化する.
 259:  * @param   string $str items 年月日を表す文字列
 260:  * @return  int 正規化した文字列/NULL:変換に失敗
 261: */
 262: function normalizeDate($str) {
 263:     $str = trim($str);
 264:     if ($str === '' || stripos($str, 'ago'!== FALSE) {
 265:         // "3 years ago" などを除去
 266:         $str = preg_replace('/\d+.*ago/', '', $str);
 267:     }
 268:     $time = strtotime($str);
 269: 
 270:     return $time ? date('Y-m-d', $time: NULL;
 271: }

解説:各ブランチの最新バージョン情報を取得する

lifecyclePHP.php

 343: /**
 344:  * 公式サイトを参照し,各ブランチの最新バージョン情報を取得する.
 345:  * @param   array  $items  ライフサイクルを格納する配列
 346:  * @param   string $errmsg エラー・メッセージを格納する(正常なら空文字)
 347:  * @return  bool array(最新バージョン, リリース年月日)/FALSE:取得失敗
 348: */
 349: function getLatestVersion(&$items ,&$errmsg) {
 350:     global $HttpHeaders, $FlagUpdate;
 351: 
 352:     // 公式サイト
 353:     $url = 'https://www.php.net/';
 354: 
 355:     // インスタンスを生成する.
 356:     $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA, $HttpHeaders);
 357: 
 358:     // コンテンツを読み込む
 359:     $errmsg = '';
 360:     $contents = $pcc->cLoad($url);
 361:     if ($contents === FALSE) {
 362:         $errmsg = $pcc->geterror();
 363:         // インスタンスを解放する.
 364:         $pcc = NULL;
 365:         return FALSE;
 366:     }
 367:     // インスタンスを解放する.
 368:     $pcc = NULL;
 369: 
 370:     // インスタンスを生成する.
 371:     $psc = new pahooScraping($contents);
 372: 
 373:     // <a>タグの値に "Released" が含まれているノードを取得する.
 374:     $nodes = $psc->queryXPath("//a[contains(text(),'PHP') and contains(text(),'Released')]");
 375:     foreach ($nodes as $node) {
 376:         // 例: PHP 8.5.4 Released!
 377:         $text = trim($node->textContent);
 378: 
 379:         // id属性(例: php-8.5.4)
 380:         $id = $node->getAttribute("id");
 381: 
 382:         // バージョン情報
 383:         if (preg_match('/PHP\s+(\d+\.\d+)(\.*\d*)/', $text, $m)) {
 384:             $branchVersion = $m[1];
 385:             $latestVersion = $branchVersion . $m[2];
 386:         }
 387:         // リリース日
 388:         if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $id, $d)) {
 389:             $latestRelease = $d[1];
 390:         }
 391: 
 392:         // 既存の $items を更新
 393:         foreach ($items as $branch=>&$item) {
 394:             if ($branchVersion === $branch) {
 395:                 // 最新バージョンが登録済みならスキップする.
 396:                 if (isset($item['latestVersion'])
 397:                     && $item['latestVersion'>$latestVersion)    break;
 398:                 // 最新バージョンを登録する.
 399:                 $item['latestVersion'] = $latestVersion;
 400:                 $item['latestRelease'] = $latestRelease;
 401:                 $FlagUpdate = TRUE;     // データベース更新フラグを立てる
 402:             }
 403:         }
 404:     }
 405:     unset($item);
 406:     // インスタンスを解放する.
 407:     $psc = NULL;
 408: 
 409:     return TRUE;
 410: }

各ブランチの最新バージョンは、PHP公式サイトのトップページに掲載されているリリース情報から読み取る。そのためのユーザー定義関数が getLatestVersion である。
前述の関数 getLifecycle 同様、キャッシュシステム pahooCache を利用し、公式サイトへのアクセス負荷を低減している。
リリースの見出しは、"PHP" および "Released" を必ず含む a タグであることから、これを XPath式で拾って、正規表現で必要な上方を取り出している。

取得した情報は、ブランチバージョンをキーにした連想配列 $items に格納していく。前述の関数 getLifecycle 同様、最新バージョンが登録済み(XMLデータファイルから読み込まれた)ものについては、配列の内容を更新せずにスキップする。配列の内容を更新したときだけ、グローバル変数 $FlagUpdate を TRUE にする。

解説:PHPのライフサイクル情報をHTMLのtableに変換する

lifecyclePHP.php

 412: /**
 413:  * 日付のステータスを求める
 414:  * @param   string $yyyymmdd 日付 yyyy-mm-dd形式
 415:  * @return  int 0:日付以外,1:ライフ中,2:間もなくライフ終了,3:ライフ終了
 416: */
 417: function getStatus($yyyymmdd) {
 418:     //本日Date
 419:     $today = new DateTime();
 420:     $dtformat = '/[0-9]{4}\-[0-9]{2}\-[0-9]{2}/';
 421: 
 422:     //日付かどうか
 423:     if (preg_match($dtformat, $yyyymmdd, $arr) == 0)    return 0;
 424:     $yyyymmdd = $arr[0];
 425: 
 426:     //日付のステータス
 427:     $dt = new DateTime($yyyymmdd);
 428:     $diff = $today->diff($dt)->format('%R%a');
 429:     if ($diff <0) {
 430:         $ret = 3;
 431:     } else if ($diff <LIFE_NEAR) {
 432:         $ret = 2;
 433:     } else {
 434:         $ret = 1;
 435:     }
 436: 
 437:     return $ret;
 438: }

lifecyclePHP.php

 440: /**
 441:  * PHPのライフサイクル情報をHTMLのtableに変換する.
 442:  * @param   array  $items  ライフサイクルを格納する配列
 443:  * @return  string 変換後HTML/FALSE:ファイルが無い、変換に失敗
 444: */
 445: function phpLifecycle2html($items) {
 446:     // 日付スタイル
 447:     $classes = array(
 448: '',
 449: '',
 450: CLASS_NEAREND,
 451: CLASS_END
 452: );
 453: 
 454:     // 現在の年月日
 455:     $today = new DateTime();
 456:     $dtformat = '/[0-9]{4}\-[0-9]{2}\-[0-9]{2]/';
 457: 
 458:     // 出力用HTML
 459:     $width1 = (int)(WIDTH * 0.188);
 460:     $width2 = (int)(WIDTH * 0.125);
 461:     $html =<<< EOT
 462: <table class="lifecyslePHP">
 463: <tr>
 464: <th style="width:{$width2}px;text-align:center;">バージョン</th>
 465: <th style="width:{$width1}px;text-align:center;">安定版<br>リリース</th>
 466: <th style="width:{$width2}px;text-align:center;">最新<br>バージョン</th>
 467: <th style="width:{$width1}px;text-align:center;">最新リリース日</th>
 468: <th style="width:{$width1}px;text-align:center;">アクティブ<br>サポート終了</th>
 469: <th style="width:{$width1}px;text-align:center;">セキュリティ<br>サポート終了</th>
 470: </tr>
 471: 
 472: EOT;
 473:     // 各バージョン
 474:     foreach ($items as $branchVerrsion=>$item) {
 475:         //日付ステータス
 476:         $st1 = getStatus((string)$item['initialRelease']);
 477:         $st2 = getStatus((string)$item['latestRelease']);
 478:         $st3 = getStatus((string)$item['activeEnd']);
 479:         $st4 = getStatus((string)$item['securityEnd']);
 480:         if ($st4 == 0) {
 481:             if ($st3 == 0) {
 482:                 if ($st2 == 0) {
 483:                     $st4 = $st3 = $st2 = $st1;
 484:                 } else {
 485:                     $st4 = $st3 = $st1 = $st2;
 486:                 }
 487:             } else {
 488:                 $st1 = $st2 = $st4 = $st3;
 489:             }
 490:         } else {
 491:             if ($st3 == 0)  $st3 = $st4;
 492:             $st1 = $st2 = $st4;
 493:         } 
 494: 
 495:         //日付スタイル
 496:         $ss1 = $classes[$st1];
 497:         $ss2 = $classes[$st2];
 498:         $ss3 = $classes[$st3];
 499:         $ss4 = $classes[$st4];
 500: 
 501:         //HTML変換
 502:         $html .=<<< EOT
 503: <tr>
 504: <td class="{$ss1}">{$branchVerrsion}</td>
 505: <td class="{$ss1}">{$item['initialRelease']}</td>
 506: <td class="{$ss2}">{$item['latestVersion']}</td>
 507: <td class="{$ss2}">{$item['latestRelease']}</td>
 508: <td class="{$ss3}">{$item['activeEnd']}</td>
 509: <td class="{$ss4}">{$item['securityEnd']}</td>
 510: </tr>
 511: 
 512: EOT;
 513:     }
 514: 
 515:     $html .=<<< EOT
 516: </table>
 517: 
 518: EOT;
 519:     return $html;
 520: }

取得したライフサイクル情報を格納した配列 $items から、表示のための一覧表(tableタグ)を生成するのがユーザー関数 phpLifecycle2html である。
日付のカラムについては、ユーザー関数 getStatus を使って、定数 LIFE_NEAR で指定した期間より小さければ、間もなく終了するという意味で、スタイルを定数 CLASS_NEAREND で指定したクラスにする。現在を過ぎていれば、終了したという意味で、スタイルを定数 CLASS_END で指定したクラスにする(デフォルトではグレーアウト)。
デフォルトのスタイルは次に示すようになっている。URLパラメータで onlyTable を指定したときはスタイルが出力されないので、ユーザーサイト側でこれらのスタイルを用意する必要がある。

lifecyclePHP.php

 116: <script>
 117: document.addEventListener('DOMContentLoaded', function() {
 118:     const tables = document.querySelectorAll('table.lifecyslePHP');
 119:     tables.forEach(function(table) {
 120:         table.style.borderColor = '#FFFFFF';
 121:         table.style.fontSize = 'small';
 122:         table.style.borderCollapse = 'collapse';
 123:         table.style.padding = '2px';
 124:         table.style.borderSpacing = '2px';
 125: 
 126:         // ヘッダー (th) のスタイル
 127:         const headers = table.querySelectorAll('th');
 128:         headers.forEach(function(th) {
 129:             th.style.backgroundColor = '#FFBB00';
 130:             th.style.textAlign = 'center';
 131:             th.style.border = 'solid 1px gray';
 132:         });
 133: 
 134:         // 行の縞模様 (tr)
 135:         const rows = table.querySelectorAll('tr');
 136:         rows.forEach(function(row, index) {
 137:             row.style.backgroundColor = (index % 2 === 0) ? '#FFDD88' : '#FFFFFF';
 138:         });
 139: 
 140:         // セル (td) のスタイル
 141:         const cells = table.querySelectorAll('td');
 142:         cells.forEach(function(td) {
 143:             td.style.padding = '4px';
 144:             td.style.border = 'solid 1px gray';
 145:             td.style.textAlign = 'center';
 146:         });
 147:     });
 148: });
 149: </script>
 150: <style>
 151: table.lifecyslePHP .near-end {
 152:     background-color: #FFCCCC;
 153: }
 154: table.lifecyslePHP .end {
 155:     background-color: #AAAAAA;
 156: }
 157: </style>

解説:メイン・プログラム

メイン・プログラムは、冒頭に紹介したプログラムの流れそのものになっている。

lifecyclePHP.php

 592: // メイン・プログラム ======================================================
 593: 
 594: // パラメータの取得
 595: $flagTable = isButton('onlyTable');
 596: $flagMe = ! isButton('noMyVersion');
 597: $items  = array();
 598: $errmsg = '';
 599: 
 600: // XMLファイルからライフサイクルを読み込む.
 601: $res = loadXMLfile(FILE_XML_VERSION, $items);
 602: if ($res === FALSE) {
 603:     $errmsg = FILE_XML_VERSION .' を読み込めません';
 604: }
 605: 
 606: // 公式サイトからブランチバージョン情報を取得する.
 607: if ($errmsg === '') {
 608:     $res = getLifecycle($items, $errmsg);
 609:     if ($res === FALSE) {
 610:         $errmsg = '公式サイトからライフサイクル情報を取得できません';
 611:     }
 612: }
 613: 
 614: // 公式サイトから最新バージョン情報を取得する.
 615: if ($errmsg === '') {
 616:     $res = getLatestVersion($items, $errmsg);
 617:     if ($res === FALSE) {
 618:         $errmsg = '公式サイトから最新バージョン情報を取得できません';
 619:     }
 620: }
 621: 
 622: // XMLファイルにライフサイクルを書き戻す.
 623: if ($errmsg === '') {
 624:     $res = saveXMLfile(FILE_XML_VERSION, $items);
 625:     if ($res === FALSE) {
 626:         $errmsg = FILE_XML_VERSION .' に書き込めません';
 627:     }
 628: }
 629: 
 630: // 表示HTMLを作成する.
 631: $table = phpLifecycle2html($items);
 632: $HtmlBody = makeCommonBody($flagMe, $table, $errmsg);
 633: 
 634: // 画面に表示する.
 635: if (! $flagTable) {
 636:     echo $HtmlHeader;
 637:     echo $HtmlBody;
 638:     echo $HtmlFooter;
 639: // テーブルのみ表示
 640: else {
 641:     echo $table;
 642: }
 643: 
 644: /*

参考サイト

(この項おわり)
header