Learn/flight_vs_laravel
Flight vs Laravel
Laravel とは?
Laravel は、すべての機能が揃ったフル機能のフレームワークで、素晴らしい開発者向けエコシステムを備えていますが、パフォーマンスと複雑さの代償を伴います。Laravel の目標は、開発者が最高レベルの生産性を発揮し、共通のタスクを容易にすることです。Laravel は、フル機能のエンタープライズ Web アプリケーションを構築したい開発者にとって優れた選択肢です。これにはいくつかのトレードオフが伴い、特にパフォーマンスと複雑さの点でそうです。Laravel の基礎を学ぶのは簡単ですが、フレームワークに習熟するには時間がかかる場合があります。
また、Laravel のモジュールが非常に多く、開発者は問題を解決する唯一の方法がこれらのモジュールを使うことだと感じることがよくあります。しかし、実際には別のライブラリを使ったり、自分のコードを書いたりするだけで十分な場合もあります。
Flight との比較での利点
- Laravel は、共通の問題を解決するために使用できる 巨大なエコシステム の開発者とモジュールを持っています。
- Laravel は、データベースとやり取りするために使用できるフル機能の ORM を備えています。
- Laravel は、フレームワークを学ぶために使用できる膨大なドキュメントとチュートリアルを持っています。これは、細部まで掘り下げるのに良い一方で、量が多すぎて大変な場合もあります。
- Laravel は、アプリケーションを保護するために使用できる組み込みの認証システムを持っています。
- Laravel は、フレームワークを学ぶために使用できるポッドキャスト、カンファレンス、ミーティング、ビデオ、その他のリソースを持っています。
- Laravel は、フル機能のエンタープライズ Web アプリケーションを構築したい経験豊富な開発者向けに設計されています。
Flight との比較での欠点
- Laravel は Flight よりも内部で多くの処理が行われており、これにより 劇的な パフォーマンスの低下が生じます。詳細は TechEmpower ベンチマーク を参照してください。
- Flight は、軽量で高速、使いやすい Web アプリケーションを構築したい開発者向けに設計されています。
- Flight は、シンプルさと使いやすさを重視しています。
- Flight のコア機能の1つは、後方互換性を最大限に保つことです。一方、Laravel はメジャーバージョン間で 多くのフラストレーション を引き起こします。
- Flight は、フレームワークの世界に初めて足を踏み入れる開発者向けです。
- Flight は依存関係がなく、一方 Laravel はひどい量の依存関係 を持っています。
- Flight もエンタープライズレベルのアプリケーションを作成できますが、Laravel ほどボイラープレートコードが多くありません。ただし、開発者が組織化と構造化を維持するためにより多くの規律を必要とします。
- Flight は開発者にアプリケーションに対するより多くの制御を与えますが、Laravel は裏側で多くのマジックがあり、それがフラストレーションを生むことがあります。
Learn/migrating_to_v3
v3 への移行
後方互換性は主に維持されていますが、v2 から v3 への移行時に注意すべきいくつかの変更点があります。これらの変更は、デザインパターンとあまりにも対立するため、いくつかの調整が必要でした。
出力バッファリングの動作
v3.5.0
出力バッファリング は、PHP スクリプトによって生成された出力がクライアントに送信される前に、PHP 内部のバッファに保存されるプロセスです。これにより、出力がクライアントに送信される前にそれを変更できます。
MVC アプリケーションでは、Controller が「マネージャー」であり、view が何をするかを管理します。Controller の外(または Flight の場合、時には匿名関数)で出力が生成されることは、MVC パターンを破ります。この変更は、MVC パターンに沿うようにし、フレームワークをより予測しやすく使いやすくするためのものです。
v2 では、出力バッファリングは一貫して自身の出力バッファを閉じない方法で処理されており、これが ユニットテスト および ストリーミング をより困難にしました。ほとんどのユーザーにとって、この変更は実際には影響を与えない可能性があります。ただし、コールバックやコントローラーの外(例: フック内)でコンテンツを出力している場合、問題が発生する可能性が高いです。フック内でコンテンツを出力したり、フレームワークが実際に実行される前に出力したりすることは、過去には動作したかもしれませんが、今後は動作しません。
問題が発生する可能性のある箇所
// index.php
require 'vendor/autoload.php';
// 例です
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// これは実際に問題ありません
echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});
Flight::before('start', function(){
// このようなものはエラーを引き起こします
echo '<html><head><title>My Page</title></head><body>';
});
Flight::route('/', function(){
// これは実際に問題ありません
echo 'Hello World';
// これも問題ないはずです
Flight::hello();
});
Flight::after('start', function(){
// これはエラーを引き起こします
echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});
v2 レンダリング動作の有効化
古いコードを書き換えずに v3 で動作させることはまだ可能ですか? はい、可能です! flight.v2.output_buffering 構成オプションを true に設定することで、v2 レンダリング動作を有効にできます。これにより、古いレンダリング動作を継続して使用できますが、今後修正することを推奨します。フレームワークの v4 では、これが削除されます。
// index.php
require 'vendor/autoload.php';
Flight::set('flight.v2.output_buffering', true);
Flight::before('start', function(){
// 今度はこれも問題ありません
echo '<html><head><title>My Page</title></head><body>';
});
// さらにコード
ディスパッチャーの変更
v3.7.0
Dispatcher の静的メソッド、例えば Dispatcher::invokeMethod()、Dispatcher::execute() などを直接呼び出していた場合、これらのメソッドを直接呼び出さないようにコードを更新する必要があります。Dispatcher は、よりオブジェクト指向的に変更され、Dependency Injection コンテナをより簡単に使用できるようにされました。Dispatcher のようにメソッドを呼び出す必要がある場合、手動で $result = $class->$method(...$params); や call_user_func_array() を使用できます。
halt() stop() redirect() および error() の変更
v3.10.0
3.10.0 以前のデフォルト動作は、ヘッダーとレスポンスボディの両方をクリアするものでした。これを、レスポンスボディのみをクリアするように変更しました。ヘッダーもクリアする必要がある場合、Flight::response()->clear() を使用できます。
Learn/configuration
設定
概要
Flightは、アプリケーションのニーズに合わせてフレームワークのさまざまな側面を設定する簡単な方法を提供します。一部はデフォルトで設定されていますが、必要に応じて上書きできます。また、アプリケーション全体で使用する独自の変数を設定することもできます。
明確で階層化された設定(ファイルのデフォルト + 環境シークレット)は、AIコーディングツールにも役立ちます。エージェントは、コントローラー内で $_ENV 読み取りを独自に作り出す代わりに、リテラルのための場所とシークレットのための場所を1つずつ学ぶことができます。
理解
Flightの特定の動作は、setメソッドを通じて設定値を設定することでカスタマイズできます。
Flight::set('flight.log_errors', true);
構造化されたアプリ(スケルトンを含む)では、通常 app/config/config.php からプロジェクト設定を読み込み、関連するキーをエンジンに適用します(例: flight.base_url、flight.views.path)。また、グローバルをあちこちで読み取る代わりに、小さな設定オブジェクトをコントローラーに注入することもできます。これにより、テストや AGENTS.md に従うエージェントにとってより親しみやすくなります。
基本的な使い方
Flight設定オプション
以下は、利用可能なすべての設定項目のリストです。
- flight.base_url
?string- Flightがサブディレクトリで実行されている場合、リクエストのベースURLを上書きします。(デフォルト: null) - flight.case_sensitive
bool- URLの大文字小文字を区別したマッチングを行います。(デフォルト: false) - flight.handle_errors
bool- Flightがすべてのエラーを内部的に処理できるようにします。(デフォルト: true) - flight.log_errors
bool- エラーをWebサーバーのエラーログファイルに記録します。(デフォルト: false)- Tracyをインストールしている場合、Tracyはこの設定ではなくTracyの設定に基づいてエラーを記録します。
- flight.debug
bool- エラー発生時に、詳細なエラー情報(例外メッセージ、コード、スタックトレース)をブラウザに出力します。(デフォルト: false)- 本番環境では絶対に有効にしないでください — 内部のアプリケーション詳細が漏洩します。ローカル開発またはステージング環境でのみ使用してください。
falseの場合、代わりに一般的な500 Internal Server Errorが表示されます。サーバー側でエラーを記録するには、flight.log_errorsと組み合わせてください。
- flight.allow_method_override
bool-X-HTTP-Method-OverrideリクエストヘッダーまたはPOST本文の_methodフィールドを介してHTTPメソッドを上書きできるようにします。(デフォルト: true)- HTMLフォームベースのメソッド偽装を必要としないアプリケーションでは、これを
falseに設定することをお勧めします。これにより、クライアントが標準のPOSTフォームを介してDELETEやPUTリクエストを偽装することを防ぎます。 - 詳細については、セキュリティを参照してください。
- HTMLフォームベースのメソッド偽装を必要としないアプリケーションでは、これを
- flight.views.path
string- ビューテンプレートファイルを含むディレクトリ。(デフォルト: ./views) - flight.views.extension
string- ビューテンプレートファイルの拡張子。(デフォルト:.php。公式スケルトンではTwigを使用する場合、これを.twigに設定します) - flight.content_length
bool-Content-Lengthヘッダーを設定します。(デフォルト: true)- Tracyを使用している場合、Tracyが正しくレンダリングされるようにこれをfalseに設定する必要があります。
- flight.v2.output_buffering
bool- レガシー出力バッファリングを使用します。v3への移行を参照してください。(デフォルト: false)
ローダー設定
ローダーにはもう1つの設定項目があります。これにより、クラス名に_を含むクラスをオートロードできます。
// アンダースコアを使用したクラス読み込みを有効にする
// デフォルトはtrue
Loader::$v2ClassLoading = false;
オートローディングは、名前空間と一致するフォルダーの大文字小文字にも依存することを忘れないでください。特にスケルトンの App\ + app/Controller/ レイアウトでは重要です。
プロジェクト設定と.env(スケルトンパターン)
Flightのコアは.envファイルを必要としません。多くのアプリはPHPの設定配列のみを使用します。公式スケルトンは設定を階層化しているため、シークレットをgitの管理外に保ちながら、Runwayがリテラル設定を安全に書き換えることができます。
.env/ 実際の環境 — シークレットとデプロイ時の上書き(gitignoreされます)。app/config/config.php— リテラルなPHP配列のデフォルト(config_sample.phpからコピー)。このファイル内では$_ENV[...]式を使わないことをお勧めします。runway config:setのようなツールはこれを静的値として書き換え、シークレットをファイルに焼き付ける可能性があります。- ブートストラップ時にマージ — マッピングされたキーでは環境変数が優先されます。アプリコードはコントローラー内の
$_ENVではなく、設定オブジェクトまたは$app->get()を読み取ります。
config_sample.php / config.php の例(簡略版):
<?php
// リテラルのみ — シークレットはスケルトンワークフローでは .env に置く
return [
'app' => [
'env' => 'development',
'debug' => true,
'base_url' => '/',
'timezone' => 'UTC',
],
'database' => [
'driver' => 'sqlite', // または mysql、または無効にする場合は ''
'host' => 'localhost',
'dbname' => '',
'user' => '',
'password' => '',
'file_path' => __DIR__ . '/../../database.sqlite',
],
// ...
];
# .env.example → .env(スケルトン)
APP_ENV=development
APP_DEBUG=true
FLIGHT_BASE_URL=/
DB_DRIVER=sqlite
# DB_PASSWORD=...
この分割は、AIフレンドリーなプロジェクトのために意図的に行われています。手順書には「デフォルトは config.php、シークレットは .env、Config / Engine を注入し、コントローラーで env アクセスを独自に作らないこと」と記載できます。既存のアプリは .env を完全に無視して、単一の設定ファイルを維持することもできます。
変数
Flightを使用すると、アプリケーションのどこでも使用できる変数を保存できます。
// 変数を保存
Flight::set('id', 123);
// アプリケーション内の別の場所
$id = Flight::get('id');
変数が設定されているかどうかを確認するには、次のようにします。
if (Flight::has('id')) {
// 何かを行う
}
変数をクリアするには、次のようにします。
// id変数をクリア
Flight::clear('id');
// すべての変数をクリア
Flight::clear();
注: 変数を設定できるからといって、それを使うべきとは限りません。この機能は控えめに使用してください。ここに保存されたものはすべてグローバル変数になるためです。グローバル変数は、アプリケーションのどこからでも変更できるため、バグの追跡が難しくなります。さらに、ユニットテストなどを複雑にする可能性があります。コントローラーが必要とするサービスや設定には、コンストラクター注入(スケルトン + Dice設定のように)を優先してください。
エラーと例外
すべてのエラーと例外はFlightによってキャッチされ、errorメソッドに渡されます(flight.handle_errorsがtrueに設定されている場合)。
デフォルトの動作は、いくつかのエラー情報を含む一般的な HTTP 500 Internal Server Error レスポンスを送信することです。
この動作は、必要に応じて上書きできます。
Flight::map('error', function (Throwable $error) {
// エラーを処理
echo $error->getTraceAsString();
});
デフォルトでは、エラーはWebサーバーに記録されません。設定を変更することで有効にできます。
Flight::set('flight.log_errors', true);
404 Not Found
URLが見つからない場合、FlightはnotFoundメソッドを呼び出します。デフォルトの動作は、簡単なメッセージを含むHTTP 404 Not Foundレスポンスを送信することです。
この動作は、必要に応じて上書きできます。
Flight::map('notFound', function () {
// 見つからない場合の処理
});
関連項目
- インストール - スケルトン設定、
.env、ブートストラップの構成。 - オートローディング - 名前空間とフォルダーの大文字小文字。
- Flightの拡張 - Flightのコア機能を拡張およびカスタマイズする方法。
- ユニットテスト - Flightアプリケーションのユニットテストの書き方。
- AIと開発者エクスペリエンス -
AGENTS.mdと一貫したプロジェクト指示。 - Tracy - 高度なエラー処理とデバッグのためのプラグイン。
- Tracy拡張機能 - TracyをFlightと統合するための拡張機能。
- APM - アプリケーションパフォーマンス監視とエラートラッキングのためのプラグイン。
- セキュリティ - セキュリティ強化フラグとシークレットの取り扱い。
トラブルシューティング
- 設定のすべての値を確認するのに問題がある場合は、
var_dump(Flight::get());を実行できます。 - Runwayまたはデプロイツールが
config.phpを書き換えた場合は、シークレットがコミットされていないことを確認してください。スケルトンパターンを使用する場合は、シークレットを.envまたは実際の環境に保持してください。
変更履歴
- ドキュメント – スケルトンスタイルの設定 /
.envの階層化と、新しいプロジェクト向けのTwigビュー拡張子のデフォルトを文書化。 - v3.18.1 -
flight.debugおよびflight.allow_method_override設定オプションを追加。 - v3.5.0 - レガシー出力バッファリング動作をサポートするための
flight.v2.output_buffering設定を追加。 - v2.0 - コア設定を追加。
Learn/ai
Flight と AI / 開発者エクスペリエンス
概要
Flight は AI コーディングツールと 連携する ように設計されており、対抗するものではありません。シンプルで予測可能な API、公式スケルトン による明確なアプリ構成、そしてプロジェクト固有の指示ファイルにより、GitHub Copilot、Cursor、Windsurf、Claude Code、Gemini などのアシスタントは、あなたが手書きするのと同じパターンに従うことができます。
組み込みの Runway コマンドを使用して LLM プロバイダーに接続し、プロジェクトの指示を生成できるため、Flight はあなたとあなたのチームが、毎回同じコンテキストをチャットに貼り付けることなく、一貫性のある関連性の高い支援を受けられるようにします。
はじめに
AI コーディングアシスタントは、プロジェクトのコンテキスト、規約、目標を理解しているときに最も役立ちます。Flight の AI ヘルパーを使用すると、次のことができます。
- プロジェクトを一般的な LLM プロバイダー(OpenAI、Grok、Claude など)に接続する
- プロジェクト固有の指示を生成・更新して、全員が同じガイダンスを受け取れるようにする
- 手書きコードと AI 生成コードを同じレイアウトに保つ(特にスケルトンを使用する場合)
これらの機能は Flight コア CLI(Runway 経由)に同梱されており、公式の flightphp/skeleton スターターに事前に組み込まれています。
スケルトンが AI 向けに提供するもの
公式スターターは、AI ツールにとって AGENTS.md を信頼できる情報源 として扱います。
| ファイル | 役割 |
|---|---|
AGENTS.md(プロジェクトルート) |
グローバルなルール、起動フロー、名前空間、DI、「やってはいけないこと」 |
スコープ付き AGENTS.md(app/、migrations/、tests/ など) |
そのツリー内で作業する際の、軽量でフォルダ固有のヒント |
SECURITY.md |
シークレット、ヘッダー、XSS/SQL、報告——セキュリティは意図的に独立して管理されます |
スケルトンには、Copilot / Cursor / Gemini / Windsurf 用の独立したハウススタイルファイルは ありません。アシスタントはルートの AGENTS.md を参照させ(スコープ付きファイルへのリンクを辿らせます)、人間はこれらのファイルを完全に無視して README を使用できます。レイアウトはどちらの場合も同じです。
ドキュメントは API を教え、スケルトンはレイアウトを教えます。 これらのドキュメントにある短い
Flight::の例は学習に適しています。スケルトンアプリでは、コントローラー内の静的ファサードよりもApp\…クラス、コンストラクターインジェクション、$this->appを優先してください。インストール と オートローディング を参照してください。
基本的な使い方
LLM 認証情報の設定
ai:init コマンドは、プロジェクトを LLM プロバイダーに接続する手順をガイドします。
php runway ai:init
次のプロンプトが表示されます。
- プロバイダーの選択(OpenAI、Grok、Claude など)
- API キーの入力
- ベース URL とモデル名の設定
これにより、後続の LLM リクエスト(指示の生成など)で使用される認証情報が作成されます。
例:
Welcome to AI Init!
Which LLM API do you want to use? [1] openai, [2] grok, [3] claude: 1
Enter the base URL for the LLM API [https://api.openai.com]:
Enter your API key for openai: sk-...
Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc) [gpt-4o]:
Credentials saved to .runway-creds.json
プロジェクト固有の AI 指示の生成
ai:generate-instructions コマンドは、あなたのプロジェクトに合わせた AI コーディングアシスタント向けの指示を作成または更新します。
php runway ai:generate-instructions
いくつかの質問(説明、データベース、テンプレートエンジン、セキュリティ、チーム規模など)に答えます。Flight は LLM プロバイダーを使用して指示を生成し、主に次の場所に書き込みます。
- プロジェクトルートの
AGENTS.md(ツールに依存しない形式。公式スケルトンと最新のエージェントのほとんどが期待する形式です)
CLI のバージョンとオプションによっては、このコマンドは古いワークフロー向けのツール固有のコピー(Copilot、Cursor、Windsurf、Gemini のルールファイルなど)も書き込む場合があります。スケルトンからの新規プロジェクトでは、AGENTS.md(および app/ の下に保持するスコープ付き AGENTS.md ファイル)を単一の情報源として扱い、5 つの異なる指示ファイルを手作業で保守しないでください。
例:
Please describe what your project is for? My awesome API
What database are you planning on using? MySQL
What HTML templating engine will you plan on using (if any)? twig
Is security an important element of this project? (y/n) y
...
AI instructions updated successfully.
これで、AI ツールは一般的な PHP チュートリアルではなく、実際のスタックとレイアウトに一致するコードを提案できるようになります。
高度な使い方
- コマンドオプションで認証情報や出力パスをカスタマイズできます(各コマンドの
--helpを参照)。 - これらのヘルパーは、OpenAI 互換 API を話す任意の LLM プロバイダーで動作します。
- プロジェクトの進化に合わせて
ai:generate-instructionsを再実行し、エージェントを最新の状態に保ってください。 - スケルトンでは、セキュリティポリシーは
SECURITY.mdに、コーディングレイアウトはAGENTS.mdに維持し、どちらのドキュメントも雑多なものにしないでください。 - エージェントが API の詳細を必要とする場合は、docs.flightphp.com と Flight MCP サーバーを優先し、発明されたメソッドは
vendor/flightphp/coreに対して検証してください。
関連情報
- Flight Skeleton –
AGENTS.md、Twig、SimplePdo、Dice を AI フレンドリーな構成で組み込んだ公式スターター - インストール – 推奨される
create-projectレイアウト - オートローディング – フォルダの大文字小文字が名前空間と一致します(
App\Controller↔app/Controller/) - Runway CLI –
ai:*およびスキャフォールディングコマンドを提供する CLI - セキュリティ – エージェント(そして人間も)が弱めるべきではない安全なデフォルト
トラブルシューティング
- 「Missing .runway-creds.json」というエラーが表示された場合は、最初に
php runway ai:initを実行してください。 - API キーが有効で、選択したモデルにアクセスできることを確認してください。
- 指示が更新されない場合は、プロジェクトディレクトリのファイル権限を確認してください。
- エージェントが Flight API をでっち上げたり、間違ったフォルダレイアウトを使用したりする場合は、ルートの
AGENTS.mdとこのドキュメントサイトを参照させてください。app/の下のコードではスケルトレイアウトが優先されます。
変更履歴
- v3.18.4 –
ai:generate-instructionsがプロジェクトルートのAGENTS.mdにプロジェクト指示を書き込むようになりました。 - v3.16.0 – AI 統合用の CLI コマンド
ai:initとai:generate-instructionsを追加しました。
Learn/unit_testing_and_solid_principles
この記事は元々 2015 年に Airpair で公開されました。全クレジットは元々この記事を書いた Airpair と Brian Fenton に帰属しますが、ウェブサイトはもはや利用できず、記事は Wayback Machine でのみ存在します。この記事は PHP コミュニティ全体の学習と教育目的でサイトに追加されました。
1 セットアップと構成
1.1 現在のものを保持
最初からこれを強調しましょう - 野生で使われている PHP のインストールの驚くほど少ない数が、現在のもの、または更新されたものです。これは共有ホスティングの制限、デフォルトが誰も変更しないこと、またはアップグレードテストのための時間/予算がないためです。PHP のバイナリは後回しにされがちです。ですから、常に現在の PHP のバージョンを使用するという明確なベストプラクティスを強調する必要があります(この記事時点では 5.6.x)。さらに、PHP 自体と使用している拡張やベンダーライブラリを定期的にアップグレードするスケジュールを組むことも重要です。アップグレードにより、新しい言語機能、改善された速度、少ないメモリ使用量、そしてセキュリティ更新が得られます。アップグレードを頻繁に行うほど、プロセスが苦痛にならなくなります。
1.2 適切なデフォルトを設定
PHP は php.ini.development と php.ini.production ファイルでデフォルトの良い設定をしますが、さらに改善できます。例えば、それらは日付/タイムゾーンを設定してくれません。これは配布の観点から理にかなっていますが、設定がないと、日付/時間関連の関数を呼び出すたびに E_WARNING エラーが発生します。以下は推奨設定です:
- date.timezone - サポートされているタイムゾーンのリスト から選択
- session.savepath - セッションをファイルで使用し、他の保存ハンドラでない場合、これを /tmp 以外の場所に設定。 /tmp をそのままにしておくと、共有ホスティング環境でリスクがあります。なぜなら /tmp_ は通常、権限が広く開かれているからです。スティッキービットが設定されていても、このディレクトリのコンテンツをリストできる人は、すべてのアクティブなセッション ID を知ることができます。
- session.cookie_secure - PHP コードを HTTPS で提供している場合、これをオンに。
- session.cookie_httponly - PHP セッションクッキーが JavaScript からアクセスされないように設定
- もっと... iniscan のようなツールを使って、構成の一般的な脆弱性をテスト
1.3 拡張
使用しない拡張(例: データベースドライバなど)は無効にする(または少なくとも有効にしない)のが良い考えです。有効になっているものを確認するには、phpinfo() コマンドを実行するか、コマンドラインでこれを実行します。
$ php -i
情報は同じですが、phpinfo() には HTML フォーマットが追加されています。CLI バージョンは、特定の情報を検索するために grep にパイプしやすくなります。例えば。
$ php -i | grep error_log
ただし、この方法の注意点: ウェブ向けのバージョンと CLI バージョンの PHP 設定が異なる可能性があります。
2 Composer を使用
これは驚きかもしれませんが、現代の PHP を書くためのベストプラクティスの一つは、少ないコードを書くことです。プログラミングを上達させる最良の方法は実際にやることでありますが、ルーティング、基本的な入力検証ライブラリ、単位変換、データベース抽象レイヤーなどの多くの問題は、PHP 領域で既に解決されています。ただ Packagist に行って調べてみてください。おそらく、解決しようとしている問題の重要な部分が既に書かれていてテストされているでしょう。
すべてを自分で書きたくなる temptation はありますが(学習体験として自分のフレームワークやライブラリを書くこと自体は問題ありません)、Not Invented Here の感情に戦って、時間と頭痛を節約してください。代わりに PIE の教義に従ってください - Proudly Invented Elsewhere。また、自分で書くものを選んだ場合、既存のものと大きく異なったり優れているものでない限り、公開しないでください。
Composer は PHP のパッケージマネージャーで、Python の pip、Ruby の gem、Node の npm に似ています。JSON ファイルでコードの依存を定義し、それらの要件を解決して必要なコードバンドルをダウンロードしてインストールします。
2.1 Composer のインストール
これはローカルプロジェクトだと仮定しますので、現在のプロジェクト用の Composer のインスタンスをインストールしましょう。プロジェクトディレクトリに移動して、これを実行します:
$ curl -sS https://getcomposer.org/installer | php
任意のダウンロードをスクリプトインタープリタ (sh, ruby, php など) に直接パイプするのはセキュリティリスクです。ですから、インストールコードを読み、実行する前に快適に感じてください。
利便性のために ( php composer.phar install より composer install とタイプするのが好みなら)、composer の単一コピーをグローバルにインストールするコマンドを使えます:
$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer
ファイル権限によって、sudo で実行する必要があるかもしれません。
2.2 Composer の使用
Composer は管理できる依存の主なカテゴリを 2 つ持っています: "require" と "require-dev"。 "require" としてリストされた依存はどこでもインストールされますが、"require-dev" の依存は特にリクエストされた場合にのみインストールされます。通常、これらはアクティブな開発中のツールで、PHP_CodeSniffer のようなものです。以下は Guzzle、人気の HTTP ライブラリをインストールする方法の例です。
$ php composer.phar require guzzle/guzzle
開発目的のみのツールをインストールするには、--dev フラグを追加します:
$ php composer.phar require --dev 'sebastian/phpcpd'
これは PHP Copy-Paste Detector を、開発専用依存としてインストールします。他のコード品質ツールです。
2.3 Install 対 update
最初に composer install を実行すると、composer.json ファイルに基づいて必要なライブラリとその依存をインストールします。それが完了すると、composer は composer.lock というロックファイルを作成します。このファイルには、composer が見つけた依存とその正確なバージョン、 hashes が含まれています。その後、将来 composer install を実行するたびに、ロックファイルを見てその正確なバージョンをインストールします。
composer update は少し違います。composer.lock ファイル (存在する場合) を無視して、composer.json の制約を満たす各依存の最新バージョンを探します。完了したら、新しい composer.lock ファイルを書き込みます。
2.4 オートロード
composer install と composer update の両方が、インストールしたライブラリを使うために必要なファイルを PHP に教える autoloader を生成します。使用するには、この行を追加します (通常、毎リクエストで実行されるブートストラップファイルに):
require 'vendor/autoload.php';
3 良い設計原則に従う
3.1 SOLID
SOLID は、良いオブジェクト指向ソフトウェア設計の 5 つの主要な原則を思い出すためのニーモニックです。
3.1.1 S - 単一責任原則
これは、クラスは 1 つの責任だけを持つべきだと言っています。つまり、変更する理由は 1 つだけです。これは、Unix の哲学である、1 つのことをうまくやる小さなツールのたくさんと一致します。1 つのことだけをするクラスは、テストしやすく、デバッグしやすく、驚かされにくくなります。Validator クラスのメソッド呼び出しが DB レコードを更新しないようにしたいです。以下は ActiveRecord pattern に基づくアプリケーションでよく見られる、SRP 違反の例です。
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
public function save() {}
}
これは基本的な entity モデルです。ただし、これらのうち 1 つはここに属していません。エンティティモデルの唯一の責任は、それが表すエンティティに関連する行動であって、自分自身を永続化する責任を持つべきではありません。
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
}
class DataStore
{
public function save(Model $model) {}
}
これは改善です。Person モデルは 1 つのことだけに戻り、save 行動は永続化オブジェクトに移動しました。また、Model のみを型ヒントに使用したことに注意してください。Person ではありません。SOLID の L と D の部分でこれに戻ります。
3.1.2 O - 開放閉鎖原則
これをまとめた素晴らしいテストがあります: 実装する機能について考えてみてください。おそらく最近作業したもの、または作業中のもの。既存のコードベースで、新しいクラスを追加するだけで、既存のクラスの変更なしにその機能を実装できますか? 構成と配線コードは少し例外ですが、ほとんどのシステムでこれは驚くほど難しいです。ポリモーフックディスパッチに頼らなければなりませんし、ほとんどのコードベースはそれに設定されていません。これに興味があるなら、polymorphism and writing code without Ifs についての良い Google トークが YouTube にあります。ボーナスとして、トークは Miško Hevery によって行われ、多くの人が AngularJs の作成者として知っています。
3.1.3 L - Liskov 置換原則
この原則は Barbara Liskov の名前にちなんで名付けられ、以下のように述べられています:
"プログラム内のオブジェクトは、そのサブタイプのインスタンスに置き換えても、プログラムの正しさを変えないべきです。"
これはすべて良さそうですが、例でより明確に示されます。
abstract class Shape
{
public function getHeight();
public function setHeight($height);
public function getLength();
public function setLength($length);
}
これは基本的な四角形を表します。何も特別なものはありません。
class Square extends Shape
{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight($height) {
$this->size = $height;
}
public function getLength() {
return $this->size;
}
public function setLength($length) {
$this->size = $length;
}
}
私たちの最初の形状、Square です。まっすぐな形状ですね? 寸法を設定するコンストラクタがあると仮定できますが、この実装から、length と height は常に同じになることがわかります。Square はそういうものです。
class Rectangle extends Shape
{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}
public function setHeight($height) {
$this->height = $height;
}
public function getLength() {
return $this->length;
}
public function setLength($length) {
$this->length = $length;
}
}
だからここに別の形状があります。同じメソッドシグネチャを持ちますが、四角形ですが、お互いに置き換えて使い始めるとどうなるでしょうか? Shape の height を変更すると、shape の length が一致しなくなります。私たちの Square 形状に与えた契約に違反しています。
これは LSP の違反の教科書的な例で、型システムを最大限に活用するためにこのような原則が必要です。duck typing でさえ、基礎的な行動が違うことを教えてくれませんし、それが壊れるまで知ることはできないので、最初から違うものにしないのが最善です。
3.1.3 I - インターフェース分離原則
この原則は、多くの小さな、細かいインターフェースを好むと言っています。一つ大きなものではなく。インターフェースは行動に基づくべきで、"これらのクラスの一つ" ではありません。PHP に付属するインターフェースを考えてみてください。Traversable、Countable、Serializable などです。それらはオブジェクトが持つ能力を宣伝し、継承するものではありません。だから、インターフェースを小さく保ってください。30 メソッドを持つものは望ましくなく、3 が良い目標です。
3.1.4 D - 依存逆転原則
これは Dependency Injection について話した他の場所で聞いたことがあるかもしれませんが、依存逆転と依存注入は全く同じものではありません。依存逆転は、システムの詳細ではなく抽象に依存すべきだと言う方法です。日常的にこれは何を意味するでしょうか?
コード全体で mysqli_query() を直接使用しないで、DataStore->query() のようなものを使ってください。
この原則の核心は抽象についてです。つまり、"データベースアダプタを使用" と言うことなので、mysqli_query のような直接呼び出しに依存しないということです。mysqli_query を半分のクラスで直接使用している場合、すべてをデータベースに直接結びつけています。ここで MySQL に反対しているわけではありませんが、mysqli_query を使用している場合、そのような低レベルの詳細は 1 つの場所に隠され、汎用ラッパー経由で公開されるべきです。
今、私はこれが hackneyed な例だと知っていますが、製品が本番環境にある後でデータベースエンジンを完全に変更する回数は非常に少ないです。私は人々が自分のコードからアイデアに慣れていると思ったので選んだものです。また、特定のデータベースに固執している場合でも、その抽象ラッパーオブジェクトはバグを修正、行動を変更、または選択したデータベースに欲しい機能を実装することを可能にします。また、ユニットテストを可能にします。
4 オブジェクトキャリスティクス
これはこれらの原則への完全な潜入ではありませんが、最初の 2 つは簡単に覚えやすく、良い価値を提供し、ほぼすべてのコードベースにすぐに適用できます。
4.1 メソッドごとのインデントを 1 レベル以内に
これは、メソッドを小さなチャンクに分解して考えるのに役立ち、より明確で自己文書化されたコードを残します。インデントのレベルが多いほど、メソッドがより多くのことをし、作業中に頭の中で追跡する状態が増えます。
すぐに人々がこれに反対するでしょうが、これはガイドライン/ヒューリスティックで、厳格なルールではありません。私は PHP_CodeSniffer のルールをこれで施行するのを期待していません (しかし people have)。
これがどうなるかを素早くサンプルで実行しましょう:
public function transformToCsv($data)
{
$csvLines = array();
$csvLines[] = implode(',', array_keys($data[0]));
foreach ($data as $row) {
if (!$row) {
continue;
}
$csvLines[] = implode(',', $row);
}
return $csvLines;
}
これはひどいコードではありません (技術的に正しく、テスト可能など) が、これを明確にするために多くを改善できます。ここでネストのレベルを減らすには?
まず、foreach ループを簡略化する必要があります (または完全に削除) ので、そこから始めましょう。
if (!$row) {
continue;
}
これは簡単です。これは空の行を無視するだけです。ループに到達する前に、PHP の組み込み関数でこれをショートカットできます。
$data = array_filter($data);
foreach ($data as $row) {
$csvLines[] = implode(',', $row);
}
今、単一のネストレベルです。しかし、これを見ると、配列の各項目に関数を適用しているだけです。これで foreach ループは必要ありません。
$data = array_filter($data);
$csvLines = array_map(function($row) {
return implode(',', $row);
}, $data);
今、ネストが全くありません、そしてコードは速くなるでしょう。なぜなら、ループをネイティブ C 関数で行っているからです。ただし、implode にコンマを渡すための少しの trickery が必要なので、前のステップで止めるのがより理解しやすいと主張できます。
4.2 else を使用しない
これは 2 つの主要なアイデアを扱っています。1 つ目は、メソッドからの複数の return 文です。メソッドの結果についての決定を下すのに十分な情報がある場合、その決定を下して return してください。2 つ目は Guard Clauses として知られるアイデアです。これらは基本的に、メソッドの先頭近くで検証チェックと早期 return を組み合わせたものです。意味を説明しましょう。
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {
$sum = $first + $second + $third;
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
return $sum;
}
これは再びストレートフォワードで、3 つの int を加えて結果を返します、またはパラメータのいずれかが整数でない場合 null を返します。AND 演算子でこれらのチェックを 1 行に組み合わせられることを無視して、入れ子になった if/else 構造がコードを追いづらくしていると思います。代わりにこの例を見てください。
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
return null;
}
if (!is_int($third)) {
return null;
}
return $first + $second + $third;
}
私にとってこの例はより追いやすいです。ここで guard clauses を使用して、パラメータについての初期の主張を検証し、それらが通過しない場合すぐにメソッドを終了します。また、sum をメソッド全体で追跡する中間変数もありません。私たちは既に happy path にあり、来たことをするだけです。再び、すべてのチェックを 1 つの if でできるのですが、原則は明確です。
5 ユニットテスト
ユニットテストは、コードの行動を検証する小さなテストを書く練習です。ほとんどいつもコードと同じ言語 (この場合 PHP) で書かれ、いつでも実行できるほど速いです。これらはコードを改善するための非常に価値あるツールです。コードが何をしているかを確保するという明らかな利点以外に、ユニットテストは設計フィードバックも提供します。テストしにくいコードは、設計問題をしばしば示します。また、回帰に対する安全網を与え、より頻繁にリファクタリングし、コードをよりクリーンな設計に進化させることを可能にします。
5.1 ツール
PHP にはいくつかのユニットテストツールがありますが、断然最も一般的なのは PHPUnit です。PHAR ファイルを directly ダウンロードするか、composer でインストールできます。composer を他のすべてに使用しているので、その方法を示します。また、PHPUnit は本番環境に展開されない可能性が高いので、dev 依存として以下コマンドでインストールできます:
composer require --dev phpunit/phpunit
5.2 テストは仕様
コード内のユニットテストの最も重要な役割は、コードが何をするはずかを提供する実行可能な仕様です。テストコードが間違っている、またはコードにバグがあるとしても、システムが supposed に何をするかを知ることは非常に価値があります。
5.3 最初にテストを書く
コードの前にテストを書いたものと、コードが完成した後に書いたものを比較すると、驚くほど違います。"後" のテストはクラスの実装詳細に焦点を当て、良い行カバレッジを確保しますが、"前" のテストは望ましい外部行動を検証します。それがユニットテストで気にするものです。つまり、クラスが正しい行動を示すことです。実装に焦点を当てたテストは、クラスの内部が変更すると壊れるので、リファクタリングを難しくします、そして OOP の情報隠蔽の利点を失います。
5.4 良いユニットテストの条件
良いユニットテストは以下の特性を共有します:
- 速い - ミリ秒で実行。
- ネットワークアクセスなし - 無線をオフにしたり、ケーブルを抜いてもすべてのテストが通る。
- ファイルシステムアクセスの制限 - 速度と環境への柔軟性を追加。
- データベースアクセスなし - コストのかかるセットアップとクリーンアップ活動を避ける。
- 1 つずつテスト - ユニットテストは失敗する理由を 1 つだけ持つ。
- 良い名前 - 5.2 を参照。
- ほとんど偽オブジェクト - ユニットテスト内の唯一の "real" オブジェクトはテストしているオブジェクトとシンプルな値オブジェクトで、残りは test double の一部。
これらのいくつかに反する理由がありますが、一般的なガイドラインとして役立ちます。
5.5 テストが苦痛なとき
Unit testing forces you to feel the pain of bad design up front - Michael Feathers
ユニットテストを書いているとき、クラスを実際に使用してものを成し遂げています。テストを最後に書くか、または最悪の場合、QA や誰かにコードを投げてテストを書かせるなら、クラスが実際にどう行動するかのフィードバックが得られません。テストを書いていて、クラスが本当に苦痛なら、それを書きながら知ることになり、これはほぼ最安の修正時間です。
クラスがテストしにくい場合、それは設計の欠陥です。異なる欠陥は異なる方法で現れます。多くの mocking をしなければならない場合、クラスに依存が多すぎるか、メソッドが多すぎる可能性があります。各テストのためのセットアップが多い場合、メソッドが多すぎる可能性が高いです。行動を練習するために複雑なテストシナリオを書かなければならない場合、クラスのメソッドが多すぎる可能性があります。プライベートメソッドと状態の内部を掘ってテストしなければならない場合、もしかすると別のクラスが外に出ようとしているのかもしれません。ユニットテストは "iceberg classes" を公開するのが非常に上手く、クラスの 80% が保護またはプライベートコードで隠されているものです。私は以前、可能な限り多くを保護にするのが大ファンでしたが、今は個々のクラスが多すぎる責任を持っていたことに気づき、真の解決策はクラスを小さな部分に分解するでした。
Brian Fenton 執筆 - Brian Fenton はミッドウェストとベイエリアで 8 年間 PHP 開発者で、現在 Thismoment で働いています。彼はコード職人技と設計原則に焦点を当てています。ブログは www.brianfenton.us、Twitter は @brianfenton。お父さんをしている以外に、食べ物、ビール、ゲーム、そして学習を楽しんでいます。
Learn/security
セキュリティ
概要
Webアプリケーションにおいて、セキュリティは非常に重要です。アプリケーションを安全に保ち、ユーザーのデータを保護する必要があります。Flightは、Webアプリケーションを保護するための多くの機能を提供します。
公式のskeletonには、専用のSECURITY.mdとセキュリティヘッダーミドルウェアも同梱されており、AIコーディングツール(および人間)が、AGENTS.mdの一般的なコーディングスタイルとは別に、シークレット、ヘッダー、XSS/SQLルールを意図的に配置できる場所を提供します。
理解
Webアプリケーションを構築する際に注意すべき一般的なセキュリティ脅威がいくつかあります。最も一般的な脅威には次のようなものがあります。
- クロスサイトリクエストフォージェリ(CSRF)
- クロスサイトスクリプティング(XSS)
- SQLインジェクション
- クロスオリジンリソースシェアリング(CORS)
テンプレートは、出力をデフォルトでエスケープすることでXSSを防ぐのに役立ちます(TwigとLatteはこれを自動で行います。この利点を活かしてください)。セッションは、以下に説明するようにCSRFトークンをユーザーのセッションに保存することでCSRFを防ぐことができます。PDOでのプリペアドステートメント、またはSimplePdoのヘルパーを使用すると、SQLインジェクションを防ぐことができます。CORSは、Flight::start()が呼び出される前のシンプルなフックで処理できます。
これらの方法はすべて連携して、Webアプリケーションのセキュリティを維持します。セキュリティのベストプラクティスを学び、理解することを常に最優先にしてください。トレードオフを理解せずにページを読み込むためだけに、AIアシスタントに「CSPを無効にする」またはヘッダーを弱めるように依頼してはいけません。
基本的な使い方
ヘッダー
HTTPヘッダーは、Webアプリケーションを保護する最も簡単な方法のひとつです。クリックジャッキング、XSS、その他の攻撃を防ぐためにヘッダーを使用できます。アプリケーションにこれらのヘッダーを追加する方法はいくつかあります。
ヘッダーのセキュリティを確認するのに役立つ優れたWebサイトは、securityheaders.comとobservatory.mozilla.orgです。以下のコードを設定したら、これらのWebサイトでヘッダーが機能していることを簡単に確認できます。
skeletonには、App\Middleware\SecurityHeadersMiddleware(リクエストごとのnonce、フレームオプション、HSTSなどを備えたCSP)が含まれています。ヘッダーを無効にするよりも、意図的に拡張することを推奨します。
手動で追加する
Flight\Responseオブジェクトのheaderメソッドを使用して、これらのヘッダーを手動で追加できます。
// クリックジャッキングを防ぐためにX-Frame-Optionsヘッダーを設定
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
// XSSを防ぐためにContent-Security-Policyヘッダーを設定
// 注:このヘッダーは非常に複雑になるため、
// アプリケーションに合わせてインターネット上の例を参照してください
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
// XSSを防ぐためにX-XSS-Protectionヘッダーを設定
Flight::response()->header('X-XSS-Protection', '1; mode=block');
// MIMEスニッフィングを防ぐためにX-Content-Type-Optionsヘッダーを設定
Flight::response()->header('X-Content-Type-Options', 'nosniff');
// 送信されるリファラー情報の量を制御するためにReferrer-Policyヘッダーを設定
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
// HTTPSを強制するためにStrict-Transport-Securityヘッダーを設定
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// 使用できる機能とAPIを制御するためにPermissions-Policyヘッダーを設定
Flight::response()->header('Permissions-Policy', 'geolocation=()');
これらは、routes.phpまたはindex.phpファイルの先頭に追加できます。
フィルターとして追加する
次のように、フィルター/フックで追加することもできます。
// フィルターでヘッダーを追加
Flight::before('start', function() {
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
Flight::response()->header('X-XSS-Protection', '1; mode=block');
Flight::response()->header('X-Content-Type-Options', 'nosniff');
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
Flight::response()->header('Permissions-Policy', 'geolocation=()');
});
ミドルウェアとして追加する
適用するルートを柔軟に指定できるミドルウェアクラスとして追加することもできます。一般に、これらのヘッダーはすべてのHTMLおよびAPIレスポンスに適用する必要があります。
skeletonスタイルのパスと名前空間(フォルダーの大文字小文字はApp\Middlewareと一致):
// app/Middleware/SecurityHeadersMiddleware.php
namespace App\Middleware;
use flight\Engine;
class SecurityHeadersMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
$response = $this->app->response();
// インラインスクリプトがある場合は、ブートストラップのCSP nonceを優先します(skeletonはcsp_nonceを設定します)
$nonce = $this->app->get('csp_nonce');
$csp = $nonce
? "default-src 'self'; script-src 'self' 'nonce-{$nonce}'; style-src 'self' 'nonce-{$nonce}'"
: "default-src 'self'";
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header('Content-Security-Policy', $csp);
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');
$response->header('Referrer-Policy', 'no-referrer-when-downgrade');
$response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->header('Permissions-Policy', 'geolocation=()');
}
}
// app/config/routes.php — 空の文字列グループ = すべてのルートに適用されるグローバルミドルウェア
use App\Middleware\SecurityHeadersMiddleware;
use flight\net\Router;
$router->group('', function (Router $router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// その他のルート
}, [SecurityHeadersMiddleware::class]);
古いプロジェクトではapp/middlewaresとapp\middlewaresがまだ使用されている場合があります。フォルダーが一致していれば機能します。新しいskeletonアプリではapp/Middleware/とApp\Middlewareを使用します。オートローディングを参照してください。
クロスサイトリクエストフォージェリ(CSRF)
クロスサイトリクエストフォージェリ(CSRF)は、悪意のあるWebサイトがユーザーのブラウザーにあなたのWebサイトへのリクエストを送信させる攻撃の一種です。これは、ユーザーの知らないうちにあなたのWebサイトでアクションを実行するために使用される可能性があります。Flightには組み込みのCSRF保護メカニズムはありませんが、ミドルウェアを使用して簡単に独自のものを実装できます。
セットアップ
まず、CSRFトークンを生成してユーザーのセッションに保存する必要があります。次に、このトークンをフォームで使用し、フォームが送信されたときにチェックできます。セッションを管理するには、flightphp/sessionプラグインを使用します。
// CSRFトークンを生成し、ユーザーのセッションに保存します
// (セッションオブジェクトを作成してFlightにアタッチしていると仮定します)
// 詳細はセッションドキュメントを参照してください
Flight::register('session', flight\Session::class);
// セッションごとにトークンを1つ生成するだけで済みます(同じユーザーの
// 複数のタブやリクエストにわたって機能します)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
デフォルトのPHP Flightテンプレートを使用する場合
<!-- フォームでCSRFトークンを使用 -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- その他のフォームフィールド -->
</form>
Twigを使用する場合(skeletonのデフォルト)
Twig関数を登録するか、トークンをすべてのフォームビューに渡します。グローバルとフォームフィールドを使用した最小限の例:
// Twigを設定するとき(例:services.php)
$twig->addGlobal('csrf_token', $app->session()->get('csrf_token'));
{# app/views/form.twig #}
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
{# その他のフィールド #}
</form>
Latteを使用する場合
LatteテンプレートでCSRFトークンを出力するカスタム関数を設定することもできます。
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// その他の設定...
// CSRFトークンを出力するカスタム関数を設定
$latte->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
$latte->render($finalPath, $data, $block);
});
そして、Latteテンプレートでcsrf()関数を使用してCSRFトークンを出力できます。
<form method="post">
{csrf()}
<!-- その他のフォームフィールド -->
</form>
CSRFトークンのチェック
CSRFトークンは、いくつかの方法でチェックできます。
ミドルウェア
// app/Middleware/CsrfMiddleware.php
namespace App\Middleware;
use flight\Engine;
class CsrfMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
if($this->app->request()->method == 'POST') {
$token = $this->app->request()->data->csrf_token;
if($token !== $this->app->session()->get('csrf_token')) {
$this->app->halt(403, 'Invalid CSRF token');
}
}
}
}
// routes.php
use App\Middleware\CsrfMiddleware;
$router->group('', function ($router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// その他のルート
}, [CsrfMiddleware::class]);
イベントフィルター
// このミドルウェアは、リクエストがPOSTかどうかを確認し、POSTの場合はCSRFトークンが有効かどうかをチェックします
Flight::before('start', function() {
if(Flight::request()->method == 'POST') {
// フォームの値からCSRFトークンを取得
$token = Flight::request()->data->csrf_token;
if($token !== Flight::session()->get('csrf_token')) {
Flight::halt(403, 'Invalid CSRF token');
// またはJSONレスポンスの場合
Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
}
}
});
クロスサイトスクリプティング(XSS)
クロスサイトスクリプティング(XSS)は、悪意のあるフォーム入力をあなたのWebサイトにコードを注入できる攻撃の一種です。これらの機会のほとんどは、エンドユーザーが記入するフォームの値から発生します。ユーザーからの出力を決して信頼してはいけません!すべてのユーザーが世界最高のハッカーであると常に想定してください。彼らは悪意のあるJavaScriptやHTMLをあなたのページに注入する可能性があります。このコードは、ユーザーから情報を盗んだり、あなたのWebサイトでアクションを実行したりするために使用される可能性があります。FlightのビュークラスまたはTwigやLatteのようなテンプレートエンジンを使用すると、XSS攻撃を防ぐために出力を簡単にエスケープできます。
// ユーザーが賢くて、これを自分の名前として使用しようとしていると仮定します
$name = '<script>alert("XSS")</script>';
// これにより出力がエスケープされます
Flight::view()->set('name', $name);
// これにより出力されます: <script>alert("XSS")</script>
// Twig(skeletonのデフォルト)とLatteはデフォルトで自動エスケープします — 生のPHP echoよりもこれらを優先してください
Flight::render('template', ['name' => $name]);
// Twig: {{ name }} → エスケープされます
// コンテンツが完全に信頼できる場合を除き、|raw やエスケープされていない出力は避けてください
SQLインジェクション
SQLインジェクションは、悪意のあるユーザーがSQLコードをデータベースに注入できる攻撃の一種です。これは、データベースから情報を盗んだり、データベースでアクションを実行したりするために使用される可能性があります。繰り返しますが、ユーザーからの入力を決して信頼してはいけません!常に彼らは血を求めていると想定してください。プリペアドステートメントを使用してください。SimplePdoヘルパーはこれをデフォルトのパスにします。
// Flight::db() が SimplePdo として登録されていると仮定します(またはコントローラーに SimplePdo を注入します)
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();
// SimplePdo(推奨)— バインドされたパラメーターを使用したワンライナー
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);
// ?プレースホルダーでも同様
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);
skeletonスタイルのコントローラーでは、テストとAI生成コードの一貫性を保つために、Flight::db()よりもSimplePdoのコンストラクター注入を推奨します(DIC)。
安全でない例
以下は、SQLプリペアドステートメントを使用して、以下のような無害な例から保護する理由です。
// エンドユーザーがWebフォームに記入します。
// フォームの値として、ハッカーは次のようなものを入力します:
$username = "' OR 1=1; -- ";
$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// クエリが構築された後、次のようになります
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// 奇妙に見えますが、これは機能する有効なクエリです。実際、
// これはすべてのユーザーを返す非常に一般的なSQLインジェクション攻撃です。
var_dump($users); // これにより、データベース内のすべてのユーザーがダンプされます(単一のユーザー名だけでなく)
シークレットと設定
- シークレットは
.env(または実際の環境)に置き、コミットされたconfig.phpサンプルには置かないでください。 - skeletonのルール:
config.phpにはリテラルのデフォルト値を設定し、ブートストラップでenvをマージします。コントローラー内で$_ENVを読み取らず、代わりに設定を注入してください。設定を参照してください。 - APIキー、DBパスワード、セッション暗号化キーをコミットしないでください。AIツールに
SECURITY.mdを指定して、安全でないショートカットを作成しないようにしてください。
JSONPコールバックの検証
FlightのFlight::jsonp()メソッドを使用する場合、FlightはJSONPコールバックパラメーター名を厳格な許可リストregex(/^[A-Za-z_$][\w$.]{0,127}$/)に対して検証することに注意してください。このパターンに一致しないコールバック名があると、Flightは例外をスローし、悪意のあるコールバック値を介した任意のJavaScriptの注入を防ぎます。
この検証は組み込まれており、追加の設定は不要ですが、JSONPエンドポイントから予期しないエラーをデバッグするときに知っておくと便利です。
CORS
クロスオリジンリソースシェアリング(CORS)は、Webページ上の多くのリソース(フォント、JavaScriptなど)を、そのリソースが発信されたドメインの外部の別のドメインからリクエストできるようにするメカニズムです。Flightには組み込みの機能はありませんが、Flight::start()メソッドが呼び出される前に実行されるフックで簡単に処理できます。
// app/Utils/CorsUtil.php (skeleton: PascalCaseのUtilsフォルダー → App\Utils)
namespace App\Utils;
use flight\Engine;
class CorsUtil
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function set(array $params = []): void
{
$request = $this->app->request();
$response = $this->app->response();
if ($request->getVar('HTTP_ORIGIN') !== '') {
$this->allowOrigins();
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Max-Age', '86400');
}
if ($request->method === 'OPTIONS') {
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
$response->header(
'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
);
}
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
$response->header(
"Access-Control-Allow-Headers",
$request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
);
}
$response->status(200);
$response->send();
exit;
}
}
private function allowOrigins(): void
{
// 許可するホストをここでカスタマイズします。
$allowed = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:4200',
'http://localhost:8080',
'http://localhost:8100',
];
$request = $this->app->request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = $this->app->response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// ブートストラップ / ルート — startの前に実行
$app = Flight::app();
$cors = new \App\Utils\CorsUtil($app);
$app->before('start', [ $cors, 'set' ]);
Flight設定の堅牢化
Flightには、セキュリティに直接影響するいくつかのエンジン設定があります。これらを正しく設定することは、アプリケーションを堅牢化する最も簡単な方法のひとつです。
flight.allow_method_override
デフォルトでは、FlightはクライアントがX-HTTP-Method-OverrideヘッダーまたはPOST本文の_methodフィールドを使用してリクエストのHTTPメソッドを上書きできるようにします。これはGET/POSTのみを送信できるHTMLフォームには便利ですが、予期していない場合は危険です。攻撃者が通常のフォームを介してDELETEまたはPUTリクエストを偽造する可能性があります。
アプリケーションがこの動作に依存していない場合(たとえば、任意のHTTP動詞を送信できる最新のクライアントやJavaScriptフロントエンドが使用するAPIを構築している場合)、これを無効にする必要があります。
// index.phpまたはブートストラップファイル内、Flight::start()の前
Flight::set('flight.allow_method_override', false);
デフォルト値は後方互換性のためtrueですが、オーバーライド機能を明示的に必要としないアプリケーションではfalseに設定することを強くお勧めします。
flight.debug
Flightには、未処理の例外が発生したときに、詳細なエラー情報(例外メッセージ、コード、完全なスタックトレース)をブラウザーに表示するかどうかを制御するflight.debug設定があります。デフォルトはfalseで、クライアントには内部詳細が漏洩せず、一般的な500 Internal Server Errorメッセージのみが表示されます。
本番サーバーでこれを有効にしないでください。ローカルまたはステージング環境でのみ使用してください。
// ローカル開発のみで安全 — 本番では絶対に使用しないでください
Flight::set('flight.debug', true);
flight.debugがfalse(デフォルト)の場合でも、flight.log_errorsを有効にすることでエラーをキャプチャできます。
// クライアントに公開せずにサーバー側でエラーを記録
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
推奨される本番設定
// index.phpまたはアプリ設定/ブートストラップから適用
Flight::set('flight.allow_method_override', false);
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
エラーハンドリング
本番環境では、攻撃者に情報を漏洩しないように、機密性の高いエラー詳細を非表示にします。本番環境では、display_errorsを0に設定してエラーを表示するのではなく、ログに記録します。
// bootstrap.php または index.php 内
// これを app/config/config.php に追加します
$environment = ENVIRONMENT;
if ($environment === 'production') {
ini_set('display_errors', 0); // エラー表示を無効化
ini_set('log_errors', 1); // 代わりにエラーをログに記録
ini_set('error_log', '/path/to/error.log');
}
// ルートまたはコントローラー内
// 制御されたエラーレスポンスには Flight::halt() を使用
Flight::halt(403, 'Access denied');
入力のサニタイズ
ユーザー入力を信頼しないでください。悪意のあるデータが混入するのを防ぐために、処理前にfilter_varを使用してサニタイズします。アプリコードでは、生の$_GET / $_POSTではなく、$app->request()(またはFlight::request())を介して入力を読み取ることを推奨します。
// $_POST['input'] と $_POST['email'] を持つ $_POST リクエストを想定します
// 文字列入力をサニタイズ
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// メールアドレスをサニタイズ
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);
パスワードのハッシュ化
PHPの組み込み関数であるpassword_hashやpassword_verifyを使用して、パスワードを安全に保存および検証します。パスワードは平文で保存してはいけません。また、可逆的な方法で暗号化してもいけません。ハッシュ化により、データベースが侵害された場合でも、実際のパスワードは保護されたままになります。
$password = Flight::request()->data->password;
// 保存時にパスワードをハッシュ化(例:登録時)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// パスワードを検証(例:ログイン時)
if (password_verify($password, $stored_hash)) {
// パスワードが一致
}
レート制限
キャッシュを使用してリクエストレートを制限し、ブルートフォース攻撃やサービス拒否攻撃から保護します。
// flightphp/cache がインストールおよび登録されていると仮定します
// フィルターで flightphp/cache を使用
Flight::before('start', function() {
$cache = Flight::cache();
$ip = Flight::request()->ip;
$key = "rate_limit_{$ip}";
$attempts = (int) $cache->retrieve($key);
if ($attempts >= 10) {
Flight::halt(429, 'Too many requests');
}
$cache->set($key, $attempts + 1, 60); // 60秒後にリセット
});
関連情報
- セッション - ユーザーセッションを安全に管理する方法。
- テンプレート - Twig/Latteの自動エスケープとXSS。
- SimplePdo - プリペアドステートメントを使用したデータベースヘルパー。
- PdoWrapper - 非推奨です。新しいコードにはSimplePdoを使用してください。
- ミドルウェア - セキュリティヘッダーを追加するプロセスを簡素化するためのミドルウェアの使用方法。
- 設定 -
.envとリテラル設定、本番フラグ。 - AIと開発者エクスペリエンス - エージェント向けに
SECURITY.mdにセキュリティポリシーを保持します。 - レスポンス - セキュリティヘッダーを使用してHTTPレスポンスをカスタマイズする方法。
- リクエスト - ユーザー入力を処理およびサニタイズする方法。
- filter_var - 入力サニタイズ用のPHP関数。
- password_hash - 安全なパスワードハッシュ化のためのPHP関数。
- password_verify - ハッシュ化されたパスワードを検証するためのPHP関数。
トラブルシューティング
- Flight Frameworkのコンポーネントに関する問題のトラブルシューティング情報については、上記の「関連情報」セクションを参照してください。
- CSPがスクリプトをブロックする場合は、nonce(skeletonパターン)を追加するか、特定のオリジンを許可リストに追加してください。計画なしに
script-src *を設定しないでください。
変更履歴
- ドキュメント - skeleton
App\Middleware、Twig CSRF/XSSメモ、SimplePdo、シークレット/.env、AIフレンドリーなプロジェクト向けSECURITY.md。 - v3.18.1 -
flight.allow_method_override、flight.debug、JSONPコールバック検証をカバーするFlight設定の堅牢化セクションを追加。 - v3.1.0 - CORS、エラーハンドリング、入力サニタイズ、パスワードハッシュ化、レート制限に関するセクションを追加。
- v2.0 - XSSを防ぐためにデフォルトビューのエスケープを追加。
Learn/routing
ルーティング
概要
Flight PHP のルーティングは、URL パターンをコールバック関数やクラスのメソッドにマッピングし、高速かつシンプルなリクエスト処理を可能にします。最小限のオーバーヘッド、初心者にも使いやすく、外部依存なしで拡張できるように設計されています。
理解
ルーティングは、HTTP リクエストをアプリケーションのロジックに接続する中核的な仕組みです。ルートを定義することで、関数、クラスメソッド、コントローラアクションのいずれを通しても、異なる URL が特定のコードをトリガーする方法を指定できます。Flight のルーティングシステムは柔軟で、基本的なパターン、名前付きパラメータ、正規表現、依存性注入やリソースフルルーティングなどの高度な機能をサポートしています。このアプローチにより、コードは整理されて保守しやすく保たれ、初心者には高速でシンプル、上級者には拡張可能です。
注: ルーティングについてもっと理解したいですか? 詳しい説明は 「なぜフレームワークを使うのか?」 のページを参照してください。
基本的な使い方
シンプルなルートの定義
Flight の基本的なルーティングは、URL パターンをコールバック関数またはクラスとメソッドの配列とマッチングさせることで行います。
Flight::route('/', function(){
echo 'hello world!';
});
ルートは定義された順序でマッチングされます。リクエストに最初にマッチしたルートが呼び出されます。
コールバックとして関数を使う
コールバックは、呼び出し可能なオブジェクトであれば何でもかまいません。通常の関数も使用できます:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');
コントローラーとしてクラスとメソッドを使う
クラスのメソッド(静的または非静的)も使用できます:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// または
Flight::route('/', [ GreetingController::class, 'hello' ]); // 推奨される方法
// または
Flight::route('/', [ 'GreetingController::hello' ]);
// または
Flight::route('/', [ 'GreetingController->hello' ]);
または、先にオブジェクトを作成してからメソッドを呼び出します:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);
注: デフォルトでは、フレームワーク内でコントローラーが呼び出されるとき、依存性注入コンテナ で指定しない限り、
flight\Engineクラスが常に注入されます。
メソッドごとのルーティング
デフォルトでは、ルートパターンはすべてのリクエストメソッドに対してマッチングされます。URL の前に識別子を置くことで、特定のメソッドに応答できます。
Flight::route('GET /', function () {
echo 'I received a GET request.';
});
Flight::route('POST /', function () {
echo 'I received a POST request.';
});
// ルートには Flight::get() を使用できません。これは変数を取得するメソッドであり、
// ルートを作成するものではないためです。
Flight::post('/', function() { /* code */ });
Flight::patch('/', function() { /* code */ });
Flight::put('/', function() { /* code */ });
Flight::delete('/', function() { /* code */ });
| 区切り文字を使用して、複数のメソッドを単一のコールバックにマッピングすることもできます:
Flight::route('GET|POST /', function () {
echo 'I received either a GET or a POST request.';
});
HEAD および OPTIONS リクエストの特別処理
Flight は、HEAD および OPTIONS HTTP リクエストに対する組み込みの処理を提供します:
HEAD リクエスト
- HEAD リクエスト は
GETリクエストと同様に扱われますが、Flight はレスポンスをクライアントに送信する前にレスポンスボディを自動的に削除します。 - つまり、
GET用のルートを定義すれば、同じ URL への HEAD リクエストは HTTP 標準に従ってヘッダーのみ(コンテンツなし)を返します。
Flight::route('GET /info', function() {
echo 'This is some info!';
});
// /info への HEAD リクエストは同じヘッダーを返しますが、ボディは返しません。
OPTIONS リクエスト
OPTIONS リクエストは、定義された任意のルートに対して Flight によって自動的に処理されます。
- OPTIONS リクエストを受信すると、Flight は
204 No Contentステータスと、そのルートでサポートされているすべての HTTP メソッドを一覧表示するAllowヘッダーで応答します。 - OPTIONS 用に別途ルートを定義する必要はありません。
// 次のように定義されたルートの場合:
Flight::route('GET|POST /users', function() { /* ... */ });
// /users への OPTIONS リクエストは次のように応答します:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS
Router オブジェクトを使う
さらに、使用できるヘルパーメソッドを持つ Router オブジェクトを取得できます:
$router = Flight::router();
// Flight::route() と同様にすべてのメソッドをマッピングする
$router->map('/', function() {
echo 'hello world!';
});
// GET リクエスト
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* code */});
$router->put('/users/update/@id', function() { /* code */});
$router->delete('/users/@id', function() { /* code */});
$router->patch('/users/@id', function() { /* code */});
正規表現(Regex)
ルートで正規表現を使用できます:
Flight::route('/user/[0-9]+', function () {
// これは /user/1234 にマッチします
});
この方法も利用可能ですが、名前付きパラメータ、または正規表現を使用した名前付きパラメータを使用することをお勧めします。読みやすく、保守が容易だからです。
名前付きパラメータ
ルート内に名前付きパラメータを指定でき、コールバック関数に渡されます。これは何よりもルートの読みやすさのためのものです。重要な注意点については以下のセクションを参照してください。
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});
名前付きパラメータに正規表現を含める場合は、: 区切り文字を使用します:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// これは /bob/123 にマッチします
// ただし /bob/12345 にはマッチしません
});
注: 位置パラメータを持つ正規表現グループ
()のマッチングはサポートされていません。例::'\(
重要な注意点
上記の例では、@name が変数 $name に直接結びついているように見えますが、実際はそうではありません。コールバック関数のパラメータの順序が、渡される内容を決定します。コールバック関数のパラメータの順序を入れ替えると、変数も入れ替わります。次に例を示します:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});
そして、/bob/123 という URL にアクセスすると、出力は hello, 123 (bob)! になります。ルートとコールバック関数を設定するときは注意してください!
オプションパラメータ
セグメントを括弧で囲むことで、マッチングにオプションの名前付きパラメータを指定できます。
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// これは次の URL にマッチします:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
マッチしなかったオプションパラメータは NULL として渡されます。
ワイルドカードルーティング
マッチングは個々の URL セグメントに対してのみ行われます。複数のセグメントにマッチさせたい場合は、* ワイルドカードを使用できます。
Flight::route('/blog/*', function () {
// これは /blog/2000/02/01 にマッチします
});
すべてのリクエストを単一のコールバックにルーティングするには、次のようにします:
Flight::route('*', function () {
// 何か処理をする
});
404 Not Found ハンドラ
デフォルトでは、URL が見つからない場合、Flight は非常にシンプルで素朴な HTTP 404 Not Found レスポンスを送信します。よりカスタマイズされた 404 レスポンスが必要な場合は、独自の notFound メソッドを マップ できます:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// カスタムテンプレートで Flight::render() を使用することもできます。
$output = <<<HTML
<h1>My Custom 404 Not Found</h1>
<h3>The page you have requested {$url} could not be found.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});
Method Not Found ハンドラ
デフォルトでは、URL は見つかったがメソッドが許可されていない場合、Flight は非常にシンプルで素朴な HTTP 405 Method Not Allowed レスポンスを送信します(例: Method Not Allowed. Allowed Methods are: GET, POST)。また、その URL で許可されているメソッドを含む Allow ヘッダーも含まれます。
よりカスタマイズされた 405 レスポンスが必要な場合は、独自の methodNotFound メソッドを マップ できます:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// カスタムテンプレートで Flight::render() を使用することもできます。
$output = <<<HTML
<h1>My Custom 405 Method Not Allowed</h1>
<h3>The method you have requested for {$url} is not allowed.</h3>
<p>Allowed Methods are: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});
上級の使い方
ルートでの依存性注入
コンテナ(PSR-11、PHP-DI、Dice など)を介した依存性注入を使用したい場合、それが利用できるルート定義は、オブジェクトを自分で直接作成してコンテナでオブジェクトを作成する方法か、呼び出すクラスとメソッドを文字列で定義する方法のいずれかです。詳細については、依存性注入 のページを参照してください。
簡単な例を示します:
use flight\database\SimplePdo;
// Greeting.php
class Greeting
{
protected SimplePdo $db;
public function __construct(SimplePdo $db) {
$this->db = $db;
}
public function hello(int $id) {
// $this->db を使って何か処理をする
$name = $this->db->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!";
}
}
// index.php
// 必要なパラメータを指定してコンテナをセットアップします
// PSR-11 の詳細については依存性注入のページを参照してください
$dice = new \Dice\Dice();
// 変数を '$dice = ' で再代入することを忘れないでください!!!!!
$dice = $dice->addRule(SimplePdo::class, [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// コンテナハンドラを登録する
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// 通常どおりルートを定義する
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// または
Flight::route('/hello/@id', 'Greeting->hello');
// または
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
次のルートへの実行の受け渡し
非推奨
コールバック関数から true を返すことで、次にマッチするルートに実行を渡すことができます。
Flight::route('/user/@name', function (string $name) {
// 何らかの条件をチェックする
if ($name !== "Bob") {
// 次のルートに進む
return true;
}
});
Flight::route('/user/*', function () {
// これは呼び出されます
});
このような複雑なユースケースを処理するには、ミドルウェア を使用することをお勧めします。
ルートエイリアス
ルートにエイリアスを割り当てることで、後でアプリ内でそのエイリアスを動的に呼び出して URL を生成できます(例: HTML テンプレート内のリンク、リダイレクト URL の生成など)。
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// または
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// 後でコード内のどこかで
class UserController {
public function update() {
// ユーザーを保存するコード...
$id = $user['id']; // 例: 5
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // '/users/5' を返します
Flight::redirect($redirectUrl);
}
}
これは、URL が変更される場合に特に役立ちます。上記の例で、users が代わりに /admin/users/@id に移動されたとします。ルートにエイリアスを設定しておけば、エイリアスが上記の例のように /admin/users/5 を返すため、コード内の古い URL をすべて見つけて変更する必要はもうありません。
ルートエイリアスはグループ内でも機能します:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// または
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});
ルート情報の確認
マッチしたルート情報を確認したい場合、2 つの方法があります:
Flight::router()オブジェクトのexecutedRouteプロパティを使用する。- ルートメソッドの第 3 パラメータに
trueを渡すことで、ルートオブジェクトをコールバックに渡すようリクエストする。ルートオブジェクトは常にコールバック関数の最後のパラメータとして渡されます。
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// $route を使って何か処理をする
// マッチした HTTP メソッドの配列
$route->methods;
// 名前付きパラメータの配列
$route->params;
// マッチした正規表現
$route->regex;
// URL パターンで使用された '*' の内容を含む
$route->splat;
// URL パスを表示する...本当に必要な場合
$route->pattern;
// このルートに割り当てられたミドルウェアを表示する
$route->middleware;
// このルートに割り当てられたエイリアスを表示する
$route->alias;
});
注:
executedRouteプロパティは、ルートが実行された後にのみ設定されます。ルートが実行される前にアクセスしようとすると、NULLになります。また、executedRoute は ミドルウェア 内でも使用できます!
ルート定義に true を渡す
Flight::route('/', function(\flight\net\Route $route) {
// マッチした HTTP メソッドの配列
$route->methods;
// 名前付きパラメータの配列
$route->params;
// マッチした正規表現
$route->regex;
// URL パターンで使用された '*' の内容を含む
$route->splat;
// URL パスを表示する...本当に必要な場合
$route->pattern;
// このルートに割り当てられたミドルウェアを表示する
$route->middleware;
// このルートに割り当てられたエイリアスを表示する
$route->alias;
}, true);// <-- この true パラメータによってそれが実現されます
ルートのグループ化とミドルウェア
関連するルートをグループ化したい場合があります(例: /api/v1)。これを行うには、group メソッドを使用します:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// /api/v1/users にマッチする
});
Flight::route('/posts', function () {
// /api/v1/posts にマッチする
});
});
グループをネストすることもできます:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() は変数を取得するもので、ルートを設定するものではありません。以下のオブジェクトコンテキストを参照してください
Flight::route('GET /users', function () {
// GET /api/v1/users にマッチする
});
Flight::post('/posts', function () {
// POST /api/v1/posts にマッチする
});
Flight::put('/posts/1', function () {
// PUT /api/v1/posts にマッチする
});
});
Flight::group('/v2', function () {
// Flight::get() は変数を取得するもので、ルートを設定するものではありません。以下のオブジェクトコンテキストを参照してください
Flight::route('GET /users', function () {
// GET /api/v2/users にマッチする
});
});
});
オブジェクトコンテキストでのグループ化
Engine オブジェクトを使用してルートグループを使うこともできます:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// $router 変数を使用する
$router->get('/users', function () {
// GET /api/v1/users にマッチする
});
$router->post('/posts', function () {
// POST /api/v1/posts にマッチする
});
});
注: これはルートとグループを $router オブジェクトで定義する推奨方法です。
ミドルウェアでのグループ化
ルートのグループにミドルウェアを割り当てることもできます:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// /api/v1/users にマッチする
});
}, [ MyAuthMiddleware::class ]); // インスタンスを使用する場合は [ new MyAuthMiddleware() ] も可
詳細については、グループミドルウェア のページを参照してください。
リソースルーティング
resource メソッドを使用して、リソース用のルートセットを作成できます。これにより、RESTful 規約に従ったリソース用のルートセットが作成されます。
リソースを作成するには、次のようにします:
Flight::resource('/users', UsersController::class);
バックグラウンドでは、次のルートが作成されます:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]
コントローラーでは、次のメソッドを使用します:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
注: 新しく追加されたルートは、
php runway routesを実行することでrunwayで確認できます。
リソースルートのカスタマイズ
リソースルートを設定するためのオプションがいくつかあります。
エイリアスベース
aliasBase を設定できます。デフォルトでは、エイリアスは指定された URL の最後の部分です。たとえば、/users/ の場合、aliasBase は users になります。これらのルートが作成されるとき、エイリアスは users.index、users.create などになります。エイリアスを変更したい場合は、aliasBase を希望する値に設定します。
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only と Except
only および except オプションを使用して、作成するルートを指定することもできます。
// これらのメソッドのみをホワイトリスト化し、残りをブラックリスト化する
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// これらのメソッドのみをブラックリスト化し、残りをホワイトリスト化する
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
これらは基本的にホワイトリストおよびブラックリストのオプションであり、作成するルートを指定できます。
ミドルウェア
resource メソッドによって作成された各ルートで実行されるミドルウェアを指定することもできます。
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
ストリーミングレスポンス
stream() または streamWithHeaders() を使用して、クライアントにレスポンスをストリーミングできるようになりました。これは、大きなファイルの送信、長時間実行プロセス、または大きなレスポンスの生成に役立ちます。ストリーミングルートは、通常のルートとは少し異なる方法で処理されます。
注: ストリーミングレスポンスは、
flight.v2.output_bufferingがfalseに設定されている場合にのみ利用できます。
手動ヘッダーによるストリーミング
ルートの stream() メソッドを使用して、クライアントにレスポンスをストリーミングできます。これを行う場合、クライアントに何かを出力する前に、すべてのヘッダーを手動で設定する必要があります。これは、PHP の header() 関数または Flight::response()->setRealHeader() メソッドを使用して行います。
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// もちろん、パスをサニタイズするなどしてください。
$fileNameSafe = basename($filename);
// ルート実行後にここで追加のヘッダーを設定する場合、
// 何かが出力される前に定義する必要があります。
// それらはすべて header() 関数への生の呼び出し、または
// Flight::response()->setRealHeader() への呼び出しである必要があります。
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// または
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'File not found');
}
// 必要に応じてコンテンツの長さを手動で設定する
header('Content-Length: '.filesize($filePath));
// または
$response->setRealHeader('Content-Length: '.filesize($filePath));
// ファイルを読み取りながらクライアントにストリーミングする
readfile($filePath);
// ここが魔法の行です
})->stream();
ヘッダー付きストリーミング
streamWithHeaders() メソッドを使用して、ストリーミングを開始する前にヘッダーを設定することもできます。
Flight::route('/stream-users', function() {
// ここに追加のヘッダーを自由に追加できます
// header() または Flight::response()->setRealHeader() を使用する必要があります
// データの取得方法は何でも構いませんが、例として...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// これはデータをクライアントに送信するために必要です
ob_flush();
}
echo '}';
// ストリーミングを開始する前にヘッダーを設定する方法は次のとおりです。
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// オプションのステータスコード、デフォルトは 200
'status' => 200
]);
関連項目
- ミドルウェア - 認証、ログ記録などのためのルートでのミドルウェアの使用。
- 依存性注入 - ルート内でのオブジェクト作成と管理の簡素化。
- なぜフレームワークを使うのか? - Flight のようなフレームワークを使用する利点を理解する。
- 拡張 -
notFoundメソッドを含む、独自の機能で Flight を拡張する方法。 - php.net: preg_match - 正規表現マッチングのための PHP 関数。
トラブルシューティング
- ルートパラメータは名前ではなく順序でマッチングされます。コールバックのパラメータ順序がルート定義と一致していることを確認してください。
Flight::get()はルートを定義しません。ルーティングにはFlight::route('GET /...')を、グループ内では Router オブジェクトコンテキスト(例:$router->get(...))を使用してください。- executedRoute プロパティはルートが実行された後にのみ設定されます。実行前は NULL です。
- ストリーミングでは、従来の Flight 出力バッファリング機能を無効にする必要があります(
flight.v2.output_buffering = false)。 - 依存性注入の場合、コンテナベースのインスタンス化をサポートするルート定義は限られています。
404 Not Found または予期しないルート動作
404 Not Found エラーが表示されている場合(しかし、それが本当に存在し、タイプミスではないと確信している場合)、これは実際にはルートエンドポイントで値をエコーせずに返していることが問題である可能性があります。この理由は意図的なものですが、一部の開発者には気づかれないかもしれません。
Flight::route('/hello', function(){
// これは 404 Not Found エラーを引き起こす可能性があります
return 'Hello World';
});
// おそらく必要なのはこちら
Flight::route('/hello', function(){
echo 'Hello World';
});
この理由は、ルーターに組み込まれた特別なメカニズムによるもので、返された出力を「次のルートに進む」ための合図として処理します。この動作は、ルーティング セクションで文書化されています。
変更履歴
- v3: リソースルーティング、ルートエイリアス、ストリーミングサポート、ルートグループ、ミドルウェアサポートを追加。
- v1: 基本的な機能の大部分が利用可能。
Learn/learn
Flightについて学ぶ
Flightは、PHP向けの高速でシンプル、そして拡張可能なフレームワークです。非常に多用途で、あらゆる種類のWebアプリケーションの構築に使用できます。 シンプルさを念頭に置いて構築されており、人間にもAIコーディングアシスタントにも理解しやすく使いやすい方法で書かれています。
注記:
Flight::を静的変数として使う例と、$app->エンジンオブジェクトを使う例があります。どちらも互換性があります。コントローラー/ミドルウェアでの$appと$this->appは、Flightチームが推奨する方法です(そして公式スケルトン+AGENTS.mdが新規プロジェクトで標準としている方法です)。
コアコンポーネント
ルーティング
Webアプリケーションのルートを管理する方法を学びます。ルートのグループ化、ルートパラメータ、ミドルウェアも含まれます。
ミドルウェア
アプリケーションでリクエストとレスポンスをフィルタリングするためのミドルウェアの使用方法を学びます。
オートローディング
独自のクラスをオートロードする方法を学びます。フォルダの大文字小文字は名前空間と一致している必要があります。スケルトンではApp\と、app/Controller/のようなパスカルケースのフォルダを使用します。
リクエスト
アプリケーションでリクエストとレスポンスを処理する方法を学びます。
レスポンス
ユーザーにレスポンスを送信する方法を学びます。
HTMLテンプレート
組み込みのPHPビューだけでなく、Twig(スケルトンのデフォルト)、Latte、または他のエンジンを使ってHTMLをレンダリングする方法を学びます。
セキュリティ
一般的なセキュリティ脅威からアプリケーションを保護する方法を学びます。
設定
アプリケーション向けにフレームワークを設定する方法を学びます。
イベントマネージャー
イベントシステムを使用してアプリケーションにカスタムイベントを追加する方法を学びます。
Flightの拡張
独自のメソッドとクラスを追加してフレームワークを拡張する方法を学びます。
メソッドフックとフィルタリング
自分のメソッドやフレームワーク内部のメソッドにイベントフックを追加する方法を学びます。
依存性注入コンテナ (DIC)
依存性注入コンテナ(DIC)を使用してアプリケーションの依存関係を管理する方法を学びます。
ユーティリティクラス
コレクション
コレクションはデータを保持し、使いやすさのために配列またはオブジェクトとしてアクセスできるようにするために使用されます。
JSONラッパー
JSONのエンコードとデコードを一貫して行うためのシンプルな関数がいくつかあります。
SimplePdo
PDOは時として必要以上に頭痛の種になることがあります。SimplePdoは、insert()、update()、delete()、transaction()のような便利なメソッドを備えた最新のPDOヘルパークラスで、データベース操作をはるかに簡単にします。
PdoWrapper(非推奨)
元のPDOラッパーはv3.18.0で非推奨となりました。代わりにSimplePdoを使用してください。
アップロードファイルハンドラー
アップロードされたファイルを管理し、恒久的な場所に移動するのに役立つシンプルなクラスです。
重要なコンセプト
なぜフレームワークなのか?
フレームワークを使うべき理由についての短い記事です。フレームワークを使い始める前に、その利点を理解しておくことは良い考えです。
さらに、@lubianaによって素晴らしいチュートリアルが作成されています。Flightについて特に詳しく説明しているわけではありませんが、このガイドはフレームワークを取り巻く主要な概念と、それらがなぜ有益であるかを理解するのに役立ちます。チュートリアルはこちらにあります。
Flightと他のフレームワークの比較
Laravel、Slim、Fat-Free、Symfonyなどの他のフレームワークからFlightに移行する場合、このページは両者の違いを理解するのに役立ちます。
その他のトピック
ユニットテスト
このガイドに従って、Flightコードを堅牢にするためのユニットテストの方法を学びましょう。
AIと開発者体験
FlightはコーディングLLMと連携するように構築されています:AGENTS.md、Runwayのai:*コマンド、そしてエージェントがパターンを維持できるようにする明確なスケルトンレイアウトです。
v2 -> v3への移行
後方互換性はほとんど維持されていますが、v2からv3に移行する際に注意すべき変更点がいくつかあります。
Learn/unit_testing
ユニットテスト
概要
Flightでのユニットテストは、アプリケーションが期待どおりに動作することを確認し、バグを早期に発見し、コードベースを保守しやすくするのに役立ちます。Flightは、最も人気のあるPHPテストフレームワークであるPHPUnitとスムーズに連携するように設計されています。
理解
ユニットテストは、アプリケーションの小さな部分(コントローラやサービスなど)の動作を単体でチェックします。Flightでは、ルート、コントローラ、ロジックがさまざまな入力にどのように応答するかを、グローバル状態や実際の外部サービスに依存せずにテストすることを意味します。
主要な原則:
- 実装ではなく動作をテストする: コードが「どのように」行うかではなく、「何を」行うかに焦点を当てます。
- グローバル状態を避ける:
Flight::set()やFlight::get()の代わりに依存性注入を使用します。 - 外部サービスをモックする: データベースやメーラーなどの外部サービスはテストダブルに置き換えます。
- テストを高速かつ焦点を絞ったものにする: ユニットテストは実際のデータベースやAPIにアクセスすべきではありません。
基本的な使い方
PHPUnitのセットアップ
- ComposerでPHPUnitをインストールします。
composer require --dev phpunit/phpunit - プロジェクトルートに
testsディレクトリを作成します。 composer.jsonにテストスクリプトを追加します。"scripts": { "test": "phpunit --configuration phpunit.xml" }phpunit.xmlファイルを作成します。<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
これで composer test を実行してテストを実行できます。
シンプルなルートハンドラのテスト
メールアドレスを検証するルートがあるとします。
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
return $this->app->json(['status' => 'success', 'message' => 'Valid email']);
}
}
このコントローラのシンプルなテスト:
use PHPUnit\Framework\TestCase;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$app->request()->data->email = 'invalid-email';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
ヒント:
- POSTデータは
$app->request()->dataを使用してシミュレートします。 - テストでは
Flight::の静的メソッドを使わず、$appインスタンスを使用してください。
テスト可能なコントローラのための依存性注入の使用
依存関係(データベースやメーラーなど)をコントローラに注入することで、テストでモックしやすくなります。
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct($app, $db, $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
モックを使ったテスト:
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$mockDb = $this->createMock(flight\database\SimplePdo::class);
$mockDb->method('runQuery')->willReturn(true);
$mockMailer = new class {
public $sentEmail = null;
public function sendWelcome($email) { $this->sentEmail = $email; return true; }
};
$app = new flight\Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
}
高度な使い方
- モッキング: PHPUnitの組み込みモックまたは匿名クラスを使用して依存関係を置き換えます。
- コントローラを直接テストする: 新しい
Engineでコントローラをインスタンス化し、依存関係をモックします。 - 過剰なモッキングを避ける: 可能な限り実際のロジックを実行させ、外部サービスのみをモックします。
関連情報
- ユニットテストガイド - ユニットテストのベストプラクティスに関する包括的なガイドです。
- 依存性注入コンテナ - DICを使用して依存関係を管理し、テスト容易性を向上させる方法。
- 拡張 - 独自のヘルパーを追加したり、コアクラスをオーバーライドする方法。
- SimplePdo - データベース操作を簡素化し、テストでモックしやすくします。
- リクエスト - FlightでHTTPリクエストを処理する方法。
- レスポンス - ユーザーにレスポンスを送信する方法。
- ユニットテストとSOLID原則 - SOLID原則がユニットテストを改善する方法を学びます。
トラブルシューティング
- コードとテストでグローバル状態(
Flight::set()、$_SESSIONなど)を使用しないようにしてください。 - テストが遅い場合は、統合テストを書いている可能性があります。外部サービスをモックしてユニットテストを高速に保ちましょう。
- テストのセットアップが複雑な場合は、依存性注入を使用するようにコードをリファクタリングすることを検討してください。
変更履歴
- v3.15.0 - 依存性注入とモッキングの例を追加しました。
Learn/flight_vs_symfony
フライト対シンフォニー
シンフォニーとは?
Symfony は、再利用可能な PHP コンポーネントと、Web プロジェクト用の PHP フレームワークです。
最高の PHP アプリケーションが構築される標準基盤。独自のアプリケーションに使用できる 50 個のスタンドアロンコンポーネントのいずれかを選択してください。
PHP Web アプリケーションの作成とメンテナンスをスピードアップします。反復的なコーディングタスクを終了し、コードを制御する力を享受します。
フライトとの比較での長所
- Symfony には一般的な問題を解決するために使用できる 膨大なエコシステム の開発者とモジュールがあります。
- Symfony にはデータベースとのやり取りに使用できる 充実した ORM (Doctrine) があります。
- Symfony には、フレームワークを学ぶために使用できる豊富なドキュメントやチュートリアルがあります。
- Symfony には、フレームワークを学ぶために使用できるポッドキャスト、会議、ミーティング、ビデオ、その他のリソースがあります。
- Symfony は、完全な機能を備えたエンタープライズ Web アプリケーションを構築しようとしている経験豊富な開発者向けです。
フライトとの比較での短所
- Symfony には Flight よりもはるかに多くのことが暗に行われています。これはパフォーマンスの面で 劇的な コストがかかります。詳細は TechEmpower benchmarks を参照してください。
- Flight は、軽量で高速かつ使いやすい Web アプリケーションを構築したい開発者向けです。
- Flight は単純さと使いやすさを重視しています。
- Flight の中心的な機能の1つは、後方互換性を維持するように最善を尽くすことです。
- Flight には依存関係がなく、一方 Symfony には多くの依存関係 があります。
- Flight は初めてフレームワークの世界に進む開発者向けです。
- Flight はエンタープライズレベルのアプリケーションもできますが、Symfony ほどの例やチュートリアルがないため、開発者側が整理された構造を維持するためにはより多くの努力が必要です。
- Flight は開発者にアプリケーション上の制御をより多く与え、一方 Symfony は裏でいくらかの魔法を行う可能性があります。
Learn/flight_vs_another_framework
他のフレームワークとのFlightの比較
もし、Laravel、Slim、Fat-Free、あるいはSymfonyのような他のフレームワークからFlightに移行している場合、このページは両者の違いを理解するのに役立ちます。
Laravel
Laravelはベルと笛がすべて揃った充実したフレームワークで、驚くべき開発者中心のエコシステムを持っていますが、パフォーマンスと複雑さと引き換えになります。
Slim
SlimはFlightに似たマイクロフレームワークです。軽量で使いやすく設計されていますが、Flightよりも少し複雑になることがあります。
Fat-Free
Fat-Freeはより小さなパッケージで提供されるフルスタックフレームワークです。ツールはすべてそろっていますが、いくつかのプロジェクトをより複雑にするデータアーキテクチャを持っています。
Symfony
Symfonyはモジュール式のエンタープライズレベルのフレームワークであり、柔軟性と拡張性を備えています。より小さなプロジェクトや新人開発者にとって、Symfonyは少し抵抗があるかもしれません。
Learn/pdo_wrapper
PdoWrapper PDO ヘルパークラス
警告
非推奨:
PdoWrapperは Flight v3.18.0 以降非推奨です。将来的なバージョンで削除されることはありませんが、後方互換性のためメンテナンスされます。代わりに SimplePdo を使用してください。これは同じ機能を提供し、共通のデータベース操作のための追加のヘルパーメソッドも提供します。
概要
Flight の PdoWrapper クラスは、PDO を使用してデータベースを操作するための親しみやすいヘルパーです。共通のデータベースタスクを簡素化し、結果を取得するための便利なメソッドを追加し、結果を Collections として返して簡単にアクセスできるようにします。また、クエリログとアプリケーションのパフォーマンス監視 (APM) を高度なユースケースでサポートします。
理解
PHP でデータベースを操作する場合、特に PDO を直接使用すると冗長になることがあります。PdoWrapper は PDO を拡張し、クエリ、取得、結果の処理をはるかに簡単にします。準備されたステートメントやフェッチモードを扱う代わりに、共通のタスクのためのシンプルなメソッドが得られ、すべての行が Collection として返されるため、配列またはオブジェクト表記を使用できます。
PdoWrapper を Flight で共有サービスとして登録し、Flight::db() を介してアプリケーションのどこでも使用できます。
基本的な使用方法
PDO ヘルパーの登録
まず、PdoWrapper クラスを Flight に登録します:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
これで、Flight::db() をどこでも使用してデータベース接続を取得できます。
クエリの実行
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
INSERT や UPDATE、または結果を手動で取得したい場合に使用します:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row は配列です
}
書き込みにも使用できます:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
データベースから単一の値を入手します:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): Collection
単一の行を Collection (配列/オブジェクトアクセス) として入手します:
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// または
echo $user->name;
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
すべての行を Collection の配列として入手します:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// または
echo $user->name;
}
IN() プレースホルダーの使用
IN() 句で単一の ? を使用し、配列またはカンマ区切りの文字列を渡せます:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// または
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);
高度な使用方法
クエリログと APM
クエリのパフォーマンスを追跡したい場合、登録時に APM 追跡を有効にします:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* オプション */], true // 最後のパラメータで APM を有効にします
]);
クエリを実行した後、手動でログを記録できますが、APM が有効な場合は自動的にログを記録します:
Flight::db()->logQueries();
これはイベント (flight.db.queries) をトリガーし、接続とクエリメトリクスを含み、Flight のイベントシステムを使用してリッスンできます。
完全な例
Flight::route('/users', function () {
// すべてのユーザーを取得
$users = Flight::db()->fetchAll('SELECT * FROM users');
// すべてのユーザーをストリーミング
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// 単一のユーザーを取得
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// 単一の値を入手
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// 特別な IN() 構文
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', ['1,2,3,4,5']);
// 新しいユーザーを挿入
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
$insert_id = Flight::db()->lastInsertId();
// ユーザーを更新
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);
// ユーザーを削除
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);
// 影響を受けた行数を入手
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
$affected_rows = $statement->rowCount();
});
関連項目
- Collections - Collection クラスを使用してデータを簡単にアクセスする方法を学びます。
トラブルシューティング
- データベース接続に関するエラーが発生した場合、DSN、ユーザー名、パスワード、オプションを確認してください。
- すべての行は Collection として返されます。プレーンな配列が必要な場合は、
$collection->getData()を使用してください。 IN (?)クエリの場合、配列またはカンマ区切りの文字列を渡すことを確認してください。
変更履歴
- v3.2.0 - 基本的なクエリとフェッチメソッドを含む PdoWrapper の初期リリース。
Learn/dependency_injection_container
依存性注入コンテナ
概要
依存性注入コンテナ(DIC)は、アプリケーションの依存関係を管理できる強力な拡張機能です。また、Flightが AIコーディングツール やユニットテストとうまく連携できる最大の理由の1つでもあります。コントローラーは、グローバルにアクセスする代わりに、コンストラクターで必要なものを受け取ります。
理解
依存性注入(DI)は、現代のPHPフレームワークにおける重要な概念であり、オブジェクトのインスタンス化と構成を管理するために使用されます。DICライブラリの例としては、flightphp/container、Dice、Pimple、PHP-DI、league/container などがあります。
DICは、クラスを一元管理された場所で作成・管理するための凝った方法です。同じオブジェクトを複数のクラス(コントローラー、ミドルウェア、コマンドなど)に渡す必要がある場合に便利です。
公式の flightphp/skeleton は、app/config/services.php で Dice を配線し、共有の flight\Engine インスタンスを置き換え、[App\Controller\HomeController::class, 'index'] のようなルートターゲットを解決します。新しいプロジェクトでは、人間とエージェントが同じ場所を編集できるように、このパターンを採用してください。
基本的な使い方
従来のやり方は次のようになるでしょう。
require 'vendor/autoload.php';
// データベースからユーザーを管理するクラス
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// routes.php ファイル内
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// その他のUserControllerルート...
Flight::start();
上記のコードから、新しい PDO オブジェクトを作成して UserController クラスに渡していることがわかります。これは小規模なアプリケーションには問題ありませんが、アプリケーションが大きくなるにつれて、同じ PDO オブジェクトを複数の場所で作成または受け渡ししていることに気づくでしょう。ここでDICが役立ちます。
次に、DIC(Diceを使用)を使った同じ例を示します。
require 'vendor/autoload.php';
// 上記と同じクラスです。何も変更されていません
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// 新しいコンテナを作成
$container = new \Dice\Dice;
// PDOオブジェクトの作成方法をコンテナに指示するルールを追加
// 下記のように必ず自分自身に再代入することを忘れないでください!
$container = $container->addRule('PDO', [
// sharedは、毎回同じオブジェクトが返されることを意味します
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// これによりコンテナハンドラーが登録され、Flightがそれを使用することを認識します。
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// これでコンテナを使ってUserControllerを作成できます
Flight::route('/user/@id', [ UserController::class, 'view' ]);
Flight::start();
きっと、この例には余分なコードがたくさん追加されたと思っているかもしれません。魔法が発揮されるのは、PDO オブジェクトを必要とする別のコントローラーがあるときです。
// すべてのコントローラーのコンストラクターがPDOオブジェクトを必要とする場合
// 以下の各ルートには自動的にPDOが注入されます!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);
DICを利用する追加の利点は、ユニットテストがはるかに簡単になることです。モックオブジェクトを作成してクラスに渡すことができます。これは、アプリケーションのテストを書く際に大きなメリットです。また、AIアシスタントがコントローラーを生成する場合、コンストラクターインジェクションにより、従うべき明確で一貫性のあるパターンが提供されます(ユニットテストガイド)。
集中管理型DICハンドラーの作成
アプリを拡張することで、サービスファイルに集中管理型のDICハンドラーを作成できます。次に例を示します。
// services.php
// 新しいコンテナを作成
$container = new \Dice\Dice;
// 下記のように必ず自分自身に再代入することを忘れないでください!
$container = $container->addRule('PDO', [
// sharedは、毎回同じオブジェクトが返されることを意味します
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// これで、任意のオブジェクトを作成するためのマップ可能なメソッドを作成できます。
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// これによりコンテナハンドラーが登録され、Flightがコントローラー/ミドルウェアにそれを使用することを認識します。
Flight::registerContainerHandler(function($class, $params) {
return Flight::make($class, $params);
});
// コンストラクターでPDOオブジェクトを受け取る次のサンプルクラスがあるとします
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// メールを送信するコード
}
}
// そして最後に、依存性注入を使用してオブジェクトを作成できます
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
flightphp/container
Flightには、依存性注入を処理するために使用できるシンプルなPSR-11準拠コンテナを提供するプラグインがあります。その使用例を簡単に示します。
// 例: index.php
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// これは正しく出力されます!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();
flightphp/container の高度な使い方
依存関係を再帰的に解決することもできます。次に例を示します。
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// 実装 ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/
DICE
独自のDICハンドラーを作成することもできます。これは、PSR-11ではない独自のコンテナ(Diceなど)を使用したい場合に便利です。これを行う方法については、基本的な使い方 のセクションを参照してください。
さらに、Flightを使用する際に作業を容易にする便利なデフォルトがいくつかあります。
Engineインスタンス($app インジェクションに必要)
コントローラーやミドルウェアで flight\Engine を型宣言する場合、Dice は新しい Engine を構築してはなりません。ブートストラップから同じインスタンスを置き換えてください。これが公式スケルトンのやり方であり、AI生成コントローラーで AGENTS.md が期待するパターンです。
// ブートストラップ / services.php のどこか
use flight\Engine;
use flight\database\SimplePdo;
$app = Flight::app(); // または $engine = Flight::app();
$container = new \Dice\Dice;
$container = $container->addRule('*', [
'substitutions' => [
// 重要: ブートストラップ済みのEngineを再利用してください。Diceに `new Engine()` をさせないでください
Engine::class => $app,
// 新しいコードではSimplePdoを推奨します
// SimplePdo::class => $db,
// Config::class => $config,
// \Twig\Environment::class => $twig,
]
]);
$app->registerContainerHandler(function ($class, $params) use ($container) {
return $container->create($class, $params);
});
// ルート以外のコード用のオプションヘルパー
$app->map('make', function ($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// app/Controller/MyController.php(スケルトンレイアウト — フォルダー名の大文字小文字は名前空間と一致します)
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// アプリ層では Flight:: ファサードを使用しません — テストが容易で、AIツールにとっても明確です
$this->app->render('welcome', ['message' => 'Hello']);
}
}
// app/config/routes.php
use App\Controller\MyController;
$router->get('/', [MyController::class, 'index']);
Engine の置き換えをスキップすると、Dice が2つ目の Engine を構築し、コントローラーがブートストラップからのルート、設定、マップされた Twig render を共有しなくなる可能性があります。
他の共有サービスを追加する(SimplePdo、Config、Twig)
use flight\database\SimplePdo;
use flight\Engine;
// services.php で $db、$config、$twig を作成した後:
$substitutions = [
Engine::class => $app,
SimplePdo::class => $db,
// App\Utils\Config::class => $config,
// \Twig\Environment::class => $twig,
];
$container = $container->addRule('*', [
'substitutions' => $substitutions,
]);
これで、コントローラーはコンストラクターで SimplePdo $db(または設定型)を受け取り、Flight::db() を呼び出す必要がなくなります。これは、ユニットテスト のガイダンスとスケルトンのハウススタイルに一致します。
他のクラスを追加する
コンテナに追加したい他のクラスがある場合、Dice ではコンテナによって自動的に解決されるため簡単です。次に例を示します。
$container = new \Dice\Dice;
// クラスに依存関係を注入する必要がない場合
// 何も定義する必要はありません!
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
class MyCustomClass {
public function parseThing() {
return 'thing';
}
}
class UserController {
protected MyCustomClass $MyCustomClass;
public function __construct(MyCustomClass $MyCustomClass) {
$this->MyCustomClass = $MyCustomClass;
}
public function index() {
echo $this->MyCustomClass->parseThing();
}
}
Flight::route('/user', 'UserController->index');
PSR-11
Flightは、PSR-11準拠の任意のコンテナも使用できます。つまり、PSR-11インターフェースを実装した任意のコンテナを使用できます。次に、LeagueのPSR-11コンテナを使用した例を示します。
require 'vendor/autoload.php';
use flight\database\SimplePdo;
// 上記と同じUserControllerの考え方ですが、素のPDOの代わりにSimplePdoを型宣言します
$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(SimplePdo::class);
$container->add(SimplePdo::class)
->addArgument('mysql:host=localhost;dbname=test')
->addArgument('user')
->addArgument('pass');
Flight::registerContainerHandler($container);
Flight::route('/user', [ 'UserController', 'view' ]);
Flight::start();
これは以前のDiceの例よりも少し冗長かもしれませんが、同じ利点で目的を達成できます!
関連情報
- インストール - スケルトレイアウトと
services.phpの場所。 - オートローディング -
App\名前空間とフォルダーの大文字小文字。 - Flightの拡張 - フレームワークを拡張して独自のクラスに依存性注入を追加する方法を学ぶ。
- 設定 - アプリケーション用にFlightを設定する方法を学ぶ。
- ルーティング - アプリケーションのルートを定義する方法と、依存性注入がコントローラーとどのように連携するかを学ぶ。
- ミドルウェア - アプリケーション用のミドルウェアを作成する方法と、依存性注入がミドルウェアとどのように連携するかを学ぶ。
- ユニットテスト - コンストラクターインジェクションが
Flight::グローバルよりも優れている理由。 - AIと開発者体験 - 人間とエージェントのための単一のDIパターン。
- SimplePdo - インジェクションに推奨されるデータベースヘルパー。
トラブルシューティング
- コンテナで問題が発生している場合は、正しいクラス名をコンテナに渡していることを確認してください。
Engineを型宣言しているのに「空の」アプリが返されるコントローラーがある場合は、Engineの置き換えを追加してください(上記参照)。Dice は2つ目の Engine をnewしてはいけません。App\Controller\…でクラスが見つからない場合:app/Controller/配下のフォルダー名の大文字小文字を確認してください — オートローディング を参照。- ハンドラーは
registerContainerHandlerから作成したオブジェクトを返す必要があります(returnなしでFlight::make()を呼び出さないでください)。
変更履歴
- ドキュメント – AIフレンドリーなプロジェクト向けに、スケルトンのDice + Engine置き換え、SimplePdo、
App\Controllerレイアウトを文書化。 - v3.7.0 - FlightにDICハンドラーを登録する機能を追加。
Learn/middleware
ミドルウェア
概要
Flight はルートおよびグループルートのミドルウェアをサポートします。ミドルウェアは、アプリケーションの一部で、ルートコールバックの前(または後)にコードが実行される場所です。これは、コードに API 認証チェックを追加する優れた方法です。また、ユーザーがルートにアクセスする権限があるかを検証することもできます。
理解
ミドルウェアはアプリを大幅に簡素化できます。複雑な抽象クラス継承やメソッドオーバーライドの代わりに、ミドルウェアを使用することで、カスタムのアプリロジックをルートに割り当ててルートを制御できます。ミドルウェアはサンドイッチのようなものだと考えられます。外側にパンがあり、その中にレタス、トマト、肉、チーズなどの層があります。そして、各リクエストがサンドイッチを一口かじるようなもので、外側の層から食べてコアに向かっていくイメージです。
ミドルウェアの動作の視覚的な例を以下に示します。その後、この機能の実践的な例を示します。
ユーザー リクエストが URL /api に到達 ---->
Middleware->before() が実行 ----->
/api にアタッチされたコールバック/メソッドが実行され、レスポンスが生成 ------>
Middleware->after() が実行 ----->
ユーザーがサーバーからレスポンスを受信
そして、実践的な例はこちらです:
ユーザーが URL /dashboard に移動
LoggedInMiddleware->before() が実行
before() が有効なログインモッションをチェック
有効な場合、何もしないで実行を続行
無効な場合、ユーザーを /login にリダイレクト
/api にアタッチされたコールバック/メソッドが実行され、レスポンスが生成
LoggedInMiddleware->after() に何も定義されていないため、実行を続行
ユーザーがサーバーからダッシュボードの HTML を受信
実行順序
ミドルウェア関数は、ルートに追加された順序で実行されます。この実行は、Slim Framework がこれを扱う方法 に似ています。
before() メソッドは追加された順序で実行され、after() メソッドは逆順で実行されます。
例: Middleware1->before()、Middleware2->before()、Middleware2->after()、Middleware1->after()。
基本的な使用方法
ミドルウェアは、匿名関数やクラス(推奨)を含む任意のコールバックメソッドとして使用できます。
匿名関数
簡単な例を以下に示します:
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// これは "Middleware first! Here I am!" を出力します
注意: 匿名関数を使用する場合、解釈されるのは
before()メソッドのみです。匿名クラスでafter()動作を定義できません。
クラスの使用
ミドルウェアはクラスとして登録できます(推奨されます)。「after」機能が必要な場合は、クラスを使用する必要があります。
class MyMiddleware {
public function before($params) {
echo 'Middleware first!';
}
public function after($params) {
echo 'Middleware last!';
}
}
$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware);
// また ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]) も可能です
Flight::start();
// これは "Middleware first! Here I am! Middleware last!" を表示します
ミドルウェアのクラス名のみを定義し、クラスをインスタンス化することもできます。
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class);
注意: ミドルウェアの名前のみを渡す場合、依存性注入コンテナ によって自動的に実行され、ミドルウェアは必要なパラメータで実行されます。依存性注入コンテナが登録されていない場合、デフォルトで
__construct(Engine $app)にflight\Engineインスタンスが渡されます。
パラメータ付きルートの使用
ルートからパラメータが必要な場合、それらはミドルウェア関数に単一の配列として渡されます。(function($params) { ... } または public function before($params) { ... })。その理由は、パラメータをグループ化し、一部のグループでパラメータの順序が異なり、誤ったパラメータを参照してミドルウェア関数を壊す可能性があるためです。この方法では、位置ではなく名前でアクセスできます。
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
// jobId は渡される場合とされない場合があります
$jobId = $params['jobId'] ?? 0;
// job ID がない場合、何も検索する必要がないかもしれません
if($jobId === 0) {
return;
}
// データベースで何らかの検索を実行
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
// 下記のグループも親のミドルウェアを受け取ります
// ただし、パラメータはミドルウェアに単一の配列として渡されます
$router->group('/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// さらにルート...
});
}, [ RouteSecurityMiddleware::class ]);
ミドルウェア付きのルートグループ化
ルートグループを追加し、そのグループ内のすべてのルートに同じミドルウェアを適用できます。これは、ヘッダーの API キーをチェックする Auth ミドルウェアなどでルートをグループ化する必要がある場合に便利です。
// グループメソッドの最後に追加
Flight::group('/api', function() {
// この「空」のルートは実際には /api に一致します
Flight::route('', function() { echo 'api'; }, false, 'api');
// これは /api/users に一致します
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// これは /api/users/1234 に一致します
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);
すべてのルートにグローバルなミドルウェアを適用したい場合、「空」のグループを追加できます:
// グループメソッドの最後に追加
Flight::group('', function() {
// これは依然として /users です
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// これは依然として /users/1234 です
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // または [ new ApiAuthMiddleware() ]、同じです
一般的な使用例
API キー検証
/api ルートを保護するために API キーが正しいかを検証したい場合、ミドルウェアで簡単に処理できます。
use flight\Engine;
class ApiMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$authorizationHeader = $this->app->request()->getHeader('Authorization');
$apiKey = str_replace('Bearer ', '', $authorizationHeader);
// データベースで API キーを検索
$apiKeyHash = hash('sha256', $apiKey);
$hasValidApiKey = !!$this->db()->fetchField("SELECT 1 FROM api_keys WHERE hash = ? AND valid_date >= NOW()", [ $apiKeyHash ]);
if($hasValidApiKey !== true) {
$this->app->jsonHalt(['error' => 'Invalid API Key']);
}
}
}
// routes.php
$router->group('/api', function(Router $router) {
$router->get('/users', [ ApiController::class, 'getUsers' ]);
$router->get('/companies', [ ApiController::class, 'getCompanies' ]);
// さらにルート...
}, [ ApiMiddleware::class ]);
これで、設定した API キー検証ミドルウェアによってすべての API ルートが保護されます!ルータグループにさらにルートを追加すると、即座に同じ保護が適用されます!
ログインバリデーション
ログインユーザーのみが利用可能なルートを保護したいですか?ミドルウェアで簡単に実現できます!
use flight\Engine;
class LoggedInMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$session = $this->app->session();
if($session->get('logged_in') !== true) {
$this->app->redirect('/login');
exit;
}
}
}
// routes.php
$router->group('/admin', function(Router $router) {
$router->get('/dashboard', [ DashboardController::class, 'index' ]);
$router->get('/clients', [ ClientController::class, 'index' ]);
// さらにルート...
}, [ LoggedInMiddleware::class ]);
ルートパラメータ検証
ユーザーが URL の値を変更してアクセスすべきでないデータにアクセスするのを防ぎたいですか?ミドルウェアで解決できます!
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
$jobId = $params['jobId'];
// データベースで何らかの検索を実行
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// さらにルート...
}, [ RouteSecurityMiddleware::class ]);
ミドルウェア実行の処理
認証ミドルウェアがあり、認証されていない場合にユーザーをログインページにリダイレクトしたいとします。いくつかのオプションがあります:
- ミドルウェア関数から false を返し、Flight が自動的に 403 Forbidden エラーを返しますが、カスタマイズはできません。
Flight::redirect()を使用してユーザーをログインページにリダイレクトできます。- ミドルウェア内でカスタムエラーを作成し、ルートの実行を停止できます。
シンプルでストレート
簡単な return false; の例を以下に示します:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
return false;
}
// true の場合、すべて続行されます
}
}
リダイレクトの例
ユーザーをログインページにリダイレクトする例を以下に示します:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
Flight::redirect('/login');
exit;
}
}
}
カスタムエラーの例
API を構築していて JSON エラーをスローする必要があるとします。以下のようにできます:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->getHeader('Authorization');
if(empty($authorization)) {
Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
// または
Flight::json(['error' => 'You must be logged in to access this page.'], 403);
exit;
// または
Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']));
}
}
}
関連項目
- ルーティング - ルートをコントローラーにマッピングし、ビューをレンダリングする方法。
- リクエスト - 受信リクエストの処理方法の理解。
- レスポンス - HTTP レスポンスのカスタマイズ方法。
- 依存性注入 - ルートでのオブジェクト作成と管理の簡素化。
- なぜフレームワークか? - Flight のようなフレームワークを使用する利点の理解。
- ミドルウェア実行戦略の例
トラブルシューティング
- ミドルウェアにリダイレクトがあるのにアプリがリダイレクトされない場合、ミドルウェアに
exit;文を追加してください。
変更履歴
- v3.1: ミドルウェアのサポートを追加。
Learn/filtering
フィルタリング
概要
Flight は、マップされたメソッド が呼び出される前と後にフィルタリングを許可します。
理解
覚える必要のある事前定義されたフックはありません。デフォルトのフレームワーク メソッドのいずれか、またはマップしたカスタム メソッドのいずれかをフィルタリングできます。
フィルター関数は以下のようになります:
/**
* @param array $params The parameters passed to the method being filtered.
* @param string $output (v2 output buffering only) The output of the method being filtered.
* @return bool Return true/void or don't return to continue the chain, false to break the chain.
*/
function (array &$params, string &$output): bool {
// Filter code
}
渡された変数を使用して、入力パラメータと/または出力を操作できます。
メソッドの前にフィルターを実行するには:
Flight::before('start', function (array &$params, string &$output): bool {
// Do something
});
メソッドの後にフィルターを実行するには:
Flight::after('start', function (array &$params, string &$output): bool {
// Do something
});
任意のメソッドに必要な数のフィルターを追加できます。それらは宣言された順序で呼び出されます。
フィルタリング プロセスの例を以下に示します:
// Map a custom method
Flight::map('hello', function (string $name) {
return "Hello, $name!";
});
// Add a before filter
Flight::before('hello', function (array &$params, string &$output): bool {
// Manipulate the parameter
$params[0] = 'Fred';
return true;
});
// Add an after filter
Flight::after('hello', function (array &$params, string &$output): bool {
// Manipulate the output
$output .= " Have a nice day!";
return true;
});
// Invoke the custom method
echo Flight::hello('Bob');
これは以下を表示するはずです:
Hello Fred! Have a nice day!
複数のフィルターを定義している場合、フィルター関数のいずれかで false を返すことでチェーンを中断できます:
Flight::before('start', function (array &$params, string &$output): bool {
echo 'one';
return true;
});
Flight::before('start', function (array &$params, string &$output): bool {
echo 'two';
// This will end the chain
return false;
});
// This will not get called
Flight::before('start', function (array &$params, string &$output): bool {
echo 'three';
return true;
});
Note: Core methods such as
mapandregistercannot be filtered because they are called directly and not invoked dynamically. See Extending Flight for more information.
関連項目
トラブルシューティング
- チェーンを停止したい場合、フィルター関数から
falseを返すようにしてください。何も返さない場合、チェーンは続行されます。
変更履歴
- v2.0 - 初回リリース。
Learn/requests
リクエスト
概要
Flight は HTTP リクエストを単一のオブジェクトにカプセル化し、以下の方法でアクセスできます:
$request = Flight::request();
理解
HTTP リクエストは、HTTP ライフサイクルの理解に不可欠なコア要素の一つです。ユーザーがウェブブラウザや HTTP クライアントでアクションを実行すると、ヘッダー、本文、URL などをあなたのプロジェクトに送信します。これらのヘッダー(ブラウザの言語、扱える圧縮の種類、ユーザーエージェントなど)をキャプチャし、Flight アプリケーションに送信された本文と URL をキャプチャできます。これらのリクエストは、アプリが次に何をするかを理解するために不可欠です。
基本的な使用方法
PHP には $_GET、$_POST、$_REQUEST、$_SERVER、$_FILES、$_COOKIE などのスーパーグローバルがあります。Flight はこれらを便利な Collections に抽象化します。query、data、cookies、files プロパティを配列またはオブジェクトとしてアクセスできます。
注意: プロジェクトでこれらのスーパーグローバルを使用することは強く推奨されません。
request()オブジェクト経由で参照してください。
注意:
$_ENVの抽象化は利用できません。
$_GET
$_GET 配列は query プロパティ経由でアクセスできます:
// GET /search?keyword=something
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
// または
$keyword = Flight::request()->query->keyword;
echo "You are searching for: $keyword";
// $keyword でデータベースをクエリしたり、その他のことを行います
});
$_POST
$_POST 配列は data プロパティ経由でアクセスできます:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
// または
$name = Flight::request()->data->name;
$email = Flight::request()->data->email;
echo "You submitted: $name, $email";
// $name と $email でデータベースに保存したり、その他のことを行います
});
$_COOKIE
$_COOKIE 配列は cookies プロパティ経由でアクセスできます:
Flight::route('GET /login', function(){
$savedLogin = Flight::request()->cookies['myLoginCookie'];
// または
$savedLogin = Flight::request()->cookies->myLoginCookie;
// 本当に保存されているかチェックし、保存されている場合は自動的にログインします
if($savedLogin) {
Flight::redirect('/dashboard');
return;
}
});
新しいクッキー値の設定に関するヘルプについては、overclokk/cookie を参照してください。
$_SERVER
$_SERVER 配列は getVar() メソッド経由でアクセスするためのショートカットがあります:
$host = Flight::request()->getVar('HTTP_HOST');
$_FILES
アップロードされたファイルは files プロパティ経由でアクセスできます:
// $_FILES プロパティへの生アクセス。推奨アプローチは以下を参照
$uploadedFile = Flight::request()->files['myFile'];
// または
$uploadedFile = Flight::request()->files->myFile;
詳細については Uploaded File Handler を参照してください。
ファイルアップロードの処理
v3.12.0
フレームワークを使用してヘルパーメソッドでファイルアップロードを処理できます。基本的に、リクエストからファイルデータを引き出し、新しい場所に移動するだけです。
Flight::route('POST /upload', function(){
// 入力フィールドが <input type="file" name="myFile"> のような場合
$uploadedFileData = Flight::request()->getUploadedFiles();
$uploadedFile = $uploadedFileData['myFile'];
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});
複数のファイルがアップロードされた場合、それらをループで処理できます:
Flight::route('POST /upload', function(){
// 入力フィールドが <input type="file" name="myFiles[]"> のような場合
$uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
foreach ($uploadedFiles as $uploadedFile) {
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
}
});
セキュリティ注意: ユーザー入力の検証とサニタイズを常に実行してください。特にファイルアップロードの場合、許可する拡張子の種類を検証し、ファイルの「マジックバイト」を検証して、ユーザーが主張するファイルの種類であることを確認してください。このヘルプには 記事 と ライブラリ が利用可能です。
リクエスト本文
POST/PUT リクエストなどの生の HTTP リクエスト本文を取得するには、以下のようにします:
Flight::route('POST /users/xml', function(){
$xmlBody = Flight::request()->getBody();
// 送信された XML で何かを行います。
});
JSON 本文
コンテンツタイプ application/json のリクエストを受け取り、例として {"id": 123} のデータの場合、data プロパティから利用可能です:
$id = Flight::request()->data->id;
リクエストヘッダー
getHeader() または getHeaders() メソッドを使用してリクエストヘッダーにアクセスできます:
// Authorization ヘッダーが必要な場合
$host = Flight::request()->getHeader('Authorization');
// または
$host = Flight::request()->header('Authorization');
// すべてのヘッダーを取得する場合
$headers = Flight::request()->getHeaders();
// または
$headers = Flight::request()->headers();
リクエストメソッド
method プロパティまたは getMethod() メソッドを使用してリクエストメソッドにアクセスできます:
$method = Flight::request()->method; // 実際には getMethod() で設定されます
$method = Flight::request()->getMethod();
注意: getMethod() メソッドは最初に $_SERVER['REQUEST_METHOD'] からメソッドを引き出し、存在する場合に $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] または $_REQUEST['_method'] で上書きできます。
リクエストオブジェクトのプロパティ
リクエストオブジェクトは以下のプロパティを提供します:
- body - 生の HTTP リクエスト本文
- url - リクエストされている URL
- base - URL の親サブディレクトリ
- method - リクエストメソッド (GET, POST, PUT, DELETE)
- referrer - リファラー URL
- ip - クライアントの IP アドレス
- ajax - リクエストが AJAX リクエストかどうか
- scheme - サーバープロトコル (http, https)
- user_agent - ブラウザ情報
- type - コンテンツタイプ
- length - コンテンツ長
- query - クエリ文字列パラメータ
- data - 投稿データまたは JSON データ
- cookies - クッキーデータ
- files - アップロードされたファイル
- secure - 接続がセキュアかどうか
- accept - HTTP accept パラメータ
- proxy_ip - クライアントのプロキシ IP アドレス。
$_SERVER配列をHTTP_CLIENT_IP、HTTP_X_FORWARDED_FOR、HTTP_X_FORWARDED、HTTP_X_CLUSTER_CLIENT_IP、HTTP_FORWARDED_FOR、HTTP_FORWARDEDの順でスキャンします。 - host - リクエストホスト名
- servername -
$_SERVERからの SERVER_NAME
ヘルパーメソッド
URL の一部を組み合わせたり、特定のヘッダーを扱うためのいくつかのヘルパーメソッドがあります。
フル URL
getFullUrl() メソッドを使用してフルリクエスト URL にアクセスできます:
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
ベース URL
getBaseUrl() メソッドを使用してベース URL にアクセスできます:
// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// 注意: 末尾のスラッシュはありません。
クエリ解析
parseQuery() メソッドに URL を渡すと、クエリ文字列を連想配列に解析できます:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
コンテンツ Accept タイプのネゴシエーション
v3.17.2
negotiateContentType() メソッドを使用して、クライアントが送信した Accept ヘッダーに基づいて、最適なコンテンツタイプを決定できます。
// 例: Accept ヘッダー: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// 以下でサポートするものを定義します。
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
// JSON レスポンスを送信
} elseif ($typeToServe === 'application/xml') {
// XML レスポンスを送信
} else {
// デフォルトで何か他のものを設定するか、エラーをスロー
}
注意:
Acceptヘッダーに利用可能なタイプが見つからない場合、メソッドはnullを返します。Acceptヘッダーが定義されていない場合、メソッドは$availableTypes配列の最初のタイプを返します。
関連項目
- Routing - ルートをコントローラーにマッピングし、ビューをレンダリングする方法を参照。
- Responses - HTTP レスポンスをカスタマイズする方法。
- Why a Framework? - リクエストが全体像にどのように適合するかを理解。
- Collections - データのコレクションを扱う。
- Uploaded File Handler - ファイルアップロードの処理。
トラブルシューティング
request()->ipとrequest()->proxy_ipは、ウェブサーバーがプロキシ、ロードバランサーなどの背後にある場合に異なる可能性があります。
変更履歴
- v3.17.2 - negotiateContentType() を追加
- v3.12.0 - リクエストオブジェクト経由でファイルアップロードを扱う機能を追加。
- v1.0 - 初回リリース。
Learn/why_frameworks
フレームワークを使う理由
一部のプログラマーは、フレームワークの使用に熱烈に反対しています。フレームワークは膨大で、遅く、学習が困難だと主張しています。 彼らは、フレームワークは不要であり、それらなしでより良いコードを書くことができると述べています。 フレームワークを使用することのデメリットについては、いくつかの妥当なポイントがあります。 ただし、フレームワークを使用する利点もたくさんあります。
フレームワークを使用する理由
フレームワークを使用したいと思う理由のいくつかを以下に示します:
- 迅速な開発: フレームワークは多くの機能を提供します。これにより、Webアプリケーションをより迅速に構築できます。 フレームワークが必要な機能の多くを提供しているため、多くのコードを書く必要がありません。
- 一貫性: フレームワークは、作業方法を一貫させる方法を提供します。 これにより、コードの動作方法を理解しやすくなり、他の開発者がコードを理解しやすくなります。 開発者チームと一緒に作業している場合、スクリプトごとに異なる一貫性が失われる可能性があります。
- セキュリティ: フレームワークは、一般的なセキュリティ脅威からWebアプリケーションを保護するためのセキュリティ機能を提供します。 これは、フレームワークが多くの部分を処理してくれるため、セキュリティについてあまり心配する必要がなくなります。
- コミュニティ: フレームワークには、フレームワークに貢献する大規模な開発者コミュニティが存在します。 これにより、質問や問題が生じた場合に他の開発者からサポートを受けることができます。 また、フレームワークの使用方法を学ぶのに利用できるリソースが豊富にあることを意味します。
- ベストプラクティス: フレームワークは、最善の方法で構築されています。 これにより、フレームワークから学び、自分自身のコードでも同じベストプラクティスを使用できます。 これにより、より優れたプログラマーになるのに役立ちます。 時には自分の知らないことがあることがあり、それが最後にあなたを苦しめる可能性があります。
- 拡張性: フレームワークは拡張可能に設計されています。 これは、フレームワークに独自の機能を追加できることを意味します。 これにより、特定のニーズに合わせたWebアプリケーションを構築できます。
Flightはマイクロフレームワークです。 つまり、小さく軽量です。 LaravelやSymfonyのような大規模なフレームワークほどの機能は提供しません。 ただし、Webアプリケーションを構築するために必要な機能の多くを提供します。 また、学びやすく使用も容易です。 これにより、簡単かつ迅速にWebアプリケーションを構築するのに適しています。 フレームワークに新しい場合は、Flightは初心者に最適なフレームワークです。 フレームワークを使用する利点を学び、過度な複雑さで圧倒されることなく学習するのに役立ちます。 Flightの経験を積んだ後は、LaravelやSymfonyなどのより複雑なフレームワークに移ることがより簡単になります。 ただし、Flightでも成功した堅牢なアプリケーションを作成できます。
ルーティングとは?
ルーティングはFlightフレームワークの中核ですが、それは一体何でしょうか? ルーティングとは、URLを取得してコード内の特定の関数に一致させるプロセスです。
これにより、WebサイトをリクエストされたURLに基づいて異なる動作をさせることができます。 たとえば、ユーザーが/user/1234を訪れたときにユーザープロフィールを表示したいが、/usersを訪れたときに全ユーザーのリストを表示したいとします。 これはすべてルーティングを通じて行われます。
以下のようになります:
- ユーザーがブラウザに移動し、
http://example.com/user/1234を入力します。 - サーバーがリクエストを受信し、URLを確認してFlightアプリケーションコードに渡します。
- あなたのFlightコードに
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);のようなものがあるとします。 FlightアプリケーションコードはURLを確認し、定義したルートに一致することを確認し、次にそのルートに対して定義したコードを実行します。 - Flightのルーターはその後動作し、
UserControllerクラス内のviewUserProfile($id)メソッドを呼び出し、メソッド内の$id引数として1234を渡します。 viewUserProfile()メソッド内のコードが実行され、指示した内容が実行されます。 ユーザープロフィールページのHTMLをエコーするか、これがRESTful APIの場合は、ユーザーの情報を含むJSON応答をエコーするかもしれません。- Flightはこれらを簡潔にまとめ、応答ヘッダーを生成し、ユーザーのブラウザに送信します。
- ユーザーは喜びに満たされ、自分自身に温かい抱擁をします!
そして、なぜ重要なの?
適切な中央集権的なルーターを持つことで、あなたの生活が劇的に簡単になる可能性があります! 最初はそれが見えにくいかもしれません。 以下は、そのいくつか理由です:
- 中央集権的なルーティング: すべてのルートを1つの場所に保管できます。 これにより、どのルートがあり、それが何を行うかが見やすくなります。 必要に応じてルートを変更する際も便利です。
- ルートパラメータ: ルートパラメータを使用して、ルートメソッドにデータを渡すことができます。 これはコードをクリーンかつ整理された状態に保つのに最適な方法です。
- ルートグループ: ルートをグループ化できます。 これはコードを整理し、一連のルートにミドルウェアを適用するために優れています。
- ルートのエイリアス設定: ルートにエイリアスを割り当てることができます。 これにより、URLを後で動的に生成できるようになります(たとえば、テンプレートの場合)。 例:コード内で
/user/1234をハードコーディングする代わりに、user_viewを参照し、idをパラメーターとして渡すことができます。 後でURLを/admin/user/1234に変更する場合に非常に便利です。 ハードコーディングしたすべてのURLを変更する必要はなくなります。 - ルートミドルウェア: ルートにミドルウェアを追加できます。 ミドルウェアは、特定のユーザーが特定のルートやルートグループにアクセスできるようにするなど、アプリケーションに特定の振る舞いを追加するには非常に強力です。
多分、Webサイトを作成するためのスクリプトごとの方法に慣れているかもしれません。 index.phpというファイルがあり、URLを確認し、URLに基づいて特定の関数を実行します。 これもルーティングの一形態ですが、整理されていない上にすぐに手に負えなくなります。 Flightのルーティングシステムは、ルーティングを処理するより整理された強力な方法です。
これ?
// /user/view_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
viewUserProfile($id);
}
// /user/edit_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
editUserProfile($id);
}
// etc...
それともこちら?
// index.php
Flight::route('/user/@id', [ 'UserController', 'viewUserProfile' ]);
Flight::route('/user/@id/edit', [ 'UserController', 'editUserProfile' ]);
// In maybe your app/controllers/UserController.php
class UserController {
public function viewUserProfile($id) {
// do something
}
public function editUserProfile($id) {
// do something
}
}
中央集権的なルーティングシステムを使用する利点が見え始めたことを願っています。 長い目で見れば、管理や理解が容易になります!
リクエストとレスポンス
Flightはリクエストとレスポンスを処理するための簡単で簡単な方法を提供します。 これがWebフレームワークの中核です。 ユーザーのブラウザからのリクエストを受け取り、処理してからレスポンスを返すことで、Webアプリケーションを構築できます。 これにより、ユーザープロフィールを表示したり、ユーザーのログインを許可したり、新しいブログ投稿を許可したりするWebアプリケーションを構築できます。
リクエスト
リクエストは、ユーザーのブラウザがウェブサイトを訪れるときにサーバーに送信するものです。 このリクエストには、ユーザーが何をしたいかに関する情報が含まれます。 たとえば、ユーザーが訪れたいURLの情報、ユーザーがサーバーに送りたいデータ、サーバーから受け取りたいデータの種類などが含まれるかもしれません。 リクエストは読み取り専用です。 リクエストを変更することはできませんが、読み取ることはできます。
Flightはリクエストに関する情報にアクセスするための簡単な方法を提供します。 Flight::request()メソッドを使用してリクエストに関する情報にアクセスできます。 このメソッドはリクエストに関する情報を含むRequestオブジェクトを返します。 このオブジェクトを使用して、URL、メソッド、ユーザーがサーバーに送信したデータなどの情報にアクセスできます。
レスポンス
レスポンスとは、ユーザーのブラウザがウェブサイトを訪れるときにサーバーがユーザーのブラウザに送り返すものです。 このレスポンスには、サーバーが行いたいことに関する情報が含まれます。 たとえば、サーバーがユーザーに送信したいデータの種類、ユーザーから受け取りたいデータの種類、サーバーがユーザーのコンピュータに保存したいデータの種類などが含まれるかもしれません。
Flightはユーザーのブラウザにレスポンスを送信する簡単な方法を提供します。 Flight::response()メソッドを使用してレスポンスを送信できます。 このメソッドは、Responseオブジェクトを引数として受け取り、そのレスポンスをユーザーのブラウザに送信します。 このオブジェクトを使用して、HTML、JSON、ファイルなど、ユーザーのブラウザにレスポンスを送信できます。 Flightはレスポンスの一部を自動生成して簡単にするお手伝いをしますが、最終的にはユーザーに送り返す内容を制御できます。
Learn/responses
レスポンス
概要
Flight はレスポンスヘッダーの一部を生成するのを手伝いますが、ユーザーに返す内容のほとんどの制御はあなたが持ちます。通常は response() オブジェクトに直接アクセスしますが、Flight にはレスポンスヘッダーの一部を設定するためのヘルパーメソッドもあります。
理解
ユーザーが request リクエストをアプリケーションに送信した後、彼らに適切なレスポンスを生成する必要があります。彼らは好みの言語、特定の種類の圧縮を扱えるかどうか、ユーザーエージェントなどを含む情報を送信してきました。すべてを処理した後、適切なレスポンスを彼らに返します。これはヘッダーの設定、HTML や JSON のボディを出力、またはページへのリダイレクトです。
基本的な使用方法
レスポンスボディの送信
Flight は出力のバッファリングに ob_start() を使用します。これにより、echo や print を使用してユーザーにレスポンスを送信でき、Flight がそれをキャプチャして適切なヘッダーと共にユーザーに返します。
// This will send "Hello, World!" to the user's browser
Flight::route('/', function() {
echo "Hello, World!";
});
// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!
代替として、write() メソッドを呼び出してボディに追加することもできます。
// This will send "Hello, World!" to the user's browser
Flight::route('/', function() {
// verbose, but gets the job sometimes when you need it
Flight::response()->write("Hello, World!");
// if you want to retrieve the body that you've set at this point
// you can do so like this
$body = Flight::response()->getBody();
});
JSON
Flight は JSON および JSONP レスポンスの送信をサポートします。JSON レスポンスを送信するには、JSON エンコードされるデータを渡します:
Flight::route('/@companyId/users', function(int $companyId) {
// somehow pull out your users from a database for example
$users = Flight::db()->fetchAll("SELECT id, first_name, last_name FROM users WHERE company_id = ?", [ $companyId ]);
Flight::json($users);
});
// [{"id":1,"first_name":"Bob","last_name":"Jones"}, /* more users */ ]
Note: By default, Flight will send a
Content-Type: application/jsonheader with the response. It will also use the flagsJSON_THROW_ON_ERRORandJSON_UNESCAPED_SLASHESwhen encoding the JSON.
ステータスコード付き JSON
2 番目の引数としてステータスコードを渡すこともできます:
Flight::json(['id' => 123], 201);
プリティプリント付き JSON
最後の位置に引数を渡してプリティプリントを有効にすることもできます:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
JSON 引数の順序変更
Flight::json() は非常に古いメソッドですが、Flight の目標はプロジェクトの後方互換性を維持することです。引数の順序を変更してよりシンプルな構文を使用したい場合、JSON メソッドを他の Flight メソッドと同様に remap するだけです:
Flight::map('json', function($data, $code = 200, $options = 0) {
// now you don't have to `true, 'utf-8'` when using the json() method!
Flight::_json($data, $code, true, 'utf-8', $options);
}
// And now it can be used like this
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON と実行の停止
v3.10.0
JSON レスポンスを送信して実行を停止したい場合、jsonHalt() メソッドを使用できます。これは、承認などのチェックを行い、ユーザーが承認されていない場合に JSON レスポンスを即座に送信し、既存のボディコンテンツをクリアして実行を停止するのに便利です。
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Check if the user is authorized
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
// no exit; needed here.
}
// Continue with the rest of the route
});
v3.10.0 以前では、以下のようにする必要がありました:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Check if the user is authorized
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// Continue with the rest of the route
});
レスポンスボディのクリア
レスポンスボディをクリアしたい場合、clearBody メソッドを使用できます:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
上記の使用例は一般的ではないかもしれませんが、middleware で使用される場合に一般的になる可能性があります。
レスポンスボディに対するコールバックの実行
addResponseBodyCallback メソッドを使用して、レスポンスボディにコールバックを実行できます:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// This will gzip all the responses for any route
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
複数のコールバックを追加でき、追加された順序で実行されます。これには任意の callable を受け入れるため、クラス配列 [ $class, 'method' ]、クロージャ $strReplace = function($body) { str_replace('hi', 'there', $body); };、または HTML コードを最小化するための関数名 'minify' などを渡せます。
Note: Route callbacks will not work if you are using the flight.v2.output_buffering configuration option.
特定のルートコールバック
これを特定のルートにのみ適用したい場合、ルート内でコールバックを追加できます:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// This will gzip only the response for this route
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
ミドルウェアオプション
middleware を使用して、すべてのルートにコールバックをミドルウェア経由で適用することもできます:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// Apply the callback here on the response() object.
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// minify the body somehow
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
ステータスコード
status メソッドを使用して、レスポンスのステータスコードを設定できます:
Flight::route('/@id', function($id) {
if($id == 123) {
Flight::response()->status(200);
echo "Hello, World!";
} else {
Flight::response()->status(403);
echo "Forbidden";
}
});
現在のステータスコードを取得したい場合、引数なしで status メソッドを使用できます:
Flight::response()->status(); // 200
レスポンスヘッダーの設定
header メソッドを使用して、レスポンスのコンテンツタイプなどのヘッダーを設定できます:
// This will send "Hello, World!" to the user's browser in plain text
Flight::route('/', function() {
Flight::response()->header('Content-Type', 'text/plain');
// or
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
リダイレクト
redirect() メソッドを使用して現在のリクエストをリダイレクトでき、新しい URL を渡します:
Flight::route('/login', function() {
$username = Flight::request()->data->username;
$password = Flight::request()->data->password;
$passwordConfirm = Flight::request()->data->password_confirm;
if($password !== $passwordConfirm) {
Flight::redirect('/new/location');
return; // this is necessary so functionality below doesn't execute
}
// add the new user...
Flight::db()->runQuery("INSERT INTO users ....");
Flight::redirect('/admin/dashboard');
});
Note: By default Flight sends a HTTP 303 ("See Other") status code. You can optionally set a custom code:
Flight::redirect('/new/location', 301); // permanent
ルート実行の停止
halt メソッドを呼び出して、任意の時点でフレームワークを停止して即座に終了できます:
Flight::halt();
オプションの HTTP ステータスコードとメッセージを指定することもできます:
Flight::halt(200, 'Be right back...');
halt を呼び出すと、その時点までのレスポンスコンテンツを破棄し、すべての実行を停止します。フレームワークを停止して現在のレスポンスを出力したい場合、stop メソッドを使用します:
Flight::stop($httpStatusCode = null);
Note:
Flight::stop()has some odd behavior such as it will output the response but continue executing your script which might not be what you are after. You can useexitorreturnafter callingFlight::stop()to prevent further execution, but it is generally recommended to useFlight::halt().
This will save the header key and value to the response object. At the end of the request lifecycle it will build the headers and send a response.
高度な使用方法
ヘッダーの即時送信
ヘッダーでカスタムなことをする必要があり、作業中のそのコード行でヘッダーを送信する必要がある場合があります。streamed route を設定する場合、これが必要です。これは response()->setRealHeader() で達成できます。
Flight::route('/', function() {
Flight::response()->setRealHeader('Content-Type: text/plain');
echo 'Streaming response...';
sleep(5);
echo 'Done!';
})->stream();
JSONP
JSONP リクエストの場合、コールバック関数を定義するためのクエリパラメータ名をオプションで渡せます:
Flight::jsonp(['id' => 123], 'q');
したがって、?q=my_func を使用した GET リクエストの場合、出力は以下のようになります:
my_func({"id":123});
クエリパラメータ名を渡さない場合、デフォルトで jsonp になります。
Note: If you are still using JSONP requests in 2025 and beyond, hop in the chat and tell us why! We love hearing some good battle/horror stories!
レスポンスデータのクリア
clear() メソッドを使用して、レスポンスボディとヘッダーをクリアできます。これはレスポンスに割り当てられたすべてのヘッダーをクリアし、レスポンスボディをクリアし、ステータスコードを 200 に設定します。
Flight::response()->clear();
レスポンスボディのみのクリア
レスポンスボディのみをクリアしたい場合、clearBody() メソッドを使用できます:
// This will still keep any headers set on the response() object.
Flight::response()->clearBody();
HTTP キャッシング
Flight は HTTP レベルのキャッシングのビルトインサポートを提供します。キャッシング条件が満たされた場合、Flight は HTTP 304 Not Modified レスポンスを返します。次にクライアントが同じリソースをリクエストすると、ローカルにキャッシュされたバージョンの使用が促されます。
ルートレベルのキャッシング
レスポンス全体をキャッシュしたい場合、cache() メソッドを使用してキャッシュ時間を渡せます。
// This will cache the response for 5 minutes
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// Alternatively, you can use a string that you would pass
// to the strtotime() method
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Last-Modified
lastModified メソッドを使用して、ページが最後に変更された日時を UNIX タイムスタンプで設定できます。クライアントは最後に変更された値が変更されるまでキャッシュを使用し続けます。
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
ETag キャッシングは Last-Modified に似ていますが、リソースに任意の ID を指定できます:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
lastModified または etag のいずれかを呼び出すと、両方ともキャッシュ値を設定してチェックします。リクエスト間でキャッシュ値が同じ場合、Flight は即座に HTTP 304 レスポンスを送信して処理を停止します。
ファイルのダウンロード
v3.12.0
エンドユーザーにファイルをストリーミングするためのヘルパーメソッドがあります。download メソッドを使用してパスを渡せます。
Flight::route('/download', function () {
Flight::download('/path/to/file.txt');
// As of v3.17.1 you can specify a custom filename for the download
Flight::download('/path/to/file.txt', 'custom_name.txt');
});
関連項目
- Routing - How to map routes to controllers and render views.
- Requests - Understanding how to handle incoming requests.
- Middleware - Using middleware with routes for authentication, logging, etc.
- Why a Framework? - Understanding the benefits of using a framework like Flight.
- Extending - How to extend Flight with your own functionality.
トラブルシューティング
- If you're having trouble with redirects not working, make sure you add a
return;to the method. stop()andhalt()are not the same thing.halt()will stop execution immediately, whilestop()will allow execution to continue.
Changelog
- v3.17.1 - Added
$fileNametodownloadFile()method. - v3.12.0 - Added downloadFile helper method.
- v3.10.0 - Added
jsonHalt. - v1.0 - Initial release.
Learn/events
イベント マネージャー
v3.15.0 時点
概要
イベントにより、アプリケーション内でカスタム動作を登録およびトリガーできます。Flight::onEvent() と Flight::triggerEvent() の追加により、アプリのライフサイクルにおける重要な時点にフックしたり、独自のイベント(通知やメールなど)を定義したりして、コードをよりモジュール化し、拡張しやすくできます。これらのメソッドは Flight の mappable methods の一部であり、必要に応じて動作をオーバーライドできます。
理解
イベントにより、アプリケーションの異なる部分を分離し、互いに過度に依存しないようにできます。この分離—しばしば デカップリング と呼ばれる—は、コードの更新、拡張、デバッグを容易にします。一つの大きな塊で全てを書く代わりに、論理を特定のアクション(イベント)に応答する小さな独立したピースに分割できます。
ブログアプリを構築していると想像してください:
- ユーザーがコメントを投稿すると、以下を行いたい場合:
- コメントをデータベースに保存。
- ブログオーナーにメールを送信。
- セキュリティのためにアクションをログに記録。
イベントなしでは、これらを一つの関数に詰め込むことになります。イベントを使うと、分離できます:一部がコメントを保存し、もう一部が 'comment.posted' のようなイベントをトリガーし、別々のリスナーがメールとログを処理します。これによりコードがクリーンになり、通知のような機能を追加または削除する際にコアロジックに触れずに済みます。
一般的なユースケース
主に、イベントはオプションのものに適しており、システムの絶対的なコア部分ではありません。例えば、以下のものは便利ですが、何らかの理由で失敗してもアプリケーションは動作するはずです:
- ログ記録: ログインやエラーなどのアクションを記録し、主コードを散らかさない。
- 通知: 何かが発生したときにメールやアラートを送信。
- キャッシュ更新: キャッシュを更新したり、他のシステムに変更を通知したり。
ただし、パスワードを忘れた機能があるとします。これはコア機能の一部であり、イベントではありません。なぜなら、そのメールが送信されなければ、ユーザーはパスワードをリセットできず、アプリケーションを使用できないからです。
基本的な使用方法
Flight のイベントシステムは、2 つの主なメソッドを中心に構築されています:イベントリスナーを登録するための Flight::onEvent() と、イベントを発火するための Flight::triggerEvent()。これらを使用する方法は以下の通りです:
イベントリスナーの登録
イベントをリッスンするには、Flight::onEvent() を使用します。このメソッドにより、イベントが発生したときに何が起こるかを定義できます。
Flight::onEvent(string $event, callable $callback): void
$event: イベントの名前(例:'user.login')。$callback: イベントがトリガーされたときに実行する関数。
イベントが発生したときに Flight に何をするかを伝えることで、イベントに「購読」します。コールバックはイベントトリガーから渡された引数を受け取ることができます。
Flight のイベントシステムは同期型です。つまり、各イベントリスナーは順番に実行されます。イベントをトリガーすると、そのイベントのすべての登録されたリスナーが完了するまでコードが続行されません。これは、非同期イベントシステム(リスナーが並行して実行されたり、後で実行されたりする)とは異なるため、理解が重要です。
シンプルな例
Flight::onEvent('user.login', function ($username) {
echo "Welcome back, $username!";
// you can send an email if the login is from a new location
// 新しい場所からのログインの場合、メールを送信できます
});
ここで、'user.login' イベントがトリガーされると、ユーザーを名前で挨拶し、必要に応じてメール送信のロジックを含めることができます。
注意: コールバックは関数、匿名関数、またはクラスのメソッドです。
イベントのトリガー
イベントを発生させるには、Flight::triggerEvent() を使用します。これにより、Flight にそのイベントのすべてのリスナーを実行し、提供したデータを渡します。
Flight::triggerEvent(string $event, ...$args): void
$event: トリガーするイベント名(登録されたイベントと一致する必要があります)。...$args: リスナーに送信するオプションの引数(任意の数の引数)。
シンプルな例
$username = 'alice';
Flight::triggerEvent('user.login', $username);
これにより、'user.login' イベントがトリガーされ、'alice' が以前定義したリスナーに送信され、出力は Welcome back, alice! になります。
- リスナーが登録されていない場合、何も起こりません—アプリは壊れません。
- 複数の引数を柔軟に渡すためにスプレッド演算子 (
...) を使用します。
イベントの停止
リスナーが false を返すと、そのイベントの追加のリスナーは実行されません。これにより、特定の条件に基づいてイベントチェーンを停止できます。リスナーの順序が重要であることを覚えておいてください。最初に false を返すものが残りを停止します。
例:
Flight::onEvent('user.login', function ($username) {
if (isBanned($username)) {
logoutUser($username);
return false; // Stops subsequent listeners
// 後続のリスナーを停止
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // this is never sent
// これは決して送信されません
});
イベントメソッドのオーバーライド
Flight::onEvent() と Flight::triggerEvent() は 拡張可能 です。つまり、それらの動作を再定義できます。これは、イベントシステムをカスタマイズしたい上級ユーザー(ログ追加やイベントディスパッチの変更など)にとって優れています。
例: onEvent のカスタマイズ
Flight::map('onEvent', function (string $event, callable $callback) {
// Log every event registration
// すべてのイベント登録をログに記録
error_log("New event listener added for: $event");
// Call the default behavior (assuming an internal event system)
// デフォルト動作を呼び出し(内部イベントシステムを想定)
Flight::_onEvent($event, $callback);
});
これで、イベントを登録するたびにログが記録された後、処理が続行されます。
オーバーライドの理由
- デバッグや監視を追加。
- 特定の環境でイベントを制限(例: テストで無効化)。
- 別のイベントライブラリと統合。
イベントの配置場所
プロジェクトでイベントの概念に慣れていない場合、アプリ内でこれらのイベントをどこで登録するのか? と疑問に思うかもしれません。Flight のシンプルさにより、厳格なルールはありません—プロジェクトに適した場所に配置できます。ただし、アプリが成長するにつれてコードを維持しやすくするために、整理しておくことが役立ちます。Flight の軽量な性質に合わせた実用的なオプションとベストプラクティスを以下に示します:
オプション 1: メインの index.php 内
小さなアプリやクイックプロトタイプの場合、index.php ファイル内でルートと共にイベントを登録できます。これにより全てを一箇所にまとめ、シンプルさを優先する場合に適しています。
require 'vendor/autoload.php';
// Register events
// イベントを登録
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
// $username が " . date('Y-m-d H:i:s') . " にログイン
});
// Define routes
// ルートを定義
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
// ログインしました!
});
Flight::start();
- 利点: シンプル、追加ファイルなし、小規模プロジェクトに最適。
- 欠点: アプリが成長し、イベントとルートが増えると散らかりやすくなります。
オプション 2: 専用の events.php ファイル
少し大きなアプリの場合、イベント登録を app/config/events.php のような専用ファイルに移動することを検討してください。index.php でルートの前にこのファイルをインクルードします。これは、Flight プロジェクトでよく見られる app/config/routes.php のルート整理を模倣しています。
// app/config/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
// $username が " . date('Y-m-d H:i:s') . " にログイン
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $name!";
// $email にメール送信: ようこそ、$name!
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
// ログインしました!
});
Flight::start();
- 利点:
index.phpをルーティングに集中させ、イベントを論理的に整理、検索と編集が容易。 - 欠点: 非常に小さなアプリでは構造が過剰に感じるかもしれません。
オプション 3: トリガーされる場所の近く
もう一つのアプローチは、イベントをトリガーされる場所の近くで登録すること、例えばコントローラーやルート定義内です。イベントがアプリの一部の特定のものに適している場合に有効です。
Flight::route('/signup', function () {
// Register event here
// ここでイベントを登録
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $email!";
// $email にようこそメールを送信!
});
$email = 'jane@example.com';
Flight::triggerEvent('user.registered', $email);
echo "Signed up!";
// 登録ありがとう!
});
- 利点: 関連コードを一緒に保ち、孤立した機能に適しています。
- 欠点: イベント登録が散らばり、全イベントを一度に把握しにくく、注意しないと重複登録のリスクがあります。
Flight のベストプラクティス
- シンプルに開始: 小さなアプリでは、
index.phpにイベントを配置。Flight のミニマリズムに合います。 - 賢く成長: アプリが拡張(例: 5-10 個以上のイベント)したら、
app/config/events.phpファイルを使用。ルートの整理のように自然なステップで、コードを整理しつつ複雑なフレームワークを追加しません。 - 過剰設計を避ける: アプリが巨大になるまで、完全な「イベントマネージャー」クラスやディレクトリを作成しないでください—Flight はシンプルさを活かします。
ヒント: 目的別にグループ化
events.php 内で、関連イベント(例: すべてのユーザー関連イベント)をコメント付きでグループ化:
// app/config/events.php
// User Events
// ユーザーイベント
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
// $username がログイン
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
// $email へようこそ!
});
// Page Events
// ページイベント
Flight::onEvent('page.updated', function ($pageId) {
Flight::cache()->delete("page_$pageId");
});
この構造は拡張しやすく、初心者向けです。
実世界の例
イベントの動作と有用性を示すために、いくつかの実世界のシナリオを歩いてみましょう。
例 1: ユーザー login のログ記録
// Step 1: Register a listener
// ステップ 1: リスナーを登録
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
// $username が $time にログイン
});
// Step 2: Trigger it in your app
// ステップ 2: アプリ内でトリガー
Flight::route('/login', function () {
$username = 'bob'; // Pretend this comes from a form
// フォームから来たと仮定
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
// こんにちは、$username!
});
有用な理由: ログインコードはログについて知る必要がなく、イベントをトリガーするだけです。後でリスナーを追加(例: ようこそメール送信)でき、ルートを変更せずに済みます。
例 2: 新規ユーザーの通知
// Listener for new registrations
// 新規登録のリスナー
Flight::onEvent('user.registered', function ($email, $name) {
// Simulate sending an email
// メール送信をシミュレート
echo "Email sent to $email: Welcome, $name!";
// $email にメール送信: ようこそ、$name!
});
// Trigger it when someone signs up
// 誰かがサインアップしたときにトリガー
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
// サインアップありがとう!
});
有用な理由: サインアップロジックはユーザー作成に集中し、イベントが通知を処理します。後でリスナーを追加(例: サインアップのログ)できます。
例 3: キャッシュのクリア
// Listener to clear a cache
// キャッシュクリアのリスナー
Flight::onEvent('page.updated', function ($pageId) {
// if using the flightphp/cache plugin
// flightphp/cache プラグインを使用する場合
Flight::cache()->delete("page_$pageId");
echo "Cache cleared for page $pageId.";
// ページ $pageId のキャッシュをクリア。
});
// Trigger when a page is edited
// ページが編集されたときにトリガー
Flight::route('/edit-page/(@id)', function ($pageId) {
// Pretend we updated the page
// ページを更新したと仮定
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
// ページ $pageId を更新。
});
有用な理由: 編集コードはキャッシングを気にせず、更新をシグナルするだけです。アプリの他の部分が必要に応じて反応できます。
ベストプラクティス
- イベント名を明確に:
'user.login'や'page.updated'のような具体的な名前を使用し、何をするかが明らかになるように。 - リスナーをシンプルに保つ: リスナーに遅いまたは複雑なタスクを置かない—アプリを高速に保つ。
- イベントをテスト: 手動でトリガーして、リスナーが期待通りに動作することを確認。
- イベントを賢く使用: デカップリングに優れていますが、多すぎるとコードが追いにくくなる—適切な場合に使用。
Flight PHP のイベントシステムは、Flight::onEvent() と Flight::triggerEvent() により、シンプルでありながら強力な方法で柔軟なアプリケーションを構築できます。アプリの異なる部分がイベントを通じて互いに通信することで、コードを整理、再利用しやすく、拡張しやすく保てます。アクションのログ、通知の送信、更新の管理など、イベントによりロジックを絡めずに実行できます。さらに、これらのメソッドをオーバーライドできるため、システムをニーズに合わせて調整できます。一つのイベントから小さく始め、アプリの構造がどのように変化するかを観察してください!
組み込みイベント
Flight PHP には、フレームワークのライフサイクルにフックするためのいくつかの組み込みイベントがあります。これらのイベントは、リクエスト/レスポンスサイクルの特定の時点でトリガーされ、特定のアクションが発生したときにカスタムロジックを実行できます。
組み込みイベントリスト
- flight.request.received:
function(Request $request)リクエストが受信、解析、処理されたときにトリガー。 - flight.error:
function(Throwable $exception)リクエストライフサイクル中にエラーが発生したときにトリガー。 - flight.redirect:
function(string $url, int $status_code)リダイレクトが開始されたときにトリガー。 - flight.cache.checked:
function(string $cache_key, bool $hit, float $executionTime)特定のキーでキャッシュがチェックされたときにトリガー(ヒットまたはミス)。 - flight.middleware.before:
function(Route $route)ビフォーミドルウェアが実行された後にトリガー。 - flight.middleware.after:
function(Route $route)アフターミドルウェアが実行された後にトリガー。 - flight.middleware.executed:
function(Route $route, $middleware, string $method, float $executionTime)任意のミドルウェアが実行された後にトリガー。 - flight.route.matched:
function(Route $route)ルートがマッチしたものの、まだ実行されていないときにトリガー。 - flight.route.executed:
function(Route $route, float $executionTime)ルートが実行され処理された後にトリガー。$executionTimeはルート実行(コントローラー呼び出しなど)に要した時間。 - flight.view.rendered:
function(string $template_file_path, float $executionTime)ビューがレンダリングされた後にトリガー。$executionTimeはテンプレートレンダリングに要した時間。注意:renderメソッドをオーバーライドした場合、このイベントを再トリガーする必要があります。 - flight.response.sent:
function(Response $response, float $executionTime)レスポンスがクライアントに送信された後にトリガー。$executionTimeはレスポンス構築に要した時間。
関連項目
- Extending Flight - Flight のコア機能を拡張およびカスタマイズする方法。
- Cache - ページが更新されたときにイベントを使用してキャッシュをクリアする例。
トラブルシューティング
- イベントリスナーが呼び出されない場合、イベントをトリガーする前に登録されていることを確認してください。登録の順序が重要です。
変更履歴
- v3.15.0 - Flight にイベントを追加。
Learn/templates
HTMLビューとテンプレート
概要
Flightはデフォルトでいくつかの基本的なHTMLテンプレート機能を提供しています。テンプレートは、アプリケーションロジックをプレゼンテーション層から切り離すための非常に効果的な方法です。専用エンジン(Twig、Latteなど)は、AIコーディングツールに馴染みのある制約された構文を提供するため、ビジネスロジックをHTMLに混ぜ込みにくくなります。
理解
アプリケーションを構築するとき、エンドユーザーに返したいHTMLがあるでしょう。PHP自体はテンプレート言語ですが、データベース呼び出しやAPI呼び出しなどのビジネスロジックをHTMLファイルに簡単に埋め込めてしまい、テストや疎結合化が非常に困難になります。データをテンプレートに渡し、テンプレート自体にレンダリングさせることで、コードの疎結合化とユニットテストがはるかに容易になります。テンプレートを使えば、きっと感謝されますよ!
基本的な使い方
Flightでは、renderをマップする(またはビュークラスを登録する)だけで、デフォルトのビューエンジンを別のものに置き換えることができます。Twig、Latte、Smarty、Bladeなどの詳細は下にスクロールしてください。
スケルトンのデフォルト: 公式のflightphp/skeletonは、
app/views/(*.twig)配下でTwigのみを使用します。コントローラーは$this->app->render('welcome', $data)を呼び出します(拡張子は省略可能)。これは新規プロジェクトのためのアプリケーション側の選択であり、Flightコアの要件ではありません。Latteやその他のエンジンも引き続き完全にサポートされています。
Twig
スケルトンのデフォルト
Twigは、Symfonyや多くのPHPプロジェクトで使用されている、柔軟で高速かつ安全なテンプレートエンジンです。AIコーディングツールは特にTwigをよく知っており、デフォルトで出力を自動エスケープするためXSS対策にも役立ちます。
インストール
composer require twig/twig
(composer create-project flightphp/skeletonでインストールした場合は、すでに含まれています。)
基本設定
renderメソッドを上書きして、デフォルトのPHPレンダラーの代わりにTwigを使用します。
// デフォルトのPHPレンダラーの代わりにTwigを使うようにrenderメソッドを上書きします
Flight::map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Twigがコンパイル済みテンプレートを保存する場所
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
]);
// "welcome" または "welcome.twig" を許可します
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
スケルトンでは、この配線(設定)はapp/config/services.phpにあります(共有Twig環境、キャッシュパス、base_url / CSP nonceなどのグローバル変数)。コードをAIフレンドリーかつテストフレンドリーに保つには、Engineを注入し、コントローラーから$app->render()を呼び出すことをお勧めします。
FlightでのTwigの使用
Twigでレンダリングできるようになったので、次のようにできます。
{# app/views/home.twig #}
<html>
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.twig', [
'title' => 'Home Page',
'name' => $name
]);
});
ブラウザで/Bobにアクセスすると、出力は次のようになります。
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
詳細情報
Twigをレイアウトと一緒に使うより完全な例は、このドキュメントのawesome pluginsセクションにあります。Tracyバーでレンダリング時間のメトリクスを確認するには、Tracy ExtensionsのTwigパネルを参照してください。
Twigの全機能については、公式ドキュメントをご覧ください。
Latte
優れた代替案
Latteは、PHPに似た構文を持つフル機能のエンジンです。Flightアプリケーションにとっても優れた選択肢です。スケルトンは、共通のデフォルトとしてTwigを採用しているだけです(特にAIツールがテンプレートを生成する場合に便利です)。
インストール
composer require latte/latte
基本設定
主なアイデアは、renderメソッドを上書きして、デフォルトのPHPレンダラーの代わりにLatteを使用することです。
// デフォルトのPHPレンダラーの代わりにLatteを使うようにrenderメソッドを上書きします
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Latteがキャッシュを保存する場所
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
FlightでのLatteの使用
Latteでレンダリングできるようになったので、次のようにできます。
<!-- app/views/home.latte -->
<html>
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, {$name}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.latte', [
'title' => 'Home Page',
'name' => $name
]);
});
ブラウザで/Bobにアクセスすると、出力は次のようになります。
<html>
<head>
<title>Home Page - My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hello, Bob!</h1>
</body>
</html>
詳細情報
Latteをレイアウトと一緒に使うより複雑な例は、このドキュメントのawesome pluginsセクションにあります。
翻訳や言語機能を含むLatteの全機能については、公式ドキュメントをご覧ください。
組み込みビューエンジン
非推奨
注: これはまだデフォルトの機能であり、技術的にはまだ動作します。
ビューテンプレートを表示するには、テンプレートファイル名とオプションのテンプレートデータを指定してrenderメソッドを呼び出します。
Flight::render('hello.php', ['name' => 'Bob']);
渡したテンプレートデータは自動的にテンプレートに注入され、ローカル変数のように参照できます。テンプレートファイルは単なるPHPファイルです。hello.phpテンプレートファイルの内容が次の場合:
Hello, <?= $name ?>!
出力は次のようになります。
Hello, Bob!
また、setメソッドを使用してビュー変数を手動で設定することもできます。
Flight::view()->set('name', 'Bob');
これで、name変数はすべてのビューで使用できるようになります。したがって、単純に次のようにできます。
Flight::render('hello');
renderメソッドでテンプレートの名前を指定するとき、.php拡張子は省略できることに注意してください。
デフォルトでは、Flightはテンプレートファイル用にviewsディレクトリを探します。次の設定を行うことで、テンプレートの代替パスを設定できます。
Flight::set('flight.views.path', '/path/to/views');
レイアウト
Webサイトでは、コンテンツを差し替えられる単一のレイアウトテンプレートファイルを持つことが一般的です。レイアウトで使用するコンテンツをレンダリングするには、renderメソッドにオプションのパラメータを渡します。
Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');
これで、ビューにはheaderContentおよびbodyContentという変数が保存されます。次に、次のようにしてレイアウトをレンダリングできます。
Flight::render('layout', ['title' => 'Home Page']);
テンプレートファイルが次のようになっている場合:
header.php:
<h1><?= $heading ?></h1>
body.php:
<div><?= $body ?></div>
layout.php:
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<?= $headerContent ?>
<?= $bodyContent ?>
</body>
</html>
出力は次のようになります。
<html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Hello</h1>
<div>World</div>
</body>
</html>
Smarty
ビューにSmartyテンプレートエンジンを使用する方法は次のとおりです。
// Smartyライブラリを読み込みます
require './Smarty/libs/Smarty.class.php';
// Smartyをビュークラスとして登録します
// また、読み込み時にSmartyを設定するコールバック関数を渡します
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
$smarty->setTemplateDir('./templates/');
$smarty->setCompileDir('./templates_c/');
$smarty->setConfigDir('./config/');
$smarty->setCacheDir('./cache/');
});
// テンプレートデータを割り当てます
Flight::view()->assign('name', 'Bob');
// テンプレートを表示します
Flight::view()->display('hello.tpl');
完全を期すために、Flightのデフォルトのrenderメソッドも上書きする必要があります。
Flight::map('render', function(string $template, array $data): void {
Flight::view()->assign($data);
Flight::view()->display($template);
});
Blade
ビューにBladeテンプレートエンジンを使用する方法は次のとおりです。
まず、Composerを使用してBladeOneライブラリをインストールする必要があります。
composer require eftec/bladeone
次に、FlightでBladeOneをビュークラスとして設定できます。
<?php
// BladeOneライブラリを読み込みます
use eftec\bladeone\BladeOne;
// BladeOneをビュークラスとして登録します
// また、読み込み時にBladeOneを設定するコールバック関数を渡します
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
$views = __DIR__ . '/../views';
$cache = __DIR__ . '/../cache';
$blade->setPath($views);
$blade->setCompiledPath($cache);
});
// テンプレートデータを共有します
Flight::view()->share('name', 'Bob');
// テンプレートを表示します
echo Flight::view()->run('hello', []);
完全を期すために、Flightのデフォルトのrenderメソッドも上書きする必要があります。
<?php
Flight::map('render', function(string $template, array $data): void {
echo Flight::view()->run($template, $data);
});
この例では、hello.blade.phpテンプレートファイルは次のようになります。
<?php
Hello, {{ $name }}!
出力は次のようになります。
Hello, Bob!
関連項目
- インストール - 新規プロジェクト向けのスケルトンレイアウト(
app/views/*.twig)。 - 拡張 - 別のテンプレートエンジンを使用するために
renderメソッドを上書きする方法。 - ルーティング - ルートをコントローラーにマップしてビューをレンダリングする方法。
- レスポンス - HTTPレスポンスをカスタマイズする方法。
- セキュリティ - 自動エスケープとXSS。
- AIと開発者体験 - 単一のビューエンジンのデフォルトがコーディングエージェントに役立つ理由。
- なぜフレームワークなのか? - テンプレートが全体像にどのように適合するか。
トラブルシューティング
- ミドルウェアにリダイレクトがあるのに、アプリがリダイレクトされていないように見える場合は、ミドルウェアに
exit;ステートメントを追加してください。 - Twigがテンプレートを見つけられない場合は、
flight.views.pathを確認し、そのパスに予期した拡張子のファイルが存在することを確認してください(スケルトン:app/views/)。
変更履歴
- ドキュメント - Twigが公式スケルトンのデフォルトとして記載されました。Latteは引き続き第一級の代替案です。
- v2.0 - 初回リリース。
Learn/simple_pdo
SimplePdo PDO ヘルパークラス
概要
Flight の SimplePdo クラスは、PDO を使用したデータベース操作のためのモダンで機能豊富なヘルパーです。PdoWrapper を拡張し、insert()、update()、delete()、およびトランザクションなどの一般的なデータベース操作のための便利なヘルパーメソッドを追加します。データベースタスクを簡素化し、結果を Collections として返して簡単なアクセスを可能にし、高度なユースケースのためのクエリログとアプリケーションのパフォーマンス監視 (APM) をサポートします。
理解
SimplePdo クラスは、PHP でのデータベース操作をはるかに簡単に設計されています。プリペアドステートメント、フェッチモード、冗長な SQL 操作を扱う代わりに、一般的なタスクのためのクリーンでシンプルなメソッドが得られます。各行は Collection として返されるため、配列表記 ($row['name']) とオブジェクト表記 ($row->name) の両方を使用できます。
このクラスは PdoWrapper のスーパーセットであり、PdoWrapper のすべての機能に加えて、コードをよりクリーンで保守しやすくする追加のヘルパーメソッドを含みます。現在 PdoWrapper を使用している場合、SimplePdo へのアップグレードは PdoWrapper を拡張しているため簡単です。
Flight で SimplePdo を共有サービスとして登録し、Flight::db() を使用してアプリのどこからでも使用できます。
基本的な使用方法
SimplePdo の登録
まず、Flight に SimplePdo クラスを登録します:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
注意
PDO::ATTR_DEFAULT_FETCH_MODEを指定しない場合、SimplePdoは自動的にPDO::FETCH_ASSOCに設定します。
これで、どこからでも Flight::db() を使用してデータベース接続を取得できます。
クエリの実行
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
INSERT、UPDATE、または結果を手動でフェッチする場合に使用します:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row は配列です
}
書き込みにも使用できます:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
データベースから単一の値を入手します:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): ?Collection
単一の行を Collection (配列/オブジェクトアクセス) として入手します:
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// または
echo $user->name;
ヒント
SimplePdoは、fetchRow()クエリにすでに存在しない場合に自動的にLIMIT 1を追加し、クエリをより効率的にします。
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
すべての行を Collections の配列として入手します:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// または
echo $user->name;
}
fetchColumn()
function fetchColumn(string $sql, array $params = []): array
単一の列を配列としてフェッチします:
$ids = Flight::db()->fetchColumn("SELECT id FROM users WHERE active = ?", [1]);
// 戻り値: [1, 2, 3, 4, 5]
fetchPairs()
function fetchPairs(string $sql, array $params = []): array
結果をキー-バリューペア (最初の列をキー、2番目の列を値) としてフェッチします:
$userNames = Flight::db()->fetchPairs("SELECT id, name FROM users");
// 戻り値: [1 => 'John', 2 => 'Jane', 3 => 'Bob']
IN() プレースホルダーの使用
IN() 句で単一の ? を使用し、配列を渡すことができます:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
ヘルパーメソッド
SimplePdo の PdoWrapper に対する主な利点の1つは、一般的なデータベース操作のための便利なヘルパーメソッドの追加です。
insert()
function insert(string $table, array $data): string
1つまたは複数の行を挿入し、最後の挿入 ID を返します。
単一の挿入:
$id = Flight::db()->insert('users', [
'name' => 'John',
'email' => 'john@example.com'
]);
一括挿入:
$id = Flight::db()->insert('users', [
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com'],
]);
update()
function update(string $table, array $data, string $where, array $whereParams = []): int
行を更新し、影響を受けた行の数を返します:
$affected = Flight::db()->update(
'users',
['name' => 'Jane', 'email' => 'jane@example.com'],
'id = ?',
[1]
);
注意
SQLite の
rowCount()は、データが実際に変更された行の数を返します。行を既存の値と同じ値で更新した場合、rowCount()は 0 を返します。これは、PDO::MYSQL_ATTR_FOUND_ROWSを使用した MySQL の動作とは異なります。
delete()
function delete(string $table, string $where, array $whereParams = []): int
行を削除し、削除された行の数を返します:
$deleted = Flight::db()->delete('users', 'id = ?', [1]);
transaction()
function transaction(callable $callback): mixed
トランザクション内でコールバックを実行します。トランザクションは成功時に自動的にコミットされ、エラー時にロールバックされます:
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John']);
$db->insert('logs', ['action' => 'user_created']);
return $db->lastInsertId();
});
コールバック内で例外が発生した場合、トランザクションは自動的にロールバックされ、例外が再スローされます。
高度な使用方法
クエリログと APM
クエリのパフォーマンスを追跡したい場合、登録時に APM 追跡を有効にします:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name',
'user',
'pass',
[/* PDO options */],
[
'trackApmQueries' => true,
'maxQueryMetrics' => 1000
]
]);
クエリを実行した後、手動でログを記録できますが、有効にされている場合 APM は自動的にログを記録します:
Flight::db()->logQueries();
これにより、接続とクエリメトリクスを含むイベント (flight.db.queries) がトリガーされ、Flight のイベントシステムを使用してリッスンできます。
完全な例
Flight::route('/users', function () {
// すべてのユーザーを取得
$users = Flight::db()->fetchAll('SELECT * FROM users');
// すべてのユーザーをストリーム
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// 単一のユーザーを取得
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// 単一の値を入手
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// 単一の列を取得
$ids = Flight::db()->fetchColumn('SELECT id FROM users');
// キー-バリューペアを取得
$userNames = Flight::db()->fetchPairs('SELECT id, name FROM users');
// 特別な IN() 構文
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
// 新しいユーザーを挿入
$id = Flight::db()->insert('users', [
'name' => 'Bob',
'email' => 'bob@example.com'
]);
// ユーザーを一括挿入
Flight::db()->insert('users', [
['name' => 'Bob', 'email' => 'bob@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com']
]);
// ユーザーを更新
$affected = Flight::db()->update('users', ['name' => 'Bob'], 'id = ?', [123]);
// ユーザーを削除
$deleted = Flight::db()->delete('users', 'id = ?', [123]);
// トランザクションを使用
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John', 'email' => 'john@example.com']);
$db->insert('audit_log', ['action' => 'user_created']);
return $db->lastInsertId();
});
});
PdoWrapper からの移行
現在 PdoWrapper を使用している場合、SimplePdo への移行は簡単です:
-
登録を更新:
// 旧 Flight::register('db', \flight\database\PdoWrapper::class, [ /* ... */ ]); // 新 Flight::register('db', \flight\database\SimplePdo::class, [ /* ... */ ]); -
すべての既存の
PdoWrapperメソッドがSimplePdoで動作 - 破壊的な変更はありません。既存のコードは引き続き動作します。 -
新しいヘルパーメソッドをオプションで使用 -
insert()、update()、delete()、transaction()を使用してコードを簡素化します。
関連項目
- Collections - 簡単なデータアクセスに Collection クラスを使用する方法を学びます。
- PdoWrapper - レガシー PDO ヘルパークラス (非推奨)。
トラブルシューティング
- データベース接続に関するエラーが発生した場合、DSN、ユーザー名、パスワード、オプションを確認してください。
- すべての行は Collections として返されます - プレーン配列が必要な場合、
$collection->getData()を使用してください。 IN (?)クエリの場合、配列を渡すことを確認してください。- 長時間実行プロセスでクエリログによるメモリの問題が発生している場合、
maxQueryMetricsオプションを調整してください。
変更履歴
- v3.18.0 - insert、update、delete、およびトランザクションのためのヘルパーメソッド付き SimplePdo の初期リリース。
Learn/collections
コレクション
概要
FlightのCollectionクラスは、データセットを管理するための便利なユーティリティです。配列表記とオブジェクト表記の両方でデータにアクセス・操作できるため、コードがよりクリーンで柔軟になります。
理解
Collectionは基本的に配列のラッパーですが、いくつかの追加機能があります。配列のように使用したり、ループしたり、アイテム数を数えたり、アイテムをオブジェクトプロパティのようにアクセスしたりできます。これは、アプリ内で構造化データを渡したい場合や、コードを少し読みやすくしたい場合に特に便利です。
コレクションはいくつかのPHPインターフェースを実装しています:
ArrayAccess(配列構文を使用できます)Iterator(foreachでループできます)Countable(count()を使用できます)JsonSerializable(簡単にJSONに変換できます)
基本的な使い方
コレクションの作成
コンストラクタに配列を渡すだけで、コレクションを作成できます:
use flight\util\Collection;
$data = [
'name' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$collection = new Collection($data);
アイテムへのアクセス
配列表記またはオブジェクト表記のどちらでもアイテムにアクセスできます:
// 配列表記
echo $collection['name']; // 出力: FlightPHP
// オブジェクト表記
echo $collection->version; // 出力: 3
存在しないキーにアクセスしようとすると、エラーではなくnullが返ります。
アイテムの設定
アイテムの設定も、どちらの表記でも行えます:
// 配列表記
$collection['author'] = 'Mike Cao';
// オブジェクト表記
$collection->license = 'MIT';
アイテムの確認と削除
アイテムが存在するか確認する:
if (isset($collection['name'])) {
// 何かを行う
}
if (isset($collection->version)) {
// 何かを行う
}
アイテムを削除する:
unset($collection['author']);
unset($collection->license);
コレクションの反復処理
コレクションは反復可能なので、foreachループで使用できます:
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
アイテム数のカウント
コレクション内のアイテム数を数えることができます:
echo count($collection); // 出力: 4
すべてのキーまたはデータを取得
すべてのキーを取得:
$keys = $collection->keys(); // ['name', 'version', 'features', 'license']
すべてのデータを配列として取得:
$data = $collection->getData();
コレクションのクリア
すべてのアイテムを削除:
$collection->clear();
JSONシリアライズ
コレクションは簡単にJSONに変換できます:
echo json_encode($collection);
// 出力: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}
高度な使い方
必要に応じて、内部のデータ配列を完全に置き換えることができます:
$collection->setData(['foo' => 'bar']);
コレクションは、コンポーネント間で構造化データを渡したい場合や、配列データに対してよりオブジェクト指向のインターフェースを提供したい場合に特に便利です。
関連項目
- Requests - HTTPリクエストの処理方法と、リクエストデータを管理するためにコレクションをどのように使用できるかについて学びます。
- SimplePdo - クエリ結果の行をコレクションとして返すデータベースヘルパーです。
トラブルシューティング
- 存在しないキーにアクセスしようとすると、エラーではなく
nullが返ります。 - コレクションは再帰的ではないことに注意してください。ネストされた配列は自動的にコレクションには変換されません。
- コレクションをリセットする必要がある場合は、
$collection->clear()または$collection->setData([])を使用します。
変更履歴
- v3.0 - 型ヒントの改善とPHP 8+サポート。
- v1.0 - Collectionクラスの初期リリース。
Learn/flight_vs_fat_free
FlightとFat-Freeの比較
Fat-Freeとは?
Fat-Free(通称 F3)は、動的で堅牢なWebアプリケーションを迅速に構築するために設計された、強力でありながら使いやすいPHPマイクロフレームワークです。
Flightは、多くの点でFat-Freeと比較でき、おそらく機能とシンプルさの面で最も近い親戚です。Fat-FreeにはFlightにはない機能が多くありますが、Flightにある機能も多く持っています。Fat-Freeは時代を感じさせ始めており、かつてほど人気はありません。
アップデートの頻度は減り、コミュニティも以前ほど活発ではありません。コード自体はシンプルですが、構文の規律に欠けるため、読み解くのが難しいこともあります。PHP 8.3では動作しますが、コード自体はまだPHP 5.3時代のものに見えます。
Flightと比較した場合の利点
- Fat-FreeはGitHubでFlightよりも少し多くのスターを獲得しています。
- Fat-Freeにはそれなりのドキュメントがありますが、明確さに欠ける部分がいくつかあります。
- Fat-Freeには、フレームワークを学ぶために利用できるYouTubeチュートリアルやオンライン記事などの資料がいくつかあります。
- Fat-Freeには、役立つことがあるプラグインが組み込まれています。
- Fat-Freeには、データベースと対話するために使用できるMapperと呼ばれるORMが組み込まれています。Flightにはactive-recordがあります。
- Fat-Freeには、セッション、キャッシュ、ローカライゼーションが組み込まれています。Flightではサードパーティライブラリが必要ですが、ドキュメントでカバーされています。
- Fat-Freeには、フレームワークを拡張するために使用できるコミュニティ作成プラグインの小さなグループがあります。Flightにはドキュメントと例のページでカバーされているものがあります。
- Fat-FreeはFlightと同様に依存関係がありません。
- Fat-FreeはFlightと同様に、開発者にアプリケーションの制御を委ね、シンプルな開発者体験を提供することに重点を置いています。
- Fat-FreeはFlightと同様に後方互換性を維持しています(部分的にはアップデートが頻度が減っているためです)。
- Fat-FreeはFlightと同様に、初めてフレームワークの世界に足を踏み入れる開発者を対象としています。
- Fat-Freeには、Flightのテンプレートエンジンよりも堅牢な組み込みテンプレートエンジンがあります。Flightではこれを実現するためにLatteを推奨しています。
- Fat-Freeには独自のCLIタイプの「ルート」コマンドがあり、Fat-Free自体の中でCLIアプリケーションを構築し、それを
GETリクエストのように扱うことができます。Flightはこれをrunwayで実現しています。
Flightと比較した場合の欠点
- Fat-Freeにはいくつかの実装テストがあり、非常に基本的な独自のテストクラスもあります。しかし、Flightのように100%ユニットテストされているわけではありません。
- ドキュメントサイトを実際に検索するには、Googleなどの検索エンジンを使う必要があります。
- Flightのドキュメントサイトにはダークモードがあります。(マイクドロップ)
- Fat-Freeには、残念ながらメンテナンスされていないモジュールがいくつかあります。
- FlightにはデータベースアクセスのためのSimplePdoがあり、Fat-Freeの組み込み
DB\SQLクラスよりも少しシンプルです(そして非推奨のPdoWrapperよりも推奨されます)。 - Flightには、アプリケーションを保護するために使用できる権限プラグインがあります。Fat-Freeではサードパーティライブラリを使用する必要があります。
- Flightにはactive-recordと呼ばれるORMがあり、Fat-FreeのMapperよりもORMらしく感じられます。
active-recordの追加の利点は、レコード間のリレーションシップを定義して自動的に結合できることです。一方、Fat-FreeのMapperでは SQLビューを作成する必要があります。 - 驚くべきことに、Fat-Freeにはルート名前空間がありません。Flightは独自のコードと衝突しないように、完全に名前空間が設定されています。
ここで最大の問題は
Cacheクラスです。 - Fat-Freeにはミドルウェアがありません。代わりに、コントローラーでリクエストとレスポンスをフィルタリングするために使用できる
beforerouteとafterrouteフックがあります。 - Fat-Freeはルートをグループ化できません。
- Fat-Freeには依存性注入コンテナのハンドラーがありますが、その使用方法に関するドキュメントは非常に乏しいです。
- 基本的にすべてが
HIVEと呼ばれるものに格納されるため、デバッグが少し難しくなることがあります。
Learn/extending
拡張
概要
Flight は拡張可能なフレームワークとして設計されています。フレームワークにはデフォルトのメソッドとコンポーネントのセットが付属していますが、ご自身のメソッドをマップしたり、ご自身のクラスを登録したり、既存のクラスやメソッドをオーバーライドしたりすることが可能です。
理解
Flight の機能を拡張する方法は 2 つあります:
- メソッドのマッピング - アプリケーション内のどこからでも呼び出せるシンプルなカスタムメソッドを作成するために使用されます。これらは、コード内のどこからでも呼び出したいユーティリティ関数に通常使用されます。
- クラスの登録 - Flight にご自身のクラスを登録するために使用されます。これは、依存関係があるクラスや設定を必要とするクラスに通常使用されます。
プロジェクトのニーズに合わせてデフォルトの動作を変更するために、既存のフレームワークメソッドをオーバーライドすることも可能です。
DIC(Dependency Injection Container)をお探しの場合、Dependency Injection Container ページに移動してください。
基本的な使用方法
フレームワークメソッドのオーバーライド
Flight は、コードを変更せずにご自身のニーズに合わせてデフォルトの機能をオーバーライドすることを許可します。オーバーライド可能なすべてのメソッドは 以下 をご覧ください。
たとえば、Flight が URL をルートにマッチングできない場合、notFound メソッドを呼び出して一般的な HTTP 404 レスポンスを送信します。この動作を map メソッドを使用してオーバーライドできます:
Flight::map('notFound', function() {
// カスタム 404 ページを表示
include 'errors/404.html';
});
Flight はフレームワークのコアコンポーネントを置き換えることも許可します。 たとえば、デフォルトの Router クラスを独自のカスタムクラスに置き換えることができます:
// カスタム Router クラスを作成
class MyRouter extends \flight\net\Router {
// ここでメソッドをオーバーライド
// たとえば、GET リクエストのショートカットで
// pass route 機能を削除
public function get($pattern, $callback, $alias = '') {
return parent::get($pattern, $callback, false, $alias);
}
}
// カスタムクラスを登録
Flight::register('router', MyRouter::class);
// Flight が Router インスタンスをロードするとき、ご自身のクラスがロードされます
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
echo "Hello World!";
}, 'hello_alias');
ただし、map や register などのフレームワークメソッドはオーバーライドできません。これを試みるとエラーが発生します(リストについては 以下 をご覧ください)。
マッピング可能なフレームワークメソッド
以下はフレームワークの完全なメソッドセットです。コアメソッド(通常の静的メソッド)と拡張可能メソッド(フィルタリングやオーバーライドが可能なマップされたメソッド)で構成されています。
コアメソッド
これらのメソッドはフレームワークのコアであり、オーバーライドできません。
Flight::map(string $name, callable $callback, bool $pass_route = false) // カスタムフレームワークメソッドを作成。
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // フレームワークメソッドにクラスを登録。
Flight::unregister(string $name) // フレームワークメソッドからクラスを登録解除。
Flight::before(string $name, callable $callback) // フレームワークメソッドの前にフィルタを追加。
Flight::after(string $name, callable $callback) // フレームワークメソッドの後にフィルタを追加。
Flight::path(string $path) // クラスの自動ロードのためのパスを追加。
Flight::get(string $key) // Flight::set() で設定された変数を取得。
Flight::set(string $key, mixed $value) // Flight エンジン内で変数を設定。
Flight::has(string $key) // 変数が設定されているかをチェック。
Flight::clear(array|string $key = []) // 変数をクリア。
Flight::init() // フレームワークをデフォルト設定に初期化。
Flight::app() // アプリケーションオブジェクトインスタンスを取得
Flight::request() // リクエストオブジェクトインスタンスを取得
Flight::response() // レスポンスオブジェクトインスタンスを取得
Flight::router() // ルーターオブジェクトインスタンスを取得
Flight::view() // ビューオブジェクトインスタンスを取得
拡張可能メソッド
Flight::start() // フレームワークを開始。
Flight::stop() // フレームワークを停止し、レスポンスを送信。
Flight::halt(int $code = 200, string $message = '') // オプションのステータスコードとメッセージでフレームワークを停止。
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // URL パターンをコールバックにマップ。
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // POST リクエスト URL パターンをコールバックにマップ。
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // PUT リクエスト URL パターンをコールバックにマップ。
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // PATCH リクエスト URL パターンをコールバックにマップ。
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // DELETE リクエスト URL パターンをコールバックにマップ。
Flight::group(string $pattern, callable $callback) // URL のグループを作成、パターンは文字列である必要があります。
Flight::getUrl(string $name, array $params = []) // ルートエイリアスに基づいて URL を生成。
Flight::redirect(string $url, int $code) // 別の URL にリダイレクト。
Flight::download(string $filePath) // ファイルをダウンロード。
Flight::render(string $file, array $data, ?string $key = null) // テンプレートファイルをレンダリング。
Flight::error(Throwable $error) // HTTP 500 レスポンスを送信。
Flight::notFound() // HTTP 404 レスポンスを送信。
Flight::etag(string $id, string $type = 'string') // ETag HTTP キャッシングを実行。
Flight::lastModified(int $time) // 最終更新日時 HTTP キャッシングを実行。
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSON レスポンスを送信。
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSONP レスポンスを送信。
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // JSON レスポンスを送信し、フレームワークを停止。
Flight::onEvent(string $event, callable $callback) // イベントリスナーを登録。
Flight::triggerEvent(string $event, ...$args) // イベントをトリガー。
map や register で追加したカスタムメソッドもフィルタリング可能です。これらのメソッドをフィルタリングする方法の例については、Filtering Methods ガイドを参照してください。
拡張可能なフレームワーククラス
拡張してご自身のクラスを登録することで、機能のオーバーライドが可能なクラスがいくつかあります。これらのクラスは:
Flight::app() // アプリケーションクラス - flight\Engine クラスを拡張
Flight::request() // リクエストクラス - flight\net\Request クラスを拡張
Flight::response() // レスポンスクラス - flight\net\Response クラスを拡張
Flight::router() // ルータークラス - flight\net\Router クラスを拡張
Flight::view() // ビュークラス - flight\template\View クラスを拡張
Flight::eventDispatcher() // イベントディスパッチャークラス - flight\core\Dispatcher クラスを拡張
カスタムメソッドのマッピング
シンプルなカスタムメソッドをマップするには、map 関数を使用します:
// メソッドをマップ
Flight::map('hello', function (string $name) {
echo "hello $name!";
});
// カスタムメソッドを呼び出し
Flight::hello('Bob');
シンプルなカスタムメソッドを作成することは可能ですが、PHP で標準関数を作成することを推奨します。これにより IDE でオートコンプリートが可能で、読みやすくなります。上記のコードの同等例は:
function hello(string $name) {
echo "hello $name!";
}
hello('Bob');
これは、メソッドに変数を渡して期待される値を取得する必要がある場合に使用されます。以下のように register() メソッドを使用するのは、設定を渡して事前設定されたクラスを呼び出す場合に適しています。
カスタムクラスの登録
ご自身のクラスを登録して設定するには、register 関数を使用します。map() よりも利点は、この関数を呼び出すたびに同じクラスを再利用できることです(Flight::db() で同じインスタンスを共有するのに役立ちます)。
// クラスを登録
Flight::register('user', User::class);
// クラスのインスタンスを取得
$user = Flight::user();
register メソッドは、クラスコンストラクタにパラメータを渡すことも許可します。カスタムクラスをロードすると、事前初期化された状態になります。コンストラクタパラメータは追加の配列を渡すことで定義できます。データベース接続をロードする例:
// コンストラクタパラメータでクラスを登録
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);
// クラスのインスタンスを取得
// これは定義されたパラメータでオブジェクトを作成します
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();
// コードの後で必要になった場合、同じメソッドを再度呼び出すだけです
class SomeController {
public function __construct() {
$this->db = Flight::db();
}
}
追加のコールバックパラメータを渡すと、クラス構築直後に実行されます。これにより、新しいオブジェクトのセットアップ手順を実行できます。コールバック関数は、新しいオブジェクトのインスタンスを 1 つのパラメータとして受け取ります。
// コールバックは構築されたオブジェクトが渡されます
Flight::register(
'db',
PDO::class,
['mysql:host=localhost;dbname=test', 'user', 'pass'],
function (PDO $db) {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
);
デフォルトでは、クラスをロードするたびに共有インスタンスが取得されます。クラスの新しいインスタンスを取得するには、単に false をパラメータとして渡します:
// クラスの共有インスタンス
$shared = Flight::db();
// クラスの新しいインスタンス
$new = Flight::db(false);
注意: マップされたメソッドは登録されたクラスよりも優先されます。同じ名前で両方を宣言した場合、マップされたメソッドのみが呼び出されます。
例
コアに組み込まれていない機能で Flight を拡張する方法の例をいくつか示します。
ログ
Flight には組み込みのログシステムはありませんが、Flight でログライブラリを使用するのは非常に簡単です。Monolog ライブラリを使用した例:
// services.php
// Flight にロガーを登録
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});
登録したら、アプリケーションで使用できます:
// コントローラーやルート内で
Flight::log()->warning('This is a warning message');
これにより、指定したログファイルにメッセージがログされます。エラーが発生したときに何かをログしたい場合、error メソッドを使用できます:
// コントローラーやルート内で
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// カスタムエラーページを表示
include 'errors/500.html';
});
before と after メソッドを使用して基本的な APM(Application Performance Monitoring)システムを作成することもできます:
// services.php ファイル内で
Flight::before('start', function() {
Flight::set('start_time', microtime(true));
});
Flight::after('start', function() {
$end = microtime(true);
$start = Flight::get('start_time');
Flight::log()->info('Request '.Flight::request()->url.' took ' . round($end - $start, 4) . ' seconds');
// リクエストやレスポンスヘッダーを追加してログすることも可能
// (リクエストが多い場合、データ量が多いので注意)
Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});
キャッシング
Flight には組み込みのキャッシングシステムはありませんが、Flight でキャッシングライブラリを使用するのは非常に簡単です。PHP File Cache ライブラリを使用した例:
// services.php
// Flight にキャッシュを登録
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
$cache->setDevMode(ENVIRONMENT === 'development');
});
登録したら、アプリケーションで使用できます:
// コントローラーやルート内で
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
// データ取得のための処理を実行
$data = [ 'some' => 'data' ];
Flight::cache()->set('my_cache_key', $data, 3600); // 1 時間キャッシュ
}
簡単な DIC オブジェクトインスタンス化
アプリケーションで DIC(Dependency Injection Container)を使用している場合、Flight を使用してオブジェクトをインスタンス化できます。Dice ライブラリを使用した例:
// services.php
// 新しいコンテナを作成
$container = new \Dice\Dice;
// 以下のように自身に再割り当てすることを忘れずに!
$container = $container->addRule('PDO', [
// shared は同じオブジェクトが毎回返されることを意味
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// 任意のオブジェクトを作成するためのマッピング可能メソッドを作成
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// これはコントローラー/ミドルウェアで使用するためのコンテナハンドラを登録
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// コンストラクタで PDO オブジェクトを受け取るサンプルクラスがあると仮定
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// メール送信コード
}
}
// 最後に、依存注入を使用してオブジェクトを作成
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
かっこいいでしょう?
関連項目
- Dependency Injection Container - Flight で DIC を使用する方法。
- File Cache - Flight でキャッシングライブラリを使用する例。
トラブルシューティング
- マップされたメソッドは登録されたクラスよりも優先されます。同じ名前で両方を宣言した場合、マップされたメソッドのみが呼び出されます。
変更履歴
- v2.0 - 初回リリース。
Learn/json
JSON Wrapper
Overview
FlightのJsonクラスは、アプリケーションでJSONデータをエンコードおよびデコードするためのシンプルで一貫した方法を提供します。PHPのネイティブJSON関数をより良いエラー処理と便利なデフォルト値でラップしており、JSONの使用をより簡単で安全にします。
Understanding
JSONの使用は、現代のPHPアプリケーションで非常に一般的です。特にAPIの構築やAJAXリクエストの処理時にそうです。Jsonクラスは、すべてのJSONエンコードとデコードを一元化するため、PHPの組み込み関数からの奇妙なエッジケースや暗号めいたエラーについて心配する必要がありません。
主な機能:
- 一貫したエラー処理(失敗時に例外をスロー)
- エンコード/デコードのデフォルトオプション(例: 未エスケープのスラッシュ)
- プリティプリントと検証のためのユーティリティメソッド
Basic Usage
データのJSONエンコード
PHPデータをJSON文字列に変換するには、Json::encode()を使用します:
use flight\util\Json;
$data = [
'framework' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$json = Json::encode($data);
echo $json;
// Output: {"framework":"Flight","version":3,"features":["routing","views","extending"]}
エンコードが失敗した場合、役立つエラーメッセージ付きの例外が発生します。
プリティプリント
JSONを人間が読みやすい形式にしたいですか? prettyPrint()を使用します:
echo Json::prettyPrint($data);
/*
{
"framework": "Flight",
"version": 3,
"features": [
"routing",
"views",
"extending"
]
}
*/
JSON文字列のデコード
JSON文字列をPHPデータに戻すには、Json::decode()を使用します:
$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Output: Flight
オブジェクトではなく連想配列が欲しい場合、2番目の引数にtrueを渡します:
$data = Json::decode($json, true);
echo $data['framework']; // Output: Flight
デコードが失敗した場合、明確なエラーメッセージ付きの例外が発生します。
JSONの検証
文字列が有効なJSONかどうかをチェックします:
if (Json::isValid($json)) {
// 有効です!
} else {
// 有効なJSONではありません
}
最後のエラーの取得
ネイティブPHP関数からの最後のJSONエラーメッセージを確認したい場合:
$error = Json::getLastError();
if ($error !== '') {
echo "Last JSON error: $error";
}
Advanced Usage
より多くの制御が必要な場合、エンコードとデコードのオプションをカスタマイズできます(PHPのjson_encodeオプションを参照):
// HEX_TAGオプションでエンコード
$json = Json::encode($data, JSON_HEX_TAG);
// カスタム深さでデコード
$data = Json::decode($json, false, 1024);
See Also
- Collections - JSONに簡単に変換できる構造化データとの作業用。
- Configuration - Flightアプリの設定方法。
- Extending - 独自のユーティリティを追加したり、コアクラスをオーバーライドしたりする方法。
Troubleshooting
- エンコードまたはデコードが失敗した場合、例外がスローされます。エラーを優雅に処理したい場合は、呼び出しをtry/catchでラップしてください。
- 予期しない結果が得られた場合、データに循環参照や非UTF8文字がないかを確認してください。
- デコード前に
Json::isValid()を使用して文字列が有効なJSONかをチェックしてください。
Changelog
- v3.16.0 - JSONラッパーユーティリティクラスを追加。
Learn/flight_vs_slim
Flight vs Slim
Slimとは?
Slim は、シンプルでありながら強力なWebアプリケーションやAPIをすばやく作成するのに役立つPHPマイクロフレームワークです。
Flight の v3 機能の多くは、実際には Slim から着想を得ています。ルートのグループ化と、特定の順序でミドルウェアを実行することは、Slim に触発された2つの機能です。Slim v3 はシンプルさを重視して登場しましたが、v4 については賛否両論があります。
Flight と比べた長所
- Slim は開発者のコミュニティが大きいため、車輪の再発明を避けるのに役立つ便利なモジュールが多数あります。
- Slim は PHP コミュニティで一般的な多くのインターフェースと標準に準拠しており、相互運用性が高まります。
- Slim には、フレームワークを学習するために使用できる十分なドキュメントとチュートリアルがあります(ただし、Laravel や Symfony には及びません)。
- Slim には、フレームワークを学習するために使用できる YouTube チュートリアルやオンライン記事などのさまざまなリソースがあります。
- Slim は PSR-7 準拠であるため、コアルーティング機能を処理するために任意のコンポーネントを使用できます。
Flight と比べた短所
- 驚くべきことに、Slim はマイクロフレームワークとして想像するほど高速ではありません。詳細については、TechEmpower ベンチマーク を参照してください。
- Flight は、軽量で高速かつ使いやすい Web アプリケーションを構築したい開発者を対象としています。
- Flight には依存関係がありませんが、Slim にはインストールしなければならない依存関係がいくつかあります。
- Flight はシンプルさと使いやすさを重視しています。
- Flight のコア機能の 1 つは、後方互換性を維持するために最善を尽くすことです。Slim v3 から v4 への移行は破壊的な変更でした。
- Flight は、初めてフレームワークの世界に足を踏み入れる開発者を対象としています。
- Flight はエンタープライズレベルのアプリケーションにも対応できますが、Slim ほど多くの例やチュートリアルはありません。また、物事を整理整頓された構造に保つために、開発者側により多くの規律が求められます。
- Flight は開発者にアプリケーションのより多くの制御を提供しますが、Slim は舞台裏で魔法をこっそりと忍び込ませることができます。
- Flight にはデータベースアクセスのための SimplePdo があります(非推奨の PdoWrapper よりも推奨されます)。Slim ではサードパーティのライブラリを使用する必要があります。
- Flight には、アプリケーションを保護するために使用できる permissions プラグイン があります。Slim ではサードパーティのライブラリを使用する必要があります。
- Flight には、データベースと対話するために使用できる active-record という ORM があります。Slim ではサードパーティのライブラリを使用する必要があります。
- Flight には、コマンドラインからアプリケーションを実行するために使用できる runway という CLI アプリケーションがあります。Slim にはありません。
Learn/autoloading
オートローディング
概要
オートローディングとは、PHPにおいてクラスを読み込むディレクトリを指定する概念です。require や include を使ってクラスを読み込むよりもはるかに有益です。また、Composerパッケージを使用するための要件でもあります。
オートローディングを正しく設定することは、AI支援開発 にとっても重要です。エージェントは名前空間が示す場所にファイルを配置するからです。フォルダの大文字小文字と名前空間の大文字小文字が一致しない場合、大文字小文字を区別しないMacのディスク上では「動いていた」としても、Linuxではクラスが見つからないエラーが発生します。
理解
デフォルトでは、あらゆる Flight クラスはComposerのおかげで自動的にオートロードされます。あなた自身のアプリケーションクラスについては、一般的な2つの方法があります。
- Composer PSR-4(公式スケルトン が使用):
composer.jsonで名前空間のプレフィックスをディレクトリにマッピングし、composer dump-autoloadを実行します。 Flight::path(): Flightのローダーにディレクトリを指定します(シンプルなアプリや、アプリコードにComposerを使わない場合に便利です)。
オートローダーを使うとコードが大幅に簡素化されます。毎回ファイルの先頭に大量の include / require を並べる代わりに、クラスを最初に使用したときに読み込まれます。
大文字小文字の区別(2回読んでください)
名前空間はディレクトリ構造と、そのディレクトリの大文字小文字の両方に一致している必要があります。
| 動作する | Linuxで壊れる |
|---|---|
App\Controller\HomeController → app/Controller/HomeController.php |
App\Controller\… でフォルダが app/controllers/ の場合 |
app\controllers\MyController → app/controllers/MyController.php |
App\ と小文字の controllers を混在させる |
PHPの名前空間は一部の文脈では大文字小文字を区別しませんが、Composerとファイルシステムは区別します。公式スケルトンは次のように統一しています。
- Composer:
"App\\": "app/" - フォルダ:
Controller、Middleware、Model、Utils(パスカルケース)。controllers/middlewaresではありません
古いドキュメントやコミュニティの例では、小文字の app\controllers が使われることがありました。フォルダが小文字であればそれでも機能します。ただし新しいスケルトンプロジェクトでは App\ + パスカルケースのフォルダを使用します。プロジェクトごとに1つの規約を選んでそれを守り、人間とAIツールが別のレイアウトを作り出さないようにしてください。
スケルトン(新規プロジェクト向け推奨)
composer create-project flightphp/skeleton の後、アプリコードはComposer経由でオートロードされます。App\ クラスに対して Flight::path() は不要です。
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
// app/Controller/HomeController.php
namespace App\Controller;
use flight\Engine;
class HomeController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
$this->app->render('welcome', ['message' => 'Hello!']);
}
}
// app/config/routes.php — Diceがコンテナ経由でApp\Controller\… を解決します
$router->get('/', [HomeController::class, 'index']);
完全なツリーは インストール を、コーディング支援ツール向けにこのレイアウトを AGENTS.md がどう文書化するかは AIと開発者体験 を参照してください。
基本的な使用方法(Flight::path())
次のようなディレクトリツリーがあると仮定します。
# パスの例
/home/user/project/my-flight-project/
├── app
│ ├── cache
│ ├── config
│ ├── controllers - このプロジェクトのコントローラを格納
│ ├── translations
│ ├── UTILS - このアプリケーション専用のクラスを格納(後で例を示すため意図的にすべて大文字)
│ └── views
└── public
└── css
└── js
└── index.php
これは典型的なアプリツリーに似ていることに気付いたかもしれません(ドキュメントサイト自体も構造化レイアウトを使用しています)。ここで小文字の controllers は有効な選択肢です。ただ、スケルトンの現在のデフォルトではありません。
各ディレクトリを次のように指定して読み込むことができます。
/**
* public/index.php
*/
// オートローダーにパスを追加
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
/**
* app/controllers/MyController.php
*/
// 名前空間は不要
// オートロードされるすべてのクラスはパスカルケース(各単語の先頭を大文字にし、スペースを入れない)を推奨
class MyController {
public function index() {
// 何かを行う
}
}
Flight::path() で名前空間を使う
名前空間を使う場合、実装は非常に簡単になります。Flight::path() メソッドで、アプリケーションのルートディレクトリ(ドキュメントルートや public/ フォルダではなく)を指定する必要があります。
/**
* public/index.php
*/
// オートローダーにパスを追加
Flight::path(__DIR__.'/../');
これで、コントローラは次のようになります。下の例を見てください。ただし、重要な情報はコメントに注目してください。
/**
* app/controllers/MyController.php
*/
// 名前空間は必須
// 名前空間はディレクトリ構造と同じ
// 名前空間はディレクトリ構造と同じ大文字小文字に従う必要がある
// 名前空間とディレクトリにはアンダースコアを含めることはできない(Loader::setV2ClassLoading(false) が設定されている場合を除く)
namespace app\controllers;
// オートロードされるすべてのクラスはパスカルケース(各単語の先頭を大文字にし、スペースを入れない)を推奨
// 3.7.2以降、Loader::setV2ClassLoading(false); を実行することでクラス名にPascal_Snake_Caseを使用できます
class MyController {
public function index() {
// 何かを行う
}
}
そして、utilsディレクトリ内のクラスをオートロードしたい場合は、基本的に同じことを行います。
/**
* app/UTILS/ArrayHelperUtil.php
*/
// 名前空間はディレクトリ構造と大文字小文字に一致させる必要がある(上記のファイルツリーのようにUTILSディレクトリがすべて大文字であることに注意)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// 何かを行う
}
}
スケルトンスタイルの名前空間(同じルール、異なる大文字小文字)
/**
* app/Controller/MyController.php
*/
namespace App\Controller;
class MyController {
// ...
}
ルールは変わりません。スケルトンが選んだフォルダ/名前空間の大文字小文字が変わるだけです。フォルダがどの大文字小文字でも、namespace 行はそれに一致させる必要があります。
クラス名のアンダースコア
3.7.2以降、Loader::setV2ClassLoading(false); を実行することでクラス名にPascal_Snake_Caseを使用できます。これにより、クラス名にアンダースコアを使用できるようになります。推奨はされませんが、必要とする人のために利用可能です。
use flight\core\Loader;
/**
* public/index.php
*/
// オートローダーにパスを追加
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);
/**
* app/controllers/My_Controller.php
*/
// 名前空間は不要
class My_Controller {
public function index() {
// 何かを行う
}
}
関連情報
- インストール - スケルトンツリーと新規プロジェクト向けの
App\デフォルト。 - ルーティング - ルートをコントローラにマッピングしてビューをレンダリングする方法。
- 依存性注入 - コントローラが
Engineやサービスを取得する方法。 - AIと開発者体験 -
AGENTS.mdを使ってエージェントをレイアウトに合わせる方法。 - なぜフレームワークを使うのか? - Flightのようなフレームワークを使う利点を理解する。
トラブルシューティング
- 名前空間付きクラスが見つからない理由がどうしても分からない場合は、
Flight::path()ではプロジェクトルート(または名前空間の正しいベース)を指すようにしてください。名前空間に反映し忘れた入れ子フォルダだけを指すのは避けましょう。 - Composer PSR-4の場合、
composer.jsonのマッピングを変更した後にcomposer dump-autoloadを実行してください。 - LinuxのCIや本番環境では、フォルダの大文字小文字の誤りが「自分の環境では動くのに」という失敗の非常に一般的な原因です。
クラスが見つからない(オートローディングが機能していない)
これが発生する理由はいくつか考えられます。以下に例を示します。
ファイル名の誤り
最も一般的なのは、クラス名がファイル名と一致しないことです。
MyClass というクラスがある場合、ファイル名は MyClass.php でなければなりません。MyClass というクラスなのにファイル名が myclass.php の場合、オートローダーはそれを見つけることができません。
名前空間またはフォルダの大文字小文字の誤り
名前空間を使用している場合、名前空間はディレクトリ構造に大文字小文字も含めて一致する必要があります。
// ...コード...
// MyControllerが app/Controller(スケルトン)にあり、名前空間が App\Controller の場合
// これは機能しません:
Flight::route('/hello', 'MyController->hello');
// スケルトンスタイル:
use App\Controller\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// 古い小文字レイアウト(フォルダが実際に app/controllers の場合のみ):
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// または完全修飾名:
Flight::route('/hello', [ 'App\Controller\MyController', 'hello' ]);
path() が定義されていない(Composer以外のアプリコード)
アプリケーションクラスにComposerではなく Flight::path() を頼る場合、それらのクラスを参照するルートの前にパスを定義してください(多くの場合、ブートストラップの早い段階または public/index.php 内)。
// オートローダーにパスを追加(名前空間付きアプリの場合はプロジェクトルート)
Flight::path(__DIR__.'/../');
公式スケルトンは主に App\ に対してComposer PSR-4を使用するため、通常コントローラやモデルに Flight::path() は必要ありません。
変更履歴
- ドキュメント – スケルトンの
App\+ パスカルケースフォルダと、人間とAIツール向けの大文字小文字の落とし穴を文書化。 - v3.7.2 -
Loader::setV2ClassLoading(false);を実行することでクラス名にPascal_Snake_Caseを使用できます。 - v2.0 - オートロード機能が追加されました。
Learn/uploaded_file
アップロードされたファイルハンドラー
概要
Flight の UploadedFile クラスは、アプリケーションでファイルのアップロードを簡単かつ安全に扱うことを可能にします。PHP のファイルアップロードプロセスの詳細をラップし、ファイル情報をアクセスし、アップロードされたファイルを移動するためのシンプルでオブジェクト指向の方法を提供します。
理解
ユーザーがフォーム経由でファイルをアップロードすると、PHP は $_FILES スーパーグローバルにファイルに関する情報を格納します。Flight では、$_FILES に直接アクセスすることはほとんどありません。代わりに、Flight の Request オブジェクト(Flight::request() 経由でアクセス可能)が getUploadedFiles() メソッドを提供し、UploadedFile オブジェクトの配列を返します。これにより、ファイルの扱いがはるかに便利で堅牢になります。
UploadedFile クラスは以下のメソッドを提供します:
- オリジナルのファイル名、MIME タイプ、サイズ、一時的な場所を取得する
- アップロードエラーをチェックする
- アップロードされたファイルを永続的な場所に移動する
このクラスは、ファイルアップロードの一般的な落とし穴(エラーの扱いやファイルの安全な移動など)を避けるのに役立ちます。
基本的な使用方法
リクエストからアップロードされたファイルにアクセスする
アップロードされたファイルにアクセスする推奨される方法は、リクエストオブジェクト経由です:
Flight::route('POST /upload', function() {
// <input type="file" name="myFile"> という名前のフォームフィールドの場合
$uploadedFiles = Flight::request()->getUploadedFiles();
$file = $uploadedFiles['myFile'];
// これで UploadedFile メソッドを使用できます
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "File uploaded successfully!";
} else {
echo "Upload failed: " . $file->getError();
}
});
複数のファイルアップロードの扱い
フォームが name="myFiles[]" を使用して複数のアップロードを行う場合、UploadedFile オブジェクトの配列が得られます:
Flight::route('POST /upload', function() {
// <input type="file" name="myFiles[]"> という名前のフォームフィールドの場合
$uploadedFiles = Flight::request()->getUploadedFiles();
foreach ($uploadedFiles['myFiles'] as $file) {
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Uploaded: " . $file->getClientFilename() . "<br>";
} else {
echo "Failed to upload: " . $file->getClientFilename() . "<br>";
}
}
});
UploadedFile インスタンスを手動で作成する
通常、UploadedFile を手動で作成することはありませんが、必要に応じて作成できます:
use flight\net\UploadedFile;
$file = new UploadedFile(
$_FILES['myfile']['name'],
$_FILES['myfile']['type'],
$_FILES['myfile']['size'],
$_FILES['myfile']['tmp_name'],
$_FILES['myfile']['error']
);
ファイル情報のアクセス
アップロードされたファイルの詳細を簡単に取得できます:
echo $file->getClientFilename(); // ユーザーのコンピューターからのオリジナルのファイル名
echo $file->getClientMediaType(); // MIME タイプ(例: image/png)
echo $file->getSize(); // バイト単位のファイルサイズ
echo $file->getTempName(); // サーバー上のテンポラリファイルパス
echo $file->getError(); // アップロードエラーコード(0 はエラーなし)
アップロードされたファイルの移動
ファイルを検証した後、永続的な場所に移動します:
try {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "File uploaded successfully!";
} catch (Exception $e) {
echo "Upload failed: " . $e->getMessage();
}
moveTo() メソッドは、何かがうまくいかない場合(アップロードエラーや権限の問題など)に例外をスローします。
アップロードエラーの扱い
アップロード中に問題が発生した場合、人間が読めるエラーメッセージを取得できます:
if ($file->getError() !== UPLOAD_ERR_OK) {
// エラーコードを使用するか、moveTo() からの例外をキャッチできます
echo "There was an error uploading the file.";
}
関連項目
- Requests - HTTP リクエストからアップロードされたファイルにアクセスする方法を学び、ファイルアップロードの例をさらに見てみましょう。
- Configuration - PHP でアップロード制限とディレクトリを設定する方法。
- Extending - Flight のコアクラスをカスタマイズまたは拡張する方法。
トラブルシューティング
- ファイルを移動する前に常に
$file->getError()をチェックしてください。 - アップロードディレクトリがウェブサーバーによって書き込み可能であることを確認してください。
moveTo()が失敗した場合、詳細のために例外メッセージを確認してください。- PHP の
upload_max_filesizeとpost_max_size設定はファイルアップロードを制限できます。 - 複数のファイルアップロードの場合、常に
UploadedFileオブジェクトの配列をループしてください。
変更履歴
- v3.12.0 - リクエストオブジェクトに
UploadedFileクラスを追加し、ファイルの扱いを容易にしました。
Guides/unit_testing
Flight PHP と PHPUnit によるユニットテスト
このガイドでは、PHPUnit を使った Flight PHP のユニットテストを紹介します。なぜユニットテストが重要なのか、そしてそれを実際にどう適用するのかを理解したい初心者を対象としています。単純な計算ではなく、メールの送信やレコードの保存など、アプリケーションが期待どおりに動作するかという動作のテストに焦点を当てます。まずは単純なルートハンドラから始めて、依存性注入(DI)とサードパーティサービスのモックを組み込んだ、より複雑なコントローラへと進みます。
なぜユニットテストを行うのか?
ユニットテストは、コードが期待どおりに動作することを保証し、本番環境に到達する前にバグを発見します。特に Flight では、軽量なルーティングと柔軟性により複雑な相互作用が生じる可能性があるため、これは非常に価値があります。個人開発者でもチームでも、ユニットテストはセーフティネットとして機能し、期待される動作を文書化し、後でコードを再訪したときにリグレッションを防ぎます。また、設計の改善にも役立ちます。テストしにくいコードは、クラスが過度に複雑であるか、結合度が高すぎることを示していることがよくあります。
単純な例(例:x * y = z のテスト)とは異なり、入力の検証、データの保存、メール送信などのアクションのトリガーといった、実際の世界の動作に焦点を当てます。目標は、テストを身近で有意義なものにすることです。
一般的な指針
- 動作をテストし、実装をテストしない: 内部の詳細ではなく、結果(例:「メール送信」「レコード保存」)に焦点を当てます。これにより、リファクタリングに対してテストが堅牢になります。
Flight::の使用をやめる: Flight の静的メソッドは非常に便利ですが、テストを困難にします。$app = Flight::app();から取得できる$app変数を使うことに慣れてください。$appにはFlight::と同じメソッドがすべてあります。コントローラ内などで$app->route()や$this->app->json()を引き続き使用できます。また、実際の Flight ルーターを$router = $app->router()で使い、$router->get()、$router->post()、$router->group()などを使用することもできます。ルーティング を参照してください。- テストを高速に保つ: テストが高速だと頻繁に実行できます。ユニットテストではデータベース呼び出しなどの低速な操作を避けてください。テストが遅い場合は、ユニットテストではなく統合テストを書いている可能性があります。統合テストとは、実際のデータベース、実際の HTTP 呼び出し、実際のメール送信などを行うテストです。それらには役割がありますが、遅く、不明な理由で失敗することがあるため不安定になる可能性があります。
- 説明的な名前を使う: テスト名は、テスト対象の動作を明確に説明する必要があります。これにより、可読性と保守性が向上します。
- グローバル変数を避ける:
$app->set()と$app->get()の使用を最小限にしてください。これらはグローバル状態のように機能し、すべてのテストでモックが必要になります。DI または DI コンテナを優先してください(依存性注入コンテナ を参照)。$app->map()メソッドの使用も技術的には「グローバル」であり、DI を優先して避けるべきです。テストでセッションオブジェクトをモックできるように、flightphp/session などのセッションライブラリを使用してください。コード内で直接$_SESSIONを呼び出さないでください。グローバル変数をコードに注入することになり、テストが困難になります。 - 依存性注入を使用する: 依存関係(例:
PDO、メーラー)をコントローラに注入して、ロジックを分離し、モックを簡単にします。依存関係が多すぎるクラスがある場合は、SOLID 原則に従って、それぞれが単一の責任を持つ小さなクラスにリファクタリングすることを検討してください。 - サードパーティサービスをモックする: データベース、HTTP クライアント(cURL)、メールサービスなどをモックして、外部呼び出しを避けます。1〜2層の深さをテストしますが、コアロジックは実行させてください。たとえば、アプリがテキストメッセージを送信する場合、テストを実行するたびに実際にテキストメッセージを送信したくはないはずです(コストがかさみ、遅くなるため)。代わりに、テキストメッセージサービスをモックし、コードが正しいパラメータでテキストメッセージサービスを呼び出したことを検証するだけにします。
- 高いカバレッジを目指すが、完璧を求めない: 100% の行カバレッジは良いことですが、コード内のすべてが期待どおりにテストされているとは限りません(PHPUnit での分岐・パスカバレッジ を調べてみてください)。重要な動作(例:ユーザー登録、API レスポンス、失敗したレスポンスの取得)を優先してください。
- ルートにはコントローラを使用する: ルート定義では、クロージャではなくコントローラを使用してください。
flight\Engine $appはデフォルトでコンストラクタを介してすべてのコントローラに注入されます。テストでは、$app = new Flight\Engine()を使用してテスト内で Flight をインスタンス化し、コントローラに注入して、メソッドを直接呼び出します(例:$controller->register())。Flight の拡張 と ルーティング を参照してください。 - モックのスタイルを選んで一貫させる: PHPUnit はいくつかのモックスタイル(例:prophecy、組み込みモック)をサポートしています。または、コード補完やメソッド定義を変更した場合に壊れるなどの利点がある匿名クラスを使用することもできます。テスト全体で一貫させてください。PHPUnit モックオブジェクト を参照してください。
- サブクラスでテストしたいメソッドやプロパティには
protected可視性を使用する: これにより、パブリックにせずにテスト用サブクラスでオーバーライドできます。これは特に匿名クラスモックに役立ちます。
PHPUnit のセットアップ
まず、Composer を使用して Flight PHP プロジェクトに PHPUnit をセットアップします。詳細は PHPUnit 入門ガイド を参照してください。
-
プロジェクトディレクトリで次のコマンドを実行します:
composer require --dev phpunit/phpunitこれにより、最新の PHPUnit が開発依存関係としてインストールされます。
-
プロジェクトのルートにテストファイル用の
testsディレクトリを作成します。 -
利便性のために
composer.jsonにテストスクリプトを追加します:// composer.json の他の内容 "scripts": { "test": "phpunit --configuration phpunit.xml" } -
ルートに
phpunit.xmlファイルを作成します:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
これで、テストが構築されたら、composer test を実行してテストを実行できます。
単純なルートハンドラのテスト
まず、ユーザーのメール入力を受け取る基本的なルートから始めましょう。その動作、つまり有効なメールには成功メッセージを返し、無効なメールにはエラーを返すことをテストします。メール検証には、filter_var を使用します。
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
$responseArray = [];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$responseArray = ['status' => 'error', 'message' => 'Invalid email'];
} else {
$responseArray = ['status' => 'success', 'message' => 'Valid email'];
}
$this->app->json($responseArray);
}
}
これをテストするには、テストファイルを作成します。テストの構造については、ユニットテストと SOLID 原則 を参照してください:
// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'test@example.com'; // POST データをシミュレート
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'invalid-email'; // POST データをシミュレート
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
重要なポイント:
- リクエストクラスを使用して POST データをシミュレートします。
$_POSTや$_GETなどのグローバルを使用しないでください。テストがより複雑になるためです(それらの値を常にリセットする必要があり、他のテストが失敗する可能性があります)。 - デフォルトでは、すべてのコントローラに
flight\Engineインスタンスが注入されます(DIC コンテナを設定しなくても)。これにより、コントローラを直接テストするのがはるかに簡単になります。 Flight::の使用は一切なく、コードをテストしやすくしています。- テストは、有効/無効なメールに対する正しいステータスとメッセージという動作を検証します。
composer test を実行して、ルートが期待どおりに動作することを確認します。Flight のリクエストとレスポンスの詳細については、関連ドキュメントを参照してください。
依存性注入を使用したテスト可能なコントローラ
より複雑なシナリオでは、依存性注入(DI)を使用してコントローラをテスト可能にします。Flight のグローバル(例:Flight::set()、Flight::map()、Flight::register())は、グローバル状態のように機能し、すべてのテストでモックが必要になるため避けてください。代わりに、Flight の DI コンテナ、DICE、PHP-DI、または手動 DI を使用してください。
生の PDO の代わりに flight\database\SimplePdo を使用しましょう。このヘルパーはモックやユニットテストがはるかに簡単です(そして非推奨の PdoWrapper よりも推奨されます)。
ユーザーをデータベースに保存し、ウェルカムメールを送信するコントローラは次のとおりです:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// return を置くことで、ユニットテストの実行を停止するのに役立ちます
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
重要なポイント:
- コントローラは、
SimplePdoインスタンスとMailerInterface(想定上のサードパーティメールサービス)に依存します。 - 依存関係はコンストラクタを介して注入され、グローバルを避けます。
モックを使ったコントローラのテスト
次に、UserController の動作をテストしましょう。メールの検証、データベースへの保存、メールの送信です。コントローラを分離するために、データベースとメーラーをモックします。
// tests/UserControllerDICTest.php
use flight\database\SimplePdo;
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// モックスタイルを混在させなければならない場合があります
// ここでは PHPUnit の組み込みモックを PDOStatement に使用します
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// SimplePdo をモックするための匿名クラスを使用
$mockDb = new class($statementMock) extends SimplePdo {
protected $statementMock;
public function __construct($statementMock) {
$this->statementMock = $statementMock;
}
// この方法でモックすると、実際のデータベース呼び出しは行われません。
// さらに、これを設定して PDOStatement モックを変更し、障害などをシミュレートすることもできます。
public function runQuery(string $sql, array $params = []): PDOStatement {
return $this->statementMock;
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
$this->sentEmail = $email;
return true;
}
};
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends SimplePdo {
// 空のコンストラクタは親コンストラクタをバイパスします
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('Should not be called');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('Should not be called');
}
};
$app = new Engine();
$app->request()->data->email = 'invalid-email';
// 終了を避けるために jsonHalt をマップする必要があります
$app->map('jsonHalt', function($data) use ($app) {
$app->json($data, 400);
});
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('error', $result['status']);
$this->assertEquals('Invalid email', $result['message']);
}
}
重要なポイント:
SimplePdoとMailerInterfaceをモックして、実際のデータベースやメール呼び出しを避けます。- テストは動作を検証します。有効なメールはデータベース挿入とメール送信をトリガーし、無効なメールは両方をスキップします。
- サードパーティの依存関係(例:
SimplePdo、MailerInterface)をモックし、コントローラのロジックを実行させます。
モックしすぎる
コードをモックしすぎないように注意してください。私たちの UserController を使って、これがなぜ良くないのかの例を以下に示します。そのチェックを isEmailValid(filter_var を使用)というメソッドに変更し、他の新しい追加を registerUser という別のメソッドに変更します。
use flight\database\SimplePdo;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!$this->isEmailValid($email)) {
// return を置くことで、ユニットテストの実行を停止するのに役立ちます
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'User registered']);
}
protected function isEmailValid($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
protected function registerUser($email) {
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
}
}
そして今度は、実際には何もテストしていない過剰にモックされたユニットテストです:
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
// ここでは「簡単」だからという理由で追加の依存性注入を省略しています
$controller = new class($app) extends UserControllerDICV2 {
protected $app;
// コンストラクタの依存関係をバイパス
public function __construct($app) {
$this->app = $app;
}
// これを強制的に有効にします。
protected function isEmailValid($email) {
return true; // 常に true を返し、実際の検証をバイパス
}
// 実際の DB とメーラーの呼び出しをバイパス
protected function registerUser($email) {
return false;
}
};
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
}
}
やった、ユニットテストがあって、それらはパスしています!でも待ってください。isEmailValid や registerUser の内部動作を実際に変更したらどうなるでしょうか?すべての機能をモックしてしまったので、テストはまだパスします。どういうことかお見せしましょう。
// UserControllerDICV2.php
class UserControllerDICV2 {
// ... 他のメソッド ...
protected function isEmailValid($email) {
// 変更されたロジック
$validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
// これで特定のドメインのみ許可されるようになりました
$validDomain = strpos($email, '@example.com') !== false;
return $validEmail && $validDomain;
}
}
上記のユニットテストを実行すると、それでもパスします!しかし、動作をテストしていなかった(コードの一部を実際に実行させていなかった)ため、本番環境で発生するバグをコードに埋め込んだ可能性があります。テストは新しい動作を考慮して修正する必要があり、また動作が期待どおりでない場合の逆のケースも考慮する必要があります。
完全な例
ユニットテストを含む Flight PHP プロジェクトの完全な例は、GitHub にあります: n0nag0n/flight-unit-tests-guide。より深く理解するには、ユニットテストと SOLID 原則 を参照してください。
よくある落とし穴
- 過剰なモック: すべての依存関係をモックしないでください。実際の動作をテストするために、一部のロジック(例:コントローラの検証)は実行させてください。ユニットテストと SOLID 原則 を参照してください。
- グローバル状態: PHP のグローバル変数(例:
$_SESSION、$_COOKIE)を多用すると、テストが壊れやすくなります。Flight::も同様です。依存関係を明示的に渡すようにリファクタリングしてください。 - 複雑なセットアップ: テストのセットアップが面倒な場合、クラスに依存関係や責任が多すぎて、SOLID 原則に違反している可能性があります。
ユニットテストのスケーリング
ユニットテストは、大規模なプロジェクトや数か月後にコードを再訪する際に力を発揮します。動作を文書化し、リグレッションを検出して、アプリを再学習する手間を省きます。個人開発者の場合は、重要なパス(例:ユーザー登録、支払い処理)をテストしてください。チームの場合は、テストによって貢献全体で一貫した動作が保証されます。フレームワークとテストを使用する利点の詳細については、フレームワークを使う理由は? を参照してください。
Flight PHP ドキュメントリポジトリにあなた自身のテストのヒントを貢献してください!
執筆: n0nag0n 2025
Guides/blog
Flight PHPでシンプルなブログを構築する
このガイドでは、Flight PHPフレームワークを使って基本的なブログを作成する手順を説明します。プロジェクトのセットアップ、ルートの定義、JSONでの投稿管理、Latteテンプレートエンジンでのレンダリングを行い、Flightのシンプルさと柔軟性を示します。最後には、ホームページ、個別投稿ページ、作成フォームを備えた機能的なブログが完成します。
前提条件
- PHP 7.4+: システムにインストールされていること。
- Composer: 依存関係の管理用。
- テキストエディタ: VS CodeやPHPStormなどの任意のエディタ。
- PHPとWeb開発の基本的な知識。
ステップ 1: プロジェクトのセットアップ
まず、新しいプロジェクトディレクトリを作成し、Composerを使ってFlightをインストールします。
-
ディレクトリを作成:
mkdir flight-blog cd flight-blog -
Flightをインストール:
composer require flightphp/core -
publicディレクトリを作成: Flightは単一のエントリポイント(
index.php)を使用します。そのためのpublic/フォルダを作成します:mkdir public -
基本的な
index.php:public/index.phpにシンプルな「Hello World」ルートを作成します:<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo 'Hello, Flight!'; }); Flight::start(); -
ビルトインサーバーを実行: PHPの開発サーバーでセットアップをテストします:
php -S localhost:8000 -t public/http://localhost:8000にアクセスして「Hello, Flight!」が表示されることを確認します。
ステップ 2: プロジェクト構造を整理する
クリーンなセットアップのために、プロジェクトを次のように構成します:
flight-blog/
├── app/
│ ├── config/
│ └── views/
├── data/
├── public/
│ └── index.php
├── vendor/
└── composer.json
app/config/: 設定ファイル(例: イベント、ルート)。app/views/: ページをレンダリングするためのテンプレート。data/: ブログ投稿を保存するJSONファイル。public/:index.phpを含むWebルート。
ステップ 3: Latteのインストールと設定
Latteは、Flightとよく統合する軽量なテンプレートエンジンです。
-
Latteをインストール:
composer require latte/latte -
FlightでLatteを設定:
public/index.phpを更新して、Latteをビューエンジンとして登録します:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::start(); -
レイアウトテンプレートを作成:
app/views/layout.latte:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>My Blog</h1> <nav> <a href="/">Home</a> | <a href="/create">Create a Post</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Flight Blog</p> </footer> </body> </html> -
ホームテンプレートを作成:
app/views/home.latte:{extends 'layout.latte'} {block content} <h2>{$title}</h2> <ul> {foreach $posts as $post} <li><a href="/post/{$post['slug']}">{$post['title']}</a></li> {/foreach} </ul> {/block}サーバーを終了した場合は再起動し、
http://localhost:8000にアクセスしてレンダリングされたページを確認します。 -
データファイルを作成:
簡単にするために、JSONファイルでデータベースをシミュレートします。
data/posts.json:[ { "slug": "first-post", "title": "My First Post", "content": "This is my very first blog post with Flight PHP!" } ]
ステップ 4: ルートを定義する
整理しやすくするために、ルートを設定ファイルに分離します。
-
routes.phpを作成:app/config/routes.php:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Post: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Create a Post']); }); -
index.phpを更新: ルートファイルを読み込みます:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); require '../app/config/routes.php'; Flight::start();
ステップ 5: ブログ投稿の保存と取得
投稿を読み込んで保存するメソッドを追加します。
-
投稿メソッドを追加:
index.phpで、投稿を読み込むメソッドを追加します:Flight::map('posts', function () { $file = __DIR__ . '/../data/posts.json'; return json_decode(file_get_contents($file), true); }); -
ルートを更新:
app/config/routes.phpを変更して投稿を使用します:<?php Flight::route('/', function () { $posts = Flight::posts(); Flight::view()->render('home.latte', [ 'title' => 'My Blog', 'posts' => $posts ]); }); Flight::route('/post/@slug', function ($slug) { $posts = Flight::posts(); $post = array_filter($posts, fn($p) => $p['slug'] === $slug); $post = reset($post) ?: null; if (!$post) { Flight::notFound(); return; } Flight::view()->render('post.latte', [ 'title' => $post['title'], 'post' => $post ]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Create a Post']); });
ステップ 6: テンプレートを作成する
投稿を表示するようにテンプレートを更新します。
-
投稿ページ(
app/views/post.latte):{extends 'layout.latte'} {block content} <h2>{$post['title']}</h2> <div class="post-content"> <p>{$post['content']}</p> </div> {/block}
ステップ 7: 投稿作成機能を追加する
フォーム送信を処理して新しい投稿を追加します。
-
フォームを作成(
app/views/create.latte):{extends 'layout.latte'} {block content} <h2>{$title}</h2> <form method="POST" action="/create"> <div class="form-group"> <label for="title">Title:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">Content:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">Save Post</button> </form> {/block} -
POSTルートを追加:
app/config/routes.php:Flight::route('POST /create', function () { $request = Flight::request(); $title = $request->data['title']; $content = $request->data['content']; $slug = strtolower(str_replace(' ', '-', $title)); $posts = Flight::posts(); $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content]; file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT)); Flight::redirect('/'); }); -
テストする:
http://localhost:8000/createにアクセスします。- 新しい投稿を送信します(例: 「Second Post」とコンテンツ)。
- ホームページにそれが一覧表示されることを確認します。
ステップ 8: エラーハンドリングを強化する
notFoundメソッドをオーバーライドして、より良い404エラーページを提供します。
index.php:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'Page Not Found']);
});
app/views/404.latteを作成:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>Sorry, that page doesn't exist!</p>
{/block}
次のステップ
- スタイリングを追加: テンプレートにCSSを使用して見た目を良くします。
- データベース: SimplePdoを使用して
posts.jsonをSQLiteなどのデータベースに置き換えます。 - バリデーション: 重複したスラッグや空の入力をチェックする機能を追加します。
- ミドルウェア: 投稿作成のための認証を実装します。
結論
Flight PHPでシンプルなブログを構築できました!このガイドでは、ルーティング、Latteを使ったテンプレート、フォーム送信の処理など、Flightのコア機能を軽量なまま保ちながら紹介しました。Flightのドキュメントを参照して、さらに高度な機能を使い、ブログをさらに発展させましょう!
License
The MIT License (MIT)
Copyright © 2024 @mikecao, @n0nag0n
個人が複製の許可を得ることができるように、このソフトウェアおよび関連ドキュメントファイル(以下「ソフトウェア」という)のコピーを入手することができます。 ソフトウェアを使用、コピー、変更、マージ、公開、配布、サブライセンス、販売する権利などを含む、制限なしでソフトウェアを扱う権利が、以下の条件に従って人々にそれを許可します:
上記の著作権表示およびこの許諾表示は、ソフトウェアのすべての複製または実質的な部分に含まれている必要があります。
ソフトウェアは、「現状有姿」で提供され、商品性、特定目的への適合性、および権利侵害を含むがこれに限定されない、いかなる種類の保証もなしに提供されます。 著作者または著作権保持者は、ソフトウェアまたは使用または他の取引に起因する契約上の行為、不法行為、その他の行為から生じるクレーム、損害、その他の責任について一切責任を負いません。
About
Flight PHP Framework
Flightは、PHP向けの高速でシンプル、かつ拡張可能なフレームワークです。迅速に作業を完了したい開発者向けに構築されています。クラシックなWebアプリ、高速なAPI、またはAIコーディングアシスタントとの組み合わせなど、Flightの軽量なフットプリントとシンプルな設計は完璧に適合します。Flightは軽量であることを意図していますが、エンタープライズアーキテクチャ要件にも対応できます。
なぜFlightを選ぶのか?
- 初心者フレンドリー: Flightは、新しいPHP開発者にとって素晴らしいスタートポイントです。その明確な構造とシンプルな構文により、ボイラープレートに迷うことなくWeb開発を学ぶことができます。
- プロフェッショナルに愛されている: 経験豊富な開発者は、その柔軟性と制御性を理由にFlightを愛用しています。フレームワークを切り替えることなく、小さなプロトタイプから本格的なアプリまでスケールアップできます。
- 後方互換性: 私たちはあなたの時間を大切にしています。Flight v3はv2の拡張であり、ほぼすべての同じAPIを保持しています。私たちは革命ではなく進化を信じています。メジャーバージョンが出るたびに「世界を破壊する」ことはありません。
- 依存関係ゼロ: Flightのコアは完全に依存関係がありません。ポリフィルも外部パッケージも、PSRインターフェースすらありません。これは、より少ない攻撃対象領域、より小さなフットプリント、上流依存関係からの予期しない破壊的変更がないことを意味します。オプションのプラグインには依存関係が含まれる場合がありますが、コアは常に軽量で安全に保たれます。
- AIフレンドリー: Flightの小さなAPIサーフェスと公式スケルトン(1つのレイアウト、
AGENTS.md、コンストラクタインジェクション)により、AIコーディングツールがパターンに従いやすくなっています。すべての行を入力する場合でもエージェントとペアリングする場合でも、同じコードベースを使用します。FlightでのAIの使用について詳しく学ぶ。
ビデオ概要
クイックスタート
高速なベアボーンインストールを行うには、Composerでインストールします:
composer require flightphp/core
または、リポジトリのzipをこちらからダウンロードすることもできます。次に、以下のような基本的なindex.phpファイルを作成します:
<?php
// composerでインストールした場合
require 'vendor/autoload.php';
// またはzipファイルで手動インストールした場合
// require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
});
Flight::route('/json', function() {
Flight::json([
'hello' => 'world'
]);
});
Flight::start();
これだけです!基本的なFlightアプリケーションが完成しました。これで、php -S localhost:8000でこのファイルを実行し、ブラウザでhttp://localhost:8000にアクセスして出力を確認できます。
このような短いFlight::の例は、学習やマイクロアプリに最適です。人間とAIツールが共有する完全なプロジェクトレイアウトについては、以下のスケルトンを使用してください。
スケルトン/ボイラープレートアプリ
新しいFlightプロジェクトを開始するための公式スターターがあります。構造、設定、Composerスクリプト、AIフレンドリーな指示を最初から設定します。
すぐに使えるプロジェクトについてはflightphp/skeletonを確認するか、インスピレーションを得るためにexamplesページを訪問してください。AIワークフローの詳細が必要ですか?AIと開発者体験を探る。
(高レベルで)得られるもの:
- PascalCaseフォルダーを持つ
App\名前空間(app/Controller/、app/Middleware/、app/Model/、…)—フォルダーの大文字小文字は名前空間と一致する必要があります(Autoloadingを参照) - コントローラーをテスト可能に保つためのDice +
Engineインジェクション(アプリコードではFlight::よりも$this->appを優先) - Twigビュー、SimplePdo + ActiveRecordサンプル、Runway migrate
- アシスタントとセキュリティポリシーのためのルート
AGENTS.md(およびスコープ付きコピー)とSECURITY.md
スケルトンアプリのインストール
簡単です!
# 新しいプロジェクトを作成
composer create-project flightphp/skeleton my-project/
# 新しいプロジェクトディレクトリに入る
cd my-project/
# すぐに開始するためにローカル開発サーバーを起動!
composer start
プロジェクト構造を作成し、config_sample.php → config.php(および存在する場合は.env.example → .env)をコピーします。これで準備完了です。オプションのサンプルデータ:
php runway migrate
# その後 /posts と /api/posts にアクセス
高パフォーマンス
Flightは、現在存在する最も高速なPHPフレームワークの1つです。その軽量なコアは、オーバーヘッドを減らし、速度を向上させます。伝統的なアプリと現代のAI支援ワークフローの両方に最適です。TechEmpowerで、すべてのベンチマークを確認できます。
他の人気のPHPフレームワークとのベンチマークを以下に示します。
| Framework | Plaintext Reqs/sec | JSON Reqs/sec |
|---|---|---|
| Flight | 190,421 | 182,491 |
| Yii | 145,749 | 131,434 |
| Fat-Free | 139,238 | 133,952 |
| Slim | 89,588 | 87,348 |
| Phalcon | 95,911 | 87,675 |
| Symfony | 65,053 | 63,237 |
| Lumen | 40,572 | 39,700 |
| Laravel | 26,657 | 26,901 |
| CodeIgniter | 20,628 | 19,901 |
FlightとAI
コーディングLLMとのFlightの組み合わせに興味がありますか?AGENTS.md、Runway ai:*コマンド、スケルトンレイアウトがアシスタントを正しい軌道に保つ方法を発見してください。
安定性と後方互換性
私たちはあなたの時間を大切にしています。私たちは皆、数年ごとに完全に自己再発明し、開発者に破損したコードと高価な移行を残すフレームワークを見てきました。Flightは違います。Flight v3はv2の拡張として設計されており、皆さんが知り愛用しているAPIが剥奪されていません。実際、ほとんどのv2プロジェクトはv3で変更なしに動作します。
Flightの安定性を保ち、フレームワークの修正ではなく、アプリの構築に集中できるようにすることにコミットしています。スケルトンは新しいプロジェクトに対しては意見を持っている場合がありますが、コアAPIは他のすべてのユーザーにとって馴染み深いままです。
コミュニティ
Matrix Chatで活動しています
そしてDiscordでも
貢献
Flightに貢献する方法は2つあります:
- コアリポジトリを訪問して、コアフレームワークに貢献する。
- ドキュメントの改善を手伝う!このドキュメントウェブサイトはGithubでホストされています。エラーを見つけた場合や改善したいことがある場合は、プルリクエストを送信してください。更新や新しいアイデアを歓迎します。特にAIと新技術に関するものを歓迎します!
要件
FlightにはPHP 7.4以上が必要です。
注意: PHP 7.4がサポートされている理由は、執筆時点(2024年)で、PHP 7.4が一部のLTS Linuxディストリビューションのデフォルトバージョンであるためです。PHP >8への移行を強制すると、それらのユーザーに多くの問題を引き起こすことになります。フレームワークはPHP >8もサポートしています。
ライセンス
FlightはMITライセンスの下でリリースされています。
Awesome-plugins/php_cookie
クッキー
overclokk/cookie はアプリ内でクッキーを管理するためのシンプルなライブラリです。
インストール
composerを使用して簡単にインストールできます。
composer require overclokk/cookie
使用法
使用法は、Flightクラスに新しいメソッドを登録するだけです。
use Overclokk\Cookie\Cookie;
/*
* ブートストラップまたはpublic/index.phpファイルに設定
*/
Flight::register('cookie', Cookie::class);
/**
* ExampleController.php
*/
class ExampleController {
public function login() {
// クッキーを設定します
// インスタンスを取得するためfalseである必要があります
// オートコンプリートを有効にしたい場合は以下のコメントを使用してください
/** @var \Overclokk\Cookie\Cookie $cookie */
$cookie = Flight::cookie(false);
$cookie->set(
'stay_logged_in', // クッキーの名前
'1', // 設定したい値
86400, // クッキーの有効期間(秒)
'/', // クッキーが利用可能なパス
'example.com', // クッキーが利用可能なドメイン
true, // セキュアな HTTPS 接続でのみクッキーが送信されます
true // クッキーはHTTPプロトコルを介してのみ利用可能です
);
// オプションで、デフォルト値を維持したい場合や、
// 長期間にわたってクッキーを簡単に設定したい場合
$cookie->forever('stay_logged_in', '1');
}
public function home() {
// クッキーがあるかどうかをチェック
if (Flight::cookie()->has('stay_logged_in')) {
// 例えば、ダッシュボードエリアにリダイレクトします。
Flight::redirect('/dashboard');
}
}
}Awesome-plugins/php_encryption
PHP 暗号化
defuse/php-encryption はデータの暗号化と復号を行うために使用できるライブラリです。すぐにデータの暗号化と復号を始めることはかなり簡単です。ライブラリの使用方法や暗号化に関連する重要なセキュリティの問題を説明する素晴らしいtutorialがあります。
インストール
composerを使用して簡単にインストールします。
composer require defuse/php-encryption
セットアップ
その後、暗号化キーを生成する必要があります。
vendor/bin/generate-defuse-key
これにより、安全に保持する必要があるキーが生成されます。キーは、ファイルの末尾にある配列内のapp/config/config.phpファイルに保存できます。完璧な場所ではありませんが、少なくとも何かです。
使用方法
ライブラリと暗号化キーがあるので、データの暗号化と復号を開始できます。
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
/*
* ブートストラップまたはpublic/index.phpファイルに設定します
*/
// 暗号化メソッド
Flight::map('encrypt', function($raw_data) {
$encryption_key = /* $config['encryption_key']またはキーを配置した場所のfile_get_contents */;
return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});
// 復号メソッド
Flight::map('decrypt', function($encrypted_data) {
$encryption_key = /* $config['encryption_key']またはキーを配置した場所のfile_get_contents */;
try {
$raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
// 攻撃! 間違ったキーが読み込まれたか、暗号文が作成されてから変更された可能性があります -- データベースで破損されたか、攻撃を実行しようとするEveによって意図的に変更された可能性があります。
// ... アプリケーションに適した方法でこのケースを処理します ...
}
return $raw_data;
});
Flight::route('/encrypt', function() {
$encrypted_data = Flight::encrypt('これは秘密です');
echo $encrypted_data;
});
Flight::route('/decrypt', function() {
$encrypted_data = '...'; // どこかから暗号化されたデータを取得します
$decrypted_data = Flight::decrypt($encrypted_data);
echo $decrypted_data;
});Awesome-plugins/php_file_cache
flightphp/cache
軽量でシンプルなスタンドアロン PHP ファイル内キャッシュクラス Wruczek/PHP-File-Cache からフォーク
利点
- 軽量、スタンドアロン、シンプル
- すべてのコードが1つのファイルに - 無意味なドライバーはありません。
- セキュア - 生成されたすべてのキャッシュファイルには die 付きの php ヘッダーがあり、誰かがパスを知っていても、サーバーが適切に設定されていない場合でも直接アクセスは不可能
- 十分にドキュメント化され、テスト済み
- flock を通じて同時実行を正しく処理
- PHP 7.4+ をサポート
- MIT ライセンスで無料
このドキュメントサイトはこのライブラリを使用して各ページをキャッシュしています!
コードを表示するにはこちらをクリックしてください。
インストール
composer 経由でインストール:
composer require flightphp/cache
使用方法
使用方法は非常に簡単です。これによりキャッシュディレクトリにキャッシュファイルが保存されます。
use flight\Cache;
$app = Flight::app();
// コンストラクタにキャッシュを保存するディレクトリを渡します
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {
// これにより、キャッシュが本番モードの場合にのみ使用されることが保証されます
// ENVIRONMENT はブートストラップファイルまたはアプリ内の他の場所で設定される定数です
$cache->setDevMode(ENVIRONMENT === 'development');
});
キャッシュ値の取得
get() メソッドを使用してキャッシュされた値を取得します。期限切れの場合にキャッシュを更新する便利なメソッドが必要な場合は、refreshIfExpired() を使用できます。
// キャッシュインスタンスを取得
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // キャッシュするデータを返す
}, 10); // 10秒
// または
$data = $cache->get('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->set('simple-cache-test', $data, 10); // 10秒
}
キャッシュ値の保存
set() メソッドを使用してキャッシュに値を保存します。
Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10秒
キャッシュ値の削除
delete() メソッドを使用してキャッシュ内の値を削除します。
Flight::cache()->delete('simple-cache-test');
キャッシュ値の存在確認
exists() メソッドを使用してキャッシュに値が存在するかどうかを確認します。
if(Flight::cache()->exists('simple-cache-test')) {
// 何かを実行
}
キャッシュのクリア
flush() メソッドを使用してキャッシュ全体をクリアします。
Flight::cache()->flush();
キャッシュからメタデータを取得
キャッシュエントリに関するタイムスタンプやその他のメタデータを取得したい場合は、正しいパラメータとして true を渡すようにしてください。
$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
echo "Refreshing data!" . PHP_EOL;
return date("H:i:s"); // キャッシュするデータを返す
}, 10, true); // true = メタデータ付きで返す
// または
$data = $cache->get("simple-cache-meta-test", true); // true = メタデータ付きで返す
/*
メタデータ付きで取得したキャッシュアイテムの例:
{
"time":1511667506, <-- 保存された unix タイムスタンプ
"expire":10, <-- 秒単位の有効期限
"data":"04:38:26", <-- 非シリアライズデータ
"permanent":false
}
メタデータを使用することで、例えばアイテムが保存された時刻や有効期限を計算することができます
"data" キーでデータ自体にアクセスすることもできます
*/
$expiresin = ($data["time"] + $data["expire"]) - time(); // データが期限切れになる unix タイムスタンプを取得し、そこから現在のタイムスタンプを減算
$cacheddate = $data["data"]; // "data" キーでデータ自体にアクセス
echo "最新のキャッシュ保存: $cacheddate, $expiresin 秒後に期限切れ";
ソースコード
コードを表示するには https://github.com/flightphp/cache をご覧ください。
Awesome-plugins/permissions
FlightPHP/Permissions
これは、アプリに複数のロールがあり、各ロールに少し異なる機能がある場合にプロジェクトで使用できる権限モジュールです。このモジュールを使用すると、各ロールの権限を定義し、現在のユーザーが特定のページにアクセスしたり特定のアクションを実行したりする権限を持っているかどうかを確認できます。
GitHubのリポジトリはこちらをご覧ください。
インストール
composer require flightphp/permissions を実行すれば準備完了です!
使い方
まず権限を設定し、次にアプリに権限の意味を伝えます。最終的には $Permissions->has()、->can()、または is() で権限を確認します。has() と can() は同じ機能を持ちますが、コードの可読性を高めるために異なる名前が付けられています。
基本的な例
アプリケーションにユーザーがログインしているかどうかを確認する機能があると仮定します。以下のように権限オブジェクトを作成できます:
// index.php
require 'vendor/autoload.php';
// 何らかのコード
// 現在の役割が誰であるかを示すものがあるでしょう
// セッション変数から現在の役割を取得するものがあるでしょう
// ログイン後、そうでなければ「guest」または「public」の役割になります。
$current_role = 'admin';
// 権限の設定
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
return $current_role !== 'guest';
});
// このオブジェクトをFlightのどこかに保存したいでしょう
Flight::set('permission', $permission);
次に、コントローラー内のどこかで、以下のようなコードがあるでしょう。
<?php
// 何らかのコントローラー
class SomeController {
public function someAction() {
$permission = Flight::get('permission');
if ($permission->has('loggedIn')) {
// 何かを実行
} else {
// 他の何かを実行
}
}
}
これを使用して、アプリケーション内で何かを実行する権限があるかどうかを追跡することもできます。 たとえば、ソフトウェア上でユーザーが投稿を操作できる方法がある場合、特定のアクションを実行する権限があるかどうかを確認できます。
$current_role = 'admin';
// 権限の設定
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
if($current_role === 'admin') {
$permissions = ['create', 'read', 'update', 'delete'];
} else if($current_role === 'editor') {
$permissions = ['create', 'read', 'update'];
} else if($current_role === 'author') {
$permissions = ['create', 'read'];
} else if($current_role === 'contributor') {
$permissions = ['create'];
} else {
$permissions = [];
}
return $permissions;
});
Flight::set('permission', $permission);
次に、コントローラー内のどこかで...
class PostController {
public function create() {
$permission = Flight::get('permission');
if ($permission->can('post.create')) {
// 何かを実行
} else {
// 他の何かを実行
}
}
}
依存性の注入
権限を定義するクロージャに依存性を注入できます。これは、トグル、ID、またはチェックしたい他のデータポイントがある場合に便利です。Class->Method タイプの呼び出しでも同じことが機能しますが、引数はメソッド内で定義します。
クロージャ
$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
// ... コード
});
// コントローラーファイル内
public function createOrder() {
$MyDependency = Flight::myDependency();
$permission = Flight::get('permission');
if ($permission->can('order.create', $MyDependency)) {
// 何かを実行
} else {
// 他の何かを実行
}
}
クラス
namespace MyApp;
class Permissions {
public function order(string $current_role, MyDependency $MyDependency = null) {
// ... コード
}
}
クラスを使用した権限設定のショートカット
クラスを使用して権限を定義することもできます。これは、多数の権限があり、コードをクリーンに保ちたい場合に便利です。以下のようにできます:
<?php
// ブートストラップコード
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');
// myapp/Permissions.php
namespace MyApp;
class Permissions {
public function order(string $current_role, int $user_id) {
// 事前に設定したと仮定
/** @var \flight\database\SimplePdo $db */
$db = Flight::db();
$allowed_permissions = [ 'read' ]; // 誰でも注文を表示できます
if($current_role === 'manager') {
$allowed_permissions[] = 'create'; // マネージャーは注文を作成できます
}
$some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
if($some_special_toggle_from_db) {
$allowed_permissions[] = 'update'; // ユーザーが特別なトグルを持っている場合、注文を更新できます
}
if($current_role === 'admin') {
$allowed_permissions[] = 'delete'; // 管理者は注文を削除できます
}
return $allowed_permissions;
}
}
素晴らしい点は、ショートカット(キャッシュも可能!)を使用できることです。権限クラスにクラス内のすべてのメソッドを権限にマッピングするよう指示するだけです。したがって、order() という名前のメソッドと company() という名前のメソッドがある場合、これらは自動的にマッピングされるため、$Permissions->has('order.read') または $Permissions->has('company.read') を実行するだけで機能します。これを定義するのは非常に難しいので、注意してください。以下のようにするだけです:
一緒にグループ化したい権限のクラスを作成します。
class MyPermissions {
public function order(string $current_role, int $order_id = 0): array {
// 権限を決定するコード
return $permissions_array;
}
public function company(string $current_role, int $company_id): array {
// 権限を決定するコード
return $permissions_array;
}
}
次に、このライブラリを使用して権限を発見可能にします。
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);
最後に、コードベースで権限を呼び出して、ユーザーが特定の権限を実行できるかどうかを確認します。
class SomeController {
public function createOrder() {
if(Flight::get('permissions')->can('order.create') === false) {
die('注文を作成できません。申し訳ありません!');
}
}
}
キャッシュ
キャッシュを有効にするには、シンプルなwruczak/phpfilecache ライブラリを参照してください。有効にする例を以下に示します。
// この $app はコードの一部である場合もあれば、
// null を渡すだけでコンストラクタで Flight::app() から取得できます
$app = Flight::app();
// 現在はファイルキャッシュとしてこれを受け入れます。他のものは簡単に
// 将来追加できます。
$Cache = new Wruczek\PhpFileCache\PhpFileCache;
$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 はこれをキャッシュする秒数です。キャッシュを使用しない場合はこれを省略してください
それでは始めましょう!
Awesome-plugins/simple_job_queue
シンプルジョブキュー
シンプルジョブキューは、非同期でジョブを処理するために使用できるライブラリです。beanstalkd、MySQL/MariaDB、SQLite、およびPostgreSQLで使用できます。
インストール
composer require n0nag0n/simple-job-queue
使用法
これを機能させるには、キューにジョブを追加する方法と、ジョブを処理する方法(ワーカー)が必要です。以下は、ジョブをキューに追加する方法と、そのジョブを処理する方法の例です。
Flightへの追加
これをFlightに追加するのは簡単で、register()メソッドを使用して行います。以下は、これをFlightに追加する方法の例です。
<?php
require 'vendor/autoload.php';
// beanstalkdを使用する場合は、['mysql']を['beanstalkd']に変更してください
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
// Flight::db()で既にPDO接続がある場合
$Job_Queue->addQueueConnection(Flight::db());
// または、beanstalkd/Pheanstalkを使用している場合
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
});
新しいジョブの追加
ジョブを追加する場合、パイプライン(キュー)を指定する必要があります。これは、RabbitMQのチャネルやbeanstalkdのチューブに相当します。
<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));
ワーカーの実行
ここにワーカーを実行する方法のサンプルファイルがあります。
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// PDO接続
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);
// または、beanstalkd/Pheanstalkを使用している場合
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
$Job_Queue->watchPipeline('send_important_emails');
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
// あなたが夜に良く眠れるように調整してください(データベースキューのみ、beanstalkdではこのif文は必要ありません)
if(empty($job)) {
usleep(500000);
continue;
}
echo "処理中 {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
} else {
// これはレディキューから取り出し、後で拾って「キック」できる別のキューに入れます。
$Job_Queue->buryJob($job);
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
}
}
Supervisordを使用した長いプロセスの処理
Supervisordは、ワーカープロセスが継続的に実行されることを保証するプロセス制御システムです。シンプルジョブキューワーカーの設定に関するより完全なガイドは次のとおりです。
Supervisordのインストール
# Ubuntu/Debian上
sudo apt-get install supervisor
# CentOS/RHEL上
sudo yum install supervisor
# Homebrewを使用したmacOS上
brew install supervisor
ワーカースクリプトの作成
最初に、ワーカーコードを専用のPHPファイルに保存します。
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// PDO接続
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);
// 監視するパイプラインを設定
$Job_Queue->watchPipeline('send_important_emails');
// ワーカーの開始をログに記録
echo date('Y-m-d H:i:s') . " - ワーカーが開始されました\n";
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
if(empty($job)) {
usleep(500000); // 0.5秒間スリープ
continue;
}
echo date('Y-m-d H:i:s') . " - ジョブ {$job['id']}を処理中\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
echo date('Y-m-d H:i:s') . " - ジョブ {$job['id']}は正常に完了しました\n";
} else {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - ジョブ {$job['id']}が失敗し、埋められました\n";
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - ジョブ {$job['id']}の処理中に例外が発生しました: {$e->getMessage()}\n";
}
}
Supervisordの設定
ワーカーのための設定ファイルを作成します。
[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d
主要な設定オプション:
command: ワーカーを実行するコマンドdirectory: ワーカーの作業ディレクトリautostart: supervisordが起動するときに自動的に開始autorestart: プロセスが終了した場合に自動的に再起動startretries: 失敗した場合に再起動を試みる回数stderr_logfile/stdout_logfile: ログファイルの場所user: プロセスを実行するシステムユーザーnumprocs: 実行するワーカーインスタンスの数process_name: 複数のワーカープロセスの命名形式
Supervisorctlによるワーカーの管理
設定を作成または変更した後:
# supervisor設定を再読み込み
sudo supervisorctl reread
sudo supervisorctl update
# 特定のワーカープロセスを制御
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*
複数のパイプラインの実行
複数のパイプラインの場合、別々のワーカーファイルと設定を作成します。
[program:email_worker]
command=php /path/to/email_worker.php
# ... その他の設定 ...
[program:notification_worker]
command=php /path/to/notification_worker.php
# ... その他の設定 ...
監視とログ
ワーカーのアクティビティを監視するために、ログを確認します。
# ログを表示
sudo tail -f /var/log/simple_job_queue.log
# ステータスを確認
sudo supervisorctl status
この設定により、ジョブワーカーはクラッシュ、サーバーの再起動、またはその他の問題が発生しても継続的に実行されることが保証され、プロダクション環境におけるキューシステムの信頼性が高まります。
Awesome-plugins/jwt
Firebase JWT - JSON Web Token 認証
JWT (JSON Web Tokens) は、アプリケーションとクライアント間で請求を表すためのコンパクトで URL セーフな方法です。サーバー側のセッションストレージが不要なステートレス API 認証に最適です!このガイドでは、Firebase JWT を Flight と統合して、セキュアなトークンベースの認証を行う方法を示します。
完全なドキュメントと詳細については、Github リポジトリ をご覧ください。
JWT とは?
JSON Web Token は、3つの部分を含む文字列です:
- ヘッダー: トークンに関するメタデータ(アルゴリズム、タイプ)
- ペイロード: あなたのデータ(ユーザー ID、ロール、期限切れなど)
- 署名: 真正性を検証するための暗号署名
例の JWT: eyJ0eXAiOiJKV1QiLCJhbGc...(意味不明に見えますが、構造化されたデータです!)
JWT を使用する理由は?
- ステートレス: サーバー側のセッションストレージが不要 — マイクロサービスや API に最適
- スケーラブル: セッション親和性要件がないため、ロードバランサーでうまく動作
- クロスドメイン: 異なるドメインやサービス間で使用可能
- モバイルフレンドリー: クッキーがうまく動作しないモバイルアプリに最適
- 標準化: 業界標準のアプローチ (RFC 7519)
インストール
Composer を使用してインストール:
composer require firebase/php-jwt
基本的な使用方法
JWT を作成して検証する簡単な例:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// あなたのシークレットキー(これを安全に保ってください!)
$secretKey = 'your-256-bit-secret-key-here-keep-it-safe';
// トークンを作成
$payload = [
'user_id' => 123,
'username' => 'johndoe',
'role' => 'admin',
'iat' => time(), // 発行日時
'exp' => time() + 3600 // 1時間後に期限切れ
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
echo "Token: " . $jwt;
// トークンを検証してデコード
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
echo "User ID: " . $decoded->user_id;
} catch (Exception $e) {
echo "Invalid token: " . $e->getMessage();
}
Flight 用の JWT ミドルウェア(推奨アプローチ)
Flight で JWT を使用する最も一般的で有用な方法は、API ルートを保護するための ミドルウェア としてです。以下は、完全で本番環境対応の例です:
ステップ 1: JWT ミドルウェアクラスを作成
// app/middleware/JwtMiddleware.php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use flight\Engine;
class JwtMiddleware {
protected Engine $app;
protected string $secretKey;
public function __construct(Engine $app) {
$this->app = $app;
// シークレットキーを app/config/config.php に保存し、ハードコードしないでください!
$this->secretKey = $app->get('config')['jwt_secret'];
}
public function before(array $params) {
$authHeader = $this->app->request()->getHeader('Authorization');
// Authorization ヘッダーの存在を確認
if (empty($authHeader)) {
$this->app->jsonHalt(['error' => '認証トークンが提供されていません'], 401);
}
// "Bearer <token>" 形式からトークンを抽出
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$this->app->jsonHalt(['error' => '無効な認証形式です。Bearer <token> を使用してください'], 401);
}
$jwt = $matches[1];
try {
// トークンをデコードして検証
$decoded = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
// ルートハンドラーで使用するためにユーザー情報をリクエストに保存
$this->app->request()->data->user = $decoded;
} catch (ExpiredException $e) {
$this->app->jsonHalt(['error' => 'トークンの有効期限が切れています'], 401);
} catch (SignatureInvalidException $e) {
$this->app->jsonHalt(['error' => '無効なトークン署名'], 401);
} catch (Exception $e) {
$this->app->jsonHalt(['error' => '無効なトークン: ' . $e->getMessage()], 401);
}
}
}
ステップ 2: 設定に JWT シークレットを登録
// app/config/config.php
return [
'jwt_secret' => getenv('JWT_SECRET') ?: 'your-fallback-secret-for-development'
];
// app/config/bootstrap.php または index.php
// 設定をアプリに公開したい場合は、この行を追加してください
$app->set('config', $config);
セキュリティノート: シークレットキーをハードコードしないでください!本番環境では環境変数を使用してください。
ステップ 3: ミドルウェアでルートを保護
// 単一のルートを保護
Flight::route('GET /api/user/profile', function() {
$user = Flight::request()->data->user; // ミドルウェアで設定
Flight::json([
'user_id' => $user->user_id,
'username' => $user->username,
'role' => $user->role
]);
})->addMiddleware(JwtMiddleware::class);
// ルートのグループ全体を保護(より一般的!)
Flight::group('/api', function() {
Flight::route('GET /users', function() { /* ... */ });
Flight::route('GET /posts', function() { /* ... */ });
Flight::route('POST /posts', function() { /* ... */ });
Flight::route('DELETE /posts/@id', function($id) { /* ... */ });
}, [ JwtMiddleware::class ]); // このグループ内のすべてのルートが保護されます!
ミドルウェアの詳細については、ミドルウェアドキュメント を参照してください。
一般的な使用事例
1. ログインエンドポイント(トークン生成)
認証成功後に JWT を生成するルートを作成:
Flight::route('POST /api/login', function() {
$data = Flight::request()->data;
$username = $data->username ?? '';
$password = $data->password ?? '';
// 認証情報を検証(例 — 独自のロジックを使用!)
$user = validateUserCredentials($username, $password);
if (!$user) {
Flight::jsonHalt(['error' => '無効な認証情報'], 401);
}
// JWT を生成
$secretKey = Flight::get('config')['jwt_secret'];
$payload = [
'user_id' => $user->id,
'username' => $user->username,
'role' => $user->role,
'iat' => time(),
'exp' => time() + (60 * 60) // 1時間の有効期限
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
Flight::json([
'success' => true,
'token' => $jwt,
'expires_in' => 3600
]);
});
function validateUserCredentials($username, $password) {
// ここにデータベース検索とパスワード検証
// 例:
$db = Flight::db();
$user = $db->fetchRow("SELECT * FROM users WHERE username = ?", [$username]);
if ($user && password_verify($password, $user['password_hash'])) {
return (object) [
'id' => $user['id'],
'username' => $user['username'],
'role' => $user['role']
];
}
return null;
}
2. トークン更新フロー
長期間のセッションのためのリフレッシュトークンシステムを実装:
Flight::route('POST /api/login', function() {
// ... 認証情報を検証 ...
$secretKey = Flight::get('config')['jwt_secret'];
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
// 短期間のアクセストークン (15 分)
$accessToken = JWT::encode([
'user_id' => $user->id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
// 長期間のリフレッシュトークン (7 日)
$refreshToken = JWT::encode([
'user_id' => $user->id,
'type' => 'refresh',
'iat' => time(),
'exp' => time() + (7 * 24 * 60 * 60)
], $refreshSecret, 'HS256');
Flight::json([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 900
]);
});
Flight::route('POST /api/refresh', function() {
$refreshToken = Flight::request()->data->refresh_token ?? '';
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
try {
$decoded = JWT::decode($refreshToken, new Key($refreshSecret, 'HS256'));
// これがリフレッシュトークンであることを検証
if ($decoded->type !== 'refresh') {
Flight::jsonHalt(['error' => '無効なトークンタイプ'], 401);
}
// 新しいアクセストークンを生成
$secretKey = Flight::get('config')['jwt_secret'];
$accessToken = JWT::encode([
'user_id' => $decoded->user_id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
Flight::json([
'access_token' => $accessToken,
'expires_in' => 900
]);
} catch (Exception $e) {
Flight::jsonHalt(['error' => '無効なリフレッシュトークン'], 401);
}
});
3. ロールベースのアクセス制御
ミドルウェアを拡張してユーザー ロールをチェック:
class JwtRoleMiddleware {
protected Engine $app;
protected array $allowedRoles;
public function __construct(Engine $app, array $allowedRoles = []) {
$this->app = $app;
$this->allowedRoles = $allowedRoles;
}
public function before(array $params) {
// JwtMiddleware がすでに実行され、ユーザー データが設定されていると仮定
$user = $this->app->request()->data->user ?? null;
if (!$user) {
$this->app->jsonHalt(['error' => '認証が必要です'], 401);
}
// ユーザーが必要なロールを持っているかをチェック
if (!empty($this->allowedRoles) && !in_array($user->role, $this->allowedRoles)) {
$this->app->jsonHalt(['error' => '権限が不足しています'], 403);
}
}
}
// 使用例: 管理者専用ルート
Flight::route('DELETE /api/users/@id', function($id) {
// ユーザー削除ロジック
})->addMiddleware([
JwtMiddleware::class,
new JwtRoleMiddleware(Flight::app(), ['admin'])
]);
4. ユーザーごとのレート制限付きパブリック API
セッションなしで JWT を使用してユーザー を追跡し、レート制限:
class RateLimitMiddleware {
public function before(array $params) {
$user = Flight::request()->data->user ?? null;
$userId = $user ? $user->user_id : Flight::request()->ip;
$cacheKey = "rate_limit:$userId";
// app/config/services.php でキャッシュサービスを設定してください
$requests = Flight::cache()->get($cacheKey, 0);
if ($requests >= 100) { // 1時間あたり 100 リクエスト
Flight::jsonHalt(['error' => 'レート制限を超えました'], 429);
}
Flight::cache()->set($cacheKey, $requests + 1, 3600);
}
}
セキュリティのベストプラクティス
1. 強力なシークレットキー を使用
// セキュアなシークレットキーを生成(一度実行し、.env ファイルに保存)
$secretKey = base64_encode(random_bytes(32));
echo $secretKey; // これを .env ファイルに保存!
2. シークレットを環境変数に保存
// シークレットをバージョン管理にコミットしないでください!
// .env ファイルと vlucas/phpdotenv などのライブラリを使用
// .env ファイル:
// JWT_SECRET=your-base64-encoded-secret-here
// JWT_REFRESH_SECRET=another-base64-encoded-secret-here
// app/config/config.php ファイルを使用してシークレットを保存することも可能
// 設定ファイルがバージョン管理にコミットされないことを確認
// return [
// 'jwt_secret' => 'your-base64-encoded-secret-here',
// 'jwt_refresh_secret' => 'another-base64-encoded-secret-here',
// ];
// アプリ内:
// $secretKey = getenv('JWT_SECRET');
3. 適切な有効期限を設定
// 良い習慣: 短期間のアクセストークン
'exp' => time() + (15 * 60) // 15 分
// リフレッシュトークンの場合: 長い有効期限
'exp' => time() + (7 * 24 * 60 * 60) // 7 日
4. 本番環境で HTTPS を使用
JWT は 常に HTTPS で送信されるべきです。本番環境ではプレーン HTTP でトークンを送信しないでください!
5. トークンクレームを検証
重要なクレームを常に検証:
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
// 期限切れのチェックはライブラリで自動的に処理されます
// ただし、カスタム検証を追加可能:
if ($decoded->iat > time()) {
throw new Exception('トークンが発行される前に使用されました');
}
if (isset($decoded->nbf) && $decoded->nbf > time()) {
throw new Exception('トークンがまだ有効ではありません');
}
6. ログアウトのためのトークンブラックリストを検討
追加のセキュリティのため、無効化されたトークンのブラックリストを維持:
Flight::route('POST /api/logout', function() {
$authHeader = Flight::request()->getHeader('Authorization');
preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches);
$jwt = $matches[1];
// トークンの有効期限を抽出
$decoded = Flight::request()->data->user;
$ttl = $decoded->exp - time();
// 有効期限までキャッシュ/Redis に保存
Flight::cache()->set("blacklist:$jwt", true, $ttl);
Flight::json(['message' => '正常にログアウトしました']);
});
// JwtMiddleware に追加:
public function before(array $params) {
// ... JWT を抽出 ...
// ブラックリストをチェック
if (Flight::cache()->get("blacklist:$jwt")) {
$this->app->jsonHalt(['error' => 'トークンが取り消されました'], 401);
}
// ... トークンを検証 ...
}
アルゴリズムとキー タイプ
Firebase JWT は複数のアルゴリズムをサポート:
対称アルゴリズム (HMAC)
- HS256 (ほとんどのアプリに推奨): 単一のシークレットキー を使用
- HS384, HS512: より強力なバリエーション
$jwt = JWT::encode($payload, $secretKey, 'HS256');
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
非対称アルゴリズム (RSA/ECDSA)
- RS256, RS384, RS512: 公開/秘密鍵ペアを使用
- ES256, ES384, ES512: 楕円曲線バリエーション
// 鍵を生成: openssl genrsa -out private.key 2048
// openssl rsa -in private.key -pubout -out public.key
$privateKey = file_get_contents('/path/to/private.key');
$publicKey = file_get_contents('/path/to/public.key');
// 秘密鍵でエンコード
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// 公開鍵でデコード
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
RSA を使用するタイミング: 検証のための公開鍵を配布する必要がある場合に RSA を使用(例: マイクロサービス、サードパーティ統合)。単一のアプリケーションの場合、HS256 がよりシンプルで十分です。
トラブルシューティング
"Expired token" エラー
トークンの exp クレームが過去です。新しいトークンを発行するか、トークン更新を実装してください。
"Signature verification failed"
- エンコードに使用したものと異なるシークレットキー でデコードしています
- トークンが改ざんされています
- サーバー間のクロックスキュー(leeway バッファを追加)
use Firebase\JWT\JWT;
JWT::$leeway = 60; // 60 秒のクロックスキューを許可
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
リクエストでトークンが送信されない
クライアントが Authorization ヘッダーを送信していることを確認:
// JavaScript の例
fetch('/api/users', {
headers: {
'Authorization': 'Bearer ' + token
}
});
メソッド
Firebase JWT ライブラリはこれらのコアメソッドを提供:
JWT::encode(array $payload, string $key, string $alg): ペイロードから JWT を作成JWT::decode(string $jwt, Key $key): JWT をデコードして検証JWT::urlsafeB64Encode(string $input): Base64 URL セーフエンコーディングJWT::urlsafeB64Decode(string $input): Base64 URL セーフデコーディングJWT::$leeway: 検証のための時間 leeway を設定する静的プロパティ(秒単位)
このライブラリを使用する理由は?
- 業界標準: Firebase JWT は PHP 用の最も人気があり信頼されている JWT ライブラリ
- 積極的なメンテナンス: Google/Firebase チームによるメンテナンス
- セキュリティ重視: 定期的な更新とセキュリティパッチ
- シンプルな API: 理解しやすく実装しやすい
- よくドキュメント化: 豊富なドキュメントとコミュニティサポート
- 柔軟: 複数のアルゴリズムと構成可能なオプションをサポート
関連資料
- Firebase JWT Github リポジトリ
- JWT.io - JWT をデバッグしてデコード
- RFC 7519 - 公式 JWT 仕様
- Flight ミドルウェアドキュメント
- Flight セッションプラグイン - 従来のセッションベース認証用
ライセンス
Firebase JWT ライブラリは BSD 3-Clause ライセンスの下でライセンスされています。詳細は Github リポジトリ を参照してください。
Awesome-plugins/n0nag0n_wordpress
WordPress 統合: n0nag0n/wordpress-integration-for-flight-framework
WordPress サイト内で Flight PHP を使用したいですか? このプラグインはそれを簡単に行えます! n0nag0n/wordpress-integration-for-flight-framework を使用すると、WordPress のインストールと並行して完全な Flight アプリを実行できます—カスタム API、マイクロサービス、またはフル機能のアプリを WordPress の快適な環境から離れずに構築するのに最適です。
これは何をするのですか?
- Flight PHP を WordPress とシームレスに統合
- URL パターンに基づいてリクエストを Flight または WordPress にルーティング
- コントローラ、モデル、ビュー (MVC) を使用してコードを整理
- 推奨される Flight フォルダ構造を設定する
- WordPress のデータベース接続または独自のものを使用
- Flight と WordPress の相互作用を細かく調整
- 設定のためのシンプルな管理インターフェース
インストール
flight-integrationフォルダを/wp-content/plugins/ディレクトリにアップロードします。- WordPress の管理画面 (Plugins メニュー) でプラグインを有効化します。
- Settings > Flight Framework に移動してプラグインを設定します。
- Flight のインストールのパスをベンダーパスに設定します (または Composer を使用して Flight をインストール)。
- アプリフォルダのパスを設定し、フォルダ構造を作成します (プラグインがこれを支援します!)。
- Flight アプリケーションの構築を開始します!
使用例
基本的なルート例
アプリの app/config/routes.php ファイルで:
Flight::route('GET /api/hello', function() {
Flight::json(['message' => 'Hello World!']);
});
コントローラ例
app/controllers/ApiController.php にコントローラを作成します:
namespace app\controllers;
use Flight;
class ApiController {
public function getUsers() {
// Flight 内で WordPress 関数を使用できます!
$users = get_users();
$result = [];
foreach($users as $user) {
$result[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email
];
}
Flight::json($result);
}
}
次に、routes.php で:
Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);
FAQ
Q: このプラグインを使用するために Flight を知っておく必要がありますか?
A: はい、これは WordPress 内で Flight を使用したい開発者向けです。Flight のルーティングとリクエスト処理の基本的な知識をおすすめします。
Q: これは私の WordPress サイトを遅くしますか?
A: いいえ! プラグインは Flight のルートに一致するリクエストのみ処理します。他のリクエストは通常通り WordPress に渡されます。
Q: Flight アプリで WordPress の関数を使用できますか?
A: もちろんです! Flight のルートとコントローラから WordPress のすべての関数、フック、グローバル変数にアクセスできます。
Q: カスタムルートを作成するにはどうしたらいいですか?
A: アプリフォルダ内の config/routes.php ファイルでルートを定義します。フォルダ構造ジェネレータで作成されたサンプルファイルを参考にしてください。
変更履歴
1.0.0
初回リリース。
詳細については、GitHub repo を確認してください。
Awesome-plugins/ghost_session
Ghostff/Session
PHP セッションマネージャー(非ブロッキング、フラッシュ、セグメント、セッション暗号化)。PHP open_ssl を使用してセッション データのオプションの暗号化/復号化をサポートします。File, MySQL, Redis, and Memcached をサポートします。
こちらをクリックしてコードを表示します。
インストール
Composer でインストールします。
composer require ghostff/session
基本的な構成
デフォルトの設定を使用するには何も渡す必要はありません。詳細な設定については、Github Readmeを参照してください。
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
// 各ページの読み込みでセッションをコミットする必要がありますことを覚えておいてください
// または、構成で auto_commit を実行する必要があります。
簡単な例
これがこの使用方法の簡単な例です。
Flight::route('POST /login', function() {
$session = Flight::session();
// ここでログインのロジックを実行します
// パスワードを検証するなど。
// ログインに成功した場合
$session->set('is_logged_in', true);
$session->set('user', $user);
// セッションに書き込んだら、意図的にコミットする必要があります。
$session->commit();
});
// このチェックは制限されたページのロジックで実行するか、ミドルウェアでラップできます。
Flight::route('/some-restricted-page', function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
// ここで制限されたページのロジックを実行します
});
// ミドルウェア版
Flight::route('/some-restricted-page', function() {
// 通常のページロジック
})->addMiddleware(function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
});
より複雑な例
これがこの使用方法のより複雑な例です。
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
// 最初の引数としてセッション構成ファイルのカスタムパスを設定します
// または、カスタム配列を与えます
$app->register('session', Session::class, [
[
// セッション データをデータベースに保存したい場合(例: 「すべてのデバイスからログアウト」機能)
Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class,
Session::CONFIG_ENCRYPT_DATA => true,
Session::CONFIG_SALT_KEY => hash('sha256', 'my-super-S3CR3T-salt'), // これは別のものに変更してください
Session::CONFIG_AUTO_COMMIT => true, // これは必要で、commit() が難しい場合のみ実行してください。
// さらに、Flight::after('start', function() { Flight::session()->commit(); }); を実行できます。
Session::CONFIG_MYSQL_DS => [
'driver' => 'mysql', # Database driver for PDO dns eg(mysql:host=...;dbname=...)
'host' => '127.0.0.1', # Database host
'db_name' => 'my_app_database', # Database name
'db_table' => 'sessions', # Database table
'db_user' => 'root', # Database username
'db_pass' => '', # Database password
'persistent_conn'=> false, # スクリプトがデータベースにアクセスするたびに新しい接続を確立するオーバーヘッドを避ける。詳細は自分で確認してください
]
]
]);
助けて! 私のセッションデータが持続しません!
セッションデータを設定してもリクエスト間で持続しない場合、セッションデータをコミットすることを忘れている可能性があります。$session->commit() を呼び出して設定した後でこれを実行してください。
Flight::route('POST /login', function() {
$session = Flight::session();
// ここでログインのロジックを実行します
// パスワードを検証するなど。
// ログインに成功した場合
$session->set('is_logged_in', true);
$session->set('user', $user);
// セッションに書き込んだら、意図的にコミットする必要があります。
$session->commit();
});
これを回避する方法として、セッション サービスを設定するときに構成で auto_commit を true に設定します。これにより、各リクエスト後にセッションデータが自動的にコミットされます。
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
$session->updateConfiguration([
Session::CONFIG_AUTO_COMMIT => true,
]);
}
);
さらに、Flight::after('start', function() { Flight::session()->commit(); }); を実行して各リクエスト後にセッションデータをコミットすることもできます。
ドキュメント
完全なドキュメントについては、Github Readmeを訪問してください。構成オプションはdefault_config.php ファイル自体でよく文書化されています。コードは自分で確認したい場合に簡単に理解できます。
Awesome-plugins/mcp
FlightPHP MCP Server
FlightPHP MCP Server は、MCP 互換の AI コーディングアシスタントに、FlightPHP のドキュメント全体(ルーティング、ミドルウェア、プラグイン、ガイドなど)への即時で構造化されたアクセスを提供します。AI が API の詳細を幻覚したり、メソッドシグネチャを推測したりする代わりに、必要に応じて実際のドキュメントを取得します。API キー不要、ホスト版ではインストールも不要です。
完全なソースコードと詳細については、Github repository をご覧ください。
Quick Start
サーバーは公開ホストされており、すぐに使用可能です:
https://mcp.flightphp.com/mcp
この URL を AI コーディング拡張機能に追加するだけです。サインアップ不要、認証情報不要。人気のツール向けのコピー&ペースト設定については、以下の IDE Configuration セクションを参照してください。
What It Does
接続後、AI アシスタントは以下の操作が可能です:
- すべての利用可能なドキュメントを閲覧 — すべてのコアトピック、ガイド、プラグインページをリストアップ
- 任意のドキュメントページを取得 — ルーティング、ミドルウェア、リクエスト、セキュリティなどの完全なコンテンツを取得
- プラグインドキュメントを検索 — ActiveRecord、Session、Tracy、Runway、およびその他の公式プラグインの完全なドキュメントを取得
- ステップバイステップのガイドに従う — ブログ、REST API、テスト済みアプリケーションの構築のための完全なウォークスルーにアクセス
- すべてを横断的に検索 — コアドキュメント、ガイド、プラグイン全体で関連ページを一度に検索
Key Points
- ゼロセットアップ —
https://mcp.flightphp.com/mcpのホストサーバーはインストールや API キーが不要です。 - 常に最新 — サーバーは docs.flightphp.com からライブでドキュメントを取得するため、常に最新です。
- どこでも動作 — MCP Streamable HTTP トランスポートをサポートする任意のツールで接続可能です。
- セルフホスト可能 — PHP >= 8.1 と Composer で独自のインスタンスを実行できます。
IDE / AI Extension Configuration
サーバーは Streamable HTTP トランスポートを使用します。以下の拡張機能を選択して設定を貼り付けてください。
Claude Code (CLI)
プロジェクトに追加するには、以下のコマンドを実行します:
claude mcp add --transport http flightphp-docs https://mcp.flightphp.com/mcp
または、プロジェクトの .mcp.json に手動で追加します:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
GitHub Copilot (VS Code)
ワークスペースの .vscode/mcp.json に追加します:
{
"servers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
Kilo Code (VS Code)
VS Code の settings.json に追加します:
{
"kilocode.mcpServers": {
"flightphp-docs": {
"url": "https://mcp.flightphp.com/mcp",
"transport": "streamable-http"
}
}
}
Continue.dev (VS Code / JetBrains)
~/.continue/config.json に追加します:
{
"mcpServers": [
{
"name": "flightphp-docs",
"transport": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
]
}
Available Tools
MCP サーバーは、AI アシスタントに以下のツールを公開します:
| Tool | Description |
|---|---|
list_docs_pages |
すべての利用可能なコアドキュメントトピックをスラッグと説明付きでリストアップ |
get_docs_page |
トピックスラッグ(例: routing、middleware、security)でコアドキュメントページを取得 |
list_guide_pages |
すべての利用可能なステップバイステップガイドをリストアップ |
get_guide_page |
スラッグ(例: blog、unit-testing)で完全なガイドを取得 |
list_plugin_pages |
すべての利用可能なプラグインと拡張ページをリストアップ |
get_plugin_docs |
スラッグ(例: active-record、session、jwt)で完全なプラグインドキュメントを取得 |
search_docs |
すべてのドキュメント、ガイド、プラグイン全体でキーワードやトピックを検索 |
fetch_url |
完全な docs.flightphp.com URL で任意のページを直接取得 |
Self-Hosting
独自のインスタンスを実行したいですか? PHP >= 8.1 と Composer が必要です。
git clone https://github.com/flightphp/mcp.git
cd mcp
composer install
php server.php
サーバーはデフォルトで http://0.0.0.0:8890/mcp で開始します。IDE 設定をローカルアドレスに更新します:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "http://localhost:8890/mcp"
}
}
}Awesome-plugins/async
Async
Async は、Flight フレームワーク用の小さなパッケージで、Swoole、AdapterMan、ReactPHP、Amp、RoadRunner、Workerman などの非同期サーバーおよびランタイム内で Flight アプリを実行できるようにします。デフォルトで Swoole と AdapterMan のアダプターが含まれています。
目標:PHP-FPM(または組み込みサーバー)で開発およびデバッグを行い、本番環境では最小限の変更で Swoole(または他の非同期ドライバー)に切り替えることです。
要件
- PHP 7.4 以上
- Flight フレームワーク 3.16.1 以上
- Swoole 拡張
インストール
Composer を使用してインストールします:
composer require flightphp/async
Swoole で実行する予定の場合、拡張をインストールします:
# pecl を使用
pecl install swoole
# または openswoole
pecl install openswoole
# またはパッケージマネージャー(Debian/Ubuntu の例)
sudo apt-get install php-swoole
Swoole の簡単な例
以下は、PHP-FPM(または組み込みサーバー)と Swoole の両方を同じコードベースでサポートする方法を示す最小限のセットアップです。
プロジェクトで必要なファイル:
- index.php
- swoole_server.php
- SwooleServerDriver.php
index.php
このファイルは、開発時にアプリを PHP モードで実行するように強制するシンプルなスイッチです。
// index.php
<?php
define('NOT_SWOOLE', true);
include 'swoole_server.php';
swoole_server.php
このファイルは Flight アプリをブートストラップし、NOT_SWOOLE が定義されていない場合に Swoole ドライバーを開始します。
// swoole_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = Flight::app();
$app->route('/', function() use ($app) {
$app->json(['hello' => 'world']);
});
if (!defined('NOT_SWOOLE')) {
// Swoole モードで実行する場合に SwooleServerDriver クラスを require します。
require_once __DIR__ . '/SwooleServerDriver.php';
Swoole\Runtime::enableCoroutine();
$Swoole_Server = new SwooleServerDriver('127.0.0.1', 9501, $app);
$Swoole_Server->start();
} else {
$app->start();
}
SwooleServerDriver.php
AsyncBridge と Swoole アダプターを使用して Swoole リクエストを Flight にブリッジする方法を示す簡潔なドライバーです。
// SwooleServerDriver.php
<?php
use flight\adapter\SwooleAsyncRequest;
use flight\adapter\SwooleAsyncResponse;
use flight\AsyncBridge;
use flight\Engine;
use Swoole\HTTP\Server as SwooleServer;
use Swoole\HTTP\Request as SwooleRequest;
use Swoole\HTTP\Response as SwooleResponse;
class SwooleServerDriver {
protected $Swoole;
protected $app;
public function __construct(string $host, int $port, Engine $app) {
$this->Swoole = new SwooleServer($host, $port);
$this->app = $app;
$this->setDefault();
$this->bindWorkerEvents();
$this->bindHttpEvent();
}
protected function setDefault() {
$this->Swoole->set([
'daemonize' => false,
'dispatch_mode' => 1,
'max_request' => 8000,
'open_tcp_nodelay' => true,
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,
'enable_coroutine' => true,
'http_compression' => false,
'enable_static_handler' => true,
'document_root' => __DIR__,
'static_handler_locations' => ['/css', '/js', '/images', '/.well-known'],
'buffer_output_size' => 4 * 1024 * 1024,
'worker_num' => 4,
]);
$app = $this->app;
$app->map('stop', function (?int $code = null) use ($app) {
if ($code !== null) {
$app->response()->status($code);
}
});
}
protected function bindHttpEvent() {
$app = $this->app;
$AsyncBridge = new AsyncBridge($app);
$this->Swoole->on('Start', function(SwooleServer $server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$this->Swoole->on('Request', function (SwooleRequest $request, SwooleResponse $response) use ($AsyncBridge) {
$SwooleAsyncRequest = new SwooleAsyncRequest($request);
$SwooleAsyncResponse = new SwooleAsyncResponse($response);
$AsyncBridge->processRequest($SwooleAsyncRequest, $SwooleAsyncResponse);
$response->end();
gc_collect_cycles();
});
}
protected function bindWorkerEvents() {
$createPools = function() {
// ここでワーカー固有の接続プールを作成します
};
$closePools = function() {
// ここでプールを閉じてクリーンアップします
};
$this->Swoole->on('WorkerStart', $createPools);
$this->Swoole->on('WorkerStop', $closePools);
$this->Swoole->on('WorkerError', $closePools);
}
public function start() {
$this->Swoole->start();
}
}
サーバーの実行
- 開発(PHP 組み込みサーバー / PHP-FPM):
- php -S localhost:8000 (index が public/ にある場合は -t public/ を追加)
- 本番(Swoole):
- php swoole_server.php
ヒント:本番環境では、TLS、静的ファイル、負荷分散を処理するために Swoole の前にリバースプロキシ(Nginx)を使用してください。
設定の注意点
Swoole ドライバーはいくつかの設定オプションを公開しています:
- worker_num: ワーカープロセスの数
- max_request: 再起動前のワーカーあたりのリクエスト数
- enable_coroutine: 並行性のためにコルーチンを使用
- buffer_output_size: 出力バッファサイズ
これらをホストのリソースとトラフィックパターンに合わせて調整してください。
エラーハンドリング
AsyncBridge は Flight のエラーを適切な HTTP レスポンスに変換します。ルートレベルのエラーハンドリングも追加できます:
$app->route('/*', function() use ($app) {
try {
// ルートロジック
} catch (Exception $e) {
$app->response()->status(500);
$app->json(['error' => $e->getMessage()]);
}
});
AdapterMan および他のランタイム
AdapterMan は代替ランタイムアダプターとしてサポートされています。このパッケージは適応性が高く設計されており、他のアダプターを追加または使用する場合も、通常同じパターンを踏襲します:サーバーリクエスト/レスポンスを AsyncBridge とランタイム固有のアダプター経由で Flight のリクエスト/レスポンスに変換します。
Awesome-plugins/migrations
マイグレーション
プロジェクトのマイグレーションは、プロジェクトに関与するすべてのデータベース変更を追跡します。 byjg/php-migration は、あなたが始めるのに非常に役立つコアライブラリです。
インストール
PHP ライブラリ
プロジェクトで PHP ライブラリのみを使用したい場合:
composer require "byjg/migration"
コマンドラインインターフェース
コマンドラインインターフェースはスタンドアロンであり、プロジェクトにインストールする必要はありません。
グローバルにインストールし、シンボリックリンクを作成できます。
composer require "byjg/migration-cli"
マイグレーション CLI に関する詳細情報は、byjg/migration-cliをご覧ください。
サポートされているデータベース
| データベース | ドライバー | 接続文字列 |
|---|---|---|
| Sqlite | pdo_sqlite | sqlite:///path/to/file |
| MySql/MariaDb | pdo_mysql | mysql://username:password@hostname:port/database |
| Postgres | pdo_pgsql | pgsql://username:password@hostname:port/database |
| Sql Server | pdo_dblib, pdo_sysbase Linux | dblib://username:password@hostname:port/database |
| Sql Server | pdo_sqlsrv Windows | sqlsrv://username:password@hostname:port/database |
どのように機能しますか?
データベースマイグレーションは、データベースのバージョン管理に純粋な SQL を使用します。 機能させるためには、次のことを行う必要があります。
- SQL スクリプトを作成する
- コマンドラインまたは API を使用して管理する。
SQL スクリプト
スクリプトは、3 つのセットのスクリプトに分かれています。
- BASE スクリプトは、新しいデータベースを作成するためのすべての SQL コマンドを含みます。
- UP スクリプトは、データベースのバージョンを "up" するためのすべての SQL マイグレーションコマンドを含みます。
- DOWN スクリプトは、データベースのバージョンを "down" するためのすべての SQL マイグレーションコマンドを含みます。
スクリプトディレクトリは次のとおりです:
<root dir>
|
+-- base.sql
|
+-- /migrations
|
+-- /up
|
+-- 00001.sql
+-- 00002.sql
+-- /down
|
+-- 00000.sql
+-- 00001.sql
- "base.sql" はベーススクリプトです
- "up" フォルダーには、バージョンをアップするためのスクリプトが含まれています。 例えば: 00002.sql は、データベースをバージョン '1' から '2' へ移動させるためのスクリプトです。
- "down" フォルダーには、バージョンをダウンするためのスクリプトが含まれています。 例えば: 00001.sql は、データベースをバージョン '2' から '1' へ移動させるためのスクリプトです。 "down" フォルダーはオプションです。
マルチ開発環境
複数の開発者や複数のブランチで作業する場合、次の番号を特定するのは難しいです。
その場合、バージョン番号の後にサフィックス "-dev" を付けます。
シナリオを見てみましょう:
- 開発者 1 がブランチを作成し、最新のバージョンが e.g. 42 です。
- 開発者 2 が同時にブランチを作成し、同じデータベースバージョン番号を持っています。
どちらの場合も、開発者は 43-dev.sql というファイルを作成します。 両方の開発者は UP と DOWN を問題なく移行し、あなたのローカルバージョンは 43 になります。
しかし、開発者 1 が変更をマージし、最終バージョン 43.sql を作成しました(git mv 43-dev.sql 43.sql)。 開発者 2 がローカルブランチを更新すると、彼はファイル 43.sql(dev 1 から)とファイル 43-dev.sql を持ちます。
彼が UP または DOWN に移行しようとすると、マイグレーションスクリプトはダウンし、バージョンが 43 の 2 つが存在することを警告します。その場合、開発者 2 はファイルを 44-dev.sql に更新し、変更をマージして最終バージョンを生成するまで作業を続行しなければなりません。
PHP API を使用してプロジェクトに統合する
基本的な使用法は
- 接続管理オブジェクトの接続を作成する。詳細については、"byjg/anydataset" コンポーネントを参照してください。
- この接続と SQL スクリプトがあるフォルダーを使用してマイグレーションオブジェクトを作成します。
- マイグレーションスクリプトを "reset"、"up" または "down" のための適切なコマンドを使用します。
例えばを見る:
<?php
// 接続 URI を作成
// 詳細: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');
// データベースまたはデータベースをその URI で処理するように登録します:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// マイグレーションインスタンスを作成する
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// 実行からの情報を受け取るためにコールバック進捗関数を追加します
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
echo "$action, $currentVersion, ${fileInfo['description']}\n";
});
// "base.sql" スクリプトを使用してデータベースを復元し
// データベースのバージョンを最新バージョンまでアップグレードするためのすべてのスクリプトを実行します
$migration->reset();
// 現在のバージョンから $version 番号までのデータベースのバージョンのためのすべてのスクリプトを実行します;
// バージョン番号が指定されていない場合、最後のデータベースバージョンまで移行します
$migration->update($version = null);
マイグレーションオブジェクトは、データベースのバージョンを制御します。
プロジェクト内のバージョン管理作成
<?php
// データベースまたはデータベースをその URI で処理するように登録します:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// マイグレーションインスタンスを作成する
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// このコマンドは、データベース内にバージョンテーブルを作成します
$migration->createVersion();
現在のバージョンの取得
<?php
$migration->getCurrentVersion();
コールバックを追加して進捗を制御
<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
echo "コマンドを実行中: $command バージョン $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});
Db ドライバーインスタンスの取得
<?php
$migration->getDbDriver();
使用するには、次を訪れてください: https://github.com/byjg/anydataset-db
部分的なマイグレーションを避ける(MySQL では利用できません)
部分的なマイグレーションは、エラーや手動の中断によりマイグレーションスクリプトがプロセスの途中で中断される場合です。
マイグレーションテーブルは partial up または partial down の状態になり、再度移行できるようになる前に手動で修正する必要があります。
この状況を避けるために、マイグレーションがトランザクショナルコンテキストで実行されることを指定できます。
マイグレーションスクリプトが失敗すると、トランザクションはロールバックされ、マイグレーションテーブルは complete とマークされ、
バージョンはエラーを引き起こしたスクリプトの直前のバージョンになります。
この機能を有効にするには、withTransactionEnabled メソッドを呼び出して、true をパラメータとして渡す必要があります:
<?php
$migration->withTransactionEnabled(true);
注: この機能は、MySQL では利用できません。DDL コマンドをトランザクション内でサポートしていないためです。 このメソッドを MySQL で使用した場合、マイグレーションは静かに無視します。 詳細情報: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html
Postgres 用の SQL マイグレーションを書く際のヒント
トリガーと SQL 関数の作成時
-- DO
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- empname と salary が指定されていることを確認します
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null'; -- これらのコメントが空でも関係ありません
END IF; --
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname; --
END IF; --
-- 誰が私たちのために働いているのか、彼らはそれのために支払わなければなりませんか?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname; --
END IF; --
-- 誰が給与を変更したのかを記憶して
NEW.last_date := current_timestamp; --
NEW.last_user := current_user; --
RETURN NEW; --
END; --
$emp_stamp$ LANGUAGE plpgsql;
-- DON'T
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- empname と salary が指定されていることを確認します
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- 誰が私たちのために働いているのか、彼らはそれのために支払わなければなりませんか?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- 誰が給与を変更したのかを記憶して
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
PDO データベース抽象層は SQL ステートメントのバッチを実行できないため、byjg/migration がマイグレーションファイルを読み込むと、ファイルの内容全体をセミコロンで分割し、ステートメントを個別に実行する必要があります。ただし、1 つの種類のステートメントはその本体の間に複数のセミコロンを持つことがあります: 関数です。
関数を正しく解析できるようにするために、byjg/migration 2.1.0 からマイグレーションファイルを セミコロン + EOL シーケンスで分割するようになりました。これにより、関数定義の各内部セミコロンの後に空のコメントを追加すると、byjg/migration がそれを解析できるようになります。
不幸にも、これらのコメントのいずれかを追加するのを忘れると、ライブラリは CREATE FUNCTION ステートメントを複数の部分に分割し、マイグレーションは失敗します。
コロン文字(:)を避ける
-- DO
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
check_in DATE NOT NULL
);
-- DON'T
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
check_in DATE NOT NULL
);
PDO は名前付きパラメータのプレフィックスとしてコロン文字を使用するため、他のコンテキストでの使用は問題を引き起こします。
例えば、PostgreSQL ステートメントは :: を使用して型の間で値をキャストできます。 一方で、PDO はこれを無効な名前付きパラメータとして解釈し、無効なコンテキストで失敗します。
この不一致を修正する唯一の方法は、コロンを完全に避けることです(この場合、PostgreSQL にも代替構文があります: CAST(value AS type))。
SQL エディタを使用する
最後に、手動で SQL マイグレーションを書くことは面倒ですが、SQL 構文を理解し、オートコンプリートを提供し、現在のデータベーススキーマを調査しているエディタを使用すれば、格段に簡単になります。
1 つのスキーマ内での異なるマイグレーションの処理
同じスキーマ内で異なるマイグレーションスクリプトやバージョンを作成する必要がある場合、可能ですがリスクが高く、私は 全く推奨しません。
これを行うには、コンストラクタにパラメータを渡して異なる "マイグレーションテーブル" を作成する必要があります。
<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");
セキュリティ上の理由から、この機能はコマンドラインでは利用できませんが、環境変数 MIGRATION_VERSION を使用して名前を保存できます。
この機能を使用しないことを強く推奨します。推奨は、1 つのスキーマにつき 1 つのマイグレーションです。
ユニットテストの実行
基本的なユニットテストは次のように実行できます:
vendor/bin/phpunit
データベーステストの実行
統合テストを実行するには、データベースを立ち上げておく必要があります。基本的な docker-compose.yml を提供しており、テストのためにデータベースを起動する際に使用できます。
データベースの実行
docker-compose up -d postgres mysql mssql
テストを実行
vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*
オプションとして、ユニットテストで使用されるホストとパスワードを設定できます。
export MYSQL_TEST_HOST=localhost # デフォルトは localhost
export MYSQL_PASSWORD=newpassword # null パスワードが必要な場合は '.' を使用
export PSQL_TEST_HOST=localhost # デフォルトは localhost
export PSQL_PASSWORD=newpassword # null パスワードが必要な場合は '.' を使用
export MSSQL_TEST_HOST=localhost # デフォルトは localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db # デフォルトは /tmp/test.dbAwesome-plugins/comment_template
CommentTemplate
CommentTemplate は、強力な PHP テンプレートエンジンで、アセットのコンパイル、テンプレートの継承、変数の処理を備えています。ビルトインの CSS/JS 最小化とキャッシュにより、シンプルで柔軟なテンプレート管理を提供します。
機能
- テンプレートの継承: レイアウトを使用し、他のテンプレートを含める
- アセットのコンパイル: 自動 CSS/JS 最小化とキャッシュ
- 変数の処理: フィルターとコマンド付きのテンプレート変数
- Base64 エンコーディング: アセットをデータ URI としてインライン化
- Flight Framework との統合: Flight PHP フレームワークとのオプションの統合
インストール
Composer を使用してインストールします。
composer require knifelemon/comment-template
基本設定
開始するための基本的な設定オプションがあります。これらについての詳細は CommentTemplate Repo を参照してください。
方法 1: コールバック関数を使用
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
$app->register('view', Engine::class, [], function (Engine $engine) use ($app) {
// ルートディレクトリ(index.php が存在する場所) - Web アプリケーションのドキュメントルート
$engine->setPublicPath(__DIR__);
// テンプレートファイルのディレクトリ - 相対パスと絶対パスの両方をサポート
$engine->setSkinPath('views'); // パブリックパスに対する相対パス
// コンパイルされたアセットの保存場所 - 相対パスと絶対パスの両方をサポート
$engine->setAssetPath('assets'); // パブリックパスに対する相対パス
// テンプレートファイルの拡張子
$engine->setFileExtension('.php');
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
方法 2: コンストラクタパラメータを使用
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
// __construct(string $publicPath = "", string $skinPath = "", string $assetPath = "", string $fileExtension = "")
$app->register('view', Engine::class, [
__DIR__, // publicPath - ルートディレクトリ(index.php が存在する場所)
'views', // skinPath - テンプレートパス(相対/絶対をサポート)
'assets', // assetPath - コンパイルされたアセットパス(相対/絶対をサポート)
'.php' // fileExtension - テンプレートファイルの拡張子
]);
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Tracy デバッガー統合
CommentTemplateは開発ログとデバッグのためにTracy Debuggerとの統合を含んでいます。

インストール
composer require tracy/tracy
使用方法
<?php
use KnifeLemon\CommentTemplate\Engine;
use Tracy\Debugger;
// Tracyを有効にする(出力前に呼び出す必要があります)
Debugger::enable(Debugger::DEVELOPMENT);
Flight::set('flight.content_length', false);
// テンプレートのオーバーライド
$app->register('view', Engine::class, [], function (Engine $builder) use ($app) {
$builder->setPublicPath($app->get('flight.views.topPath'));
$builder->setAssetPath($app->get('flight.views.assetPath'));
$builder->setSkinPath($app->get('flight.views.path'));
$builder->setFileExtension($app->get('flight.views.extension'));
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
$app->start();
デバッグパネル機能
CommentTemplateは4つのタブを持つカスタムパネルをTracyのデバッグバーに追加します:
- Overview: 設定、パフォーマンス指標、カウント
- Assets: 圧縮率を含むCSS/JSコンパイルの詳細
- Variables: 適用されたフィルターを含む元の値と変換された値
- Timeline: すべてのテンプレート操作の時系列ビュー
ログに記録される内容
- テンプレートレンダリング(開始/終了、期間、レイアウト、インポート)
- アセットコンパイル(CSS/JSファイル、サイズ、圧縮率)
- 変数処理(元の値/変換された値、フィルター)
- アセット操作(base64エンコーディング、ファイルコピー)
- パフォーマンス指標(期間、メモリ使用量)
注意: Tracyがインストールされていないか無効になっている場合、パフォーマンスへの影響はゼロです。
Flight PHPでの完全な動作例を参照してください。
テンプレートディレクティブ
レイアウト継承
共通の構造を作成するためにレイアウトを使用します:
layout/global_layout.php:
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
</head>
<body>
<!--@contents-->
</body>
</html>
view/page.php:
<!--@layout(layout/global_layout)-->
<h1>{$title}</h1>
<p>{$content}</p>
アセット管理
CSS ファイル
<!--@css(/css/styles.css)--> <!-- 最小化されキャッシュされる -->
<!--@cssSingle(/css/critical.css)--> <!-- 単一ファイル、最小化されない -->
JavaScript ファイル
CommentTemplate は異なる JavaScript 読み込み戦略をサポートします:
<!--@js(/js/script.js)--> <!-- 最小化され、ボトムで読み込まれる -->
<!--@jsAsync(/js/analytics.js)--> <!-- 最小化され、ボトムで async で読み込まれる -->
<!--@jsDefer(/js/utils.js)--> <!-- 最小化され、ボトムで defer で読み込まれる -->
<!--@jsTop(/js/critical.js)--> <!-- 最小化され、head で読み込まれる -->
<!--@jsTopAsync(/js/tracking.js)--> <!-- 最小化され、head で async で読み込まれる -->
<!--@jsTopDefer(/js/polyfill.js)--> <!-- 最小化され、head で defer で読み込まれる -->
<!--@jsSingle(/js/widget.js)--> <!-- 単一ファイル、最小化されない -->
<!--@jsSingleAsync(/js/ads.js)--> <!-- 単一ファイル、最小化されない、async -->
<!--@jsSingleDefer(/js/social.js)--> <!-- 単一ファイル、最小化されない、defer -->
CSS/JS ファイル内のアセットディレクティブ
CommentTemplate は、コンパイル中に CSS と JavaScript ファイル内のアセットディレクティブも処理します:
CSS の例:
/* あなたの CSS ファイル内で */
@font-face {
font-family: 'CustomFont';
src: url('<!--@asset(fonts/custom.woff2)-->') format('woff2');
}
.background-image {
background: url('<!--@asset(images/bg.jpg)-->');
}
.inline-icon {
background: url('<!--@base64(icons/star.svg)-->');
}
JavaScript の例:
/* あなたの JS ファイル内で */
const fontUrl = '<!--@asset(fonts/custom.woff2)-->';
const imageData = '<!--@base64(images/icon.png)-->';
Base64 エンコーディング
<!--@base64(images/logo.png)--> <!-- データ URI としてインライン -->
例:
<!-- 小さな画像をデータ URI としてインライン化して高速読み込み -->
<img src="<!--@base64(images/logo.png)-->" alt="Logo">
<div style="background-image: url('<!--@base64(icons/star.svg)-->');">
背景としての小さなアイコン
</div>
アセットのコピー
<!--@asset(images/photo.jpg)--> <!-- 単一アセットをパブリックディレクトリにコピー -->
<!--@assetDir(assets)--> <!-- 全体のディレクトリをパブリックディレクトリにコピー -->
例:
<!-- 静的アセットをコピーして参照 -->
<img src="<!--@asset(images/hero-banner.jpg)-->" alt="Hero Banner">
<a href="<!--@asset(documents/brochure.pdf)-->" download>ブロシュアをダウンロード</a>
<!-- 全体のディレクトリ(フォント、アイコンなど)をコピー -->
<!--@assetDir(assets/fonts)-->
<!--@assetDir(assets/icons)-->
テンプレートのインクルード
<!--@import(components/header)--> <!-- 他のテンプレートを含める -->
例:
<!-- 再利用可能なコンポーネントを含める -->
<!--@import(components/header)-->
<main>
<h1>当社のウェブサイトへようこそ</h1>
<!--@import(components/sidebar)-->
<div class="content">
<p>ここにメインコンテンツ...</p>
</div>
</main>
<!--@import(components/footer)-->
変数の処理
基本変数
<h1>{$title}</h1>
<p>{$description}</p>
変数フィルター
{$title|upper} <!-- 大文字に変換 -->
{$content|lower} <!-- 小文字に変換 -->
{$html|striptag} <!-- HTML タグを除去 -->
{$text|escape} <!-- HTML をエスケープ -->
{$multiline|nl2br} <!-- 改行を <br> に変換 -->
{$html|br2nl} <!-- <br> タグを改行に変換 -->
{$description|trim} <!-- 空白をトリム -->
{$subject|title} <!-- タイトルケースに変換 -->
変数コマンド
{$title|default=Default Title} <!-- デフォルト値を設定 -->
{$name|concat= (Admin)} <!-- テキストを連結 -->
変数コマンド
{$content|striptag|trim|escape} <!-- 複数のフィルターをチェーン -->
コメント
テンプレートコメントは出力から完全に削除され、最終的な HTML に表示されません:
{* これは1行のテンプレートコメントです *}
{*
これは複数行の
テンプレートコメントです
複数行にわたります
*}
<h1>{$title}</h1>
{* デバッグコメント: title 変数が動作するかを確認 *}
<p>{$content}</p>
注意: テンプレートコメント {* ... *} は HTML コメント <!-- ... --> と異なります。テンプレートコメントは処理中に削除され、ブラウザに到達しません。
例のプロジェクト構造
project/
├── source/
│ ├── layouts/
│ │ └── default.php
│ ├── components/
│ │ ├── header.php
│ │ └── footer.php
│ ├── css/
│ │ ├── bootstrap.min.css
│ │ └── custom.css
│ ├── js/
│ │ ├── app.js
│ │ └── bootstrap.min.js
│ └── homepage.php
├── public/
│ └── assets/ # 生成されたアセット
│ ├── css/
│ └── js/
└── vendor/Awesome-plugins/easy_query
EasyQuery
knifelemon/easy-query は、軽量で流暢な SQL クエリビルダーであり、プリペアドステートメント用の SQL とパラメータを生成します。SimplePdo と連携します。
機能
- 🔗 Fluent API - 読みやすいクエリ構築のためのメソッドチェーン
- 🛡️ SQL インジェクション保護 - プリペアドステートメントによる自動パラメータバインド
- 🔧 Raw SQL サポート -
raw()で生の SQL 式を挿入 - 📝 複数のクエリタイプ - SELECT、INSERT、UPDATE、DELETE、COUNT
- 🔀 JOIN サポート - 別名付きの INNER、LEFT、RIGHT ジョイン
- 🎯 高度な条件 - LIKE、IN、NOT IN、BETWEEN、比較演算子
- 🌐 データベース非依存 - SQL + パラメータを返すため、任意の DB 接続で使用可能
- 🪶 軽量 - 依存関係ゼロの最小フットプリント
インストール
composer require knifelemon/easy-query
クイックスタート
use KnifeLemon\EasyQuery\Builder;
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit(10)
->build();
// FlightのSimplePdoと使用
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
build() の理解
build() メソッドは、sql と params を持つ配列を返します。この分離により、プリペアドステートメントを使用してデータベースを安全に保ちます。
$q = Builder::table('users')
->where(['email' => 'user@example.com'])
->build();
// 返される値:
// [
// 'sql' => 'SELECT * FROM users WHERE email = ?',
// 'params' => ['user@example.com']
// ]
クエリタイプ
SELECT
// すべての列を選択
$q = Builder::table('users')->build();
// SELECT * FROM users
// 特定の列を選択
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->build();
// SELECT id, name, email FROM users
// テーブル別名付き
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name'])
->build();
// SELECT u.id, u.name FROM users AS u
INSERT
$q = Builder::table('users')
->insert([
'name' => 'John Doe',
'email' => 'john@example.com',
'status' => 'active'
])
->build();
// INSERT INTO users SET name = ?, email = ?, status = ?
Flight::db()->runQuery($q['sql'], $q['params']);
$userId = Flight::db()->lastInsertId();
UPDATE
$q = Builder::table('users')
->update(['status' => 'inactive', 'updated_at' => date('Y-m-d H:i:s')])
->where(['id' => 123])
->build();
// UPDATE users SET status = ?, updated_at = ? WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
DELETE
$q = Builder::table('users')
->delete()
->where(['id' => 123])
->build();
// DELETE FROM users WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
COUNT
$q = Builder::table('users')
->count()
->where(['status' => 'active'])
->build();
// SELECT COUNT(*) AS cnt FROM users WHERE status = ?
$count = Flight::db()->fetchField($q['sql'], $q['params']);
WHERE 条件
単純な等価性
$q = Builder::table('users')
->where(['id' => 123, 'status' => 'active'])
->build();
// WHERE id = ? AND status = ?
比較演算子
$q = Builder::table('users')
->where([
'age' => ['>=', 18],
'score' => ['<', 100],
'name' => ['!=', 'admin']
])
->build();
// WHERE age >= ? AND score < ? AND name != ?
LIKE
$q = Builder::table('users')
->where(['name' => ['LIKE', '%john%']])
->build();
// WHERE name LIKE ?
IN / NOT IN
// IN
$q = Builder::table('users')
->where(['id' => ['IN', [1, 2, 3, 4, 5]]])
->build();
// WHERE id IN (?, ?, ?, ?, ?)
// NOT IN
$q = Builder::table('users')
->where(['status' => ['NOT IN', ['banned', 'deleted']]])
->build();
// WHERE status NOT IN (?, ?)
BETWEEN
$q = Builder::table('products')
->where(['price' => ['BETWEEN', [100, 500]]])
->build();
// WHERE price BETWEEN ? AND ?
OR 条件
orWhere() を使用して OR グループ化された条件を追加します:
$q = Builder::table('users')
->where(['status' => 'active'])
->orWhere([
'role' => 'admin',
'permissions' => ['LIKE', '%manage%']
])
->build();
// WHERE status = ? AND (role = ? OR permissions LIKE ?)
JOIN
INNER JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name', 'p.title'])
->innerJoin('posts', 'u.id = p.user_id', 'p')
->build();
// SELECT u.id, u.name, p.title FROM users AS u INNER JOIN posts AS p ON u.id = p.user_id
LEFT JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.name', 'o.total'])
->leftJoin('orders', 'u.id = o.user_id', 'o')
->build();
// ... LEFT JOIN orders AS o ON u.id = o.user_id
複数の JOIN
$q = Builder::table('orders')
->alias('o')
->select(['o.id', 'u.name AS customer', 'p.title AS product'])
->innerJoin('users', 'o.user_id = u.id', 'u')
->leftJoin('order_items', 'o.id = oi.order_id', 'oi')
->leftJoin('products', 'oi.product_id = p.id', 'p')
->where(['o.status' => 'completed'])
->build();
並べ替え、グループ化、制限
ORDER BY
$q = Builder::table('users')
->orderBy('created_at DESC')
->build();
// ORDER BY created_at DESC
GROUP BY
$q = Builder::table('orders')
->select(['user_id', 'COUNT(*) as order_count'])
->groupBy('user_id')
->build();
// SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id
LIMIT と OFFSET
$q = Builder::table('users')
->limit(10)
->build();
// LIMIT 10
$q = Builder::table('users')
->limit(10, 20) // limit, offset
->build();
// LIMIT 10 OFFSET 20
Raw SQL 式
SQL 関数やバインドパラメータとして扱われない式が必要な場合に raw() を使用します。
基本的な Raw
$q = Builder::table('users')
->update([
'login_count' => Builder::raw('login_count + 1'),
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => 123])
->build();
// SET login_count = login_count + 1, updated_at = NOW()
バインドパラメータ付き Raw
$q = Builder::table('orders')
->update([
'total' => Builder::raw('COALESCE(subtotal, ?) + ?', [0, 10])
])
->where(['id' => 1])
->build();
// SET total = COALESCE(subtotal, ?) + ?
// params: [0, 10, 1]
WHERE 内の Raw (サブクエリ)
$q = Builder::table('products')
->where([
'price' => ['>', Builder::raw('(SELECT AVG(price) FROM products)')]
])
->build();
// WHERE price > (SELECT AVG(price) FROM products)
ユーザー入力のための安全な識別子
列名がユーザー入力から来る場合、safeIdentifier() を使用して SQL インジェクションを防ぎます:
$sortColumn = $_GET['sort']; // 例: 'created_at'
$safeColumn = Builder::safeIdentifier($sortColumn);
$q = Builder::table('users')
->orderBy($safeColumn . ' DESC')
->build();
// ユーザーが試した場合: "name; DROP TABLE users--"
// InvalidArgumentException をスロー
ユーザー提供の列名のための rawSafe
$userColumn = $_GET['aggregate_column'];
$q = Builder::table('orders')
->select([
Builder::rawSafe('SUM({col})', ['col' => $userColumn])->value . ' AS total'
])
->build();
// 列名を検証し、無効な場合は例外をスロー
警告: ユーザー入力を
raw()に直接連結しないでください。常にバインドパラメータまたはsafeIdentifier()を使用してください。
クエリビルダーの再利用
クリアメソッド
ビルダーを再利用するために特定の部分をクリアします:
$query = Builder::table('users')
->select(['id', 'name'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// 最初のクエリ
$q1 = $query->limit(10)->build();
// クリアして再利用
$query->clearWhere()->clearLimit();
// 異なる条件の2番目のクエリ
$q2 = $query
->where(['status' => 'pending'])
->limit(5)
->build();
利用可能なクリアメソッド
| メソッド | 説明 |
|---|---|
clearWhere() |
WHERE 条件とパラメータをクリア |
clearSelect() |
SELECT 列をデフォルトの '*' にリセット |
clearJoin() |
すべての JOIN 句をクリア |
clearGroupBy() |
GROUP BY 句をクリア |
clearOrderBy() |
ORDER BY 句をクリア |
clearLimit() |
LIMIT と OFFSET をクリア |
clearAll() |
ビルダーを初期状態にリセット |
ページネーションの例
$baseQuery = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// 合計カウントを取得
$countQuery = clone $baseQuery;
$countResult = $countQuery->clearSelect()->count()->build();
$total = Flight::db()->fetchField($countResult['sql'], $countResult['params']);
// ページネーションされた結果を取得
$page = 1;
$perPage = 20;
$listResult = $baseQuery->limit($perPage, ($page - 1) * $perPage)->build();
$users = Flight::db()->fetchAll($listResult['sql'], $listResult['params']);
動的クエリ構築
$query = Builder::table('products')->alias('p');
if (!empty($categoryId)) {
$query->where(['p.category_id' => $categoryId]);
}
if (!empty($minPrice)) {
$query->where(['p.price' => ['>=', $minPrice]]);
}
if (!empty($maxPrice)) {
$query->where(['p.price' => ['<=', $maxPrice]]);
}
if (!empty($searchTerm)) {
$query->where(['p.name' => ['LIKE', "%{$searchTerm}%"]]);
}
$result = $query->orderBy('p.created_at DESC')->limit(20)->build();
$products = Flight::db()->fetchAll($result['sql'], $result['params']);
完全な FlightPHP の例
use KnifeLemon\EasyQuery\Builder;
// ページネーション付きのユーザー一覧
Flight::route('GET /users', function() {
$page = (int) (Flight::request()->query['page'] ?? 1);
$perPage = 20;
$q = Builder::table('users')
->select(['id', 'name', 'email', 'created_at'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit($perPage, ($page - 1) * $perPage)
->build();
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Flight::json(['users' => $users, 'page' => $page]);
});
// ユーザー作成
Flight::route('POST /users', function() {
$data = Flight::request()->data;
$q = Builder::table('users')
->insert([
'name' => $data->name,
'email' => $data->email,
'created_at' => Builder::raw('NOW()')
])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['id' => Flight::db()->lastInsertId()]);
});
// ユーザー更新
Flight::route('PUT /users/@id', function($id) {
$data = Flight::request()->data;
$q = Builder::table('users')
->update([
'name' => $data->name,
'email' => $data->email,
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
// ユーザー削除
Flight::route('DELETE /users/@id', function($id) {
$q = Builder::table('users')
->delete()
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
API リファレンス
静的メソッド
| メソッド | 説明 |
|---|---|
Builder::table(string $table) |
テーブルの新しいビルダーインスタンスを作成 |
Builder::raw(string $sql, array $bindings = []) |
生の SQL 式を作成 |
Builder::rawSafe(string $expr, array $identifiers, array $bindings = []) |
安全な識別子置換付きの生の式 |
Builder::safeIdentifier(string $identifier) |
列名/テーブル名を検証して安全なものを返す |
インスタンスメソッド
| メソッド | 説明 |
|---|---|
alias(string $alias) |
テーブル別名を設定 |
select(string\|array $columns) |
選択する列を設定 (デフォルト: '*') |
where(array $conditions) |
WHERE 条件 (AND) を追加 |
orWhere(array $conditions) |
OR WHERE 条件を追加 |
join(string $table, string $condition, string $alias, string $type) |
JOIN 句を追加 |
innerJoin(string $table, string $condition, string $alias) |
INNER JOIN を追加 |
leftJoin(string $table, string $condition, string $alias) |
LEFT JOIN を追加 |
groupBy(string $groupBy) |
GROUP BY 句を追加 |
orderBy(string $orderBy) |
ORDER BY 句を追加 |
limit(int $limit, int $offset = 0) |
LIMIT と OFFSET を追加 |
count(string $column = '*') |
クエリを COUNT に設定 |
insert(array $data) |
クエリを INSERT に設定 |
update(array $data) |
クエリを UPDATE に設定 |
delete() |
クエリを DELETE に設定 |
build() |
['sql' => ..., 'params' => ...] を構築して返す |
get() |
build() のエイリアス |
Tracy デバッガーの統合
EasyQuery は、インストールされている場合に Tracy デバッガーと自動的に統合します。設定は不要です!
composer require tracy/tracy
use Tracy\Debugger;
Debugger::enable();
// すべてのクエリが Tracy パネルに自動的にログ出力されます
$q = Builder::table('users')->where(['status' => 'active'])->build();
Tracy パネルには以下が表示されます:
- クエリの合計とタイプ別の内訳
- 生成された SQL (構文強調表示)
- パラメータ配列
- クエリ詳細 (テーブル、WHERE、JOIN など)
完全なドキュメントについては、GitHub リポジトリ をご覧ください。
Awesome-plugins/twig
Twig
Twig は、PHP向けの柔軟で高速かつ安全なテンプレートエンジンです。Symfonyやその他の多くのプロジェクトで使用されているテンプレート言語であり、AIコーディングツールや大多数のPHP開発者がその構文をよく理解しています。Twigはテンプレートを最適化されたPHPにコンパイルし、デフォルトで出力を自動エスケープ(XSS対策に有効)し、フィルタ、関数、拡張機能で簡単に拡張できます。
インストール
composerでインストールします。
composer require twig/twig
基本設定
開始するための基本的な設定オプションがいくつかあります。詳細はTwigドキュメントでご覧いただけます。
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Twigがコンパイル済みテンプレートを保存する場所
'cache' => __DIR__ . '/../cache/twig',
// ソースが変更されたときにテンプレートを再コンパイル(開発時に便利)
'auto_reload' => true,
]);
echo $twig->render($template, $data);
});
Twigをビュークラスとして登録する
単一のTwig環境を再利用したい場合(本番環境で推奨)、それを登録してrenderをそれに向けることができます:
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
],
]);
$app->map('render', function(string $template, array $data): void {
echo Flight::view()->render($template, $data);
});
シンプルなレイアウト例
これはレイアウトファイルの簡単な例です。このファイルは他のすべてのビューをラップするために使用されます。
{# app/views/layout.twig #}
<!doctype html>
<html lang="en">
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
{# ここにナビゲーション要素を配置 #}
</nav>
</header>
<div id="content">
{# ここが魔法です #}
{% block content %}{% endblock %}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
そして、コンテンツブロック内にレンダリングされるファイルを作成します:
{# app/views/home.twig #}
{# これはTwigにこのファイルが「layout.twigの中にある」ことを伝えます #}
{% extends 'layout.twig' %}
{# これはコンテンツブロック内のレイアウト内にレンダリングされるコンテンツです #}
{% block content %}
<h1>Home Page</h1>
<p>Welcome to my app!</p>
{% endblock %}
その後、関数またはコントローラー内でこれをレンダリングする場合、以下のようにします:
// シンプルなルート
Flight::route('/', function () {
Flight::render('home.twig', [
'title' => 'Home Page'
]);
});
// またはコントローラーを使用する場合
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.twig', [
'title' => 'Home Page'
]);
}
}
Twigを最大限に活用する方法の詳細については、Twigドキュメントをご覧ください!
デバッグ
Twigには、テンプレート内で使用できるdump()関数を追加するデバッグ拡張機能が付属しています。開発時のみ有効にします:
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'debug' => true, // dump()関数に必要
'auto_reload' => true,
],
], function (\Twig\Environment $twig): void {
$twig->addExtension(new \Twig\Extension\DebugExtension());
});
テンプレート内では:
{{ dump(user) }}
また、PHPレベルのデバッグ用にTracyとTwigを組み合わせることもできます。テンプレートレベルのメトリクス(レンダリング時間、メモリ、実行されたテンプレート/ブロック)については、flightphp/tracy-extensionsのオプションのTwigパネルを使用します:Twig\Profiler\Profileをtwig_profileとしてTracyExtensionLoaderに渡します。オプションのTwigTracyExtensionは、Tracyがオンのときにテンプレート内で{{ dump() }} / {{ bdump() }} / {{ dumpe() }}を公開します。
セキュリティに関する注意
Twigはデフォルトで出力を自動エスケープするため、XSS攻撃から保護するのに役立ちます。テキストには{{ variable }}を優先してください。HTMLコンテンツを意図的に信頼する場合(例:サーバーサイドで既に処理済みのサニタイズされたMarkdown)のみ、|rawフィルタを使用してください。
Awesome-plugins/session
FlightPHP セッション - 軽量なファイルベースのセッション ハンドラ
これは、Flight PHP Framework 向けの軽量でファイルベースのセッション ハンドラ プラグインです。ノンブロッキングのセッション読み込み、オプションの暗号化、オートコミット機能、開発用のテスト モードなどの機能を提供し、セッション管理を簡単かつ強力にします。セッション データはファイルに保存されるため、データベースを必要としないアプリケーションに理想的です。
データベースを使用したい場合は、同じ機能の多くを持つがデータベース バックエンドを備えた ghostff/session プラグインを参照してください。
完全なソース コードと詳細については、Github リポジトリを訪問してください。
インストール
Composer を介してプラグインをインストールします:
composer require flightphp/session
基本的な使用方法
Flight アプリケーションで flightphp/session プラグインを使用する簡単な例です:
require 'vendor/autoload.php';
use flight\Session;
$app = Flight::app();
// セッション サービスを登録
$app->register('session', Session::class);
// セッションを使用した例のルート
Flight::route('/login', function() {
$session = Flight::session();
$session->set('user_id', 123);
$session->set('username', 'johndoe');
$session->set('is_admin', false);
echo $session->get('username'); // 出力: johndoe
echo $session->get('preferences', 'default_theme'); // 出力: default_theme
if ($session->get('user_id')) {
Flight::json(['message' => 'User is logged in!', 'user_id' => $session->get('user_id')]);
}
});
Flight::route('/logout', function() {
$session = Flight::session();
$session->clear(); // すべてのセッション データをクリア
Flight::json(['message' => 'Logged out successfully']);
});
Flight::start();
重要なポイント
- Non-Blocking: デフォルトで
read_and_closeを使用し、セッション ロックの問題を防ぎます。 - Auto-Commit: デフォルトで有効なので、シャットダウン時に変更が自動的に保存されますが、無効にすることもできます。
- File Storage: セッションはデフォルトでシステムの temp ディレクトリの下の
/flight_sessionsに保存されます。
構成
登録時にオプションの配列を渡すことで、セッション ハンドラをカスタマイズできます:
// はい、二重配列です :)
$app->register('session', Session::class, [ [
'save_path' => '/custom/path/to/sessions', // セッション ファイルのディレクトリ
'prefix' => 'myapp_', // セッション ファイルのプレフィックス
'encryption_key' => 'a-secure-32-byte-key-here', // 暗号化を有効にする (AES-256-CBC のために 32 バイト推奨)
'auto_commit' => false, // オートコミットを無効にして手動制御
'start_session' => true, // 自動的にセッションを開始 (デフォルト: true)
'test_mode' => false, // 開発用のテスト モードを有効
'serialization' => 'json', // シリアル化方法: 'json' (デフォルト) または 'php' (レガシー)
] ]);
構成オプション
| Option | Description | Default Value |
|---|---|---|
save_path |
セッション ファイルが保存されるディレクトリ | sys_get_temp_dir() . '/flight_sessions' |
prefix |
保存されたセッション ファイルのプレフィックス | sess_ |
encryption_key |
AES-256-CBC 暗号化のためのキー (オプション) | null (暗号化なし) |
auto_commit |
シャットダウン時にセッション データを自動保存 | true |
start_session |
自動的にセッションを開始 | true |
test_mode |
PHP セッションに影響を与えないテスト モードで実行 | false |
test_session_id |
テスト モード用のカスタム セッション ID (オプション) | 設定されていない場合ランダム生成 |
serialization |
シリアル化方法: 'json' (デフォルト、安全) または 'php' (レガシー、オブジェクトを許可) | 'json' |
シリアル化モード
このライブラリはデフォルトで JSON シリアル化 を使用し、セッション データの安全性が高く、PHP オブジェクト注入の脆弱性を防ぎます。セッションに PHP オブジェクトを保存する必要がある場合 (ほとんどのアプリでは推奨されません) は、レガシーの PHP シリアル化を選択できます:
'serialization' => 'json'(デフォルト):- セッション データに配列とプリミティブのみを許可。
- より安全: PHP オブジェクト注入に耐性あり。
- ファイルは
J(プレーン JSON) またはF(暗号化 JSON) でプレフィックス付け。
'serialization' => 'php':- PHP オブジェクトの保存を許可 (注意して使用)。
- ファイルは
P(プレーン PHP シリアル化) またはE(暗号化 PHP シリアル化) でプレフィックス付け。
注: JSON シリアル化を使用している場合、オブジェクトを保存しようとすると例外が発生します。
高度な使用方法
手動コミット
オートコミットを無効にした場合、変更を手動でコミットする必要があります:
$app->register('session', Session::class, ['auto_commit' => false]);
Flight::route('/update', function() {
$session = Flight::session();
$session->set('key', 'value');
$session->commit(); // 変更を明示的に保存
});
暗号化によるセッション セキュリティ
機密データを保護するために暗号化を有効にします:
$app->register('session', Session::class, [
'encryption_key' => 'your-32-byte-secret-key-here'
]);
Flight::route('/secure', function() {
$session = Flight::session();
$session->set('credit_card', '4111-1111-1111-1111'); // 自動的に暗号化
echo $session->get('credit_card'); // 取得時に復号化
});
セッション ID の再生成
セキュリティのために (例: ログイン後) セッション ID を再生成します:
Flight::route('/post-login', function() {
$session = Flight::session();
$session->regenerate(); // 新しい ID、データを保持
// または
$session->regenerate(true); // 新しい ID、古いデータを削除
});
ミドルウェアの例
セッション ベースの認証でルートを保護します:
Flight::route('/admin', function() {
Flight::json(['message' => 'Welcome to the admin panel']);
})->addMiddleware(function() {
$session = Flight::session();
if (!$session->get('is_admin')) {
Flight::halt(403, 'Access denied');
}
});
これはミドルウェアでの簡単な例です。より詳細な例については、middleware ドキュメントを参照してください。
メソッド
Session クラスは以下のメソッドを提供します:
set(string $key, $value): セッションに値を保存。get(string $key, $default = null): 値を取得し、キーが存在しない場合にデフォルト値をオプションで指定。delete(string $key): 特定のキーをセッションから削除。clear(): すべてのセッション データを削除しますが、同じファイル名を保持。commit(): 現在のセッション データをファイル システムに保存。id(): 現在のセッション ID を返します。regenerate(bool $deleteOldFile = false): セッション ID を再生成し、新しいセッション ファイルを作成します。古いデータを保持し、古いファイルはシステムに残ります。$deleteOldFileがtrueの場合、古いセッション ファイルを削除。destroy(string $id): 指定された ID のセッションを破棄し、セッション ファイルをシステムから削除します。これはSessionHandlerInterfaceの一部で、$idは必須です。典型的な使用例は$session->destroy($session->id())です。getAll(): 現在のセッションのすべてのデータを返します。
get() と id() を除くすべてのメソッドは、チェイニングのために Session インスタンスを返します。
このプラグインを使う理由
- Lightweight: 外部依存なし—just ファイルのみ。
- Non-Blocking: デフォルトで
read_and_closeを使用してセッション ロックを回避。 - Secure: 機密データ用の AES-256-CBC 暗号化をサポート。
- Flexible: オートコミット、テスト モード、手動制御のオプション。
- Flight-Native: Flight フレームワーク専用に構築。
技術詳細
- Storage Format: セッション ファイルは構成された
save_pathにsess_でプレフィックス付けされて保存されます。ファイル コンテンツのプレフィックス:J: プレーン JSON (デフォルト、暗号化なし)F: 暗号化 JSON (デフォルト、暗号化あり)P: プレーン PHP シリアル化 (レガシー、暗号化なし)E: 暗号化 PHP シリアル化 (レガシー、暗号化あり)
- Encryption:
encryption_keyが提供された場合、各セッション 書き込みごとにランダム IV を使用して AES-256-CBC を適用。JSON と PHP シリアル化の両方で動作。 - Serialization: JSON がデフォルトで最も安全。PHP シリアル化はレガシー/高度な使用のために利用可能ですが、セキュリティが低い。
- Garbage Collection: 期限切れのセッションをクリーンアップするための PHP の
SessionHandlerInterface::gc()を実装。
貢献
貢献を歓迎します! リポジトリ をフォークし、変更を加えてプル リクエストを送信してください。バグの報告や機能の提案は Github のイシュー トラッカーで行ってください。
ライセンス
このプラグインは MIT ライセンスの下でライセンスされています。詳細は Github リポジトリ を参照してください。
Awesome-plugins/runway
ランナウェイ
ランナウェイは、Flightアプリケーションを管理するためのCLIアプリケーションです。コントローラーの生成、すべてのルートの表示、AIセットアップヘルパー、マイグレーション(スケルトン内)などを実行できます。優れたadocore/php-cliライブラリを基にしています。
コードを表示するにはこちらをクリックしてください。
スキャフォールディングコマンドはofficial skeletonと意図的に連携されており、AI coding toolsと人間が毎回同じパス、名前空間、コンストラクタインジェクションスタイルを取得できるようにしています。
インストール
composerでインストールします。
composer require flightphp/runway
スケルトンはすでにランナウェイに依存しているため、プロジェクトルートからphp runwayを使用します。
基本設定
ランナウェイを初めて実行すると、app/config/config.phpの'runway'キーを介してrunway設定を見つけようとします。
<?php
// app/config/config.php
return [
'runway' => [
'app_root' => 'app/',
'public_root' => 'public/',
// オプション: スケルトンはパブリックエントリのindex_rootも使用します
'index_root' => 'public/index.php',
],
];
注意 - v1.2.0以降、
.runway-config.jsonはapp/config/config.phpに置き換えられ、非推奨となりました。古いプロジェクトをアップグレードする場合はphp runway config:migrateで移行してください。スケルトンは互換性のため、create-project時に小さな.runway-config.jsonを書き込む場合がありますが、今後はconfig.phpのrunwayキーを優先してください。
プロジェクトルート検出
ランナウェイはプロジェクトのルートを検出する機能が十分にあり、サブディレクトリから実行しても検出できます。composer.json、.git、app/config/config.phpなどのインジケータを探して、プロジェクトルートを判断します。つまり、プロジェクト内のどこからでもランナウェイコマンドを実行できるということです!
使用方法
ランナウェイには、Flightアプリケーションを管理するために使用できるいくつかのコマンドがあります。ランナウェイを使用するには、2つの簡単な方法があります。
- スケルトンプロジェクトを使用している場合は、プロジェクトのルートから
php runway [command]を実行できます。 - composer経由でインストールされたパッケージとしてランナウェイを使用している場合は、プロジェクトのルートから
vendor/bin/runway [command]を実行できます。
コマンドリスト
php runwayコマンドを実行すると、利用可能なすべてのコマンドのリストを表示できます。
php runway
インストールに実際に表示されるコマンドのみに依存してください(コアランナウェイコマンドと、スケルトンのmigrateのようなプロジェクト固有のコマンド)。
コマンドヘルプ
任意のコマンドで--helpフラグを渡すと、コマンドの使用方法に関する詳細情報を取得できます。
php runway routes --help
php runway make:controller --help
いくつかの例を以下に示します:
コントローラーの生成
make:controllerは公式スケルトンレイアウトに一致するコントローラーをスキャフォールドします:
| パス | app/Controller/{Name}.php |
| 名前空間 | App\Controller |
| スタイル | flight\Engineのコンストラクタインジェクション(クラス本体にFlight::なし) |
php runway make:controller MyController
# → app/Controller/MyController.php
# namespace App\Controller;
期待される形状の例(簡略化):
<?php
declare(strict_types=1);
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// 例: $this->app->render('…', […]);
}
}
Diceがコントローラーを構築できるようにクラス呼び出し可能で登録します:
// app/config/routes.php
use App\Controller\MyController;
$router->get('/mine', [MyController::class, 'index']);
このレイアウトの理由は? フォルダーの大文字小文字は、LinuxでのComposer PSR-4のために名前空間と一致する必要があります(controllersではなくController) - Autoloadingを参照してください。ルートとスコープ付きAGENTS.mdファイルがAIツールに使用するよう指示するパスも同じであり、生成されたコントローラーと手書きのコントローラーが同一に保たれます。
古いドキュメントやコミュニティプロジェクトでは、
app/controllers/とapp\controllersが使用されることがありました。あなたのツリーがまだ小文字のフォルダーを使用している場合は、そのまま有効です。新しいスケルトンプロジェクトと現在のmake:controller出力は、app/Controller/+App\Controllerを使用します。
アクティブレコードモデルの生成
まず、Active Recordプラグインをインストールしていることを確認してください。
php runway make:record users
公式スケルトンでは、モデルは名前空間App\Modelでapp/Model/の下に配置され、DB接続はSimplePdo(ActiveRecordコンストラクタに注入または渡す)です。生成されるファイル名と名前空間はランナウェイの現在のデフォルトとrunway設定に従います。新しいモデルをApp\Modelに合わせることで、autoloadingとAGENTS.mdに一致するようにしてください。
スケルトンの投稿デモと一致するモデルの例:
<?php
declare(strict_types=1);
namespace App\Model;
use flight\ActiveRecord;
/**
* @property int $id
* @property string $title
* // …
*/
class Post extends ActiveRecord
{
protected array $relations = [];
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'posts');
}
}
古いジェネレータがまだapp/records / app\recordsを出力する場合は、レガシーアプリでその規約を維持するか、ファイルをapp/Model/に移動して名前空間をフォルダーケースに合わせて更新できます。
マイグレーション(スケルトン)
公式スケルトンには、app/commands/から検出されたプロジェクトコマンド(以下のようなもの)が付属しています:
php runway migrate
マイグレーションはmigrations/の下にあるSQLファイル(SQLiteの場合はYYYYMMDDHHMMSS_description.sql、MySQLの場合は…_description.mysql.sqlなど)で、データベースドライバ設定/環境から選択されます。正確なフラグと動作はそのプロジェクトコマンドによって定義されています - アプリでphp runway migrate --helpを実行してください。
AIヘルパー
ランナウェイはAI & developer experienceで使用されるAI指向のコマンドを公開しています:
php runway ai:init
php runway ai:generate-instructions
これらはLLM認証情報を保存し、プロジェクトの指示(主にAGENTS.md)を生成します。スケルトンでは、AGENTS.md(およびapp/の下のスコープ付きコピー)とSECURITY.mdをエージェントの真実のソースとして扱ってください。
すべてのルートの表示
現在Flightに登録されているすべてのルートを表示します。
php runway routes
特定のルートのみを表示したい場合は、フラグを渡してルートをフィルタリングできます。
# GETルートのみを表示
php runway routes --get
# POSTルートのみを表示
php runway routes --post
# など
ランナウェイへのカスタムコマンドの追加
Flight用のパッケージを作成する場合、またはプロジェクトに独自のカスタムコマンドを追加したい場合は、プロジェクト/パッケージのsrc/commands/、flight/commands/、app/commands/、またはcommands/ディレクトリを作成することで実行できます。さらなるカスタマイズが必要な場合は、以下の設定セクションを参照してください。
スケルトンでは、プロジェクトコマンドは名前空間App\Commandでapp/commands/に配置されます。ランナウェイはパスでそれらを検出します。そのフォルダーは、プロジェクトがすでにComposer classmap/PSR-4と同期しているようにしてください。
コマンドを作成するには、AbstractBaseCommandクラスを拡張し、最低限__constructメソッドとexecuteメソッドを実装します。
<?php
declare(strict_types=1);
namespace App\Command;
use flight\commands\AbstractBaseCommand;
class ExampleCommand extends AbstractBaseCommand
{
/**
* コンストラクタ
*
* @param array<string,mixed> $config app/config/config.phpからの設定
*/
public function __construct(array $config)
{
parent::__construct('make:example', 'ドキュメントの例を作成します', $config);
$this->argument('<funny-gif>', '面白いgifの名前');
}
/**
* 関数を実行します
*
* @return void
*/
public function execute()
{
$io = $this->app()->io();
$io->info('例を作成中...');
// ここで何かを実行
$io->ok('例が作成されました!');
}
}
Flightアプリケーションに独自のカスタムコマンドを構築する方法の詳細については、adhocore/php-cli Documentationを参照してください!
設定管理
設定はv1.2.0以降、app/config/config.phpに移動したため、設定を管理するためのヘルパーコマンドがいくつかあります。
スケルトンのヒント:
config.phpをリテラルなPHP値として保持してください。シークレットは.envに属します。config.php内に$_ENV[...]式を使用しないでください -config:setはファイルを静的データとして書き換えるため、シークレットがファイルに焼き付けられる可能性があります。Configurationを参照してください。
古い設定の移行
古い.runway-config.jsonファイルがある場合は、次のコマンドでapp/config/config.phpに簡単に移行できます:
php runway config:migrate
設定値の設定
config:setコマンドを使用して設定値を設定できます。これはファイルを開かずに設定値を更新したい場合に便利です。
php runway config:set app_root "app/"
設定値の取得
config:getコマンドを使用して設定値を取得できます。
php runway config:get app_root
すべてのランナウェイ設定
ランナウェイの設定をカスタマイズする必要がある場合は、app/config/config.phpにこれらの値を設定できます。以下に設定できる追加の設定をいくつか示します:
<?php
// app/config/config.php
return [
// ... 他の設定値 ...
'runway' => [
// アプリケーションのディレクトリが配置されている場所
'app_root' => 'app/',
// ルートインデックスファイルが配置されているディレクトリ
'index_root' => 'public/',
// 他のプロジェクトのルートへのパス
'root_paths' => [
'/home/user/different-project',
'/var/www/another-project'
],
// ベースパスはほとんどの場合設定する必要はありませんが、必要であればここにあります
'base_paths' => [
'/includes/libs/vendor', // ベンダーディレクトリや何かに対して本当にユニークなパスがある場合
],
// ファイナルパスはコマンドファイルを検索するためのプロジェクト内の場所です
'final_paths' => [
'src/diff-path/commands',
'app/module/admin/commands',
],
// フルパスを追加したい場合は、すぐに追加してください(プロジェクトルートからの絶対パスまたは相対パス)
'paths' => [
'/home/user/different-project/src/diff-path/commands',
'/var/www/another-project/app/module/admin/commands',
'app/my-unique-commands'
]
]
];
設定へのアクセス
設定値に効果的にアクセスする必要がある場合は、__constructメソッドまたはapp()メソッドを通じてアクセスできます。app/config/services.phpファイルがある場合、それらのサービスもコマンドで利用できることも重要です。
public function execute()
{
$io = $this->app()->io();
// 設定へのアクセス
$app_root = $this->config['runway']['app_root'];
// データベース接続などのサービスへのアクセス
$database = $this->config['database']
// ...
}
AIヘルパーラッパー
ランナウェイには、AIがコマンドを生成しやすくするためのヘルパーラッパーがいくつかあります。Symfony Consoleに似た方法でaddOptionとaddArgumentを使用できます。これはAIツールを使用してコマンドを生成する場合に役立ちます。
public function __construct(array $config)
{
parent::__construct('make:example', 'ドキュメントの例を作成します', $config);
// モード引数はnull可能で、完全にオプションがデフォルトです
$this->addOption('name', '例の名前', null);
}
関連項目
- Installation - スケルトンツリーとcreate-projectのデフォルト
- Autoloading -
App\とフォルダーケース - Dependency Injection - 生成されたコントローラーのDice + Engineインジェクション
- AI & Developer Experience -
ai:init、ai:generate-instructions、AGENTS.md - Active Record -
make:record/ スケルトンApp\Modelで使用されるモデル - SimplePdo - スケルトンのマイグレーションとモデルで使用されるDB接続
Awesome-plugins/tracy_extensions
Tracy Flight パネル拡張
これはFlightの作業をより豊かにするための拡張機能セットです。
- Flight - すべてのFlight変数を分析。
- Database - ページで実行されたすべてのクエリを分析(データベース接続を正しく初期化した場合)
- Request - すべての
$_SERVER変数を分析し、すべてのグローバルペイロード($_GET、$_POST、$_FILES)を調査 - Session - セッションがアクティブな場合、すべての
$_SESSION変数を分析。 - Twig (オプション) - Twigテンプレートのレンダリング時間、メモリ、およびどのテンプレート/ブロック/マクロが実行されたかを分析(
twig/twigとtwig_profile設定が必要)
これは特にofficial skeletonで便利で、デフォルトでTwigを使用しています:同じレイアウトAIツールに従うと、Tracyバーにも明確に表示されます。
これがパネルです

各パネルにはアプリケーションに関する非常に役立つ情報が表示されます!

コードを表示するにはこちらをクリックしてください。
インストール
composer require flightphp/tracy-extensions --devを実行すれば準備完了です!
Twigはパッケージのハード依存関係ではありません。Twigパネルが必要な場合のみtwig/twigをインストールしてください(スケルトンでは既にビュー用にインストールされています)。
設定
これを開始するために必要な設定はほとんどありません。Tracyデバッガーをこのhttps://tracy.nette.org/en/guideを使用する前に初期化する必要があります:
<?php
use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;
// bootstrapコード
require __DIR__ . '/vendor/autoload.php';
Debugger::enable();
// 環境を指定する必要があるかもしれません:Debugger::enable(Debugger::DEVELOPMENT)
// アプリでデータベース接続を使用する場合、
// 開発専用(本番環境では使用しないでください!)に使用する
// 必要なPDOラッパーがあります
// 通常のPDO接続と同じパラメータを持っています
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// またはFlightフレームワークにこれをアタッチする場合
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// これでクエリを実行するたびに時間、クエリ、パラメータがキャプチャされます
// ドットを接続します
if(Debugger::$showBar === true) {
// Tracyが実際にレンダリングできないため、これをfalseにする必要があります :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
// より多くのコード
Flight::start();
追加設定
セッションデータ
ghostff/sessionのようなカスタムセッションハンドラを使用している場合、任意のセッションデータ配列をTracyに渡すことができ、自動的に出力されます。TracyExtensionLoaderコンストラクタの2番目のパラメータのsession_dataキーで渡します。
use Ghostff\Session\Session;
// またはflight\Sessionを使用;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
if(Debugger::$showBar === true) {
// Tracyが実際にレンダリングできないため、これをfalseにする必要があります :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}
// ルートやその他のもの...
Flight::start();
Twigパネル(オプション)
アプリがTwigを使用している場合(公式スケルトンを含む)、Tracyバーにテンプレートメトリクスを表示できます。TwigのProfileを作成し、ProfilerExtensionを環境にアタッチし、そのプロファイルをtwig_profileキーの下のローダーに渡します。開発時のみプロファイリングをアタッチしてください。
<?php
use flight\debug\tracy\TracyExtensionLoader;
use flight\debug\tracy\TwigTracyExtension;
use Tracy\Debugger;
use Twig\Environment;
use Twig\Extension\ProfilerExtension;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
$loader = new FilesystemLoader(__DIR__ . '/views');
$twig = new Environment($loader, [
'debug' => true,
'cache' => false,
]);
// オプション:テンプレートでTracyダンプヘルパーを公開
// {{ dump(var) }}, {{ bdump(var) }}, {{ dumpe(var) }}
$twig->addExtension(new TwigTracyExtension());
$tracyConfig = [];
if (Debugger::$showBar === true) {
$profile = new Profile();
$twig->addExtension(new ProfilerExtension($profile));
$tracyConfig['twig_profile'] = $profile;
}
if (Debugger::$showBar === true) {
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), $tracyConfig);
}
// Flight::render()をTwigにマッピング(例)
Flight::map('render', function (string $template, array $data = []) use ($twig) {
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
パネルに表示される内容
- Twigの総レンダリング時間とメモリ
- テンプレート/ブロック/マクロの呼び出し回数
- レンダリングされた各テンプレートとその時間とメモリ
Twigタブは、リクエストでテンプレートがレンダリングされなかった場合、またはtwig_profileを省略した場合(またはTwigがインストールされていない場合)に非表示になります - 他のFlightパネルは引き続き動作します。
skeletonスタイルのservices.phpでは、デバッグがオンの時に同じ$profile/ProfilerExtensionを構築し、twig_profileをTracyExtensionLoaderに渡し、$app->render()に共有Twig環境を使用し続けます。
Latte
このセクションではPHP 8.1+が必要です。
プロジェクトにLatteがインストールされている場合、Tracyにはテンプレートを分析するためのLatteとのネイティブ統合があります。Latteインスタンスに拡張機能を登録するだけです(これは上記のTwigパネルではなく、Latte独自のTracyブリッジです)。
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function($template, $data, $block = null) {
$latte = new Latte\Engine;
// その他の設定...
// Tracyデバッグバーが有効な場合のみ拡張機能を追加
if(Debugger::$showBar === true) {
// ここでLatteパネルをTracyに追加
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($template, $data, $block);
});
関連項目
- Tracy - Flight用の基本Tracyセットアップ
- Twig - スケルトンとTwigパネルで使用されるテンプレートエンジン
- Templates - Flightが
renderをTwig/Latteにマッピングする方法 - Installation - スケルトンにはdevにtracy-extensionsが含まれています
Awesome-plugins/apm
FlightPHP APM ドキュメント
FlightPHP APMへようこそ—あなたのアプリ専用のパフォーマンスコーチです!このガイドは、FlightPHPを使ったアプリケーションパフォーマンスモニタリング(APM)のセットアップ、使用、習得への道筋です。遅いリクエストの調査やレイテンシチャートの分析など、私たちがサポートします。アプリをより速く、ユーザーをより満足させ、デバッグセッションをスムーズにしましょう!
Flight Docsサイトのdemoを表示します。

APMが重要な理由
あなたのアプリが忙しいレストランだと想像してみてください。注文にかかる時間やキッチンの混雑状況を追跡する方法がなければ、なぜ顧客が不機嫌に帰ってしまうのかを推測することになります。APMはあなたのスーシェフのようなもので、着信リクエストからデータベースクエリまで、すべてのステップを監視し、遅延の原因を特定します。ページの表示が遅いとユーザーを失います(調査によると、サイトの読み込みに3秒以上かかると53%が離脱します!)が、APMは問題が深刻化する前に発見するのに役立ちます。これは積極的な安心感—「なぜこれが壊れているの?」という瞬間を減らし、「これがどれだけスムーズに動いているか!」という成功体験を増やします。
インストール
Composerで始めましょう:
composer require flightphp/apm
必要なもの:
- PHP 7.4+: 最新のPHPをサポートしながら、LTS Linuxディストリビューションとの互換性を維持します。
- FlightPHP Core v3.15+: 私たちが強化している軽量フレームワーク。
サポートされるデータベース
FlightPHP APMは現在、メトリクスを保存するために以下のデータベースをサポートしています:
- SQLite3: シンプルでファイルベースで、ローカル開発や小規模アプリに最適。ほとんどのセットアップでデフォルトオプション。
- MySQL/MariaDB: 堅牢でスケーラブルなストレージが必要な大規模プロジェクトや本番環境に最適。
設定ステップ(以下参照)でデータベースの種類を選択できます。PHP環境に必要な拡張機能がインストールされていることを確認してください(例:pdo_sqlite または pdo_mysql)。
はじめに
APMの素晴らしさへのステップバイステップ:
1. APMの登録
トラッキングを開始するために、index.php または services.php ファイルに以下を追加します:
use flight\apm\logger\LoggerFactory;
use flight\database\SimplePdo;
use flight\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// データベース接続を追加する場合
// SimplePdo(または開発環境ではTracy ExtensionsのPdoQueryCapture)を推奨。
// オプション配列(5番目の引数)でAPMクエリトラッキングを有効化。
$pdo = new SimplePdo('mysql:host=localhost;dbname=example', 'user', 'pass', null, [
'trackApmQueries' => true, // APM用のクエリキャプチャに必要
]);
$Apm->addPdoConnection($pdo);
ここで何が起こっているか?
LoggerFactory::create()は設定(後述)を取得してロガーをセットアップします—デフォルトでSQLite。Apmが主役です—Flightのイベント(リクエスト、ルート、エラーなど)をリッスンしてメトリクスを収集します。bindEventsToFlightInstance($app)はすべてをあなたのFlightアプリに結びつけます。
プロのヒント: サンプリング アプリが忙しい場合、すべてのリクエストをログに記録するとシステムに負荷がかかる可能性があります。サンプルレート(0.0から1.0)を使用してください:
$Apm = new Apm($ApmLogger, 0.1); // 10%のリクエストをログに記録
これによりパフォーマンスを維持しながら、信頼性の高いデータを提供します。
2. 設定
.runway-config.json を作成するために以下を実行します:
php vendor/bin/runway apm:init
これは何をするか?
- 生のメトリクスのソースと処理されたデータの宛先を尋ねるウィザードを起動します。
- デフォルトはSQLite—例:ソース用に
sqlite:/tmp/apm_metrics.sqlite、宛先用にもう一つ。 - 以下のような設定が作成されます:
{ "apm": { "source_type": "sqlite", "source_db_dsn": "sqlite:/tmp/apm_metrics.sqlite", "storage_type": "sqlite", "dest_db_dsn": "sqlite:/tmp/apm_metrics_processed.sqlite" } }
このプロセスでは、このセットアップのためのマイグレーションを実行するかどうかも尋ねられます。初めて設定する場合は、はいと答えてください。
なぜ2つの場所が必要か? 生のメトリクスは急速に蓄積されます(フィルタリングされていないログのように)。ワーカーはそれらをダッシュボード用の構造化された宛先に処理します。整理された状態を維持します!
3. ワーカーでメトリクスを処理
ワーカーは生のメトリクスをダッシュボード対応データに変換します。一度実行します:
php vendor/bin/runway apm:worker
何をしているか?
- ソース(例:
apm_metrics.sqlite)から読み取ります。 - 最大100のメトリクス(デフォルトのバッチサイズ)を宛先に処理します。
- 完了するか、メトリクスが残っていない場合に停止します。
継続的な実行 ライブアプリの場合、継続的な処理が必要です。オプションは以下の通りです:
-
デーモンモード:
php vendor/bin/runway apm:worker --daemon永久に実行し、メトリクスが来るたびに処理します。開発や小規模なセットアップに最適。
-
Crontab: crontab(
crontab -e)に以下を追加:* * * * * php /path/to/project/vendor/bin/runway apm:worker毎分実行—本番環境に最適。
-
Tmux/Screen: 分離可能なセッションを開始:
tmux new -s apm-worker php vendor/bin/runway apm:worker --daemon # Ctrl+B, then D to detach; `tmux attach -t apm-worker` to reconnectログアウトしても実行を維持します。
-
カスタム調整:
php vendor/bin/runway apm:worker --batch_size 50 --max_messages 1000 --timeout 300--batch_size 50: 一度に50のメトリクスを処理。--max_messages 1000: 1000のメトリクス後に停止。--timeout 300: 5分後に終了。
なぜ重要か? ワーカーがなければ、ダッシュボードは空です。生のログと実用的な洞察の間の橋渡しです。
4. ダッシュボードの起動
アプリの状態を確認:
php vendor/bin/runway apm:dashboard
これは何をするか?
http://localhost:8001/apm/dashboardでPHPサーバーを起動します。- リクエストログ、遅いルート、エラー率などを表示します。
カスタマイズ:
php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php
--host 0.0.0.0: 任意のIPからアクセス可能(リモート表示に便利)。--port 8080: 8001が使用中の場合に異なるポートを使用。--php-path: PHPがPATHにない場合に指定。
ブラウザでURLを開いて探索してください!
本番モード
本番環境では、ファイアウォールやその他のセキュリティ対策があるため、ダッシュボードを実行するためにいくつかのテクニックを試す必要があるかもしれません。いくつかのオプション:
- リバースプロキシの使用: NginxまたはApacheをセットアップしてリクエストをダッシュボードに転送。
- SSHトンネル: サーバーにSSHできる場合、
ssh -L 8080:localhost:8001 youruser@yourserverを使用してダッシュボードをローカルマシンにトンネル。 - VPN: サーバーがVPNの背後にある場合、VPNに接続してダッシュボードに直接アクセス。
- ファイアウォールの設定: あなたのIPまたはサーバーのネットワーク用にポート8001を開く。(または設定したポート)。
- Apache/Nginxの設定: アプリケーションの前にウェブサーバーがある場合、ドメインまたはサブドメインに設定できます。その場合、ドキュメントルートを
/path/to/your/project/vendor/flightphp/apm/dashboardに設定します。
異なるダッシュボードが必要ですか?
独自のダッシュボードを構築できます!独自のダッシュボード用のデータの表示方法については、vendor/flightphp/apm/src/apm/presenterディレクトリを参照してください!
ダッシュボードの機能
ダッシュボードはAPMの本部です—ここで確認できる内容:
- リクエストログ: タイムスタンプ、URL、レスポンスコード、総時間を含むすべてのリクエスト。「詳細」をクリックしてミドルウェア、クエリ、エラーを表示。
- 最も遅いリクエスト: 時間を消費しているトップ5のリクエスト(例:「/api/heavy」が2.5秒)。
- 最も遅いルート: 平均時間によるトップ5のルート—パターンの発見に最適。
- エラー率: 失敗したリクエストの割合(例:2.3%の500エラー)。
- レイテンシパーセンタイル: 95パーセンタイル(p95)と99パーセンタイル(p99)のレスポンスタイム—最悪のケースを把握。
- レスポンスコードチャート: 時間の経過に伴う200、404、500の可視化。
- 長いクエリ/ミドルウェア: トップ5の遅いデータベース呼び出しとミドルウェアレイヤー。
- キャッシュヒット/ミス: キャッシュが役立つ頻度。
その他の機能:
- 「過去1時間」「過去1日」「過去1週間」でフィルタリング。
- 深夜のセッション用のダークモード切り替え。
例:
/users へのリクエストは以下を表示する可能性があります:
- 総時間: 150ms
- ミドルウェア:
AuthMiddleware->handle(50ms) - クエリ:
SELECT * FROM users(80ms) - キャッシュ:
user_listでヒット (5ms)
カスタムイベントの追加
API呼び出しや支払い処理など、任意のものを追跡:
use flight\apm\CustomEvent;
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('api_call', [
'endpoint' => 'https://api.example.com/users',
'response_time' => 0.25,
'status' => 200
]));
どこに表示されるか? ダッシュボードのリクエスト詳細の「カスタムイベント」セクション—見やすいJSONフォーマットで展開可能。
ユースケース:
$start = microtime(true);
$apiResponse = file_get_contents('https://api.example.com/data');
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('external_api', [
'url' => 'https://api.example.com/data',
'time' => microtime(true) - $start,
'success' => $apiResponse !== false
]));
これでそのAPIがアプリを遅くしているかどうかがわかります!
データベースモニタリング
以下のようにPDOクエリを追跡:
use flight\database\SimplePdo;
$pdo = new SimplePdo('sqlite:/path/to/db.sqlite', null, null, null, [
'trackApmQueries' => true, // APM用のクエリキャプチャに必要
]);
$Apm->addPdoConnection($pdo);
取得できる内容:
- クエリテキスト(例:
SELECT * FROM users WHERE id = ?) - 実行時間(例:0.015秒)
- 行数(例:42)
注意:
- オプション: DBトラッキングが必要ない場合はスキップ可能。
- SimplePdo(推奨):
trackApmQueries => trueでSimplePdoを使用。非推奨のPdoWrapperも動作します(5番目のコンストラクタ引数でtrue)。生のコアPDOはまだフックされていません—お楽しみに! - パフォーマンス警告: DB負荷の高いサイトで毎回のクエリをログに記録するとパフォーマンスが低下する可能性があります。サンプリング(
$Apm = new Apm($ApmLogger, 0.1))を使用して負荷を軽減してください。
出力例:
- クエリ:
SELECT name FROM products WHERE price > 100 - 時間: 0.023秒
- 行数: 15
ワーカーオプション
好みに合わせてワーカーを調整:
--timeout 300: 5分後に停止—テストに適しています。--max_messages 500: 500メトリクスでキャップ—有限に保ちます。--batch_size 200: 一度に200を処理—速度とメモリのバランス。--daemon: ノンストップで実行—ライブモニタリングに最適。
例:
php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600
1時間実行し、一度に100のメトリクスを処理します。
アプリ内のリクエストID
各リクエストには追跡用のユニークなリクエストIDがあります。アプリ内でこのIDを使用してログとメトリクスを関連付けることができます。例えば、エラーページにリクエストIDを追加できます:
Flight::map('error', function($message) {
// レスポンスヘッダーX-Flight-Request-IdからリクエストIDを取得
$requestId = Flight::response()->getHeader('X-Flight-Request-Id');
// さらにFlight変数から取得することも可能
// この方法はswooleやその他の非同期プラットフォームではうまく動作しません。
// $requestId = Flight::get('apm.request_id');
echo "Error: $message (Request ID: $requestId)";
});
アップグレード
APMの新しいバージョンにアップグレードする場合、実行する必要があるデータベースマイグレーションがある可能性があります。以下のコマンドを実行することで実行できます:
php vendor/bin/runway apm:migrate
これにより、データベーススキーマを最新バージョンに更新するために必要なマイグレーションが実行されます。
注意: APMデータベースのサイズが大きい場合、これらのマイグレーションには時間がかかる可能性があります。オフピーク時間にこのコマンドを実行することをお勧めします。
0.4.3から0.5.0へのアップグレード
0.4.3から0.5.0にアップグレードする場合、以下のコマンドを実行する必要があります:
php vendor/bin/runway apm:config-migrate
これにより、古い形式の.runway-config.jsonファイルを使用した設定を、新しい形式のconfig.phpファイルにキーと値を保存する形式に移行します。
古いデータの削除
データベースを整理するために、古いデータを削除できます。忙しいアプリを実行していてデータベースサイズを管理したい場合に特に便利です。以下のコマンドを実行することで実行できます:
php vendor/bin/runway apm:purge
これにより、30日以上前のすべてのデータがデータベースから削除されます。--daysオプションに異なる値を渡すことで、日数を調整できます:
php vendor/bin/runway apm:purge --days 7
これにより、7日以上前のすべてのデータがデータベースから削除されます。
トラブルシューティング
困った場合は以下を試してください:
-
ダッシュボードにデータがない場合?
- ワーカーは実行されていますか?
ps aux | grep apm:workerで確認。 - 設定パスは一致していますか?
.runway-config.jsonのDSNが実際のファイルを指しているか確認。 php vendor/bin/runway apm:workerを手動で実行して保留中のメトリクスを処理。
- ワーカーは実行されていますか?
-
ワーカーエラーの場合?
- SQLiteファイルを確認(例:
sqlite3 /tmp/apm_metrics.sqlite "SELECT * FROM apm_metrics_log LIMIT 5")。 - PHPログでスタックトレースを確認。
- SQLiteファイルを確認(例:
-
ダッシュボードが起動しない場合?
- ポート8001が使用中ですか?
--port 8080を使用。 - PHPが見つかりませんか?
--php-path /usr/bin/phpを使用。 - ファイアウォールでブロックされていますか?ポートを開くか、
--host localhostを使用。
- ポート8001が使用中ですか?
-
遅すぎる場合?
- サンプルレートを下げる:
$Apm = new Apm($ApmLogger, 0.05)(5%)。 - バッチサイズを減らす:
--batch_size 20。
- サンプルレートを下げる:
-
例外/エラーが追跡されない場合?
- プロジェクトでTracyが有効になっている場合、Flightのエラーハンドリングをオーバーライドします。Tracyを無効にしてから
Flight::set('flight.handle_errors', true);が設定されていることを確認する必要があります。
- プロジェクトでTracyが有効になっている場合、Flightのエラーハンドリングをオーバーライドします。Tracyを無効にしてから
-
データベースクエリが追跡されない場合?
- 5番目のコンストラクタ引数(オプション配列)として
['trackApmQueries' => true]でSimplePdoを優先。 - 非推奨の
PdoWrapperを使用している場合、5番目の引数にtrueを渡す。 - 接続作成後に
$Apm->addPdoConnection($pdo)を呼び出す。
- 5番目のコンストラクタ引数(オプション配列)として
Awesome-plugins/tracy
Tracy
TracyはFlightで使用できる素晴らしいエラーハンドラです。アプリケーションのデバッグに役立つ複数のパネルを備えています。また、非常に簡単に拡張でき、独自のパネルを追加することも可能です。Flightチームは、flightphp/tracy-extensionsプラグインを使用してFlightプロジェクト向けにいくつかのパネルを作成しました(Flight変数、DBクエリ、リクエスト、セッション、およびプロファイラープロファイルを渡す場合のオプションのTwigパネル—Tracy Extensionsを参照)。
インストール
Composerでインストールします。また、Tracyには本番環境用のエラーハンドリングコンポーネントが付属しているため、dev版ではなく通常版をインストールすることをお勧めします。
composer require tracy/tracy
基本設定
開始するための基本的な設定オプションがあります。詳細についてはTracy Documentationをご覧ください。
require 'vendor/autoload.php';
use Tracy\Debugger;
// Tracyを有効化
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // 明示的に指定する必要がある場合もあります(Debugger::PRODUCTIONも同様)
// Debugger::enable('23.75.345.200'); // IPアドレスの配列を指定することもできます
// エラーと例外が記録される場所です。このディレクトリが存在し、書き込み可能であることを確認してください。
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // すべてのエラーを表示
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // 非推奨通知を除くすべてのエラー
if (Debugger::$showBar) {
$app->set('flight.content_length', false); // デバッガーバーが表示されている場合、Flightはcontent-lengthを設定できません
// 含めている場合はFlight用のTracy Extensionに固有です
// それ以外の場合はコメントアウトしてください。
new TracyExtensionLoader($app);
}
役立つヒント
コードをデバッグする際、データを表示するための非常に便利な関数があります。
bdump($var)- 変数をTracy Barの別パネルにダンプします。dumpe($var)- 変数をダンプした後、すぐに終了します。
Awesome-plugins/active_record
Flight Active Record
アクティブ レコードは、データベース エンティティを PHP オブジェクトにマッピングするものです。簡単に言うと、データベースに users テーブルがある場合、そのテーブルの行をコードベース内の User クラスと $user オブジェクトに「変換」できます。基本例 を参照してください。
GitHub のリポジトリは こちら をクリックしてください。
Basic Example
以下のテーブルがあると仮定しましょう:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
password TEXT
);
このテーブルを表す新しいクラスを設定できます:
/**
* ActiveRecord クラスは通常単数形です
*
* テーブルのプロパティをコメントとしてここに追加することを強く推奨します
*
* @property int $id
* @property string $name
* @property string $password
*/
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
// この方法で設定できます
parent::__construct($database_connection, 'users');
// またはこの方法で
parent::__construct($database_connection, null, [ 'table' => 'users']);
}
}
今、マジックが起こります!
// SQLite の場合
$database_connection = new PDO('sqlite:test.db'); // これは単なる例です。本物のデータベース接続を使用するはずです
// MySQL の場合
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');
// または mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// またはオブジェクトベースでない mysqli の作成
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');
$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// または $user->save();
echo $user->id; // 1
$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// ここで $user->save() を使用できません。更新と判断されるためです!
echo $user->id; // 2
新しいユーザーを追加するのに、これほど簡単だったとは!データベースにユーザーの行が存在する今、それを引き出すにはどうしますか?
$user->find(1); // データベースで id = 1 を検索して返します。
echo $user->name; // 'Bobby Tables'
すべてのユーザーを検索したい場合はどうでしょうか?
$users = $user->findAll();
特定の条件付きで?
$users = $user->like('name', '%mamma%')->findAll();
これがどれほど楽しいかわかりますか?インストールして始めましょう!
Installation
Composer で簡単にインストールします
composer require flightphp/active-record
Usage
これはスタンドアロン ライブラリとして使用するか、Flight PHP Framework と共に使用できます。完全にあなた次第です。
Standalone
コンストラクタに PDO 接続を渡すことを確認してください。
$pdo_connection = new PDO('sqlite:test.db'); // これは単なる例です。本物のデータベース接続を使用するはずです
$User = new User($pdo_connection);
コンストラクタでデータベース接続を毎回設定したくない場合、データベース接続管理 を参照して他のアイデアを見てください!
Flight でメソッドとして登録
Flight PHP Framework を使用している場合、ActiveRecord クラスをサービスとして登録できますが、必須ではありません。
Flight::register('user', 'User', [ $pdo_connection ]);
// コントローラー、関数などで以下のように使用できます。
Flight::user()->find(1);
runway Methods
runway は Flight の CLI ツールで、このライブラリ用のカスタムコマンドがあります。
# Usage
php runway make:record database_table_name [class_name]
# Example
php runway make:record users
これにより、app/records/ ディレクトリに UserRecord.php という新しいクラスが作成され、以下の内容が含まれます:
<?php
declare(strict_types=1);
namespace app\records;
/**
* users テーブルの ActiveRecord クラス。
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $username
* @property string $email
* @property string $password_hash
* @property string $created_dt
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations モデルのリレーションシップを設定します
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [
// 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
];
/**
* コンストラクタ
* @param mixed $databaseConnection データベースへの接続
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
CRUD functions
find($id = null) : boolean|ActiveRecord
1 つのレコードを検索し、現在のオブジェクトに割り当てます。$id を渡すと、主キーに対してその値で検索を実行します。何も渡さない場合、テーブル内の最初のレコードを検索します。
さらに、他のヘルパー メソッドを渡してテーブルをクエリできます。
// 事前に条件を指定してレコードを検索
$user->notNull('password')->orderBy('id DESC')->find();
// 特定の id でレコードを検索
$id = 123;
$user->find($id);
findAll(): array<int,ActiveRecord>
指定したテーブルのすべてのレコードを検索します。
$user->findAll();
isHydrated(): boolean (v0.4.0)
現在のレコードがデータベースから取得(ハイドレート)されている場合に true を返します。
$user->find(1);
// データ付きのレコードが見つかった場合...
$user->isHydrated(); // true
insert(): boolean|ActiveRecord
現在のレコードをデータベースに挿入します。
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
テキストベースの主キー
テキストベースの主キー(例: UUID)がある場合、挿入前に主キー値を 2 つの方法のいずれかで設定できます。
$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // または $user->save();
または、イベントを通じて主キーを自動生成できます。
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
// 上記の配列の代わりにこの方法で primaryKey を設定することもできます。
$this->primaryKey = 'uuid';
}
protected function beforeInsert(self $self) {
$self->uuid = uniqid(); // またはユニーク ID を生成する方法
}
}
挿入前に主キーを設定しない場合、rowid に設定され、データベースが生成しますが、テーブルにそのフィールドが存在しない場合、永続化されません。これがイベントを使用して自動的に処理することを推奨する理由です。
update(): boolean|ActiveRecord
現在のレコードをデータベースに更新します。
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();
save(): boolean|ActiveRecord
現在のレコードをデータベースに挿入または更新します。レコードに id がある場合更新し、そうでない場合は挿入します。
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();
注意: クラスにリレーションシップが定義されている場合、定義、インスタンス化、更新が必要なダーティ データがある場合、それらのリレーションを再帰的に保存します。(v0.4.0 以降)
delete(): boolean
現在のレコードをデータベースから削除します。
$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();
事前の検索を実行して複数のレコードを削除することもできます。
$user->like('name', 'Bob%')->delete();
dirty(array $dirty = []): ActiveRecord
ダーティ データとは、レコード内で変更されたデータを指します。
$user->greaterThan('id', 0)->orderBy('id desc')->find();
// この時点では何も「ダーティ」ではありません。
$user->email = 'test@example.com'; // 変更されたので email は「ダーティ」と見なされます。
$user->update();
// 更新されてデータベースに永続化されたので、ダーティ データはなくなります
$user->password = password_hash()'newpassword'); // これがダーティになります
$user->dirty(); // 何も渡さないとすべてのダーティ エントリがクリアされます。
$user->update(); // ダーティとしてキャプチャされたものが何もないので何も更新されません。
$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // name と password の両方が更新されます。
copyFrom(array $data): ActiveRecord (v0.4.0)
これは dirty() メソッドのエイリアスです。何をしているのかが少し明確です。
$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // name と password の両方が更新されます。
isDirty(): boolean (v0.4.0)
現在のレコードが変更されている場合に true を返します。
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
現在のレコードを初期状態にリセットします。ループ型の動作で使用するのに非常に便利です。true を渡すと、現在のオブジェクトを検索するために使用されたクエリ データもリセットされます(デフォルト動作)。
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);
foreach($users as $user) {
$user_company->reset(); // クリーンな状態から開始
$user_company->user_id = $user->id;
$user_company->company_id = $some_company_id;
$user_company->insert();
}
getBuiltSql(): string (v0.4.1)
find()、findAll()、insert()、update()、または save() メソッドを実行した後、構築された SQL を取得してデバッグに使用できます。
SQL Query Methods
select(string $field1 [, string $field2 ... ])
テーブル内の特定の列のみを選択できます(多くの列を持つ広いテーブルでパフォーマンスが向上します)
$user->select('id', 'name')->find();
from(string $table)
技術的には別のテーブルを選択することもできます!なぜそうしないのですか?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
データベース内の別のテーブルにジョインすることもできます。
$user->join('contacts', 'contacts.user_id = users.id')->find();
where(string $where_conditions)
カスタム where 引数を設定できます(この where 文ではパラメータを設定できません)
$user->where('id=1 AND name="demo"')->find();
セキュリティ 注意 - $user->where("id = '{$id}' AND name = '{$name}'")->find(); のようなことをしたくなるかもしれません。絶対にこれをしないでください!!!これは SQL インジェクション攻撃の脆弱性があります。オンラインにたくさんの記事があります。「sql injection attacks php」を Google 検索してください。このトピックに関する多くの記事が見つかります。このライブラリでこれを適切に扱う方法は、この where() メソッドの代わりに、$user->eq('id', $id)->eq('name', $name)->find(); のようなことを行うことです。絶対にこれをしなければならない場合、PDO ライブラリには $pdo->quote($var) があり、それをエスケープします。quote() を使用した後でのみ、where() 文で使用できます。
group(string $group_by_statement)/groupBy(string $group_by_statement)
特定の条件で結果をグループ化します。
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
返されるクエリを特定の方法でソートします。
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
返されるレコードの数を制限します。2 番目の int が与えられた場合、SQL のようにオフセット、リミットになります。
$user->orderby('name DESC')->limit(0, 10)->findAll();
WHERE conditions
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Where field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Where field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Where field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Where field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Where field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Where field < $value
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
Where field >= $value
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Where field <= $value
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
Where field LIKE $value または field NOT LIKE $value
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Where field IN($value) または field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Where field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
OR Conditions
条件を OR 文でラップすることが可能です。これは startWrap() と endWrap() メソッドを使用するか、フィールドと値の後の条件の 3 番目のパラメータを埋めることで行います。
// Method 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// これは `id = 1 AND (name = 'demo' OR name = 'test')` に評価されます
// Method 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// これは `id = 1 OR name = 'demo'` に評価されます
Relationships
このライブラリを使用して、テーブル間の one->many および one->one リレーションシップを設定できます。これにはクラス内で少し追加の設定が必要です。
$relations 配列を設定するのは簡単ですが、正しい構文を推測するのは混乱するかもしれません。
protected array $relations = [
// キーの名前は任意に付けられます。ActiveRecord の名前が良いでしょう。例: user, contact, client
'user' => [
// 必須
// self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
self::HAS_ONE, // これはリレーションシップのタイプです
// 必須
'Some_Class', // これは参照する「他の」ActiveRecord クラスです
// 必須
// リレーションシップのタイプによって異なります
// self::HAS_ONE = ジョインを参照する外部キー
// self::HAS_MANY = ジョインを参照する外部キー
// self::BELONGS_TO = ジョインを参照するローカルキー
'local_or_foreign_key',
// FYI、これも「他の」モデルの主キーにのみジョインします
// オプション
[ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // ジョイン時の追加条件
// $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))
// オプション
'back_reference_name' // これを自身にバック参照したい場合、例: $user->contact->user;
];
]
class User extends ActiveRecord{
protected array $relations = [
'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
}
class Contact extends ActiveRecord{
protected array $relations = [
'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'contacts');
}
}
これで参照が設定されたので、簡単に使用できます!
$user = new User($pdo_connection);
// 最新のユーザーを検索。
$user->notNull('id')->orderBy('id desc')->find();
// リレーションを使用して連絡先を取得:
foreach($user->contacts as $contact) {
echo $contact->id;
}
// または逆方向に。
$contact = new Contact();
// 1 つの連絡先を検索
$contact->find();
// リレーションを使用してユーザー取得:
echo $contact->user->name; // これはユーザー名です
かなりクールですね?
Eager Loading
Overview
Eager loading は、N+1 クエリ問題を解決し、リレーションシップを事前にロードします。各レコードのリレーションシップごとに別々のクエリを実行する代わりに、リレーションシップごとに 1 つの追加クエリですべての関連データを取得します。
注意: Eager loading は v0.7.0 以降でのみ利用可能です。
Basic Usage
with() メソッドを使用して、eager load するリレーションシップを指定します:
// N+1 の代わりに 2 つのクエリでユーザーとその連絡先をロード
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
foreach ($u->contacts as $contact) {
echo $contact->email; // 追加のクエリなし!
}
}
Multiple Relations
複数のリレーションシップを一度にロード:
$users = $user->with(['contacts', 'profile', 'settings'])->findAll();
Relationship Types
HAS_MANY
// 各ユーザーのすべての連絡先を eager load
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
// $u->contacts はすでに配列としてロードされています
foreach ($u->contacts as $contact) {
echo $contact->email;
}
}
HAS_ONE
// 各ユーザーの 1 つの連絡先を eager load
$users = $user->with('contact')->findAll();
foreach ($users as $u) {
// $u->contact はすでにオブジェクトとしてロードされています
echo $u->contact->email;
}
BELONGS_TO
// すべての連絡先の親ユーザーを eager load
$contacts = $contact->with('user')->findAll();
foreach ($contacts as $c) {
// $c->user はすでにロードされています
echo $c->user->name;
}
With find()
Eager loading は findAll() と find() の両方で動作します:
$user = $user->with('contacts')->find(1);
// ユーザーとすべての連絡先が 2 つのクエリでロードされます
Performance Benefits
Eager loading なし(N+1 問題):
$users = $user->findAll(); // 1 クエリ
foreach ($users as $u) {
$contacts = $u->contacts; // N クエリ(ユーザーごとに 1 つ!)
}
// 合計: 1 + N クエリ
Eager loading あり:
$users = $user->with('contacts')->findAll(); // 合計 2 クエリ
foreach ($users as $u) {
$contacts = $u->contacts; // 追加のクエリ 0!
}
// 合計: 2 クエリ(ユーザー用 1 + すべての連絡先用 1)
10 人のユーザーの場合、クエリが 11 から 2 に減少し、82% の削減です!
Important Notes
- Eager loading は完全にオプションです - 遅延ロードは以前通り動作します
- すでにロードされたリレーションシップは自動的にスキップされます
- バック参照は eager loading で動作します
- リレーションコールバックは eager loading 中に尊重されます
Limitations
- ネストされた eager loading(例: with(['contacts.addresses']) )は現在サポートされていません
- クロージャによる eager load 制約はこのバージョンでサポートされていません
Setting Custom Data
時には ActiveRecord にカスタム計算などのユニークなものをアタッチする必要がある場合があります。これをテンプレートに渡されるオブジェクトにアタッチする方が簡単かもしれません。
setCustomData(string $field, mixed $value)
setCustomData() メソッドを使用してカスタム データをアタッチします。
$user->setCustomData('page_view_count', $page_view_count);
次に、通常のオブジェクト プロパティのように参照します。
echo $user->page_view_count;
Events
このライブラリのもう一つの超すごい機能はイベントについてです。イベントは特定のメソッドを呼び出す特定のタイミングでトリガーされます。データを自動的に設定するのに非常に役立ちます。
onConstruct(ActiveRecord $ActiveRecord, array &config)
デフォルトの接続を設定する必要がある場合に非常に役立ちます。
// index.php または bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
//
//
//
// User.php
class User extends flight\ActiveRecord {
protected function onConstruct(self $self, array &$config) { // & 参照を忘れずに
// 接続を自動的に設定するためにこれを実行できます
$config['connection'] = Flight::db();
// またはこれ
$self->transformAndPersistConnection(Flight::db());
// この方法でテーブル名も設定できます。
$config['table'] = 'users';
}
}
beforeFind(ActiveRecord $ActiveRecord)
毎回クエリ操作が必要な場合にのみ有用です。
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFind(self $self) {
// それがお好みなら id >= 0 を常に実行
$self->gte('id', 0);
}
}
afterFind(ActiveRecord $ActiveRecord)
このレコードが取得されるたびに常にロジックを実行する必要がある場合に、より有用です。何かを復号化する必要がありますか?毎回カスタム カウント クエリを実行する必要がありますか(パフォーマンスは悪いですが、まあ)?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFind(self $self) {
// 何かを復号化
$self->secret = yourDecryptFunction($self->secret, $some_key);
// クエリのようなカスタムなものを保存?
$self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count'];
}
}
beforeFindAll(ActiveRecord $ActiveRecord)
毎回クエリ操作が必要な場合にのみ有用です。
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFindAll(self $self) {
// それがお好みなら id >= 0 を常に実行
$self->gte('id', 0);
}
}
afterFindAll(array<int,ActiveRecord> $results)
afterFind() に似ていますが、すべてのレコードに対して実行できます!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFindAll(array $results) {
foreach($results as $self) {
// afterFind() のように何かクールなことをする
}
}
}
beforeInsert(ActiveRecord $ActiveRecord)
毎回デフォルト値を設定する必要がある場合に非常に役立ちます。
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// いくつかの健全なデフォルトを設定
if(!$self->created_date) {
$self->created_date = gmdate('Y-m-d');
}
if(!$self->password) {
$self->password = password_hash((string) microtime(true));
}
}
}
afterInsert(ActiveRecord $ActiveRecord)
挿入後にデータを変更するユース ケースがあるかもしれません?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// あなた次第
Flight::cache()->set('most_recent_insert_id', $self->id);
// または何でも....
}
}
beforeUpdate(ActiveRecord $ActiveRecord)
更新時に毎回デフォルト値を設定する必要がある場合に非常に役立ちます。
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// いくつかの健全なデフォルトを設定
if(!$self->updated_date) {
$self->updated_date = gmdate('Y-m-d');
}
}
}
afterUpdate(ActiveRecord $ActiveRecord)
更新後にデータを変更するユース ケースがあるかもしれません?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// あなた次第
Flight::cache()->set('most_recently_updated_user_id', $self->id);
// または何でも....
}
}
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
挿入または更新の両方でイベントが発生するようにしたい場合に有用です。長い説明は省きますが、何かわかるはずです。
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeSave(self $self) {
$self->last_updated = gmdate('Y-m-d H:i:s');
}
}
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
ここで何をしたいかわかりませんが、判断はしません!やってみてください!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeDelete(self $self) {
echo 'He was a brave soldier... :cry-face:';
}
}
Database Connection Management
このライブラリを使用する場合、データベース接続をいくつかの方法で設定できます。コンストラクタで設定するか、$config['connection'] で設定するか、setDatabaseConnection() で設定できます (v0.4.1)。
$pdo_connection = new PDO('sqlite:test.db'); // 例
$user = new User($pdo_connection);
// または
$user = new User(null, [ 'connection' => $pdo_connection ]);
// または
$user = new User();
$user->setDatabaseConnection($pdo_connection);
アクティブ レコードを呼び出すたびに $database_connection を常に設定したくない場合、それには方法があります!
// index.php または bootstrap.php
// Flight で登録されたクラスとして設定
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
// User.php
class User extends flight\ActiveRecord {
public function __construct(array $config = [])
{
$database_connection = $config['connection'] ?? Flight::db();
parent::__construct($database_connection, 'users', $config);
}
}
// そして今、引数は不要!
$user = new User();
注意: ユニット テストを計画している場合、この方法でいくつかの課題が生じる可能性がありますが、
setDatabaseConnection()または$config['connection']で接続をインジェクトできるため、全体としてそれほど悪くありません。
データベース接続を更新する必要がある場合、例えば長時間実行される CLI スクリプトを実行していて、定期的に接続を更新する必要がある場合、$your_record->setDatabaseConnection($pdo_connection) で接続を再設定できます。
Contributing
ぜひ貢献してください。 :D
Setup
貢献する場合、composer test-coverage を実行して 100% のテスト カバレッジを維持してください(これは真のユニット テスト カバレッジではなく、統合テストに近いです)。
また、composer beautify と composer phpcs を実行して、リンティング エラーを修正してください。
License
MIT
Awesome-plugins/latte
Latte
Latte は、非常に使いやすく、Twig や Smarty よりも PHP 構文に近いフル機能のテンプレートエンジンです。また、拡張して独自のフィルターや関数を追加することも非常に簡単です。
インストール
Composer でインストールします。
composer require latte/latte
基本設定
開始するための基本的な設定オプションがあります。これらについての詳細は、Latte ドキュメント を参照してください。
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Latte がキャッシュを格納する場所
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
シンプルなレイアウト例
ここにレイアウトファイルのシンプルな例を示します。これは、他のすべてのビューをラップするために使用されるファイルです。
<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
<!-- ここにナビゲーション要素を配置 -->
</nav>
</header>
<div id="content">
<!-- ここが魔法の部分です -->
{block content}{/block}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
そして、content ブロック内にレンダリングされるファイルです:
<!-- app/views/home.latte -->
<!-- これにより、Latte にこのファイルが layout.latte ファイルの「内部」であることを伝えます -->
{extends layout.latte}
<!-- レイアウト内の content ブロック内にレンダリングされるコンテンツです -->
{block content}
<h1>ホームページ</h1>
<p>私のアプリへようこそ!</p>
{/block}
次に、関数やコントローラー内でこれをレンダリングする場合、以下のようにします:
// シンプルなルート
Flight::route('/', function () {
Flight::render('home.latte', [
'title' => 'Home Page'
]);
});
// またはコントローラーを使用する場合
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.latte', [
'title' => 'Home Page'
]);
}
}
Latte を最大限に活用する方法の詳細については、Latte ドキュメント を参照してください!
Tracy を使用したデバッグ
このセクションには PHP 8.1+ が必要です。
Tracy を使用して、Latte テンプレートファイルをすぐにデバッグすることもできます! すでに Tracy をインストールしている場合、Tracy に Latte 拡張を追加する必要があります。
// services.php
use Tracy\Debugger;
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Latte がキャッシュを格納する場所
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
// Tracy デバッグバーが有効な場合のみ拡張を追加します
if (Debugger::$showBar === true) {
// ここで Tracy に Latte パネルを追加します
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($finalPath, $data, $block);
});Awesome-plugins/awesome_plugins
素晴らしいプラグイン
Flightは非常に拡張可能です。Flightアプリケーションに機能を追加するために使用できるプラグインがいくつかあります。一部はFlightチームによって公式にサポートされており、他のものは開始に役立つマイクロ/ライトライブラリです。
AIツール
FlightはAIを活用したプラグインでさらにクールにすることができます。
- Flight MCP - FlightとMCP (Model Control Protocol) を統合するためのプラグインで、シームレスなAIを活用した機能を実現します。主にドキュメントページに焦点を当てており、Flightプロジェクトに関する最新情報を提供することでトークンコストを抑えるのに役立ちます。
APIドキュメント
APIドキュメントはどのAPIにとっても重要です。開発者がAPIとの対話方法や期待される結果を理解するのに役立ちます。FlightプロジェクトのAPIドキュメントを生成するためのツールがいくつか利用可能です。
- FlightPHP OpenAPI Generator - Daniel Schreiber氏による、FlightPHPでOpenAPI Specを使用したAPIファーストアプローチによるAPI構築方法についてのブログ記事です。
- SwaggerUI - Swagger UIはFlightプロジェクトのAPIドキュメントを生成するのに役立つ素晴らしいツールです。非常に使いやすく、ニーズに合わせてカスタマイズできます。これはSwaggerドキュメントを生成するためのPHPライブラリです。
アプリケーションパフォーマンス監視 (APM)
アプリケーションパフォーマンス監視 (APM) はどのアプリケーションにとっても重要です。アプリケーションのパフォーマンスやボトルネックを理解するのに役立ちます。Flightで使用できるAPMツールがいくつかあります。
- official flightphp/apm - Flight APMはFlightアプリケーションを監視するために使用できるシンプルなAPMライブラリです。アプリケーションのパフォーマンスを監視し、ボトルネックを特定するのに役立ちます。
非同期処理
Flightはすでに高速なフレームワークですが、ターボエンジンを搭載することで、さらに楽しく(そして挑戦的になります)!
- flightphp/async - 公式Flight Asyncライブラリ。このライブラリはアプリケーションに非同期処理を追加するシンプルな方法です。Swoole/Openswooleを使用して、シンプルで効果的な非同期タスク実行方法を提供します。
認可/権限
認可と権限は、誰が何にアクセスできるかを制御する必要があるアプリケーションにとって重要です。
- official flightphp/permissions - 公式Flight Permissionsライブラリ。このライブラリは、ユーザーおよびアプリケーションレベルの権限をアプリケーションに追加するシンプルな方法です。
認証
認証は、ユーザーIDの検証やAPIエンドポイントのセキュリティ保護が必要なアプリケーションに不可欠です。
- firebase/php-jwt - PHP用のJSON Web Token (JWT) ライブラリ。Flightアプリケーションでトークンベースの認証を実装するためのシンプルで安全な方法です。ステートレスAPI認証、ミドルウェアによるルートの保護、OAuthスタイルの認可フローの実装に最適です。
キャッシュ
キャッシュはアプリケーションを高速化する素晴らしい方法です。Flightで使用できるキャッシュライブラリがいくつかあります。
- official flightphp/cache - 軽量でシンプル、スタンドアロンのPHPファイル内キャッシュクラス
CLI
CLIアプリケーションはアプリケーションと対話する素晴らしい方法です。コントローラーの生成、すべてのルートの表示などに使用できます。
- official flightphp/runway - RunwayはFlightアプリケーションの管理を支援するCLIアプリケーションです。
クッキー
クッキーはクライアント側に少量のデータを保存する素晴らしい方法です。ユーザー設定、アプリケーション設定などの保存に使用できます。
- overclokk/cookie - PHP Cookieはクッキーの管理を提供するシンプルで効果的なPHPライブラリです。
デバッグ
ローカル環境での開発時には、デバッグが重要です。デバッグ体験を向上させるプラグインがいくつかあります。
- tracy/tracy - Flightで使用できるフル機能のエラーハンドラです。アプリケーションのデバッグに役立つパネルが多数用意されており、非常に拡張しやすく、独自のパネルを追加することもできます。
- official flightphp/tracy-extensions - Tracy エラーハンドラと組み合わせて使用し、Flightプロジェクト特有のデバッグを支援する追加パネルを追加するプラグインです。
データベース
データベースはほとんどのアプリケーションの中核です。データの保存と取得に使用されます。一部のデータベースライブラリはクエリを記述するための単なるラッパーであり、一部は本格的なORMです。
- official flightphp/core SimplePdo - コアの一部である公式Flight PDOヘルパーです。
insert()、update()、delete()、transaction()などの便利なヘルパーメソッドを備えたモダンなラッパーで、データベース操作を簡素化します。すべての結果はCollectionsとして返され、柔軟な配列/オブジェクトアクセスが可能です。ORMではなく、PDOで作業するためのより良い方法です。 - deprecated flightphp/core PdoWrapper - コアの一部である公式Flight PDOラッパー(v3.18.0で非推奨)。代わりにSimplePdoを使用してください。
- official flightphp/active-record - 公式Flight ActiveRecord ORM/Mapper。データベース内のデータの取得と保存を簡単に行うための優れた小さなライブラリです。
- byjg/php-migration - プロジェクトのすべてのデータベース変更を追跡するためのプラグインです。
- knifelemon/easy-query - 準備済みステートメント用のSQLとパラメータを生成する軽量で流暢なSQLクエリビルダー。SimplePdo と連携して動作します。
暗号化
暗号化は機密データを保存するアプリケーションにとって重要です。データの暗号化と復号化はそれほど難しくありませんが、暗号化キーの適切な保存は難しい場合があります。難しい場合があります。難しい場合があります。最も重要なことは、暗号化キーを公開ディレクトリに保存したり、コードリポジトリにコミットしたりしないことです。
- defuse/php-encryption - データの暗号化と復号化に使用できるライブラリです。データの暗号化と復号化を開始するのはかなり簡単です。
ジョブキュー
ジョブキューはタスクを非同期で処理するのに非常に便利です。メールの送信、画像の処理、またはリアルタイムで行う必要のないタスクなどに使用できます。
- n0nag0n/simple-job-queue - Simple Job Queueは非同期でジョブを処理するために使用できるライブラリです。beanstalkd、MySQL/MariaDB、SQLite、PostgreSQLで使用できます。
セッション
セッションはAPIにはあまり役に立ちませんが、Webアプリケーションを構築する場合は、状態とログイン情報を維持するためにセッションが重要になります。
- official flightphp/session - 公式Flight Sessionライブラリ。これはセッションデータの保存と取得に使用できるシンプルなセッションライブラリです。PHPの組み込みセッション処理を使用します。
- Ghostff/Session - PHPセッションマネージャー(ノンブロッキング、フラッシュ、セグメント、セッション暗号化)。セッションデータのオプションの暗号化/復号化にPHP open_sslを使用します。
テンプレート
テンプレートはUIを持つWebアプリケーションの中核です。Flightで使用できるテンプレートエンジンがいくつかあります。
- deprecated flightphp/core View - コアの一部である非常に基本的なテンプレートエンジンです。プロジェクトに数ページ以上のページがある場合は使用しないことをお勧めします。
- latte/latte - Latteは非常に使いやすく、TwigやSmartyよりもPHPの構文に近いと感じるフル機能のテンプレートエンジンです。非常に拡張しやすく、独自のフィルタや関数を追加することもできます。
- twig/twig - Twigは柔軟で高速、セキュアなテンプレートエンジンです(Symfonyで使用されているものと同じ)。AIツールや多くのPHP開発者がよく知っており、デフォルトで出力を自動エスケープし、拡張機能の巨大なエコシステムがあります。
- knifelemon/comment-template - CommentTemplateは、アセットコンパイル、テンプレート継承、変数処理を備えた強力なPHPテンプレートエンジンです。自動CSS/JS縮小化、キャッシュ、Base64エンコード、オプションのFlight PHPフレームワーク統合機能を備えています。
WordPress統合
WordPressプロジェクトでFlightを使用したいですか?それのための便利なプラグインがあります!
- n0nag0n/wordpress-integration-for-flight-framework - このWordPressプラグインは、FlightをWordPressと並行して実行できるようにします。Flightフレームワークを使用してカスタムAPI、マイクロサービス、または完全なアプリをWordPressサイトに追加するのに最適です。両方の世界の良いところを使いたい場合に非常に便利です!
貢献
共有したいプラグインがありますか?プルリクエストを送信してリストに追加してください!
Media
メディア
Flight に関するインターネット上のさまざまな種類のメディアを可能な限り追跡しようとしました。Flight についてさらに学ぶために使用できるさまざまなリソースを以下に示します。
記事と解説
- Unit Testing and SOLID Principles by Brian Fenton (2015?)
- PHP Web Framework Flight by ojambo (2025)
- Define, Generate, and Implement: An API-First Approach with OpenAPI Generator and FlightPHP by Daniel Schreiber (2025)
- Best PHP Micro Frameworks for 2024 by n0nag0n (2024)
- Creating a RESTful API with Flight Framework by n0nag0n (2024)
- Building a Simple Blog with Flight Part 2 by n0nag0n (2024)
- Building a Simple Blog with Flight Part 1 by n0nag0n (2024)
- 🚀 Build a Simple CRUD API in PHP with the Flight Framework by soheil-khaledabadi (2024)
- Building a PHP Web Application with the Flight Micro-framework by Arthur C. Codex (2023)
- Best PHP Frameworks for Web Development in 2024 by Ravikiran A S (2023)
- Top 12 PHP Frameworks: A Comprehensive Guide for 2023 by marketing kbk (2023)
- 5 PHP Frameworks You've (Probably) Never Heard of by n0nag0n (2022)
- 12 top PHP frameworks for web developers to consider in 2023 by Anna Monus (2022)
- The Best PHP Microframeworks on a Cloud Server by Shahzeb Ahmed (2021)
- PHP framework: Top 15 powerful ones for your web development by AHT Tech (2020)
- Easy PHP Routing with FlightPHP by Lucas Conceição (2019)
- Trying Out New PHP Framework (Flight) by Leon (2017)
- Setting up FlightPHP to work with Backbonejs by Timothy Tocci (2015)
動画とチュートリアル
- Build a Flight PHP App with MVC & MariaDB in 10 Minutes! (Beginner Friendly) by ojamboshop (2025)
- Create a REST API for IoT Devices Using PHP & FlightPHP - ESP32 API by IoT Craft Hub (2024)
- PHP Flight Framework Simple Introductory Video by n0nag0n (2024)
- Set header HTTP code in Flightphp (3 Solutions!!) by Roel Van de Paar (2024)
- PHP Flight Framework Tutorial. Super easy API Project! by n0nag0n (2022)
- Aplicación web CRUD con php y mysql y bootstrap usando flight by Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Lighttpd rewrite rule for Flight PHP microframework by Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (Tagalog) by Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (Tagalog) by Info Singkat Official (2020)
- How To Create JSON REST API IN PHP - Part 2 by Codewife (2018)
- How To Create JSON REST API IN PHP - Part 1 by Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel by Codemarket (2016)
- Tutorial 1 Flight PHP - Instalación by absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 by absagg (2014)
何か欠けているものはありますか?
あなたが書いたり録音したりしたものが欠けていませんか?イシューやプルリクエストでお知らせください!
Examples
クイックスタートが必要ですか?
新しい Flight プロジェクトを始めるための 2 つのオプションがあります:
- Full Skeleton Boilerplate: コントローラーとビューを含む、より完全な例。
- Single File Skeleton Boilerplate: アプリを単一のシンプルなファイルで実行するために必要なすべてを含む単一ファイル。
コミュニティ提供の例:
- flightravel: Laravel ディレクトリ付きの FlightPHP、PHP ツール + GH Actions
- fleact - ReactJS 統合の FlightPHP スターターキット。
- flastro - Astro 統合の FlightPHP スターターキット。
- velt - Velt は FlightPHP バックエンド付きの Svelte スターターテンプレートで、クイックで簡単です。
- vite-flightphp - FlightPHPとViteとTailwind CSSを搭載したモダンフロントエンド、ホットリロード機能付き。
インスピレーションが必要ですか?
これらは Flight チームの公式スポンサーではありませんが、Flight で構築した独自のプロジェクトの構造化方法についてのアイデアを提供する可能性があります!
- ASC REST API Spell Checker - FlightPHP と ArPHP ライブラリを使用して構築された、アラビア語のスペルチェックのための軽量 REST API。この API は、誤字検出と修正提案を含むアラビア語テキストのスペルチェック機能を提供します。
- Eventify - Eventify はイベント主催者と参加者を結ぶシングルページアプリです。PHP (FlightPHP)、JavaScript、MySQL で構築され、JWT 認証、イベント管理、OpenAPI を使用した RESTful API ドキュメントを備えています。
- Ivox Car Rental - Ivox Car Rental は PHP (FlightPHP)、JavaScript、MySQL で構築された、シングルページのモバイルフレンドリーなカーレンタルウェブアプリケーションです。ユーザー登録、閲覧、車の予約をサポートし、管理者は車、ユーザー、予約を管理できます。アプリには REST API、JWT 認証、モダンなレンタル体験のためのレスポンシブデザインが備わっています。
- Decay - ゾンビに関する HTMX と SleekDB を備えた Flight v3!(Demo)
- Flight Example Blog - ミドルウェア、コントローラー、Active Record、Latte を備えた Flight v3。
- Flight CRUD RESTful API - Flight フレームワークを使用したシンプルな CRUD API プロジェクトで、新しいユーザーが迅速に PHP アプリケーションを CRUD 操作とデータベース接続でセットアップするための基本構造を提供します。このプロジェクトは RESTful API 開発のための Flight の使用方法を示し、初心者向けの学習ツールおよび経験豊富な開発者向けの便利なスターターキットとして理想的です。
- Flight School Management System - Flight v3
- Paste Bin with Comments - Flight v3
- Basic Skeleton App
- Example Wiki
- The IT-Innovator PHP Framework Application
- LittleEducationalCMS (Spanish)
- Italian Yellow Pages API
- Generic Content Management System (with....very little documentation)
- A tiny php framework based on Flight and medoo.
- Example MVC Application
- Production ready Flight Boilerplate - 開発の数週間を節約する本番環境対応の認証フレームワーク。エンタープライズグレードのセキュリティを備えています:2FA/TOTP、LDAP 統合、Azure SSO、インテリジェントなレート制限、セッション指紋認証、ブルートフォース保護、セキュリティ分析ダッシュボード、包括的な監査ログ、細かなロールベースのアクセス制御。
自分の例を共有したいですか?
共有したいプロジェクトがある場合、このリストに追加するためのプルリクエストを送信してください!
Install/install
インストール手順
Flightをインストールする前に、いくつかの前提条件があります。具体的には以下が必要です:
- システムにPHPをインストール
- 最良の開発体験のためにComposerをインストール
基本的なインストール
Composerを使用している場合は、次のコマンドを実行できます:
composer require flightphp/core
これにより、Flightのコアファイルだけがシステムにインストールされます。プロジェクト構造、レイアウト、依存関係、設定、オートローディングなどは自分で定義する必要があります。この方法では、Flight以外の依存関係はインストールされません。
ファイルをダウンロードして、Webディレクトリに直接展開することもできます。
基本的なインストールは、学習、マイクロAPI、コピー&ペーストの実験に最適です。人間とAIコーディングツールが同じ方法で従える完全なアプリレイアウトが必要な場合は、以下の推奨スケルトンを使用してください。
推奨インストール
新しいプロジェクトには、flightphp/skeletonアプリから始めることを強くお勧めします。インストールは簡単です。
composer create-project flightphp/skeleton my-project/
cd my-project/
composer start
# オプションのサンプルDB + 投稿デモ
php runway migrate
このステップにより、プロジェクト構造、Composer PSR-4オートローディング、設定、およびTracy、Tracy Extensions、Runwayなどのツールがセットアップされます。また、ルートのAGENTS.md(およびapp/配下のスコープ付きコピー)が同梱されているため、AIアシスタントはあなたと同じレイアウトを共有できます。AIと開発者体験を参照してください。
スケルトンが提供するもの
project-root/
├── AGENTS.md # AI / エージェントの情報源
├── SECURITY.md # セキュリティの期待値
├── .env.example # シークレット / デプロイオーバーレイ(.envにコピー)
├── public/index.php # Webエントリのみ
├── app/
│ ├── config/ # bootstrap、routes、services、config_sample.php
│ ├── Controller/ # App\Controller\*(パスカルケースのフォルダ!)
│ ├── Middleware/ # App\Middleware\*
│ ├── Model/ # App\Model\*(ActiveRecord)
│ ├── Utils/ # Config、Env、DatabaseFactory
│ ├── commands/ # Runway CLIコマンド
│ ├── views/ # Twigテンプレート(*.twig)
│ ├── cache/
│ └── log/
├── migrations/ # SQLマイグレーション(.sql / .mysql.sql)
└── tests/ # PHPUnit
名前空間はフォルダーの大文字小文字に従います。 Composerは"App\\": "app/"をマッピングするため、次のようになります:
| ディスク上のパス | 名前空間 |
|---|---|
app/Controller/HomeController.php |
App\Controller\HomeController |
app/Middleware/… |
App\Middleware\… |
app/Model/… |
App\Model\… |
app/Utils/… |
App\Utils\… |
Linuxでは、app/controller/はapp/Controller/と同じではありません。オートローディングは大文字小文字を区別します。スケルトンのパスカルケースフォルダーに合わせてください。詳細:オートローディング。
スタックのデフォルト(新規プロジェクト): Twigビュー、SimplePdo + ActiveRecord、Engine注入を使用したDice(アプリクラス内でのFlight::の使用は避ける)、php runway migrate後のオプションのSQLite。
create-projectは通常、app/config/config_sample.phpをconfig.phpに、.env.exampleを.envにコピーします(存在する場合)。ルートはapp/config/routes.phpに、サービスとDIはapp/config/services.phpにあります。
ドキュメント ↔ スケルトン: これらのドキュメントはFlightのAPIを教えます(多くの場合、短い
Flight::サンプルを使用)。スケルトンはアプリケーションの形を固定します。app/配下にコードを追加する場合は、スケルトンのツリーに従ってください。メソッド名、オプション、プラグインについてはドキュメントを使用してください。
Webサーバーの設定
PHPビルトイン開発サーバー
これは断然最も簡単な起動方法です。ビルトインサーバーを使用してアプリケーションを実行でき、データベースにSQLiteを使用することもできます(システムにsqlite3がインストールされていれば)。PHPがインストールされていれば、次のコマンドを実行するだけです:
php -S localhost:8000
# またはスケルトンアプリの場合
composer start
その後、ブラウザを開いてhttp://localhost:8000にアクセスします。
プロジェクトのドキュメントルートを別のディレクトリにしたい場合(例:プロジェクトが~/myprojectで、ドキュメントルートが~/myproject/public/の場合)、~/myprojectディレクトリにいる状態で次のコマンドを実行できます:
php -S localhost:8000 -t public/
# スケルトンアプリでは、これは既に設定されています
composer start
その後、ブラウザを開いてhttp://localhost:8000にアクセスします。
Apache
Apacheがシステムにインストールされていることを確認してください。インストールされていない場合は、お使いのシステムへのApacheのインストール方法をGoogleで検索してください。
Apacheの場合、.htaccessファイルを次のように編集します:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
注: flightをサブディレクトリで使用する必要がある場合は、
RewriteEngine Onの直後にRewriteBase /subdir/の行を追加してください。
注: DBやenvファイルなど、すべてのサーバーファイルを保護したい場合は、
.htaccessファイルに次のように記述します:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
Nginxがシステムにインストールされていることを確認してください。インストールされていない場合は、お使いのシステムへのNginxのインストール方法をGoogleで検索してください。
Nginxの場合、サーバー宣言に次を追加します:
server {
location / {
try_files $uri $uri/ /index.php;
}
}
index.phpファイルを作成する
基本的なインストールを行う場合は、開始するためのコードが必要です。
<?php
// Composerを使用している場合は、オートローダーを読み込みます。
require 'vendor/autoload.php';
// Composerを使用しない場合は、フレームワークを直接読み込みます
// require 'flight/Flight.php';
// 次にルートを定義し、リクエストを処理する関数を割り当てます。
Flight::route('/', function () {
echo 'hello world!';
});
// 最後に、フレームワークを起動します。
Flight::start();
スケルトンアプリでは、パブリックエントリはアプリを起動するだけです。ルートはapp/config/routes.phpで登録されます(通常は[App\Controller\…::class, 'method']の形式で、Diceが依存関係を注入できるようにします)。サービス、Twig、SimplePdo、コンテナはapp/config/services.phpで配線されます。この構造は、AIツールと人間が毎回同じ場所を編集するように意図されています。
PHPのインストール
お使いのシステムにphpが既にインストールされている場合は、これらの手順をスキップしてダウンロードセクションに進んでください。
macOS
Homebrewを使用したPHPのインストール
-
Homebrewをインストール(まだインストールされていない場合):
- ターミナルを開いて実行:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- ターミナルを開いて実行:
-
PHPをインストール:
- 最新バージョンをインストール:
brew install php - 特定のバージョン(例:PHP 8.1)をインストールする場合:
brew tap shivammathur/php brew install shivammathur/php/php@8.1
- 最新バージョンをインストール:
-
PHPバージョンの切り替え:
- 現在のバージョンのリンクを解除し、希望するバージョンにリンク:
brew unlink php brew link --overwrite --force php@8.1 - インストールされたバージョンを確認:
php -v
- 現在のバージョンのリンクを解除し、希望するバージョンにリンク:
Windows 10/11
PHPの手動インストール
-
PHPをダウンロード:
- PHP for Windowsにアクセスし、最新版または特定のバージョン(例:7.4、8.0)を非スレッドセーフ版のzipファイルとしてダウンロードします。
-
PHPを展開:
- ダウンロードしたzipファイルを
C:\phpに展開します。
- ダウンロードしたzipファイルを
-
PHPをシステムのPATHに追加:
- システムのプロパティ > 環境変数に移動します。
- システム変数の下でPathを見つけ、編集をクリックします。
- パス
C:\php(またはPHPを展開した場所)を追加します。 - OKをクリックしてすべてのウィンドウを閉じます。
-
PHPを設定:
php.ini-developmentをphp.iniにコピーします。php.iniを編集して、必要に応じてPHPを設定します(例:extension_dirの設定、拡張機能の有効化)。
-
PHPのインストールを確認:
- コマンドプロンプトを開いて実行:
php -v
- コマンドプロンプトを開いて実行:
複数バージョンのPHPをインストール
-
各バージョンについて上記の手順を繰り返し、それぞれを別のディレクトリ(例:
C:\php7、C:\php8)に配置します。 -
システムのPATH変数を目的のバージョンのディレクトリに調整して、バージョンを切り替えます。
Ubuntu(20.04、22.04など)
aptを使用したPHPのインストール
-
パッケージリストを更新:
- ターミナルを開いて実行:
sudo apt update
- ターミナルを開いて実行:
-
PHPをインストール:
- 最新のPHPバージョンをインストール:
sudo apt install php - 特定のバージョン(例:PHP 8.1)をインストールする場合:
sudo apt install php8.1
- 最新のPHPバージョンをインストール:
-
追加モジュールをインストール(オプション):
- 例えば、MySQLサポートをインストールする場合:
sudo apt install php8.1-mysql
- 例えば、MySQLサポートをインストールする場合:
-
PHPバージョンの切り替え:
update-alternativesを使用:sudo update-alternatives --set php /usr/bin/php8.1
-
インストールされたバージョンを確認:
- 実行:
php -v
- 実行:
Rocky Linux
yum/dnfを使用したPHPのインストール
-
EPELリポジトリを有効化:
- ターミナルを開いて実行:
sudo dnf install epel-release
- ターミナルを開いて実行:
-
Remiリポジトリをインストール:
- 実行:
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm sudo dnf module reset php
- 実行:
-
PHPをインストール:
- デフォルトバージョンをインストール:
sudo dnf install php - 特定のバージョン(例:PHP 7.4)をインストールする場合:
sudo dnf module install php:remi-7.4
- デフォルトバージョンをインストール:
-
PHPバージョンの切り替え:
dnfモジュールコマンドを使用:sudo dnf module reset php sudo dnf module enable php:remi-8.0 sudo dnf install php
-
インストールされたバージョンを確認:
- 実行:
php -v
- 実行:
一般的な注意事項
- 開発環境では、プロジェクトの要件に応じてPHP設定を構成することが重要です。
- PHPバージョンを切り替える際は、使用予定の特定バージョンに対応するすべての関連PHP拡張機能がインストールされていることを確認してください。
- PHPバージョンの切り替えや設定の更新後は、Webサーバー(Apache、Nginxなど)を再起動して変更を適用してください。
Guides
ガイド
Flight PHP はシンプルでありながら強力に設計されており、私たちのガイドは実際のアプリケーションをステップバイステップで構築するのに役立ちます。これらの実践的なチュートリアルは、Flight を効果的に使用する方法を示すために、完全なプロジェクトを説明します。
公式ガイド
Building a Blog
Flight PHP を使用して機能的なブログアプリケーションを作成する方法を学びます。このガイドでは以下を説明します:
- プロジェクト構造の設定
- Latte を使用したテンプレートの扱い
- 投稿用のルートの実装
- データの保存と取得
- フォームの送信処理
- 基本的なエラー処理
このチュートリアルは、実際のアプリケーションですべての部品がどのように組み合わさるかを理解したい初心者向けです。
Unit Testing and SOLID Principles
このガイドは、Flight PHP アプリケーションでのユニットテストの基本をカバーします。以下を含みます:
- PHPUnit の設定
- SOLID 原則を使用してテスト可能なコードの記述
- 依存関係のモック
- 避けるべき一般的な落とし穴
- アプリケーションの成長に合わせてテストを拡大する このチュートリアルは、コードの品質とメンテナビリティを向上させたい開発者向けです。
非公式ガイド
これらのガイドは Flight チームによって公式にメンテナンスされているわけではありませんが、コミュニティによって作成された貴重なリソースです。さまざまなトピックとユースケースをカバーし、Flight PHP の使用に関する追加の洞察を提供します。
Creating a RESTful API with Flight Framework
このガイドでは、Flight PHP フレームワークを使用して RESTful API を作成する方法を説明します。API の基本的な設定、ルートの定義、JSON 応答の返却をカバーします。
Building a Simple Blog
このガイドでは、Flight PHP フレームワークを使用して基本的なブログを作成する方法を説明します。実際には 2 つのパートがあり、1 つは基本をカバーし、もう 1 つは本番環境向けの高度なトピックと改良をカバーします。
- Building a Simple Blog with Flight - Part 1 - シンプルなブログの始め方。
- Building a Simple Blog with Flight - Part 2 - 本番環境向けのブログの改良。
Building a Pokémon API in PHP: A Beginner's Guide
この楽しいガイドでは、Flight PHP を使用してシンプルな Pokémon API を作成する方法を説明します。API の基本的な設定、ルートの定義、JSON 応答の返却をカバーします。
貢献
ガイドのアイデアがありますか? 間違いを見つけましたか? 貢献を歓迎します! 私たちのガイドは FlightPHP ドキュメント リポジトリ でメンテナンスされています。
Flight で興味深いものを構築し、それをガイドとして共有したい場合、プルリクエストを送信してください。知識を共有することで Flight コミュニティを成長させることができます。
API ドキュメントをお探しですか?
Flight のコア機能とメソッドに関する特定の情報を探している場合、私たちのドキュメントの Learn セクションを確認してください。