Learn

フライトについて学ぶ

Flightは、PHP向けの高速でシンプルで拡張性のあるフレームワークです。非常に汎用性が高く、どんな種類のWebアプリケーションの構築にも使用できます。シンプルさを重視して構築されており、理解しやすく使用しやすい方法で書かれています。

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

なぜフレームワークを利用するのか?

フレームワークを使用すべき理由についての短い記事です。フレームワークの利点を理解することは、それを使用し始める前に知っておくと良いアイデアです。

また、@lubianaによって素晴らしいチュートリアルが作成されています。特にFlightについては詳しく説明していませんが、このガイドはフレームワークやそれらを使用するメジャーコンセプトを理解するのに役立ちます。チュートリアルはこちらで見つけることができます。

コアトピック

オートローディング

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

ルーティング

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

ミドルウェア

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

リクエスト

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

レスポンス

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

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/migrating_to_v3

v3への移行

後方互換性は大部分維持されていますが、v2からv3に移行する際に注意すべき変更点がいくつかあります。

出力バッファリングの動作(3.5.0)

出力バッファリング は、PHPスクリプトによって生成された出力がクライアントに送信される前に PHP 内部のバッファに格納されるプロセスです。これにより、出力をクライアントに送信する前に出力を変更することができます。

MVC アプリケーションでは、コントローラが「マネージャー」として機能し、ビューの動作を管理します。コントローラの外部で出力を生成する(またはFlightsの場合、時には無名関数で)と、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 は、よりオブジェクト指向に変換され、DI コンテナをより簡単に使用できるようになりました。Dispatcherのようにメソッドを呼び出す必要がある場合は、$result = $class->$method(...$params);call_user_func_array() のようなものを手動で使用することができます。

Learn/configuration

設定

特定の動作をカスタマイズするには、set メソッドを使用して Flight の設定値を設定できます。

Flight::set('flight.log_errors', true);

利用可能な設定

以下は利用可能なすべての設定のリストです:

変数

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

セキュリティ

Webアプリケーションに関するセキュリティは重要です。アプリケーションが安全であり、ユーザーのデータが安全であることを確認したいと思います。Flight は、Webアプリケーションを保護するための機能をいくつか提供しています。

ヘッダー

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

セキュリティヘッダーを確認するための2つの優れたウェブサイトは、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');

// リファラーポリシーを設定して、送信されるリファラー情報の量を制御
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// HTTPS を強制するために Strict-Transport-Security ヘッダーを設定
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// 使用できる機能やAPIを制御するために Permissions-Policy ヘッダーを設定
Flight::response()->header('Permissions-Policy', 'geolocation=()');

これらは bootstrap.php または index.php ファイルの先頭に追加できます。

フィルターとして追加

次のようなフィルター/フックでこれらを追加することもできます:

// フィルター内でヘッダーを追加
Flight::before('start', function() {
    Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
    Flight::response()->header("Content-Security-Policy", "default-src 'self'");
    Flight::response()->header('X-XSS-Protection', '1; mode=block');
    Flight::response()->header('X-Content-Type-Options', 'nosniff');
    Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
    Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
    Flight::response()->header('Permissions-Policy', 'geolocation=()');
});

ミドルウェアとして追加

これらをミドルウェアクラスとして追加することもできます。これはコードを清潔で整理された状態に保つ方法です。

// app/middleware/SecurityHeadersMiddleware.php

namespace app\middleware;

class SecurityHeadersMiddleware
{
    public function before(array $params): void
    {
        Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
        Flight::response()->header("Content-Security-Policy", "default-src 'self'");
        Flight::response()->header('X-XSS-Protection', '1; mode=block');
        Flight::response()->header('X-Content-Type-Options', 'nosniff');
        Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
        Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        Flight::response()->header('Permissions-Policy', 'geolocation=()');
    }
}

// index.php またはルートがある場所
// この空の文字列グループは、すべてのルートのためのグローバルミドルウェアとして機能します。
// もちろん、同じようにして特定のルートにのみ追加することもできます。
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // 各種ルート
}, [ new SecurityHeadersMiddleware() ]);

クロスサイトリクエストフォージェリ(CSRF)

クロスサイトリクエストフォージェリ(CSRF)は、悪意のあるWebサイトがユーザーのブラウザに対してあなたのWebサイトにリクエストを送信させる攻撃のタイプです。これにより、ユーザーの知識を持たずにWebサイト上でアクションを実行することができます。Flight は組込みの CSRF 保護機構を提供していませんが、ミドルウェアを使用して簡単に独自の保護を実装できます。

設定

まず、CSRF トークンを生成してユーザーのセッションに保存する必要があります。フォームでこのトークンを使用し、フォームが送信されたときに確認できます。

