- HTTP要求と応答の抽象化、組み込み実装の不便さの補償。
- 個別のモジュール(ミドルウェア)から要求ハンドラーを構築する機能。
- 最も重要:URLディスパッチ、一連のディスパッチルールの単純な構造。
- 最も汎用性の高いアーキテクチャとしてのREST。
基本的に、最後の2つのポイントに焦点を当てたいと思います。したがって、特にこれらは明らかなことなので、残りは非常に表面的です。
HTTPリクエストとレスポンスの抽象化
さまざまな言語の大多数のWebフレームワークとは異なり、PHPには(少なくとも平均的な構成では)要求オブジェクトを操作するための通常のインターフェイスがありません。 バラバラで、常に均一に構造化された情報を含まない特別な変数が多数あります(たとえば、
$_POST
      
      と
$_FILES
      
      のデータ構造の違いに値するだけ
$_FILES
      
      )。
したがって、PHPの最高の伝統では、
Net_HTTP_Request
      
      クラスのオブジェクトの形で自転車をすばやく発明しました。 その主な機能:要求ヘッダーのパラメーターとフィールドへの便利なアクセス、ファイルオブジェクトの形式でのアップロードなど。
1 <?php <br> 
      
        
        
        
      
     2 $ id = $ request [ ' id ' ] ;<br> 
      
        
        
        
      
     3 $ auth = $ request -> header [ ' Authorization ' ] <br> 
      
        
        
        
      
     4 foreach ( $ request [ ' upload ' ] as $ line ) { /* ... */ } <br> 
      
        
        
        
      
     5 $ stores = $ request [ ' upload ' ] -> copy_to ( $ permanent_path ) ;<br> 
      
        
        
        
      
     6 ?> <br> 
      
        
        
        
      
    
      
      同様に、応答は
Net_HTTP_Response
      
      クラスのオブジェクトによって表されます。これは、本文を文字列、反復子、またはファイルオブジェクトなどとして表す、応答ヘッダーを使用した便利な作業を提供します。
1 <?php <br> 
      
        
        
        
      
     2 $ file = IO_FS :: File ( $ path ) ;<br> 
      
        
        
        
      
     3 return Net_HTTP :: Response () - > <br> 
      
        
        
        
      
     4 status ( Net_HTTP :: OK ) - > <br> 
      
        
        
        
      
     5 content_type ( MIME :: type_for_file ( $ file )) - > <br> 
      
        
        
        
      
     6 body ( $ file ) ;<br> 
      
        
        
        
      
     7 ?> <br> 
      
        
        
        
      
    
      
      リクエストハンドラー、ミドルウェア
要求処理は、標準インターフェイスを実装するサービスオブジェクトによって実行されます。
1 <?php <br> 
      
        
        
        
      
     2 interface WS_ServiceInterface { <br> 
      
        
        
        
      
     3 public function run ( WS_Environment $ env ) ;<br> 
      
        
        
        
      
     4 } <br> 
      
        
        
        
      
     5 ?> <br> 
      
        
        
        
      
    
      
      run()
      
      メソッドの結果は、
Net_HTTP_Response
      
      クラスのオブジェクトです。
WS_Environment
      
      クラスのオブジェクトは、サービスオブジェクト間の情報交換を目的としており、各サービスは環境パラメーターの値を作成、読み取り、または書き込みできます。 デフォルトでは、環境には
$env->request
      
      クラスの
$env->request
      
      要素が含まれています。
処理中のサービスが別のサービスを呼び出し、要求、応答、または環境を変更すると、いわゆるミドルウェアコンポーネントが取得され、WSGI標準によってその名前が使用されます。 アプリケーション構成、データベースへの接続、キャッシング、認証、そして最も興味深いディスパッチを実装する、簡単なミドルウェアサービスのセットがあります。
一般に、それは明らかな前文でした;最後に、救急車に移ります。
RESTベースのディスパッチ
多くの既存のフレームワークでは、ディスパッチはさまざまなアプリケーションメソッドの呼び出しをURL文字列に適用される正規表現に一致させることに基づいています。 RESTサポートの必要性は、このスキームを介したさまざまなREST指向のアドオンの出現につながり、まずHTTP要求メソッドの分析を提供します。
一連の正規表現に基づくディスパッチには、主にいくつかの欠点があります。
- 大規模なアプリケーションの場合、異種ルールの大規模で複雑なシステムを取得するリスクがあり、サポートが困難です。
- 規則システムは静的であり、そのプロセスでスケジューリングの結果に直接影響を与えることは困難です。
- 任意のレベルのネストを持つ組み込みリソースのスケジューリングは困難です。
ライブラリのスケジューリングスキームを選択するプロセスでは、さまざまな既存のオプションを検討しましたが、 Java標準JAX-RS (JSR-311)で提案されているアプローチが最も気に入っています。
なぜこの標準を選んだのですか?
- シンプル;
- RESTモデルをアプリケーションコードレベルに透過的に転送します。
- 投資したリソースを便利に使用できます。
- リソースを実装するクラスに制限を課しません。
PHP固有のものを考慮して、標準の簡易バージョンを実装しました。
- リソースは、注釈ではなく独自のDSLを使用して記述されます。
- リソースを決定する任意の順序の可能性を拒否したため、アルゴリズムが大幅に簡素化されました。
リクエストマネージャーはサービスオブジェクトとして実装されます(
WS_ServiceInteface) その
run()
      
      メソッドは、アプリケーションリソースセットの記述に基づいて、ディスパッチャーによって作成されたカスタムリソースオブジェクトに処理を委任します。
