Learn

Flightについて学ぶ

Flightは、PHPのための高速でシンプルな拡張可能なフレームワークです。非常に多用途であり、あらゆる種類のウェブアプリケーションの構築に使用できます。
シンプルさを念頭に置いて構築されており、理解しやすく、使いやすい形で書かれています。

重要なフレームワークの概念

フレームワークを使う理由

なぜフレームワークを使用すべきかについての短い記事があります。フレームワークを使い始める前に、その利点を理解するのは良い考えです。

さらに、@lubianaによって素晴らしいチュートリアルが作成されました。具体的にFlightについて詳細に説明しているわけではありませんが、
このガイドは、フレームワークに関する主要な概念を理解し、なぜそれらを使用することが有益であるかを理解するのに役立ちます。
チュートリアルはこちらで見つけることができます。

Flightと他のフレームワークの比較

Laravel、Slim、Fat-Free、Symfonyなどの他のフレームワークからFlightに移行している場合、このページは両者の違いを理解するのに役立ちます。

コアトピック

オートローディング

アプリケーションで独自のクラスをオートロードする方法を学びます。

ルーティング

ウェブアプリケーションのルートを管理する方法を学びます。これには、ルートのグループ化、ルートパラメータ、およびミドルウェアも含まれます。

ミドルウェア

アプリケーションでリクエストとレスポンスをフィルタリングするためにミドルウェアを使用する方法を学びます。

リクエスト

アプリケーションでリクエストとレスポンスを処理する方法を学びます。

レスポンス

ユーザーにレスポンスを送信する方法を学びます。

イベント

アプリケーションにカスタムイベントを追加するためのイベントシステムの使用方法を学びます。

HTMLテンプレート

組み込みのビューエンジンを使用してHTMLテンプレートをレンダリングする方法を学びます。

セキュリティ

一般的なセキュリティ脅威からアプリケーションを保護する方法を学びます。

設定

アプリケーションのためにフレームワークを設定する方法を学びます。

Flightの拡張

独自のメソッドやクラスを追加してフレームワークを拡張する方法を学びます。

イベントとフィルタリング

イベントシステムを使用してメソッドや内部フレームワークメソッドにフックを追加する方法を学びます。

依存性注入コンテナ

アプリケーションの依存関係を管理するために依存性注入コンテナ(DIC)を使用する方法を学びます。

フレームワークAPI

フレームワークのコアメソッドについて学びます。

v3への移行

互換性はほとんど維持されていますが、v2からv3に移行する際に知っておくべきいくつかの変更があります。

トラブルシューティング

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モジュールが非常に多いですが、実際には別のライブラリを使用するか、独自のコードを書くこともできます。

フライトとの比較でのメリット

フライトとの比較でのデメリット

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);

利用可能な設定

以下は利用可能な設定の一覧です:

ローダーの設定

_ をクラス名に含める場合の追加のローダーの設定があります。これにより、クラスを自動的に読み込むことができます。

// アンダースコアを使用したクラスのローディングを有効にする
// デフォルトは 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/security

セキュリティ

セキュリティはウェブアプリケーションにとって非常に重要です。アプリケーションが安全で、ユーザーのデータが守られていることを確認したいでしょう。Flightは、ウェブアプリケーションのセキュリティを確保するための多くの機能を提供しています。

ヘッダー

HTTPヘッダーは、ウェブアプリケーションを保護する最も簡単な方法の1つです。ヘッダーを使用して、クリックジャッキング、XSS、およびその他の攻撃を防ぐことができます。これらのヘッダーをアプリケーションに追加する方法はいくつかあります。

ヘッダーのセキュリティを確認するための優れたウェブサイトは、securityheaders.comobservatory.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.phpindex.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);
// これは出力されます: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// もしあなたがビュークラスとして登録された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();

mapregisterなどのフレームワークのメソッドはオーバーライドできません。これを試みるとエラーが発生します。

Learn/routing

ルーティング

注: ルーティングについてもっと理解したいですか?より詳細な説明については、"なぜフレームワーク?"ページをチェックしてください。

Flightでの基本的なルーティングは、URLパターンをコールバック関数またはクラスとメソッドの配列と照合することによって行われます。

Flight::route('/', function(){
    echo 'こんにちは世界!';
});

ルートは定義された順序で一致します。リクエストに一致する最初のルートが呼び出されます。

コールバック/関数

コールバックは呼び出し可能な任意のオブジェクトである必要があります。つまり、通常の関数を使用することができます:

function hello() {
    echo 'こんにちは世界!';
}

Flight::route('/', 'hello');

クラス

クラスの静的メソッドを使用することもできます:

class Greeting {
    public static function hello() {
        echo 'こんにちは世界!';
    }
}

Flight::route('/', [ 'Greeting','hello' ]);

または、最初にオブジェクトを作成し、メソッドを呼び出すこともできます:


// Greeting.php
class Greeting
{
    public function __construct() {
        $this->name = 'ジョン・ドー';
    }

    public function hello() {
        echo "こんにちは、{$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(依存性注入コンテナ)による依存性注入

コンテナを介して依存性注入を使用したい場合(PSR-11、PHP-DI、Diceなど)、利用できるルートの種類は、オブジェクトを自分で直接作成し、コンテナを使用してオブジェクトを作成するか、呼び出すクラスとメソッドを文字列で定義する必要があります。詳細については、依存性注入ページをご覧ください。

以下は簡単な例です:


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 "こんにちは、世界!私の名前は{$name}です!";
    }
}

// index.php