// CSRF トークンを生成してユーザーのセッションに保存
// (Flight が作成されたセッションオブジェクトを想定しています)
// セッションごとに単一のトークンを生成する必要があります(同じユーザーの複数のタブとリクエストで機能するためです)
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 を使用する

Latte テンプレートで CSRF トークンを出力するカスタム関数を設定できます。

// 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 リクエストかどうかをチェックし、POST リクエストの場合は CSRF トークンが有効かどうかをチェックします
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // フォーム値から csrf トークンを取得
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Invalid CSRF token');
        }
    }
});

またはミドルウェアクラスを使用することもできます:

// 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, 'Invalid CSRF token');
            }
        }
    }
}

// index.php またはルートがある場所
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // その他のルート
}, [ new CsrfMiddleware() ]);

クロスサイトスクリプティング(XSS)

クロスサイトスクリプティング(XSS)は、悪意のあるWebサイトがコードをあなたのWebサイトに注入できる攻撃のタイプです。これらの機会のほとんどは、エンドユーザーが入力するフォーム値から来ます。ユーザーからの出力を決して信頼しないでください!常に彼ら全員が世界で最も優れたハッカーであると仮定してください。彼らはあなたのページに悪意のある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 またはルートがある場所## 結論

セキュリティは重要で、Webアプリケーションを安全にすることが重要です。Flight は、Webアプリケーションを保護するための機能をいくつか提供していますが、常に用心深く、ユーザーのデータを安全に保つためにできる限りのことを行っていることを確認することが重要です。常に最悪の状況を想定し、ユーザーからの入力を信頼しないでください。出力をエスケープし、SQLインジェクションを防ぐためにプリペアドステートメントを使用してください。CSRF および CORS 攻撃からルートを保護するためにミドルウェアを常に使用してください。これらすべてを行うことで、安全なWebアプリケーションの構築への道のりは確実になります。

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

ルーティング

Note: ルーティングについてさらに理解したい場合は、"なぜフレームワークか?" ページをチェックして詳細な説明をご覧ください。

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

DIC(Dependency Injection Container)を介した依存性の注入

依存性の注入をコンテナ(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');
// or
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() { /* コード */ });

| デリミタを使用して、1つのコールバックに複数のメソッドをマップすることもできます:

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 には一致しません
});

Note: 名前付きパラメータと一致する正規表現グループ () はサポートされていません。 :'(

オプションパラメータ

マッチングがオプションであることを明示するために、セグメントをカッコで括り、名前付きパラメータを指定できます。

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 !== "太郎") {
    // 次のルートに続行
    return true;
  }
});

Flight::route('/user/*', function () {
  // これが呼び出されます
});

ルートのエイリアス

ルートにエイリアスを割り当てて、後でコード内で動的にURLを生成できるようにすることができます(例: テンプレートなど)。

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

// あとでコードのある場所で
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5' を返します

URLが変更される可能性がある場合に特に便利です。上記の例では、ユーザーが /admin/users/@id に移動した場合でも、 エイリアスを参照している場所を変更する必要がないため、エイリアスは便利です。

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

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'user:'.$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;

  // サイトのパスを表示します....本当に必要があれば
  $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 に一致します
  });
});

ストリーミング

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```markdown
# ルーティング

> **注意:** ルーティングについて詳しく知りたい場合は、より詳細な説明のために["フレームワークの必要性は?"](/learn/why-frameworks)ページをご覧ください。

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

```php
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' ]);

DIC(Dependency Injection Container)を介した依存関係の注入

コンテナ(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');
// or
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() { /* コード */ });

| デリミタを使用して、1つのコールバックに複数のメソッドをマップすることもできます:

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 には一致しません
});

注意: 名前付きパラメータと一致する正規表現グループ ()はサポートされていません。 :'(

オプションのパラメータ

オプションでマッチする名前付きパラメータを指定できます。 セグメントを括弧で囲むことでオプションのパラメータを指定します。

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 'user:'.$id; }, false, 'user_view');

// 後でコードのある場所で
Flight::getUrl('user_view', [ 'id' => 5 ]); // '/users/5' を返します

URLが変更される可能性がある場合に特に便利です。上記の例では、ユーザーが /admin/users/@id に移動した場合でも、 エイリアスを参照している場所を変更する必要がないため、エイリアスは便利です。

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

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'user:'.$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;

  // サイトのパスを表示します....本当に必要があれば
  $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 オブジェクト内の Router 変数を使用して、group メソッドを使用することもできます:

$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 に一致します
  });
});

ストリーミング

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

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 '私がここにいます!'; })->addMiddleware(function() {
    echo '最初にミドルウェア!';
});

