usePHPというライブラリを作っている

ここ数年PHPを使った開発を避けるようにしてきた。
理由は簡単で、Vercel + Nextjs(App Router)が結構強力な存在だったから。

ここ数年PHPを使わない理由

PHPでバックエンド作ってAPI仕様書を用意して、フロントエンド側として独立して開発みたいなの一見良さそうに見えるけど結構ツラミがある

  1. APIがバックエンド視点で設計されていることが多い
  2. SPAなんだかんだで辛い(認証とか・SEOとか)
  3. バックエンドエンジニア・フロントエンドエンジニアの分離によるコミュニケーションコスト

こういう経験を経て昔の開発スタイルを振り返ると、Webアプリケーションを作っているのにバックエンド/フロントエンドで分割するのはよくないなぁと感じた。
もちろんどっちが得意みたいな領域はでてきてしまうけど、それでも僕はやっぱWebアプリケーションエンジニアで、Webアプリを作って価値を届けるみたいなところに軸を置きたかったので、そうなるとやっぱVercel + Next.jsがすごくハマっていた。
というところで、最近はNext.jsばかりだった。

またPHPに興味を持ち始めた

しかし、この記事をみてからPHPだいぶ進化したしもう一度使ってみたいなと考えるようになった。

ただし、PHPをバックエンドAPIとしてだけ使うのは、自分の考えとは合わない。
言語的な進化がかなりいい方向に来ているPHPだし、Hypertext Preprocessorなんだし、もっとハイパーテキスト(HTML)作る方向に立ち返るのもありなのでは?と思ったわけです。

そこで、ある条件のライブラリを作ればHypertext Preprocessorとしての立場を取り戻せるのでは?と考えた。

  • Reactライクなマークアップの記述
  • Reactライクなステート管理
  • HTMXのようなJavaScriptレスでも動く構造
  • とはいえHotwireのような部分的再レンダリングにも対応

みたいな課題がクリアできるライブラリを作ればいいのでは?と

そこで作ったのがこのライブラリです

polidog/usePHP

usePHPとは?

「React Hooks風の書き心地で、最小限のJavaScriptでサーバードリブンUIを実現するフレームワーク。」
という形のフレームワークです。

Reactみたいにコンポーネントを組み合わせていくんだけど、以下のように簡単にコンポーネントを作成できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
// components/Counter.php

use Polidog\UsePhp\Html\H;
use Polidog\UsePhp\Runtime\Element;

use function Polidog\UsePhp\Runtime\fc;
use function Polidog\UsePhp\Runtime\useState;

// Define a counter component with fc() wrapper
$Counter = fc(function(array $props): Element {
    [$count, $setCount] = useState($props['initial'] ?? 0);

    return H::div(
        className: 'counter',
        children: [
            H::span(children: "Count: {$count}"),
            H::button(
                onClick: fn() => $setCount($count + 1),
                children: '+'
            ),
            H::button(
                onClick: fn() => $setCount($count - 1),
                children: '-'
            ),
        ]
    );
}, 'counter'); // 'counter' is the key for state management

もちろんonClickで設定された関数はちゃんと実行されるようになっている。
ただ、PHPではJavaScriptのようにブラウザでは実行できないので、formタグに変換してpostする形になる。

1
H::button(onClick: fn() => $setCount($count + 1), children: '+')

このようなコードは以下のようなHTMLに変換される。

1
2
3
4
5
<form method="post" data-usephp-form style="display:inline;">
  <input type="hidden" name="_usephp_component" value="counter#0" />
  <input type="hidden" name="_usephp_action" value='{"type":"setState","payload":{"index":0,"value":1}}' />
  <button type="submit">+</button>
</form>

usephp.js(わずか約40行)を読み込んでいれば + ボタンを押せば部分的に再描画可能となる。
usephp.jsを読み込まれてない場合は画面が全部再描画されてしまうが、それでもステートは保持できるのでそこまで問題ない。

useStateのステート管理について

ステート管理には3つの手法がある

  1. session
  2. memory
  3. snapshot

session

これはphpのsessionで管理する方法で、もっともPHPらしいステート管理手法とも言える。
Sessionに入れておけば、リロードしても状態が変わるわけではないので、非常に便利。

memory

これはリクエストごとにリセットされる。ページ遷移やリロードした場合は状態が保持されない。
一時的なUI状態だったり、モーダルとかユーザーの操作によるアラート表示みたいなのに使える。

snapshot

HTMLに状態を埋め込むタイプ。サーバーレス環境でも動作する。
より細かいステート管理したい場合は役立つはず。

ちなみにSnapshotはRouterと組み合わせることで、どの画面までステートを共有するかコントロールすることも可能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Isolated(デフォルト)- ページ固有の状態
$router->get('/page', PageComponent::class)->isolatedSnapshot();

// Persistent - 遷移時にURLで状態を渡す
$router->get('/cart', CartComponent::class)->persistentSnapshot();

// Session - セッションに状態を保存
$router->get('/wizard', WizardComponent::class)->sessionSnapshot();

// Shared - 特定のルート間で状態を共有
$router->get('/step1', Step1Component::class)->sharedSnapshot('checkout');
$router->get('/step2', Step2Component::class)->sharedSnapshot('checkout');

最後に

正直な話このusePHPの実装はAI(Claude Code)が実装しているので、自分が作ったと言っていいのか戸惑うときがある?企画しただけだろ?って言われたらそうかもしれない。
でも、もう一度PHPを使いたいとという気持ちと、Json Preprocessorになりがちな昨今のPHPがもう一度Hypertext Preprocessorとして使われてほしいという願いは嘘じゃないし誰にも負けない。

だから今後も作り続けようと思う。

カテゴリ

comments powered by Disqus