PhalconPHP:高負荷のRESTfulAPIのソリューション

公開: 2022-03-11

PHPMVCフレームワークに基づいて高負荷のプロジェクトを作成する必要があるとします。 おそらく、可能な限りキャッシュを使用します。 プロジェクトを単一のファイルでビルドしたり、最小限の機能で独自のMVCフレームワークを作成したり、別のフレームワークの一部を書き直したりする場合もあります。 はい、これは機能しますが、少し注意が必要ですよね。 幸い、これらの操作のほとんどを不要にするソリューションがもう1つあり(おそらくキャッシュ用に保存します)、このソリューションはPhalconPHPフレームワークと呼ばれます。

PhalconPHPとは何ですか?

PhalconPHPは、Cで記述されコンパイルされたPHP拡張機能として提供されるPHP用のMVCフレームワークです。 これが、利用可能な最速のフレームワークの1つです(正直に言うと、最速のフレームワークはYafですが、マイクロフレームワークであり、Phalconよりもはるかに機能が制限されています)。 PhalconPHPは、PHPファイルを使用した長い操作を必要とせず、リクエストごとに解釈する必要もありません。Webサーバーの起動時に一度RAMにロードされ、リソースをほとんど消費しません。

MVCフレームワークは、長い間Web開発のベストプラクティスと見なされてきました。これは一種の専門的な標準であるため、ほとんどのWeb開発者はPHP用の少なくとも1つのMVCフレームワーク(Symfony、Yii、Laravel、CodeIgniter、Zend)に精通しています。フレームワークなど。それぞれに長所と短所がありますが、共通点は何ですか。 それらはすべてPHPで記述されており、コードが実行されるたびに、要求ごとにインタープリターによって実行される必要がある大量のロジックを含む多くの含まれるPHPファイルで構成されています。 これは優れた透明性をもたらしますが、私たちはパフォーマンスで支払います。 大量のコードと大量のインクルードファイルは、特にPHPでは(コンパイルではなく解釈されるため)大量のメモリと時間を消費します。 はい、PHP 7の状況ははるかに良くなっていますが、まだ改善すべき点がたくさんあり、PhalconPHPはそれらの改善をテーブルにもたらします。

いくつかのベンチマークを見てみましょう。

PhalconPHPベンチマーク

公式のベンチマークは5年前のものであり、現在有効にするには古すぎますが、それでもPhalconPHPの特徴を劇的に確認できます。 新しいものを見てみましょう。 2016年の比較では、Phalconはトップ5にランクインしています。これは、プロフェッショナルフレームワークの中で明らかなリーダーであり、生のPHPと一部のマイクロフレームワークのみを認めています。

PhalconPHPベンチマーク

だから、ファルコンは速いです。 生のPHPも高速ですが、MVCフレームワークが提供する必要のあるすべての機能が必要であり、Phalconは次のようなコンポーネントを含めて課題に直面します。

  • ORM
  • ボルトテンプレートエンジン
  • 依存性注入(DI)コンテナ
  • キャッシング
  • ロギング
  • ルーティングシステム
  • セキュリティブロック
  • オートローダー
  • フォームモジュール

それはほんの一例です。 簡単に言うと、PhalconPHPには、高負荷システム用のRESTfulAPIなどの大規模なエンタープライズアプリケーションを構築するために必要なすべてのものが揃っています。

Phalconのもう1つの優れた点は、その小さなスタイルです。PhalconORMと巨大なDoctrine2を比較してください。

PhalconPHPプロジェクトの作成を見てみましょう。

2種類のPhalconプロジェクト:フルスタックとマイクロ

一般に、MVCフレームワークには2つのタイプがあります。フルスタックフレームワーク(Symfony、Yiiなど)とマイクロフレームワーク(Lumen、Slim、Silexなど)です。

フルスタックフレームワークは、より多くの機能を提供するため、大規模なプロジェクトに適していますが、実行するにはもう少し資格と時間が必要です。

マイクロフレームワークを使用すると、軽量のプロトタイプを非常に迅速に作成できますが、機能が不足しているため、大規模なプロジェクトでは使用しない方が一般的です。 ただし、マイクロフレームワークの利点の1つは、そのパフォーマンスです。 これらは通常、フルスタックのものよりもはるかに高速です(たとえば、Yafフレームワークは生のPHPよりもパフォーマンスが劣ります)。