Flight::start();

// これにより "最初にミドルウェア!私がここにいます!" が出力されます。

ミドルウェアについて知っておくべきいくつかの重要な注意事項があります:

ミドルウェアクラス

ミドルウェアをクラスとして登録することもできます。"after" 機能が必要な場合は、必ずクラスを使用する必要があります。

class MyMiddleware {
    public function before($params) {
        echo '最初にミドルウェア!';
    }

    public function after($params) {
        echo '最後にミドルウェア!';
    }
}

$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo '私がここにいます!'; })->addMiddleware($MyMiddleware); // または ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// これにより "最初にミドルウェア!私がここにいます!最後にミドルウェア!" が表示されます。

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

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

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

基本的な例

ここに単純な return false; の例があります:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            return false;
        }

        // true の場合、すべてが続行されます
    }
}

リダイレクト例

ユーザーをログインページにリダイレクトする例は次の通りです:

class MyMiddleware {
    public function before($params) {
        if (isset($_SESSION['user']) === false) {
            Flight::redirect('/login');
            exit;
        }
    }
}

カスタムエラーの例

APIを構築しているため、JSONエラーをスローする必要があるとします。以下のように行うことができます:

class MyMiddleware {
    public function before($params) {
        $authorization = Flight::request()->headers['Authorization'];
        if(empty($authorization)) {
            Flight::json(['error' => 'このページにアクセスするにはログインする必要があります。'], 403);
            exit;
            // または
            Flight::halt(403, json_encode(['error' => 'このページにアクセスするにはログインする必要があります。']);
        }
    }
}

ミドルウェアのグループ化

ルートグループを追加し、そのグループ内のすべてのルートに同じミドルウェアが適用されます。これは、ヘッダー内のAPIキーをチェックするような場合にルートをグループ化する必要がある場合に便利です。


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

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

すべてのルートにグローバルミドルウェアを適用したい場合は、"empty" グループを追加できます:


// グループメソッドの最後に追加
Flight::group('', function() {
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    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();

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

querydatacookiesfiles プロパティには、配列やオブジェクトとしてアクセスできます。

そのため、クエリ文字列パラメータを取得するには、次のようにします:

$id = Flight::request()->query['id'];

または、次のようにもできます:

$id = Flight::request()->query->id;

RAW リクエストボディ

例えば PUT リクエストを処理する場合など、生の HTTP リクエストボディを取得するには、次のようにします:

$body = Flight::request()->getBody();

JSON 入力

タイプが application/json でデータが {"id": 123} としてリクエストを送信した場合、それは data プロパティから利用できます:

$id = Flight::request()->data->id;

$_SERVER へのアクセス

getVar() メソッドを介して $_SERVER 配列にアクセスするショートカットが利用可能です:

$host = Flight::request()->getVar['HTTP_HOST'];

リクエストヘッダへのアクセス

getHeader() または getHeaders() メソッドを使用してリクエストヘッダにアクセスできます:

// たとえば、Authorization ヘッダが必要な場合
$host = Flight::request()->getHeader('Authorization');

// すべてのヘッダを取得する必要がある場合
$headers = Flight::request()->getHeaders();

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(string $key, mixed $value) // 変数を設定します。
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::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/why_frameworks

フレームワークの理由

プログラマーの中には、フレームワークの使用に強く反対する人もいます。フレームワークは過剰で遅く、学びにくいと主張する人もいます。彼らは、フレームワークは不要であり、それらなしでより優れたコードを書くことができると言います。フレームワークの利点については確かにいくつかの有効なポイントがあります。ただし、フレームワークを使用する利点も多く存在します。

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

次に、フレームワークを使用する理由のいくつかを示します:

Flightはマイクロフレームワークです。これは小さく軽量であることを意味します。LaravelやSymfonyのような大規模なフレームワークほど多くの機能を提供していません。しかし、ウェブアプリケーションを構築するために必要な機能の多くを提供しています。また、学習や使用が簡単です。これは、ウェブアプリケーションを迅速かつ簡単に構築するための適切な選択肢です。フレームワークに初めて取り組む場合、Flightは初心者向けの素晴らしいフレームワークです。これにより、フレームワークの利点を学びながら、複雑すぎることなく十分な経験を積むことができます。Flightの経験があると、後にLaravelやSymfonyなどのより複雑なフレームワークに移行しやすくなりますが、Flightでも成功した堅牢なアプリケーションを作成できます。

ルーティングとは?

ルーティングはFlightフレームワークの中心ですが、それは具体的に何でしょうか?ルーティングとは、URLを取り、そのURLに基づいて特定の関数と一致させるプロセスです。これにより、ユーザーがリクエストしたURLに基づいてウェブサイトに異なる作業をさせることができます。例えば、ユーザーが/user/1234を訪れたときにはユーザーのプロフィールを表示したいかもしれませんし、/usersを訪れた場合には全ユーザーのリストを表示したいかもしれません。これはすべてルーティングを介して行われます。

以下のように機能するかもしれません:

なぜ重要なのか?

適切な中央集約型のルーターを持つと、実際にはあなたの生活を著しく楽にすることができます!最初はそれが見えにくいかもしれませんが、次の理由があります:

おそらく、ウェブサイトを作成するときのスクリプト単位の方法についてはご存知かもしれません。たとえば、index.phpというファイルがあり、URLを確認して特定の関数を実行するためのifステートメントがいくつか含まれているかもしれません。これはルーティングの形態ですが、整理されておらず、すぐに手に負えなくなる可能性があります。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' ]);

// Maybe in your app/controllers/UserController.php
class UserController {
    public function viewUserProfile($id) {
        // do something
    }

    public function editUserProfile($id) {
        // do something
    }
}

中央集約型のルーティングシステムを使用するメリットが徐々に見えてくるといいですね。結局、将来的に何を返すかを制御しますが、管理しやすく、理解しやすいです!

リクエストとレスポンス

Flightはリクエストとレスポンスを処理する簡単かつコンパクトな方法を提供します。これがウェブフレームワークの中心部分です。ユーザーのブラウザからのリクエストを受け取り、処理し、応答を返すことにより、ユーザーのプロフィールを表示したり、ユーザーのログインを許可したり、ユーザーが新しいブログ投稿を行ったりするようなウェブアプリケーションを構築できます。

リクエスト

リクエストは、ユーザーのブラウザがあなたのウェブサイトを訪れたときにサーバーに送信するものです。このリクエストには、ユーザーが行いたいことに関する情報が含まれます。たとえば、ユーザーが訪れたい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() を使用しています。つまり、echoprint を使ってユーザーにレスポンスを送信し、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

レスポンス本体でコールバックを実行する

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); };」、または関数名 '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) {
            // This is a 
            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');
    echo "Hello, World!";
});

