PHP DSL要素:ライブラリAPIを使いやすくする

内部フレームワークを開発するとき(残念ながら、PHPは一般的に自転車の絶え間ない再発明を大いに促進します)、これらのインターフェイスを使用するクライアントコードがシンプルで簡潔で読みやすい方法でライブラリモジュールインターフェイスを設計しようとしました。



理想的には、特定の問題を解決するように設計された専用モジュールは、開発者が問題を解決する結果または問題を可能な限り主題分野の用語に近づけることができるようにする簡略化された言語を形成する必要があります。 同時に、使用するプログラミング言語のフレームワークを超えない場合は、いわゆる内部DSLの実装について話します。







さまざまな言語でのDSLの実装について多くのことが書かれています。たとえば、 このトピックに関するパターンのカタログは Fowler Webサイトで入手できます。 設計パターンの機能は、主に実装言語によって決定されます;これは、DSLパターンについても同様です。 残念ながら、PHPが提供できる可能性の範囲は非常に限られています。 それでも、2つの標準テンプレート( メソッドチェーン式ビルダー )を使用すると、より便利で読みやすいAPIを実現できます。



クラスとメソッドの適切な命名は、DSLスタイルのAPIを開発するときの戦いの半分です。 メソッドは、ソフトウェア実装ではなく、サブジェクトエリアにできるだけ近い名前を付けることが重要です。 見た目は悪いですが、たとえばGoFの古典的なデザインパターンの実装など、ネーミングが原因である場合、多くの例を見つけることができます。



メソッドチェーンを使用すると、コードがより簡潔になり、場合によっては特殊なDSLの効果を実現できます。 ライブラリモジュールを開発するときは、ルールに従うようにします。メソッドが機能的に必要な結果を返さない場合は、 $this



返します。 また、通常、オブジェクトの内部プロパティを設定するための一連のメソッドを提供します。これにより、式内のオブジェクトパラメータを構成でき、コードがより簡潔になります。



Builderパターンを使用すると、親が子へのリンクを含み、それらが子へのリンクを含む場合に、ネストされたオブジェクトのシステムをより便利に構築できます。 PHPでは、双方向リンク(親オブジェクトは子を参照し、子は親を参照する)を回避することをお勧めします。ガーベッジコレクターは循環リンクでは機能しないためです。



このようなシステムを作成するには、非常に単純な基本クラスを作成します。







  1. <?php
  2. クラス DSL_Builder {
  3. 保護された $ parent ;
  4. 保護された $オブジェクト ;
  5. パブリック関数 __construct( $ parent$ object ){
  6. $ this- > parent = $ parent ;
  7. $ this- > object = $ object ;
  8. }
  9. パブリック関数 __get( $ property ){
  10. switch$ property ){
  11. ケース 「終了」
  12. $ this- > parentを返しますか? $ this- > parent: $ this- > object;
  13. ケース 「オブジェクト」
  14. $ this- > $ propertyを 返し ます
  15. デフォルト
  16. 新しい Core_MissingPropertyException( $ property );
  17. }
  18. }
  19. public function __set( $ property$ value ){ throw new Core_ReadOnlyObjectException( $ this ); }
  20. パブリック関数 __isset( $ property ){
  21. switch$ property ){
  22. ケース 「オブジェクト」
  23. return isset$ this- > $ property );
  24. デフォルト
  25. falseを返します
  26. }
  27. }
  28. public function __unset( $ property ){ throw新しい Core_ReadOnlyObjectException( $ this ); }
  29. パブリック関数 __call( $ method$ args ){
  30. method_exists( $ this-> object、 $ method )?
  31. call_user_func_array( array$ this-> object、 $ method )、 $ args ):
  32. $ this-> object-> $ method = $ args [ 0 ];
  33. $ thisを 返します。
  34. }
  35. }
  36. ?>




このクラスのオブジェクトは、 $object



フィールドに格納される参照であるターゲットオブジェクトを設定し、メソッドの呼び出しとプロパティの設定を委任します。 もちろん、ビルダーオブジェクトは、ターゲットオブジェクトのより複雑な構成のために独自のメソッドのセットを定義することもできます。 同時に、擬似プロパティend



使用すると、親オブジェクトのビルダーなどに戻ることができます。



このクラスに基づいて、アプリケーションの構成を記述する最も単純なDSLを作成します。







  1. <?php
  2. クラス Config_DSL_Builder DSL_Builderを拡張します{
  3. public function __construct(Config_DSL_Builder $ parent = nullstdClass $ object = null ){
  4. parent :: __構文( $ parent 、Core :: if_null( $ objectnew stdClass ()));
  5. }
  6. public function load( $ file ){
  7. ob_start();
  8. インクルード$ file );
  9. ob_end_clean();
  10. $ thisを 返します。
  11. }
  12. public function begin( $ name ){
  13. return new Config_DSL_Builder( $ this$ this-> object-> $ name = new stdClass ());
  14. }
  15. パブリック関数 __get( $ property ){
  16. return (strpos( $ property'begin_' )=== 0 )?
  17. $ this-> begin(substr( $ property6 )):
  18. 親:: __ get( $ property );
  19. }
  20. パブリック関数 __call( $ method$ args ){
  21. $ this-> object-> $ method = $ args [ 0 ];
  22. $ thisを 返します。
  23. }
  24. }
  25. ?>