PhalconPHPは、次の両方をサポートしています。フルスタックまたはマイクロアプリケーションのいずれかを作成できます。 さらに良いことに、PhalconPHPでプロジェクトをマイクロアプリケーションとして開発する場合でも、Phalconの強力な機能のほとんどにアクセスでき、そのパフォーマンスはフルスタックアプリケーションよりも高速のままです。

過去の仕事で、私のチームは高負荷のRESTfulシステムを構築する必要がありました。 私たちが行ったことの1つは、PhalconのフルスタックアプリケーションとPhalconマイクロアプリケーションのプロトタイプのパフォーマンスを比較することでした。 PhalconPHPのマイクロアプリケーションははるかに高速である傾向があることがわかりました。 NDAの理由によりベンチマークを表示することはできませんが、私の意見では、Phalconのパフォーマンスを最大限に活用したい場合は、マイクロアプリケーションを使用してください。 フルスタックアプリケーションよりもマイクロアプリケーションをコーディングする方が便利ではありませんが、PhalconPHPのマイクロアプリケーションには、プロジェクトとパフォーマンスの向上に必要なすべてのものが備わっています。 説明のために、Phalconで非常に単純なRESTfulマイクロアプリケーションを作成してみましょう。

RESTfulAPIの構築

RESTfulアプリケーションのほとんどすべての順列には、 Userエンティティという共通点が1つあります。 したがって、このサンプルプロジェクトでは、ユーザーを作成、読み取り、更新、および削除するための小さなRESTアプリケーション(CRUDとも呼ばれます)を作成します。

このプロジェクトが完全に完了したのは、私のGitLabリポジトリで確認できます。 このプロジェクトを2つの部分に分割することにしたため、2つのブランチがあります。最初のブランチであるmasterには、特定のPhalconPHP機能を含まない基本的な機能のみが含まれ、2番目のブランチであるlogging-and-cacheには、Phalconのロギングおよびキャッシュ機能が含まれます。 それらを比較して、Phalconでそのような関数を実装するのがいかに簡単かを確認できます。

インストール

インストールについては説明しません。任意のデータベース、任意のオペレーティングシステム、および任意のWebサーバーを使用できます。 公式のインストールドキュメントに詳しく説明されているので、オペレーティングシステムに応じた手順に従ってください。

Webサーバーのインストールに関する注意事項は、Phalconの公式ドキュメントでも入手できます。

PHPのバージョンは5.6以上である必要があることに注意してください。

私はUbuntu16.10、PostgreSQL 9.5.6、Nginx 1.10.0、PHP 7、およびPhalcon3.0を使用しています。 プロジェクトにNginx構成サンプルとPostgreSQLダンプファイルを含めたので、それらを自由に使用してください。 別の構成が必要な場合は、変更するのは難しくありません。

プロジェクトの構造と構成

まず、初期プロジェクト構造を作成します。

Phalconでは任意の構造を使用できますが、この演習で選択した構造は、MVCパターンを部分的に実装しています。 これはRESTfulプロジェクトであるためビューはありませんが、コントローラーとモデルがあり、それぞれに独自のフォルダーとサービスがあります。 サービスは、プロジェクトのビジネスロジックを実装するクラスであり、MVCの「モデル」部分をデータモデル(データベースと通信する)とビジネスロジックモデルの2つに分割します。

publicフォルダーにあるindex.phpは、必要なすべてのパーツと構成をロードするブートストラップファイルです。 すべての構成ファイルがconfigフォルダーに配置されていることに注意してください。 これらをブートストラップファイルに入れることもできますが(これは公式ドキュメントに示されている方法です)、私の意見では、これは大きなプロジェクトでは読み取れなくなるため、最初からフォルダーを分離することをお勧めします。

index.phpの作成

index.phpでの最初のパスは、構成クラスと自動ロードクラスをロードしてから、ルート、依存性注入コンテナー、およびPhalconPHPマイクロアプリケーションを初期化します。 次に、そのマイクロアプリケーションコアに制御を渡します。マイクロアプリケーションコアは、ルートに従って要求を処理し、ビジネスロジックを実行して、結果を返します。