JSON

Flight は JSON と JSONP レスポンスを送信するためのサポートを提供しています。JSON レスポンスを送信するには、JSON にエンコードするデータを渡します:

Flight::json(['id' => 123]);

ステータスコード付きの JSON

2 番目の引数としてステータスコードを渡すこともできます:

Flight::json(['id' => 123], 201);

綺麗な出力の JSON

最後の引数に引数を渡すことで、綺麗なプリントを有効にできます:

Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);

Flight::json() に渡すオプションを変更し、よりシンプルな構文を使用したい場合は、JSON メソッドを再マップするだけです:

Flight::map('json', function($data, $code = 200, $options = 0) {
    Flight::_json($data, $code, true, 'utf-8', $options);
}

// そして、このように使用できます
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON と実行の停止

JSON レスポンスを送信して実行を停止したい場合は、jsonHalt メソッドを使用します。 これは、認証のチェックをしている場合など、ユーザーが認証されていない場合はすぐに JSON レスポンスを送信し、既存の本文コンテンツをクリアし、実行を停止するために便利です。

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // ユーザーが認証されているか確認
    if($authorized === false) {
        Flight::jsonHalt(['error' => 'Unauthorized'], 401);
    }

    // 残りのルートを続行
});

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

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 'このコンテンツはキャッシュされます。';
});

Last-Modified

lastModified メソッドを使用して、ページの最終更新日時を設定できます。クライアントは、最終更新日時の値が変更されるまで、キャッシュを継続します。

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/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/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

ビュー

Flight はデフォルトでいくつかの基本的なテンプレート機能を提供します。

より複雑なテンプレートが必要な場合は、カスタムビュー セクションの Smarty および Latte の例を参照してください。

ビューテンプレートを表示するには、 render メソッドを呼び出して、テンプレートファイルの名前とオプションのテンプレートデータを指定します。

Flight::render('hello.php', ['name' => 'Bob']);

渡すテンプレートデータは自動的にテンプレートに挿入され、ローカル変数のように参照することができます。テンプレートファイルは単純に PHP ファイルです。 hello.php テンプレートファイルの内容が次のような場合:

Hello, <?= $name ?>!

出力は次のようになります:

Hello, Bob!

set メソッドを使用してビュー変数を手動で設定することもできます:

Flight::view()->set('name', 'Bob');

変数 name はこれ以降すべてのビューで使用可能になります。したがって、次のように単に行うことができます:

Flight::render('hello');

render メソッドの中でテンプレートの名前を指定する際に、.php 拡張子を省略することができることに注意してください。

