RRelayer
ホーム/はじめに

usePHP(PSX エンジン)#

Relayerpolidog/use-php の上に構築されています。ルーティング・DI・認証・キャッシュなどのアプリ機構は Relayer が担い、ビュー(描画)は usePHP が担当します。このページは、その描画エンジンである usePHP の要点をまとめます。

PSX とは#

PSX は「JSX 風の構文を書ける PHP」です。.psx ファイルを usePHP がコンパイルし、H:: 呼び出し(ハイパースクリプト)に変換します。

<?php
use Polidog\UsePhp\Html\H;

return fn () => (
    <section className="card">
        <h1>It works</h1>
        <p>これは {date('Y')} 年のページです。</p>
    </section>
);

上は次と等価です(コンパイル結果のイメージ)。

return fn () => H::section(className: 'card', children: [
    H::h1(children: 'It works'),
    H::p(children: ['これは ', date('Y'), ' 年のページです。']),
]);
  • 属性は className(React 流。class に変換)。{ ... } で PHP 式を埋め込み。
  • .psx 内では JSX を書く。.php のヘルパクラスは JSX 不可なので H:: を直接使う。

テキストは必ずエスケープされる(XSS 安全)#

Renderer はすべてのテキスト子要素を htmlspecialchars でエスケープします。 生 HTML をそのまま注入する仕組みはありません(テキストは常にエスケープされ、構造だけが本物のタグになる)。

そのため Markdown を表示するときは「HTML 文字列」ではなく H:: の Element ツリーへ変換します(このサイトの App\Docs\Markdown がまさにそれ — 構造は本物のタグ、テキストはエスケープ=安全)。

コンポーネント#

PascalCase のタグはコンポーネント。use で FQCN を解決します。

<?php
namespace App\Components;

use Polidog\UsePhp\Html\H;

return fn (array $props) => (
    <div className="card">
        <h3>{$props['title']}</h3>
        <div>{$props['children'] ?? null}</div>
    </div>
);
use App\Components\Card;

return fn () => (
    <Card title="お知らせ">
        <p>本文…</p>
    </Card>
);

ループや条件は式で書きます。

<ul>
    {array_map(fn ($x) => <li key={$x['id']}>{$x['name']}</li>, $items)}
</ul>
{$loggedIn ? (<a href="/logout">Logout</a>) : (<a href="/login">Login</a>)}

フック(useState / useEffect / useRouter)#

フックは fc() でラップした関数コンポーネントの中でのみ機能します(コンポーネントのコンテキストが必要)。状態はサーバー側で保持され、保存方法を StorageType で選びます。提供されるフックは useState / useEffect / useRouter(と fc)です。

use Polidog\UsePhp\Storage\StorageType;
use function Polidog\UsePhp\Runtime\{fc, useState, useEffect, useRouter};

fc()#

fc(callable $component, ?string $key = null,
   StorageType $storageType = StorageType::Session, ?Defer $defer = null): FunctionComponent

$key はコンポーネントインスタンスの識別子、$storageType は状態の保持方法、 $defer は遅延描画(後述)用。フックを使うコンポーネントは必ず fc() で包みます。

useState#

useState($initial)[値, セッター] を返します。セッターは Action を返すので、イベント(onClick など)に結び付けて使います。usephp.js がプログレッシブに強化し、JS 無しでもフォーム POST にフォールバックします。

return fc(function (array $props) {
    [$count, $setCount] = useState($props['initial'] ?? 0);

    return (
        <div>
            <span>Count: {$count}</span>
            <button onClick={fn () => $setCount($count + 1)}>+</button>
            <button onClick={fn () => $setCount(0)}>reset</button>
        </div>
    );
}, 'counter', StorageType::Snapshot);

useEffect#

useEffect(callable $callback, ?array $deps = null): void
  • 初回(マウント)と、依存値が変わったときに実行。
  • $deps = null … 毎回の描画で実行 / [] … マウント時のみ /

[$a, $b] … その値が変わったときだけ。

  • $callbackクリーンアップ関数を返すと、次回実行前

(およびクリーンアップ時)に呼ばれます。

return fc(function (array $props) {
    [$tab, $setTab] = useState('home');

    useEffect(function () use ($tab) {
        \error_log("tab changed: {$tab}");
        return function () {
            \error_log('cleanup before next effect');
        };
    }, [$tab]); // $tab が変わったときだけ

    return (
        <div>
            <button onClick={fn () => $setTab('home')}>Home</button>
            <button onClick={fn () => $setTab('about')}>About</button>
            <p>active: {$tab}</p>
        </div>
    );
}, 'tabs', StorageType::Session);

