← Blog一覧へ

PHPでスマホアプリが作れるのは、BladeがHTMLを出さなくなったから

#tech#PHP#NativePHP#Laravel

「PHPでスマホアプリが作れるらしい」というタイトルで、SAPPORO ENGINEER BASE #16に登壇しました。資料を作りながら一番驚いたのは、Bladeが返すものがHTMLではなくなっていたことです。

資料はPHPでスマホアプリが作れるらしいに置いてあります。

PHPそのものをアプリの中に入れた

アプリはプログラムがスマホの中に入っているので、圏外でも動き、カメラや位置情報も触れます。そこに置けるのはiPhoneならSwift、AndroidならKotlin、両方まとめてやるならFlutterやReact Nativeで、PHPは選択肢に入っていませんでした。

NativePHPがやったのは、PHPそのものをアプリの中に丸ごと入れることです。

A pre-compiled version of PHP is bundled with your code into a Swift/Kotlin shell application. (コンパイル済みのPHPが、あなたのコードとともにSwift/Kotlinのシェルアプリケーションに同梱される)

— NativePHP「Introduction — NativePHP mobile v4

サーバーもネットワークも介さず、アプリ自身がPHPを実行する。Laravelの作者であるTaylor Otwellの反応が、そのまま資料の1枚になりました。

I think it’s super cool… it’s just wild (すごくクールだと思う…ただただ驚きだ)

— NativePHP「Baking Delicious Native Apps

WebViewをうまく隠しているのだと思っていた

最初に想像したのは、画面いっぱいにWebViewを貼る構成でした。WebViewはブラウザの描画エンジンを部品としてアプリに埋め込む仕組みで、中身はHTMLとCSSです。PHPが端末に入ったところで、見えている画面はどうせブラウザだろう、と思っていました。

引っかかったのは公式サイトに出ている数字です。

It’s ridiculously fast, and can render screens well above 240fps, leaving lots of headroom for demanding computation. (ばかげた速さで、240fpsを大きく超えて画面を描画できる。重い計算のための余裕も十分に残る)

— NativePHP「Baking Delicious Native Apps

WebViewの中でDOMを組み立てて出す数字ではない。読み直すと、想像していた構成とは違うことが最初のページに書いてありました。

You’re not sandboxed into a web view — NativePHP for Mobile renders full native UI by default (web viewに閉じ込められることはない。NativePHP for Mobileは既定で完全なネイティブUIを描画する)

— NativePHP「Introduction — NativePHP mobile v4

Bladeの出力先がHTMLではなくなった

v4のネイティブUIはSuperNativeと呼ばれています。

SuperNative is our name for a combination of technologies that enable PHP to produce platform-native UI at blistering speeds. (SuperNativeは、PHPが猛烈な速さでプラットフォーム固有のネイティブUIを生み出すことを可能にする技術群につけた名前だ)

— NativePHP「SuperNative Introduction

書き手が触るのはEDGE(Element Definition and Generation Engine)というコンポーネント群で、見た目はBladeのタグです。クラスの当て方もTailwind風のユーティリティクラスなので、Laravelを書いている人間にとって新しい記法はほとんどありません。

<native:column class="w-full h-full p-4 gap-4 bg-theme-background text-theme-on-background">
    <native:text class="text-2xl font-bold">Welcome</native:text>
    <native:button label="Refresh" @press="refresh" />
</native:column>

この <native:button> は、スタイルを当てた <div> ではありません。iOSではSwiftUI、AndroidではJetpack Composeという、それぞれのOSが標準で持っているUIの仕組みのビューになります。同じOSの他のアプリと同じボタンが出てくる、ということです。

では、Bladeは何を渡しているのか。ドキュメントはrender、publish、mountの3段階で説明していて、PHPが組み立てた要素ツリーを、固定レイアウトのバイナリレコードとして共有メモリに書き込むと書かれています。

PHP and the UI live in the same process and share memory. Publishing a frame means writing bytes into a buffer, not encoding JSON and shipping it over a bridge. (PHPとUIは同じプロセスに存在し、メモリを共有している。フレームを公開するとは、バッファにバイト列を書き込むことであって、JSONにエンコードしてブリッジ越しに送ることではない)

— NativePHP「The Renderer

Blade側から見た説明は、SuperNativeのページの「What SuperNative is not」にありました。

Neither is it a transpiler or HTML-to-native converter. We’ve built our own Blade engine that converts real Blade components into a simple binary representation instead of HTML. (トランスパイラでもHTML→ネイティブの変換器でもない。私たちは、本物のBladeコンポーネントをHTMLではなく単純なバイナリ表現に変換する、独自のBladeエンジンを作った)

— NativePHP「SuperNative Introduction

Bladeエンジンの出力先そのものが、HTMLからバイト列に差し替わっている。更新のたびに値をJSONにしてブリッジ越しに渡す構成と比べると、境界で通行料を払う工程がまるごとありません。240fpsという数字は、ここから出てきたものだと理解しました。

WebViewは消えたのではなく、1つのコンポーネントになった

WebViewはよくできています。それでも差が出るところは残る。スクロールの物理、テキストの描画、画面遷移、コンテキストメニュー、ダークモード、文字サイズ設定、スクリーンリーダー。ブラウザ側で近似できるものはありますが、OS純正なら標準装備です。

v4ではネイティブUIが既定になり、WebViewは画面に埋め込むコンポーネントのひとつとして残りました。

<webview php url="/" fullscreen />

adopt SuperNative one screen at a time whenever you’re ready — or not at all (準備ができたときに1画面ずつSuperNativeを採用すればいい。あるいは、まったく採用しなくてもいい)

— NativePHP「SuperNative Introduction

既存のBladeやLivewireの画面を抱えたまま、移したい画面から移せる。全部を書き直す前提ではありませんでした。

触るのは3行、ただし新規のLaravelでは素通りしなかった

デモ用のリポジトリ(nativephp/super-native)を動かすだけなら3コマンドです。登壇ではここまでを見せました。

composer install
php artisan native:install
php artisan native:run

事前に手元で作ったのは新規のLaravelアプリで、こちらは2行目の前で止まりました。Laravel 13が入れるguzzleと、nativephp/mobile 4.2.0が要求するguzzleが噛み合いません。

- nativephp/mobile[...4.2.0] require guzzlehttp/guzzle ^7.9 -> found guzzlehttp/guzzle[7.9.0, ..., 7.15.5]
  but the package is fixed to 8.1.0 (lock file version) by a partial update

laravel/framework 自体は ^7.8.2 || ^8.0 を許容しているので、guzzleを7系に落とせば解決します。依存ごと解決させれば通りました。

-composer require nativephp/mobile
+composer require nativephp/mobile -W

まとめ

PHPを触れればモバイルアプリが作れる、という時代が近づいているのかもしれません。