Flight はデフォルトでテンプレートファイルのために views ディレクトリを探します。テンプレートの代替パスを設定するには、次の設定を行います:

Flight::set('flight.views.path', '/path/to/views');

レイアウト

ウェブサイトでは、入れ替わるコンテンツを持つ単一のレイアウトテンプレートファイルを持つことが一般的です。 レイアウトに使用されるコンテンツをレンダリングするには、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 テンプレートエンジンをビューに使用します:

// 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 のデフォルトのレンダリングメソッドもオーバーライドする必要があります:

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 の素晴らしい点の1つは、テンプレートを変更すると自動的にキャッシュを更新することです!
    $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);
});

Learn/extending

拡張

Flightは拡張可能なフレームワークとして設計されています。フレームワークにはデフォルトのメソッドとコンポーネントのセットが付属していますが、独自のメソッドをマップしたり、独自のクラスを登録したり、既存のクラスやメソッドをオーバーライドしたりすることができます。

DIC(依存性注入コンテナ)をお探しの方は、依存性注入コンテナ ページをご覧ください。

メソッドのマッピング

独自のシンプルなカスタムメソッドをマップするには、map 関数を使用します:

// メソッドをマップ
Flight::map('hello', function (string $name) {
  echo "hello $name!";
});

// カスタムメソッドを呼び出す
Flight::hello('Bob');

これは、メソッドに変数を渡して予期される値を取得する必要がある場合により使用されます。下記のように register() メソッドを使用することは、構成を渡してから事前に構成されたクラスを呼び出すためのより多くのものです。

クラスの登録

独自のクラスを登録し構成するには、register 関数を使用します:

// クラスを登録
Flight::register('user', User::class);

// クラスのインスタンスを取得
$user = Flight::user();

登録メソッドでは、クラスのコンストラクタにパラメータを渡すこともできます。したがって、独自のクラスをロードするときには、事前に初期化されます。追加の配列を渡すことでコンストラクタパラメータを定義できます。データベース接続をロードする例を示します:

// コンストラクタパラメータを持つクラスを登録
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();
  }
}

追加のコールバックパラメータを渡すと、クラスの構築後に直ちに実行されます。これにより、新しいオブジェクトのセットアップ手順を実行できます。コールバック関数は、新しいオブジェクトのインスタンスを一つのパラメータとして受け取ります。

// 作成されたオブジェクトが渡されます
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が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/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
 */

// 名前空間が必要ありません

// すべてのオートロードされるクラスはパスカルケースであることが推奨されます (各単語が大文字で、スペースはなし)
// 3.7.2 以降、お使いのクラス名に対して Pascal_Snake_Case を使用するには Loader::setV2ClassLoading(false); を実行できます
class MyController {

    public function index() {
        // 何かを行う
    }
}

名前空間

もし名前空間を持っている場合、実装が非常に簡単になります。アプリケーションのルートディレクトリ (ドキュメントルートや public/ フォルダではない) を指定するために Flight::path() メソッドを使用するべきです。


/**
 * public/index.php
 */

// オートローダーへのパスを追加
Flight::path(__DIR__.'/../');

これはあなたのコントローラがどのように見えるかです。以下の例を見てくださいが、重要な情報を知るためにコメントに注意してください。

/**
 * app/controllers/MyController.php
 */

// 名前空間は必須です
// 名前空間はディレクトリ構造と同じです
// 名前空間はディレクトリ構造と同じケースを守る必要があります
// 名前空間とディレクトリにはアンダースコアを含めることはできません (Loader::setV2ClassLoading(false) が設定されていない限り)
namespace app\controllers;

// すべてのオートロードされるクラスはパスカルケースであることが推奨されます (各単語が大文字で、スペースはなし)
// 3.7.2 以降、お使いのクラス名に対して Pascal_Snake_Case を使用するには Loader::setV2ClassLoading(false); を実行できます
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セクションで文書化されています。

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

License

The MIT License (MIT)
=====================

著作権 © `2023` `@mikecao, @n0nag0n`

許可がここに与えられます。このソフトウェアおよび関連する文書のコピーを入手する個人は、料金なしで、制限なしでソフトウェアを取り扱う権利を有しています。
これには、使用、コピー、変更、統合、公開、配布、サブライセンス、販売の権利が含まれ、ソフトウェアのコピーを保持する人にソフトウェアを提供することが含まれます。 ただし、以下の条件に従う限りです。

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

本ソフトウェアは、明示的または黙示の保証を問わず、「そのまま」提供されており、商品性、特定目的への適合性、および権利の侵害に関する保証を含め、いかなる種類の保証もありません。 作者または著作権所有者は、契約行為、不法行為、その他の行為による、ソフトウェアの使用または他の取引に起因する、主張、損害、またはその他の責任について、一切責任を負いません。