useRouter#

現在 URL とルートパラメータを取得します。

$router = useRouter();
$router['currentUrl']; // 現在の URL
$router['params'];     // ['id' => '42'] など(動的セグメント)

StorageType#

種別挙動
Session(既定)サーバーセッションに保持。ページ遷移を跨いで残る
Memoryページ読み込みごとにリセット
Snapshot状態を(署名付きで)HTML に埋め込み往復。サーバーはステートレス。本番は USEPHP_SNAPSHOT_SECRET 必須

useMemo / useRef などはありません。提供されるのは上記のフックのみ。フォーム送信の検証などは バリデーション / サーバーアクション を参照。

コンパイルと配布#

.psx はそのままでは動かず、コンパイルが必要です。

vendor/bin/usephp compile src/Components   # .psx をコンパイル + manifest 生成
vendor/bin/usephp publish                  # public/usephp.js を配置
  • devAPP_ENV=dev)はリクエスト時に自動コンパイル。
  • 本番はデプロイ時に事前コンパイル必須(デプロイ 参照)。
  • キャッシュキーは ソースの realpath の sha1。ビルドと実行時で同じ絶対パス

である必要があります。

エディタのシンタックスハイライト#

.psx は VS Code や Vim/Neovim が標準では認識しないので、放っておくと PHP 扱いになるか、プレーンテキストとして開かれます。polidog/use-php リポジトリには .psx 用のハイライト定義が同梱されていて、入れると以下が効きます。

  • *.psx のファイルタイプ判定
  • エディタ標準の PHP 文法による PHP コードのハイライト
  • JSX 風タグを上に重ねるルール
    • HTML 要素(<div>, <span> …)と PascalCase のコンポーネントタグ(<Counter />
    • 自己閉鎖タグとフラグメント(<>…</>
    • 属性(リテラル文字列と { ... } の PHP 式)
    • { ... } の中身は PHP としてハイライト

定義は polidog/use-php リポジトリの editors/ に入っています。インストール手順は各エディタの README を参照してください。

  • VS Codeeditors/vscode/README)。

vsce package.vsix をビルドして入れるのが推奨。Marketplace への公開は未。

  • Neovim / Vimeditors/nvim/README)。

lazy.nvim / packer / 手動コピーいずれも可。Vim-script のみで Lua 依存なし。

同梱されていないもの(現時点では scope 外):LSP(型チェック・補完)、 tree-sitter 文法、PSX 対応 PHPStan 拡張、フォーマッタ連携。これらは別リポジトリとして切り出される予定です。

どちらの定義も正規表現ベースなので、$a < $b のような比較式が稀にタグと誤認されることがあります。踏んだら最小再現とともに use-php 側に issue を立ててください。

ページ単位のアセット#

グローバルに JS/CSS を足さず、必要なルートだけで宣言できます。

return function (PageContext $ctx) {
    $ctx->js('https://example.com/highlight.min.js', defer: true);
    return fn () => (/* ... */);
};

このサイトでもコードハイライト用 highlight.js を、<pre><code> を持つドキュメントページだけで $ctx->js() 読み込みしています(ページ単位のスクリプト 参照)。

エスケープハッチ#

リッチな UI が必要な箇所は React アイランドでクライアント描画に逃がせます(React アイランド 参照)。重い部分は 遅延コンポーネント#[Defer] / fc(..., defer: ...)/_defer/{name} を usephp.js が後追い取得)にできます。

Relayer との関係#

あなたのアプリ
  └─ Relayer        ルーティング / DI / 認証 / キャッシュ / バリデーション / DB
       └─ usePHP    PSX コンパイル / Element 描画 / 状態 / アイランド / 遅延

usePHP は「描画の心臓部」。Relayer はその周りに Web アプリの骨格を足したもの、と捉えると全体像が掴みやすいです。次は ルーティングとページ へ。

最終更新: 2026-05-20
変更履歴 (3)
  • Add editor syntax highlighting section (VS Code / Neovim) referencing use-php editors/
  • バージョン差分表記(vX.Y.Z 追加/破壊的変更/依存バージョン注記)を削除し現在形に整理
  • 新規作成