// 必要なパラメータでコンテナを設定
// PSR-11に関する詳細情報は依存性注入ページを参照してください
$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 'GETリクエストを受け取りました。';
});

Flight::route('POST /', function () {
  echo 'POSTリクエストを受け取りました。';
});

// Flight::get()はルートには使用できません。これは変数を取得するためのメソッドであり、ルートを作成するものではありません。
// Flight::post('/', function() { /* コード */ });
// Flight::patch('/', function() { /* コード */ });
// Flight::put('/', function() { /* コード */ });
// Flight::delete('/', function() { /* コード */ });

複数のメソッドを単一のコールバックにマッピングすることもできます。|区切りを使用します:

Flight::route('GET|POST /', function () {
  echo 'GETまたはPOSTリクエストのいずれかを受け取りました。';
});

さらに、いくつかのヘルパーメソッドを使用するためのRouterオブジェクトを取得できます:


$router = Flight::router();

// すべてのメソッドをマッピングする
$router->map('/', function() {
    echo 'こんにちは世界!';
});

// GETリクエスト
$router->get('/users', function() {
    echo 'ユーザー';
});
// $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 "こんにちは、$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 "こんにちは、$name ($id)!";
});

次のURLでアクセスした場合:/bob/123、出力はこんにちは、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 () {
  // 何かをする
});

パススルー

コールバック関数から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 'ユーザー:'.$id; }, false, 'user_view');

// 後でコード内のどこかで
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5'を返します

これは、URLが変更される場合に特に便利です。上記の例では、ユーザーが/admin/users/@idに移動したとします。 エイリアスを使用しているため、エイリアスを参照する場所すべてを変更する必要はありません。エイリアスは現在/admin/users/5を返します。

ルートエイリアスはグループ内でも機能します:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'ユーザー:'.$id; }, false, 'user_view');
});

// 後でコード内のどこかで
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5'を返します

ルート情報

一致するルート情報を調査したい場合は、ルートメソッドの第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);

ルートグループ

関連するルートをグループ化したい場合があります(例えば/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に一致します
  });
});

リソースルーティング

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
    {
    }
}

注: 新しく追加されたルートは、php runway routesを実行してrunwayで表示できます。

リソースルートのカスタマイズ

リソースルートを構成するいくつかのオプションがあります。

エイリアスベース

aliasBaseを構成できます。デフォルトでは、エイリアスは指定されたURLの最後の部分です。 例えば、/users/usersaliasBaseになります。これらのルートが作成されると、エイリアスはusers.indexusers.createなどとなります。エイリアスを変更する場合は、aliasBaseを希望する値に設定してください。

Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);

除外とのみ

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) {

    // 明らかに、パスやその他のものをサニタイズする必要があります。
    $fileNameSafe = basename($filename);

    // ルートが実行された後にここに追加のヘッダーを設定する場合、
    // 出力される前にすべて定義する必要があります。
    // それらはすべてheader()関数への生の呼び出しか、
    // Flight::response()->setRealHeader()への呼び出しである必要があります。
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // または
    Flight::response()->setRealHeader('Content-Disposition', 'attachment; filename="'.$fileNameSafe.'"');

    $fileData = file_get_contents('/some/path/to/files/'.$fileNameSafe);

    // エラー処理とその他の処理
    if(empty($fileData)) {
        Flight::halt(404, 'ファイルが見つかりません');
    }

    // 必要に応じて手動でコンテンツ長を設定
    header('Content-Length: '.filesize($filename));

    // データをクライアントにストリーミング
    echo $fileData;

// これが魔法の行です
})->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 アプリケーションの作成とメンテナンスをスピードアップします。反復的なコーディングタスクを終了し、コードを制御する力を享受します。

フライトとの比較での長所

フライトとの比較での短所

Learn/flight_vs_another_framework

他のフレームワークとのFlightの比較

もし、Laravel、Slim、Fat-Free、あるいはSymfonyのような他のフレームワークからFlightに移行している場合、このページは両者の違いを理解するのに役立ちます。

Laravel

Laravelはベルと笛がすべて揃った充実したフレームワークで、驚くべき開発者中心のエコシステムを持っていますが、パフォーマンスと複雑さと引き換えになります。

LaravelとFlightの比較を見る.

Slim

SlimはFlightに似たマイクロフレームワークです。軽量で使いやすく設計されていますが、Flightよりも少し複雑になることがあります。

SlimとFlightの比較を見る.

Fat-Free

Fat-Freeはより小さなパッケージで提供されるフルスタックフレームワークです。ツールはすべてそろっていますが、いくつかのプロジェクトをより複雑にするデータアーキテクチャを持っています。

Fat-FreeとFlightの比較を見る.

Symfony

Symfonyはモジュール式のエンタープライズレベルのフレームワークであり、柔軟性と拡張性を備えています。より小さなプロジェクトや新人開発者にとって、Symfonyは少し抵抗があるかもしれません。

SymfonyとFlightの比較を見る.

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!」と表示されます。

ミドルウェアについて重要な注意事項がいくつかありますので、使用する前に認識しておく必要があります:

ミドルウェアクラス

ミドルウェアはクラスとしても登録できます。"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!」が表示されます。

ミドルウェアエラーの処理

認証ミドルウェアがあるとして、認証されていない場合にユーザーをログインページにリダイレクトしたいとします。その場合、次のオプションがいくつかあります:

  1. ミドルウェア関数からfalseを返すと、Flightは自動的に403 Forbiddenエラーを返しますが、カスタマイズはできません。
  2. Flight::redirect()を使用してユーザーをログインページにリダイレクトできます。
  3. ミドルウェア内でカスタムエラーを作成し、ルートの実行を停止できます。