リソースは、単に任意のクラスのオブジェクトです。 標準の親クラスから継承する必要はなく、標準のインターフェイスを実装する必要はありません。 同時に、ユーザーアプリケーションでは、コードの重複を避けるために継承階層を導入するのが理にかなっていますが、フレームワークではこれを必要としません。
リソースクラスは、3つのタイプに分類できる一連のメソッドを実装します。
- クラス内で使用するためのヘルパーメソッド。
- さまざまなタイプの要求を処理し、応答オブジェクトを返すHTTPメソッド。
- ネストされたリソースクラスのインスタンスを生成するサブロケーター。
アプリケーションリソース
リソースのセットがアプリケーションを形成します。 アプリケーションにはそのリソースの説明が含まれており、内部DSLを使用して説明を作成します。
リソースメソッドの呼び出しについて決定するには、使用可能なリソースとそのメソッドの完全な説明が必要です。 1つまたは複数のリソースのコードが実際に実行されるため、すべてのクラスをロードする必要はありません。 したがって、アプリケーションを構成するリソースの説明は、実際の実装から分離する必要があります。
サンプルアプリケーション図:
1 <?php <br> 
      
        
        
        
      
     2 $ companies = WS_REST_DSL :: Application () - > <br> 
      
        
        
        
      
     3 begin_resource ( ' company ', ' App.WS.Company ', ' company/{name:[a-zA-Z][a-zA-Z-]+} ' ) - > <br> 
      
        
        
        
      
     4 sublocator ( ' blog ' ) - > //    -  <br> 
      
        
        
        
      
     5 sublocator ( ' vacancies ' ) - > //    -   <br> 
      
        
        
        
      
     6 for_format ( ' html ' ) - > <br> 
      
        
        
        
      
     7 index () - > // /company/Techart/ -   <br> 
      
        
        
        
      
     8 end - > <br> 
      
        
        
        
      
     9 end - > <br> 
      
        
        
        
      
     10 begin_resource ( ' blog ', ' App.WS.Blog ', null ) - > <br> 
      
        
        
        
      
     11 sublocator ( ' entry ', ' {\d+:id} ' ) - > <br> 
      
        
        
        
      
     12 for_format ( ' html ' ) - > <br> 
      
        
        
        
      
     13 get_for ( ' {page_no:\d+} ', ' index ' ) - > // /company/Techart/blog/5.html -   <br> 
      
        
        
        
      
     14 post () - > //    -    create() <br> 
      
        
        
        
      
     15 index () - > // /company/Techart/blog/ -     <br> 
      
        
        
        
      
     16 end - > <br> 
      
        
        
        
      
     17 for_format ( ' rss ' ) - > <br> 
      
        
        
        
      
     18 get ( ' index_rss ' ) - > // /company/Techart/blog/index.rss - RSS-  <br> 
      
        
        
        
      
     19 end - > <br> 
      
        
        
        
      
     20 end - > <br> 
      
        
        
        
      
     21 begin_resource ( ' entry ', ' App.WS.Entry ', null ) - > <br> 
      
        
        
        
      
     22 for_format ( ' html ' ) - > <br> 
      
        
        
        
      
     23 index () - > // /company/Techart/blog/82715/ -     <br> 
      
        
        
        
      
     24 get_for ( ' print ', ' print_version ' ) - > // /company/Techart/blog/82715/print.html -    <br> 
      
        
        
        
      
     25 put () - > //  ,   -  update() <br> 
      
        
        
        
      
     26 delete () - > //   ,   -  delete() <br> 
      
        
        
        
      
     27 end - > <br> 
      
        
        
        
      
     28 end - > <br> 
      
        
        
        
      
     29 begin_resource ( ' vacancies ', ' App.WS.Job ', null ) - > <br> 
      
        
        
        
      
     30 for_format ( ' html ' ) - > <br> 
      
        
        
        
      
     31 index () - > // /company/Techart/vacancies/ -    <br> 
      
        
        
        
      
     32 end - > <br> 
      
        
        
        
      
     33 end - > <br> 
      
        
        
        
      
     34 end ;<br> 
      
        
        
        
      
     35 ?> <br> 
      
        
        
        
      
    
      
      この図では、リソースは3つのパラメーターで説明されています。