About

フライトとは何ですか?

フライトはPHP向けの高速でシンプルで拡張性のあるフレームワークです。非常に汎用性があり、あらゆる種類のWebアプリケーションの構築に使用できます。シンプリシティを念頭に置いて構築され、理解しやすく使用しやすいように記述されています。

フライトは、PHPに新しく取り組み、Webアプリケーションの構築方法を学びたい初心者向けの優れたフレームワークです。また、Webアプリケーションについてより多くの制御を求める経験豊富な開発者にとっても優れたフレームワークです。RESTful API、シンプルなWebアプリケーション、または複雑なWebアプリケーションを簡単に構築できるように設計されています。

クイックスタート

<?php

// if installed with composer
require 'vendor/autoload.php';
// or if installed manually by zip file
// require 'flight/Flight.php';

Flight::route('/', function() {
  echo 'hello world!';
});

Flight::route('/json', function() {
  Flight::json(['hello' => 'world']);
});

Flight::start();

十分シンプルですよね?ドキュメントでフライトについて詳しく学んでください!

スケルトン/ボイラープレートアプリ

フライトフレームワークを使って始めるのに役立つ例のアプリがあります。始め方については、flightphp/skeletonにアクセスしてください!また、Flightでできることのいくつかのインスピレーションに関する資料があるexamplesページもご覧いただけます。

コミュニティ

私たちはMatrix上にいます!私たちとチャットするには、#flight-php-framework:matrix.orgにアクセスしてください。

貢献

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

Wruczek/PHP-File-Cache

軽量でシンプルでスタンドアロンのPHPインファイルキャッシュクラス

利点

インストール

Composerを使用してインストール:

composer require wruczek/php-file-cache

使用法

使用法はかなり簡単です。

use Wruczek\PhpFileCache\PhpFileCache;

$app = Flight::app();

// キャッシュが保存されるディレクトリをコンストラクタに渡します
$app->register('cache', PhpFileCache::class, [ __DIR__ . '/../cache/' ], function(PhpFileCache $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/Wruczek/PHP-File-Cache をご覧ください。また、examples フォルダを確認してください。

Awesome-plugins/index

すばらしいプラグイン

Flightは非常に拡張可能です。Flightアプリケーションに機能を追加するために使用できるプラグインがいくつかあります。いくつかはFlightチームによって公式にサポートされており、他には開始を手助けするためのミクロ/ライトライブラリがあります。

キャッシュ

キャッシュはアプリケーションの高速化に役立つ方法です。Flightと使用できるキャッシュライブラリがいくつかあります。

デバッグ

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

データベース

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

セッション

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

テンプレーティング

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

貢献

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

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/session

Ghostff/Session

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

インストール

Composer を使用してインストールします。

composer require ghostff/session

基本的な設定

セッションを使用するためにデフォルト設定を渡す必要はありません。詳細な設定については Github Readme を参照してください。


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

// 各ページロードでセッションをコミットする必要があることを覚えておく必要があります
// または構成で auto_commit を実行する必要があります。

シンプルな例

これはこのように使用する可能性のあるシンプルな例です。

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

    // ここでログインロジックを実行します
    // パスワードの検証など

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

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

// このチェックは、制限されたページロジック内にあるか、ミドルウェアでラップされているかもしれません。
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // 制限されたページロジックをここで実行します
});

// ミドルウェアバージョン
Flight::route('/some-restricted-page', function() {
    // 通常のページロジック
})->addMiddleware(function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }
});

より複雑な例

これはこのように使用する可能性のあるより複雑な例です。


use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// カスタムパスをセッション構成ファイルに設定し、セッション 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, // 必要であればこれを実行するか、session を 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/runway

滑走路

滑走路は、Flightアプリケーションを管理するのに役立つCLIアプリケーションです。コントローラを生成したり、すべてのルートを表示したりすることができます。これは、優れた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_atのスキーマを持っている場合、app/records/UserRecord.phpファイルに次のようなファイルが作成されます:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * usersテーブルのアクティブレコードクラス。
 * @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
 */
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

# その他

Runwayのカスタマイズ

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 コンストラクタの第2パラメータで 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 パネルを使用できます。Latte インスタンスを TracyExtensionLoader コンストラクタの第2パラメータで latte キーを使用して渡すことができます。



use Latte\Engine;

require 'vendor/autoload.php';

$app = Flight::app();

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

    // ここで Latte パネルを Tracy に追加します
    $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
});