基本的な例

次に、単純な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キーをチェックするために、多くのルートをグループ化する必要がある場合に便利です。


// グループメソッドの最後に追加
Flight::group('/api', function() {

    // この「空」に見えるルートは実際には/apiに一致します
    Flight::route('', function() { echo 'api'; }, false, 'api');
    // これは/api/usersに一致します
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // これは/api/users/1234に一致します
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

すべてのルートにグローバルミドルウェアを適用する場合は、次のようにして「空の」グループを追加できます:


// グループメソッドの最後に追加
Flight::group('', function() {

    // これは依然として/usersです
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // そしてこれは依然として/users/1234です
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ 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;
});

mapregister などのコアメソッドは、直接呼び出されて動的に呼び出されないため、フィルタリングすることはできません。

Learn/requests

リクエスト

FlightはHTTPリクエストを単一のオブジェクトにカプセル化し、 次のようにアクセスできます:

$request = Flight::request();

一般的な使用例

Webアプリケーションでリクエストを処理する際は、通常、ヘッダーを 取り出したり、$_GET$_POSTのパラメータを取得したり、あるいは 生のリクエストボディを取得したいと思うことでしょう。Flightはそれを 簡単に行うためのインターフェースを提供します。

クエリ文字列パラメータを取得する例は以下の通りです:

Flight::route('/search', function(){
    $keyword = Flight::request()->query['keyword'];
    echo "あなたが検索しているのは: $keyword";
    // $keywordを使ってデータベースにクエリするか、何か他のことをする
});

POSTメソッドのフォームの例はこちらです:

Flight::route('POST /submit', function(){
    $name = Flight::request()->data['name'];
    $email = Flight::request()->data['email'];
    echo "あなたが送信したのは: $name, $email";
    // $nameと$emailを使ってデータベースに保存するか、何か他のことをする
});

リクエストオブジェクトのプロパティ

リクエストオブジェクトは以下のプロパティを提供します:

querydatacookies、および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'];

ファイルアップロードの処理

フレームワークを使用してファイルアップロードを処理できます。基本的には リクエストからファイルデータを取り出し、それを新しい場所に移動することです。

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

getFullUrl()メソッドを使用して完全なリクエストURLにアクセスできます:

$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar

ベースURL

getBaseUrl()メソッドを使用してベースURLにアクセスできます:

$url = Flight::request()->getBaseUrl();
// 注意: トレーリングスラッシュはありません。
// https://example.com

クエリ解析

parseQuery()メソッドにURLを渡すことで、クエリ文字列を連想配列に解析できます:

$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レスポンスを送信します。

mapregisterで追加された任意のカスタムメソッドもフィルタリングできます。

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

フレームワークを使う理由

一部のプログラマーは、フレームワークの使用に熱烈に反対しています。フレームワークは膨大で、遅く、学習が困難だと主張しています。 彼らは、フレームワークは不要であり、それらなしでより良いコードを書くことができると述べています。 フレームワークを使用することのデメリットについては、いくつかの妥当なポイントがあります。 ただし、フレームワークを使用する利点もたくさんあります。

フレームワークを使用する理由

フレームワークを使用したいと思う理由のいくつかを以下に示します:

Flightはマイクロフレームワークです。 つまり、小さく軽量です。 LaravelやSymfonyのような大規模なフレームワークほどの機能は提供しません。 ただし、Webアプリケーションを構築するために必要な機能の多くを提供します。 また、学びやすく使用も容易です。 これにより、簡単かつ迅速にWebアプリケーションを構築するのに適しています。 フレームワークに新しい場合は、Flightは初心者に最適なフレームワークです。 フレームワークを使用する利点を学び、過度な複雑さで圧倒されることなく学習するのに役立ちます。 Flightの経験を積んだ後は、LaravelやSymfonyなどのより複雑なフレームワークに移ることがより簡単になります。 ただし、Flightでも成功した堅牢なアプリケーションを作成できます。

ルーティングとは?

ルーティングはFlightフレームワークの中核ですが、それは一体何でしょうか? ルーティングとは、URLを取得してコード内の特定の関数に一致させるプロセスです。 これにより、WebサイトをリクエストされたURLに基づいて異なる動作をさせることができます。 たとえば、ユーザーが/user/1234を訪れたときにユーザープロフィールを表示したいが、/usersを訪れたときに全ユーザーのリストを表示したいとします。 これはすべてルーティングを通じて行われます。

以下のようになります:

そして、なぜ重要なの?

適切な中央集権的なルーターを持つことで、あなたの生活が劇的に簡単になる可能性があります! 最初はそれが見えにくいかもしれません。 以下は、そのいくつか理由です:

多分、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() {
        // レスポンス() オブジェクトにここでコールバックを適用します。
        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

第二引数としてステータスコードを渡すこともできます:

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();

レスポンスデータのクリア

clear() メソッドを使用してレスポンスボディとヘッダーをクリアできます。これにより、レスポンスに割り当てられたヘッダーがクリアされ、レスポンスボディがクリアされ、ステータスコードが 200 に設定されます。

Flight::response()->clear();

レスポンスボディのみのクリア

レスポンスボディのみをクリアしたい場合は、clearBody() メソッドを使用できます:

// これにより、レスポンス() オブジェクトに設定されたヘッダーは保持されます。
Flight::response()->clearBody();

HTTP キャッシング

Flight には、HTTP レベルのキャッシングに対するビルトインのサポートがあります。キャッシング条件が満たされると、Flight は HTTP 304 Not Modified レスポンスを返します。クライアントが同じリソースを再度リクエストする際には、ローカルにキャッシュされたバージョンを使用するように促されます。

ルートレベルのキャッシング

レスポンス全体をキャッシュしたい場合は、cache() メソッドを使用し、キャッシュする時間を渡すことができます。


// これにより、レスポンスが5分間キャッシュされます
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo 'このコンテンツはキャッシュされます。';
});

// または、strtotime() メソッドに渡す文字列を使用できます
Flight::route('/news', function () {
  Flight::response()->cache('+5 minutes');
  echo 'このコンテンツはキャッシュされます。';
});

最終更新日時

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 レスポンスを送信し、処理を停止します。

ファイルのダウンロード (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のマッパブルメソッドの一部であり、必要に応じてその動作をオーバーライドできます。

このガイドでは、イベントの価値、使用方法、および初心者がその力を理解するのに役立つ実践的な例を含め、イベントの使用を開始するために必要なすべての情報をカバーします。

なぜイベントを使用するのか?

イベントは、アプリケーションの異なる部分を分離して、それぞれが過度に依存しないようにします。この分離は、しばしばデカップリングと呼ばれ、コードの更新、拡張、デバッグを容易にします。すべてを大きな塊で書く代わりに、特定のアクション(イベント)に応答する小さく独立したロジックに分割できます。

ブログアプリを構築していると想像してください:

イベントがないと、すべてを1つの関数に無理やり詰め込むことになります。イベントを使用すると、コメントを保存する部分、'comment.posted'のようなイベントをトリガーする部分、そしてメールやログを処理するリスナーを別々に設けることができます。これにより、コードがすっきりし、コアのロジックに触れずに機能(通知など)を追加または削除できます。

一般的な使用例

イベントリスナーの登録

イベントをリッスンするには、Flight::onEvent()を使用します。このメソッドは、イベントが発生したときに何が起こるべきかを定義することを可能にします。

構文

Flight::onEvent(string $event, callable $callback): void

仕組み

あなたは、イベントが発生したときにFlightに何をするかを知らせることで、「サブスクライブ」します。コールバックは、イベントトリガーから渡される引数を受け取ることができます。

Flightのイベントシステムは同期的であり、これは各イベントリスナーが順次実行され、1つずつ処理されることを意味します。イベントをトリガーすると、そのイベントのすべての登録されたリスナーが実行を完了するまで、あなたのコードは続行しません。これは非同期イベントシステムとは異なり、リスナーが並行して動作したり、後で実行される可能性があるため、理解することが重要です。

簡単な例

Flight::onEvent('user.login', function ($username) {
    echo "お帰りなさい、$username!";
});

ここでは、'user.login'イベントがトリガーされると、ユーザーの名前で挨拶します。

重要なポイント

イベントをトリガーする

イベントを発生させるには、Flight::triggerEvent()を使用します。これにより、Flightはそのイベントのために登録されたすべてのリスナーを実行し、提供されたデータを渡します。

構文

Flight::triggerEvent(string $event, ...$args): void

簡単な例

$username = 'alice';
Flight::triggerEvent('user.login', $username);

これは、'user.login'イベントをトリガーし、先に定義したリスナーに'alice'を送信します。その結果、お帰りなさい、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("新しいイベントリスナーが追加されました: $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 が " . date('Y-m-d H:i:s') . " にログインしました");
});

