可能性を追求しているところです。
今回は、テーマファイルからAppを呼び出す仕組みを開発してみました。
通常のWEBアプリでは、まずAppを呼び出して処理した後に
Appでの実行結果をテーマに書き出すような処理順序になると思います。
今回、この処理順序をちょっと変えて、
テーマファイルに「app=blog fct=EntryView id=3」のように書いておけば
該当箇所にAppの実行結果が自動的に表示されるようにしてみました。
※イメージ的には、SoyCMSでやってるようなことです。
SoyCMSでは独自テンプレートエンジンでやってると思いますが、
当方では今回これをTwigという有名なテンプレートエンジン上で
やってみている、というワケです。
以下、実装クラスのソースコードを晒しておきますので、
ご参考いただければ幸いです。
Extensionクラス
当該クラスで、TokenParserを定義します。(c.f. getTokenParsers())<?php
/**
* Twig_Extension_Sample
* @author spark43
*
*/
class Twig_Extension_Sample extends Twig_Extension
{
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_Extension::getTokenParsers()
*/
public function getTokenParsers()
{
return array(
new Twig_TokenParser_Sample(),
);
}
/**
* (non-PHPdoc)
* @see v0.01/Sample/_opt/Twig/Twig_Extension::getFilters()
*/
public function getFilters()
{
return array(
'nl2br' => new Twig_Filter_Method($this, 'nl2br'),
);
}
/**
* (non-PHPdoc)
* @see v0.01/Sample/_opt/Twig/Twig_ExtensionInterface::getName()
*/
public function getName()
{
return 'aither';
}
/**
* nl2br
* @param $string
*/
public function nl2br($string)
{
return str_replace("\n", "<br />", Twig_Filter_Method);
}
}
?>
/**
* Twig_Extension_Sample
* @author spark43
*
*/
class Twig_Extension_Sample extends Twig_Extension
{
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_Extension::getTokenParsers()
*/
public function getTokenParsers()
{
return array(
new Twig_TokenParser_Sample(),
);
}
/**
* (non-PHPdoc)
* @see v0.01/Sample/_opt/Twig/Twig_Extension::getFilters()
*/
public function getFilters()
{
return array(
'nl2br' => new Twig_Filter_Method($this, 'nl2br'),
);
}
/**
* (non-PHPdoc)
* @see v0.01/Sample/_opt/Twig/Twig_ExtensionInterface::getName()
*/
public function getName()
{
return 'aither';
}
/**
* nl2br
* @param $string
*/
public function nl2br($string)
{
return str_replace("\n", "<br />", Twig_Filter_Method);
}
}
?>
TokenParserクラス
当該クラスでテーマに書かれた構文をトークンに解析します。使用するフレームワークの構文に合わせることが必要です。
※オープンソースCMSなどに見られる場合があるのですが、
使用モジュールごとにそれぞれ独自なフレームワークを使っている場合、
該当フレームワークごとに必要となる場合もあるかと思います。
<?php
/**
* Twig_TokenParser_Sample
* @author spark43
*
*/
class Twig_TokenParser_Sample extends Twig_TokenParser
{
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_TokenParserInterface::parse()
*/
public function parse(Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$exp = $this->parser->getExpressionParser();
// parse
$request = array();
$param = array();
while($stream->test(Twig_Token::NAME_TYPE)) {
$expk = $exp->parseExpression();
$stream->expect(Twig_Token::OPERATOR_TYPE, '=');
$expv = $exp->parseExpression();
switch ($expk['name']) {
case 'app':
case 'fct':
case 'with':
if (! ($expv instanceof Twig_Node_Expression_Name)) {
throw new Twig_SyntaxError("'". $expk['name']. "' paameter is invalid", $lineno);
}
$request[$expk['name']] = $expv;
break;
default:
// TODO : other expressions...?
if (! ($expv instanceof Twig_Node_Expression_Name)
&& ! ($expv instanceof Twig_Node_Expression_Constant)) {
throw new Twig_SyntaxError("'". $expk['name']. "' paameter is invalid", $lineno);
}
$param[$expk['name']] = $expv;
break;
}
}
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
// check
if (! isset($request['app'])) {
throw new Twig_SyntaxError("no 'app' paameter", $lineno);
}
if (! isset($request['fct'])) {
throw new Twig_SyntaxError("no 'fct' paameter", $lineno);
}
if (! isset($request['with'])) {
throw new Twig_SyntaxError("no 'with' paameter", $lineno);
}
// return
return new Twig_Node_Sample(new Twig_Node($param), new Twig_Node($request), $lineno, $this->getTag());
}
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_TokenParserInterface::getTag()
*/
public function getTag()
{
// using tag name
return 'aither';
}
}
?>
/**
* Twig_TokenParser_Sample
* @author spark43
*
*/
class Twig_TokenParser_Sample extends Twig_TokenParser
{
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_TokenParserInterface::parse()
*/
public function parse(Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$exp = $this->parser->getExpressionParser();
// parse
$request = array();
$param = array();
while($stream->test(Twig_Token::NAME_TYPE)) {
$expk = $exp->parseExpression();
$stream->expect(Twig_Token::OPERATOR_TYPE, '=');
$expv = $exp->parseExpression();
switch ($expk['name']) {
case 'app':
case 'fct':
case 'with':
if (! ($expv instanceof Twig_Node_Expression_Name)) {
throw new Twig_SyntaxError("'". $expk['name']. "' paameter is invalid", $lineno);
}
$request[$expk['name']] = $expv;
break;
default:
// TODO : other expressions...?
if (! ($expv instanceof Twig_Node_Expression_Name)
&& ! ($expv instanceof Twig_Node_Expression_Constant)) {
throw new Twig_SyntaxError("'". $expk['name']. "' paameter is invalid", $lineno);
}
$param[$expk['name']] = $expv;
break;
}
}
$this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
// check
if (! isset($request['app'])) {
throw new Twig_SyntaxError("no 'app' paameter", $lineno);
}
if (! isset($request['fct'])) {
throw new Twig_SyntaxError("no 'fct' paameter", $lineno);
}
if (! isset($request['with'])) {
throw new Twig_SyntaxError("no 'with' paameter", $lineno);
}
// return
return new Twig_Node_Sample(new Twig_Node($param), new Twig_Node($request), $lineno, $this->getTag());
}
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_TokenParserInterface::getTag()
*/
public function getTag()
{
// using tag name
return 'aither';
}
}
?>
Nodeクラス
当該クラスでトークンをコンパイルします。TokenParserクラスと同様に、
使用するフレームワークとの整合性に
十分に配慮する必要があります。
<?php
/**
* Twig_Node_Sample
* @author spark43
*
*/
class Twig_Node_Sample extends Twig_Node
{
/**
* __construct
* @param $name
* @param $value
* @param $lineno
* @param $tag
*/
public function __construct($param, $request, $lineno, $tag = null)
{
parent::__construct(array('param' => $param), array('request' => $request), $lineno, $tag);
}
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_Node::compile()
*/
public function compile($compiler)
{
$compiler->addDebugInfo($this);
// prepare request (from token parser)
$app = $this['request']->app['name'];
$fct = $this['request']->fct['name'];
$with = (isset($this['request']->with['name'])
? $this['request']->with['name']
: VApp::BY_TPL);
switch ($with) {
case VApp::BY_OBJ:
case VApp::BY_TPL:
$withX = $with;
break;
default:
$withX = VApp::BY_OBJ;
break;
}
$compiler->write("\$req = new VRequest();\n")
->write("\$req->app = '". $app. "';\n")
->write("\$req->fct = '". $fct. "';\n")
->write("\$req->with = '". $withX. "';\n");
// prepare parameters
$param = $this->param;
foreach ($param as $pk => $pv) {
if ($pv instanceof Twig_Node_Expression_Name) {
$compiler->write("\$req->". $pk. "=". $pv['name']. ";\n");
}
if ($pv instanceof Twig_Node_Expression_Constant) {
$compiler->write("\$req->". $pk. "=". $pv['value']. ";\n");
}
}
// execute requested app
$compiler->write("\$vservice = new ThemeService(\$req);\n")
->write("\$aether_result = \$vservice->service();\n");
switch ($withX) {
case VApp::BY_OBJ:
$compiler->write("\$context['". $with. "'] = \$aether_result;\n");
break;
case VApp::BY_TPL:
$compiler->write("echo \$aether_result;\n");
break;
}
}
}
?>
/**
* Twig_Node_Sample
* @author spark43
*
*/
class Twig_Node_Sample extends Twig_Node
{
/**
* __construct
* @param $name
* @param $value
* @param $lineno
* @param $tag
*/
public function __construct($param, $request, $lineno, $tag = null)
{
parent::__construct(array('param' => $param), array('request' => $request), $lineno, $tag);
}
/**
* (non-PHPdoc)
* @see v0.02/Sample/_opt/Twig/Twig_Node::compile()
*/
public function compile($compiler)
{
$compiler->addDebugInfo($this);
// prepare request (from token parser)
$app = $this['request']->app['name'];
$fct = $this['request']->fct['name'];
$with = (isset($this['request']->with['name'])
? $this['request']->with['name']
: VApp::BY_TPL);
switch ($with) {
case VApp::BY_OBJ:
case VApp::BY_TPL:
$withX = $with;
break;
default:
$withX = VApp::BY_OBJ;
break;
}
$compiler->write("\$req = new VRequest();\n")
->write("\$req->app = '". $app. "';\n")
->write("\$req->fct = '". $fct. "';\n")
->write("\$req->with = '". $withX. "';\n");
// prepare parameters
$param = $this->param;
foreach ($param as $pk => $pv) {
if ($pv instanceof Twig_Node_Expression_Name) {
$compiler->write("\$req->". $pk. "=". $pv['name']. ";\n");
}
if ($pv instanceof Twig_Node_Expression_Constant) {
$compiler->write("\$req->". $pk. "=". $pv['value']. ";\n");
}
}
// execute requested app
$compiler->write("\$vservice = new ThemeService(\$req);\n")
->write("\$aether_result = \$vservice->service();\n");
switch ($withX) {
case VApp::BY_OBJ:
$compiler->write("\$context['". $with. "'] = \$aether_result;\n");
break;
case VApp::BY_TPL:
$compiler->write("echo \$aether_result;\n");
break;
}
}
}
?>
Twigでタグを作成する場合、それも特にNodeクラスを作成する場合には、
キャッシュしておくとcompile結果がキャッシュ・ファイルに出力されるので
compile結果がPHPとして正しく動作するか、
組み込むフレームワークとの連動が正しく行われるか、
実行結果がただしく画面表示されるか、
というようにソースコードのレベルでチェックできると思います。
このように、独自でタグを作成する場合には、
詳述しませんが、
・Tokenクラス
・TokenStreamクラス
・Nodeクラス
・Node/Expression/以下の、NodeExpressionクラス
などのソースを熟読することをお勧めしておきます。
0 件のコメント:
コメントを投稿
どうかお気軽にコメント頂ければ幸いです。