ラムダ関数とクロージャーを使用したPHPテンプレート

php 5.3以降、クロージャーと匿名関数を使用する絶好の機会を得ました。 それらは、代替構文と共に、標準化での使用に非常に便利です(もちろん、レイアウトデザイナーがphpにアクセスする必要がない場合を除く)。また、それらに基づくテンプレートは迅速で、アクセラレータによってバイトコードに簡単に変換され、ブロック継承をサポートできます。コンパイルとキャッシュ、スキンのサポート、開発が非常に便利です。

読者は、twigなどのテンプレートエンジンの経験があることを前提としています。 カットの下の詳細。



まず、セマンティクスと構文を決定します。

1)テンプレート内のLambda関数は命令と呼ばれ、大文字で記述します。

2)テンプレート内のシステム変数とサービス変数は下線で始まります。

3)テンプレート内の他のすべての変数はその即時引数であり、小文字で書かれています。



テンプレートエンジンは、renderメソッドを持つサービスオブジェクトです。

public function exec($_template,array $_data=array(),$_skin=null,$_type='php',&$_buffer=null) { }
      
      





このメソッドの名前空間では、最も興味深いことが起こります。

まず、変数を定義し、クロージャーを含むいくつかの命令を宣言し、最後に結果処理を追加します。

 public function exec($_template,array $_data=array(),$_skin=null,$_type='php',&$_buffer=null) { if (!isset($_skin)) $_skin = $this->api->cfg['default_skin']; if (!$_filename = $this->getFile($_template,$_skin,$_type)) return ''; $_parent = null; $_api = $this->api; //           $R = function($name) use ($_template, $_skin) { echo "/res/t/{$_skin}/{$_template}/$name"; }; //     $BEGIN = function($blockname) { ob_start(); }; //     $END = function($blockname) use (&$_buffer,$_parent) { if (isset($_buffer[$blockname])) { ob_end_clean(); echo $_buffer[$blockname]; } else { $_buffer[$blockname] = isset($_parent)?ob_get_clean():ob_get_flush(); } }; //     $EXTEND = function($template,$type=null) use (&$_parent) { if ($template) $_parent =array($template,$type); }; //      $INCLUDE = function($template,$type=null) use ($_data,$_api,$_skin) { if ($template) echo $_api->templater->exec($template,$_data,$_skin,$type); }; //  css-   dom- $CLASS = function() use ($_template,$_skin) { echo "t-{$_template} s-{$_skin}"; }; if (!isset($this->instructions)) $this->instructions = $this->getInstructions(); //      $V = function(&$var,$default='',$raw=false) use ($api) { if (isset($var)) { if (is_scalar($var)) echo $raw?$var:htmlspecialchars($var); else $api->templater->dump($var); } else { echo $raw?$default:htmlspecialchars($default); } }; //      $GV = function(&$var,$default='') { if (isset($var)) return $var; else return $default; }, extract($_data); //   -   -  ( ,   ) if (!isset($_language)) $_language = $this->api->cfg['default_language']; $L = function(&$stringhash,$default=array('?')) use ($_language,$_api) { if (!isset($stringhash)) $stringhash = $default; if (is_string($stringhash)) { echo htmlspecialchars($stringhash); return; } if (isset($stringhash[$_language[0]])) echo htmlspecialchars($stringhash[$_language[0]]); elseif (isset($stringhash[$_api->cfg['default_language'][0]])) echo htmlspecialchars($stringhash[$_api->cfg['default_language'][0]]); else echo htmlspecialchars(reset($stringhash)); }; extract($this->instructions); ob_start(); include $_filename; $content = ob_get_clean(); if ($_parent) $content = $this->exec($_parent[0],$_data,$_skin,$_parent[1],$_buffer); return $content; }
      
      







ブロックの継承と再定義は次のように機能します。

1)テンプレートの先頭に$ EXTEND()命令が指定されている場合、拡張可能なテンプレートは現在のテンプレートの「祖先」によって設定されます。

2)継承ブロックの先頭( "$ BEGIN()")で、書き込みバッファが開きます。 パラメーターの名前は、セマンティクスにのみ必要です。

3)継承されたブロックの終わりに、書き込みバッファが出力にスローされるか、既存の保存されたバッファがいっぱいになった場合にスローされます(ニュアンスがあります:両方のブロックが実行されます-祖先と子孫の両方が出力されます;これは実際には必須ではありません);