// ルートを定義
Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "ログインしました!";
});

Flight::start();

オプション 2: 別のevents.phpファイル

少し大きなアプリでは、app/config/events.phpのような専用ファイルにイベント登録を移動することを検討してください。このファイルをindex.php内のルートの前に含めます。これは、Flightプロジェクトでのルートがapp/config/routes.phpに整理されているのと類似しています。

// app/config/events.php
Flight::onEvent('user.login', function ($username) {
    error_log("$username が " . date('Y-m-d H:i:s') . " にログインしました");
});

Flight::onEvent('user.registered', function ($email, $name) {
    echo "$email宛にメールを送信しました: ようこそ、$nameさん!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';

Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "ログインしました!";
});

Flight::start();

オプション 3: トリガー近くに

別のアプローチは、トリガーされる場所の近くでイベントを登録することです。コントローラーやルート定義の中で行います。これは、イベントがアプリの特定の部分に特有である場合に効果的です。

Flight::route('/signup', function () {
    // ここでイベントを登録
    Flight::onEvent('user.registered', function ($email) {
        echo "$email宛にようこそメールが送信されました!";
    });

    $email = 'jane@example.com';
    Flight::triggerEvent('user.registered', $email);
    echo "サインアップしました!";
});

Flightのベストプラクティス

ヒント:目的別にグループ化

events.php内で、関連するイベント(例:すべてのユーザー関連イベントを一緒に)をコメントでグループ化します:

// app/config/events.php
// ユーザーイベント
Flight::onEvent('user.login', function ($username) {
    error_log("$username がログインしました");
});
Flight::onEvent('user.registered', function ($email) {
    echo "$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 が $time にログインしました");
});

// ステップ 2: アプリ内でトリガーする
Flight::route('/login', function () {
    $username = 'bob'; // これはフォームから来たと仮定
    Flight::triggerEvent('user.login', $username);
    echo "こんにちは、$username!";
});

なぜ役立つのか:ログインコードはロギングのことを考える必要がなく、ただイベントをトリガーします。後で、他のリスナー(例:ウェルカムメールを送信)を追加できるのです。