- name-リソース名、補助パラメーター、まだ注意を払うことはできません。
- classname-リソースを実装するクラスの名前。
- pathは、リソースに対応するURLパターンです。
URLパターンは、名前付きパラメーターを使用した正規表現です(そうでない場合でも!)。
HTTPメソッド
HTTPメソッドは、さまざまなタイプの要求を処理し、応答を生成します。 メソッド記述パラメーター:
- name-メソッド名;
- http_mask-リソースメソッドが処理するhttpメソッドの組み合わせを指定するマスク。
- path-メソッドに対応するURLパターン。
- format-メソッドが形成するプレゼンテーション形式のリスト。
リソースクラスコンストラクターとリソースメソッドは、任意の引数セットを持つことができます。 URLパターンに、メソッドの引数と名前が一致するパラメーターが含まれている場合、リソースのコンストラクターまたはメソッドが呼び出されると、パラメーター値が自動的に置き換えられます。 さらに、
$env
      
      、
$request
      
      、および
$format
      
      などのいくつかの定義済み標準パラメーターがあり
$format
      
      。 引数名がテンプレートパラメータセットの一部ではなく、事前定義されたパラメータではない場合、
null
      
      代入されます。
メソッドごとに、プレゼンテーション形式のリストを指定できます。 要求された形式は、HTTP要求のヘッダーまたは要求されたドキュメントの拡張子によって決まります。 各形式に個別のメソッドを提供するか、
$format
      
      パラメーターを使用して1つのメソッドで処理を実行できます。 形式は、個々のメソッドとリソース全体の両方に対して指定できます。
サブロケーター
ネストされたリソースに対応するクラスのインスタンスの作成は、処理中に動的に実行できます。 これを行うには、いわゆるサブリソースロケーターを使用します。 リソース記述にメソッドが存在する場合、メソッドはサブロケーターですが、HTTPマスクにメソッドが指定されていません。 サブロケーターは要求を処理せず、代わりに、ネストされたリソースクラスのインスタンスを作成します。 したがって、特定のリソースを作成する決定は、特定の外部条件に応じて、リクエストの処理時に行うことができます。
リソースクラス
上記の例では、いくつかのリソースクラスのスケルトンは次のようになります。
1 <?php <br> 
      
        
        
        
      
     2 //    -     <br> 
      
        
        
        
      
     3 class App_WS_Resource { <br> 
      
        
        
        
      
     4 protected $ env ;<br> 
      
        
        
        
      
     5 protected $ db ;<br> 
      
        
        
        
      
     6 <br> 
      
        
        
        
      
     7 public function __construct ( WS_Environment $ env ) { <br> 
      
        
        
        
      
     8 $ this -> env = $ env ;<br> 
      
        
        
        
      
     9 $ this -> db = $ env -> db;<br> 
      
        
        
        
      
     10 } <br> 
      
        
        
        
      
     11 } <br> 
      
        
        
        
      
     12 <br> 
      
        
        
        
      
     13 //  blog <br> 
      
        
        
        
      
     14 class App_WS_Blog extends App_WS_Resource { <br> 
      
        
        
        
      
     15 <br> 
      
        
        
        
      
     16 public function index ( $ page_no = 1 ) { /* $page_no    URL  */ } <br> 
      
        
        
        
      
     17 <br> 
      
        
        
        
      
     18 public function index_rss () { /* RSS- */ } <br> 
      
        
        
        
      
     19 <br> 
      
        
        
        
      
     20 public function entry ( $ id ) { <br> 
      
        
        
        
      
     21 //  -          <br> 
      
        
        
        
      
     22 //   <br> 
      
        
        
        
      
     23 if ( $ entry = $ this -> db -> blog -> entries [ $ id ]) <br> 
      
        
        
        
      
     24 return new App_WS_Entry ( $ this -> env, $ entry ) ; <br> 
      
        
        
        
      
     25 } <br> 
      
        
        
        
      
     26 <br> 
      
        
        
        
      
     27 public function create () { /*    */ } <br> 
      
        
        
        
      
     28 } <br> 
      
        
        
        
      
     29 <br> 
      
        
        
        
      
     30 //  entry <br> 
      
        
        
        
      
     31 class App_WS_Entry extends App_WS_Resource { <br> 
      
        
        
        
      
     32 protected $ entry ;<br> 
      
        
        
        
      
     33 <br> 
      
        
        
        
      
     34 public function __construct ( WS_Environment $ env , App_DB_Entry $ entry ) { <br> 
      
        
        
        
      
     35 parent :: __construct ( $ env ) ;<br> 
      
        
        
        
      
     36 $ this -> entry = $ entry ;<br> 
      
        
        
        
      
     37 } <br> 
      
        
        
        
      
     38 <br> 
      
        
        
        
      
     39 public function index () { /*   */ } <br> 
      
        
        
        
      
     40 public function print_version () { /*    */ } <br> 
      
        
        
        
      
     41 public function update () { /*   */ } <br> 
      
        
        
        
      
     42 public function delete () { /*   */ } <br> 
      
        
        
        
      
     43 } <br> 
      
        
        
        
      
    
      
      スケジューリングアルゴリズム