コードを見てみましょう:

 <?php try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( function () use ($app) { // Returning a successful response } ); // Processing request $app->handle(); } catch (\Exception $e) { // Returning an error response }

\Phalcon\Configオブジェクトの構成

Phalconに構成ファイルを保存する方法はいくつかあります。

  • YAMLファイル
  • JSONファイル
  • INIファイル
  • PHP配列

構成をPHP配列に格納するのが最速のオプションであり、高負荷のアプリケーションを作成していて、パフォーマンスを低下させる必要がないため、これを実行します。 具体的には、 \Phalcon\Configオブジェクトを使用して、構成オプションをプロジェクトにロードします。 非常に短い構成オブジェクトがあります。

 <?php return new \Phalcon\Config( [ 'database' => [ 'adapter' => 'Postgresql', 'host' => 'localhost', 'port' => 5432, 'username' => 'postgres', 'password' => '12345', 'dbname' => 'articledemo', ], 'application' => [ 'controllersDir' => "app/controllers/", 'modelsDir' => "app/models/", 'baseUri' => "/", ], ] );

このファイルには、データベース用とアプリケーション用の2つの基本構成が含まれています。 明らかに、データベース構成はデータベースへの接続に使用されます。 applicationアレイについては、Phalconのシステムツールで使用されるため、後で必要になります。 Phalconの構成について詳しくは、公式ドキュメントをご覧ください。

loader.php構成

次の構成ファイルloader.phpを見てみましょう。 loader.phpファイルは、 \Phalcon\Loaderオブジェクトを介して名前空間を対応するディレクトリに登録します。 それはさらに簡単です:

 <?php $loader = new \Phalcon\Loader(); $loader->registerNamespaces( [ 'App\Services' => realpath(__DIR__ . '/../services/'), 'App\Controllers' => realpath(__DIR__ . '/../controllers/'), 'App\Models' => realpath(__DIR__ . '/../models/'), ] ); $loader->register();

これで、これらの名前空間のすべてのクラスが自動的に読み込まれ、使用できるようになります。 新しい名前空間とディレクトリを追加する場合は、このファイルに1行追加するだけです。 特定のディレクトリまたは特定のファイルを登録することで、名前空間の使用を回避することもできます。 これらの可能性はすべて、PhalconPHPローダーのドキュメントに記載されています。

依存性注入コンテナの構成

他の多くの現代的なフレームワークと同様に、Phalconは依存性注入(DI)パターンを実装しています。 オブジェクトはDIコンテナで初期化され、そこからアクセスできます。 同様に、DIコンテナはアプリケーションオブジェクトに接続されており、コントローラやサービスなど、 \Phalcon\DI\Injectableクラスから継承するすべてのクラスからアクセスできます。

PhalconのDIパターンは非常に強力です。 このコンポーネントはこのフレームワークで最も重要なものの1つであると考えており、その動作を理解するためにドキュメント全体を読むことを強くお勧めします。 これは、Phalconの多くの機能の鍵を提供します。

それらのいくつかを見てみましょう。 di.phpファイルは次のようになります。

 <?php use Phalcon\Db\Adapter\Pdo\Postgresql; // Initializing a DI Container $di = new \Phalcon\DI\FactoryDefault(); /** * Overriding Response-object to set the Content-type header globally */ $di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } ); /** Common config */ $di->setShared('config', $config); /** Database */ $di->set( "db", function () use ($config) { return new Postgresql( [ "host" => $config->database->host, "username" => $config->database->username, "password" => $config->database->password, "dbname" => $config->database->dbname, ] ); } ); return $di;

ご覧のとおり、依存性注入(DI)ファイルはもう少し複雑であり、注意が必要な詳細がいくつかあります。 まず、初期化文字列について考えます。 $di = new \Phalcon\DI\FactoryDefault();\Phalcon\Diを継承するFactoryDefaultオブジェクトを作成します(Phalconを使用すると、必要なDIファクトリを作成できます)。 ドキュメントによると、 FactoryDefaultは「フレームワークによって提供されるすべてのサービスを自動的に登録します。 このおかげで、開発者はフルスタックフレームワークを提供する各サービスを個別に登録する必要がありません。」 これは、 RequestResponseなどの一般的なサービスがフレームワーククラス内でアクセスできることを意味します。 このようなサービスの完全なリストは、Phalconサービスのドキュメントで確認できます。

次に重要なのは設定プロセスです。DIコンテナに何かを登録する方法はいくつかあり、それらはすべてPhalconPHP登録ドキュメントで完全に説明されています。 ただし、このプロジェクトでは、無名関数、変数、文字列の3つの方法を使用します。

匿名関数を使用すると、クラスの初期化中に多くのことができます。 具体的には、このプロジェクトでは、最初にResponseオブジェクトをオーバーライドして、プロジェクトのすべての応答に対してcontent-typeJSONとして設定し、次に構成オブジェクトを使用してデータベースアダプターを初期化します。

前述したように、このプロジェクトはPostgreSQLを使用しています。 別のデータベースエンジンを使用する場合は、 db関数でデータベースアダプタを変更するだけです。 利用可能なデータベースアダプタとデータベースレイヤーの詳細については、PhalconPHPのデータベースドキュメントを参照してください。

3つ目の注意点は、 \Phalcon\Configサービスを実装する$config変数を登録することです。 サンプルプロジェクトでは実際には使用されていませんが、最も一般的に使用されるサービスの1つであるため、ここに含めることにしました。 他のプロジェクトでは、ほとんどすべての場所で構成にアクセスする必要がある場合があります。

ここで最後に興味深いのは、実際のsetSharedメソッド自体です。 これを呼び出すと、サービスが「共有」されます。つまり、シングルトンのように動作し始めます。 ドキュメントによると、「サービスが初めて解決されると、コンシューマーがコンテナーからサービスを取得するたびに、同じインスタンスが返されます。」

ルートの設定routes.php …またはそうではない

最後に含まれるファイルはroutes.phpです。 とりあえず空のままにしておきましょう。コントローラーと一緒に塗りつぶします。

RESTfulコアの実装

WebプロジェクトをRESTfulにする理由は何ですか? Wikipediaによると、RESTfulアプリケーションには3つの主要な部分があります。-ベースURL-状態遷移データ要素を定義するインターネットメディアタイプ-標準HTTPメソッド( GETPOSTPUTDELETE )および標準HTTP応答コード(200、 403、400、500など)。

私たちのプロジェクトでは、ベースURLをroutes.phpファイルに配置し、その他のポイントについて説明します。

リクエストデータをapplication/x-www-form-urlencodedとして受信し、レスポンスデータをapplication/jsonとして送信します。 実際のアプリケーションでx-www-form-urlencodedを使用するのは良い考えではないと思いますが(複雑なデータ構造と連想配列をx-www-form-urlencodedで送信するのに苦労するため)、私は簡単にするために、この標準を実装することにしました。

覚えているかと思いますが、DIファイルに応答JSONヘッダーをすでに設定しています。

 $di->setShared( 'response', function () { $response = new \Phalcon\Http\Response(); $response->setContentType('application/json', 'utf-8'); return $response; } );

次に、応答コードと応答形式を設定する必要があります。 公式チュートリアルでは、すべてのメソッドでJSON応答を作成することを提案していますが、それは良い考えではないと思います。 コントローラメソッドの結果を配列として返し、それらを標準のJSON応答に変換する方がはるかに一般的です。 プロジェクト内の1か所でHTTP応答コードを作成することも賢明です。 index.phpファイルでこれを行います。

これを行うには、 $app->before() )メソッドと$app->after()メソッドを使用して、リクエスト処理の前後にコードを実行するPhalconの機能を利用します。 目的のために、 $app->after()メソッドにコールバックを配置します。

 // Making the correct answer after executing $app->after( function () use ($app) { // Getting the return value of method $return = $app->getReturnedValue(); if (is_array($return)) { // Transforming arrays to JSON $app->response->setContent(json_encode($return)); } elseif (!strlen($return)) { // Successful response without any content $app->response->setStatusCode('204', 'No Content'); } else { // Unexpected response throw new Exception('Bad Response'); } // Sending response to the client $app->response->send(); }

ここでは、戻り値を取得し、配列をJSONに変換します。 すべてがOKであるが、戻り値が空の場合(たとえば、新しいユーザーを正常に追加した場合)、204 HTTPコードを指定し、コンテンツを送信しません。 他のすべての場合、例外をスローします。

例外の処理

RESTfulアプリケーションの最も重要な側面の1つは、正確で有益な応答です。 高負荷のアプリケーションは通常大きく、検証エラー、アクセスエラー、接続エラー、予期しないエラーなど、さまざまなタイプのエラーが発生する可能性があります。これらのエラーをすべて統合HTTP応答コードに変換したいと考えています。 それは例外の助けを借りて簡単に行うことができます。

私のプロジェクトでは、2種類の例外を使用することにしました。「ローカル」例外があります。 \RuntimeExceptionクラスから継承され、サービス、モデル、アダプターなどで区切られた特別なクラスです(このような分割は各レベルの処理に役立ちます)別のモデルとしてのMVCモデルの)-次に、 AbstractHttpExceptionクラスから継承されたHttpExceptionsがあります。 これらの例外はHTTP応答コードと一致しているため、それらの名前はHttp400ExceptionHttp500Exceptionなどです。