例 2: 新しいユーザーについて通知

// 新しい登録のリスナー
Flight::onEvent('user.registered', function ($email, $name) {
    // メールを送信するシミュレーション
    echo "$email宛にメールが送信されました: ようこそ、$nameさん!";
});

// 誰かがサインアップするときにトリガー
Flight::route('/signup', function () {
    $email = 'jane@example.com';
    $name = 'Jane';
    Flight::triggerEvent('user.registered', $email, $name);
    echo "サインアップありがとうございました!";
});

なぜ役立つのか:サインアップのロジックはユーザーの作成に集中し、イベントが通知を処理します。後で、他のリスナー(例:サインアップをログに記録)を追加できます。

例 3: キャッシュをクリアする

// キャッシュをクリアするリスナー
Flight::onEvent('page.updated', function ($pageId) {
    unset($_SESSION['pages'][$pageId]); // 該当する場合、セッションキャッシュをクリア
    echo "$pageIdのキャッシュがクリアされました。";
});

// ページが編集されるときにトリガー
Flight::route('/edit-page/(@id)', function ($pageId) {
    // ページを更新したと仮定
    Flight::triggerEvent('page.updated', $pageId);
    echo "$pageIdページが更新されました。";
});

なぜ役立つのか:編集コードはキャッシングのことを考える必要がなく、更新を信号として発信します。他のアプリの部分が必要に応じて反応できます。

ベストプラクティス

Flight::onEvent()Flight::triggerEvent()によるFlight PHPのイベントシステムは、柔軟なアプリケーションを構築するためのシンプルでありながら強力な方法を提供します。アプリの異なる部分がイベントを通じてお互いに話し合うことを可能にすることにより、コードを整理し、再利用可能で、拡張しやすくできます。アクションを記録したり、通知を送信したり、更新を管理したりする際に、ロジックを絡ませることなく行うのに役立ちます。さらに、これらのメソッドをオーバーライドできることで、ニーズに合わせてシステムをカスタマイズする自由があります。最初は単一のイベントから始め、その後アプリの構造がどのように変わっていくかを見てみてください。

ビルトインイベント

Flight PHPには、フレームワークのライフサイクルにフックするために使用できるいくつかのビルトインイベントがあります。これらのイベントは、リクエスト/レスポンスサイクルの特定のポイントでトリガーされ、特定のアクションが発生したときにカスタムロジックを実行することを可能にします。

ビルトインイベント一覧

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');

その後、ビューには headerContentbodyContent という名前の保存された変数があります。次に、次のようにしてレイアウトをレンダリングできます:

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');

これにより、headerContentbodyContent という名前の保存された変数を持つことができます。そして、次のようにしてレイアウトをレンダリングできます:

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

Flightと比較したCons

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';
});

また、beforeafterメソッドを使用して基本的な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();

ただし、mapregisterのようなフレームワークメソッドはオーバーライドできません。そうしようとするとエラーが発生します。

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と比較したメリット

Flightと比較したデメリット

Learn/autoloading

オートローディング

オートローディングは、PHPにおいてクラスを読み込むディレクトリを指定する概念です。これは、requireincludeを使用してクラスをロードするよりも有益です。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/blog

Flight PHPを使ったシンプルなブログの構築

このガイドでは、Flight PHPフレームワークを使用して基本的なブログを作成する方法を説明します。プロジェクトをセットアップし、ルートを定義し、JSONを使用して投稿を管理し、Latteテンプレーティングエンジンでレンダリングします。すべてがFlightのシンプルさと柔軟性を示しています。最後には、ホームページ、個別の投稿ページ、および作成フォームを持つ機能的なブログが完成します。

前提条件

ステップ1: プロジェクトのセットアップ

新しいプロジェクトディレクトリを作成し、Composerを介してFlightをインストールします。

  1. ディレクトリの作成:

    mkdir flight-blog
    cd flight-blog
  2. Flightのインストール:

    composer require flightphp/core
  3. パブリックディレクトリの作成: Flightは単一のエントリーポイント(index.php)を使用します。それ用にpublic/フォルダを作成します:

    mkdir public
  4. 基本的なindex.php: シンプルな「Hello World」ルートを持つpublic/index.phpを作成します:

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
       echo 'こんにちは、Flight!';
    });
    
    Flight::start();
  5. 組み込みサーバーの起動: PHPの開発サーバーを使用してセットアップをテストします:

    php -S localhost:8000 -t public/

    http://localhost:8000にアクセスして「こんにちは、Flight!」を見ることができます。

ステップ2: プロジェクト構造の整理

クリーンなセットアップのために、プロジェクトを以下のように構成します:

flight-blog/
├── app/
│   ├── config/
│   └── views/
├── data/
├── public/
│   └── index.php
├── vendor/
└── composer.json

ステップ3: Latteのインストールと設定

Latteは、Flightとよく統合される軽量なテンプレーティングエンジンです。

  1. Latteのインストール:

    composer require latte/latte
  2. 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();
  3. レイアウトテンプレートを作成する: 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>&copy; {date('Y')} Flightブログ</p>
    </footer>
    </body>
    </html>
  4. ホームテンプレートを作成: 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にアクセスしてレンダリングされたページを確認してください。

  5. データファイルを作成:

    簡単のためにデータベースのシミュレーションとしてJSONファイルを使用します。

    data/posts.jsonで:

    [
       {
           "slug": "first-post",
           "title": "私の最初の投稿",
           "content": "これはFlight PHPを使用した私の初めてのブログ投稿です!"
       }
    ]