JAX-RS標準で説明されているアルゴリズムの簡略版であるスケジューリングアルゴリズムは、次のようになります。
- 要求ヘッダーまたはドキュメント拡張機能により、必要なプレゼンテーション形式を決定します。
- リソースの説明を見て、それぞれのパスをURLの先頭と比較します。
- URLがパターンと一致するリソースが見つからず、形式がサポートされているもののリストに含まれている場合-404;
- リソースが見つかりました。メソッドを探しています。 リソースのパスに一致する部分をURLから削除します。
- メソッドURLパターンをURLの残りの部分の先頭と照合することにより、リソースメソッドのすべての説明を調べます。
- URLパターンと表示形式に一致するメソッドまたはサブロケーターがない場合-404;
- 適切なURLパターン、形式、およびHTTPマスクを持つメソッドが見つかった場合、リソースクラスのオブジェクトを作成し、メソッドを呼び出してパラメーターの置換を実行します。 実行結果-応答を表すオブジェクト、作業は完了します。
- 適切なURLパターンを持つサブロケーターが見つかった場合、リソースクラスのオブジェクトを作成し、メソッドを呼び出してパラメーターの置換を実行します。
- サブロケーターの結果は、新しいリソースです。 リソースのリストで対応するクラスのリソースの説明を探しています。
- リソースクラスが説明にない場合-404;
- httpメソッドが見つかるまで、URLの先頭から一致する行を削除し、手順4に戻ります。
アルゴリズムを単純化するために、アドレス/ resource /および/resource/index.html(html形式の場合)は同等であると考えています。さらに、アルゴリズムの反復の最大数を強制します。
パフォーマンスを向上させるには、まずリソースの説明をキャッシュし、次に、メインアプリケーションドメインのURLサブディレクトリまたはサブドメインにリンクすることにより、大きなアプリケーションを個別の小さなアプリケーションに分割します。
結果
提案されたスキームを実装することで何が得られますか?
- アプリケーションの種類(従来のWebアプリケーション、Webサービス、AJAXアプリケーション)に関係なく、RESTモデルのすべての利点を完全に使用できます。
- アプリケーションコードは実装メカニズムから最大限に分離されているため、テストが大幅に簡素化され、開発者はアプリケーションのアプリケーションモデルをより自由に設計できます。
- ディスパッチルールと、結果として、リソースクラスはかなり均一な構造を持ち、設計とさらなるサポートを促進します。 ご希望の場合(すべて一緒になりますが、一緒になりません)、アプリケーションの構造を説明するグラフィカルな表記法を開発し、 graphvizを使用して視覚化できます。
- 任意のネストの深さの埋め込みリソースを使用するのは簡単です。
一般に、提案されたスキームに非常に満足しています。私たちの意見では、実際に開発者に制限を課すことはなく、実装が非常に簡単です。 興味深い予想外の結果は、プリミティブなORMレイヤーとの非常に単純な統合です。 2つの非常に単純なリソースクラスのみを実装したため、ORMレイヤーへの命令でHTTPリクエストの透過的なマッピングを受け取りました。たとえば、
GET /api/news/stories/most_popular 
      
        
        
        
      
     $db->news->stories->most_popular->select() 
      
        
        
        
      
     
      
        
        
        
      
     POST /api/news/stories/ 
      
        
        
        
      
     $db->news->stories->insert($story) 
      
        
        
        
      
     
      
        
        
        
      
     PUT /api/news/stories/15 
      
        
        
        
      
     $db->news->stories->update($db->news->stories[15]); 
      
        
        
        
      
    
      
      などなど。
さて、私たちの内部RESTセミナーの資料は突然、誰もが興味を持つようになります。