AbstractHttpExceptionクラスには、 httpCodehttpMessage 、およびappErrorの3つのプロパティがあります。 最初の2つのプロパティは継承者でオーバーライドされ、 httpCode: 400httpMessage: Bad request Badrequestなどの基本的な応答情報が含まれています。 appErrorプロパティは、エラーの説明を含む詳細なエラー情報の配列です。

index.phpの最終バージョンは、3種類の例外をキャッチします。上記のように、 AbstractHttpExceptions 。 Phalconリクエストの例外。リクエストの解析中に発生する可能性があります。 および他のすべての予期しない例外。 それらはすべてき​​れいなJSON形式に変換され、標準のPhalconResponseクラスを介してクライアントに送信されます。

 <?php use App\Controllers\AbstractHttpException; try { // Loading Configs $config = require(__DIR__ . '/../app/config/config.php'); // Autoloading classes require __DIR__ . '/../app/config/loader.php'; // Initializing DI container /** @var \Phalcon\DI\FactoryDefault $di */ $di = require __DIR__ . '/../app/config/di.php'; // Initializing application $app = new \Phalcon\Mvc\Micro(); // Setting DI container $app->setDI($di); // Setting up routing require __DIR__ . '/../app/config/routes.php'; // Making the correct answer after executing $app->after( // After Code ); // Processing request $app->handle(); } catch (AbstractHttpException $e) { $response = $app->response; $response->setStatusCode($e->getCode(), $e->getMessage()); $response->setJsonContent($e->getAppError()); $response->send(); } catch (\Phalcon\Http\Request\Exception $e) { $app->response->setStatusCode(400, 'Bad request') ->setJsonContent([ AbstractHttpException::KEY_CODE => 400, AbstractHttpException::KEY_MESSAGE => 'Bad request' ]) ->send(); } catch (\Exception $e) { // Standard error format $result = [ AbstractHttpException::KEY_CODE => 500, AbstractHttpException::KEY_MESSAGE => 'Some error occurred on the server.' ]; // Sending error response $app->response->setStatusCode(500, 'Internal Server Error') ->setJsonContent($result) ->send(); }

Phalcon開発ツールを使用したモデルの作成

最新のIDEを使用している場合は、おそらくハイライトと補完のコーディングに慣れているでしょう。 同様に、一般的なPHPフレームワークでは、フレームワークを含むフォルダーを含めて、ワンクリックで関数宣言に移動できます。 Phalconは拡張機能であるため、このオプションは自動的に取得されません。 幸い、Composerを介してインストールできる「PhalconDev Tools」と呼ばれるこのギャップを埋めるツールがあります(それが何であるかまだわからない場合は、この素晴らしいパッケージマネージャーについて知る時が来ました)。 Phalcon Dev Toolsは、Phalconのすべてのクラスと関数のコードスタブで構成され、PhalconPHPWebサイトに記載されているコンソールバージョンとGUIバージョンの両方を備えたコードジェネレーターを提供します。 これらのツールはMVCパターンのすべての部分を作成するのに役立ちますが、ここではモデルの生成についてのみ説明します。

OK、Composerを介してPhalconDevToolsをインストールしましょう。 composer.jsonファイルは次のようになります。

 { "require": { "php": ">=5.6.0", "ext-phalcon": ">=3", "ext-pgsql": "*" }, "require-dev": { "phalcon/devtools": "3.*.*@dev" } }

ご覧のとおり、PHP 5.6、Phalcon 3、およびpgsql拡張機能(データベース拡張機能に変更するか、完全に除外することができます)が必要です。

正しいPHP、Phalcon、およびDB拡張バージョンがあることを確認し、composerを実行します。

 $ composer install

次のステップは、データベースを作成することです。 これは非常に単純で、1つのテーブルusersのみで構成されています。 プロジェクトにpg_dumpファイルを含めましたが、PostgreSQL方言のSQLは次のとおりです。

 CREATE DATABASE articledemo; CREATE TABLE public.users ( id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('users_id_seq'::regclass), first_name CHARACTER VARYING(255), last_name CHARACTER VARYING(255), pass CHARACTER VARYING(255), login CHARACTER VARYING(255) NOT NULL );

データベースが作成されたので、モデル生成プロセスに進むことができます。 Phalcon Dev Toolsは、空の.phalconフォルダーを使用して、アプリケーションがPhalconプロジェクトであるかどうかを検出するため、プロジェクトルートにこの空のフォルダーを作成する必要があります。 また、作成した構成ファイルのいくつかの設定( applicationセクションの下に格納されているすべての変数とdatabaseセクションのadapter )も使用します。 モデルを生成するには、プロジェクトのルートフォルダーから次のコマンドを実行する必要があります。

 $ php vendor/phalcon/devtools/phalcon.php model users --namespace="App\Models" --get-set

前のすべての手順が正しく実行されると、 modelsフォルダーに作業モデルファイルUsers.phpが作成され、コマンドラインに示されているようにgetterとsetterを使用して名前空間に既に配置されます。 次はコントローラーです。

コントローラーとルーティング

このアプリケーションはユーザーのCRUD(作成、読み取り、更新、削除)のみを行うため、次の操作でUsersコントローラーという1つのコントローラーのみを作成します。

  • ユーザーを追加する
  • ユーザーのリストを表示する
  • ユーザーを更新する
  • ユーザーを削除する

コントローラーはPhalconDevToolsを使用して作成できますが、手動で作成し、 AbstractControllerとその子であるUsersControllerを実装します。

AbstractControllerを作成することは、依存性注入から取得する必要なすべてのクラスをPHPDocブロックに配置できるため、Phalconにとって適切な決定です。 これは、IDEのオートコンプリート機能に役立ちます。 また、すべての潜在的なコントローラーに共通するいくつかのエラー定数をプログラムすることもできます。

今のところ、抽象コントローラーは次のようになります。

 <?php namespace App\Controllers; /** * Class AbstractController * * @property \Phalcon\Http\Request $request * @property \Phalcon\Http\Response $htmlResponse * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config * @property \App\Services\UsersService $usersService * @property \App\Models\Users $user */ abstract class AbstractController extends \Phalcon\DI\Injectable { /** * Route not found. HTTP 404 Error */ const ERROR_NOT_FOUND = 1; /** * Invalid Request. HTTP 400 Error. */ const ERROR_INVALID_REQUEST = 2; }

extends構文で指定されている単純なPhalconインジェクタブルクラスであり、それ以上のものではありません。 次に、 UsersControllerスケルトンを作成しましょう。

 <?php namespace App\Controllers; /** * Operations with Users: CRUD */ class UsersController extends AbstractController { /** * Adding user */ public function addAction() { } /** * Returns user list * * @return array */ public function getUserListAction() { } /** * Updating existing user * * @param string $userId */ public function updateUserAction($userId) { } /** * Delete an existing user * * @param string $userId */ public function deleteUserAction($userId) { } }

現時点では、対応するHTTPリクエストを最終的に保持する空のアクションを持つクラスにすぎません。

次に、 routes.phpファイルに入力します。 Phalconマイクロアプリケーションでは、コントローラーごとに1つずつコレクションを作成し、処理されたすべてのリクエストをgetpostputdeleteメソッドとして追加します。これらのメソッドは、ルートパターンと進行関数を引数として取ります。 先行関数は無名関数またはコントローラーのメソッド名のいずれかである必要があることに注意してください。 routes.phpファイルは次のようになります。

 <?php $usersCollection = new \Phalcon\Mvc\Micro\Collection(); $usersCollection->setHandler('\App\Controllers\UsersController', true); $usersCollection->setPrefix('/user'); $usersCollection->post('/add', 'addAction'); $usersCollection->get('/list', 'getUserListAction'); $usersCollection->put('/{userId:[1-9][0-9]*}', 'updateUserAction'); $usersCollection->delete('/{userId:[1-9][0-9]*}', 'deleteUserAction'); $app->mount($usersCollection); // not found URLs $app->notFound( function () use ($app) { $exception = new \App\Controllers\HttpExceptions\Http404Exception( _('URI not found or error in request.'), \App\Controllers\AbstractController::ERROR_NOT_FOUND, new \Exception('URI not found: ' . $app->request->getMethod() . ' ' . $app->request->getURI()) ); throw $exception; } );

また、処理コントローラーとURIプレフィックスを設定します。 この例では、URIはhttp://article.dev/user/addのようになり、 postリクエストである必要があります。 ユーザーデータを変更する場合、URIはputリクエストである必要があり、IDが12のユーザーのデータを変更するにはhttp://article.dev/user/12のようになります。 また、エラーをスローするnotfoundURLハンドラーも定義します。 詳細については、フルスタックアプリケーションのルートおよびマイクロアプリケーションのルートについて、PhalconPHPのドキュメントを参照してください。

コントローラの本体、具体的にはaddActionメソッドに移動しましょう(他のすべては類似しています。アプリケーションコードで確認できます)。 コントローラメソッドは5つのことを行います。

  1. リクエストパラメータを取得して検証します
  2. サービス方式のデータを準備します
  3. サービスメソッドを呼び出します
  4. 例外を処理します
  5. 応答を送信します

検証から始めて、各ステップを見ていきましょう。 Phalconには強力な検証コンポーネントがありますが、この場合、古いスタイルの方法でデータを検証する方がはるかに便利です。したがって、検証ブロックは次のようになります。

 $errors = []; $data = []; $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; }