if(Debugger::$showBar === true) {
    // これは false でなければならず、そうでないと Tracy が実際にレンダリングできません :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

Awesome-plugins/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

飛行アクティブレコード

アクティブレコードはデータベースエンティティをPHPオブジェクトにマッピングするものです。単純に言うと、データベースにusersテーブルがある場合、そのテーブルの行をUserクラスと$userオブジェクトに「変換」できます。基本例を参照してください。

基本例

以下のテーブルがあると仮定しましょう:

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 PHPフレームワーク

Flight PHPフレームワークを使用している場合、ActiveRecordクラスをサービスとして登録できます(しかし、必ずしも必要ではありません)。

Flight::register('user', 'User', [ $pdo_connection ]);

// コントローラー、関数などでこれを使用できます。

Flight::user()->find(1);

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

// この時点では「dirty」データはありません。

$user->email = 'test@example.com'; // これにより「email」が「dirty」に変更された
$user->update();
// `dirty`に保持されているデータが更新、適用されました。

$user->password = password_hash()'newpassword'); // これが 「dirty」になります
$user->dirty(); // 何も渡さないと、すべての「dirty」エントリがクリアされます。
$user->update(); // 「dirty」にキャプチャされたデータがないため、更新されません。

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 「name」と「password」の両方が更新されました。

copyFrom(array $data): ActiveRecord(v0.4.0)

これはdirty()メソッドのエイリアスです。何を行っているかを明確に示しています。

$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // 「name」と「password」が更新されました。

isDirty(): boolean(v0.4.0)

現在のレコードが変更された場合はtrueを返します。

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

現在のレコードを初期状態にリセットします。ループ型の動作で使用するのに非常に便利です。trueを渡すと、現在のオブジェクトを検索するために使用されたクエリデータもリセットされます(デフォルトの動作)。

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // 詳細な初期状態に戻す
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string(v0.4.1)

find()findAll()insert()update()、またはsave()メソッドを実行した後、構築されたSQLを取得してデバッグ目的に使用できます。

SQLクエリメソッド

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インジェクション攻撃に対して脆弱です。多くのオンライン記事がありますので、Googleで「sql injection attacks php」と検索してください。このライブラリを使ってこれを行う適切な方法は、$user->eq('id', $id)->eq('name', $name)->find();のように、このwhere()メソッドではなく、より適切な方法で行うことです。絶対にこれを行う必要がある場合は、PDOライブラリが$pdo->quote($var)を使用してエスケープする方法があることを覚えていてください。quote()を使用した後でないと、where()ステートメントで使用できません。

group(string $group_by_statement)/groupBy(string $group_by_statement)

特定の条件で結果をグループ化します。

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

返されるクエリを特定の方法で並べ替えます。

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

返されるレコードの数を制限します。2番目のintが与えられると、オフセット、リミットがSQLと同じようになります。

$user->orderby('name DESC')->limit(0, 10)->findAll();

WHERE条件

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)の関係を使用して、テーブル間でいくつかの種類の関係を設定できます。テーブル間の1対多および1対1の関係を設定できます。これには、事前にクラスで多少の追加のセットアップが必要です。

$relations配列を設定することは難しくありませんが、正しい構文を推測することが複雑になる場合があります。

protected array $relations = [
    // キーは何でもよいですが、ActiveRecordの名前がよいでしょう。例: user, contact, client
    'user' => [
        // 必須
        // self::HAS_MANY、self::HAS_ONE、self::BELONGS_TO
        self::HAS_ONE, // これが関係のタイプです

        // 必須
        'Some_Class', // これが参照する「他の」ActiveRecordクラスです

        // 必須
        // 関係の種類に応じて
        // self::HAS_ONE = 結合を参照する外部キー
        // self::HAS_MANY = 結合を参照する外部キー
        // self::BELONGS_TO = 結合を参照するローカルキー
        'local_or_foreign_key',
        // FYI、これは他のモデルの主キーにも結合します

        // オプション
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' => 5 ], // リレーションを結合する際に追加の条件が必要な場合
        // $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))

        // オプション
        'back_reference_name' // これは、このリレーションを自分自身に戻す場合に使用します。例: $user->contact->user;
    ];
]
class User extends ActiveRecord{
    protected array $relations = [
        'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
        'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
    ];

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }
}

class Contact extends ActiveRecord{
    protected array $relations = [
        'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
        'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
    ];
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'contacts');
    }
}

これでセットアップが整ったので、非常に簡単に使用できます!

$user = new User($pdo_connection);

// 最新のユーザーを見つけます。
$user->notNull('id')->orderBy('id desc')->find();

// 関係を使用してお問い合わせを取得する:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// または逆に移動できます。
$contact = new Contact();

