diff --git a/docs/ja/CONTRIBUTING.md b/docs/ja/CONTRIBUTING.md new file mode 100644 index 00000000..ea2e8ee4 --- /dev/null +++ b/docs/ja/CONTRIBUTING.md @@ -0,0 +1,219 @@ +# コントリビューション + +## PHPのコンパイル + +### Dockerを使用する場合(Linux) + +開発用Dockerイメージをビルドします: + +```console +docker build -t frankenphp-dev -f dev.Dockerfile . +docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev +``` + +このイメージには通常の開発ツール(Go、GDB、Valgrind、Neovimなど)が含まれており、PHP設定ファイルは以下の場所に配置されます。 + +- php.ini: `/etc/frankenphp/php.ini` 開発用のプリセットが適用されたphp.iniファイルがデフォルトで提供されます。 +- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini` +- PHP拡張モジュール: `/usr/lib/frankenphp/modules/` + +お使いのDockerのバージョンが23.0未満の場合、dockerignore[パターンの問題](https://github.com/moby/moby/pull/42676)によりビルドに失敗する可能性があります。以下のように`.dockerignore`にディレクトリを追加してください。 + +```patch + !testdata/*.php + !testdata/*.txt ++!caddy ++!internal +``` + +### Dockerを使用しない場合(LinuxおよびmacOS) + +[ソースからのコンパイル手順](https://frankenphp.dev/docs/compile/)に従い、`--debug`設定フラグを渡してください。 + +## テストスイートの実行 + +```console +go test -tags watcher -race -v ./... +``` + +## Caddyモジュール + +FrankenPHPのCaddyモジュール付きでCaddyをビルドします: + +```console +cd caddy/frankenphp/ +go build -tags watcher,brotli,nobadger,nomysql,nopgx +cd ../../ +``` + +FrankenPHPのCaddyモジュール付きでCaddyを実行します: + +```console +cd testdata/ +../caddy/frankenphp/frankenphp run +``` + +サーバーは`127.0.0.1:80`で待ち受けています: + +> [!NOTE] +> Dockerを使用している場合は、コンテナのポート80をバインドするか、コンテナ内で実行する必要があります。 + +```console +curl -vk http://127.0.0.1/phpinfo.php +``` + +## 最小構成のテストサーバー + +最小構成のテストサーバーをビルドします: + +```console +cd internal/testserver/ +go build +cd ../../ +``` + +テストサーバーを実行します: + +```console +cd testdata/ +../internal/testserver/testserver +``` + +サーバーは`127.0.0.1:8080`で待ち受けています: + +```console +curl -v http://127.0.0.1:8080/phpinfo.php +``` + +## Dockerイメージをローカルでビルドする + +bakeプランを出力します: + +```console +docker buildx bake -f docker-bake.hcl --print +``` + +amd64用のFrankenPHPイメージをローカルでビルドします: + +```console +docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64" +``` + +arm64用のFrankenPHPイメージをローカルでビルドします: + +```console +docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64" +``` + +arm64とamd64用のFrankenPHPイメージをスクラッチからビルドしてDocker Hubにプッシュします: + +```console +docker buildx bake -f docker-bake.hcl --pull --no-cache --push +``` + +## 静的ビルドでのセグメンテーション違反のデバッグ + +1. GitHubからFrankenPHPバイナリのデバッグ版をダウンロードするか、デバッグシンボルを含む独自の静的ビルドを作成します: + + ```console + docker buildx bake \ + --load \ + --set static-builder.args.DEBUG_SYMBOLS=1 \ + --set "static-builder.platform=linux/amd64" \ + static-builder + docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp + ``` + +2. 現在使用している`frankenphp`を、デバッグ版のFrankenPHP実行ファイルに置き換えます +3. 通常通りFrankenPHPを起動します(あるいは、GDBで直接FrankenPHPを開始することもできます:`gdb --args frankenphp run`) +4. GDBでプロセスにアタッチします: + + ```console + gdb -p `pidof frankenphp` + ``` + +5. 必要に応じて、GDBシェルで`continue`と入力します +6. FrankenPHPをクラッシュさせます +7. GDBシェルで`bt`と入力します +8. 出力結果をコピーします + +## GitHub Actionsでのセグメンテーション違反のデバッグ + +1. `.github/workflows/tests.yml`を開きます +2. PHPデバッグシンボルを有効にします + + ```patch + - uses: shivammathur/setup-php@v2 + # ... + env: + phpts: ts + + debug: true + ``` + +3. `tmate`を有効にしてコンテナに接続できるようにします + + ```patch + - name: Set CGO flags + run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV" + + - run: | + + sudo apt install gdb + + mkdir -p /home/runner/.config/gdb/ + + printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit + + - uses: mxschmitt/action-tmate@v3 + ``` + +4. コンテナに接続します +5. `frankenphp.go`を開きます +6. `cgosymbolizer`を有効にします + + ```patch + - //_ "github.com/ianlancetaylor/cgosymbolizer" + + _ "github.com/ianlancetaylor/cgosymbolizer" + ``` + +7. モジュールを取得します:`go get` +8. コンテナ内で、GDBなどを使用できます: + + ```console + go test -tags watcher -c -ldflags=-w + gdb --args frankenphp.test -test.run ^MyTest$ + ``` + +9. バグが修正されたら、これらの変更をすべて元に戻します + +## その他の開発リソース + +- [uWSGIでのPHP埋め込み](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c) +- [NGINX UnitでのPHP埋め込み](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c) +- [Go言語でのPHP埋め込み (go-php)](https://github.com/deuill/go-php) +- [Go言語でのPHP埋め込み (GoEmPHP)](https://github.com/mikespook/goemphp) +- [C++でのPHP埋め込み](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7) +- [Sara Golemon 著『Extending and Embedding PHP』](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false) +- [TSRMLS_CCとは何か?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html) +- [SDL バインディング](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main) + +## Docker関連リソース + +- [Bakeファイル定義](https://docs.docker.com/build/customize/bake/file-definition/) +- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/) + +## 便利なコマンド + +```console +apk add strace util-linux gdb +strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1 +``` + +## ドキュメントの翻訳 + +新しい言語でドキュメントとサイトを翻訳するには、 +以下の手順で行ってください。 + +1. このリポジトリの`docs/`ディレクトリに、言語の2文字ISOコードを名前にした新しいディレクトリを作成します +2. `docs/`ディレクトリのルートにある全ての`.md`ファイルを新しいディレクトリにコピーします(翻訳のソースとして常に英語版を使用してください。英語版が最新版だからです) +3. ルートディレクトリから`README.md`と`CONTRIBUTING.md`ファイルを新しいディレクトリにコピーします +4. ファイルの内容を翻訳しますが、ファイル名は変更せず、`> [!`で始まる文字列も翻訳しないでください(これはGitHub用の特別なマークアップです) +5. 翻訳でプルリクエストを作成します +6. [サイトリポジトリ](https://github.com/dunglas/frankenphp-website/tree/main)で、`content/`、`data/`、`i18n/`ディレクトリの翻訳ファイルをコピーして翻訳します +7. 作成されたYAMLファイルの値を翻訳します +8. サイトリポジトリでプルリクエストを開きます diff --git a/docs/ja/README.md b/docs/ja/README.md new file mode 100644 index 00000000..fe5da0d4 --- /dev/null +++ b/docs/ja/README.md @@ -0,0 +1,110 @@ +# FrankenPHP: PHPのためのモダンなアプリケーションサーバー + +

FrankenPHP