ここでは、 postパラメーターが正規表現に一致する文字列であるかどうかを確認します。 すべての値は$data配列に入れられ、 UsersServiceクラスに渡されます。 すべてのエラーは$errors配列に配置され、 Http400Exception内のエラー詳細の配列に追加されます。ここで、 index.phpに表示される詳細な応答に変換されます。

これは、すべての検証を含む完全なaddActionメソッドコードです。これには、 UsersServicecreateUserメソッドの呼び出しが含まれます(まだ作成していません)。

 public function addAction() { /** Init Block **/ $errors = []; $data = []; /** End Init Block **/ /** Validation Block **/ $data['login'] = $this->request->getPost('login'); if (!is_string($data['login']) || !preg_match('/^[A-z0-9_-]{3,16}$/', $data['login'])) { $errors['login'] = 'Login must consist of 3-16 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['password'] = $this->request->getPost('password'); if (!is_string($data['password']) || !preg_match('/^[A-z0-9_-]{6,18}$/', $data['password'])) { $errors['password'] = 'Password must consist of 6-18 latin symbols, numbers or \'-\' and \'_\' symbols'; } $data['first_name'] = $this->request->getPost('first_name'); if ((!empty($data['first_name'])) && (!is_string($data['first_name']))) { $errors['first_name'] = 'String expected'; } $data['last_name'] = $this->request->getPost('last_name'); if ((!empty($data['last_name'])) && (!is_string($data['last_name']))) { $errors['last_name'] = 'String expected'; } if ($errors) { $exception = new Http400Exception(_('Input parameters validation error'), self::ERROR_INVALID_REQUEST); throw $exception->addErrorDetails($errors); } /** End Validation Block **/ /** Passing to business logic and preparing the response **/ try { $this->usersService->createUser($data); } catch (ServiceException $e) { switch ($e->getCode()) { case AbstractService::ERROR_ALREADY_EXISTS: case UsersService::ERROR_UNABLE_CREATE_USER: throw new Http422Exception($e->getMessage(), $e->getCode(), $e); default: throw new Http500Exception(_('Internal Server Error'), $e->getCode(), $e); } } /** End Passing to business logic and preparing the response **/ }