// 1つのコンタクトを見つける
$contact->find();

// 関係を使用してユーザーを取得する:
echo $contact->user->name; // これはユーザー名です

かなりすごいですね?

カスタムデータの設定

カスタムな計算など、ActiveRecordに独自の情報を添付する必要がある場合があります。これは、テンプレートに渡すためにオブジェクトに簡単に添付できるかもしれません。

setCustomData(string $field, mixed $value)

setCustomData()メソッドを使用してカスタムデータを添付します。

$user->setCustomData('page_view_count', $page_view_count);

その後、通常のオブジェクトのプロパティとして参照します。

echo $user->page_view_count;

イベント

このライブラリのさらに素晴らしい機能の1つは、イベントについてです。イベントは、特定のメソッドを呼び出したときに特定の時間にトリガーされます。一定のデータを自動的に設定するのに非常に便利です。

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)

これは、通常、レコードが取得されるたびに実行する必要がある場合に役立ちます。何かを復号化する必要がありますか?カスタムのcountクエリを頻繁に実行する必要がありますか(効率的ではありませんが、それまで)。

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFind(self $self) {
        // 何かを復号化する
        $self->secret = yourDecryptFunction($self->secret, $some_key);

        // 何か特別なことを記憶するかもしれませんか??
        $self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']; 
    } 
}

beforeFindAll(ActiveRecord $ActiveRecord)

おそらく、各回ごとにクエリの操作が必要な場合があります。

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeFindAll(self $self) {
        // 常にid >= 0が実行されます
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

afterFind()に似ていますが、すべてのレコードにその処理を適用できます。

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterFindAll(array $results) {

        foreach($results as $self) {
            // afterFindのようなかっこいいことを行います
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

各回ごとにデフォルトの値を設定するのに非常に役立ちます。

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // いくつかの素晴らしいデフォルト値を設定します
        if(!$self->created_date) {
            $self->created_date = gmdate('Y-m-d');
        }

        if(!$self->password) {
            $self->password = password_hash((string) microtime(true));
        }
    } 
}

afterInsert(ActiveRecord $ActiveRecord)

挿入後にデータを変更するためのケースがありますか?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // あなたはあなた自身についてです
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // または何か他のこと....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

各回ごとにデフォルトの値を設定するのに非常に役立ちます。

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // いくつかの素晴らしいデフォルトを設定します
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

更新後にデータを変更するためのケースがありますか?

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function afterInsert(self $self) {
        // あなたはあなた自身についてです
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // または何か他のこと....
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

これは、挿入または更新が発生したときの両方のイベントが必要な場合に役立ちます。説明を省略しますが、何が起こるかはおそらく予想できます。

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeSave(self $self) {
        $self->last_updated = gmdate('Y-m-d H:i:s');
    } 
}

beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)

ここで何をしたいかよくわかりませんが、何が起こるかはあなた次第です!やってください!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo 'He was a brave soldier... :cry-face:';
    } 
}

データベース接続の管理

このライブラリを使用する際、データベース接続を数種類の方法で設定できます。コンストラクタで接続を設定するか、設定変数$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);

データベース接続をリフレッシュする必要がある場合、たとえば長時間実行されるCLIスクリプトを実行している場合は、$your_record->setDatabaseConnection($pdo_connection)で接続を再設定できます。

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->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ファイルの「内部」であることがラテに伝えられます -->
{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' => 'ホームページ'
        ]);
    }
}

ラテを最大限に活用する方法の詳細については、ラテのドキュメントを参照してください!

Awesome-plugins/awesome_plugins

すばらしいプラグイン

Flightは非常に拡張性があります。Flightアプリケーションに機能を追加するために使用できるプラグインがいくつかあります。一部はFlightチームによって公式にサポートされており、他にはマイクロ/ライトなライブラリがあります。

キャッシュ

キャッシュはアプリケーションの高速化に役立ちます。Flightと使用できる多くのキャッシュライブラリがあります。

CLI

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

Cookies

Cookieはクライアント側に小さなデータを保存する素晴らしい方法です。ユーザーの設定、アプリケーションの設定などを保存するために使用できます。

デバッグ

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

データベース

データベースはほとんどのアプリケーションの中心です。これはデータの保存と取得方法です。一部のデータベースライブラリはクエリを書くためのラッパーであり、一部は完全なORMです。

暗号化

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

セッション

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

テンプレート

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

貢献

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

Examples

マルチ言語対応

Flightの始め方を知りたいですか?

Flightで始めるための2つのオプションがあります:

インスピレーションを得るには?

これらはFlightチームによって公式にスポンサーされていませんが、Flightで構築されたプロジェクトの構造についてアイデアを提供することができます!

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

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