+ +FrankenPHPは、[Caddy](https://caddyserver.com/) Webサーバーをベースに構築された、PHPのためのモダンなアプリケーションサーバーです。 + +FrankenPHPは、[_Early Hints_](https://frankenphp.dev/docs/early-hints/)、[ワーカーモード](https://frankenphp.dev/docs/worker/)、[リアルタイム機能](https://frankenphp.dev/docs/mercure/)、自動HTTPS、HTTP/2、HTTP/3などの驚異的な機能により、あなたのPHPアプリに強力な力を与えます。 + +FrankenPHPはあらゆるPHPアプリと連携し、ワーカーモードの公式統合によってLaravelやSymfonyプロジェクトをこれまで以上に高速化します。 + +また、FrankenPHPはスタンドアロンのGoライブラリとしても利用可能で、`net/http`を使って任意のアプリにPHPを埋め込むことができます。 + +[**詳しくは** _frankenphp.dev_](https://frankenphp.dev)と、このスライド資料もご参照ください: + +Slides + +## はじめに + +### スタンドアロンバイナリ + +LinuxとmacOS向けに、[PHP 8.4](https://www.php.net/releases/8.4/en.php)と人気のPHP拡張モジュールを含む静的な +FrankenPHPバイナリを提供しています。 + +Windowsをお使いの場合は、[WSL](https://learn.microsoft.com/windows/wsl/)を使用してFrankenPHPを実行してください。 + +[FrankenPHPをダウンロード](https://github.com/php/frankenphp/releases)するか、以下のコマンドを +ターミナルにコピーして実行すると、環境に合ったバージョンが自動的にインストールされます: + +```console +curl https://frankenphp.dev/install.sh | sh +mv frankenphp /usr/local/bin/ +``` + +現在のディレクトリのコンテンツを配信するには、以下を実行してください: + +```console +frankenphp php-server +``` + +コマンドラインスクリプトも実行できます: + +```console +frankenphp php-cli /path/to/your/script.php +``` + +### Docker + +また、[Dockerイメージ](https://frankenphp.dev/docs/docker/)も利用可能です: + +```console +docker run -v .:/app/public \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +ブラウザで`https://localhost`にアクセスして、FrankenPHPをお楽しみください! + +> [!TIP] +> +> `https://127.0.0.1`ではなく、`https://localhost`を使用して、自己署名証明書を受け入れてください。 +> 使用するドメインを変更したい場合は、[`SERVER_NAME` 環境変数](docs/config.md#environment-variables)を設定してください。 + +### Homebrew + +FrankenPHPはmacOSおよびLinux向けに[Homebrew](https://brew.sh)パッケージとしても利用可能です。 + +インストール方法: + +```console +brew install dunglas/frankenphp/frankenphp +``` + +現在のディレクトリのコンテンツを配信するには、以下を実行してください: + +```console +frankenphp php-server +``` + +## ドキュメント + +- [クラシックモード](https://frankenphp.dev/docs/classic/) +- [ワーカーモード](https://frankenphp.dev/docs/worker/) +- [Early Hintsサポート(103 HTTPステータスコード)](https://frankenphp.dev/docs/early-hints/) +- [リアルタイム](https://frankenphp.dev/docs/mercure/) +- [大きな静的ファイルの効率的な提供](https://frankenphp.dev/docs/x-sendfile/) +- [設定](https://frankenphp.dev/docs/config/) +- [Dockerイメージ](https://frankenphp.dev/docs/docker/) +- [本番環境でのデプロイ](https://frankenphp.dev/docs/production/) +- [パフォーマンス最適化](https://frankenphp.dev/docs/performance/) +- [**スタンドアロン**、自己実行可能なPHPアプリの作成](https://frankenphp.dev/docs/embed/) +- [静的バイナリの作成](https://frankenphp.dev/docs/static/) +- [ソースからのコンパイル](https://frankenphp.dev/docs/compile/) +- [FrankenPHPの監視](https://frankenphp.dev/docs/metrics/) +- [Laravel統合](https://frankenphp.dev/docs/laravel/) +- [既知の問題](https://frankenphp.dev/docs/known-issues/) +- [デモアプリ(Symfony)とベンチマーク](https://github.com/dunglas/frankenphp-demo) +- [Goライブラリドキュメント](https://pkg.go.dev/github.com/dunglas/frankenphp) +- [コントリビューションとデバッグ](https://frankenphp.dev/docs/contributing/) + +## 例とスケルトン + +- [Symfony](https://github.com/dunglas/symfony-docker) +- [API Platform](https://api-platform.com/docs/symfony) +- [Laravel](https://frankenphp.dev/docs/laravel/) +- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp) +- [WordPress](https://github.com/StephenMiracle/frankenwp) +- [Drupal](https://github.com/dunglas/frankenphp-drupal) +- [Joomla](https://github.com/alexandreelise/frankenphp-joomla) +- [TYPO3](https://github.com/ochorocho/franken-typo3) +- [Magento2](https://github.com/ekino/frankenphp-magento2) diff --git a/docs/ja/classic.md b/docs/ja/classic.md new file mode 100644 index 00000000..17e99641 --- /dev/null +++ b/docs/ja/classic.md @@ -0,0 +1,11 @@ +# クラシックモードの使用 + +追加の設定を行わなくても、FrankenPHPはクラシックモードで動作します。このモードでは、FrankenPHPは従来のPHPサーバーのように機能し、PHPファイルを直接提供します。これにより、PHP-FPMやmod_phpを使ったApacheの置き換えとしてシームレスに利用できます。 + +Caddyと同様に、FrankenPHPは無制限の接続を受け付け、[固定数のスレッド](config.md#caddyfile-config)でそれらを処理します。受け入れられキューに入れられる接続の数は、利用可能なシステムリソースによってのみ制限されます。 +PHPスレッドプールは、起動時に初期化された固定数のスレッドで動作し、これはPHP-FPMの静的モードに相当します。また、PHP-FPMの動的モードと同様に、[実行時にスレッドを自動的にスケール](performance.md#max_threads)させることも可能です。 + +キューに入った接続は、PHPスレッドが空くまで無期限に待機します。これを避けるために、FrankenPHP のグローバル設定内の `max_wait_time` [設定](config.md#caddyfile-config)を使って、リクエストが空きスレッドを待てる最大時間を制限し、それを超えるとリクエストが拒否されるようにできます。 +加えて、[Caddy側で適切な書き込みタイムアウト](https://caddyserver.com/docs/caddyfile/options#timeouts)を設定することも可能です。 + +各Caddyインスタンスは、1つのFrankenPHPスレッドプールのみを起動し、すべての`php_server`ブロック間でこのプールを共有します。 diff --git a/docs/ja/compile.md b/docs/ja/compile.md new file mode 100644 index 00000000..156a809c --- /dev/null +++ b/docs/ja/compile.md @@ -0,0 +1,127 @@ +# ソースからのコンパイル + +このドキュメントでは、PHPを動的ライブラリとしてロードするFrankenPHPバイナリの作成方法を説明します。 +これが推奨される方法です。 + +または、[完全静的およびほぼ静的なビルド](static.md)も作成できます。 + +## PHPのインストール + +FrankenPHPはPHP 8.2以上と互換性があります。 + +### Homebrewを使用する場合(LinuxとMac) + +FrankenPHPと互換性のあるlibphpのバージョンをインストールする最も簡単な方法は、[Homebrew PHP](https://github.com/shivammathur/homebrew-php)が提供するZTSパッケージを使用することです。 + +まず、まだインストールしていない場合は[Homebrew](https://brew.sh)をインストールしてください。 + +次に、PHPのZTSバリアント、Brotli(オプション、圧縮サポート用)、watcher(オプション、ファイル変更検出用)をインストールします: + +```console +brew install shivammathur/php/php-zts brotli watcher +brew link --overwrite --force shivammathur/php/php-zts +``` + +### PHPをコンパイルする場合 + +別の方法として、FrankenPHPに必要なオプションを指定してPHPをソースからコンパイルすることもできます。 + +まず、[PHPのソース](https://www.php.net/downloads.php)を取得して展開します: + +```console +tar xf php-* +cd php-*/ +``` + +次に、プラットフォームに応じて必要なオプションを指定して`configure`スクリプトを実行します。 +以下の`./configure`フラグは必須ですが、例えば拡張機能モジュールや追加機能をコンパイルするために他のフラグを追加することもできます。 + +#### Linux + +```console +./configure \ + --enable-embed \ + --enable-zts \ + --disable-zend-signals \ + --enable-zend-max-execution-timers +``` + +#### Mac + +[Homebrew](https://brew.sh/)パッケージマネージャーを使用して、必須およびオプションの依存関係をインストールします: + +```console +brew install libiconv bison brotli re2c pkg-config watcher +echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc +``` + +その後、以下のようにconfigureスクリプトを実行します: + +```console +./configure \ + --enable-embed \ + --enable-zts \ + --disable-zend-signals \ + --with-iconv=/opt/homebrew/opt/libiconv/ +``` + +#### PHPのコンパイル + +最後に、PHPをコンパイルしてインストールします: + +```console +make -j"$(getconf _NPROCESSORS_ONLN)" +sudo make install +``` + +## オプション依存関係のインストール + +FrankenPHPの一部の機能は、システムにインストールされているオプションの依存パッケージに依存しています。 +または、Goコンパイラにビルドタグを渡すことで、これらの機能を無効にできます。 + +| 機能 | 依存関係 | 無効にするためのビルドタグ | +|--------------------------------|-----------------------------------------------------------------------|-------------------------| +| Brotli圧縮 | [Brotli](https://github.com/google/brotli) | nobrotli | +| ファイル変更時のワーカー再起動 | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher | + +## Goアプリのコンパイル + +いよいよ最終的なバイナリをビルドできるようになりました。 + +### xcaddyを使う場合 + +推奨される方法は、[xcaddy](https://github.com/caddyserver/xcaddy)を使用してFrankenPHPをコンパイルする方法です。 +`xcaddy`を使うと、[Caddyのカスタムモジュール](https://caddyserver.com/docs/modules/)やFrankenPHP拡張を簡単に追加できます: + +```console +CGO_ENABLED=1 \ +XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ +CGO_CFLAGS=$(php-config --includes) \ +CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ +xcaddy build \ + --output frankenphp \ + --with github.com/dunglas/frankenphp/caddy \ + --with github.com/dunglas/mercure/caddy \ + --with github.com/dunglas/vulcain/caddy + # 追加のCaddyモジュールとFrankenPHP拡張をここに追加 +``` + +> [!TIP] +> +> musl libc(Alpine Linuxのデフォルト)とSymfonyを使用している場合、 +> デフォルトのスタックサイズを増やす必要がある場合があります。 +> そうしないと、`PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`のようなエラーが発生する可能性があります。 +> +> これを行うには、`XCADDY_GO_BUILD_FLAGS`環境変数を +> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`のようなものに変更してください +> (アプリの要件に応じてスタックサイズの値を変更してください)。 + +### xcaddyを使用しない場合 + +代替として、`xcaddy`を使わずに`go`コマンドを直接使ってFrankenPHPをコンパイルすることも可能です: + +```console +curl -L https://github.com/php/frankenphp/archive/refs/heads/main.tar.gz | tar xz +cd frankenphp-main/caddy/frankenphp +CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx +``` diff --git a/docs/ja/config.md b/docs/ja/config.md new file mode 100644 index 00000000..f11e117a --- /dev/null +++ b/docs/ja/config.md @@ -0,0 +1,306 @@ +# 設定 + +FrankenPHP、Caddy、そしてMercureやVulcainモジュールは、[Caddyでサポートされる形式](https://caddyserver.com/docs/getting-started#your-first-config)を使用して設定できます。 + +[Dockerイメージ](docker.md)では、`Caddyfile`は`/etc/frankenphp/Caddyfile`に配置されています。 +静的バイナリは、`frankenphp run`コマンドを実行したディレクトリ内の`Caddyfile`を参照します。 +また、`-c`または`--config`オプションでカスタムのパスを指定できます。 + +PHP自体の設定は[`php.ini` ファイルを使用](https://www.php.net/manual/en/configuration.file.php)して行えます。 + +インストール方法に応じて、PHPインタープリターは上記いずれかの場所にある設定ファイルを参照します。 + +## Docker + +- `php.ini`: `/usr/local/etc/php/php.ini`(デフォルトでは`php.ini`は含まれていません) +- 追加の設定ファイル: `/usr/local/etc/php/conf.d/*.ini` +- PHP拡張モジュール: `/usr/local/lib/php/extensions/no-debug-zts-/` +- PHPプロジェクトが提供する公式テンプレートをコピーすることを推奨します: + +```dockerfile +FROM dunglas/frankenphp + +# 本番環境: +RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini + +# または開発環境: +RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini +``` + +## RPMおよびDebianパッケージ + +- `php.ini`: `/etc/frankenphp/php.ini`(本番環境向けのプリセットの`php.ini`ファイルがデフォルトで提供されます) +- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini` +- PHP拡張モジュール: `/usr/lib/frankenphp/modules/` + +## 静的バイナリ + +- `php.ini`: `frankenphp run`または`frankenphp php-server`を実行したディレクトリ内、なければ`/etc/frankenphp/php.ini`を参照 +- 追加の設定ファイル: `/etc/frankenphp/php.d/*.ini` +- PHP拡張モジュール: ロードできません、バイナリ自体にバンドルする必要があります +- [PHPソース](https://github.com/php/php-src/)で提供される`php.ini-production`または`php.ini-development`のいずれかをコピーしてください + +## Caddyfileの設定 + +`php_server`または`php`の[HTTPディレクティブ](https://caddyserver.com/docs/caddyfile/concepts#directives)は、サイトブロック内で使用してPHPアプリを配信できます。 + +最小構成の例: + +```caddyfile +localhost { + # 圧縮を有効化(オプション) + encode zstd br gzip + # 現在のディレクトリ内のPHPファイルを実行し、アセットを配信 + php_server +} +``` + +グローバルオプションを使用してFrankenPHPを明示的に設定することもできます: +`frankenphp`の[グローバルオプション](https://caddyserver.com/docs/caddyfile/concepts#global-options)を使用してFrankenPHPを構成できます。 + +```caddyfile +{ + frankenphp { + num_threads # 開始するPHPスレッド数を設定します。デフォルト: 利用可能なCPU数の2倍。 + max_threads # 実行時に追加で開始できるPHPスレッドの最大数を制限します。デフォルト: num_threads。 'auto'を設定可能。 + max_wait_time # リクエストがタイムアウトする前にPHPのスレッドが空くのを待つ最大時間を設定します。デフォルト: 無効。 + php_ini # php.iniのディレクティブを設定します。複数のディレクティブを設定するために何度でも使用できます。 + worker { + file # ワーカースクリプトのパスを設定します。 + num # 開始するPHPスレッド数を設定します。デフォルト: 利用可能なCPU数の2倍。 + env # 追加の環境変数を指定された値に設定する。複数の環境変数に対して複数回指定することができます。 + watch # ファイル変更を監視するパスを設定します。複数のパスに対して複数回指定できます。 + name # ワーカーの名前を設定します。ログとメトリクスで使用されます。デフォルト: ワーカーファイルの絶対パス + max_consecutive_failures # workerが不健全とみなされるまでの、連続失敗の最大回数を設定します。 -1 はワーカーを常に再起動することを意味します。デフォルトは 6 です。 + } + } +} + +# ... +``` + +代わりに、`worker`オプションのワンライナー形式を使用することもできます: + +```caddyfile +{ + frankenphp { + worker + } +} + +# ... +``` + +同じサーバーで複数のアプリを提供する場合は、複数のワーカーを定義することもできます: + +```caddyfile +app.example.com { + root /path/to/app/public + php_server { + root /path/to/app/public # キャッシュ効率を高める + worker index.php + } +} + +other.example.com { + root /path/to/other/public + php_server { + root /path/to/other/public + worker index.php + } +} + +# ... +``` + +通常は`php_server`ディレクティブを使えば十分ですが、 +より細かい制御が必要な場合は、より低レベルの`php`ディレクティブを使用できます。 +`php`ディレクティブは、対象がPHPファイルかどうかを確認せず、すべての入力をPHPに渡します。 +詳しくは[パフォーマンスページ](performance.md#try_files)をお読みください。 + +`php_server`ディレクティブの使用は、以下の設定と同等です: + +```caddyfile +route { + # ディレクトリへのリクエストに末尾スラッシュを追加 + @canonicalPath { + file {path}/index.php + not path */ + } + redir @canonicalPath {path}/ 308 + # 要求されたファイルが存在しない場合は、indexファイルを試行 + @indexFiles file { + try_files {path} {path}/index.php index.php + split_path .php + } + rewrite @indexFiles {http.matchers.file.relative} + # FrankenPHP! + @phpFiles path *.php + php @phpFiles + file_server +} +``` + +`php_server`と`php`ディレクティブには以下のオプションがあります: + +```caddyfile +php_server [] { + root # サイトのルートフォルダを設定します。デフォルト: `root`ディレクティブ。 + split_path # URIを2つの部分に分割するための部分文字列を設定します。最初にマッチする部分文字列がURIから「パス情報」を分割するために使用されます。最初の部分はマッチする部分文字列で接尾辞が付けられ、実際のリソース(CGIスクリプト)名とみなされます。2番目の部分はスクリプトが使用する PATH_INFO に設定されます。デフォルト: `.php` + resolve_root_symlink false # シンボリックリンクが存在する場合`root`ディレクトリをシンボリックリンクの評価によって実際の値に解決することを無効にする(デフォルトで有効)。 + env # 追加の環境変数を指定された値に設定する。複数の環境変数を指定する場合は、複数回指定することができます。 + file_server off # 組み込みのfile_serverディレクティブを無効にします。 + worker { # このサーバー固有のワーカーを作成します。複数のワーカーに対して複数回指定できます。 + file # ワーカースクリプトへのパスを設定します。php_serverのルートからの相対パスとなります。 + num # 起動するPHPスレッド数を設定します。デフォルトは利用可能なスレッド数の 2 倍です。 + name # ログとメトリクスで使用されるワーカーの名前を設定します。デフォルト: ワーカーファイルの絶対パス。php_server ブロックで定義されている場合は、常にm#で始まります。 + watch # ファイルの変更を監視するパスを設定する。複数のパスに対して複数回指定することができる。 + env # 追加の環境変数を指定された値に設定する。複数の環境変数を指定する場合は、複数回指定することができます。このワーカーの環境変数もphp_serverの親から継承されますが、 ここで上書きすることもできます。 + match # ワーカーをパスパターンにマッチさせます。try_filesを上書きし、php_serverディレクティブでのみ使用できます。 + } + worker # グローバルfrankenphpブロックのような短縮形式も使用できます。 +} +``` + +### ファイルの変更監視 + +ワーカーはアプリケーションを一度だけ起動してメモリに保持するため、 +PHPファイルに変更を加えても即座には反映されません。 + +代わりに、`watch`ディレクティブを使用してファイル変更時にワーカーを再起動させることができます。 +これは開発環境において有用です。 + +```caddyfile +{ + frankenphp { + worker { + file /path/to/app/public/worker.php + watch + } + } +} +``` + +`watch`ディレクトリが指定されていない場合、`./**/*.{php,yaml,yml,twig,env}`にフォールバックします。 +これは、FrankenPHPプロセスが開始されたディレクトリおよびそのサブディレクトリ内のすべての`.php`、`.yaml`、`.yml`、`.twig`、`.env`ファイルすべてを監視します。 +代わりに、[シェルのファイル名パターン](https://pkg.go.dev/path/filepath#Match)を使用して +1つ以上のディレクトリを指定することもできます: + +```caddyfile +{ + frankenphp { + worker { + file /path/to/app/public/worker.php + watch /path/to/app # /path/to/app以下すべてのサブディレクトリのファイルを監視 + watch /path/to/app/*.php # /path/to/app内の.phpで終わるファイルを監視 + watch /path/to/app/**/*.php # /path/to/appおよびサブディレクトリのPHPファイルを監視 + watch /path/to/app/**/*.{php,twig} # /path/to/appおよびサブディレクトリ内のPHPとTwigファイルを監視 + } + } +} +``` + +- `**` パターンは再帰的な監視を意味します +- ディレクトリは相対パス(FrankenPHPプロセスの開始ディレクトリから)にもできます +- 複数のワーカーが定義されている場合、いずれかのファイルが変更されるとすべてのワーカーが再起動されます +- 実行時に生成されるファイル(ログなど)を監視対象に含めると、意図しないワーカーの再起動を引き起こす可能性があるため注意が必要です + +ファイルウォッチャーは[e-dant/watcher](https://github.com/e-dant/watcher)に基づいています。 + +## パスにワーカーをマッチさせる + +従来のPHPアプリケーションでは、スクリプトは常にpublicディレクトリに配置されます。 +これはワーカースクリプトにも当てはまり、他のPHPスクリプトと同様に扱われます。 +ワーカースクリプトをpublicディレクトリの外に配置したい場合は、`match`ディレクティブを使用して実現できます。 + +`match`ディレクティブは、`try_files`の最適化された代替手段であり、`php_server`および`php`の中でのみ使用できます。 +次の例では、public ディレクトリ内にファイルが存在すればそれを配信し、 +存在しなければ、パスパターンに一致するワーカーにリクエストを転送します。 + +```caddyfile +{ + frankenphp { + php_server { + worker { + file /path/to/worker.php # ファイルはpublicパス外でも可 + match /api/* # /api/で始まるすべてのリクエストはこのワーカーで処理される + } + } + } +} +``` + +### フルデュプレックス(HTTP/1) + +HTTP/1.xを使用する場合、全体のボディが読み取られる前にレスポンスを書き込めるようにするため、 +フルデュプレックスモードを有効にすることが望ましい場合があります(例:WebSocket、Server-Sent Eventsなど)。 + +これは明示的に有効化する必要がある設定で、`Caddyfile`のグローバルオプションに追加する必要があります: + +```caddyfile +{ + servers { + enable_full_duplex + } +} +``` + +> [!CAUTION] +> +> このオプションを有効にすると、フルデュプレックスをサポートしない古いHTTP/1.xクライアントでデッドロックが発生する可能性があります。 +> これは`CADDY_GLOBAL_OPTIONS`環境設定を使用しても設定できます: + +```sh +CADDY_GLOBAL_OPTIONS="servers { + enable_full_duplex +}" +``` + +この設定の詳細については、[Caddyドキュメント](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex)をご覧ください。 + +## 環境変数 + +以下の環境変数を使用することで、`Caddyfile`を直接変更せずにCaddyディレクティブを注入できます: + +- `SERVER_NAME`: [待ち受けアドレス](https://caddyserver.com/docs/caddyfile/concepts#addresses)を変更し、指定したホスト名はTLS証明書の生成にも使用されます +- `SERVER_ROOT`: サイトのルートディレクトリを変更します。デフォルトは`public/` +- `CADDY_GLOBAL_OPTIONS`: [グローバルオプション](https://caddyserver.com/docs/caddyfile/options)を注入します +- `FRANKENPHP_CONFIG`: `frankenphp`ディレクティブの下に設定を注入します + +FPM や CLI SAPI と同様に、環境変数はデフォルトで`$_SERVER`スーパーグローバルで公開されます。 + +[`variables_order` PHPディレクティブ](https://www.php.net/manual/en/ini.core.php#ini.variables-order)の`S`値は、このディレクティブ内での`E`の位置にかかわらず常に`ES`と同等です。 + +## PHP設定 + +[追加のPHP設定ファイル](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan)を読み込むには、 +`PHP_INI_SCAN_DIR`環境変数を使用できます。 +設定されると、PHPは指定されたディレクトリに存在する`.ini`拡張子を持つすべてのファイルを読み込みます。 + +また、`Caddyfile`の`php_ini`ディレクティブを使用してPHP設定を変更することもできます: + +```caddyfile +{ + frankenphp { + php_ini memory_limit 256M + + # または + + php_ini { + memory_limit 256M + max_execution_time 15 + } + } +} +``` + +## デバッグモードの有効化 + +Dockerイメージを使用する場合、`CADDY_GLOBAL_OPTIONS`環境変数に`debug`を設定するとデバッグモードが有効になります: + +```console +docker run -v $PWD:/app/public \ + -e CADDY_GLOBAL_OPTIONS=debug \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` diff --git a/docs/ja/docker.md b/docs/ja/docker.md new file mode 100644 index 00000000..7bce8e08 --- /dev/null +++ b/docs/ja/docker.md @@ -0,0 +1,205 @@ +# カスタムDockerイメージのビルド + +[FrankenPHPのDockerイメージ](https://hub.docker.com/r/dunglas/frankenphp)は、[公式PHPイメージ](https://hub.docker.com/_/php/)をベースにしています。主要なアーキテクチャに対してDebianとAlpine Linuxのバリアントを提供しており、Debianバリアントの使用を推奨しています。 + +PHP 8.2、8.3、8.4向けのバリアントが提供されています。 + +タグは次のパターンに従います:`dunglas/frankenphp:-php-` + +- ``および``は、それぞれFrankenPHPおよびPHPのバージョン番号で、メジャー(例:`1`)、マイナー(例:`1.2`)からパッチバージョン(例:`1.2.3`)まであります。 +- ``は`bookworm`(Debian Bookworm用)または`alpine`(Alpine最新安定版用)のいずれかです。 + +[タグを閲覧](https://hub.docker.com/r/dunglas/frankenphp/tags)。 + +## イメージの使用方法 + +プロジェクトに`Dockerfile`を作成します: + +```dockerfile +FROM dunglas/frankenphp + +COPY . /app/public +``` + +次に、以下のコマンドを実行してDockerイメージをビルドし、実行します: + +```console +docker build -t my-php-app . +docker run -it --rm --name my-running-app my-php-app +``` + +## PHP拡張モジュールの追加インストール方法 + +ベースイメージには[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer)スクリプトが含まれており、 +追加のPHP拡張モジュールを簡単にインストールできます: + +```dockerfile +FROM dunglas/frankenphp + +# 追加の拡張モジュールをここに追加: +RUN install-php-extensions \ + pdo_mysql \ + gd \ + intl \ + zip \ + opcache +``` + +## Caddyモジュールの追加インストール方法 + +FrankenPHPはCaddyをベースに構築されているため、すべての[Caddyモジュール](https://caddyserver.com/docs/modules/)をFrankenPHPでも使用できます。 + +カスタムCaddyモジュールをインストールする最も簡単な方法は、[xcaddy](https://github.com/caddyserver/xcaddy)を使用することです: + +```dockerfile +FROM dunglas/frankenphp:builder AS builder + +# builderイメージにxcaddyをコピー +COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy + +# FrankenPHPをビルドするにはCGOを有効にする必要があります +RUN CGO_ENABLED=1 \ + XCADDY_SETCAP=1 \ + XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ + CGO_CFLAGS=$(php-config --includes) \ + CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ + xcaddy build \ + --output /usr/local/bin/frankenphp \ + --with github.com/dunglas/frankenphp=./ \ + --with github.com/dunglas/frankenphp/caddy=./caddy/ \ + --with github.com/dunglas/caddy-cbrotli \ + # MercureとVulcainは公式ビルドに含まれていますが、お気軽に削除してください + --with github.com/dunglas/mercure/caddy \ + --with github.com/dunglas/vulcain/caddy + # ここに追加のCaddyモジュールを指定してください + +FROM dunglas/frankenphp AS runner + +# 公式バイナリをカスタムモジュールを含むものに置き換え +COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp +``` + +FrankenPHPが提供する`builder`イメージには、コンパイル済みの`libphp`が含まれています。 +[ビルダーイメージ](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder)は、FrankenPHPおよびPHPのすべてのバージョンに対して、DebianとAlpineの両方が提供されています。 + +> [!TIP] +> +> Alpine LinuxとSymfonyを使用している場合は、 +> [デフォルトのスタックサイズを増やす](compile.md#using-xcaddy) 必要がある場合があります。 + +## デフォルトでワーカーモードを有効にする + +FrankenPHPをワーカースクリプトで起動するには、`FRANKENPHP_CONFIG`環境変数を設定します: + +```dockerfile +FROM dunglas/frankenphp + +# ... + +ENV FRANKENPHP_CONFIG="worker ./public/index.php" +``` + +## 開発時にボリュームを使う + +FrankenPHPでの開発を簡単に行うには、ホスト側のアプリケーションのソースコードを含むディレクトリを、Dockerコンテナ内にボリュームとしてマウントします: + +```console +docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app +``` + +> [!TIP] +> +> `--tty`オプションを使うと、JSONではなく人間が読みやすいログが表示されます。 + +Docker Composeを使用する場合: + +```yaml +# compose.yaml + +services: + php: + image: dunglas/frankenphp + # カスタムDockerfileを使用したい場合は以下の行のコメントを外してください + #build: . + # 本番環境で使用する場合は以下の行のコメントを外してください + # restart: always + ports: + - "80:80" # HTTP + - "443:443" # HTTPS + - "443:443/udp" # HTTP/3 + volumes: + - ./:/app/public + - caddy_data:/data + - caddy_config:/config + # 開発環境で人間が読みやすいログを出力するため、本番ではこの行をコメントアウトしてください + tty: true + +# Caddyの証明書や設定に必要なボリューム +volumes: + caddy_data: + caddy_config: +``` + +## 非rootユーザーとして実行する + +FrankenPHPはDockerで非rootユーザーとして実行できます。 + +これを行うサンプル`Dockerfile`は以下の通りです: + +```dockerfile +FROM dunglas/frankenphp + +ARG USER=appuser + +RUN \ + # Alpine系ディストリビューションでは "adduser -D ${USER}" を使用 + useradd ${USER}; \ + # ポート 80 や 443 にバインドするための追加ケーパビリティを追加 + setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \ + # /data/caddy および /config/caddy への書き込み権限を付与 + chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy + +USER ${USER} +``` + +### ケーパビリティなしでの実行 + +FrankenPHPをroot以外のユーザーで実行する場合でも、特権ポート(80と443)でWebサーバーを +バインドするために`CAP_NET_BIND_SERVICE`ケーパビリティが必要です。 + +FrankenPHPを非特権ポート(1024以上)で公開する場合は、 +ウェブサーバーを非rootユーザーとして実行し、ケーパビリティを必要とせずに実行することが可能です: + +```dockerfile +FROM dunglas/frankenphp + +ARG USER=appuser + +RUN \ + # Alpine 系ディストリビューションでは "adduser -D ${USER}" を使用 + useradd ${USER}; \ + # デフォルトのケーパビリティを削除 + setcap -r /usr/local/bin/frankenphp; \ + # /data/caddy と /config/caddy への書き込み権限を付与 + chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy + +USER ${USER} +``` + +その後、`SERVER_NAME`環境変数を設定して非特権ポートを使用します。 +例: `:8000` + +## アップデート + +Dockerイメージは以下のタイミングでビルドされます: + +- 新しいリリースがタグ付けされたとき +- 公式PHPイメージに新しいバージョンがある場合、毎日UTC午前4時に自動ビルド + +## 開発版 + +開発版は[`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev)Dockerリポジトリで利用できます。 +GitHubリポジトリのmainブランチにコミットがpushされるたびに新しいビルドが実行されます。 + +`latest*`タグは`main`ブランチのヘッドを指しており、 +`sha-` 形式のタグも利用可能です。 diff --git a/docs/ja/early-hints.md b/docs/ja/early-hints.md new file mode 100644 index 00000000..1165c9ed --- /dev/null +++ b/docs/ja/early-hints.md @@ -0,0 +1,21 @@ +# Early Hints + +FrankenPHPは[103 Early Hints ステータスコード](https://developer.chrome.com/blog/early-hints/)をネイティブサポートしています。 +Early Hintsを使用することで、ウェブページの読み込み時間を30%改善できます。 + +```php +; rel=preload; as=style'); +headers_send(103); + +// 遅いアルゴリズムとSQLクエリ 🤪 + +echo <<<'HTML' + +Hello FrankenPHP + +HTML; +``` + +Early Hintsは通常モードと[ワーカー](worker.md)モードの両方でサポートされています。 diff --git a/docs/ja/embed.md b/docs/ja/embed.md new file mode 100644 index 00000000..14bb9515 --- /dev/null +++ b/docs/ja/embed.md @@ -0,0 +1,143 @@ +# PHPアプリのスタンドアロンバイナリ化 + +FrankenPHPには、PHPアプリケーションのソースコードやアセットを静的な自己完結型バイナリに埋め込む機能があります。 + +この機能により、PHPアプリケーション自体に加えて、PHPインタープリターや本番環境対応のWebサーバーCaddyも含んだスタンドアロンバイナリとして配布できます。 + +この機能について詳しくは、[SymfonyCon 2023でKévinが行ったプレゼンテーション](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/)をご覧ください。 + +Laravelアプリケーションの埋め込みについては、[こちらの専用ドキュメント](laravel.md#laravel-apps-as-standalone-binaries)をお読みください。 + +## アプリの準備 + +自己完結型バイナリを作成する前に、アプリが埋め込みに対応できる状態にあることを確認してください。 + +例えば、以下のような作業が必要です: + +- 本番環境用の依存パッケージをインストールする +- オートローダーをダンプする +- アプリケーションの本番モードを有効にする(ある場合) +- 最終バイナリのサイズを減らすために`.git`やテストなどの不要なファイルを除外する + +例えば、Symfonyアプリの場合、以下のコマンドを使用できます: + +```console +# .git/ などを除去するためにプロジェクトをエクスポート +mkdir $TMPDIR/my-prepared-app +git archive HEAD | tar -x -C $TMPDIR/my-prepared-app +cd $TMPDIR/my-prepared-app + +# 適切な環境変数を設定 +echo APP_ENV=prod > .env.local +echo APP_DEBUG=0 >> .env.local + +# テストやその他不要ファイルを削除して容量削減 +# あるいは、 .gitattributes の export-ignore 属性にこれらを追加してもよい +rm -Rf tests/ + +# 依存パッケージをインストール +composer install --ignore-platform-reqs --no-dev -a + +# .env を最適化 +composer dump-env prod +``` + +### 設定のカスタマイズ + +[設定](config.md) をカスタマイズするには、埋め込まれるアプリのメインディレクトリ +(前の例では`$TMPDIR/my-prepared-app`)に`Caddyfile`と`php.ini`ファイルを配置できます。 + +## Linux用バイナリの作成 + +Linux用バイナリを作成する最も簡単な方法は、提供されているDockerベースのビルダーを使用することです。 + +1. アプリのリポジトリに`static-build.Dockerfile`というファイルを作成します: + + ```dockerfile + FROM --platform=linux/amd64 dunglas/frankenphp:static-builder + + # アプリをコピー + WORKDIR /go/src/app/dist/app + COPY . . + + # 静的バイナリをビルド + WORKDIR /go/src/app/ + RUN EMBED=dist/app/ ./build-static.sh + ``` + + > [!CAUTION] + > + > 一部の`.dockerignore`ファイル(例:デフォルトの[Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore)) + > は`vendor/`ディレクトリと`.env`ファイルを無視します。ビルド前に`.dockerignore`ファイルを調整または削除してください。 + +2. ビルドします: + + ```console + docker build -t static-app -f static-build.Dockerfile . + ``` + +3. バイナリを抽出します: + + ```console + docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp + ``` + +生成されるバイナリは、現在のディレクトリの`my-app`というファイル名になります。 + +## 他のOS用のバイナリの作成 + +Dockerを使用したくない場合や、macOSバイナリを作成したい場合は、提供されているシェルスクリプトを使用してください: + +```console +git clone https://github.com/php/frankenphp +cd frankenphp +EMBED=/path/to/your/app ./build-static.sh +``` + +生成されるバイナリは、`dist/`ディレクトリの`frankenphp--`という名前のファイルです。 + +## バイナリの使い方 + +これで完了です!`my-app`ファイル(または他のOSでは`dist/frankenphp--`)には、自己完結型アプリが含まれています! + +Webアプリを起動するには、以下を実行します: + +```console +./my-app php-server +``` + +アプリに[ワーカースクリプト](worker.md)が含まれている場合は、以下のようにワーカーを開始します: + +```console +./my-app php-server --worker public/index.php +``` + +HTTPS(Let's Encrypt証明書は自動作成)、HTTP/2、HTTP/3を有効にするには、使用するドメイン名を指定してください: + +```console +./my-app php-server --domain localhost +``` + +バイナリに埋め込まれたPHP CLIスクリプトも実行できます: + +```console +./my-app php-cli bin/console +``` + +## PHP拡張モジュール + +デフォルトでは、スクリプトはプロジェクトの`composer.json`ファイルで必要な拡張モジュールをビルドします(存在する場合)。 +`composer.json`ファイルが存在しない場合、[静的ビルドのドキュメント](static.md)に記載されているデフォルトの拡張モジュールがビルドされます。 + +拡張モジュールをカスタマイズしたい場合は、`PHP_EXTENSIONS`環境変数を使用してください。 + +## ビルドのカスタマイズ + +バイナリをカスタマイズする方法(拡張モジュール、PHPバージョンなど)については、[静的ビルドのドキュメント](static.md)をお読みください。 + +## バイナリの配布 + +Linuxでは、作成されたバイナリは[UPX](https://upx.github.io)を使用して圧縮されます。 + +Macでは、送信前にファイルサイズを減らすために圧縮できます。 +`xz`の使用をお勧めします。 diff --git a/docs/ja/extensions.md b/docs/ja/extensions.md new file mode 100644 index 00000000..4d251459 --- /dev/null +++ b/docs/ja/extensions.md @@ -0,0 +1,639 @@ +# GoでPHP拡張モジュールを作成する + +FrankenPHPでは、**GoでPHP拡張モジュールを作成する**ことができます。これにより、PHPから直接呼び出せる**高パフォーマンスなネイティブ関数**を作成できます。アプリケーションは既存または新しいGoライブラリを活用でき、**PHPコードから直接goroutineの**強力な並行性モデルを使用できます。 + +PHP拡張モジュールの記述は通常Cで行われますが、少しの追加作業で他の言語でも作成可能です。PHP拡張モジュールは低レベル言語の力を活用してPHPの機能を拡張することができます。例えば、ネイティブ関数を追加したり、特定の操作を最適化したりできます。 + +Caddyモジュールのおかげで、GoでPHP拡張モジュールを書いてFrankenPHPに簡単に統合できます。 + +## 2つのアプローチ + +FrankenPHPでは、GoでPHP拡張モジュールを作成する2つの方法を提供します: + +1. **拡張モジュールジェネレーターを使用** - ほとんどのユースケースに必要なボイラープレートを自動生成する推奨アプローチで、Goコードの記述に集中できます +2. **手動実装** - 拡張モジュール構造を細かく制御したい高度なユースケース + +最初に始めやすいジェネレーター方式を紹介し、その後で完全な制御が必要な場合の手動実装方式を説明します。 + +## 拡張モジュールジェネレーターを使用する + +FrankenPHPにはGoのみを使用して**PHP拡張モジュールを作成する**ツールが付属しています。**Cコードを書く必要がなく**、CGOを直接使用する必要もありません。FrankenPHPには**パブリック型API**も含まれており、**PHP/CとGo間の型変換**を心配することなくGoでPHP拡張を書くのに役立ちます。 + +> [!TIP] +> 拡張モジュールをGoで一から書く方法を理解したい場合は、ジェネレーターを使用せずにGoでPHP拡張モジュールを書く方法を紹介する後述の手動実装セクションを参照してください。 + +注意すべきことは、このツールは**完全な拡張モジュールジェネレーター**ではないことです。GoでシンプルなPHP拡張モジュールを書くのには十分役立ちますが、高度なPHP拡張モジュールの機能には対応していません。より**複雑で最適化された**拡張モジュールを書く必要がある場合は、Cコードを書いたり、CGOを直接使用したりする必要があるかもしれません。 + +### 前提条件 + +以下の手動実装セクションでも説明しているように、[PHPのソースを取得](https://www.php.net/downloads.php)し、新しいGoモジュールを作成する必要があります。 + +#### 新しいモジュールの作成とPHPソースの取得 + +GoでPHP拡張モジュールを書く最初のステップは、新しいGoモジュールの作成です。以下のコマンドを使用できます: + +```console +go mod init github.com/my-account/my-module +``` + +2番目のステップは、次のステップのために[PHPのソースを取得](https://www.php.net/downloads.php)することです。取得したら、Goモジュールのディレクトリ内ではなく、任意のディレクトリに展開します: + +```console +tar xf php-* +``` + +### 拡張モジュールの記述 + +これでGoでネイティブ関数を書く準備が整いました。`stringext.go`という名前の新しいファイルを作成します。最初の関数は文字列を引数として取り、それを指定された回数だけ繰り返し、文字列を逆転するかどうかを示すブール値を受け取り、結果の文字列を返します。これは以下のようになります: + +```go +import ( + "C" + "github.com/dunglas/frankenphp" + "strings" +) + +//export_php:function repeat_this(string $str, int $count, bool $reverse): string +func repeat_this(s *C.zend_string, count int64, reverse bool) unsafe.Pointer { + str := frankenphp.GoString(unsafe.Pointer(s)) + + result := strings.Repeat(str, int(count)) + if reverse { + runes := []rune(result) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + result = string(runes) + } + + return frankenphp.PHPString(result, false) +} +``` + +ここで重要なポイントが2つあります: + +* ディレクティブコメント`//export_php:function`はPHPでの関数シグネチャを定義します。これにより、ジェネレーターは適切なパラメータと戻り値の型でPHP関数を生成する方法を知ることができます。 +* 関数は`unsafe.Pointer`を返さなければなりません。FrankenPHPはCとGo間の型変換を支援するAPIを提供しています。 + +前者は理解しやすいですが、後者は少し複雑かもしれません。次のセクションで型変換について詳しく説明します。 + +### 型変換 + +C/PHPとGoの間でメモリ表現が同じ変数型もありますが、直接使用するにはより多くのロジックが必要な型もあります。これは拡張モジュールを書く際の最も挑戦的な部分かもしれません。Zendエンジンの内部仕組みや、変数がPHP内でどのように格納されているかを理解する必要があるためです。以下の表は、知っておくべき重要な情報をまとめています: + +| PHP型 | Go型 | 直接変換 | CからGoヘルパー | GoからCヘルパー | クラスメソッドサポート | +|--------------------|------------------|-------------------|-----------------------|------------------------|-----------------------| +| `int` | `int64` | ✅ | - | - | ✅ | +| `?int` | `*int64` | ✅ | - | - | ✅ | +| `float` | `float64` | ✅ | - | - | ✅ | +| `?float` | `*float64` | ✅ | - | - | ✅ | +| `bool` | `bool` | ✅ | - | - | ✅ | +| `?bool` | `*bool` | ✅ | - | - | ✅ | +| `string`/`?string` | `*C.zend_string` | ❌ | frankenphp.GoString() | frankenphp.PHPString() | ✅ | +| `array` | `slice`/`map` | ❌ | _未実装_ | _未実装_ | ❌ | +| `object` | `struct` | ❌ | _未実装_ | _未実装_ | ❌ | + +> [!NOTE] +> この表はまだ完全ではなく、FrankenPHPの型APIがより完全になるにつれて完成されます。 +> +> クラスメソッドについては、現在プリミティブ型のみがサポートされています。配列とオブジェクトはまだメソッドパラメータや戻り値の型として使用できません。 + +前のセクションのコードスニペットを参照すると、最初のパラメータと戻り値の変換にヘルパーが使用されていることがわかります。 `repeat_this()`関数の2番目と3番目の引数は、基礎となる型のメモリ表現がCとGoで同じであるため、変換する必要がありません。 + +### ネイティブPHPクラスの宣言 + +ジェネレーターは、PHPオブジェクトを作成するために使用できる**不透明クラス(opaque classes)**をGo構造体として宣言することをサポートしています。`//export_php:class`ディレクティブコメントを使用してPHPクラスを定義できます。例: + +```go +//export_php:class User +type UserStruct struct { + Name string + Age int +} +``` + +#### 不透明クラスとは何ですか? + +**不透明クラス(opaque classes)**は、内部構造(プロパティ)がPHPコードから隠されているクラスです。これは以下を意味します: + +* **プロパティへの直接アクセス不可** :PHPから直接プロパティを読み書きできません(`$user->name`は機能しません) +* **メソッド経由のみで操作** - すべてのやりとりはGoで定義したメソッドを通じて行う必要があります +* **より良いカプセル化** - 内部データ構造は完全にGoコードによって制御されます +* **型安全性** - PHP側から誤った型で内部状態が破壊されるリスクがありません +* **よりクリーンなAPI** - 適切な公開インターフェースを設計することを強制します + +このアプローチは優れたカプセル化を実現し、PHPコードがGoオブジェクトの内部状態を意図せずに破壊してしまうことを防ぎます。オブジェクトとのすべてのやりとりは、明示的に定義したメソッドを通じて行う必要があります。 + +#### クラスにメソッドを追加する + +プロパティは直接アクセスできないため、不透明クラスとやりとりするには **メソッドを定義する必要があります** 。`//export_php:method`ディレクティブを使用して動作を定義します: + +```go +//export_php:class User +type UserStruct struct { + Name string + Age int +} + +//export_php:method User::getName(): string +func (us *UserStruct) GetUserName() unsafe.Pointer { + return frankenphp.PHPString(us.Name, false) +} + +//export_php:method User::setAge(int $age): void +func (us *UserStruct) SetUserAge(age int64) { + us.Age = int(age) +} + +//export_php:method User::getAge(): int +func (us *UserStruct) GetUserAge() int64 { + return int64(us.Age) +} + +//export_php:method User::setNamePrefix(string $prefix = "User"): void +func (us *UserStruct) SetNamePrefix(prefix *C.zend_string) { + us.Name = frankenphp.GoString(unsafe.Pointer(prefix)) + ": " + us.Name +} +``` + +#### Nullableパラメータ + +ジェネレーターは、PHPシグネチャにおける`?`プレフィックスを使用ったnullableパラメータをサポートしています。パラメータがnullableの場合、Go関数内ではポインタとして扱われ、PHP側で値が`null`だったかどうかを確認できます: + +```go +//export_php:method User::updateInfo(?string $name, ?int $age, ?bool $active): void +func (us *UserStruct) UpdateInfo(name *C.zend_string, age *int64, active *bool) { + // nameが渡された(nullではない)かチェック + if name != nil { + us.Name = frankenphp.GoString(unsafe.Pointer(name)) + } + + // ageが渡された(nullではない)かチェック + if age != nil { + us.Age = int(*age) + } + + // activeが渡された(nullではない)かチェック + if active != nil { + us.Active = *active + } +} +``` + +**Nullableパラメータの重要なポイント:** + +* **プリミティブ型のnullable** (`?int`, `?float`, `?bool`) はGoではそれぞれポインタ (`*int64`, `*float64`, `*bool`) になります +* **nullable文字列** (`?string`) は `*C.zend_string` のままですが、`nil` になることがあります +* ポインタ値を逆参照する前に **`nil`をチェック** してください +* **PHPの`null`はGoの`nil`になります** - PHPが`null`を渡すと、Go関数は`nil`ポインタを受け取ります + +> [!WARNING] +> 現在、クラスメソッドには次の制限があります。**配列とオブジェクトはパラメータ型や戻り値の型としてサポートされていません**。サポートされるのは`string`、`int`、`float`、`bool`、`void`(戻り値の型)といったスカラー型のみです。**nullableなスカラー型はすべてサポートされています** (`?string`、`?int`、`?float`、`?bool`)。 + +拡張を生成した後、PHP側でクラスとそのメソッドを使用できるようになります。ただし**プロパティに直接アクセスできない**ことに注意してください: + +```php +setAge(25); +echo $user->getName(); // 出力: (empty、デフォルト値) +echo $user->getAge(); // 出力: 25 +$user->setNamePrefix("Employee"); + +// ✅ これも動作します - nullableパラメータ +$user->updateInfo("John", 30, true); // すべて指定 +$user->updateInfo("Jane", null, false); // Ageがnull +$user->updateInfo(null, 25, null); // Nameとactiveがnull + +// ❌ これは動作しません - プロパティへの直接アクセス +// echo $user->name; // エラー: privateプロパティにアクセスできません +// $user->age = 30; // エラー: privateプロパティにアクセスできません +``` + +この設計により、Goコードがオブジェクトの状態へのアクセスと変更方法を完全に制御でき、より良いカプセル化と型安全性を提供します。 + +### 定数の宣言 + +ジェネレーターは、2つのディレクティブを使用してGo定数をPHPにエクスポートすることをサポートしています:グローバル定数用の`//export_php:const`とクラス定数用の`//export_php:classconstant`です。これにより、GoとPHPコード間で設定値、ステータスコード、その他の定数を共有できます。 + +#### グローバル定数 + +`//export_php:const`ディレクティブを使用してグローバルなPHP定数を作成できます: + +```go +//export_php:const +const MAX_CONNECTIONS = 100 + +//export_php:const +const API_VERSION = "1.2.3" + +//export_php:const +const STATUS_OK = iota + +//export_php:const +const STATUS_ERROR = iota +``` + +#### クラス定数 + +`//export_php:classconstant ClassName`ディレクティブを使用して、特定のPHPクラスに属する定数を作成できます: + +```go +//export_php:classconstant User +const STATUS_ACTIVE = 1 + +//export_php:classconstant User +const STATUS_INACTIVE = 0 + +//export_php:classconstant User +const ROLE_ADMIN = "admin" + +//export_php:classconstant Order +const STATE_PENDING = iota + +//export_php:classconstant Order +const STATE_PROCESSING = iota + +//export_php:classconstant Order +const STATE_COMPLETED = iota +``` + +クラス定数は、PHPでクラス名スコープを使用してアクセスできます: + +```php + [!NOTE] +> `GEN_STUB_FILE`環境変数に、先ほどダウンロードしたPHPソースの`gen_stub.php`ファイルのパスを設定するのを忘れないでください。これは手動実装セクションで言及されているのと同じ`gen_stub.php`スクリプトです。 + +すべてがうまくいけば、`build`という名前の新しいディレクトリが作成されているはずです。このディレクトリには、生成されたPHP関数スタブを含む`my_extension.go`ファイルなど、拡張用の生成されたファイルが含まれています。 + +### 生成された拡張モジュールをFrankenPHPへ統合する + +拡張モジュールがコンパイルされ、FrankenPHPに統合される準備が整いました。これを行うには、FrankenPHPのコンパイル方法を学ぶために、FrankenPHPの[コンパイルドキュメント](compile.md)を参照してください。`--with`フラグを使用してモジュールを追加し、モジュールのパスを指定します: + +```console +CGO_ENABLED=1 \ +XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ +CGO_CFLAGS=$(php-config --includes) \ +CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ +xcaddy build \ + --output frankenphp \ + --with github.com/my-account/my-module/build +``` + +このとき、生成ステップで作成された`/build`サブディレクトリを指していることに注意してください。ただし、これは必須ではなく、生成されたファイルをモジュールのディレクトリにコピーして、直接それを指定することも可能です。 + +### 生成された拡張モジュールのテスト + +作成した関数とクラスをテストするPHPファイルを作成しましょう。例えば、以下の内容で`index.php`ファイルを作成します: + +```php +process('Hello World', StringProcessor::MODE_LOWERCASE); // "hello world" +echo $processor->process('Hello World', StringProcessor::MODE_UPPERCASE); // "HELLO WORLD" +``` + +前のセクションで示したように拡張モジュールをFrankenPHPに統合し、`./frankenphp php-server`を使用してこのテストファイルを実行することで、拡張モジュールが動作しているのを確認できるはずです。 + +## 手動実装 + +拡張モジュールの仕組みを理解したい、または拡張モジュールを完全に制御したい場合は、手動で書くこともできます。このアプローチは完全な制御を実現できますが、より多くのボイラープレートコードが必要になります。 + +### 基本的な関数 + +ここでは、新しいネイティブ関数を定義するシンプルなPHP拡張モジュールをGoで手動実装する方法を紹介します。この関数はPHPから呼び出され、その関数がgoroutineを使ってCaddyのログにメッセージ出力するという処理を行います。この関数は引数を取らず、戻り値もありません。 + +#### Go関数の定義 + +モジュール内で、PHPから呼び出される新しいネイティブ関数を定義する必要があります。これを行うには、例えば`extension.go`のように任意の名前でファイルを作成し、以下のコードを追加します: + +```go +package ext_go + +//#include "extension.h" +import "C" +import ( + "unsafe" + "github.com/caddyserver/caddy/v2" + "github.com/dunglas/frankenphp" +) + +func init() { + frankenphp.RegisterExtension(unsafe.Pointer(&C.ext_module_entry)) +} + +//export go_print_something +func go_print_something() { + go func() { + caddy.Log().Info("Hello from a goroutine!") + }() +} +``` + +`frankenphp.RegisterExtension()`関数は、内部のPHP登録ロジックを処理することで拡張登録プロセスを簡素化します。`go_print_something`関数は`//export`ディレクティブを使用して、CGOのおかげで、これから書くCコードでアクセスできるようになることを示しています。 + +この例では、新しい関数がCaddyのログにメッセージ出力するgoroutineをトリガーします。 + +#### PHP関数の定義 + +PHPがGo関数を呼び出せるようにするには、対応するPHP関数を定義する必要があります。このために、例えば`extension.stub.php`のようにスタブファイルを作成し、以下のコードを記述します: + +```php + + +extern zend_module_entry ext_module_entry; + +#endif +``` + +次に、以下のステップを実行する`extension.c`という名前のファイルを作成します: + +* PHPヘッダーをインクルードする +* 新しいネイティブPHP関数`go_print()`を宣言する +* 拡張モジュールのメタデータを宣言する + +まずは必要なヘッダーのインクルードから始めましょう: + +```c +#include +#include "extension.h" +#include "extension_arginfo.h" + +// Goによってエクスポートされたシンボルを含みます +#include "_cgo_export.h" +``` + +次に、PHP関数をネイティブ言語関数として定義します: + +```c +PHP_FUNCTION(go_print) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + go_print_something(); +} + +zend_module_entry ext_module_entry = { + STANDARD_MODULE_HEADER, + "ext_go", + ext_functions, /* Functions */ + NULL, /* MINIT */ + NULL, /* MSHUTDOWN */ + NULL, /* RINIT */ + NULL, /* RSHUTDOWN */ + NULL, /* MINFO */ + "0.1.1", + STANDARD_MODULE_PROPERTIES +}; +``` + +この場合、関数はパラメータを取らず、何も返しません。単に`//export`ディレクティブを使用してエクスポートした、先ほど定義したGo関数を呼び出します。 + +最後に、名前、バージョン、プロパティなど、拡張のメタデータを`zend_module_entry`構造体で定義します。この情報はPHPが私たちの拡張モジュールを認識してロードするために必要です。`ext_functions`は定義したPHP関数へのポインタの配列であり、`gen_stub.php`スクリプトによって自動生成された`extension_arginfo.h`ファイル内に定義されています。 + +拡張モジュールの登録は、Goコード内で呼び出しているFrankenPHPの`RegisterExtension()`関数によって自動的に処理されます。 + +### 高度な使用方法 + +基本的なPHP拡張をGoで作成する方法が分かったところで、少し例を複雑にしてみましょう。今度は文字列を引数として受け取り、その大文字版を返すPHP関数を作成します。 + +#### PHP関数スタブの定義 + +新しいPHP関数を定義するために、`extension.stub.php`ファイルを修正し、次の関数シグネチャを含めます: + +```php + [!TIP] +> 関数のドキュメントを軽視しないでください!拡張スタブを他の開発者と共有する際、拡張機能の使い方や提供している機能を伝えるための重要な手段になります。 + +`gen_stub.php`スクリプトでスタブファイルを再生成すると、`extension_arginfo.h`ファイルは以下のようになるはずです: + +```c +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_go_upper, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_FUNCTION(go_upper); + +static const zend_function_entry ext_functions[] = { + ZEND_FE(go_upper, arginfo_go_upper) + ZEND_FE_END +}; +``` + +この出力から、`go_upper`関数が`string`型の引数を1つ受け取り、`string`型の戻り値を返すことが定義されていのがわかります。 + +#### GoとPHP/C間の型変換(Type Juggling) + +Go関数はPHPの文字列を引数として直接受け取ることはできません。そのためPHPの文字列をGoの文字列へ変換する必要があります。幸いなことに、FrankenPHPは、ジェネレーターアプローチで見たものと同様に、PHP文字列とGo文字列間の変換を処理するヘルパー関数を提供しています。 + +ヘッダーファイルはシンプルなままです: + +```c +#ifndef _EXTENSION_H +#define _EXTENSION_H + +#include + +extern zend_module_entry ext_module_entry; + +#endif +``` + +次に、`extension.c`ファイルにGoとC間のブリッジを書きます。ここではPHPの文字列を直接Go関数に渡します: + +```c +PHP_FUNCTION(go_upper) +{ + zend_string *str; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + zend_string *result = go_upper(str); + RETVAL_STR(result); +} +``` + +`ZEND_PARSE_PARAMETERS_START`や引数のパースについては、[PHP Internals Book](https://www.phpinternalsbook.com/php7/extensions_design/php_functions.html#parsing-parameters-zend-parse-parameters)の該当ページで詳しく学ぶことができます。この例では、関数が`zend_string`として`string`型の必須引数を1つ取ることをPHPに伝えています。その後、この文字列を直接Go関数に渡し、`RETVAL_STR`を使用して結果を返します。 + +残るはただ一つ、Go側で`go_upper`関数を実装するだけです。 + +#### Go関数の実装 + +Go側の関数では`*C.zend_string`を引数として受け取り、FrankenPHPのヘルパー関数を使用してGoの文字列に変換し、処理を行ったうえで、結果を新たな`*C.zend_string`として返します。メモリ管理と変換の複雑さは、ヘルパー関数がすべて対応してくれます。 + +```go +import "strings" + +//export go_upper +func go_upper(s *C.zend_string) *C.zend_string { + str := frankenphp.GoString(unsafe.Pointer(s)) + + upper := strings.ToUpper(str) + + return (*C.zend_string)(frankenphp.PHPString(upper, false)) +} +``` + +このアプローチは、手動メモリ管理よりもはるかにクリーンで安全です。FrankenPHPのヘルパー関数は、PHPの`zend_string`形式とGoの文字列間の変換を自動的に処理してくれます。`PHPString()`に`false`引数を指定していることで、新しい非永続文字列(リクエストの終了時に解放される)を作成したいことを示しています。 + +> [!TIP] +> この例ではエラーハンドリングを省略していますが、Go関数内でポインタが`nil`ではないこと、渡されたデータが有効であることを常に確認するべきです。 + +### 拡張モジュールのFrankenPHPへの統合 + +拡張モジュールがコンパイルされ、FrankenPHPに統合される準備が整いました。手順についてはFrankenPHPのコンパイル方法を学ぶために、FrankenPHPの[コンパイルドキュメント](compile.md)を参照してください。`--with`フラグを使用してモジュールを追加し、モジュールのパスを指定します: + +```console +CGO_ENABLED=1 \ +XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \ +CGO_CFLAGS=$(php-config --includes) \ +CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \ +xcaddy build \ + --output frankenphp \ + --with github.com/my-account/my-module +``` + +これで完了です!拡張モジュールがFrankenPHPに統合され、PHPコードで利用できるようになりました。 + +### 拡張モジュールのテスト + +拡張モジュールをFrankenPHPに統合したら、実装した関数を試すための`index.php`ファイルを作成します: + +```php + [!CAUTION] +> +> `172.17.0.3`の部分は、実際にコンテナに割り当てられるIPに置き換えてください。 + +これでホストマシンから`https://127.0.0.1`へアクセスできるはずです。 + +うまくいかない場合は、FrankenPHPをデバッグモードで起動して問題を特定してみてください: + +```console +docker run \ + -e CADDY_GLOBAL_OPTIONS="debug" \ + -e SERVER_NAME="127.0.0.1" \ + -v $PWD:/app/public \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +## `@php` を参照するComposerスクリプト + +[Composerスクリプト](https://getcomposer.org/doc/articles/scripts.md)では、いくつかのタスクでPHPバイナリを実行したい場合があります。例えば、[Laravelプロジェクト](laravel.md)で`@php artisan package:discover --ansi`を実行する場合です。しかし現在これは以下の2つの理由で[失敗します](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915): + +- ComposerはFrankenPHPバイナリを呼び出す方法を知りません +- Composerはコマンドで`-d`フラグを使用してPHP設定を追加する場合があり、FrankenPHPはまだサポートしていません + +回避策として、未サポートのパラメータを削除してFrankenPHPを呼び出すシェルスクリプトを`/usr/local/bin/php`に作成できます: + +```bash +#!/usr/bin/env bash +args=("$@") +index=0 +for i in "$@" +do + if [ "$i" == "-d" ]; then + unset 'args[$index]' + unset 'args[$index+1]' + fi + index=$((index+1)) +done + +/usr/local/bin/frankenphp php-cli ${args[@]} +``` + +次に、環境変数`PHP_BINARY`にこの`php`スクリプトのパスを設定してComposerを実行します: + +```console +export PHP_BINARY=/usr/local/bin/php +composer install +``` + +## 静的バイナリでのTLS/SSL問題のトラブルシューティング + +静的バイナリを使用する場合、例えばSTARTTLSを使用してメールを送信する際に以下のTLS関連エラーが発生する可能性があります: + +```text +Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages: +error:80000002:system library::No such file or directory +error:80000002:system library::No such file or directory +error:80000002:system library::No such file or directory +error:0A000086:SSL routines::certificate verify failed +``` + +静的バイナリにはTLS証明書がバンドルされていないため、OpenSSLにローカルのCA証明書の位置を明示する必要があります。 + +[`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php)の出力を調べて、 +CA証明書をどこにインストールすべきか確認し、その場所に保存してください。 + +> [!WARNING] +> +> WebとCLIコンテキストでは設定が異なる場合があります。 +> 適切なコンテキストで`openssl_get_cert_locations()`を実行してください。 + +[Mozillaから抽出されたCA証明書はcurlのサイトでダウンロードできます](https://curl.se/docs/caextract.html)。 + +または、Debian、Ubuntu、Alpineなどのディストリビューションでも、これらの証明書を含む`ca-certificates`というパッケージを提供しています。 + +`SSL_CERT_FILE`および`SSL_CERT_DIR`を使用してOpenSSLにCA証明書を探す場所をヒントとして与えることも可能です: + +```console +# TLS 証明書の環境変数を設定 +export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt +export SSL_CERT_DIR=/etc/ssl/certs +``` diff --git a/docs/ja/laravel.md b/docs/ja/laravel.md new file mode 100644 index 00000000..22a983ed --- /dev/null +++ b/docs/ja/laravel.md @@ -0,0 +1,183 @@ +# Laravel + +## Docker + +FrankenPHPを使用して[Laravel](https://laravel.com)のWebアプリケーションを配信するのは簡単で、公式Dockerイメージの`/app`ディレクトリにプロジェクトをマウントするだけです。 + +Laravelアプリのメインディレクトリからこのコマンドを実行してください: + +```console +docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp +``` + +お楽しみください! + +## ローカルインストール + +または、ローカルマシンでFrankenPHPを使用してLaravelプロジェクトを実行することもできます: + +1. [使用しているシステムに対応するバイナリをダウンロードします](../#standalone-binary) +2. Laravelプロジェクトのルートディレクトリに`Caddyfile`という名前のファイルを作成し、以下の設定を追加します: + + ```caddyfile + { + frankenphp + } + + # サーバーのドメイン名 + localhost { + # webroot を public/ ディレクトリに設定 + root public/ + # 圧縮を有効にする(任意) + encode zstd br gzip + # public/ ディレクトリ内の PHP ファイルを実行し、アセットを提供 + php_server { + try_files {path} index.php + } + } + ``` + +3. LaravelプロジェクトのルートディレクトリからFrankenPHPを起動します: `frankenphp run` + +## Laravel Octane + +OctaneはComposerパッケージマネージャーを使用してインストールできます: + +```console +composer require laravel/octane +``` + +Octaneをインストールした後、`octane:install` Artisanコマンドを実行すると、Octaneの設定ファイルがアプリケーションにインストールされます: + +```console +php artisan octane:install --server=frankenphp +``` + +Octaneサーバーは`octane:frankenphp` Artisanコマンドで開始できます。 + +```console +php artisan octane:frankenphp +``` + +`octane:frankenphp`コマンドは以下のオプションが利用可能です: + +- `--host`: サーバーがバインドするIPアドレス(デフォルト:`127.0.0.1`) +- `--port`: サーバーが使用するポート(デフォルト: `8000`) +- `--admin-port`: 管理サーバーが使用するポート(デフォルト: `2019`) +- `--workers`: リクエスト処理に使うワーカー数(デフォルト: `auto`) +- `--max-requests`: サーバーを再起動するまでに処理するリクエスト数(デフォルト: `500`) +- `--caddyfile`: FrankenPHPの`Caddyfile`ファイルのパス(デフォルト: [Laravel OctaneのスタブCaddyfile](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile)) +- `--https`: HTTPS、HTTP/2、HTTP/3を有効にし、証明書を自動的に生成・更新する +- `--http-redirect`: HTTPからHTTPSへのリダイレクトを有効にする(--httpsオプション指定時のみ有効) +- `--watch`: アプリケーションが変更されたときに自動的にサーバーをリロードする +- `--poll`: ネットワーク越しのファイル監視のためにファイルシステムポーリングを使用する +- `--log-level`: ネイティブCaddyロガーを使用して、指定されたログレベル以上でログメッセージを記録する + +> [!TIP] +> 構造化されたJSONログ(ログ分析ソリューションを使用する際に便利)を取得するには、明示的に`--log-level`オプションを指定してください。 + +詳しくは[Laravel Octaneの公式ドキュメント](https://laravel.com/docs/octane)をご覧ください。 + +## Laravelアプリのスタンドアロンバイナリ化 + +[FrankenPHPのアプリケーション埋め込み機能](embed.md)を使用して、Laravelアプリをスタンドアロンバイナリとして +配布することが可能です。 + +LaravelアプリをLinux用のスタンドアロンバイナリとしてパッケージ化するには、以下の手順に従ってください: + +1. アプリのリポジトリに`static-build.Dockerfile`という名前のファイルを作成します: + + ```dockerfile + FROM --platform=linux/amd64 dunglas/frankenphp:static-builder + + # アプリをコピー + WORKDIR /go/src/app/dist/app + COPY . . + + # スペースを節約するためにテストやその他の不要なファイルを削除 + # 代わりに .dockerignore に記述して除外することも可能 + RUN rm -Rf tests/ + + # .envファイルをコピー + RUN cp .env.example .env + # APP_ENV と APP_DEBUG を本番用に変更 + RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env + + # 必要に応じて .env ファイルにさらに変更を加える + + # 依存関係をインストール + RUN composer install --ignore-platform-reqs --no-dev -a + + # 静的バイナリをビルド + WORKDIR /go/src/app/ + RUN EMBED=dist/app/ ./build-static.sh + ``` + + > [!CAUTION] + > + > 一部の`.dockerignore`ファイルは + > `vendor/`ディレクトリや`.env`ファイルを無視します。ビルド前に`.dockerignore`ファイルを調整または削除してください。 + +2. ビルドします: + + ```console + docker build -t static-laravel-app -f static-build.Dockerfile . + ``` + +3. バイナリを取り出します: + + ```console + docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp + ``` + +4. キャッシュを構築します: + + ```console + frankenphp php-cli artisan optimize + ``` + +5. データベースマイグレーションを実行します(ある場合): + + ```console + frankenphp php-cli artisan migrate + ``` + +6. アプリの秘密鍵を生成します: + + ```console + frankenphp php-cli artisan key:generate + ``` + +7. サーバーを起動します: + + ```console + frankenphp php-server + ``` + +これで、アプリの準備は完了です! + +利用可能なオプションや他のOSでバイナリをビルドする方法については、[アプリケーション埋め込み](embed.md)ドキュメントをご覧ください。 + +### ストレージパスの変更 + +Laravelはアップロードされたファイルやキャッシュ、ログなどをデフォルトでアプリケーションの`storage/`ディレクトリに保存します。 +しかし、これは埋め込みアプリケーションには適していません。なぜなら、アプリの新しいバージョンごとに異なる一時ディレクトリに展開されるためです。 + +この問題を回避するには、`LARAVEL_STORAGE_PATH`環境変数を設定(例:`.env`ファイル内)するか、 `Illuminate\Foundation\Application::useStoragePath()`メソッドを呼び出して、一時ディレクトリの外にある任意のディレクトリを使用してください。 + +### スタンドアロンバイナリでOctaneを実行する + +Laravel Octaneアプリもスタンドアロンバイナリとしてパッケージ化することが可能です! + +そのためには、[Octaneを正しくインストール](#laravel-octane)し、[前のセクション](#laravelアプリのスタンドアロンバイナリ化)で説明した手順に従ってください。 + +次に、Octaneを通じてワーカーモードでFrankenPHPを起動するには、以下を実行してください: + +```console +PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp +``` + +> [!CAUTION] +> +> コマンドを動作させるためには、スタンドアロンバイナリのファイル名が**必ず**`frankenphp`でなければなりません。 +> Octaneは`frankenphp`という名前の実行ファイルがパス上に存在することを前提としています。 diff --git a/docs/ja/mercure.md b/docs/ja/mercure.md new file mode 100644 index 00000000..0aae6d30 --- /dev/null +++ b/docs/ja/mercure.md @@ -0,0 +1,15 @@ +# リアルタイム + +FrankenPHPには組み込みの[Mercure](https://mercure.rocks)ハブが付属しています! +Mercureを使用すると、接続されているすべてのデバイスにリアルタイムイベントをプッシュでき、各デバイスは即座にJavaScriptイベントを受信します。 + +JSライブラリやSDKは必要ありません! + +![Mercure](mercure-hub.png) + +Mercureハブを有効にするには、[Mercureのサイト](https://mercure.rocks/docs/hub/config)で説明されているように`Caddyfile`を更新してください。 + +Mercureハブのパスは`/.well-known/mercure`です。 +FrankenPHPをDocker内で実行している場合、完全な送信URLは`http://php/.well-known/mercure`のようになります。ここでの`php`はFrankenPHPを実行するコンテナの名前です。 + +コードからMercureの更新をプッシュするには、[Symfony Mercure Component](https://symfony.com/components/Mercure)をお勧めします。なお、Symfonyのフルスタックフレームワークは必要ありません。 diff --git a/docs/ja/metrics.md b/docs/ja/metrics.md new file mode 100644 index 00000000..6e7025f7 --- /dev/null +++ b/docs/ja/metrics.md @@ -0,0 +1,17 @@ +# メトリクス + +[Caddyのメトリクス](https://caddyserver.com/docs/metrics)が有効になっていると、FrankenPHPは以下のメトリクスを公開します: + +- `frankenphp_total_threads`: PHPスレッドの総数 +- `frankenphp_busy_threads`: 現在リクエストを処理中のPHPスレッド数。なお、実行中のワーカーは常にスレッドを消費します +- `frankenphp_queue_depth`: 通常のキューに入っているリクエストの数 +- `frankenphp_total_workers{worker="[worker_name]"}`: ワーカーの総数 +- `frankenphp_busy_workers{worker="[worker_name]"}`: 現在リクエストを処理中のワーカーの数 +- `frankenphp_worker_request_time{worker="[worker_name]"}`: すべてのワーカーがリクエスト処理に費やした時間 +- `frankenphp_worker_request_count{worker="[worker_name]"}`: すべてのワーカーが処理したリクエスト数 +- `frankenphp_ready_workers{worker="[worker_name]"}`: 少なくとも一度は `frankenphp_handle_request` を呼び出したワーカーの数 +- `frankenphp_worker_crashes{worker="[worker_name]"}`: ワーカーが予期せず終了した回数 +- `frankenphp_worker_restarts{worker="[worker_name]"}`: ワーカーが意図的に再起動された回数 +- `frankenphp_worker_queue_depth{worker="[worker_name]"}`: キューに入っているリクエストの数 + +ワーカーメトリクスの`[worker_name]`プレースホルダーは、Caddyfileに指定されたワーカー名に置き換えられます。ワーカー名が指定されていない場合は、ワーカーファイルの絶対パスが使用されます。 diff --git a/docs/ja/performance.md b/docs/ja/performance.md new file mode 100644 index 00000000..5139a034 --- /dev/null +++ b/docs/ja/performance.md @@ -0,0 +1,157 @@ +# パフォーマンス + +デフォルトでは、FrankenPHPはパフォーマンスと使いやすさのバランスが取れた構成を提供するよう設計されています。 +ただし、適切な設定により、パフォーマンスを大幅に向上させることが可能です。 + +## スレッド数とワーカー数 + +デフォルトでは、FrankenPHPは利用可能なCPU数の2倍のスレッドとワーカー(ワーカーモードで)を開始します。 + +適切な値は、アプリケーションの書き方、機能、ハードウェアに大きく依存します。 +これらの値を調整することを強く推奨します。最適なシステムの安定性のためには、`num_threads` x `memory_limit` < `available_memory`とすることをお勧めします。 + +適切な値を見つけるには、実際のトラフィックをシミュレートした負荷テストを実行するのが最も効果的です。 +そのためのツールとして、[k6](https://k6.io)や[Gatling](https://gatling.io)が有用です。 + +スレッド数を設定するには、`php_server`や`php`ディレクティブ内の`num_threads`オプションを使用してください。 +ワーカー数を変更するには、`frankenphp`ディレクティブ内の`worker`セクションにある`num`オプションを使用してください。 + +### `max_threads` + +実際のトラフィックがどのようなものになるかを正確に把握できれば理想ですが、現実のアプリケーションでは +予測困難な挙動が多いものです。`max_threads`[設定](config.md#caddyfile-config) により、FrankenPHPは指定された制限まで実行時に追加スレッドを自動的に生成できます。 +`max_threads`はトラフィックを処理するために必要なスレッド数を把握するのに役立ち、レイテンシのスパイクに対してサーバーをより回復力のあるものにできます。 +`auto`に設定すると、制限は`php.ini`の`memory_limit`に基づいて推定されます。推定できない場合、 +`auto`は代わりに`num_threads`の2倍がデフォルトになります。`auto`は必要なスレッド数を大幅に過小評価する可能性があることに留意してください。 +`max_threads`はPHP FPMの[pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children)に似ています。主な違いは、FrankenPHPがプロセスではなくスレッドを使用し、 +必要に応じて異なるワーカースクリプトと「クラシックモード」間で自動的に委譲することです。 + +## ワーカーモード + +[ワーカーモード](worker.md)を有効にするとパフォーマンスが劇的に向上しますが、 +アプリがこのモードと互換性があるように適応する必要があります: +ワーカースクリプトを作成し、アプリがメモリリークしていないことを確認する必要があります。 + +## muslを使用しない + +公式Dockerイメージと私たちが提供するデフォルトバイナリのAlpine Linuxバリアントは、[musl libc](https://musl.libc.org)を使用しています。 + +PHPは、従来のGNUライブラリの代わりにこの代替Cライブラリを使用すると[遅くなる](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381)ことが知られており、 +特にFrankenPHPに必要なZTSモード(スレッドセーフ)でコンパイルされた場合です。高度にスレッド化された環境では、差が大きくなる可能性があります。 + +また、[一部のバグはmuslを使用した場合にのみ発生します](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl)。 + +本番環境では、glibcにリンクされたFrankenPHPを使用することをお勧めします。 + +これは、Debian Dockerイメージ(デフォルト)を使用するか、[リリースページ](https://github.com/php/frankenphp/releases)から -gnu サフィックス付きバイナリをダウンロードするか、あるいは[FrankenPHPをソースからコンパイル](compile.md)することで実現できます。 + +または、[mimalloc allocator](https://github.com/microsoft/mimalloc)でコンパイルされた静的muslバイナリも提供しており、これによりスレッド環境での問題を軽減できます。 + +## Go Runtime設定 + +FrankenPHPはGoで書かれています。 + +一般的に、Go runtimeは特別な設定を必要としませんが、特定の状況では、 +特定の設定でパフォーマンスが向上する場合があります。 + +おそらく`GODEBUG`環境変数を`cgocheck=0`に設定したいでしょう(FrankenPHP Dockerイメージのデフォルト)。 + +FrankenPHPをコンテナ(Docker、Kubernetes、LXC...)で実行しており、コンテナで利用可能なメモリを制限している場合は、 +`GOMEMLIMIT`環境変数に利用可能なメモリ量を設定してください。 + +詳細については、Go ランタイムを最大限に活用するために、[この主題に特化したGoドキュメントページ](https://pkg.go.dev/runtime#hdr-Environment_Variables)を読むことを強く推奨します。 + +## `file_server` + +デフォルトでは、`php_server`ディレクティブは自動的にファイルサーバーを設定して +ルートディレクトリに保存された静的ファイル(アセット)を配信します。 + +この機能は便利ですが、コストがかかります。 +無効にするには、以下の設定を使用してください: + +```caddyfile +php_server { + file_server off +} +``` + +## `try_files` + +`php_server`は、静的ファイルとPHPファイルに加えて、アプリケーションのインデックスファイル +およびディレクトリインデックスファイル(`/path/` -> `/path/index.php`)も試行します。ディレクトリインデックスが不要な場合、 +次のように`try_files`を明示的に定義して無効にできます: + +```caddyfile +php_server { + try_files {path} index.php + root /root/to/your/app # ここで root を明示的に追加すると、キャッシュの効率が向上します +} +``` + +これにより、不要なファイルの操作の回数を大幅に削減できます。 + +ファイルシステムへの不要な操作を完全にゼロにする代替アプローチとして、`php`ディレクティブを使用し、 +パスによってPHPファイルとそれ以外を分ける方法があります。アプリケーション全体が1つのエントリーファイルで提供される場合、この方法は有効です。 +たとえば`/assets`フォルダの背後で静的ファイルを提供する[設定](config.md#caddyfile-config)は次のようになります: + +```caddyfile +route { + @assets { + path /assets/* + } + + # /assets 以下のリクエストはファイルサーバーが処理する + file_server @assets { + root /root/to/your/app + } + + # /assets 以外のすべてのリクエストは index または worker の PHP ファイルで処理する + rewrite index.php + php { + root /root/to/your/app # ここで root を明示的に追加すると、キャッシュの効率が向上します + } +} +``` + +## プレースホルダー + +`root`および`env`ディレクティブ内では、[プレースホルダー](https://caddyserver.com/docs/conventions#placeholders)を使用できます。 +ただし、これによりこれらの値をキャッシュすることができなくなり、大幅なパフォーマンスコストが発生します。 + +可能であれば、これらのディレクティブではプレースホルダーの使用を避けてください。 + +## `resolve_root_symlink` + +デフォルトでは、ドキュメントルートがシンボリックリンクである場合、FrankenPHP はそれを自動的に解決します(これは PHP が正しく動作するために必要です)。 +ドキュメントルートがシンボリックリンクでない場合、この機能を無効にできます。 + +```caddyfile +php_server { + resolve_root_symlink false +} +``` + +この設定は、`root`ディレクティブに[プレースホルダー](https://caddyserver.com/docs/conventions#placeholders)が含まれている場合にパフォーマンスを向上させます。 +それ以外の場合の効果はごくわずかです。 + +## ログ + +ログ出力は当然ながら非常に有用ですが、その性質上、 +I/O操作およびメモリ確保が必要となり、パフォーマンスを大幅に低下させます。 +[ログレベルを正しく設定](https://caddyserver.com/docs/caddyfile/options#log)し、 +必要なもののみをログに記録するようにしてください。 + +## PHPパフォーマンス + +FrankenPHPは公式のPHPインタープリターを使用しています。 +通常のPHPに関するパフォーマンス最適化はすべてFrankenPHPでも有効です。 + +特に以下の点を確認してください: + +- [OPcache](https://www.php.net/manual/en/book.opcache.php)がインストールされ、有効化され、適切に設定されていること +- [Composer autoloader optimizations](https://getcomposer.org/doc/articles/autoloader-optimization.md)を有効にすること +- `realpath`キャッシュがアプリケーションのニーズに合わせて十分な大きさであること +- [preloading](https://www.php.net/manual/en/opcache.preloading.php)を使用すること + +詳細については、[Symfonyの専用ドキュメントエントリ](https://symfony.com/doc/current/performance.html)をお読みください +(Symfonyを使用していなくても、多くのヒントが役立ちます)。 diff --git a/docs/ja/production.md b/docs/ja/production.md new file mode 100644 index 00000000..cd5f8217 --- /dev/null +++ b/docs/ja/production.md @@ -0,0 +1,144 @@ +# 本番環境でのデプロイ + +このチュートリアルでは、Docker Composeを使用して単一サーバーにPHPアプリケーションをデプロイする方法を学びます。 + +Symfonyを使用している場合は、Symfony Dockerプロジェクトの「[本番環境へのデプロイ](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)」ドキュメントを参照してください。 + +API Platformを使用している場合は、[フレームワークのデプロイドキュメント](https://api-platform.com/docs/deployment/)を参照してください。 + +## アプリの準備 + +まず、PHPプロジェクトのルートディレクトリに`Dockerfile`を作成します: + +```dockerfile +FROM dunglas/frankenphp + +# "your-domain-name.example.com" を実際のドメイン名に置き換えてください +ENV SERVER_NAME=your-domain-name.example.com +# HTTPSを無効にしたい場合は、次の値を代わりに使用してください: +#ENV SERVER_NAME=:80 + +# プロジェクトで "public" ディレクトリをWebルートとして使用していない場合、ここで設定できます: +# ENV SERVER_ROOT=web/ + +# PHPの本番設定を有効化 +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +# プロジェクトのPHPファイルをpublicディレクトリにコピー +COPY . /app/public +# Symfony や Laravel を使用している場合は、代わりにプロジェクト全体をコピーする必要があります: +#COPY . /app +``` + +より詳細な情報やカスタマイズ方法、PHP拡張モジュールやCaddyモジュールのインストール方法については、 +「[カスタムDockerイメージのビルド](docker.md)」を参照してください。 + +プロジェクトでComposerを使用している場合は、 +DockerイメージにComposerを含め、依存関係をインストールしてください。 + +次に、 `compose.yaml` ファイルを追加します: + +```yaml +services: + php: + image: dunglas/frankenphp + restart: always + ports: + - "80:80" # HTTP + - "443:443" # HTTPS + - "443:443/udp" # HTTP/3 + volumes: + - caddy_data:/data + - caddy_config:/config + +# Caddyの証明書と設定に必要なボリューム +volumes: + caddy_data: + caddy_config: +``` + +> [!NOTE] +> +> 上記の例は本番環境向けです。 +> 開発環境では、ボリューム、異なるPHP設定、`SERVER_NAME`環境変数の異なる値を使用したい場合があります。 +> +> [Symfony Docker](https://github.com/dunglas/symfony-docker)プロジェクト(FrankenPHPを使用)では、 +> マルチステージイメージ、Composer、追加のPHP拡張モジュールなどを活用した、 +> より高度な例を見ることができます。 + +最後に、Gitを使用している場合は、これらのファイルをコミットしてプッシュします。 + +## サーバーの準備 + +本番環境にアプリケーションをデプロイするには、サーバーが必要です。 +このチュートリアルではDigitalOceanの仮想マシンを使用しますが、他のLinuxサーバーでも同様に動作します。 +DockerがインストールされたLinuxサーバーが既にある場合は、[次のセクション](#ドメイン名の設定)に進んでください。 + +まだサーバーがない場合は、[このアフィリエイトリンク](https://m.do.co/c/5d8aabe3ab80)を使用して$200の無料クレジットを取得し、アカウントを作成してください。その後、「Create a Droplet」をクリックします。 +次に、「Choose an image」セクションの下の「Marketplace」タブをクリックし、「Docker」という名前のアプリを検索します。 +これにより、DockerとDocker Composeの最新バージョンが既にインストールされたUbuntuサーバーがプロビジョニングされます! + +テスト目的であれば、最安のプランで十分です。 +実際の本番使用では、おそらくニーズに合わせて「general purpose」セクションのプランを選びたいでしょう。 + +![FrankenPHPをDockerでDigitalOceanにデプロイ](digitalocean-droplet.png) + +他の設定はデフォルトのままにするか、必要に応じて調整も可能です。 +SSHキーを追加するかパスワードを作成することを忘れずに行い、「Finalize and create」ボタンをクリックしてください。 + +次に、Dropletがプロビジョニングされるまで数秒待ちます。 +Dropletの準備ができたら、SSHを使用して接続します: + +```console +ssh root@ +``` + +## ドメイン名の設定 + +ほとんどの場合、サイトにドメイン名を関連付けたいでしょう。 +まだドメイン名を所有していない場合は、レジストラーを通じて購入する必要があります。 + +次に、サーバーのIPアドレスを指すドメイン名のタイプ`A`のDNSレコードを作成します: + +```dns +your-domain-name.example.com. IN A 207.154.233.113 +``` + +DigitalOceanのドメインサービス(「Networking」 > 「Domains」)での例: + +![DigitalOceanでのDNS設定](digitalocean-dns.png) + +> [!NOTE] +> +> FrankenPHPがデフォルトで使用しているTLS証明書の自動生成サービスLet's Encryptは、IPアドレスの直接使用をサポートしていません。Let's Encryptを使用するにはドメイン名の使用が必須です。 + +## デプロイ + +`git clone`や`scp`など、目的に合ったツールを使用してプロジェクトをサーバーにコピーします。 +GitHubを使用している場合は、[deploy key](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys)の使用を検討してください。 +deploy keyは[GitLabでもサポートされています](https://docs.gitlab.com/ee/user/project/deploy_keys/)。 + +Gitでの例: + +```console +git clone git@github.com:/.git +``` + +プロジェクトディレクトリ(``)に移動し、本番モードでアプリを開始します: + +```console +docker compose up --wait +``` + +サーバーが起動し、HTTPS証明書が自動的に生成されます。 +`https://your-domain-name.example.com`にアクセスしてお楽しみください! + +> [!CAUTION] +> +> Dockerはキャッシュレイヤーを持つ可能性があるため、各デプロイメントで正しいビルドを持っているか確認するか、キャッシュの問題を避けるために`--no-cache`オプションでプロジェクトを再ビルドしてください。 + +## 複数ノードへのデプロイ + +複数のマシンクラスターにアプリをデプロイしたい場合は、提供されるComposeファイルと互換性のある[Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/)を +使用できます。 +Kubernetesでデプロイするには、FrankenPHPを使用する[API Platformで提供されるHelmチャート](https://api-platform.com/docs/deployment/kubernetes/)をご覧ください。 diff --git a/docs/ja/static.md b/docs/ja/static.md new file mode 100644 index 00000000..18872012 --- /dev/null +++ b/docs/ja/static.md @@ -0,0 +1,161 @@ +# 静的ビルドの作成 + +PHPライブラリのローカルインストールを使用する代わりに、 +[static-php-cli プロジェクト](https://github.com/crazywhalecc/static-php-cli)を利用して、FrankenPHPの静的またはほぼ静的なビルドを作成することが可能です(プロジェクト名に「CLI」とありますが、CLIだけでなく全てのSAPIをサポートしています)。 + +この方法を使えば、PHPインタープリター、Caddy Webサーバー、FrankenPHPをすべて含んだ単一でポータブルなバイナリを作成できます! + +完全に静的なネイティブ実行ファイルは依存関係を全く必要とせず、[`scratch` Dockerイメージ](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch)上でも実行可能です。 +ただし、動的PHP拡張モジュール(Xdebugなど)をロードできず、musl libcを使用しているため、いくつかの制限があります。 + +ほぼ静的なバイナリは`glibc`のみを必要とし、動的拡張モジュールをロードできます。 + +可能であれば、glibcベースのほぼ静的ビルドの使用をお勧めします。 + +また、FrankenPHPは[静的バイナリへのPHPアプリの埋め込み](embed.md)もサポートしています。 + +## Linux + +静的なLinuxバイナリをビルドするためのDockerイメージを提供しています: + +### muslベースの完全静的ビルド + +依存関係なしにあらゆるLinuxディストリビューションで動作する完全静的バイナリ(ただし拡張モジュールの動的ロードはサポートしない)を作成するには、以下を実行します: + +```console +docker buildx bake --load static-builder-musl +docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl +``` + +高い並行性が求められるシナリオでは、より良いパフォーマンスのため、[mimalloc](https://github.com/microsoft/mimalloc)アロケーターの使用を検討してください。 + +```console +docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl +``` + +### glibcベースのほぼ静的なビルド(動的拡張モジュールのサポートあり) + +選択した拡張モジュールを静的にコンパイルしながら、さらにPHP拡張モジュールを動的にロードできるバイナリを作成するには、以下を実行します: + +```console +docker buildx bake --load static-builder-gnu +docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu +``` + +このバイナリは、glibcバージョン2.17以上をすべてサポートしますが、muslベースシステム(Alpine Linuxなど)では動作しません。 + +生成されたほぼ静的(`glibc`を除く)バイナリは`frankenphp`という名前で、カレントディレクトリに出力されます。 + +Dockerを使わずに静的バイナリをビルドしたい場合は、macOS向けの手順を参照してください。これらの手順はLinuxでも使用できます。 + +### カスタム拡張モジュール + +デフォルトでは、よく使われるPHP拡張モジュールがコンパイルされます。 + +バイナリのサイズを削減したり、攻撃対象領域(アタックサーフェス)を減らすために、`PHP_EXTENSIONS`というDocker引数を使用してビルドする拡張モジュールを明示的に指定できます。 + +例えば、`opcache`と`pdo_sqlite`拡張モジュールのみをビルドするには、以下のように実行します: + +```console +docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl +# ... +``` + +有効にした拡張に必要なライブラリを追加するには、`PHP_EXTENSION_LIBS`というDocker引数を渡すことができます: + +```console +docker buildx bake \ + --load \ + --set static-builder-musl.args.PHP_EXTENSIONS=gd \ + --set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \ + static-builder-musl +``` + +### 追加のCaddyモジュール + +Caddyの拡張モジュールを追加したい場合は、`XCADDY_ARGS`というDocker引数を使用して、[xcaddy](https://github.com/caddyserver/xcaddy)に渡す引数を以下のように指定できます: + +```console +docker buildx bake \ + --load \ + --set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \ + static-builder-musl +``` + +この例では、Caddy用の[Souin](https://souin.io)HTTPキャッシュモジュールと[cbrotli](https://github.com/dunglas/caddy-cbrotli)、[Mercure](https://mercure.rocks)、[Vulcain](https://vulcain.rocks)モジュールを追加しています。 + +> [!TIP] +> +> cbrotli、Mercure、Vulcainモジュールは、`XCADDY_ARGS`が空または設定されていない場合はデフォルトで含まれます。 +> `XCADDY_ARGS`の値をカスタマイズする場合、デフォルトのモジュールは含まれなくなるため、必要なものは明示的に記述してください。 + +[ビルドのカスタマイズ](#ビルドのカスタマイズ)も参照してください + +### GitHubトークン + +GitHub API レート制限に達した場合は、`GITHUB_TOKEN`という名前の環境変数にGitHub Personal Access Tokenを設定してください: + +```console +GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl +# ... +``` + +## macOS + +macOS用の静的バイナリを作成するには以下のスクリプトを実行してください([Homebrew](https://brew.sh/)がインストールされている必要があります): + +```console +git clone https://github.com/php/frankenphp +cd frankenphp +./build-static.sh +``` + +なお、このスクリプトはLinux(おそらく他のUnix系OS)でも動作し、私たちが提供するDockerイメージ内部でも使用されています。 + +## ビルドのカスタマイズ + +以下の環境変数を`docker build`や`build-static.sh` +スクリプトに渡すことで、静的ビルドをカスタマイズできます: + +- `FRANKENPHP_VERSION`: 使用するFrankenPHPのバージョン +- `PHP_VERSION`: 使用するPHPのバージョン +- `PHP_EXTENSIONS`: ビルドするPHP拡張([サポートされる拡張のリスト](https://static-php.dev/en/guide/extensions.html)) +- `PHP_EXTENSION_LIBS`: 拡張モジュールに追加機能を持たせるためにビルドする追加ライブラリ +- `XCADDY_ARGS`: 追加のCaddyモジュールを導入するなど[xcaddy](https://github.com/caddyserver/xcaddy)に渡す引数 +- `EMBED`: バイナリに埋め込むPHPアプリケーションのパス +- `CLEAN`: 指定するとlibphpおよびそのすべての依存関係がスクラッチからビルドされます(キャッシュなし) +- `NO_COMPRESS`: UPXを使用して結果のバイナリを圧縮しない +- `DEBUG_SYMBOLS`: 指定すると、デバッグシンボルが除去されず、バイナリに含まれます +- `MIMALLOC`: (実験的、Linuxのみ)パフォーマンス向上のためにmuslのmallocngを[mimalloc](https://github.com/microsoft/mimalloc)に置き換えます。muslをターゲットとするビルドにのみこれを使用することをお勧めします。glibcの場合は、このオプションを無効にして、代わりにバイナリを実行する際に[`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html)を使用することをお勧めします。 +- `RELEASE`: (メンテナー用)指定すると、生成されたバイナリがGitHubにアップロードされます + +## 拡張モジュール + +glibcまたはmacOSベースのバイナリでは、PHP拡張モジュールを動的にロードできます。ただし、これらの拡張はZTSサポートでコンパイルされている必要があります。 +ほとんどのパッケージマネージャーは現在、拡張のZTSバージョンを提供していないため、自分でコンパイルする必要があります。 + +このために、`static-builder-gnu`Dockerコンテナをビルドして実行し、リモートでアクセスし、`./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`で拡張をコンパイルできます。 + +[Xdebug拡張モジュール](https://xdebug.org)の場合: + +```console +docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 . +docker create --name static-builder-gnu -it gnu-ext /bin/sh +docker start static-builder-gnu +docker exec -it static-builder-gnu /bin/sh +cd /go/src/app/dist/static-php-cli/buildroot/bin +git clone https://github.com/xdebug/xdebug.git && cd xdebug +source scl_source enable devtoolset-10 +../phpize +./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config +make +exit +docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so +docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp +docker stop static-builder-gnu +docker rm static-builder-gnu +docker rmi gnu-ext +``` + +これにより、現在のディレクトリに`frankenphp`と`xdebug-zts.so`が作成されます。 +`xdebug-zts.so`を拡張ディレクトリに移動し、php.iniに`zend_extension=xdebug-zts.so`を追加してFrankenPHPを実行すると、Xdebugがロードされます。 diff --git a/docs/ja/worker.md b/docs/ja/worker.md new file mode 100644 index 00000000..3440d0c3 --- /dev/null +++ b/docs/ja/worker.md @@ -0,0 +1,181 @@ +# FrankenPHPワーカーの使用 + +アプリケーションを一度起動してメモリに保持します。 +FrankenPHPは数ミリ秒で受信リクエストを処理します。 + +## ワーカースクリプトの開始 + +### Docker + +`FRANKENPHP_CONFIG`環境変数の値を`worker /path/to/your/worker/script.php`に設定します: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +### スタンドアロンバイナリ + +`php-server`コマンドの`--worker`オプションを使って、現在のディレクトリのコンテンツをワーカーを通じて提供できます: + +```console +frankenphp php-server --worker /path/to/your/worker/script.php +``` + +PHPアプリが[バイナリに埋め込まれている](embed.md)場合は、アプリのルートディレクトリにカスタムの`Caddyfile`を追加することができます。 +これが自動的に使用されます。 + +また、`--watch`オプションを使えば、[ファイルの変更に応じてワーカーを再起動](config.md#watching-for-file-changes)することも可能です。 +以下のコマンドは、`/path/to/your/app/`ディレクトリおよびそのサブディレクトリ内の`.php`で終わるファイルが変更された場合に再起動をトリガーします: + +```console +frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php" +``` + +## Symfonyランタイム + +FrankenPHPのワーカーモードは[Symfony Runtime Component](https://symfony.com/doc/current/components/runtime.html)によってサポートされています。 +ワーカーでSymfonyアプリケーションを開始するには、FrankenPHP用の[PHP Runtime](https://github.com/php-runtime/runtime)パッケージをインストールします: + +```console +composer require runtime/frankenphp-symfony +``` + +アプリケーションサーバーを起動するには、FrankenPHP Symfony Runtimeを使用するように`APP_RUNTIME`環境変数を定義します: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker ./public/index.php" \ + -e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +## Laravel Octane + +[専用ドキュメント](laravel.md#laravel-octane)を参照してください。 + +## カスタムアプリ + +以下の例は、サードパーティライブラリに依存せずに独自のワーカースクリプトを作成する方法を示しています: + +```php +boot(); + +// ループの外側にハンドラーを配置してパフォーマンスを向上(処理量を減らす) +$handler = static function () use ($myApp) { + // リクエストを受信した際に呼び出され、 + // スーパーグローバルや php://input などがリセットされます。 + echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER); +}; + +$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0); +for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) { + $keepRunning = \frankenphp_handle_request($handler); + + // HTTPレスポンスの送信後に何か処理を行います + $myApp->terminate(); + + // ページ生成の途中でガベージコレクタが起動する可能性を減らすために、ここでガベージコレクタを明示的に呼び出す。 + gc_collect_cycles(); + + if (!$keepRunning) break; +} + +// クリーンアップ +$myApp->shutdown(); +``` + +次に、アプリを開始し、`FRANKENPHP_CONFIG`環境変数を使用してワーカーを設定します: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker ./public/index.php" \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +デフォルトでは、CPU当たり2つのワーカーが開始されます。 +開始するワーカー数を設定することもできます: + +```console +docker run \ + -e FRANKENPHP_CONFIG="worker ./public/index.php 42" \ + -v $PWD:/app \ + -p 80:80 -p 443:443 -p 443:443/udp \ + dunglas/frankenphp +``` + +### 一定数のリクエスト処理後にワーカーを再起動する + +PHPはもともと長時間実行されるプロセス向けに設計されていなかったため、メモリリークを引き起こすライブラリやレガシーコードがいまだに多く存在します。 +こうしたコードをワーカーモードで利用するための回避策として、一定数のリクエストを処理した後にワーカースクリプトを再起動する方法があります: + +前述のワーカー用スニペットでは、`MAX_REQUESTS`という名前の環境変数を設定することで、処理する最大リクエスト数を設定できます。 + +### ワーカーの手動再起動 + +[ファイルの変更を監視](config.md#watching-for-file-changes)してワーカーを再起動することも可能ですが、 +[Caddy admin API](https://caddyserver.com/docs/api)を使用してすべてのワーカーをグレースフルに(安全に)再起動することも可能です。adminが +[Caddyfile](config.md#caddyfile-config)で有効になっている場合、次のような単純なPOSTリクエストで再起動エンドポイントにpingできます: + +```console +curl -X POST http://localhost:2019/frankenphp/workers/restart +``` + +### ワーカーの失敗 + +ワーカースクリプトがゼロ以外の終了コードでクラッシュした場合、FrankenPHP は指数的バックオフ戦略を用いて再起動を行います。 +ワーカースクリプトが最後のバックオフ時間 × 2 より長く稼働し続けた場合、 +それ以降の再起動ではペナルティを科しません。 +しかし、スクリプトにタイプミスがあるなど短時間で何度もゼロ以外の終了コードで失敗し続ける場合、 +FrankenPHP は`too many consecutive failures`というエラーとともにクラッシュします。 + +連続失敗の回数上限は、[Caddyfile](config.md#caddyfile-config)の`max_consecutive_failures`オプションで設定できます: + +```caddyfile +frankenphp { + worker { + # ... + max_consecutive_failures 10 + } +} +``` + +## スーパーグローバルの動作 + +[PHPのスーパーグローバル](https://www.php.net/manual/en/language.variables.superglobals.php)(`$_SERVER`、`$_ENV`、`$_GET`など) +は以下のように動作します: + +- `frankenphp_handle_request()`が最初に呼び出される前は、スーパーグローバルにはワーカースクリプト自体にバインドされた値が格納されています +- `frankenphp_handle_request()`の呼び出し中および呼び出し後は、スーパーグローバルには処理されたHTTPリクエストから生成された値が格納され、`frankenphp_handle_request()`を呼び出すたびにスーパーグローバルの値が変更されます + +コールバック内でワーカースクリプトのスーパーグローバルにアクセスするには、それらをコピーしてコールバックのスコープにコピーをインポートする必要があります: + +```php +