これで、次の形式でアプリケーションの構成を記述するconfig.php



を作成できます。



  1. <?php
  2. $ this- >
  3. begin_db->
  4. dsn( 'mysql://ユーザー:パスワード@ localhost / db' )->
  5. 終わり->
  6. begin_cache->
  7. dsn( 'ダミー://' )->
  8. default_timeout( 300 )->
  9. タイムアウト( 配列
  10. 'front / index' => 300
  11. 'news / most_popular' => 300
  12. 'ニュース/カテゴリ' => 300 ))->
  13. 終わり->
  14. begin_site->
  15. begin_from->
  16. top_limit( 7 )->
  17. 終わり->
  18. begin_news->
  19. most_popular_limit( 5 )->
  20. 終わり->
  21. 終わり;
  22. ?>




呼び出しを使用して構成をロードできます。



  1. <?php
  2. $ config = Config_DSL :: Builder()-> load( 'config.php' );
  3. ?>




もちろん、問題は構成だけに限定されません。 たとえば、次のようなRESTアプリケーションの構造を説明します。



  1. <?php
  2. WS_REST_DSL ::アプリケーション()->
  3. media_type( 'html''text / html'true )->
  4. media_type( 'rss''application / xhtml + xml' )->
  5. begin_resource( 'gallery''App.Photo.Gallery''galleries / {id:\ d +}' )->
  6. for_format( 'html' )->
  7. get_for( '{page_no:\ d +}''index' )->
  8. post_for( 'vote''vote' )->
  9. インデックス()->
  10. 終わり->
  11. 終わり->
  12. begin_resource( 'index''App.Photo.Index' )->
  13. for_format( 'rss' )->
  14. get( 'index_rss' )->
  15. get_for( 'top''top_rss' )->
  16. 終わり->
  17. for_format( 'html' )->
  18. get_for( '{page_no:\ d +}''index' )->
  19. インデックス()->
  20. 終わり->
  21. 終わり->
  22. 終わり;
  23. ?>




高速なDSLスタイルのAPIを使用すると、たとえばアプリケーションコントローラーメソッドで、短くて読みやすいコードを取得できます。







  1. <?php
  2. パブリック関数インデックス( $ page_no = 1 ){
  3. $ pager = Data_Pagination :: pager( $ this-> db-> photo- > galleries-> count()、 $ page_no 、self :: PAGE_LIMIT);
  4. $ this-> html( 'index' )->を返します
  5. 配列
  6. 'top' => $ this-> db-> photo-> galleries-> most_important()-> select()、
  7. 'pager' => $ pager
  8. 'ギャラリー' => $ this-> db-> photo-> galleries->
  9. 公開済み()->
  10. paginate_with( $ pager )->
  11. select()));
  12. }
  13. ?>




比較的まれなケースでは、さらに先へ進むことができます。 DSL_Builder



クラスをDSL_Builder



、静的な構造だけでなく、一連のアクション、つまり特定のシナリオも記述できます。 たとえば、次のようにGoogle AdWords APIを使用できます







  1. <?php
  2. Service_Google_AdWords_DSL ::スクリプト()->
  3. for_campaign( $ campaign_id )->
  4. for_ad_group( $ group_id )->
  5. for_each( 'text''keyword1''keyword2''keyword3' )->
  6. add_keyword_criteria()->
  7. バインド( 'text' )->
  8. 終わり->
  9. 終わり->
  10. add_ad()->
  11. 'headline''headline'
  12. 'displayUrl''www.techart.ru'
  13. 'destinationUrl''http://www.techart.ru/'
  14. 'description1''desc1'
  15. 'description2''desc2' )->
  16. 形式( 「作成された広告」 )->
  17. 終わり->
  18. 終わり->
  19. 終わり->
  20. for_each_campaign()->
  21. 形式( "キャンペーン:%d、%s \ n"'campaign.id''campaign.name' )->
  22. ダンプ( 'campaign' )->
  23. for_each_ad_group()->
  24. 形式( "広告グループ:%d、%s \ n"'ad_group.id''ad_group.name' )->
  25. for_each_criteria()->
  26. 形式( "基準:%d、%s \ n"'criteria.id''criteria.text' )->
  27. 終わり->
  28. 終わり->
  29. 終わり->
  30. 終わり->
  31. run_for(Service_Google_AdWords ::クライアント()->
  32. useragent( 'ユーザーエージェント' )->
  33. メール( 'email@domain.com' ));
  34. ?>




もちろん、このアプローチは妥当な範囲内で使用する必要がありますが、非常に良い結果が得られる場合もあります。



All Articles