ご覧のとおり、最後のセクションでは2つの既知の例外を処理します。 user already existsし、データベース接続エラーなどの内部問題のためにunable to create user 。 デフォルトでは、不明な例外がHTTP 500 (内部サーバーエラー)としてスローされます。 エンドユーザーには詳細を提供しませんが、すべてのエラーの詳細(トレースを含む)をログに保存することを強くお勧めします。

そして、他の名前空間から借用した、必要なすべてのクラスをuseことを忘れないでください。

 use App\Controllers\HttpExceptions\Http400Exception; use App\Controllers\HttpExceptions\Http422Exception; use App\Controllers\HttpExceptions\Http500Exception; use App\Services\AbstractService; use App\Services\ServiceException; use App\Services\UsersService;

ビジネスの論理

作成する最後の部分はビジネスロジックです。 コントローラの場合と同様に、抽象サービスクラスを作成します。

 <?php namespace App\Services; /** * Class AbstractService * * @property \Phalcon\Db\Adapter\Pdo\Postgresql $db * @property \Phalcon\Config $config */ abstract class AbstractService extends \Phalcon\DI\Injectable { /** * Invalid parameters anywhere */ const ERROR_INVALID_PARAMETERS = 10001; /** * Record already exists */ const ERROR_ALREADY_EXISTS = 10002; }

