Learn
Flight について学ぶ
Flight は、速く、シンプルで、拡張可能な PHP フレームワークです。非常に多用途で、どんな種類のウェブアプリケーションでも構築できます。
シンプルさを念頭に置いて設計されており、理解しやすく使いやすい方法で書かれています。
🚀 AI & Developer Experience with Flight
Flight は、速くてシンプルなだけでなく、現代の AI ツールを使ってよりスマートにコードを書いて生産性を高めるように設計されています。AI 駆動のコーディングアシスタントを使ったり、反復的なタスクを自動化したり、生産性を向上させたい場合、Flight の軽量なフットプリントとストレートな構造は、最新の開発体験と AI ワークフローを一緒に使うのに最適です。
- AI-Enhanced Coding: Flight は AI コーディングアシスタントとよく連携し、足場を構築したり、リファクタリングしたり、機能をこれまでより速く構築できます。
- Faster Prototyping: AI ツールの助けを借りて、新しいアイデアを素早く立ち上げて繰り返し改善できます — Flight は邪魔になりません。
- Integrate AI APIs: AI サービスに接続したり、スマートな機能を追加したりしたい場合、Flight はそれを簡単に行えますが、AI の専門家でなくてもメリットを得られます。
始め方を気になりますか? Explore our AI & DevEx guide を探して、Flight を使用して速く賢く作業するためのヒント、ツール、実世界の例を学んでください!
重要なフレームワークの概念
Why a Framework?
フレームワークを使う理由についての短い記事です。フレームワークを使う前に、その利点を理解することが良い考えです。
また、@lubiana によって作成された素晴らしいチュートリアルがあります。Flight について詳しく説明していませんが、
このガイドはフレームワークの主要な概念とその利点を理解するのに役立ちます。
チュートリアルは here で見つかります。
Flight Compared to Other Frameworks
Laravel、Slim、Fat-Free、または Symfony などの他のフレームワークから Flight に移行する場合、このページは両者の違いを理解するのに役立ちます。
コアトピック
AI & Developer Experience
AI ツールと現代の開発ワークフローで Flight がどのように連携して、速くスマートにコードを書くかを学んでください。
Autoloading
アプリケーションで独自のクラスを自動ロードする方法を学んでください。
Routing
ウェブアプリケーションのルートを管理する方法を学んでください。これにはルートのグループ化、ルートパラメータ、およびミドルウェアが含まれます。
Middleware
アプリケーションでリクエストとレスポンスをフィルタリングするためにミドルウェアを使う方法を学んでください。
Requests
アプリケーションでリクエストとレスポンスを扱う方法を学んでください。
Responses
ユーザーにレスポンスを送信する方法を学んでください。
Events
アプリケーションにカスタムイベントを追加するためにイベントシステムを使う方法を学んでください。
HTML Templates
組み込みのビューエンジンを使って HTML テンプレートをレンダリングする方法を学んでください。
Security
一般的なセキュリティ脅威からアプリケーションを保護する方法を学んでください。
Configuration
アプリケーションのためにフレームワークを設定する方法を学んでください。
Extending Flight
独自のメソッドとクラスを追加してフレームワークを拡張する方法を学んでください。
Events and Filtering
イベントシステムを使ってメソッドや内部フレームワークメソッドにフックを追加する方法を学んでください。
Dependency Injection Container
アプリケーションの依存関係を管理するために依存性注入コンテナ (DIC) を使う方法を学んでください。
Framework API
フレームワークのコアメソッドについて学んでください。
Migrating to v3
後方互換性はほとんど維持されていますが、v2 から v3 に移行する際に知っておくべきいくつかの変更点があります。
Troubleshooting
Flight を使用する際に遭遇する可能性のある一般的な問題をトラブルシューティングするのに役立つページです。
Learn/stopping
停止
フレームワークをhalt
メソッドを呼び出すことでいつでも停止できます:
Flight::halt();
オプションでHTTP
ステータスコードとメッセージを指定することもできます:
Flight::halt(200, 'Be right back...');
halt
を呼び出すと、その時点までのレスポンスコンテンツが破棄されます。フレームワークを停止して現在のレスポンスを出力するには、stop
メソッドを使用してください:
Flight::stop();
Learn/errorhandling
エラー処理
エラーと例外
Flight によってすべてのエラーや例外がキャッチされ、error
メソッドに渡されます。
デフォルトの動作は、いくつかのエラー情報を含む汎用 HTTP 500 内部サーバーエラー
応答を送信することです。
独自のニーズに合わせてこの動作を上書きすることができます:
Flight::map('error', function (Throwable $error) {
// エラーを処理する
echo $error->getTraceAsString();
});
デフォルトでは、エラーはウェブサーバーに記録されません。これを有効にすることでログを取得できます:
Flight::set('flight.log_errors', true);
見つかりません
URL が見つからない場合、Flight は notFound
メソッドを呼び出します。デフォルトの動作は、簡単なメッセージを含む HTTP 404 Not Found
応答を送信することです。
独自のニーズに合わせてこの動作を上書きすることができます:
Flight::map('notFound', function () {
// 見つからない場合の処理
});
Learn/flight_vs_laravel
フライト vs ララベル
ララベルとは何ですか?
Laravel は、すべての機能が備わっており、素晴らしい開発者向けエコシステムを持つフル機能のフレームワークですが、パフォーマンスと複雑さにはコストがかかります。 Laravelの目標は、開発者が最高レベルの生産性を持ち、一般的なタスクを簡単に行えるようにすることです。 Laravelは、フル機能のエンタープライズ Web アプリケーションを構築しようとしている開発者にとって優れた選択肢です。 これには、パフォーマンスと複雑さの観点でいくつかのトレードオフが伴います。 Laravelの基本を学ぶことは簡単ですが、フレームワークを習得するには時間がかかるかもしれません。
開発者がしばしば問題を解決する唯一の方法はこれらのモジュールであるかのように感じるほど、Laravelモジュールが非常に多いですが、実際には別のライブラリを使用するか、独自のコードを書くこともできます。
フライトとの比較でのメリット
- Laravel には一般的な問題を解決するために使用できる開発者とモジュールの膨大なエコシステムがあります。
- Laravel にはデータベースとやり取りするために使用できる豊富な ORM があります。
- Laravel には、フレームワークを学ぶのに使用できる膨大な文書とチュートリアルがあります。
- Laravel には、アプリケーションを保護するために使用できる組み込みの認証システムがあります。
- Laravel には、フレームワークを学ぶのに使用できるポッドキャスト、カンファレンス、ミーティング、ビデオ、その他のリソースがあります。
- Laravel は、フル機能のエンタープライズ Web アプリケーションを構築しようとしている経験豊富な開発者向けです。
フライトとの比較でのデメリット
- Laravel は Flight よりも深いところで多くのことを行っています。 これはパフォーマンスの観点で大きなコストがかかります。詳細については、TechEmpower ベンチマーク を参照してください。
- Flight は、軽量で高速で使いやすい Web アプリケーションを構築しようとしている開発者向けです。
- Flight は、シンプリシティと使いやすさを重視しています。
- Flight の主な機能の1つは、後方互換性を維持するために最善を尽くすことです。 Laravel は主要バージョン間で多くの苦情を引き起こします。
- Flight は、初めてフレームワークの世界に足を踏み入れる開発者向けです。
- Flight には依存関係がなく、一方、Laravel には多くの依存関係があります。
- Flight はエンタープライズレベルのアプリケーションも行うことができますが、Laravel のように多くのぼいらプレートコードがないです。 開発者がものを整理し、よく構造化するためにはより多くのディシプリンが必要です。
- Flight は開発者にアプリケーションをより多くコントロールさせ、一方、Laravel は裏側にたくさんの魔法があり、これがイライラすることがあります。
Learn/migrating_to_v3
v3への移行
ほとんどの場合、下位互換性は維持されていますが、v2からv3に移行する際に注意すべき変更がいくつかあります。
出力バッファリングの動作(3.5.0)
出力バッファリングは、PHPスクリプトによって生成された出力がクライアントに送信される前にバッファー(PHP内部)に保存されるプロセスです。これにより、出力をクライアントに送信する前に変更できます。
MVCアプリケーションでは、コントローラーが「マネージャー」であり、ビューの動作を管理します。コントローラーの外部(またはFlightの場合、時々無名関数内)で生成された出力は、MVCパターンを壊します。この変更は、MVCパターンにより準拠し、フレームワークを予測可能かつ使いやすくするためです。
v2では、出力バッファリングは、自身の出力バッファーを一貫してクローズしていなかったため、ユニットテストやストリーミングが困難になることがありました。ほとんどのユーザーにとって、この変更は実際には影響しないかもしれません。ただし、コールバックやコントローラーの外部でコンテンツをエコーしている場合(例えば、フック内で)、問題が発生する可能性があります。フック内やフレームワークの実際の実行より前にコンテンツをエコーしても、過去には動作していたかもしれませんが、今後は動作しません。
問題が発生する可能性がある場所
// index.php
require 'vendor/autoload.php';
// just an example
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// this will actually be fine
echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});
Flight::before('start', function(){
// things like this will cause an error
echo '<html><head><title>My Page</title></head><body>';
});
Flight::route('/', function(){
// this is actually just fine
echo 'Hello World';
// This should be just fine as well
Flight::hello();
});
Flight::after('start', function(){
// this will cause an error
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(){
// Now this will be just fine
echo '<html><head><title>My Page</title></head><body>';
});
// more code
ディスパッチャーの変更(3.7.0)
Dispatcher::invokeMethod()
、Dispatcher::execute()
などのDispatcher
の静的メソッドを直接呼び出している場合、Dispatcher
がよりオブジェクト指向に変換されたため、これらのメソッドを直接呼び出さないようにコードを更新する必要があります。 依存性注入コンテナをより簡単に使用できるようにDispatcher
が変更されました。 Dispatcherと同様のメソッドを呼び出す必要がある場合は、手動で$result = $class->$method(...$params);
またはcall_user_func_array()
のようなものを使用することができます。
halt()
stop()
redirect()
および error()
の変更(3.10.0)
3.10.0以前のデフォルト動作は、ヘッダーとレスポンスボディの両方をクリアすることでした。これは、レスポンスボディのみをクリアするように変更されました。ヘッダーもクリアする必要がある場合は、Flight::response()->clear()
を使用できます。
Learn/configuration
設定
set
メソッドを使用して、Flight の特定の動作をカスタマイズできます。
Flight::set('flight.log_errors', true);
利用可能な設定
以下は利用可能な設定の一覧です:
- flight.base_url
?string
- リクエストのベース URL をオーバーライドします。 (デフォルト: null) - flight.case_sensitive
bool
- URL の大文字と小文字を区別します。 (デフォルト: false) - flight.handle_errors
bool
- Flight がすべてのエラーを内部で処理できるようにします。 (デフォルト: true) - flight.log_errors
bool
- エラーをウェブサーバーのエラーログファイルに記録します。 (デフォルト: false) - flight.views.path
string
- ビューテンプレートファイルが含まれるディレクトリ。 (デフォルト: ./views) - flight.views.extension
string
- ビューテンプレートファイルの拡張子。 (デフォルト: .php) - flight.content_length
bool
-Content-Length
ヘッダーを設定します。 (デフォルト: true) - flight.v2.output_buffering
bool
- 旧バージョンの出力バッファリングを使用します。 v3 へのマイグレーション を参照してください。 (デフォルト: false)
ローダーの設定
_
をクラス名に含める場合の追加のローダーの設定があります。これにより、クラスを自動的に読み込むことができます。
// アンダースコアを使用したクラスのローディングを有効にする
// デフォルトは true
Loader::$v2ClassLoading = false;
変数
Flight を使用すると、アプリケーション内のどこからでも使用できるように変数を保存できます。
// 変数を保存する
Flight::set('id', 123);
// アプリケーション内の別の場所で
$id = Flight::get('id');
変数が設定されているかどうかを確認するには、次のようにします:
if (Flight::has('id')) {
// 何かを行う
}
次のようにして変数をクリアできます:
// id 変数をクリアする
Flight::clear('id');
// すべての変数をクリア
Flight::clear();
Flight は設定目的で変数も使用します。
Flight::set('flight.log_errors', true);
エラー処理
エラーと例外
すべてのエラーと例外は Flight によってキャッチされ、error
メソッドに渡されます。デフォルトの動作は、一般的な HTTP 500 Internal Server Error
応答を送信し、いくつかのエラー情報を含めることです。
独自のニーズに合わせてこの動作をオーバーライドできます:
Flight::map('error', function (Throwable $error) {
// エラーを処理する
echo $error->getTraceAsString();
});
デフォルトでは、エラーはウェブサーバーにログ記録されていません。これを有効にできます:
Flight::set('flight.log_errors', true);
見つからない場合
URL が見つからない場合、Flight は notFound
メソッドを呼び出します。デフォルトの動作は、簡単なメッセージを含む HTTP 404 Not Found
応答を送信することです。
独自のニーズに合わせてこの動作をオーバーライドできます:
Flight::map('notFound', function () {
// 見つからなかった時の処理
});
Learn/ai
AI と開発者体験 with Flight
Flight は、より速く、より賢く、摩擦を少なくして構築するのを助けるものです。特に、AI 駆動のツールや現代の開発ワークフローで作業する場合です。このページでは、Flight がプロジェクトを AI で強化しやすくする方法、そしてフレームワークとスケルトンプロジェクトに組み込まれた新しい AI ヘルパーの使い方を説明します。
AI-Ready by Default: The Skeleton Project
公式の flightphp/skeleton スターターには、以下の人気の AI コーディングアシスタントの指示と設定が含まれています:
- GitHub Copilot
- Cursor
- Windsurf
これらのツールは、プロジェクト固有の指示で事前に設定されているため、コードを書く際に最も関連性が高く、文脈を考慮した助けを得られます。つまり:
- AI アシスタントは、プロジェクトの目標、スタイル、要件を理解します
- すべての貢献者に対して一貫したガイダンスを提供します
- 文脈を説明する時間を減らし、構築する時間を増やします
なぜこれが重要ですか?
AI ツールがプロジェクトの意図と規約を知っている場合、機能のスキャフォールディング、コードのリファクタリング、一般的なミスの回避を助けてくれます。これにより、初日からあなた(とあなたのチーム)がより生産的になります。
New AI Commands in Flight Core
v3.16.0+
Flight core には、プロジェクトを設定し、AI で導くのに役立つ 2 つの強力な CLI コマンドが含まれています:
1. ai:init
— Connect to Your Favorite LLM Provider
このコマンドは、OpenAI、Grok、または Anthropic (Claude) などの LLM (Large Language Model) プロバイダーの資格情報を設定する手順を案内します。
Example:
php runway ai:init
プロバイダーの選択、API キーの入力、モデルの選択を求められます。これにより、プロジェクトを最新の AI サービスに簡単に接続できます—手動設定は不要です。
2. ai:generate-instructions
— Project-Aware AI Coding Instructions
このコマンドは、プロジェクト固有の指示を AI コーディングアシスタント用に作成または更新します。プロジェクトの用途、使用するデータベース、チームの規模など、いくつかの簡単な質問をします。その後、LLM プロバイダーを使用して、調整された指示を生成します。
指示がすでに存在する場合、提供した回答を反映して更新します。これらの指示は自動的に以下に書き込まれます:
.github/copilot-instructions.md
(for Github Copilot).cursor/rules/project-overview.mdc
(for Cursor).windsurfrules
(for Windsurf)
Example:
php runway ai:generate-instructions
なぜこれが役立つのですか?
最新のプロジェクト固有の指示があれば、AI ツールは次のようにできます:
- より良いコードの提案を提供します
- プロジェクトの独自のニーズを理解します
- 新しい貢献者のオンボーディングを速めます
- プロジェクトが進化するにつれて、摩擦と混乱を減らします
Not Just for Building AI Apps
AI 駆動の機能(例:チャットボット、スマート API、または統合)を構築するために Flight を使用することもできますが、真の強みは、開発者として AI ツールをより良く活用する点にあります。それは:
- 生産性を向上させる AI 支援コーディング
- チームを揃える 共有され、進化する指示
- 新しい貢献者のオンボーディングを容易にする
- 構築に集中し、ツールとの戦いを避ける
Learn More & Get Started
- See the Flight Skeleton for a ready-to-go, AI-friendly starter
- Check out the rest of the Flight documentation for tips on building fast, modern PHP apps
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
セキュリティ
セキュリティはウェブアプリケーションにとって非常に重要です。アプリケーションが安全で、ユーザーのデータが守られていることを確認したいでしょう。Flightは、ウェブアプリケーションのセキュリティを確保するための多くの機能を提供しています。
ヘッダー
HTTPヘッダーは、ウェブアプリケーションを保護する最も簡単な方法の1つです。ヘッダーを使用して、クリックジャッキング、XSS、およびその他の攻撃を防ぐことができます。これらのヘッダーをアプリケーションに追加する方法はいくつかあります。
ヘッダーのセキュリティを確認するための優れたウェブサイトは、securityheaders.com と observatory.mozilla.org です。
手動で追加
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=()');
これらは、bootstrap.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=()');
});
ミドルウェアとして追加
ミドルウェアクラスとして追加することもできます。これは、コードをクリーンで整理する良い方法です。
// app/middleware/SecurityHeadersMiddleware.php
namespace app\middleware;
class SecurityHeadersMiddleware
{
public function before(array $params): void
{
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=()');
}
}
// index.php またはルートがある場所
// FYI、この空の文字列グループはすべてのルートに対するグローバルミドルウェアとして機能します。
// もちろん、同じことをして特定のルートにのみ追加することもできます。
Flight::group('', function(Router $router) {
$router->get('/users', [ 'UserController', 'getUsers' ]);
// その他のルート
}, [ new SecurityHeadersMiddleware() ]);
クロスサイトリクエストフォージェリ (CSRF)
クロスサイトリクエストフォージェリ (CSRF) は、悪意のあるウェブサイトがユーザーのブラウザを使用して、あなたのウェブサイトにリクエストを送信させる攻撃の一種です。これを使用して、ユーザーの知らないうちにあなたのウェブサイトでアクションを実行することができます。Flightは、組み込みのCSRF保護メカニズムを提供していませんが、ミドルウェアを使用して自分自身のものを簡単に実装できます。
セットアップ
まず、CSRFトークンを生成し、ユーザーのセッションに保存する必要があります。その後、このトークンをフォームで使用し、フォームが送信されるときに確認します。
// CSRFトークンを生成し、ユーザーのセッションに保存
// (Flightにセッションオブジェクトを作成してアタッチしたと仮定)
// 詳細についてはセッションのドキュメントを参照してください
Flight::register('session', \Ghostff\Session\Session::class);
// セッションあたり1つのトークンを生成する必要があります(複数のタブや同じユーザーのリクエストで機能するため)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
<!-- フォームにCSRFトークンを使用 -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- 他のフォームフィールド -->
</form>
Latteを使用する
独自の関数を定義してCSRFトークンをLatteテンプレートに出力することもできます。
// CSRFトークンを出力するカスタム関数を設定
// 注意: ViewはLatteをビューエンジンとして構成されています
Flight::view()->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
これで、Latteテンプレート内でcsrf()
関数を使用してCSRFトークンを出力できます。
<form method="post">
{csrf()}
<!-- 他のフォームフィールド -->
</form>
短くてシンプルですよね?
CSRFトークンの確認
イベントフィルターを使用してCSRFトークンを確認できます:
// このミドルウェアはリクエストが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, '無効なCSRFトークン');
// あるいはJSONレスポンスのために
Flight::jsonHalt(['error' => '無効なCSRFトークン'], 403);
}
}
});
または、ミドルウェアクラスを使用することもできます:
// app/middleware/CsrfMiddleware.php
namespace app\middleware;
class CsrfMiddleware
{
public function before(array $params): void
{
if(Flight::request()->method == 'POST') {
$token = Flight::request()->data->csrf_token;
if($token !== Flight::session()->get('csrf_token')) {
Flight::halt(403, '無効なCSRFトークン');
}
}
}
}
// index.php またはルートがある場所
Flight::group('', function(Router $router) {
$router->get('/users', [ 'UserController', 'getUsers' ]);
// その他のルート
}, [ new CsrfMiddleware() ]);
クロスサイトスクリプティング (XSS)
クロスサイトスクリプティング (XSS) は、悪意のあるウェブサイトがあなたのウェブサイトにコードを挿入できるようになる攻撃の一種です。これらの機会のほとんどは、あなたのエンドユーザーが記入するフォームの値から来ます。ユーザーからの出力を決して信頼しないでください!彼らが世界最高のハッカーであると常に仮定してください。彼らはあなたのページに悪意のあるJavaScriptやHTMLを挿入できます。このコードは、ユーザーの情報を盗むために使用されたり、あなたのウェブサイトでアクションを実行したりできます。Flightのビュークラスを使用することで、出力を簡単にエスケープしてXSS攻撃を防ぐことができます。
// ユーザーが賢いと仮定してこの名前を使用しようとした場合
$name = '<script>alert("XSS")</script>';
// これは出力をエスケープします
Flight::view()->set('name', $name);
// これは出力されます: <script>alert("XSS")</script>
// もしあなたがビュークラスとして登録されたLatteのようなものを使用すると、
// それも自動的にエスケープされます。
Flight::view()->render('template', ['name' => $name]);
SQLインジェクション
SQLインジェクションは、悪意のあるユーザーがデータベースにSQLコードを注入できるようになる攻撃の一種です。これを使用して、データベースから情報を盗んだり、データベースでアクションを実行したりできます。再び、ユーザーからの入力を決して信頼しないでください!彼らが血を求めていると常に仮定してください。PDO
オブジェクトでプリペアドステートメントを使用することで、SQLインジェクションを防ぐことができます。
// Flight::db()があなたのPDOオブジェクトとして登録されていると仮定
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();
// PdoWrapperクラスを使用すると、これを1行で簡単に行えます
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);
// ? プレースホルダーを使用するPDOオブジェクトでも同様のことができます
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);
// こんなことは絶対にしないと約束してください...
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE username = '{$username}' LIMIT 5");
// だってもし$username = "' OR 1=1; -- "; だったらどうするのでしょうか?
// クエリが構築された後は、次のようになります
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// 不思議に見えますが、これは有効なクエリで機能します。実際、
// これはすべてのユーザーを返す非常に一般的なSQLインジェクション攻撃です。
CORS
クロスオリジンリソース共有 (CORS) は、ウェブページ上の多くのリソース(フォント、JavaScriptなど)が、リソースの発信元ドメイン外の別のドメインからリクエストされることを可能にするメカニズムです。Flightは組み込み機能を提供していませんが、Flight::start()
メソッドが呼び出される前に実行するフックで簡単に処理できます。
// app/utils/CorsUtil.php
namespace app\utils;
class CorsUtil
{
public function set(array $params): void
{
$request = Flight::request();
$response = Flight::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 = Flight::request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = Flight::response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// index.php またはルートがある場所
$CorsUtil = new CorsUtil();
// これはstartが実行される前に実行される必要があります。
Flight::before('start', [ $CorsUtil, 'setupCors' ]);
エラーハンドリング
攻撃者に情報を漏らさないように、プロダクション環境では敏感なエラーの詳細を非表示にします。
// bootstrap.php または index.php で
// flightphp/skeletonでは、これは 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, 'アクセス拒否');
入力のサニタイズ
ユーザー入力を信頼しないでください。悪意のあるデータが入り込まないように、処理の前にサニタイズします。
// $_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 = 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, 'リクエストが多すぎます');
}
$cache->set($key, $attempts + 1, 60); // 60秒後にリセット
});
結論
セキュリティは非常に重要であり、ウェブアプリケーションが安全であることを確認することが重要です。Flightはウェブアプリケーションを保護するための多くの機能を提供しますが、常に警戒し、ユーザーのデータを守るためにできることをすべて行うことが重要です。最悪の事態を常に想定し、ユーザーからの入力を決して信頼しないでください。出力は常にエスケープし、SQLインジェクションを防ぐためにプリペアドステートメントを使用してください。CSRFやCORS攻撃からルートを保護するために、常にミドルウェアを使用してください。これらすべてを実行すれば、安全なウェブアプリケーションを構築する道のりが開けるでしょう。
Learn/overriding
オーバーライド
Flightは、コードを変更することなく、独自のニーズに合わせてデフォルトの機能をオーバーライドできるようにします。
たとえば、FlightがURLをルートにマッチさせられない場合、notFound
メソッドが呼び出され、一般的な HTTP 404
レスポンスが送信されます。この動作を以下のようにオーバーライドできます:
Flight::map('notFound', function() {
// カスタム404ページを表示
include 'errors/404.html';
});
Flightはまた、フレームワークのコアコンポーネントを置換することを許可します。 たとえば、デフォルトのRouterクラスを独自のカスタムクラスで置き換えることができます:
// カスタムクラスを登録
Flight::register('router', MyRouter::class);
// FlightがRouterインスタンスをロードするとき、あなたのクラスがロードされます
$myrouter = Flight::router();
map
やregister
などのフレームワークのメソッドはオーバーライドできません。これを試みるとエラーが発生します。
Learn/routing
ルーティング
注記: ルーティングについてさらに理解したいですか? "why a framework?" ページをチェックして、より詳細な説明を確認してください。
Flight の基本的なルーティングは、URL パターンとコールバック関数、またはクラスとメソッドの配列をマッチさせることで行われます。
Flight::route('/', function(){
echo 'hello world!';
});
ルートは定義された順序でマッチされます。リクエストにマッチした最初のルートが呼び出されます。
コールバック/関数
コールバックは、呼び出し可能な任意のオブジェクトです。したがって、通常の関数を使用できます:
function hello() {
echo 'hello world!'; // これはコメントです
}
Flight::route('/', 'hello');
クラス
クラスの静的メソッドも使用できます:
class Greeting {
public static function hello() {
echo 'hello world!'; // これはコメントです
}
}
Flight::route('/', [ 'Greeting','hello' ]);
または、まずオブジェクトを作成して次にメソッドを呼び出す:
// Greeting.php
class Greeting
{
public function __construct() {
$this->name = 'John Doe'; // これはコメントです
}
public function hello() {
echo "Hello, {$this->name}!"; // これはコメントです
}
}
// index.php
$greeting = new Greeting();
Flight::route('/', [ $greeting, 'hello' ]);
// オブジェクトを先に作成せずにこれを行うこともできます
// 注記: コンストラクタに引数は注入されません
Flight::route('/', [ 'Greeting', 'hello' ]);
// さらに短い構文も使用できます
Flight::route('/', 'Greeting->hello');
// または
Flight::route('/', Greeting::class.'->hello');
DIC を使用した依存性注入 (Dependency Injection Container)
コンテナ経由の依存性注入 (PSR-11、PHP-DI、Dice など) を使用したい場合、利用可能なルートのタイプは、オブジェクトを自分で作成してコンテナで作成するか、クラスとメソッドを定義するための文字列を使用するだけです。詳細については Dependency Injection ページを参照してください。
簡単な例です:
use flight\database\PdoWrapper;
// Greeting.php
class Greeting
{
protected PdoWrapper $pdoWrapper;
public function __construct(PdoWrapper $pdoWrapper) {
$this->pdoWrapper = $pdoWrapper; // これはコメントです
}
public function hello(int $id) {
// $this->pdoWrapper を使用して何かを行う
$name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hello, world! My name is {$name}!"; // これはコメントです
}
}
// index.php
// 必要なパラメータでコンテナを設定
// PSR-11 に関する詳細は Dependency Injection ページを参照
$dice = new \Dice\Dice();
// 変数を再割り当てすることを忘れないでください! '$dice = ' を使用
$dice = $dice->addRule('flight\database\PdoWrapper', [
'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();
メソッド ルーティング
デフォルトでは、ルート パターンはすべてのリクエスト メソッドに対してマッチされます。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.'; // これはコメントです
});
さらに、Router オブジェクトを取得して、いくつかのヘルパー メソッドを使用できます:
$router = Flight::router();
// すべてのメソッドをマップ
$router->map('/', function() {
echo 'hello world!'; // これはコメントです
});
// GET リクエスト
$router->get('/users', function() {
echo 'users'; // これはコメントです
});
// $router->post();
// $router->put();
// $router->delete();
// $router->patch();
正規表現
ルートで正規表現を使用できます:
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)!"; // これはコメントです
});
次の URL: /bob/123
にアクセスすると、出力は 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 () {
// 何かを行う // これはコメントです
});
Passing
コールバック関数から true
を返すことで、次のマッチング ルートに実行を渡せます。
Flight::route('/user/@name', function (string $name) {
// いくつかの条件をチェック
if ($name !== "Bob") {
// 次のルートに継続
return true; // これはコメントです
}
});
Flight::route('/user/*', function () {
// これは呼び出されます // これはコメントです
});
ルートエイリアス
ルートにエイリアスを割り当てると、コードの後で URL を動的に生成できます (例: テンプレート)。
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); // これはコメントです
// コードのどこかで後
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5' を返します // これはコメントです
URL が変更された場合に特に便利です。上記の例で、users が /admin/users/@id
に移動したとします。エイリアスを使用していれば、参照箇所を変更する必要はありません。エイリアスは /admin/users/5
を返します。
グループ内のルートエイリアスも動作します:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view'); // これはコメントです
});
// コードのどこかで後
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5' を返します // これはコメントです
ルート情報
マッチング ルート情報を検査したい場合、2 つの方法があります。executedRoute
プロパティを使用するか、ルート メソッドの 3 番目のパラメータに 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);
または、最後に実行されたルートを検査したい場合:
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
になります。ミドルウェアでも使用できます!
ルート グループ化
関連するルートをグループ化したい場合 (例: /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 = new \flight\Engine();
$app->group('/api/v1', function (Router $router) {
// $router 変数を使用
$router->get('/users', function () {
// GET /api/v1/users にマッチ // これはコメントです
});
$router->post('/posts', function () {
// POST /api/v1/posts にマッチ // これはコメントです
});
});
ミドルウェア付きのグループ化
ルートのグループにミドルウェアを割り当てられます:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// /api/v1/users にマッチ // これはコメントです
});
}, [ MyAuthMiddleware::class ]); // または [ new MyAuthMiddleware() ] を使用してインスタンスを使用
詳細は group middleware ページを参照してください。
リソース ルーティング
resource
メソッドを使用して、リソースのルート セットを作成できます。これにより、RESTful 規約に従うリソースのルート セットが作成されます。
リソースを作成するには、以下のように:
Flight::resource('/users', UsersController::class);
背景で何が起こるかというと、以下のルートが作成されます:
[
'index' => 'GET ',
'create' => 'GET /create',
'store' => 'POST ',
'show' => 'GET /@id',
'edit' => 'GET /@id/edit',
'update' => 'PUT /@id',
'destroy' => 'DELETE /@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
{
}
}
注記: 新しく追加されたルートは
runway
で確認できます。php runway routes
を実行してください。
リソース ルートのカスタマイズ
リソース ルートを構成するためのいくつかのオプションがあります。
エイリアス ベース
aliasBase
を構成できます。デフォルトでは、エイリアスは指定された URL の最後の部分です。例えば /users/
は aliasBase
を users
にします。これらのルートが作成されると、エイリアスは users.index
、users.create
などになります。エイリアスを変更したい場合、aliasBase
を望みの値に設定します。
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only and 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 ] ]);
ストリーミング
streamWithHeaders()
メソッドを使用して、クライアントにレスポンスをストリーミングできます。これは、大容量のファイル、長時間実行のプロセス、または大容量のレスポンスを送信するのに便利です。ルートのストリーミングは、通常のルートとは少し異なります。
注記: ストリーミング レスポンスは、
flight.v2.output_buffering
が false に設定されている場合にのみ利用可能です。
手動ヘッダー付きのストリーム
stream()
メソッドを使用して、クライアントにレスポンスをストリーミングできます。これを行う場合、クライアントに出力する前にすべてのメソッドを手動で設定する必要があります。これは header()
PHP 関数または Flight::response()->setRealHeader()
メソッドで行います。
Flight::route('/@filename', function($filename) {
// 明らかにパスを sanitizing などを行います。
$fileNameSafe = basename($filename); // これはコメントです
// ルートが実行された後に追加のヘッダーを設定する必要がある場合
// 何も出力される前に定義する必要があります。
// すべて header() 関数の raw 呼び出しまたは Flight::response()->setRealHeader() の呼び出しでなければなりません
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// または
Flight::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));
// ファイルが読み込まれると同時にクライアントにストリーミング
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
]);
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/variables
変数
Flight は、変数を保存してアプリケーション内のどこでも使用できるようにします。
// あなたの変数を保存する
Flight::set('id', 123);
// アプリケーションの別の場所で
$id = Flight::get('id');
変数が設定されているかどうかを確認するには、次のようにします。
if (Flight::has('id')) {
// 何かを実行
}
変数をクリアするには、次のようにします。
// id 変数をクリア
Flight::clear('id');
// すべての変数をクリア
Flight::clear();
Flight は設定目的で変数も使用します。
Flight::set('flight.log_errors', true);
Learn/dependency_injection_container
依存性の注入コンテナ
はじめに
依存性の注入コンテナ (DIC) は、アプリケーションの依存関係を管理する強力なツールであります。これは、現代のPHPフレームワークで重要な概念であり、オブジェクトのインスタンス化と構成を管理するために使用されます。DIC ライブラリの例には次のものがあります: Dice, Pimple, PHP-DI, および league/container。
DIC とは、クラスを中央集権的に作成および管理できるという洒落た方法です。これは、同じオブジェクトを複数のクラス (たとえば、コントローラー) に渡す必要がある場合に役立ちます。簡単な例は、この概念を理解するのに役立つでしょう。
基本的な例
昔は以下のように行っていたかもしれません:
// 以前の方法
以上のコードからわかるように、新しい PDO
オブジェクトを作成して UserController
クラスに渡しています。これは小規模なアプリケーションには適していますが、アプリケーションが成長すると、同じ PDO
オブジェクトを複数箇所で作成していることがわかります。ここで DIC が役立ちます。
こちらは (Dice を使用した) DIC を使用して同じ例を示したものです:
// DIC を使用した同じ例
もしかすると、この例には多くの余分なコードが追加されたと思っているかもしれません。その魔法は、PDO
オブジェクトが必要な別のコントローラーを持っている場合に来ます。
// 他のコントローラーが PDO オブジェクトが必要な場合
DIC を利用することの追加メリットは、ユニットテストがはるかに容易になることです。モックオブジェクトを作成してクラスに渡すことができます。これは、アプリケーションのテストを書く際に非常に有益です!
PSR-11
Flight は PSR-11 に準拠したコンテナも利用できます。これは、PSR-11 インターフェースを実装した任意のコンテナを使用できることを意味します。以下は、League の PSR-11 コンテナを使用した例です:
// PSR-11 コンテナを使用した例
この Dice の例よりも少し冗長かもしれませんが、同じ利点を持って問題なく機能します!
カスタム DIC ハンドラ
独自の DIC ハンドラも作成することができます。これは、PSR-11 ではないカスタムコンテナを使用したい場合に役立ちます。これを行う方法についての詳細については、基本的な例を参照してください。
さらに、Flight の使用時に生活をより簡単にするためのいくつかの便利なデフォルトがあります。
エンジンインスタンス
コントローラー/ミドルウェアで Engine
インスタンスを使用する場合、以下のように構成できます:
// エンジンインスタンスを使用する方法
他のクラスの追加
コンテナに追加したい他のクラスがある場合、Dice を使用すると自動的に解決されます。以下は例です:
// 他のクラスの追加
Learn/middleware
ルートミドルウェア
Flightはルートとグループルートのミドルウェアをサポートします。ミドルウェアはルートコールバックの前(または後)に実行される関数です。これは、コードにAPI認証チェックを追加したり、ユーザーがルートにアクセスする権限を持っていることを検証したりするのに最適な方法です。
基本的なミドルウェア
以下は基本的な例です:
// 匿名の関数だけを供給した場合、ルートコールバックの前に実行されます。
// 「after」のミドルウェア関数はありません。クラスを使用した場合のみ利用可能です(以下を参照)。
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// これは「Middleware first! Here I am!」を出力します。
ミドルウェアに関するいくつかの重要な注意点があります。使用する前に把握しておくべきです:
- ミドルウェア関数は、ルートに追加された順序で実行されます。実行方法はSlim Frameworkが扱うものと似ています。
- Beforeは追加された順序で実行され、Afterは逆順で実行されます。
- ミドルウェア関数がfalseを返した場合、すべての実行が停止され、403 Forbiddenエラーがスローされます。おそらくFlight::redirect()などの方法でより柔軟に処理したいでしょう。
- ルートのパラメータが必要な場合、それらはミドルウェア関数に単一の配列として渡されます。(
function($params) { ... }
またはpublic function before($params) {}
)。これを行う理由は、パラメータをグループ化し、そのグループ内でパラメータの順序が変わる可能性があるため、ミドルウェア関数が壊れないようにするためです。この方法で、位置ではなく名前でアクセスできます。 - ミドルウェアの名前だけを渡した場合、dependency injection containerによって自動的に実行され、必要なパラメータでミドルウェアが実行されます。依存注入コンテナが登録されていない場合、
flight\Engine
インスタンスが__construct()
に渡されます。
ミドルウェアクラス
ミドルウェアはクラスとして登録することもできます。「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!」を表示します。
ミドルウェアエラーの処理
認証ミドルウェアがあり、ユーザーが認証されていない場合にログイン画面にリダイレクトしたいとします。いくつかのオプションがあります:
- ミドルウェア関数からfalseを返し、Flightが自動的に403 Forbiddenエラーを返す方法ですが、カスタマイズできません。
Flight::redirect()
を使用してユーザーをログイン画面にリダイレクトします。- ミドルウェア内でカスタムエラーを作成し、ルートの実行を停止します。
基本的な例
以下はシンプルなreturn false; の例です:
class MyMiddleware {
public function before($params) {
if (isset($_SESSION['user']) === false) {
return false;
}
// trueの場合、すべてが続行されます
}
}
リダイレクト例
以下はユーザーをログイン画面にリダイレクトする例です:
class MyMiddleware {
public function before($params) {
if (isset($_SESSION['user']) === false) {
Flight::redirect('/login');
exit;
}
}
}
カスタムエラー例
APIを構築していて、JSONエラーをスローする必要がある場合、以下のようにできます:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->headers['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.']));
}
}
}
グループ化されたミドルウェア
ルートグループを追加し、そのグループ内のすべてのルートに同じミドルウェアを適用できます。これは、ヘッダーのAPIキーをチェックするAuthミドルウェアで一連のルートをグループ化する必要がある場合に便利です。
// groupメソッドの最後に追加
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() ]);
すべてのルートにグローバルミドルウェアを適用したい場合、空のグループを追加できます:
// groupメソッドの最後に追加
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() ]、同じです
Learn/filtering
フィルタリング
Flightは、メソッドが呼び出される前後にフィルタリングを行うことができます。覚える必要のある事前定義されたフックはありません。デフォルトのフレームワークメソッドやマップしたカスタムメソッドのいずれに対してもフィルタリングを行うことができます。
フィルタ関数は次のようになります:
function (array &$params, string &$output): bool {
// フィルタコード
}
渡された変数を使用して、入力パラメータや出力を操作することができます。
メソッドの前にフィルタを実行するには、次のようにします:
Flight::before('start', function (array &$params, string &$output): bool {
// 何かを行う
});
メソッドの後にフィルタを実行するには、次のようにします:
Flight::after('start', function (array &$params, string &$output): bool {
// 何かを行う
});
任意のメソッドに複数のフィルタを追加することができます。宣言された順序通りに呼び出されます。
以下はフィルタリングプロセスの例です:
// カスタムメソッドをマップ
Flight::map('hello', function (string $name) {
return "Hello, $name!";
});
// フィルタを追加
Flight::before('hello', function (array &$params, string &$output): bool {
// パラメータを操作する
$params[0] = 'Fred';
return true;
});
// フィルタを追加
Flight::after('hello', function (array &$params, string &$output): bool {
// 出力を操作する
$output .= " Have a nice day!";
return true;
});
// カスタムメソッドを呼び出す
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';
// これによりチェーンが終了します
return false;
});
// これは呼び出されません
Flight::before('start', function (array &$params, string &$output): bool {
echo 'three';
return true;
});
map
や register
などのコアメソッドは、直接呼び出されて動的に呼び出されないため、フィルタリングすることはできません。
Learn/requests
リクエスト
Flight は HTTP リクエストを単一のオブジェクトにカプセル化し、次のようにアクセスできます:
$request = Flight::request();
典型的な使用例
ウェブアプリケーションでリクエストを扱う場合、通常はヘッダー、または $_GET
または $_POST
パラメータ、または生のリクエストボディを引き出したいと思うでしょう。Flight はこれらの操作を簡単に行うインターフェースを提供します。
クエリ文字列パラメータを取得する例です:
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
echo "You are searching for: $keyword";
// データベースをクエリしたり、$keywordを使って他のことを行う
});
POST メソッドのフォームの例です:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
echo "You submitted: $name, $email";
// データベースに保存したり、$name と $email を使って他のことを行う
});
リクエストオブジェクトのプロパティ
リクエストオブジェクトは以下のプロパティを提供します:
- 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 - POST データまたは JSON データ
- cookies - クッキーデータ
- files - アップロードされたファイル
- secure - 接続がセキュアかどうかを示す
- accept - HTTP アクセプトパラメータ
- 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
query
、data
、cookies
、および files
プロパティは配列またはオブジェクトとしてアクセスできます。
クエリ文字列パラメータを取得するには:
$id = Flight::request()->query['id'];
または:
$id = Flight::request()->query->id;
生のリクエストボディ
PUT リクエストなどの場合に生の HTTP リクエストボディを取得するには:
$body = Flight::request()->getBody();
JSON 入力
application/json
タイプで {"id": 123}
のデータを送信した場合、data
プロパティから利用できます:
$id = Flight::request()->data->id;
$_GET
$_GET
配列は query
プロパティ経由でアクセスできます:
$id = Flight::request()->query['id'];
$_POST
$_POST
配列は data
プロパティ経由でアクセスできます:
$id = Flight::request()->data['id'];
$_COOKIE
$_COOKIE
配列は cookies
プロパティ経由でアクセスできます:
$myCookieValue = Flight::request()->cookies['myCookieName'];
$_SERVER
$_SERVER
配列にアクセスするためのショートカットとして getVar()
メソッドがあります:
$host = Flight::request()->getVar('HTTP_HOST');
アップロードされたファイルを $_FILES
経由でアクセス
アップロードされたファイルは files
プロパティ経由でアクセスできます:
$uploadedFile = Flight::request()->files['myFile'];
ファイルアップロードの処理 (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());
}
});
セキュリティ注意: ユーザー入力、特にファイルアップロードを扱う場合は常に検証とサニタイズを行ってください。許可する拡張子の種類を検証するだけでなく、ファイルの "マジックバイト" を検証して、ユーザーが主張するファイルタイプであることを確認してください。これを助けるための 記事 と ライブラリ があります。
リクエストヘッダー
リクエストヘッダーは getHeader()
または getHeaders()
メソッドを使ってアクセスできます:
// Authorization ヘッダーが必要な場合
$host = Flight::request()->getHeader('Authorization');
// または
$host = Flight::request()->header('Authorization');
// すべてのヘッダーを取得する場合
$headers = Flight::request()->getHeaders();
// または
$headers = Flight::request()->headers();
リクエストボディ
生のリクエストボディは getBody()
メソッドを使ってアクセスできます:
$body = Flight::request()->getBody();
リクエストメソッド
リクエストメソッドは method
プロパティまたは getMethod()
メソッドを使ってアクセスできます:
$method = Flight::request()->method; // 実際には getMethod() を呼び出す
$method = Flight::request()->getMethod();
注意: getMethod()
メソッドはまず $_SERVER['REQUEST_METHOD']
からメソッドを引き出し、存在する場合に $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']
または $_REQUEST['_method']
で上書きできます。
リクエスト URL
URL の一部を組み合わせるためのヘルパーメソッドがいくつかあります。
完全 URL
完全なリクエスト URL は getFullUrl()
メソッドを使ってアクセスできます:
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
ベース URL
ベース URL は getBaseUrl()
メソッドを使ってアクセスできます:
$url = Flight::request()->getBaseUrl();
// 注意: 末尾のスラッシュなし。
// https://example.com
クエリ解析
URL を parseQuery()
メソッドに渡すと、クエリ文字列を連想配列に解析できます:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
Learn/frameworkmethods
フレームワークのメソッド
Flightは使いやすく理解しやすいように設計されています。以下はフレームワークの完全なメソッドセットです。 コアメソッドには、通常の静的メソッドであるコアメソッドと、フィルタリングやオーバーライドが可能なマップされたメソッドである拡張メソッドが含まれています。
コアメソッド
Flight::map(string $name, callable $callback, bool $pass_route = false) // カスタムフレームワークメソッドを作成します。
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // クラスをフレームワークメソッドに登録します。
Flight::before(string $name, callable $callback) // フレームワークメソッドの前にフィルタを追加します。
Flight::after(string $name, callable $callback) // フレームワークメソッドの後にフィルタを追加します。
Flight::path(string $path) // クラスの自動読み込み用のパスを追加します。
Flight::get(string $key) // 変数を取得します。
Flight::set(string $key, mixed $value) // 変数を設定します。
Flight::has(string $key) // 変数が設定されているかどうかを確認します。
Flight::clear(array|string $key = []) // 変数をクリアします。
Flight::init() // フレームワークをデフォルト設定に初期化します。
Flight::app() // アプリケーションオブジェクトのインスタンスを取得します。
拡張メソッド
Flight::start() // フレームワークを開始します。
Flight::stop() // フレームワークを停止してレスポンスを送信します。
Flight::halt(int $code = 200, string $message = '') // オプションのステータスコードとメッセージでフレームワークを停止します。
Flight::route(string $pattern, callable $callback, bool $pass_route = false) // URLパターンをコールバックにマップします。
Flight::group(string $pattern, callable $callback) // URLのグループを作成します。パターンは文字列でなければなりません。
Flight::redirect(string $url, int $code) // 別のURLにリダイレクトします。
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レスポンスを送信します。
map
とregister
で追加された任意のカスタムメソッドもフィルタリングできます。
Learn/api
フレームワークAPIメソッド
Flightは使いやすく、理解しやすいように設計されています。以下はフレームワークの完全なメソッドセットです。これは、通常の静的メソッドであるコアメソッドと、フィルタリングやオーバーライドが可能なマッピングメソッドである拡張可能メソッドで構成されています。
コアメソッド
これらのメソッドはフレームワークのコアであり、オーバーライドすることはできません。
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
で追加された任意のカスタムメソッドもフィルタリングできます。これらのメソッドをマッピングする方法の例については、Flightを拡張ガイドを参照してください。
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/httpcaching
HTTP キャッシング
Flight は HTTP レベルのキャッシングを組み込みでサポートしています。キャッシングの条件を満たすと、Flight は HTTP 304 Not Modified
のレスポンスを返します。クライアントが同じリソースを次にリクエストするときは、ローカルにキャッシュされたバージョンを使用するよう促されます。
Last-Modified
lastModified
メソッドを使用して、UNIX タイムスタンプを渡すことでページが最後に変更された日時を設定できます。クライアントは最終変更日時の値が変更されるまで、キャッシュを引き続き使用します。
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'このコンテンツはキャッシュされます。';
});
ETag
ETag
キャッシングは Last-Modified
と似ていますが、リソースに任意の ID を指定できます:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'このコンテンツはキャッシュされます。';
});
lastModified
または etag
のいずれかを呼び出すと、キャッシュの値が設定されてチェックされます。リクエスト間でキャッシュの値が同じ場合、Flight は直ちに HTTP 304
レスポンスを送信して処理を停止します。
Learn/responses
レスポンス
Flight はレスポンスヘッダーの一部を生成するのを助けてくれますが、ユーザーに返送する内容のほとんどを制御するのはあなたです。時折、直接 Response
オブジェクトにアクセスできますが、ほとんどの場合、Flight
インスタンスを使ってレスポンスを送信します。
基本レスポンスの送信
Flight は ob_start() を使用して出力をバッファリングします。これにより、echo
または print
を使用してユーザーにレスポンスを送信でき、Flight がそれをキャプチャして適切なヘッダーと一緒に返送します。
// これはユーザーのブラウザに "Hello, World!" を送信します
Flight::route('/', function() {
echo "Hello, World!";
});
// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!
代わりに、write()
メソッドを呼び出してボディに追加することもできます。
// これはユーザーのブラウザに "Hello, World!" を送信します
Flight::route('/', function() {
// 冗長ですが、必要な場合に役立つ
Flight::response()->write("Hello, World!");
// この時点で設定したボディを取得したい場合
// 以下のようにできます
$body = Flight::response()->getBody();
});
ステータスコード
レスポンスのステータスコードを設定するには、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
レスポンスボディの設定
レスポンスボディを設定するには、write
メソッドを使用しますが、echo
または print
を使用すると、それはキャプチャされて出力バッファリング経由でレスポンスボディとして送信されます。
Flight::route('/', function() {
Flight::response()->write("Hello, World!");
});
// これは以下と同等です
Flight::route('/', function() {
echo "Hello, World!";
});
レスポンスボディのクリア
レスポンスボディをクリアしたい場合、clearBody
メソッドを使用します:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
レスポンスボディに対するコールバックの実行
レスポンスボディでコールバックを実行するには、addResponseBodyCallback
メソッドを使用します:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// これはすべてのルートのレスポンスをgzipします
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
複数のコールバックを追加でき、それらは追加された順序で実行されます。これは callable を受け入れるため、クラス配列 [ $class, 'method' ]
、クロージャ $strReplace = function($body) { str_replace('hi', 'there', $body); };
、または例えば HTML コードを最小化する関数名 'minify'
を受け入れます。
注: ルートコールバックは、flight.v2.output_buffering
構成オプションを使用している場合に動作しません。
特定のルートに対するコールバック
これを特定のルートにのみ適用したい場合、ルート内でコールバックを追加できます:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// これはこのルートのレスポンスのみgzipします
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
ミドルウェアオプション
ミドルウェアを使用して、すべてのルートにコールバックを適用することもできます:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// ここで response() オブジェクトにコールバックを適用します。
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// ボディを何らかの方法で最小化します
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
レスポンスヘッダーの設定
レスポンスのヘッダー、例えばコンテンツタイプを設定するには、header
メソッドを使用します:
// これはユーザーのブラウザにプレーンテキストで "Hello, World!" を送信します
Flight::route('/', function() {
Flight::response()->header('Content-Type', 'text/plain');
// または
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
JSON
Flight は JSON および JSONP レスポンスの送信をサポートします。JSON レスポンスを送信するには、JSON エンコードするデータを渡します:
Flight::json(['id' => 123]);
注: デフォルトで、Flight はレスポンスに
Content-Type: application/json
ヘッダーを送信します。また、JSON をエンコードする際にJSON_THROW_ON_ERROR
とJSON_UNESCAPED_SLASHES
定数を使用します。
JSON とステータスコード
ステータスコードを第2引数として渡すこともできます:
Flight::json(['id' => 123], 201);
JSON の整形出力
整形出力を有効にするために、最後の位置に引数を渡すこともできます:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Flight::json()
に渡されるオプションを変更し、よりシンプルな構文を望む場合、JSON メソッドを再マップできます:
Flight::map('json', function($data, $code = 200, $options = 0) {
Flight::_json($data, $code, true, 'utf-8', $options);
}
// そして今、これのように使用できます
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON と実行停止 (v3.10.0)
JSON レスポンスを送信して実行を停止したい場合、jsonHalt()
メソッドを使用します。これは、例えば承認チェックを行い、ユーザーが承認されていない場合にすぐに JSON レスポンスを送信し、既存のボディコンテンツをクリアして実行を停止するのに便利です。
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// ユーザーが承認されているか確認
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
}
// ルートの残りを続行
});
v3.10.0 以前では、以下のようにする必要がありました:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// ユーザーが承認されているか確認
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// ルートの残りを続行
});
JSONP
JSONP リクエストの場合、オプションでコールバック関数を定義するためのクエリパラメーター名を渡せます:
Flight::jsonp(['id' => 123], 'q');
例えば、?q=my_func
を使用して GET リクエストを行うと、出力は以下のようになります:
my_func({"id":123});
クエリパラメーター名を渡さない場合、デフォルトで jsonp
になります。
別の URL へのリダイレクト
現在のリクエストをリダイレクトするには、redirect()
メソッドを使用して新しい URL を渡します:
Flight::redirect('/new/location');
デフォルトで Flight は HTTP 303 ("See Other") ステータスコードを送信します。オプションでカスタムコードを設定できます:
Flight::redirect('/new/location', 401);
停止
フレームワークを任意の時点で停止するには、halt
メソッドを呼びます:
Flight::halt();
オプションで HTTP ステータスコードとメッセージを指定できます:
Flight::halt(200, 'Be right back...');
halt
を呼び出すと、それまでのレスポンスコンテンツは破棄されます。フレームワークを停止して現在のレスポンスを出力したい場合、stop
メソッドを使用します:
Flight::stop($httpStatusCode = null);
注:
Flight::stop()
は、レスポンスを出力するがスクリプトの実行を継続するなどの奇妙な動作をします。実行を防ぐためにexit
またはreturn
をFlight::stop()
の後に使用できますが、一般的にFlight::halt()
を使用することを推奨します。
レスポンスデータのクリア
レスポンスボディとヘッダーをクリアするには、clear()
メソッドを使用します。これにより、レスポンスに割り当てられたヘッダーをクリアし、レスポンスボディをクリアし、ステータスコードを 200
に設定します。
Flight::response()->clear();
レスポンスボディのみのクリア
レスポンスボディのみをクリアしたい場合、clearBody()
メソッドを使用します:
// これは response() オブジェクトに設定されたヘッダーを保持します。
Flight::response()->clearBody();
HTTP キャッシング
Flight は HTTP レベルでのキャッシングを組み込みでサポートします。キャッシング条件が満たされると、Flight は HTTP 304 Not Modified
レスポンスを返します。次にクライアントが同じリソースをリクエストすると、ローカルにキャッシュされたバージョンの使用が促されます。
ルートレベルのキャッシング
レスポンス全体をキャッシングしたい場合、cache()
メソッドを使用してキャッシング時間を渡します。
// これはレスポンスを5分間キャッシングします
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// または、strtotime() メソッドに渡す文字列を使用
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Last-Modified
ページが最後に変更された日時を設定するには、lastModified
メソッドに UNIX タイムスタンプを渡します。クライアントは last modified 値が変更されるまでキャッシュを継続して使用します。
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');
});
Learn/frameworkinstance
フレームワークのインスタンス
Flightをグローバルな静的クラスとして実行する代わりに、オブジェクトのインスタンスとして実行することもできます。
require 'flight/autoload.php';
$app = Flight::app();
$app->route('/', function () {
echo 'hello world!';
});
$app->start();
静的なメソッドを呼び出す代わりに、同じ名前のインスタンスメソッドをEngineオブジェクトで呼び出すことになります。
Learn/redirects
リダイレクト
redirect
メソッドを使用して、新しい URL を指定して現在のリクエストをリダイレクトできます:
Flight::redirect('/new/location');
Flight はデフォルトで HTTP 303 ステータスコードを送信します。オプションでカスタムコードを設定できます:
Flight::redirect('/new/location', 401);
Learn/events
Flight PHP のイベントシステム (v3.15.0+)
Flight PHP は、軽量で直感的なイベントシステムを導入し、アプリケーションでカスタムイベントを登録およびトリガーできます。Flight::onEvent()
と Flight::triggerEvent()
の追加により、アプリのライフサイクルにおける重要なタイミングにフックしたり、自分でイベントを定義したりして、コードをよりモジュール化し拡張しやすくできます。これらのメソッドは Flight の マップ可能なメソッド であり、必要に応じて動作をオーバーライドできます。
このガイドでは、イベントの基礎知識から、なぜそれらが有用か、使い方、実践的な例までをカバーし、初心者がその力を理解する手助けをします。
なぜイベントを使うのか?
イベントを使うことで、アプリケーションの異なる部分を互いに過度に依存しないように分離できます。この分離(デカップリング と呼ばれる)は、コードの更新、拡張、デバッグを容易にします。一つの大きな塊で全てを書く代わりに、特定のアクション(イベント)に応答する小さな独立した部分にロジックを分割できます。
ブログアプリを作成していると想像してください:
- ユーザーがコメントを投稿したとき:
- コメントをデータベースに保存する。
- ブログオーナーにメールを送信する。
- セキュリティのためにアクションをログに記録する。
イベントを使わずにこれらを一つの関数に詰め込むことになりますが、イベントを使うと分割できます:一つの部分でコメントを保存し、もう一つの部分で 'comment.posted'
というイベントをトリガーし、別のリスナーがメール送信とログを処理します。これにより、コードがクリーンになり、機能(例: 通知)を追加または削除する際にコアロジックを触らずに済みます。
一般的な用途
- ログ記録: ログインやエラーのようなアクションを記録し、メイン�コードを散らかさない。
- 通知: 何かが起きたときにメールやアラートを送信する。
- 更新: キャッシュをリフレッシュしたり、他のシステムに変更を通知したりする。
イベントリスナーの登録
イベントをリッスンするには Flight::onEvent()
を使います。このメソッドでイベントが発生したときに何が起こるかを定義します。
構文
// イベント名とコールバックを指定します
Flight::onEvent(string $event, callable $callback): void
$event
: イベントの名前 (例:'user.login'
).$callback
: イベントがトリガーされたときに実行される関数。
動作の仕組み
イベントに「購読」することで、発生したときに何をするかを Flight に伝えます。コールバックはイベントトリガーから渡された引数を受け取ることができます。
Flight のイベントシステムは同期型です。つまり、各イベントリスナーは順番に実行され、すべての登録されたリスナーが完了するまでコードの実行が続きます。これは非同期のイベントシステムとは異なり、リスナーが並行して実行されたり後で実行されたりしない点が重要です。
簡単な例
// 'user.login' イベントがトリガーされたら、ユーザーを挨拶します
Flight::onEvent('user.login', function ($username) {
echo "Welcome back, $username!";
});
ここで、'user.login'
イベントがトリガーされると、ユーザーの名前で挨拶します。
重要なポイント
- 同じイベントに複数のリスナーを追加できます。それらは登録された順序で実行されます。
- コールバックは関数、匿名関数、またはクラスのメソッドにできます。
イベントのトリガー
イベントを発生させるには Flight::triggerEvent()
を使います。これにより、登録されたリスナーを実行し、必要なデータを渡します。
構文
// イベント名と任意の引数を指定します
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; // 以降のリスナーを停止
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // これは実行されません
});
イベントメソッドのオーバーライド
Flight::onEvent()
と Flight::triggerEvent()
は 拡張 可能で、動作を再定義できます。これはイベントシステムをカスタマイズしたい上級ユーザーに便利です。例えば、ログの追加やイベントのディスパッチ方法の変更などです。
例: onEvent
のカスタマイズ
// イベント登録をログに記録します
Flight::map('onEvent', function (string $event, callable $callback) {
// 毎回のイベント登録をログに記録
error_log("New event listener added for: $event");
// 内部のデフォルト動作を呼び出す (内部イベントシステムを仮定)
Flight::_onEvent($event, $callback);
});
今度は、イベントを登録するたびにログが記録されます。
なぜオーバーライドするのか?
- デバッグや監視を追加する。
- 特定の環境でイベントを制限する (例: テスト環境で無効化)。
- 他のイベントライブラリと統合する。
イベントをどこに置くか
初心者の方は、アプリでこれらのイベントをどこに登録するのか? と疑問に思うかもしれません。Flight のシンプルさから厳格なルールはありませんが、整理しておくことでアプリが成長してもコードを維持しやすくなります。以下は実践的なオプションとベストプラクティスで、Flight の軽量性を考慮しています:
オプション 1: メインの index.php
ファイル内
小さなアプリやクイックプロトタイプの場合、イベントを index.php
ファイルにルートと一緒に登録できます。これにより全てが一つの場所にまとまり、シンプルさを優先できます。
require 'vendor/autoload.php';
// イベントを登録
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
// ルートを定義
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/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $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 () {
// ここでイベントを登録
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $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
// ユーザー関連のイベント
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
});
// ページ関連のイベント
Flight::onEvent('page.updated', function ($pageId) {
unset($_SESSION['pages'][$pageId]);
});
この構造はスケーラブルで、初心者にも親しみやすい。
初心者向けの例
実際のシナリオを通じて、イベントがどのように動作し、なぜ役立つかを説明します。
例 1: ユーザーログインのログ記録
// ステップ 1: リスナーを登録
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
});
// ステップ 2: アプリ内でトリガー
Flight::route('/login', function () {
$username = 'bob'; // フォームから取得したと仮定
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
});
なぜ役立つか: ログインコードはログについて知らなくてもイベントをトリガーするだけです。後でリスナーを追加 (例: ウェルカムメール) してもルートを変更せずに済みます。
例 2: 新規ユーザーの通知
// 新規登録のリスナー
Flight::onEvent('user.registered', function ($email, $name) {
// メール送信をシミュレート
echo "Email sent to $email: Welcome, $name!";
});
// サインアップ時にトリガー
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
});
なぜ役立つか: サインアップロジックはユーザー作成に集中し、イベントが通知を処理します。後でリスナーを追加 (例: サインアップのログ) できます。
例 3: キャッシュのクリア
// キャッシュをクリアするリスナー
Flight::onEvent('page.updated', function ($pageId) {
unset($_SESSION['pages'][$pageId]); // セッションキャッシュをクリア
echo "Cache cleared for page $pageId.";
});
// ページを編集したときにトリガー
Flight::route('/edit-page/(@id)', function ($pageId) {
// ページを更新したと仮定
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
});
なぜ役立つか: 編集コードはキャッシングを気にせず、更新をシグナルするだけです。他の部分が対応できます。
ベストプラクティス
- イベント名を明確に:
'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
はレスポンスの構築にかかった時間。
Learn/views
ビュー
Flightは、デフォルトでいくつかの基本的なテンプレーティング機能を提供します。ビューテンプレートを表示するには、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');
レイアウト
ウェブサイトには、入れ替わるコンテンツを持つ単一のレイアウトテンプレートファイルを持つことが一般的です。レイアウトにレンダリングするコンテンツを渡すには、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>
カスタムビュー
Flightを使用すると、独自のビュークラスを登録するだけでデフォルトのビューエンジンを切り替えることができます。ビューに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);
});
Learn/templates
HTML ビューとテンプレート
Flight はデフォルトで基本的なテンプレーティング機能を提供します。
Flight を使用すると、独自のビュークラスを登録するだけでデフォルトのビューエンジンを切り替えることができます。Smarty、Latte、Blade などの使用例を以下で確認してください!
組み込みビューエンジン
ビュー テンプレートを表示するには、テンプレートファイルの名前とオプションのテンプレートデータを使って 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');
レイアウト
ウェブサイトには、入れ替え可能なコンテンツを持つ単一のレイアウトテンプレートファイルが一般的です。レイアウトで使用するコンテンツをレンダリングするには、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);
});
Latte
ビュー用の Latte テンプレートエンジンを使用する方法は以下の通りです:
// Latte をビュークラスとして登録します
// Latte をロード時に設定するためのコールバック関数も渡します
Flight::register('view', Latte\Engine::class, [], function (Latte\Engine $latte) {
// ここが Latte がテンプレートをキャッシュして速度を向上させる場所です
// Latte の一つの素晴らしい点は、テンプレートに変更を加えると自動的にキャッシュを更新することです!
$latte->setTempDirectory(__DIR__ . '/../cache/');
// ビューのルートディレクトリがどこになるかを Latte に教えます
$latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../views/'));
});
// Flight::render() を正しく使用できるようにラップします
Flight::map('render', function(string $template, array $data): void {
// これは $latte_engine->render($template, $data)のようなものです
echo Flight::view()->render($template, $data);
});
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!
これらの手順に従うことで、Blade テンプレートエンジンを Flight に統合し、ビューをレンダリングすることができます。
Learn/flight_vs_fat_free
Fat-Free vs Flight
何がFat-Freeか?
Fat-Free(愛称F3)は、迅速に動的かつ堅牢なウェブアプリケーションを構築するのに役立つ強力で使いやすいPHPマイクロフレームワークです。
Flightは多くの点でFat-Freeと比較され、機能とシンプリシティの面ではおそらく最も近しい親戚です。 Fat-FreeにはFlightにはない機能が多く含まれていますが、Flightにはある機能も多くあります。 Fat-Freeは時代遅れになりつつあり、かつてほど人気がありません。
更新頻度が低くなり、コミュニティも以前ほど活発ではありません。コードは十分にシンプルですが、構文の規律が欠如していることが時々読み取りやすさを損なうことがあります。PHP 8.3でも動作しますが、コード自体はまだPHP 5.3であるかのように見えます。
Flightと比較したPros
- Fat-FreeにはFlightよりもGitHubでいくつかのスターが多い。
- 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には、「route」と呼ばれるユニークなCLI型コマンドがあり、Fat-Free自体内でCLIアプリを構築して、それをGETリクエストのように処理できます。Flightはこれをrunwayで実現しています。
Flightと比較したCons
- Fat-Freeには一部の実装テストがあり、非常に基本的な自社のtest クラスがありますが、Flightのように100%ユニットテストされていません。
- ドキュメントサイトを実際に検索するにはGoogleのような検索エンジンを使用する必要があります。
- Flightのドキュメントサイトにはダークモードがあります。(マイクを落とす)
- Fat-Freeにはメンテナンスされていないモジュールがいくつかあります。
- Flightには、Fat-Freeの組み込みの
DB \ SQL
クラスよりも少しシンプルなPdoWrapperがあります。 - Flightにはアプリケーションを保護するために使用できるpermissionsプラグインがあります。Slimではサードパーティライブラリを使用する必要があります。
- Flightには、Fat-FreeのMapperよりもORMらしいactive-recordがあります。
active-record
の追加メリットは、Fat-FreeのMapperがSQLビューを作成する必要があるのに対し、レコード間の関係を定義して自動結合することができます。 - 驚くべきことに、Fat-Freeにはルート名前空間がありません。Flightは、独自のコードと衝突しないようにすべての方法で名前空間が付けられています。
Cache
クラスが最も問題があります。 - Fat-Freeにはミドルウェアがありません。代わりに、リクエストとレスポンスをフィルタリングするために使用できる
beforeroute
およびafterroute
フックがあります。 - Fat-Freeでは、ルートをグループ化することはできません。
- Fat-Freeには依存性注入コンテナハンドラがありますが、その使用方法に関するドキュメントが非常にわずかです。
- デバッギングは、基本的にすべてが
HIVE
に保存されているため、少し複雑になることがあります。
Learn/extending
拡張
Flightは拡張可能なフレームワークとして設計されています。このフレームワークにはデフォルトのメソッドとコンポーネントのセットが付属していますが、独自のメソッドをマッピングしたり、自分のクラスを登録したり、既存のクラスやメソッドをオーバーライドすることもできます。
DIC(依存性注入コンテナ)を探しているなら、依存性注入コンテナ ページをご覧ください。
メソッドのマッピング
独自のシンプルなカスタムメソッドをマップするには、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
関数を使用します:
// あなたのクラスを登録する
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とともにロギングライブラリを使用するのは非常に簡単です。以下はMonologライブラリを使用した例です:
// index.phpまたはbootstrap.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('これは警告メッセージです');
これは、指定されたログファイルにメッセージを記録します。エラーが発生したときに何かをログに記録したい場合は、error
メソッドを使用できます:
// あなたのコントローラやルートの中で
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// あなたのカスタムエラーページを表示する
include 'errors/500.html';
});
また、before
とafter
メソッドを使用して基本的なAPM(アプリケーションパフォーマンスモニタリング)システムを作成することもできます:
// あなたのブートストラップファイルの中で
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('リクエスト '.Flight::request()->url.' は ' . round($end - $start, 4) . ' 秒かかりました');
// あなたのリクエストまたはレスポンスヘッダーを追加することもできます
// それらをログに記録するために(多くのリクエストがあるときはデータが大量になるので注意してください)
Flight::log()->info('リクエストヘッダー: ' . json_encode(Flight::request()->headers));
Flight::log()->info('レスポンスヘッダー: ' . json_encode(Flight::response()->headers));
});
フレームワークメソッドのオーバーライド
Flightは、コードを修正することなく、デフォルトの機能を自分のニーズに合わせてオーバーライドすることを可能にします。オーバーライドできるすべてのメソッドをこちらで確認できます。
たとえば、FlightがURLをルートに一致させることができない場合、notFound
メソッドが呼び出され、一般的なHTTP 404
レスポンスが送信されます。この動作をオーバーライドするには、map
メソッドを使用します:
Flight::map('notFound', function() {
// カスタム404ページを表示する
include 'errors/404.html';
});
Flightはフレームワークのコアコンポーネントを置き換えることもできます。 たとえば、デフォルトのRouterクラスを独自のカスタムクラスに置き換えることができます:
// あなたのカスタムクラスを登録する
Flight::register('router', MyRouter::class);
// FlightがRouterインスタンスをロードするとき、あなたのクラスがロードされます
$myrouter = Flight::router();
ただし、map
やregister
のようなフレームワークメソッドはオーバーライドできません。そうしようとするとエラーが発生します。
Learn/json
JSON
FlightはJSONとJSONPレスポンスを送信するためのサポートを提供します。 JSONレスポンスを送信するには、JSONエンコードするデータを渡します:
Flight::json(['id' => 123]);
JSONPリクエストの場合は、コールバック関数を定義するために使用するクエリパラメータ名をオプションで指定できます:
Flight::jsonp(['id' => 123], 'q');
そのため、?q=my_func
を使用してGETリクエストを行うと、次の出力が返されるはずです:
my_func({"id":123});
クエリパラメータ名を指定しない場合、デフォルトでjsonp
になります。
Learn/flight_vs_slim
Flight vs Slim
Slimとは?
Slim は、シンプルでありながらパワフルなウェブアプリケーションやAPIを素早く作成できるPHPマイクロフレームワークです。
v3の一部機能に対するインスピレーションの多くは、実際にはSlimからきています。ルートのグループ化や、ミドルウェアの特定の順序での実行といった2つの機能はSlimからの影響を受けています。Slim v3はシンプルさを重視した形でリリースされましたが、v4に関しては賛否両論があります。
Flightと比較したメリット
- Slimには、車輪の再発明を避けるのに役立つ便利なモジュールを作る開発者コミュニティが大規模です。
- Slimは、PHPコミュニティで一般的な多くのインターフェースと規格に従っており、相互運用性が高まっています。
- Slimには、フレームワークの学習に使用できる、まずまずのドキュメントやチュートリアルがあります(ただし、LaravelやSymfonyとは比べものになりません)。
- Slimには、フレームワークの学習に使用できるYouTubeチュートリアルやオンライン記事など、さまざまなリソースがあります。
- Slimを使用すると、PSR-7に準拠しているため、コアルーティング機能を処理するために必要なコンポーネントを自由に選択できます。
Flightと比較したデメリット
- 意外なことに、Slimはマイクロフレームワークとして考えるよりも速くありません。詳細については、TechEmpower benchmarksを参照してください。
- Flightは、軽量で高速かつ使いやすいウェブアプリケーションを構築したい開発者を対象としています。
- Flightには依存関係がなく、一方、Slimにはいくつかの依存関係があり、インストールする必要があります。
- Flightはシンプルさと使いやすさを重視しています。
- Flightの中核的な機能の1つは、後方互換性を維持することです。Slim v3からv4への移行は互換性がない変更でした。
- Flightは、初めてフレームワークの世界に足を踏み入れる開発者を対象としています。
- Flightはエンタープライズレベルのアプリケーションも可能ですが、Slimほどの例やチュートリアルがないため、開発者が組織化し、構造化されたものを保つためにより多くの努力が必要です。
- Flightは開発者にアプリケーションの制御権を与え、一方、Slimは一部のマジックを裏で使用することがあります。
- Flightには、データベースとやり取りするために使用できるシンプルなPdoWrapperがあります。Slimではサードパーティのライブラリを使用する必要があります。
- Flightにはアプリケーションをセキュリティで保護するために使用できるpermissionsプラグインがあります。Slimでは、サードパーティのライブラリを使用する必要があります。
- Flightには、データベースとやり取りするために使用できるactive-recordと呼ばれるORMがあります。Slimでは、サードパーティのライブラリを使用する必要があります。
- Flightには、アプリケーションをコマンドラインから実行するために使用できるrunwayと呼ばれるCLIアプリケーションがあります。Slimにはありません。
Learn/autoloading
オートローディング
オートローディングは、PHPにおいてクラスを読み込むディレクトリを指定する概念です。これは、require
やinclude
を使用してクラスをロードするよりも有益です。Composerパッケージを使用する際にも必要です。
デフォルトでは、Flight
クラスはComposerのおかげで自動的にオートロードされます。ただし、独自のクラスをオートロードする場合は、Flight::path()
メソッドを使用してクラスを読み込むディレクトリを指定できます。
基本例
以下のようなディレクトリツリーを持つとします:
# 例えばのパス
/home/user/project/my-flight-project/
├── app
│ ├── cache
│ ├── config
│ ├── controllers - このプロジェクトのコントローラーが含まれる
│ ├── translations
│ ├── UTILS - このアプリケーション専用のクラスが含まれる(これは後の例のためにわざと全てキャピタライズされています)
│ └── views
└── public
└── css
└── js
└── index.php
このドキュメンテーションサイトと同じファイル構造であることに気づかれたかもしれません。
次のように各ディレクトリを指定できます:
/**
* public/index.php
*/
// オートローダーにパスを追加
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
/**
* app/controllers/MyController.php
*/
// 名前空間は必要ありません
// すべてのオートロードされるクラスはパスカルケース(各単語を大文字にして、スペースなし)であることが推奨されます
// ローダー::setV2ClassLoading(false);を実行することで、クラス名にパスカル_スネーク_ケースを使用できます(バージョン3.7.2以降)
class MyController {
public function index() {
// 何かを実行
}
}
名前空間
名前空間がある場合、これを実装するのは実際には非常に簡単です。Flight::path()
メソッドを使用して、アプリケーションのルートディレクトリ(ドキュメントルートや public/
フォルダではない)を指定する必要があります。
/**
* public/index.php
*/
// オートローダーにパスを追加
Flight::path(__DIR__.'/../');
これがあなたのコントローラーの見た目です。以下の例を見てくださいが、重要な情報はコメントに注目してください。
/**
* app/controllers/MyController.php
*/
// 名前空間は必須です
// 名前空間はディレクトリ構造と同じです
// 名前空間はディレクトリ構造と同じケースを使用する必要があります
// 名前空間とディレクトリにはアンダースコアを含めることはできません(Loader::setV2ClassLoading(false)が設定されていない限り)
namespace app\controllers;
// すべてのオートロードされるクラスはパスカルケース(各単語を大文字にして、スペースなし)であることが推奨されます
// ローダー::setV2ClassLoading(false);を実行することで、クラス名にパスカル_スネーク_ケースを使用できます(バージョン3.7.2以降)
class MyController {
public function index() {
// 何かを実行
}
}
それと、utilsディレクトリ内のクラスをオートロードしたい場合は、基本的に同じことを行います:
/**
* app/UTILS/ArrayHelperUtil.php
*/
// 名前空間はディレクトリ構造とケースと一致する必要があります(UTILSディレクトリがファイルツリー内で全てキャピタライズされていることに注意)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// 何かを実行
}
}
クラス名にアンダースコアが含まれる場合
バージョン3.7.2以降、Loader::setV2ClassLoading(false);
を実行することで、クラス名にパスカル_スネーク_ケースを使用できます。これにより、クラス名にアンダースコアを使用できます。これは推奨されませんが、必要な方には利用可能です。
/**
* 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() {
// 何かを実行
}
}
Learn/troubleshooting
トラブルシューティング
このページでは、Flightを使用している際に遭遇するかもしれない一般的な問題のトラブルシューティングを支援します。
一般的な問題
404 Not Found または予期しないルートの動作
404 Not Found エラーが表示される場合(しかし、それが実際に存在していることを誓って、タイプミスではないと主張する場合)、実際にはこれは、単にそれをエコーするのではなく、ルートエンドポイントで値を返すことが問題である可能性があります。これは意図的に行われている理由ですが、開発者の一部には忍び込む可能性があります。
Flight::route('/hello', function(){
// これが 404 Not Found エラーの原因となる可能性があります
return 'Hello World';
});
// おそらく望む動作
Flight::route('/hello', function(){
echo 'Hello World';
});
これは、ルーターに組み込まれている特別なメカニズムのために行われます。このメカニズムは、戻り出力を単一の「次のルートに移動する」として処理します。この動作はRoutingセクションで文書化されています。
クラスが見つかりません(オートローディングが機能していない)
これにはいくつかの理由が考えられます。以下にいくつかの例を示しますが、autoloadingセクションも確認してください。
ファイル名が間違っています
最も一般的なのは、クラス名がファイル名と一致していないことです。
クラス名が MyClass
の場合、ファイル名は MyClass.php
とする必要があります。クラス名が MyClass
でファイル名が myclass.php
の場合、オートローダーはそれを見つけることができません。
名前空間が正しくありません
名前空間を使用している場合、名前空間はディレクトリ構造と一致している必要があります。
// コード
// もし MyController が app/controllers ディレクトリにあり、名前空間が付いている場合
// この方法は機能しません。
Flight::route('/hello', 'MyController->hello');
// 以下のオプションのいずれかを選択する必要があります
Flight::route('/hello', 'app\controllers\MyController->hello');
// または先頭に use 文がある場合
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// また、以下のように記述することもできます
Flight::route('/hello', MyController::class.'->hello');
// また...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);
path()
が定義されていません
スケルトンアプリでは、これは config.php
ファイル内で定義されていますが、クラスを見つけるためには、使用する前に path()
メソッドが定義されていることを確認する必要があります(おそらくディレクトリのルートに)。
// オートローダーにパスを追加
Flight::path(__DIR__.'/../');
Install
インストール
ファイルをダウンロードします。
もしComposerを使用している場合、次のコマンドを実行できます:
composer require flightphp/core
または、ファイルをダウンロードして、それらをウェブディレクトリに直接展開することもできます。
ウェブサーバを構成します。
Apache
Apacheを使用する場合、.htaccess
ファイルを以下のように編集してください:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
注意: サブディレクトリでflightを使用する必要がある場合は、
RewriteEngine On
の直後に行を追加してください:RewriteBase /subdir/
。注意: データベースや環境ファイルなどのすべてのサーバファイルを保護する必要がある場合は、
.htaccess
ファイルに以下を追加してください:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
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();
Guides/unit_testing
Flight PHP での PHPUnit を使用したユニットテスト
このガイドは、PHPUnit を使用して Flight PHP でユニットテストを行う入門者向けのものです。why ユニットテストが重要かを理解し、実際的に適用する方法に焦点を当てます。計算のような単純なものではなく、behavior、つまりメールの送信やレコードの保存など、アプリケーションが期待通りに動作することを確認します。シンプルな route handler から始め、controller に進み、dependency injection (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()
などを行います。 Routing を参照してください。- テストを高速に保つ: 高速なテストは頻繁な実行を促します。ユニットテストでデータベース呼び出しのような遅い操作を避けてください。テストが遅い場合、それは統合テストを書いているサインです。統合テストは実際のデータベース、HTTP 呼び出し、メール送信などを含みます。これらは有用ですが、遅く、不安定で、理由不明に失敗することがあります。
- 記述的な名前を使用する: テスト名はテストされる動作を明確に記述するべきです。これにより、読みやすさとメンテナビリティが向上します。
- グローバル変数を避ける:
$app->set()
や$app->get()
の使用を最小限にし、これらはグローバル状態として振る舞い、毎回のテストでモックが必要になります。DI または DI コンテナを優先してください(Dependency Injection Container を参照)。$app->map()
の使用も技術的に「グローバル」なので、DI に代えてください。 flightphp/session などのセッションライブラリを使用し、テストでセッションオブジェクトをモックします。Do not$_SESSION
を直接コードで呼び出さないでください。これはグローバル変数を注入し、テストを困難にします。 - 依存性注入を使用する: コントローラーに依存性(例:
PDO
、メール送信者)を注入して、論理を分離し、モックを簡略化します。依存性が多すぎるクラスがある場合、SOLID principles に従って単一責任を持つ小さなクラスにリファクタリングを検討してください。 - サードパーティサービスをモックする: データベース、HTTP クライアント (cURL)、またはメールサービスをモックして外部呼び出しを避けます。コア論理を実行しつつ、1 つか 2 つの層だけをテストします。例えば、アプリケーションがテキストメッセージを送信する場合、テストごとに実際に送信したくありません(料金が積み上がり、遅くなります)。代わりに、テキストメッセージサービスをモックし、コードが正しいパラメータでサービスを呼び出したことを検証します。
- 高いカバレッジを目指すが、完璧を求めない: 100% 行カバレッジは良いですが、すべてが正しくテストされているわけではありません(branch/path coverage in PHPUnit を調べてください)。重要な動作(例: ユーザー登録、API レスポンス、失敗したレスポンスのキャプチャ)を優先してください。
- ルートでコントローラーを使用する: ルート定義でクロージャではなくコントローラーを使用してください。デフォルトで、
flight\Engine $app
はコンストラクタ経由ですべてのコントローラーに注入されます。テストでは、$app = new Flight\Engine()
を使用して Flight をインスタンス化し、コントローラーに注入し、メソッドを直接呼び出します(例:$controller->register()
)。 Extending Flight と Routing を参照してください。 - モッキングスタイルを選択し、堅持する: PHPUnit は複数のモッキングスタイルをサポートします(例: prophecy、ビルトインのモック)、または匿名クラスを使用できます。これらはコード補完、メソッド定義の変更による破損などの利点があります。テスト全体で一貫性を保ってください。 PHPUnit Mock Objects を参照してください。
- サブクラスでテストしたいメソッド/プロパティに
protected
-visibility を使用する: これにより、パブリックにせずにテストサブクラスでオーバーライドできます。これは匿名クラスモックで特に有用です。
PHPUnit の設定
まず、PHPUnit を Composer を使用して Flight PHP プロジェクトに設定します。詳細は PHPUnit Getting Started guide を参照してください。
-
プロジェクトディレクトリで実行します:
composer require --dev phpunit/phpunit
これで最新の PHPUnit を開発依存としてインストールします。
-
プロジェクトルートに
tests
ディレクトリを作成して、テストファイルを置きます。 -
利便性のために
composer.json
にテストスクリプトを追加します:// other composer.json content "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
を実行してテストを実行します。
シンプルなルートハンドラーのテスト
基本的な route から始め、ユーザーのメール入力の検証を行います。動作をテストします:有効なメールに対して成功メッセージを返し、無効なものに対してエラーを返します。メール検証には 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' => '無効なメール'];
} else {
$responseArray = ['status' => 'success', 'message' => '有効なメール'];
}
$this->app->json($responseArray);
}
}
これをテストするために、テストファイルを作成します。 Unit Testing and SOLID Principles でテストの構造化について詳しく知れます:
// 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('有効なメール', $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('無効なメール', $output['message']);
}
}
重要なポイント:
- 要求クラスを使用して POST データのシミュレーションを行います。
$_POST
、$_GET
などのグローバルを使用しないでください。これらはテストを複雑にします(値のリセットが必要で、他のテストが失敗する可能性があります)。 - すべてのコントローラーは、DI コンテナを設定せずにデフォルトで
flight\Engine
インスタンスが注入されます。これにより、コントローラーを直接テストしやすくなります。 Flight::
の使用が一切ないため、コードがテストしやすくなります。- テストは動作を検証します:有効/無効なメールに対して正しいステータスとメッセージ。
composer test
を実行して、ルートが期待通りに動作することを確認します。Flight の requests と responses については、関連ドキュメントを参照してください。
テスト可能なコントローラーに対する依存性注入の使用
より複雑なシナリオでは、dependency injection (DI) を使用してコントローラーをテスト可能にします。Flight のグローバル(例: Flight::set()
、Flight::map()
、Flight::register()
)を避け、毎回のテストでモックが必要になります。代わりに、Flight の DI コンテナ、DICE、PHP-DI、または手動 DI を使用します。
flight\database\PdoWrapper
を raw PDO の代わりに使用します。このラッパーはモックしやすく、ユニットテストが簡単です!
データベースにユーザーを保存し、ウェルカムメールを送信するコントローラーの例:
use flight\database\PdoWrapper;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, PdoWrapper $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' => '無効なメール']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'ユーザーが登録されました']);
}
}
重要なポイント:
- コントローラーは
PdoWrapper
インスタンスとMailerInterface
(架空のサードパーティメールサービス) に依存します。 - 依存性はコンストラクタ経由で注入され、グローバルを使用しません。
コントローラーのテストにモックを使用する
UserController
の動作をテストします:メールの検証、データベースへの保存、メールの送信。データベースとメール送信者をモックしてコントローラーを分離します。
// tests/UserControllerDICTest.php
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// スタイルを混ぜる必要がある場合があります
// ここでは PHPUnit のビルトインのモックで PDOStatement を使用
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// PdoWrapper を匿名クラスでモック
$mockDb = new class($statementMock) extends PdoWrapper {
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('ユーザーが登録されました', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends PdoWrapper {
// 親コンストラクタをバイパスする空のコンストラクタ
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('呼び出されるべきではありません');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('呼び出されるべきではありません');
}
};
$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('無効なメール', $result['message']);
}
}
重要なポイント:
PdoWrapper
とMailerInterface
をモックして、実際のデータベースやメール呼び出しを避けます。- テストは動作を検証します:有効なメールはデータベースの挿入とメール送信をトリガーし、無効なメールは両方をスキップします。
- サードパーティの依存性(例:
PdoWrapper
、MailerInterface
)をモックし、コントローラーの論理を実行します。
過度なモック
コードの多くをモックしないように注意してください。以下に、なぜ悪い例を示します。UserController
を使用して、isEmailValid
(filter_var
を使用)と registerUser
の新しいメソッドに変更します。
use flight\database\PdoWrapper;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, PdoWrapper $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' => '無効なメール']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'ユーザーが登録されました']);
}
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;
}
// 常に true を返すことで実際の検証をバイパス
protected function isEmailValid($email) {
return 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('ユーザーが登録されました', $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。 詳細は Unit Testing and SOLID Principles と Troubleshooting を参照してください。
一般的な落とし穴
- 過度なモック: すべての依存性をモックしないでください;一部の論理(例: コントローラーの検証)を実行して実際の動作をテストします。 Unit Testing and SOLID Principles を参照してください。
- グローバル状態: PHP のグローバル変数(例:
$_SESSION
、$_COOKIE
)やFlight::
の多用はテストを脆くします。明示的に依存性を渡すようリファクタリングしてください。 - 複雑なセットアップ: テストセットアップが面倒な場合、クラスに依存性や責任が多すぎる可能性があり、SOLID principles に違反しているかもしれません。
ユニットテストによるスケーリング
ユニットテストは大規模プロジェクトや数ヶ月後にコードを再訪する際に輝きます。動作を文書化し、回帰を検知してアプリの再学習を防ぎます。個人開発者には重要なパス(例: ユーザーサインアップ、支払い処理)をテストしてください。チームでは、貢献acrossで一貫した動作を確保します。 Why Frameworks? でフレームワークとテストの利点について詳しく知れます。
Flight PHP ドキュメントリポジトリにあなたのテストチップを寄与してください!
Written by 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
-
パブリックディレクトリの作成: Flightは単一のエントリーポイント(
index.php
)を使用します。それ用にpublic/
フォルダを作成します:mkdir public
-
基本的な
index.php
: シンプルな「Hello World」ルートを持つpublic/index.php
を作成します:<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo 'こんにちは、Flight!'; }); Flight::start();
-
組み込みサーバーの起動: PHPの開発サーバーを使用してセットアップをテストします:
php -S localhost:8000 -t public/
http://localhost:8000
にアクセスして「こんにちは、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' => '私のブログ']); }); Flight::start();
-
レイアウトテンプレートを作成する:
app/views/layout.latte
で:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>私のブログ</h1> <nav> <a href="/">ホーム</a> | <a href="/create">投稿を作成</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Flightブログ</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": "私の最初の投稿", "content": "これはFlight PHPを使用した私の初めてのブログ投稿です!" } ]
ステップ4: ルートの定義
ルートを構成ファイルに分けることで、整理を良くしましょう。
-
routes.php
を作成:app/config/routes.php
で:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => '私のブログ']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => '投稿: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => '投稿を作成']); });
-
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' => '私のブログ', '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' => '投稿を作成']); });
ステップ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">タイトル:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">コンテンツ:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">投稿を保存</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
を訪問します。- 新しい投稿(例:「第二の投稿」とその内容)を送信します。
- ホームページでそれがリストされているのを確認します。
ステップ8: エラーハンドリングの強化
より良い404エクスペリエンスのためにnotFound
メソッドをオーバーライドします。
index.php
で:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'ページが見つかりません']);
});
app/views/404.latte
を作成します:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>申し訳ありませんが、そのページは存在しません!</p>
{/block}
次のステップ
- スタイリングの追加: より良い見た目のためにテンプレートにCSSを使用します。
- データベース:
posts.json
をSQLiteなどのデータベースに置き換えます。 - バリデーション: 重複スラッグや空の入力のチェックを追加します。
- ミドルウェア: 投稿作成のための認証を実装します。
結論
Flight PHPを使ってシンプルなブログを構築しました! このガイドでは、ルーティング、Latteによるテンプレーティング、およびフォーム送信の処理などのコア機能を示しました。すべてを軽量に保ちながら実施しています。さらにブログを進化させるためにFlightのドキュメントを探求してください!
License
The MIT License (MIT)
Copyright © 2024
@mikecao, @n0nag0n
個人が複製の許可を得ることができるように、このソフトウェアおよび関連ドキュメントファイル(以下「ソフトウェア」という)のコピーを入手することができます。 ソフトウェアを使用、コピー、変更、マージ、公開、配布、サブライセンス、販売する権利などを含む、制限なしでソフトウェアを扱う権利が、以下の条件に従って人々にそれを許可します:
上記の著作権表示およびこの許諾表示は、ソフトウェアのすべての複製または実質的な部分に含まれている必要があります。
ソフトウェアは、「現状有姿」で提供され、商品性、特定目的への適合性、および権利侵害を含むがこれに限定されない、いかなる種類の保証もなしに提供されます。 著作者または著作権保持者は、ソフトウェアまたは使用または他の取引に起因する契約上の行為、不法行為、その他の行為から生じるクレーム、損害、その他の責任について一切責任を負いません。
About
Flight とは?
Flight は、速く、シンプルで、拡張可能な PHP フレームワークです。開発者が素早く作業を完了させたい場合に最適で、一切の面倒なことを避けられます。クラシックなウェブアプリ、驚異的に速い API、または最新の AI 駆動ツールの実験など、Flight の低負荷で直感的な設計は、さまざまな用途にぴったりです。
Flight を選ぶ理由?
- 初心者向け: Flight は、PHP の新しい開発者にとって素晴らしい出発点です。明確な構造とシンプルな構文により、余計なコードに惑わされずにウェブ開発を学べます。
- プロの愛用: 経験豊富な開発者は、Flight の柔軟性と制御性に魅了されます。小規模なプロトタイプから本格的なアプリまで、スケールアップ可能で、他のフレームワークに切り替える必要はありません。
- AI 対応: Flight の最小限のオーバーヘッドとクリーンなアーキテクチャは、AI ツールや API の統合に理想的です。スマートなチャットボット、AI 駆動のダッシュボード、または単なる実験など、Flight は邪魔をせずに本質に集中できます。 AI を Flight で使用する方法について詳しく知る
クイックスタート
まず、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 は、PHP フレームワークの中でも最も速いもののひとつです。軽量なコアにより、オーバーヘッドが少なく、速度が向上します。これは、伝統的なアプリや現代の 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 の開始に役立つ例のアプリがあります。flightphp/skeleton を確認して、すぐに使えるプロジェクトを入手するか、examples ページでインスピレーションを得てください。AI の統合に興味がある場合? AI 駆動の例を探す。
コミュニティ
Matrix Chat で参加できます
そして Discord も
コントリビュート
Flight に貢献する方法は 2 つあります:
- コアフレームワークに貢献する: core repository を訪れてください。
- ドキュメントを改善する! このドキュメントウェブサイトは Github でホストされています。エラーを発見したり、改善したい場合、プルリクエストを送信してください。更新や新しいアイデア、特に AI と新技術に関するものを大歓迎です!
必要条件
Flight には PHP 7.4 以上が必要です。
注意: PHP 7.4 は、2024 年現在でいくつかの 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インファイルキャッシュクラス
利点
- 軽量でスタンドアロン、シンプル
- すべてのコードが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');
});
次のようにコード内で使用できます:
// キャッシュインスタンスを取得
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // キャッシュするデータを返します
}, 10); // 10秒
// または
$data = $cache->retrieve('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->store('simple-cache-test', $data, 10); // 10秒
}
ドキュメンテーション
完全なドキュメンテーションについてはhttps://github.com/flightphp/cacheをご覧いただき、examplesフォルダーを必ず確認してください。
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\PdoWrapper $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('You can\'t create an order. Sorry!');
}
}
}
キャッシュ
キャッシュを有効にするには、単純なwruczak/phpfilecacheライブラリを参照してください。これを有効にする例は以下の通りです。
// この $app はあなたのコードの一部である可能性があり、
// コンストラクター内で Flight::app() から取得されるか
// null を渡すと、それがコンストラクター内で取得されます
$app = Flight::app();
// 現時点では、ファイルキャッシュとしてこれを受け入れます。今後他のものも簡単に追加できます。
$Cache = new Wruczek\PhpFileCache\PhpFileCache;
$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 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/index
すばらしいプラグイン
Flightは非常に拡張可能です。Flightアプリケーションに機能を追加するために使用できるプラグインがいくつかあります。いくつかはFlightチームによって公式にサポートされており、他には開始を手助けするためのミクロ/ライトライブラリがあります。
キャッシュ
キャッシュはアプリケーションの高速化に役立つ方法です。Flightと使用できるキャッシュライブラリがいくつかあります。
- Wruczek/PHP-File-Cache - 軽量でシンプルなPHPファイル内キャッシュクラス
デバッグ
開発を行うローカル環境ではデバッグが重要です。いくつかのプラグインを使用するとデバッグ体験を向上させることができます。
- tracy/tracy - Flightと使用できるフル機能のエラーハンドラ。アプリケーションのデバッグに役立ついくつかのパネルがあります。また、容易に拡張して独自のパネルを追加できます。
- flightphp/tracy-extensions - Tracy エラーハンドラと共に使用し、Flightプロジェクトのデバッグを支援する追加パネルが含まれています。
データベース
データベースはほとんどのアプリケーションの中心です。これによりデータの保存と取得が可能になります。一部のデータベースライブラリはクエリの記述や実行を簡素化するラッパーであり、一部は完全なORMです。
- flightphp/core PdoWrapper - Flightの公式PDOラッパーで、コアの一部です。クエリの記述と実行のプロセスを簡単にするためのシンプルなラッパーです。ORMではありません。
- flightphp/active-record - Flightの公式ActiveRecord ORM/Mapper。データの簡単な取得と保存に適した優れたライブラリ。
セッション
APIにはあまり役立たないが、Webアプリケーションの構築にはセッションが状態とログイン情報の維持に重要です。
- Ghostff/Session - PHPセッションマネージャー(非同期、フラッシュ、セグメント、セッション暗号化)。セッションデータの暗号化/復号化のためにPHP open_sslを使用します。
テンプレーティング
テンプレートはUIを持つWebアプリケーションにとって重要です。Flightと使用できるいくつかのテンプレートエンジンがあります。
- flightphp/core View - コアの一部である非常に基本的なテンプレートエンジンです。プロジェクト内に複数のページがある場合は推奨されません。
- latte/latte - PHP構文に近い感覚で非常に使いやすい完全機能のテンプレートエンジンです。TwigやSmartyよりも簡単に拡張して独自のフィルターや関数を追加できます。
貢献
共有したいプラグインがありますか?リストに追加するためにプルリクエストを送信してください!
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/pdo_wrapper
PdoWrapper PDO ヘルパークラス
Flight には PDO 用のヘルパークラスが付属しています。これにより、準備/実行/fetchAll() の混乱を簡単にクエリすることができます。データベースのクエリが大幅に簡素化されます。各行の結果は Flight Collection クラスとして返され、配列構文またはオブジェクト構文を使用してデータにアクセスできます。
PDO ヘルパークラスの登録
// PDO ヘルパークラスを登録します
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
]
]);
使用法
このオブジェクトは PDO を拡張しているため、通常の PDO メソッドをすべて使用できます。データベースのクエリをより簡単にするために次のメソッドが追加されています:
runQuery(string $sql, array $params = []): PDOStatement
INSERT や UPDATE に使用したり、SELECT を while ループで使用する予定がある場合に使用します。
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM table WHERE something = ?", [ $something ]);
while($row = $statement->fetch()) {
// ...
}
// またはデータベースに書き込む場合
$db->runQuery("INSERT INTO table (name) VALUES (?)", [ $name ]);
$db->runQuery("UPDATE table SET name = ? WHERE id = ?", [ $name, $id ]);
fetchField(string $sql, array $params = []): mixed
クエリから最初のフィールドを取得します
$db = Flight::db();
$count = $db->fetchField("SELECT COUNT(*) FROM table WHERE something = ?", [ $something ]);
fetchRow(string $sql, array $params = []): array
クエリから1行を取得します
$db = Flight::db();
$row = $db->fetchRow("SELECT id, name FROM table WHERE id = ?", [ $id ]);
echo $row['name'];
// または
echo $row->name;
fetchAll(string $sql, array $params = []): array
クエリからすべての行を取得します
$db = Flight::db();
$rows = $db->fetchAll("SELECT id, name FROM table WHERE something = ?", [ $something ]);
foreach($rows as $row) {
echo $row['name'];
// または
echo $row->name;
}
IN()
構文について
IN()
ステートメント用の便利なラッパーもあります。IN()
のプレースホルダーとして単一のクエスチョンマークを渡し、その後に値の配列を単純に渡すことができます。以下はその例です:
$db = Flight::db();
$name = 'Bob';
$company_ids = [1,2,3,4,5];
$rows = $db->fetchAll("SELECT id, name FROM table WHERE name = ? AND company_id IN (?)", [ $name, $company_ids ]);
完全な例
// 例として、このラッパーを使用する方法とルートを示します
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'];
// または echo $user->name;
}
// 単一のユーザーを取得
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// 単一の値を取得
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// 進行補助として IN() 構文を使用します (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();
});
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.db
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
ランウェイ
ランウェイはCLIアプリケーションで、Flightアプリケーションの管理を支援します。コントローラを生成したり、すべてのルートを表示したりすることができます。優れたadhocore/php-cliライブラリに基づいています。
こちらをクリックして、コードを表示してください。
インストール
Composerを使用してインストールしてください。
composer require flightphp/runway
基本設定
ランウェイを実行する最初の回は、セットアッププロセスを進め、プロジェクトのルートに.runway.json
構成ファイルを作成します。このファイルには、ランウェイが正しく動作するために必要ないくつかの構成が含まれています。
使用法
ランウェイには、Flightアプリケーションを管理するために使用できる複数のコマンドがあります。ランウェイを使用する方法は2つあります。
- スケルトンプロジェクトを使用している場合、プロジェクトのルートから
php runway [command]
を実行できます。 - Composerを介してインストールされたパッケージとしてRunwayを使用している場合、プロジェクトのルートから
vendor/bin/runway [command]
を実行できます。
任意のコマンドに対して、--help
フラグを渡すと、そのコマンドの使用方法に関するより詳細な情報を取得できます。
php runway routes --help
以下はいくつかの例です。
コントローラを生成する
.runway.json
ファイルの構成に基づいて、デフォルトの場所は app/controllers/
ディレクトリにコントローラを生成します。
php runway make:controller MyController
アクティブレコードモデルを生成する
.runway.json
ファイルの構成に基づいて、デフォルトの場所は app/records/
ディレクトリにコントローラを生成します。
php runway make:record users
たとえば、次のスキーマを持つ users
テーブルがある場合:id
、name
、email
、created_at
、updated_at
、app/records/UserRecord.php
ファイルに類似したファイルが作成されます:
<?php
declare(strict_types=1);
namespace app\records;
/**
* ユーザーテーブルのアクティブレコードクラス。
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $name
* @property string $email
* @property string $created_at
* @property string $updated_at
* // 関係を定義した場合、ここに関係を追加できます
* @property CompanyRecord $company 関係の例
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations モデルの関係を設定します
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [];
/**
* コンストラクタ
* @param mixed $databaseConnection データベースへの接続
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
すべてのルートを表示する
登録されているすべてのFlightのルートを表示します。
php runway routes
特定のルートのみを表示したい場合、フラグを渡してルートをフィルタリングできます。
# GETルートのみを表示
php runway routes --get
# POSTルートのみを表示
php runway routes --post
# など
ランウェイのカスタマイズ
Flight向けのパッケージを作成しているか、プロジェクトに独自のカスタムコマンドを追加したい場合は、プロジェクト/パッケージ向けに src/commands/
、flight/commands/
、app/commands/
、または commands/
ディレクトリを作成してください。
コマンドを作成するには、AbstractBaseCommand
クラスを拡張し、__construct
メソッドとexecute
メソッドを最低限実装します。
<?php
declare(strict_types=1);
namespace flight\commands;
class ExampleCommand extends AbstractBaseCommand
{
/**
* コンストラクタ
*
* @param array<string,mixed> $config .runway-config.jsonからのJSON構成
*/
public function __construct(array $config)
{
parent::__construct('make:example', 'ドキュメントの例を作成', $config);
$this->argument('<funny-gif>', '面白いGIFの名前');
}
/**
* 関数を実行
*
* @return void
*/
public function execute(string $controller)
{
$io = $this->app()->io();
$io->info('例を作成します...');
// ここで何かを実行
$io->ok('例が作成されました!');
}
}
独自のカスタムコマンドをFlightアプリケーションに組み込む方法については、adhocore/php-cliドキュメントを参照してください!
Awesome-plugins/tracy_extensions
Tracy Flight パネル拡張
これは、Flight をより豊かに扱うための拡張機能のセットです。
- Flight - すべての Flight 変数を分析します。
- Database - ページで実行されたすべてのクエリを分析します(データベース接続を正しく開始した場合)。
- Request - すべての
$_SERVER
変数を分析し、グローバルペイロード($_GET
、$_POST
、$_FILES
)を調べます。 - Session - セッションが有効な場合、すべての
$_SESSION
変数を分析します。
これはパネルです
各パネルは、アプリケーションに関する非常に役立つ情報を表示します!
こちら をクリックしてコードを表示します。
インストール
composer require flightphp/tracy-extensions --dev
を実行すると、すぐに開始できます!
設定
これを開始するには、ほとんど設定は必要ありません。この拡張機能を使用する前に、Tracy デバッガーを開始する必要があります https://tracy.nette.org/en/guide:
<?php
use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;
// ブートストラップコード
require __DIR__ . '/vendor/autoload.php';
Debugger::enable();
// 環境を指定する必要がある場合、Debugger::enable(Debugger::DEVELOPMENT) を使用します
// アプリでデータベース接続を使用する場合、
// 開発環境でのみ使用する(本番環境では使用しないでください!)
// 通常のPDO接続と同じパラメータです
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// Flight フレームワークにこれを付与する場合
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// クエリを実行すると、時間、クエリ、パラメータをキャプチャします
// これでつながります
if(Debugger::$showBar === true) {
// これをfalseにしないと、Tracy が正しく表示されません
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) {
// これをfalseにしないと、Tracy が正しく表示されません
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}
// ルートや他のもの...
Flight::start();
Latte
プロジェクトに Latte をインストールしている場合、Latte パネルを使用してテンプレートを分析できます。TracyExtensionLoader
コンストラクタの2番目のパラメータで latte
キーで Latte インスタンスを渡します。
use Latte\Engine;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('latte', Engine::class, [], function($latte) {
$latte->setTempDirectory(__DIR__ . '/temp');
// ここで Latte パネルを Tracy に追加します
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});
if(Debugger::$showBar === true) {
// これをfalseにしないと、Tracy が正しく表示されません
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
Awesome-plugins/apm
FlightPHP APM ドキュメント
FlightPHP APM にようこそ—あなたのアプリのパーソナルパフォーマンスコーチです! このガイドは、Application Performance Monitoring (APM) を FlightPHP で設定し、使用し、習得するためのロードマップです。 遅いリクエストを追いかける場合でも、レイテンシチャートに熱中したい場合でも、私たちがカバーします。 アプリをより速くし、ユーザーをより幸せにし、デバッグセッションを簡単にするために進みましょう!
APM の重要性
想像してみてください:あなたのアプリは忙しいレストランです。 オーダーにどれだけ時間がかかるかを追跡する方法がない、またはキッチンがどこで詰まっているのかわからない場合、顧客が不機嫌になって去る理由を推測するだけです。 APM はあなたの副料理長のようなものです—着信リクエストからデータベースクエリまですべてのステップを監視し、遅延を引き起こすものをフラグします。 遅いページはユーザーを失います(研究によると、サイトの読み込みに 3 秒以上かかると 53% が離脱します!)、そして APM はそれらの問題を 事前に キャッチします。 これは積極的な安心—「なぜこれは壊れているの?」という瞬間を少なくし、「これがどれほどスムーズに動くか!」という勝利を増やします。
インストール
Composer で始めましょう:
composer require flightphp/apm
必要なもの:
- PHP 7.4+: LTS Linux ディストリビューションとの互換性を保ちつつ、現代的な PHP をサポートします。
- 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\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// もしデータベース接続を追加する場合
// これは Tracy Extensions からの PdoWrapper または PdoQueryCapture でなければなりません
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True を指定して APM での追跡を有効にします。
$Apm->addPdoConnection($pdo);
ここで何が起こっているのか?
LoggerFactory::create()
はあなたの構成を入手し(すぐに詳しく)、ロガーをセットアップします—デフォルトで SQLite。Apm
はスター—it は 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" } }
このプロセスは、このセットアップの移行を実行するかどうかを尋ねます。 初めて設定する場合、答えは yes です。
なぜ 2 つの場所が必要なのか? 生のメトリクスは急速に積み上がります(フィルタリングされていないログを考えて)。 ワーカーがそれを構造化された宛先でダッシュボード用に処理します。 すべてを整頓します!
3. ワーカーでメトリクスを処理する
ワーカーは生のメトリクスをダッシュボード対応のデータに変換します。 1 度実行します:
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 で分離; `tmux attach -t apm-worker` で再接続
ログアウトしても実行を継続。
-
カスタム調整:
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
: PATH にない場合、PHP を指す。
ブラウザで URL を開いて探検!
プロダクションモード
プロダクションでは、ファイアウォールや他のセキュリティ対策があるため、ダッシュボードを実行するためにいくつかの手法を試す必要があるかもしれません。 オプションは:
- リバースプロキシを使用: Nginx または Apache を設定してリクエストをダッシュボードに転送。
- SSH トンネル: サーバーに SSH でアクセスできる場合、
ssh -L 8080:localhost:8001 youruser@yourserver
を使用してダッシュボードをローカルマシンにトンネル。 - VPN: サーバーが VPN の背後にあり、接続して直接ダッシュボードにアクセス。
- ファイアウォールを構成: ポート 8001 をあなたの IP またはサーバーのネットワーク用に開く(または設定したポート)。
- Apache/Nginx を構成: アプリケーションの前にウェブサーバーがある場合、ドメインまたはサブドメインに構成。 これを行う場合、文書ルートを
/path/to/your/project/vendor/flightphp/apm/dashboard
に設定。
異なるダッシュボードを望む?
独自のダッシュボードを作成できます! vendor/flightphp/apm/src/apm/presenter ディレクトリを調べて、データを表示するためのアイデアを得てください!
ダッシュボードの機能
ダッシュボードはあなたの APM HQ—以下が見えます:
- リクエストログ: タイムスタンプ、URL、レスポンスコード、合計時間を持つすべてのリクエスト。 「詳細」をクリックしてミドルウェア、クエリ、エラーを表示。
- 最も遅いリクエスト: 時間を消費するトップ 5 リクエスト(例: 「/api/heavy」 at 2.5s)。
- 最も遅いルート: 平均時間によるトップ 5 ルート—パターンを特定するのに最適。
- エラーレート: 失敗するリクエストの割合(例: 2.3% 500s)。
- レイテンシパーセンタイル: 95th (p95) と 99th (p99) レスポンス時間—最悪のシナリオを知る。
- レスポンスコードチャート: 時間経過による 200s、404s、500s の視覚化。
- 長いクエリ/ミドルウェア: トップ 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\PdoWrapper;
$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True を指定して APM での追跡を有効にします。
$Apm->addPdoConnection($pdo);
何を得るのか:
- クエリテキスト(例:
SELECT * FROM users WHERE id = ?
) - 実行時間(例: 0.015s)
- 行数(例: 42)
注意:
- オプション: DB 追跡が必要ない場合、スキップ。
- PdoWrapper のみ: コア PDO はまだフックされていません—待機中!
- パフォーマンス警告: DB が重いサイトですべてのクエリをログに残すと遅くなる可能性があります。 サンプリング(
$Apm = new Apm($ApmLogger, 0.1)
)を使用して負荷を軽減。
例の出力:
- クエリ:
SELECT name FROM products WHERE price > 100
- 時間: 0.023s
- 行: 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 を追加:
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 データベースが大きい場合、これらの移行には時間がかかる可能性があります。 オフピーク時に実行することを検討してください。
古いデータの消去
データベースを整頓するために、古いデータを消去できます。 これは忙しいアプリを実行していて、データベースのサイズを管理したい場合に特に便利です。 以下のコマンドで実行:
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 を無効にし、
-
データベースクエリを追跡していない?
PdoWrapper
をデータベース接続で使用していることを確認。- コンストラクタの最後の引数を
true
にしていることを確認。
Awesome-plugins/tracy
Tracy
Tracy は Flight と一緒に使用できる素晴らしいエラーハンドラです。アプリケーションのデバッグに役立つ数々のパネルがあります。拡張して独自のパネルを追加するのも非常に簡単です。Flight チームは、flightphp/tracy-extensions プラグイン用にいくつかのパネルを作成しました。
インストール
Composer でインストールします。Tracy は本番用のエラーハンドリングコンポーネントが付属しているため、実際には dev バージョンなしでインストールする必要があります。
composer require tracy/tracy
基本設定
開始するための基本的な設定オプションがあります。詳細については、Tracy ドキュメント を参照してください。
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); // Debugger バーが表示されている場合、Flight によって content-length が設定できません。
// これは Flight 用の Tracy 拡張機能に固有のものです。これを含めた場合は有効にしてください。
new TracyExtensionLoader($app);
}
便利なヒント
コードのデバッグ中に、データを出力するための非常に役立つ関数がいくつかあります。
bdump($var)
- これにより、変数が Tracy バーにダンプされます(別のパネルで表示されます)。dumpe($var)
- これにより、変数がダンプされ、その後すぐにプログラムが停止します。
Awesome-plugins/active_record
Flight アクティブレコード
アクティブレコードとは、データベースのエンティティをPHPオブジェクトにマッピングすることです。簡単に言えば、データベースにユーザーテーブルがある場合、そのテーブルの行をUser
クラスと$user
オブジェクトに「翻訳」することができます。 基本例を参照してください。
GitHubのリポジトリについてはこちらをクリックしてください。
基本例
次のテーブルがあると仮定しましょう:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
password TEXT
);
このテーブルを表す新しいクラスを設定できます:
/**
* アクティブレコードクラスは通常単数です
*
* ここにテーブルのプロパティをコメントとして追加することを強くお勧めします
*
* @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();
どうですか?楽しいでしょう?インストールして始めましょう!
インストール
Composerで簡単にインストールできます
composer require flightphp/active-record
使用法
これはスタンドアロンライブラリとして使用することも、Flight PHPフレームワークと一緒に使用することもできます。完全にあなたの好みです。
スタンドアロン
コンストラクタにPDO接続を渡すことを確認してください。
$pdo_connection = new PDO('sqlite:test.db'); // これは単なる例で、実際にはデータベース接続を使用することになります
$User = new User($pdo_connection);
常にコンストラクタでデータベース接続を設定したくないですか?他のアイデアについてはデータベース接続管理を参照してください!
Flightでメソッドとして登録
Flight PHPフレームワークを使用している場合、ActiveRecordクラスをサービスとして登録できますが、本当にそうする必要はありません。
Flight::register('user', 'User', [ $pdo_connection ]);
// 次に、コントローラや関数などでこのように使用できます。
Flight::user()->find(1);
runway
メソッド
runway は、Flight用のCLIツールで、このライブラリ用のカスタムコマンドがあります。
# 使用法
php runway make:record database_table_name [class_name]
# 例
php runway make:record users
これにより、app/records/
ディレクトリにUserRecord.php
という新しいクラスが作成され、次の内容が含まれます:
<?php
declare(strict_types=1);
namespace app\records;
/**
* ユーザーテーブル用のアクティブレコードクラスです。
* @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関数
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' ]);
// 上記の配列の代わりにこのようにプライマリキーを設定することもできます。
$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'; // これは変更されているので「ダーティ」と見なされます。
$user->update();
// 更新され、データベースに永続化されたので、ダーティデータはありません。
$user->password = password_hash('newpassword'); // これはダーティです。
$user->dirty(); // 引数を何も渡さないと、すべてのダーティエントリがクリアされます。
$user->update(); // 何も捕捉されていないため、何も更新されません。
$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 名前とパスワードの両方が更新されます。
copyFrom(array $data): ActiveRecord
(v0.4.0)
これはdirty()
メソッドの別名です。何をしているのかがより明確です。
$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 名前とパスワードの両方が更新されます。
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クエリメソッド
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)
返されるレコードの数を制限します。二つ目のintが指定された場合、SQLと同様にオフセットを制限します。
$user->orderby('name DESC')->limit(0, 10)->findAll();
WHERE条件
equal(string $field, mixed $value) / eq(string $field, mixed $value)
field = $value
の場合
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
field <> $value
の場合
$user->ne('id', 1)->find();
isNull(string $field)
field IS NULL
の場合
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
field IS NOT NULL
の場合
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
field > $value
の場合
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
field < $value
の場合
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
field >= $value
の場合
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
field <= $value
の場合
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
field LIKE $value
または field NOT LIKE $value
の場合
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
field IN($value)
または field NOT IN($value)
の場合
$user->in('id', [1, 2])->find();
between(string $field, array $values)
field BETWEEN $value AND $value1
の場合
$user->between('id', [1, 2])->find();
OR条件
条件をOR文でラップすることも可能です。これは、startWrap()
およびendWrap()
メソッドを使用するか、フィールドと値の後に条件の3番目のパラメータを指定することで行います。
// メソッド 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// これは `id = 1 AND (name = 'demo' OR name = 'test')` と評価されます
// メソッド 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// これは `id = 1 OR name = 'demo'` と評価されます
リレーションシップ
このライブラリを使用して、いくつかの種類のリレーションシップを設定できます。一対多および一対一のリレーションシップをテーブル間に設定できます。これには、事前にクラス内で追加のセットアップが必要です。
$relations
配列の設定は難しくはありませんが、正しい構文を推測することは混乱を招くことがあります。
protected array $relations = [
// キーの名前は好きなように設定できます。アクティブレコードの名前はおそらく良いでしょう。例:user、contact、client
'user' => [
// 必須
// self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
self::HAS_ONE, // これはリレーションのタイプです
// 必須
'Some_Class', // これは参照する「他の」アクティブレコードクラスです
// 必須
// リレーションシップの種類に応じて
// self::HAS_ONE = 結合を参照する外部キー
// self::HAS_MANY = 結合を参照する外部キー
// self::BELONGS_TO = 結合を参照するローカルキー
'local_or_foreign_key',
// 他のモデルのプライマリキーにのみ結合しますので、ご注意ください。
// オプショナル
[ '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; // これはユーザー名です
すごいですね!
カスタムデータの設定
場合によっては、アクティブレコードにカスタム計算など、オブジェクトに直接接続したいユニークなものを添付する必要があるかもしれません。
setCustomData(string $field, mixed $value)
カスタムデータは、setCustomData()
メソッドを使用して添付します。
$user->setCustomData('page_view_count', $page_view_count);
そして、通常のオブジェクトプロパティのように参照します。
echo $user->page_view_count;
イベント
このライブラリのもう一つの素晴らしい機能は、イベントに関するものです。イベントは、呼び出す特定のメソッドに基づいて特定のタイミングでトリガーされます。自動的にデータをセットアップするのに非常に役立ちます。
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 beforeUpdate(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 afterUpdate(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 '彼は勇敢な兵士でした... :cry-face:';
}
}
データベース接続管理
このライブラリを使用する際、データベース接続をいくつかの異なる方法で設定できます。コンストラクタ内で接続を設定することも、$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)
で接続を再設定することができます。
貢献
ぜひご協力ください。 :D
セットアップ
貢献する際は、composer test-coverage
を実行して100%のテストカバレッジを維持してください(これは真の単体テストカバレッジではなく、むしろ統合テストのカバレッジです)。
また、composer beautify
およびcomposer phpcs
を実行して、すべてのリンティングエラーを修正することを確認してください。
ライセンス
MIT
Awesome-plugins/latte
ラッテ
ラッテ は、非常に使いやすく、Twig や Smarty よりも PHP 構文に近いテンプレートエンジンです。フル機能を備えており、独自のフィルタや関数を追加することも非常に簡単です。
インストール
Composer を使用してインストールします。
composer require latte/latte
基本的な設定
始めるための基本的な設定オプションがあります。ラッテドキュメント で詳細を確認できます。
use Latte\Engine as LatteEngine;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('latte', LatteEngine::class, [], function(LatteEngine $latte) use ($app) {
// ここがラッテがテンプレートをキャッシュして処理を高速化する場所です
// ラッテの素晴らしい点の1つは、テンプレートを変更すると自動的にキャッシュを更新します!
$latte->setTempDirectory(__DIR__ . '/../cache/');
// ビューのルートディレクトリを示す Latte の設定
// $app->get('flight.views.path') は config.php ファイルで設定されています
// または `__DIR__ . '/../views/'` のようなものも行えます
$latte->setLoader(new \Latte\Loaders\FileLoader($app->get('flight.views.path')));
});
シンプルなレイアウトの例
以下はレイアウトファイルのシンプルな例です。このファイルは他のすべてのビューを囲むために使用されます。
<!-- 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">
© 著作権
</div>
</body>
</html>
そして、コンテンツブロック内でレンダリングされるファイルがあります:
<!-- app/views/home.latte -->
<!-- このファイルが layout.latte ファイル内にあることを Latte に伝えます -->
{extends layout.latte}
<!-- レイアウト内のコンテンツブロック内に表示されるコンテンツです -->
{block content}
<h1>ホームページ</h1>
<p>アプリへようこそ!</p>
{/block}
次に、この内容を関数またはコントローラ内でレンダリングする際には、次のように行います:
// シンプルなルート
Flight::route('/', function () {
Flight::latte()->render('home.latte', [
'title' => 'ホームページ'
]);
});
// もしくはコントローラを使用している場合
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::latte()->render('home.latte', [
'title' => 'ホームページ'
]);
}
}
ラッテを最大限に活用するための詳細については、Latte Documentation を参照してください。
Awesome-plugins/awesome_plugins
素晴らしいプラグイン
Flight は非常に拡張性が高く、さまざまなプラグインを使って Flight アプリケーションに機能を追加できます。一部は Flight チームによって公式にサポートされており、他のものはマイクロ/ライトなライブラリで、始めるのに役立ちます。
API ドキュメント
API ドキュメントはどの API にとっても重要です。開発者が API とどのように相互作用するかを理解し、何を期待するかを知る助けになります。Flight プロジェクトの API ドキュメントを生成するためのいくつかのツールがあります。
- FlightPHP OpenAPI Generator - Daniel Schreiber によるブログ投稿で、OpenAPI 仕様を FlightPHP と一緒に使用して、API ファーストアプローチで API を構築する方法について説明しています。
- SwaggerUI - Swagger UI は、Flight プロジェクトの API ドキュメントを生成するのに優れたツールです。使いやすく、ニーズに合わせてカスタマイズできます。これは Swagger ドキュメントを生成するための PHP ライブラリです。
アプリケーション性能監視 (APM)
アプリケーション性能監視 (APM) はどのアプリケーションにとっても重要です。アプリケーションのパフォーマンスを理解し、ボトルネックがどこにあるかを把握する助けになります。Flight で使用できる APM ツールがいくつかあります。
- official flightphp/apm - Flight APM は、Flight アプリケーションを監視するためのシンプルな APM ライブラリです。アプリケーションのパフォーマンスを監視し、ボトルネックを特定するのに使用できます。
認可/権限
認可と権限は、誰が何にアクセスできるかを制御する必要があるアプリケーションにとって重要です。
- official flightphp/permissions - 公式の Flight Permissions ライブラリです。このライブラリは、ユーザーとアプリケーションのレベルで権限を追加する簡単な方法です。
キャッシング
キャッシングはアプリケーションの速度を向上させる優れた方法です。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 PdoWrapper - 公式の Flight PDO Wrapper で、コアの一部です。これはクエリを書くプロセスを簡素化するためのシンプルなラッパーです。ORM ではありません。
- official flightphp/active-record - 公式の Flight ActiveRecord ORM/Mapper です。データベースからデータを簡単に取得し、保存するための優れた小さなライブラリです。
- byjg/php-migration - プロジェクトのすべてのデータベース変更を追跡するためのプラグインです。
暗号化
暗号化は、機密データを保存するアプリケーションにとって重要です。データを暗号化し復号化するのはそれほど難しくありませんが、暗号化キーを適切に保存するのはcan be difficultです。一番重要なのは、暗号化キーを公開ディレクトリに保存したり、コードリポジトリにコミットしたりしないことです。
- 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 Session Manager (非ブロッキング、フラッシュ、セグメント、セッション暗号化)。PHP open_ssl を使用してセッションデータのオプションの暗号化/復号化を行います。
テンプレート
テンプレートは UI を持つ Web アプリケーションの基盤です。Flight で使用できるテンプレートエンジンがいくつかあります。
- deprecated flightphp/core View - これはコアの一部である非常に基本的なテンプレートエンジンです。プロジェクトに数ページ以上ある場合、推奨されません。
- latte/latte - Latte は、使いやすく Twig や Smarty よりも PHP 構文に近い、フル機能のテンプレートエンジンです。また、拡張して独自のフィルターや関数を追加することも簡単です。
WordPress 統合
Flight を WordPress プロジェクトで使用したいですか? それ用の便利なプラグインがあります!
- n0nag0n/wordpress-integration-for-flight-framework - この WordPress プラグインは、Flight を WordPress と並行して実行できます。カスタム API、マイクロサービス、または Flight フレームワークを使って WordPress サイトにフルアプリを追加するのに最適です。両方の世界のベストを組み合わせたい場合に超便利です!
貢献
共有したいプラグインがありますか? リストに追加するためにプルリクエストを送信してください!
Media
メディア
私たちは、Flightに関するインターネット上のさまざまなタイプのメディアを可能な限り追跡しようとしました。以下に、Flightについてさらに学ぶために使用できるさまざまなリソースがあります。
記事と解説
- ユニットテストとSOLID原則 by Brian Fenton (2015?)
- PHP Web Framework Flight by ojambo (2025)
- 定義、生成、および実装: OpenAPI Generator と FlightPHP を使用した API-First アプローチ by Daniel Schreiber (2025)
- 2024 年のベスト PHP マイクロフレームワーク by n0nag0n (2024)
- Flight Framework を使用した RESTful API の作成 by n0nag0n (2024)
- Flight を使用したシンプルなブログの構築 パート 2 by n0nag0n (2024)
- Flight を使用したシンプルなブログの構築 パート 1 by n0nag0n (2024)
- 🚀 PHP で Flight Framework を使用したシンプルな CRUD API の構築 by soheil-khaledabadi (2024)
- Flight マイクロフレームワークを使用した PHP ウェブアプリケーションの構築 by Arthur C. Codex (2023)
- 2024 年のウェブ開発におけるベスト PHP フレームワーク by Ravikiran A S (2023)
- 2023 年のためのトップ 12 PHP フレームワーク: 包括的なガイド by marketing kbk (2023)
- おそらく聞いたことがない 5 つの PHP フレームワーク by n0nag0n (2022)
- 2023 年にウェブ開発者が検討すべき 12 つのトップ PHP フレームワーク by Anna Monus (2022)
- クラウドサーバー上のベスト PHP マイクロフレームワーク by Shahzeb Ahmed (2021)
- PHP フレームワーク: ウェブ開発のためのトップ 15 の強力なもの by AHT Tech (2020)
- FlightPHP を使用した簡単な PHP ルーティング by Lucas Conceição (2019)
- 新しい PHP フレームワーク (Flight) を試す by Leon (2017)
- Backbonejs と一緒に FlightPHP を設定する by Timothy Tocci (2015)
動画とチュートリアル
- 10 分で Flight PHP アプリを MVC と MariaDB で構築! (初心者向け) by ojamboshop (2025)
- PHP と FlightPHP を使用して IoT デバイス用の REST API を作成 - ESP32 API by IoT Craft Hub (2024)
- PHP Flight Framework 簡単な導入ビデオ by n0nag0n (2024)
- Flightphp でヘッダー HTTP コードを設定 (3 つの解決策!!) by Roel Van de Paar (2024)
- PHP Flight Framework チュートリアル。超簡単な API プロジェクト! by n0nag0n (2022)
- flight を使用した php と mysql と bootstrap の CRUD ウェブアプリケーション by Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Flight PHP マイクロフレームワークのための Lighttpd リライトルール by Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (タガログ語) by Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (タガログ語) by Info Singkat Official (2020)
- PHP で JSON REST API を作成する方法 - Part 2 by Codewife (2018)
- PHP で JSON REST API を作成する方法 - Part 1 by Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel by Codemarket (2016)
- Tutorial 1 Flight PHP - インストール by absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 by absagg (2014)
Examples
迅速なスタートが必要ですか?
新しいFlightプロジェクトを始めるためのオプションは2つあります:
- フルスケルトンボイラープレート: コントローラーとビューを含む、より完全な例です。
- シングルファイルスケルトンボイラープレート: アプリをシンプルな単一ファイルで実行するために必要なすべてを含む単一ファイルです。
コミュニティが提供した例:
- flightravel: FlightPHPとLaravelディレクトリ、PHPツール + GHアクションが含まれています。
- fleact - ReactJSとの統合を持つFlightPHPスターターキット。
- flastro - Astro統合を持つFlightPHPスターターキット。
- velt - Veltは、FlightPHPバックエンドを持つ迅速で簡単なSvelteスターターテンプレートです。
インスピレーションが必要ですか?
これらはFlightチームによって公式にスポンサーされているわけではありませんが、Flightを使って構築された自分のプロジェクトを構成する方法についてのアイデアを得ることができます!
- Decay - HTMXとSleekDBを使用し、ゾンビに関するFlight v3! (デモ)
- Flight Example Blog - ミドルウェア、コントローラー、アクティブレコード、Latteを使ったFlight v3。
- Flight CRUD RESTful API - Flightフレームワークを使用したシンプルなCRUD APIプロジェクトで、新しいユーザーがCRUD操作とデータベース接続を迅速に設定できる基本ストラクチャを提供します。このプロジェクトは、RESTful API開発のためのFlightの使用法を示しており、初心者にとって理想的な学習ツールであり、より経験豊富な開発者にとって有用なスターターキットです。
- Flight School Management System - Flight v3
- コメント付きのペーストビン - Flight v3
- 基本的なスケルトンアプリ
- 例のウィキ
- ITイノベーターPHPフレームワークアプリケーション
- LittleEducationalCMS (スペイン語)
- イタリアのイエローページAPI
- 一般的なコンテンツ管理システム (非常に少ないドキュメント付き)
- Flightとmedooに基づく小さなphpフレームワーク。
- 例のMVCアプリケーション
自分の例を共有したいですか?
共有したいプロジェクトがある場合は、このリストに追加するためのプルリクエストを送信してください!
Install/install
インストール
ファイルのダウンロード
システムにPHPがインストールされていることを確認してください。されていない場合は、システムにインストールする方法についてはこちらをクリックしてください。
Composerを使用している場合は、次のコマンドを実行できます:
composer require flightphp/core
もしくはファイルをダウンロードして、それらをウェブディレクトリに直接展開します。
Webサーバーの設定
組み込みのPHP開発サーバー
これは最も簡単に立ち上げる方法です。組み込みサーバーを使用してアプリケーションを実行したり、SQLiteをデータベースとして使用したりすることができます(システムにsqlite3がインストールされている限り)、そして多くのものを必要としません! PHPがインストールされたら、次のコマンドを実行してください:
php -S localhost:8000
その後、ブラウザを開いてhttp://localhost:8000
に移動します。
プロジェクトのドキュメントルートを異なるディレクトリにする場合(例: プロジェクトが ~/myproject
であるが、ドキュメントルートは ~/myproject/public/
である場合)、~/myproject
ディレクトリにいる場合は、以下のコマンドを実行できます:
php -S localhost:8000 -t public/
その後、ブラウザを開いて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をインストールしていない場合は、システムにNginx Apacheをインストールする方法を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();
PHPのインストール
すでにシステムに php
がインストールされている場合は、この手順をスキップしてダウンロードセクションに移動してください
分かりました!macOS、Windows 10/11、Ubuntu、Rocky Linux にPHPをインストールする手順についての説明があります。さらに、異なるバージョンのPHPをインストールする方法についても説明します。
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 セクションを確認してください。