ステップ4: ルートの定義

ルートを構成ファイルに分けることで、整理を良くしましょう。

  1. 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' => '投稿を作成']);
    });
  2. 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: ブログ投稿の保存と取得

投稿を読み込み、保存するメソッドを追加します。

  1. 投稿メソッドを追加: index.phpで、投稿を読み込むメソッドを追加します:

    Flight::map('posts', function () {
       $file = __DIR__ . '/../data/posts.json';
       return json_decode(file_get_contents($file), true);
    });
  2. ルートの更新: 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: テンプレートの作成

投稿を表示するためにテンプレートを更新します。

  1. 投稿ページ(app/views/post.latte:

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$post['title']}</h2>
        <div class="post-content">
            <p>{$post['content']}</p>
        </div>
    {/block}

ステップ7: 投稿作成の追加

新しい投稿を追加するためのフォーム送信を処理します。

  1. フォーム(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}
  2. 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('/');
    });
  3. テストする:

    • 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}

次のステップ

結論

Flight PHPを使ってシンプルなブログを構築しました! このガイドでは、ルーティング、Latteによるテンプレーティング、およびフォーム送信の処理などのコア機能を示しました。すべてを軽量に保ちながら実施しています。さらにブログを進化させるためにFlightのドキュメントを探求してください!

License

The MIT License (MIT)

Copyright © 2024 @mikecao, @n0nag0n

個人が複製の許可を得ることができるように、このソフトウェアおよび関連ドキュメントファイル(以下「ソフトウェア」という)のコピーを入手することができます。 ソフトウェアを使用、コピー、変更、マージ、公開、配布、サブライセンス、販売する権利などを含む、制限なしでソフトウェアを扱う権利が、以下の条件に従って人々にそれを許可します:

上記の著作権表示およびこの許諾表示は、ソフトウェアのすべての複製または実質的な部分に含まれている必要があります。

ソフトウェアは、「現状有姿」で提供され、商品性、特定目的への適合性、および権利侵害を含むがこれに限定されない、いかなる種類の保証もなしに提供されます。 著作者または著作権保持者は、ソフトウェアまたは使用または他の取引に起因する契約上の行為、不法行為、その他の行為から生じるクレーム、損害、その他の責任について一切責任を負いません。

About

Flightとは?

Flightは、PHP用の高速でシンプル、拡張可能なフレームワークです。非常に多用途で、あらゆる種類のウェブアプリケーションを構築するために使用できます。シンプルさを念頭に置いて構築されており、理解しやすく、使いやすい形式で記述されています。

Flightは、PHPを学び始めたばかりの初心者にとって素晴らしいフレームワークであり、ウェブアプリケーションの構築を学びたい方に最適です。また、ウェブアプリケーションに対してより多くの制御を求める経験豊富な開発者にも素晴らしいフレームワークです。RESTful API、シンプルなウェブアプリケーション、または複雑なウェブアプリケーションを簡単に構築できるように設計されています。

クイックスタート

まず、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フレームワークの一つです。すべてのベンチマークはTechEmpowerで確認できます。

以下は、他の人気のPHPフレームワークとのベンチマークです。

フレームワーク プレーン・テキストのリクエスト/sec JSONリクエスト/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にアクセスして、使い始める方法を確認してください!また、Flightでできることのインスピレーションを得られるのページも訪れてみてください。

コミュニティ

私たちはMatrixチャットで活動しています。

Matrix

そしてDiscordでも。

貢献

Flightに貢献する方法は2つあります:

  1. コアリポジトリを訪問して、コアフレームワークに貢献できます。
  2. ドキュメントに貢献できます。このドキュメントサイトは、Githubにホストされています。エラーに気付いたり、より良い内容を充実させたい場合は、遠慮なく修正してプルリクエストを送信してください!私たちは物事を把握しようとしていますが、更新や言語翻訳を歓迎します。

要件

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インファイルキャッシュクラス

利点

このドキュメントサイトは、このライブラリを使用して各ページをキャッシュしています!

コードを表示するにはこちらをクリックしてください。

インストール

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

主要な設定オプション:

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と使用できるキャッシュライブラリがいくつかあります。

デバッグ

開発を行うローカル環境ではデバッグが重要です。いくつかのプラグインを使用するとデバッグ体験を向上させることができます。

データベース

データベースはほとんどのアプリケーションの中心です。これによりデータの保存と取得が可能になります。一部のデータベースライブラリはクエリの記述や実行を簡素化するラッパーであり、一部は完全なORMです。

セッション

APIにはあまり役立たないが、Webアプリケーションの構築にはセッションが状態とログイン情報の維持に重要です。

テンプレーティング

テンプレートはUIを持つWebアプリケーションにとって重要です。Flightと使用できるいくつかのテンプレートエンジンがあります。

貢献

共有したいプラグインがありますか?リストに追加するためにプルリクエストを送信してください!

Awesome-plugins/ghost_session

Ghostff/Session

PHPセッションマネージャー(ノンブロッキング、フラッシュ、セグメント、セッション暗号化)。オプションの暗号化/復号化のためにPHPのopen_sslを使用します。ファイル、MySQL、Redis、Memcachedをサポートしています。

こちらをクリックしてコードを表示します。

インストール

コンポーザーでインストールします。

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();