考え方はコントローラーのブロックと全く同じなので、コメントはしません。 UsersServiceクラスのスケルトンは次のとおりです。

 <?php namespace App\Services; use App\Models\Users; /** * business logic for users * * Class UsersService */ class UsersService extends AbstractService { /** Unable to create user */ const ERROR_UNABLE_CREATE_USER = 11001; /** * Creating a new user * * @param array $userData */ public function createUser(array $userData) { } }

そして、 createUserメソッド自体:

 public function createUser(array $userData) { try { $user = new Users(); $result = $user->setLogin($userData['login']) ->setPass(password_hash($userData['password'], PASSWORD_DEFAULT)) ->setFirstName($userData['first_name']) ->setLastName($userData['last_name']) ->create(); if (!$result) { throw new ServiceException('Unable to create user', self::ERROR_UNABLE_CREATE_USER); } } catch (\PDOException $e) { if ($e->getCode() == 23505) { throw new ServiceException('User already exists', self::ERROR_ALREADY_EXISTS, $e); } else { throw new ServiceException($e->getMessage(), $e->getCode(), $e); } } }

この方法は可能な限り簡単です。 新しいモデルオブジェクトを作成し、そのセッターを呼び出し(オブジェクト自体を返します。これにより、呼び出しチェーンを作成できます)、エラーが発生した場合はServiceExceptionをスローします。 それでおしまい! これでテストに進むことができます。

