Appearance
日別記事 初期描画フロー
日別記事(daily_article)公開画面の初期描画フローを記録したドキュメントです。
プロジェクト構成(概要) の詳細です。ショートコードの一般的な MVC フローは ショートコードの処理の流れ を参照してください。
概要
日別記事の公開画面は、次の 2 系統 × 3 段階 で構成されています。
| 段階 | 系統 A: ページ全体(DailyArticleTemplate) | 系統 B: 日別記事結果(DailyArticleResult ショートコード) |
|---|---|---|
| 1. PHP 同期 | Twig テンプレート + ナビ用 link_day_data(L1)+ 先頭ホール MailImage SSR(Issue #3609)。HTML 断片 Transient ヒット時に Twig/SC をスキップ(プレビュー以外。ログイン状態は問わない)。断片 HIT/MISS 後に遅延ブロック HTML を stitch | プレースホルダー HTML + CSS/JS enqueue(ブロックキャッシュ HIT 時はページ側で完成 HTML に置換済み) |
| 2. キャッシュ | 共有データは warm REST で L2/Hash Transient。初回 HTML は rendered HTML Transient(広告は未展開)。遅延ブロックは block HTML Transient + POST /async-warm-block-html | ランキング等は UI REST でも block HTML を get/set |
| 3. JS 非同期 | L2 warm と block-html warm と残 MailImage バッチを並列 → L2 warm 後に IntersectionObserver で断片(PH 0 件なら REST スキップ) | ランキング・ヒートマップ・末尾等も同様に IO 遅延 REST |
設計の要点
- 初回 HTML では
initialize_navigation()により 前日・翌日・前年リンク用の link_day_data を同期取得する(HTML 断片ヒット時はスキップ)。 - HTML 断片(
daily_article_html_v2_)は Content →DailyArticleTemplateFrontendRendererの最外周。ヒット時は遅延ブロック stitch →(変更があれば断片 Transient を更新)→DailyArticleSingularAssetEnqueuerで orchestrator / nonce / MailImage 等を enqueue し、広告ショートコードのみdo_shortcode。鮮度は記事保存・日付クリア・HashInvalidator・有料記事の公開/Twitter 更新・機種表示マッピング更新・デプロイ時の全体世代更新の明示クリアが正(TTL 12h は安全網)。 - 遅延ブロック HTML(
daily_article_block_html_v1_)は UI REST の完成 HTML をブロック単位で保存し、次回ページ GET でプレースホルダを置換する。ヒートマップ簡易はpc/spキーを分離し、当面 stitch / warm は PC キーのみ(SP 表示は #2828 のまま非表示)。POST /async-warm-block-htmlが MailImage / MoveDayHall / 関連日 / kishu_count_delta / ランキング / 末尾 / ヒートマップ簡易(PC)を埋める(kishudata は UI REST 経由。既存 L2 warm とは別。UI REST ゲートに使わない)。 - 先頭ホールの MailImage のみ
MailImageController::execute()で同期描画する(失敗時はプレースホルダへフォールバック。アーカイブは最大 2 フレーム)。他ホールの MailImage・関連日・MoveDayHall・日別記事結果は プレースホルダ + REST(ブロックキャッシュ HIT 時はプレースホルダ無し)。 daily_article_orchestrator.jsが L2 warm・block-html warm・残 MailImage バッチを並列し、L2 warm 完了後に残りの UI REST を開始する(.mail-image-placeholderが 0 件なら MailImage バッチはスキップ。stitch 済みで PH 0 件なら該当 REST もスキップ)。- 共有データ(主に
daily_link_list+daily_data_kishu_list、加えてlink_day_dataも DTO に含む)の L2/Hash はwarm_shared_template_data()で生成する。Shared L1($cached_data)は PHP static のため HTTP リクエストをまたいでは共有されない。warm_shared_template_data()は warm REST 内でも L1 に DTO を載せるが、後続の HTTP リクエスト(UI REST 等)へ引き継がれる永続化は L2/Hash Transient のみである。後続 UI REST では L2_HIT 時に同一リクエスト内で L1 に復元する。L2 ヒット時のハッシュ検証はcompute_lightweight_data_hash()で行う。Hash Transient のデフォルト TTL は 300 秒(HASH_TRANSIENT_EXPIRATION、L2 Transient TTL と揃える。フィルターslot_kouryaku_daily_article_template_hash_transient_expirationで変更可)。link_day の insert/update/delete 時は push 型で対象日の L2 + Hash Transient および HTML 断片・ブロック HTML を無効化する。
キャッシュ優先順位
- HTML 断片(ヒットならテンプレ組み立てをしない。続けてブロック HTML stitch)
- 遅延ブロック完成 HTML(ページ stitch / UI REST HIT)
- L2 DTO(warm / UI REST)
- 考察
the_contentTransient(ミス経路) - 日付→投稿ID(パーマリンク)
同期 / 遅延の境界
同期(初回 HTML に含める)
| 要素 | データ源 | 備考 |
|---|---|---|
| 記事タイトル | post meta | 現状どおり |
| 前日・翌日テキストリンク | initialize_navigation() | Twig 上部ナビでは link_day_data.previous_day / next_day のみ表示。last_year はナビには出さず、MoveDayHallService 内で前年リンク生成(prev_year_post_data)に利用される。1 クエリ |
| 先頭ホール MailImage | MailImageController::execute() | Issue #3609。hall_names[0] のみ。成功時は完成 HTML(mail-image-placeholder なし)+ mail_image.css / mail_image.js を SC-007 と同様に enqueue。失敗時はプレースホルダ |
| 月別リンク | [MonthlyLinkByYear] | 既存 ShortCode(共有 warm とは独立) |
| 考察・広告等 | 既存 ShortCode | 同期は維持 |
遅延(DOMContentLoaded 以降)
| 要素 | REST | 備考 |
|---|---|---|
| 共有データ warm | POST /async-warm-template-data | HTML なし、L2/Hash 生成 |
| 遅延ブロック HTML warm | POST /async-warm-block-html | HTML なし。対象ブロックを Transient に埋める(L2 warm / UI REST と並列) |
| 関連日 + 日別カレンダー | GET /async-relational-day | ホール数分 |
| MoveDayHall | GET /async-move-day-hall-batch | 全ホール 1 リクエスト(失敗ホールは単体 GET でリトライ) |
| MailImage(2 ホール目〜) | GET /async-mail-image-batch | プレースホルダがあるホールのみ(先頭 SSR 成功分は除外。失敗ホールは単体 GET) |
| ランキング等 | API-001-1, 3, 9 等 | DailyArticleResult 系 |
既存投稿本文に直書きされた [MailImage] ショートコードは 従来どおり同期のまま(テンプレート先頭ホール SSR とは別経路。二重定義しない)。詳細は SC-007 を参照。
関連ファイル
図 1: 全体俯瞰シーケンス
Browser から HTML 受信後、orchestrator が warm → UI REST を開始するまでの時系列です。
補足
- 系統 A はページ全体を Twig で描画します。RelationalDay / MoveDayHall / MailImage は Twig 内のプレースホルダ div です。Twig 内の
[CustomCode_CreateDailyArticleResult ...]等はdo_shortcode()で展開されます。 - 系統 B の初回 HTML はプレースホルダーです。ランキング・ヒートマップ・末尾データの実データは orchestrator 経由の REST で後から取得します。
DailyArticleResultController.build_presentation()は REST 非同期用(service->execute()+ ヒートマップレイアウト取得)であり、ショートコード初回 HTML では使われません。DailyArticleTemplateDataService.initialize()はinitialize_navigation()+warm_shared_template_data()の一括実行用(deprecated)。初回 HTML ではinitialize_navigation()のみ呼ばれます。
図 2: warm_shared_template_data() キャッシュ分岐
共有データ(daily_link_list + daily_data_kishu_list + link_day_data)の取得経路です。POST /async-warm-template-data または各 UI REST Handler のフォールバックから呼ばれます。
キャッシュ層の定義
| 層 | 実装 | 格納内容 | 有効範囲 |
|---|---|---|---|
| ナビ L1 | static $navigation_link_day_data | LinkDayData(前日・翌日・前年 URL) | 同一 HTTP リクエスト内のみ(initialize_navigation) |
| L1(Shared) | static $cached_data | DailyArticleTemplateDataPresentationDto | 同一 HTTP リクエスト内のみ |
| L2(Shared) | WordPress Transients | ['dto' => DTO, 'data_hash' => string] | リクエスト間(TTL 300 秒) |
| Hash Transient | WordPress Transients(日付単位) | SHA-256 ハッシュ文字列 | リクエスト間(デフォルト TTL 300 秒。フィルター slot_kouryaku_daily_article_template_hash_transient_expiration で変更可) |
Transient TTL / cleanup 運用設計
| 項目 | 設計 |
|---|---|
| L2 Transient TTL | 300 秒(DailyArticleTemplateDataService::TRANSIENT_EXPIRATION)。date + halls ごとに 1 キー。 |
| Hash Transient TTL | 300 秒(DailyArticleTemplateDataService::HASH_TRANSIENT_EXPIRATION、フィルターで変更可)。date ごとに 1 キー。 |
| 想定エントリ数 | 同時滞留キーは概ね「直近 TTL 区間でアクセスされた date + halls 件数 + date 件数」。TTL 経過で期限切れ化。 |
| 定期掃除 | 期限切れ transient は 保守クリーンアップバッチ で掃除する。 |
| 多重実行対策 | 定期掃除側の排他制御は 保守クリーンアップバッチ を参照する。 |
この設計により、期限切れ transient の残存は短TTL + 定期 cleanup で収束し、wp_options 肥大化リスクを抑える。
分岐パス一覧
| パス | DB アクセス | 備考 |
|---|---|---|
| NAV_L1_HIT | なし | ナビ用 link_day_data を static から利用 |
| NAV_FETCH | fetch_link_day_data 1 本 | 初回 HTML 同期時 |
| L1_HIT(Shared) | なし | static 変数から DTO をそのまま利用 |
| L2_HIT | 原則なし(Hash Transient ミス時のみ集約 stats 2 本) | Transient から DTO 復元 → L1 に載せ替え |
| L2_HASH_MISMATCH | あり(DB_FETCH へ) | L2・Hash Transient を削除 |
| L2_MISS / L2_INVALID | あり(DB_FETCH へ) | 古い L2 を削除して下へ |
| DB_FETCH | フル取得 | link 一覧、機種サマリ、link_day → L1/L2/Hash Transient 保存 |
ハッシュ検証の目的
L2 は 5 分 TTL のため、phpMyAdmin 等の DB 直接変更を Transient 期限だけでは検知できません。compute_lightweight_data_hash() / compute_data_hash_from_fetched_data() で SHA-256 ハッシュを計算し、L2 に保存した data_hash と照合します。Hash Transient が有効な間(デフォルト最大 300 秒)は DB 変更を即時検知しません。link_day の URL/event 更新は push 型無効化で検知します。
図 3: JS orchestrator シーケンス
daily_article_orchestrator.js が DOMContentLoaded 後に warm と MailImage バッチを並列開始し、warm 完了後に残りの UI REST を呼び出す流れです。
orchestrator のルール
- 残 MailImage バッチは warm と並列開始(
.mail-image-placeholderのみ。先頭ホール SSR 成功ノードは除外。PH 0 件なら投入しない。可視があれば優先度 0)— 共有 L2 に依存しないためスタンピードを増やさない - それ以外の UI REST は warm 完了(または失敗待機)後に並列発火 — キャッシュスタンピード防止。MoveDayHall も全ホールバッチ 1 本
- warm 中は(先行した MailImage バッチ以外)プレースホルダ表示のまま
- warm 失敗時も UI REST をそのまま並列発火(各 Handler が必要に応じて
warm_shared_template_dataで個別フォールバック。MailImage はフォールバック不要) - UI REST のレスポンスは生 HTML ではなく JSON(バッチは
items+ 共通assets。単体はhtml)。失敗ホールは単体 GET でリトライ - 二重取得は
isAsyncLoadStarted/ バッチ enqueue フラグで抑止する
REST エンドポイント
ベース URL: /wp-json/daily-article/v1/(dailyArticleAsync.restUrl 経由)
| コード | パス | method | トリガー | 用途 |
|---|---|---|---|---|
| API-001-10 | /async-warm-template-data | POST | ページ読み込み直後(MailImage バッチ・block-html warm と並列) | 共有データ L2 warm(HTML なし) |
| API-001-18 | /async-warm-block-html | POST | ページ読み込み直後(L2 warm 完了を待たない。UI ゲートなし) | 遅延ブロック HTML warm(HTML なし) |
| API-001-11 | /async-relational-day | GET | warm 後・IO(rootMargin 100px) | 関連日 + 日別カレンダー |
| API-001-17 | /async-move-day-hall-batch | GET | warm 後・全ホール 1 本(プール優先度 1/2) | MoveDayHall バッチ(単体はリトライ用) |
| API-001-16 | /async-mail-image-batch | GET | warm と並列・全ホール 1 本(可視あれば優先度 0) | MailImage バッチ(L2 warm なし) |
| API-001-12 | /async-move-day-hall | GET | バッチ失敗ホールのリトライ | MoveDayHall 単体 |
| API-001-13 | /async-mail-image | GET | バッチ失敗ホールのリトライ | MailImage 単体(L2 warm なし) |
| API-001-9 | /async-heatmap-simple | GET | warm 後・IO(rootMargin 100px、デスクトップのみ) | ヒートマップ簡易 |
| API-001-1 | /async-ranking | GET | warm 後・IO(rootMargin 100px) | ランキング |
| API-001-3 | /async-end-number | GET | warm 後・IO(rootMargin 100px) | 末尾データ |
| API-001-2 | /async-heatmap-detail | GET | 詳細ボタン初回クリック | ヒートマップ詳細 |
| API-001-4 | /async-kishu-search | GET | 検索ボタンクリック | 機種指定データ検索 |
| API-001-14 | /async-kishudata | GET | warm 後・IO(rootMargin 100px) | 機種データ(SC-009) |
権限: 上記 Async 系は nonce + IP レート制限(読み取り系 60 秒 300 回、warm 系 60 秒 60 回)。詳細は API-一覧.md を参照。
PHP → JS データ受け渡し
core_src/View/templates/daily_article_result/index.php が wp_localize_script で以下を渡します。
| キー | 内容 |
|---|---|
restUrl | rest_url('daily-article/v1/') |
nonce | wp_create_nonce('wp_rest') |
msgRankingFailed 等 | 非同期エラー文言 |
Twig ルート要素 .daily-article-template の data-warm-date / data-warm-halls が warm リクエストの入力です。
Twig プレースホルダ
daily_article_template.twig では次のプレースホルダを使用します。
| 要素 | CSS クラス | data 属性(例) |
|---|---|---|
| MailImage | mail-image-placeholder | data-hall, data-date(先頭ホール SSR 成功時はクラス無しの完成 HTML。バッチ対象外) |
| MoveDayHall | move-day-hall-placeholder | data-hall, data-date, data-preday, data-nextday |
| RelationalDay | relational-day-placeholder | data-hall, data-date(固定シェル=見出し・pending・カレンダー骨格を SSR 先行表示。失敗時は .relational-day-placeholder__pending 内のみエラー) |
| DailyArticleResult | 既存プレースホルダクラス | data-hall, data-date 等 |
CSS 責務
- 枠・主要レイアウト:
daily_article_template.cssでも最終 BEM(.mail-image__content/.move-day-hall/.relational-day-result-section/.daily-result-calendar-wrapper等)に載せる。 成功パスで*-placeholderを外してもスタイルが切れない。 *-placeholder: 読込中セマンティクスのみ(aria-busyと組む状態、予約min-height、pending スピナー、screen-reader フォールバック等)。- component CSS(
mail_image.css/move_day_hall.css/daily_result_calendar.css): ショートコード単独・詳細スタイルの正本。first-paint と同値のプロパティは template CSS と同期する。 - 配信: 日別記事では
daily_article_template.cssを head preload。component CSS は RESTassets経由で差し替え前に注入。