// セッション設定ファイルへのカスタムパスを設定し、セッションIDにランダムな文字列を与えます
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        // または手動で設定オプションをオーバーライドすることもできます
        $session->updateConfiguration([
            // セッションデータをデータベースに保存したい場合(「すべてのデバイスからログアウトする」機能のように)
            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',             # PDO DNS用のデータベースドライバー(例:mysql:host=...;dbname=...)
                'host'      => '127.0.0.1',         # データベースホスト
                'db_name'   => 'my_app_database',   # データベース名
                'db_table'  => 'sessions',          # データベーステーブル
                'db_user'   => 'root',              # データベースユーザー名
                'db_pass'   => '',                  # データベースパスワード
                'persistent_conn'=> false,          # スクリプトがデータベースと通信するたびに新しい接続を確立するオーバーヘッドを避け、その結果、より高速なWebアプリケーションになります。自分で裏側を見つけてください
            ]
        ]);
    }
);

助けて!セッションデータが永続化されていません!

セッションデータを設定しているのに、それがリクエスト間で永続化されていないですか?セッションデータをコミットするのを忘れているかもしれません。セッションデータを設定した後に$session->commit()を呼び出すことでこれを行えます。

Flight::route('POST /login', function() {
    $session = Flight::session();

    // ここにログインロジックを実装します
    // パスワードを検証します等。

    // ログインが成功した場合
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // セッションに書き込むたびに、必ず意図的にコミットする必要があります。
    $session->commit();
});

これを回避するもう一つの方法は、セッションサービスを設定する際に、設定でauto_committrueに設定する必要があることです。これにより、各リクエストの後にセッションデータが自動的にコミットされます。


$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 スクリプト

スクリプトは、3 つのセットのスクリプトに分かれています。

スクリプトディレクトリは次のとおりです:

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

マルチ開発環境

複数の開発者や複数のブランチで作業する場合、次の番号を特定するのは難しいです。

その場合、バージョン番号の後にサフィックス "-dev" を付けます。

シナリオを見てみましょう:

どちらの場合も、開発者は 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 を使用してプロジェクトに統合する

基本的な使用法は

例えばを見る:

<?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_id' => $session->get('user_id')]);
    }
});

Flight::route('/logout', function() {
    $session = Flight::session();
    $session->clear(); // すべてのセッションデータをクリア
    Flight::json(['message' => '正常にログアウトしました']);
});

Flight::start();

重要なポイント

設定

セッションハンドラーを登録する際に、オプションの配列を渡すことでカスタマイズできます:

$app->register('session', Session::class, [
    'save_path' => '/custom/path/to/sessions',         // セッションファイルのディレクトリ
    'encryption_key' => 'a-secure-32-byte-key-here',   // 暗号化を有効にする(AES-256-CBCに推奨される32バイト)
    'auto_commit' => false,                            // 手動制御のため自動コミットを無効にする
    'start_session' => true,                           // 自動的にセッションを開始する(デフォルト: true)
    'test_mode' => false                               // 開発用にテストモードを有効にする
]);

設定オプション

オプション 説明 デフォルト値
save_path セッションファイルが保存されるディレクトリ sys_get_temp_dir() . '/flight_sessions'
encryption_key AES-256-CBC暗号化用のキー(オプション) null(暗号化なし)
auto_commit シャットダウン時にセッションデータを自動保存 true
start_session 自動的にセッションを開始 true
test_mode PHPセッションに影響を与えずにテストモードで実行 false
test_session_id テストモード用のカスタムセッションID(オプション) 設定されていない場合はランダムに生成

高度な使い方

手動コミット

自動コミットを無効にすると、変更を手動でコミットする必要があります:

$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を再生成します(例: ログイン後):

Flight::route('/post-login', function() {
    $session = Flight::session();
    $session->regenerate(); // 新しいID、データを保持
    // または
    $session->regenerate(true); // 新しいID、古いデータを削除
});

ミドルウェアの例

セッションベースの認証でルートを保護します:

Flight::route('/admin', function() {
    Flight::json(['message' => '管理パネルへようこそ']);
})->addMiddleware(function() {
    $session = Flight::session();
    if (!$session->get('is_admin')) {
        Flight::halt(403, 'アクセス拒否');
    }
});

これはミドルウェアでの使い方の簡単な例です。詳細な例については、ミドルウェアのドキュメントを参照してください。

メソッド

Sessionクラスは以下のメソッドを提供します:

get()id()を除くすべてのメソッドは、チェーンのためにSessionインスタンスを返します。

このプラグインを使用する理由

技術的詳細

貢献

貢献は歓迎します!リポジトリをフォークし、変更を加えてプルリクエストを送信してください。バグを報告するか、Githubのイシュートラッカーを通じて機能を提案してください。

ライセンス

このプラグインはMITライセンスの下でライセンスされています。詳細については、Githubリポジトリを参照してください。

Awesome-plugins/runway

ランウェイ

ランウェイはCLIアプリケーションで、Flightアプリケーションの管理を支援します。コントローラを生成したり、すべてのルートを表示したりすることができます。優れたadhocore/php-cliライブラリに基づいています。

こちらをクリックして、コードを表示してください。

インストール

Composerを使用してインストールしてください。

composer require flightphp/runway

基本設定

ランウェイを実行する最初の回は、セットアッププロセスを進め、プロジェクトのルートに.runway.json構成ファイルを作成します。このファイルには、ランウェイが正しく動作するために必要ないくつかの構成が含まれています。

使用法

ランウェイには、Flightアプリケーションを管理するために使用できる複数のコマンドがあります。ランウェイを使用する方法は2つあります。

  1. スケルトンプロジェクトを使用している場合、プロジェクトのルートから php runway [command] を実行できます。
  2. 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 テーブルがある場合:idnameemailcreated_atupdated_atapp/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 Panel Extensions