テスト

それでは、Postmanを使用して結果を見てみましょう。 最初にいくつかのゴミ箱データをテストしましょう:

無効なデータを持つ郵便配達員。

リクエスト:

 POST http://article.dev/user/add login:1 password:1 first_name:Name last_name:Sourname

応答(400:不正な要求):

 { "error": 2, "error_description": "Input parameters validation error", "details": { "login": "Login must consist of 3-16 latin symbols, numbers or '-' and '_' symbols", "password": "Password must consist of 6-18 latin symbols, numbers or '-' and '_' symbols" } }

それはチェックアウトします。 ここで、いくつかの正しいデータについて:

有効なデータを持つ郵便配達員。

リクエスト:

 POST http://article.dev/user/add login:user4 password:password4 first_name:Name last_name:Sourname

応答(204):

コンテンツはありません。これは私たちが期待していたことです。 それが機能することを確認して、完全なユーザーリストを取得しましょう(これについては記事では説明していませんが、アプリケーションの例で確認できます)。

リクエスト:

 GET http://article.dev/user/list

Response (200 OK):

 [ { "id": 1, "login": "user4", "first_name": "Name", "last_name": "Sourname" } ]

まあ、それは動作します!

Logging and Caching

It's hard to imagine a high-load application without logging and caching, and Phalcon provides very seductive classes for it. But I'm writing an article here, not a book; I've added logging and caching to the sample application, but I've placed this code into another branch called logging-and-cache so you can easily look at it and see the difference in the code. Just like the other Phalcon features, these two are well-documented: Logging and Caching.

短所

As you can see, Phalcon is really cool, but like other frameworks, it has its disadvantages, the first of which is the same as its main advantage—it's a compiled C extension. That's why there is no way for you to change its code easily. Well, if you know C, you can try to understand its code and make some changes, run make and get your own modification of Phalcon, but it is much more complicated than making some tweaks in PHP code. So, generally, if you find a bug inside Phalcon, it won't be so easy to fix.

This is partially solved in Phalcon 2 and Phalcon 3, which let you write extensions to Phalcon in Zephir. Zephir is a programming language designed to ease the creation and maintainability of extensions for PHP with a focus on type and memory safety. Its syntax is very close to PHP and Zephir code is compiled into shared libraries, same as the PHP extension. So, if you want to enhance Phalcon, now you can.

The second disadvantage is the free framework structure. While Symfony makes developers use a firm project structure, Phalcon has very few strict rules; developers can create any structure they like, though there is a structure that is recommended by its authors. This isn't a critical disadvantage, but some people may consider it too raw when you write the paths to all the directories in a bootstrap file manually.

PhalconPHP: Not Just For High-load Apps

I hope you've enjoyed this brief overview of PhalconPHP's killing features and the accompanying simple example of a Phalcon project. Obviously, I didn't cover all the possibilities of this framework since it's impossible to describe all of them in one article, but fortunately Phalcon has brilliantly detailed documentation with seven marvelous tutorials which help you understand almost everything about Phalcon.

You've now got a brand new way to create high load applications easily, and you'll find, if you like Phalcon, it can be a good choice for other types of applications too.