したがって、最初に子孫がレンダリングされ、次に祖先がレンダリングされます。 祖先に子孫で定義されたブロックがある場合、それらは表示されませんが、子孫のブロックに置き換えられます。 この場合、祖先のスキンは子孫のスキンから取得されます(レンダーが呼び出されたときに設定されます)。 祖先と子孫の通信レベルが3つ以上存在する可能性があります。レンダリング関数の結果、指定された名前を持つ最後のブロックのみが存在します。



命令$ V、$ L、および$ GVは変数のみを受け入れます(存在しない変数への警告のアクセスを防ぐために変更する "&"許可を使用します。ラムダ関数の内部変数はnullです)



$ R命令は、テンプレートにバインドされたリソースを出力するためのものです。 たとえば、次のように:

 <?foreach($GV($pictures,array()) as $picture):?> <img src="<?$R($picture)?>"/> <?endforeach;?>
      
      





追加の命令(たとえば、定数の操作など)を抽出経由でメソッドに渡すことができます($ this->命令)。このラムダ関数の配列は動的に生成されます。



テンプレートの例(クラスはhtmlタグで使用され、高さを指定します:たとえば、スキンの1つで100%):

 <!DOCTYPE html> <html class="<?$CLASS()?>" id="<?$V($_id)?>"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title><?$L($title,array('en'=>'No title','ru'=>' '))?></title> <link rel="stylesheet" href="/res/var/t.css?<?=$_api->build?>" type="text/css" media="all" /> <script type="text/javascript" src="/res/jquery.js?<?=$_api->build?>"></script> <script type="text/javascript" src="/res/var/t.js?<?=$_api->build?>"></script> <script type="text/javascript" src="/res/var/frontend.js?<?=$_api->build?>"></script> </head> <body> <header> <?$BEGIN('header')?> <h1><?$L($title,array('en'=>'No title','ru'=>' '))?></h1> <?$SLOT($langswitch)?> <?$END('header')?> </header> <section> <?$BEGIN('content')?> <?$L($content,array('en'=>'No content','ru'=>' '))?> <?$END('content')?> </section> <footer> <?$BEGIN('footer')?> <?$SLOT($menu_footer)?> <p class="copy"> <?=date('Y')?> MyProject </p> <p class="info"> <?$s=array('en'=>'Generated','ru'=>'');$L($s)?>: <?=date('r')?> </p> <?$END('footer')?> </footer> </body> </html>
      
      







CSSを構築するときにテンプレートエンジンを使用する例:

 . {background: url("<?$R('bg.png')?>")} . > .left {width: <?$C('left-margin')?>; border: 1px <?$C('border-color')?> solid;}
      
      





ここで、$ Cは定数を操作するための命令です。 テンプレートのおかげで、スタイルファイルでは、ループを実行したり、座標計算関数を埋め込んだり、CSSを展開したり、サーバー側に含めたりすることができます。 スタイルはドットで始まります。CSSコレクターは行の先頭のドットをテンプレートブロックセレクターに自動的に置き換えるため、ブロックレイアウトがサポートされます。



同様に、プロジェクトコンポーネントをアセンブルするときに、javascriptファイルをテンプレートエンジンに渡すことができます。 たとえば、これは、$ PATH( 'route_name'、$ args)命令を使用して、ajaxリクエストや他のリンクへのパスを置き換えるのに便利です。



すべてのテンプレートリソースはコレクターによってドックのフォルダーに収集される必要があり、テンプレートのすべてのcssおよびjsにはセレクター(「$ CLASS()」を参照)のプレフィックスが付けられ、ドック内の1つ(まあ、2つ)のファイルに接着されます。 これらのメカニズムについては、他の記事で個別に説明します。



このアプローチは、稼働中のシステムのフレームワーク内のプロトタイプに存在することに注意してください。明らかな理由により、この記事では説明しません。実験的です。 主な目標は、許容可能なパフォーマンスを維持しながら、多数のテンプレートの長いコンパイル(小枝)による開発時間の損失を減らすことでした。 したがって、このアプローチのニュアンスと落とし穴について経験豊富な人々からコメントで読んでうれしいです。



All Articles