これはFlightを使いやすくするための拡張機能セットです。

これはパネルです

Flight Bar

それぞれのパネルはアプリケーションについて非常に役立つ情報を表示します!

Flight Data Flight Database Flight Request

ここをクリックしてコードを表示します。

インストール

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接続と同じパラメーターを持っています
$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 コンストラクターの第二パラメーターで session_data キーで渡します。


use Ghostff\Session\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 コンストラクターの第二パラメーターで latte キーでLatteインスタンスを渡すことができます。



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('latte', Engine::class, [], function($latte) {
    $latte->setTempDirectory(__DIR__ . '/temp');

    // これでLatte Panelを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/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);
}

便利なヒント

コードのデバッグ中に、データを出力するための非常に役立つ関数がいくつかあります。

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">
            &copy; 著作権
        </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ドキュメンテーションを生成するためのツールがいくつか用意されています。

認証/認可

認証と認可は、誰が何にアクセスできるかを管理するために必要なアプリケーションにとって重要です。

キャッシング

キャッシングはアプリケーションを高速化するための優れた方法です。Flightと一緒に使用できるキャッシングライブラリがいくつかあります。

CLI

CLIアプリケーションは、アプリケーションと対話するための素晴らしい方法です。コントローラを生成したり、すべてのルートを表示したりするために使用できます。

クッキー

クッキーはクライアント側に小さなデータを保存するための優れた方法です。ユーザーの設定やアプリケーションの設定などを保存するために使用できます。

デバッグ

デバッグは、ローカル環境で開発しているときに重要です。デバッグ体験を向上させるためのプラグインがいくつかあります。

データベース

データベースはほとんどのアプリケーションの中心です。これはデータを保存し、取得する方法です。いくつかのデータベースライブラリは、クエリを書くためのラッパーに過ぎないものもあれば、完全なORMであるものもあります。

暗号化

暗号化は、機密データを保存するアプリケーションにとって重要です。データを暗号化および復号化することはそれほど難しくありませんが、暗号化キーを適切に保存することは難しいことがあります。最も重要なのは、暗号化キーを公開ディレクトリに保存したり、コードリポジトリにコミットしたりしないことです。

ジョブキュー

ジョブキューは、非同期にタスクを処理するのに非常に便利です。これには、メールの送信、画像の処理、リアルタイムで行う必要がないその他の作業が含まれます。

セッション

セッションはAPIにはあまり便利ではありませんが、Webアプリケーションを構築するためには、状態とログイン情報を維持するために重要です。

テンプレーティング

テンプレーティングは、UIを持つWebアプリケーションの中心です。Flightと一緒に使用できるテンプレーティングエンジンがいくつかあります。

貢献

共有したいプラグインがありますか?リストに追加するためにプルリクエストを送信してください!

Media

メディア

私たちは、Flightに関するインターネット上のさまざまなメディアの種類を追跡しようとしました。Flightについてもっと学ぶために使用できるさまざまなリソースについては、以下をご覧ください。

記事とレビュー

ビデオとチュートリアル

Examples

迅速なスタートが必要ですか?

新しいFlightプロジェクトを始めるためのオプションは2つあります:

コミュニティが提供した例:

インスピレーションが必要ですか?

これらはFlightチームによって公式にスポンサーされているわけではありませんが、Flightを使って構築された自分のプロジェクトを構成する方法についてのアイデアを得ることができます!

自分の例を共有したいですか?

共有したいプロジェクトがある場合は、このリストに追加するためのプルリクエストを送信してください!

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を効果的に使用する方法を示すために、完全なプロジェクトを通じて案内します。

公式ガイド

ブログの構築

Flight PHPを使用して機能的なブログアプリケーションを作成する方法を学びます。このガイドでは以下の内容を説明します:

このチュートリアルは、すべての要素がどのようにリアルなアプリケーションに組み合わさるのかを見ることを望む初心者に最適です。

非公式ガイド

これらのガイドはFlightチームによって公式に維持されているわけではありませんが、コミュニティによって作成された貴重なリソースです。さまざまなトピックとユースケースをカバーし、Flight PHPの使用に関する追加の洞察を提供します。

Flight Frameworkを使用したRESTful APIの作成

このガイドでは、Flight PHPフレームワークを使用してRESTful APIを作成する方法を説明します。APIの設定、ルートの定義、JSONレスポンスを返す基本についてカバーします。

シンプルなブログの構築

このガイドでは、Flight PHPフレームワークを使用して基本的なブログを作成する方法を説明します。実際には2部構成で、基本についてのガイドと、プロダクションに対応したブログのためのより高度なトピックと改良についてのガイドがあります。

PHPでのポケモンAPIの構築: 初心者向けガイド

この楽しいガイドでは、Flight PHPを使用してシンプルなポケモンAPIを作成する方法を説明します。APIの設定、ルートの定義、JSONレスポンスを返す基本についてカバーします。

貢献

ガイドのアイデアがありますか?間違いを見つけましたか?貢献を歓迎します!私たちのガイドは、FlightPHPドキュメントリポジトリで維持されています。

Flightを使用して興味深いものを作成し、それをガイドとして共有したい場合は、プルリクエストを提出してください。知識を共有することで、Flightコミュニティの成長を助けます。

APIドキュメントをお探しですか?

Flightのコア機能やメソッドについての具体的な情報をお探しなら、私たちのドキュメントの学習セクションをチェックしてください。