ryunosuke/night-dragon

简单的原生模板引擎

v2.0.2 2024-08-09 12:03 UTC

README

描述

这是一个简单的模板引擎,它保留了php素材的原味。以下是其功能与特性:

  • (因为是raw php,所以)可以使用php实现任何事情
  • (原则上)raw,因此速度极快
  • <?= $string ?> 会自动进行html转义
  • <?= $string ?> 的最后换行符不会被删除
  • <?= $string | strtoupper ?> 会将 strtoupper($string) 转换(可以嵌套)
  • <?= $string & strtoupper ?> 当 $string 非空时将其转换为 strtoupper($string)(可以嵌套)
  • <?= $array->key ?> 会转换为 $array['key'](可以嵌套)
  • 根据分配的变量的类型信息,会自动嵌入 /** @var \Hoge $hoge */
  • 支持模板继承

然而,以下一些模板引擎的功能并不存在。

  • 指定模板目录(始终使用完整路径指定)
    • 但是,在模板内部,可以使用相对路径指定,因此只需要在最初的大模板时指定。
  • 检查模板更新日期的功能
    • 目前还没有必要,所以没有实现。
  • 指定扩展名(始终包括扩展名指定)
    • 由于是raw,固定扩展名的好处特别不明显(phtml, php 等各有偏好)
  • 非文件数据源
    • 由于php有强大的流包装器,因此无需单独处理
  • 插件机制
    • 大多数情况下,只需定义辅助函数即可,并且可以分配给 <?= $plugin->func() ?> 来调用,因此无需此功能

安装

{
    "require": {
        "ryunosuke/night-dragon": "dev-master"
    }
}

规范

以下是一个包含所有功能的模板示例。使用了继承功能。

layout.phtml
<html lang="ja">
<head>
    <title><?= $title ?> - <?= $SiteName ?></title>
</head>
<body>
<?php $this->begin('main') ?>
これは子テンプレートから渡された変数です:<?= $childvar ?>
<?php $this->end() ?>
</body>
</html>
action.phtml
<?php $this->extend(__DIR__ . '/layout.phtml', [
    'title'    => 'PageTitle',
    'childvar' => 'ChildVar',
]) ?>

<?php $this->begin('main') ?>
<section>
    <h2>親・子コンテンツの関係</h2>
    親テンプレートに渡した変数は使えません:<?= $childvar ?? 'not defined' ?>
    parent メソッドを使用して親コンテンツを表示します。
    <?php $this->parent() ?>

    これは子供コンテンツです。
    色々変数を表示しています。
</section>

<section>
    <h2>オートエスケープ</h2>
    これはただの文字列表示です(デフォルトで html エスケープされます):<?= "this is $string" ?>
    ショート echo タグではなく、php タグはエスケープされません:<?php echo "this is $string" ?>
    ただの php タグは改行もされません(php のデフォルトです。ショート echo タグを使うとその挙動を抑制できます)。
    これは自動エスケープの無効化です(@をつけると生出力になります):<?= @"this is $string" ?>
</section>

<section>
    <h2>修飾子機能</h2>
    これは修飾子機能です("|" でパイプ演算子のような挙動になります):<?= $string | strtoupper ?>
    修飾子は繋げられるし、 $_ という特殊変数を使うと任意引数位置に適用できます:<?= $string | strtoupper ?? 'default' | str_replace('TITLE', 'subject', $_) ?>
    登録しておけば静的メソッドも呼べます:<?= $float | number ?>
    引数付きです:<?= $float | number(3) ?>

    & は基本的に | と同じですが、値が null の場合にスルーされます(ぼっち演算子みたいなものです):<?= $null & number ?? 'default' ?>
    引数付きです:<?= $float & number(3) ?? 'default' ?>

    | と & は混在可能です:<?= $null & number | is_null | var_export ?>
</section>

<section>
    <h2>配列・オブジェクトアクセス</h2>
    これは配列のアクセスです("->" で配列アクセスできます):<?= $array->hoge ?>
    配列アクセスはネストできます:<?= $array->fuga->x ?>
    "?->" で nullsafe アクセスができます:<?= $array?->undefined1?->undefined2 ?? 'default' ?>
    オブジェクトもアクセスできます:<?= $object->hoge ?>
    配列とオブジェクトは混在して OK です:<?= $object->fuga->x ?>
    埋め込み構文も使えます1:<?= "prefix-{$object->fuga->x}-suffix" ?>
    埋め込み構文も使えます2:<?= "prefix-{$closure(strtolower($object->fuga->x))}-suffix" ?>
    このように ?? 演算子とも併用できます:<?= $array->undefined ?? 'default' ?>
    オブジェクトも可能できます。共にネストも出来ます:<?= $object->undefined1->undefined2 ?? 'default' | strtoupper ?>

    上記2つの機能は「配列アクセス -> 修飾子」のときのみ組み合わせ可能です:<?= $array->fuga | implode(',', $_) ?>
    右記のような順番の組み合わせはできません:<?= @"<?=" ?> $string | str_split->z <?= @"?>" ?>
</section>

<section>
    <h2>埋め込み構文</h2>
    `` の中で ${} を使用するとその中で式の展開や定数の埋め込みが可能になります。
    式の展開:<?= `this is $float / 1000 + 5:${$float / 1000 + 5}` ?>
    定数の埋め込み:<?= `ArrayObject::ARRAY_AS_PROPS:${\ArrayObject::ARRAY_AS_PROPS}` ?>
</section>

<section>
    <h2>キャプチャ・サブテンプレート</h2>
    Smarty でいう capture のような機能はありませんが、所詮素の php なので ob_ 系を使用することで簡単に模倣できます。
    <?php ob_start() ?>
    <blockquote>
        これはキャプチャー中の変数表示です:<?= $string | strtoupper ?>
    </blockquote>
    <?php $buffer = ob_get_clean() ?>
    キャプチャ結果を呼び出します。<?= @$buffer ?>

    Smarty でいう function のような機能はありませんが、所詮素の php なのでクロージャを使用することで簡単に模倣できます。
    <?php $template = function ($arg1, $arg2) { ?>
        <blockquote>
            arg1+arg2: <?= $arg1 | strtoupper ?> <?= $arg2 | strtolower ?>
        </blockquote>
    <?php } ?>
    テンプレートを引数付きで呼び出します。<?= @$template("Hello's", 'World') ?>

    上記2つの機能は利便性が高く、使用頻度もそれなりのため、 組み込みの機能として実装する予定はあります。
</section>

<section>
    <h2>カスタムタグ</h2>
    タグのコールバックを登録すると特定タグに対してコールバックが実行されます。
    例えばデフォルトでは strip タグが登録されていて、空白を除去できます(Smarty の {strip} に相当) 。
    <strip>
        このタグ内の空白はすべて除去されます。ただし、変数の中身には関与しません。
        <div
            id="stripping"
            class="hoge fuga piyo"
        >
            <?= $multiline ?>
        </div>
    </strip>
</section>

<section>
    <h2>ショートタグ</h2>
    上記の構文の一部はショートオープンタグ内でも限定的に使えます。
    これらの機能は compatibleShortTag を true にすると php 本体で short_open_tag が無効にされていても使用できます(詳細は README にて)。

    foreach でアクセス子が使える
    <? foreach ($array->fuga as $key => $value): ?>
        <?= $value ?>
    <? endforeach ?>

    if で アクセス子が使える
    <? if (empty($array->undefined)): ?>
        $object.undefined is undefined
    <? endif ?>
</section>

おまけ:所詮素の php なのであらゆる表現が可能です。
<?php foreach ($array as $key => $value): ?>
    <?php if ($key === 'hoge'): ?>
        <?php echo "$value です"; ?>
        <?php echo "ショートタグが使いたいなぁ"; ?>
    <?php endif ?>
<?php endforeach ?>

<?php $this->end() ?>

乍一看代码可能不明所以,但这完全是一段有效的php代码,并且可以完全接受IDE的支持。

将其渲染后,源代码会在内部被转换为以下形式。

layout.phtml
<html lang="ja">
<head>
    <title><?=\ryunosuke\NightDragon\Renderer::html($title)?> - <?=\ryunosuke\NightDragon\Renderer::html($SiteName)?></title>
</head>
<body>
<?php $this->begin('main') ?>
これは子テンプレートから渡された変数です:<?=\ryunosuke\NightDragon\Renderer::html($childvar),"\n"?>
<?php $this->end() ?>
</body>
</html>
action.phtml
<?php $this->extend(__DIR__ . '/layout.phtml', [
    'title'    => 'PageTitle',
    'childvar' => 'ChildVar',
]) ?>

<?php $this->begin('main') ?>
<section>
    <h2>親・子コンテンツの関係</h2>
    親テンプレートに渡した変数は使えません:<?=\ryunosuke\NightDragon\Renderer::html($childvar ?? 'not defined'),"\n"?>
    parent メソッドを使用して親コンテンツを表示します。
    <?php $this->parent() ?>

    これは子供コンテンツです。
    色々変数を表示しています。
</section>

<section>
    <h2>オートエスケープ</h2>
    これはただの文字列表示です(デフォルトで html エスケープされます):<?=\ryunosuke\NightDragon\Renderer::html("this is $string"),"\n"?>
    ショート echo タグではなく、php タグはエスケープされません:<?php echo "this is $string" ?>
    ただの php タグは改行もされません(php のデフォルトです。ショート echo タグを使うとその挙動を抑制できます)。
    これは自動エスケープの無効化です(@をつけると生出力になります):<?="this is $string","\n"?>
</section>

<section>
    <h2>修飾子機能</h2>
    これは修飾子機能です("|" でパイプ演算子のような挙動になります):<?=\ryunosuke\NightDragon\Renderer::html(strtoupper($string)),"\n"?>
    修飾子は繋げられるし、 $_ という特殊変数を使うと任意引数位置に適用できます:<?=\ryunosuke\NightDragon\Renderer::html(str_replace('TITLE','subject',strtoupper($string) ?? 'default')),"\n"?>
    登録しておけば静的メソッドも呼べます:<?=\ryunosuke\NightDragon\Renderer::html(\Modifier::number($float)),"\n"?>
    引数付きです:<?=\ryunosuke\NightDragon\Renderer::html(\Modifier::number($float,3)),"\n"?>

    & は基本的に | と同じですが、値が null の場合にスルーされます(ぼっち演算子みたいなものです):<?=\ryunosuke\NightDragon\Renderer::html(((${"\0"}=$null) === null ? ${"\0"} : \Modifier::number(${"\0"})) ?? 'default'),"\n"?>
    引数付きです:<?=\ryunosuke\NightDragon\Renderer::html(((${"\0"}=$float) === null ? ${"\0"} : \Modifier::number(${"\0"},3)) ?? 'default'),"\n"?>

    | と & は混在可能です:<?=\ryunosuke\NightDragon\Renderer::html(var_export(is_null(((${"\0"}=$null) === null ? ${"\0"} : \Modifier::number(${"\0"}))))),"\n"?>
</section>

<section>
    <h2>配列・オブジェクトアクセス</h2>
    これは配列のアクセスです("->" で配列アクセスできます):<?=\ryunosuke\NightDragon\Renderer::html(\ryunosuke\NightDragon\Renderer::access($array,[false,'hoge'])),"\n"?>
    配列アクセスはネストできます:<?=\ryunosuke\NightDragon\Renderer::html(\ryunosuke\NightDragon\Renderer::access($array,[false,'fuga'],[false,'x'])),"\n"?>
    "?->" で nullsafe アクセスができます:<?=\ryunosuke\NightDragon\Renderer::html(@\ryunosuke\NightDragon\Renderer::access($array,[true,'undefined1'],[true,'undefined2']) ?? 'default'),"\n"?>
    オブジェクトもアクセスできます:<?=\ryunosuke\NightDragon\Renderer::html(\ryunosuke\NightDragon\Renderer::access($object,[false,'hoge'])),"\n"?>
    配列とオブジェクトは混在して OK です:<?=\ryunosuke\NightDragon\Renderer::html(\ryunosuke\NightDragon\Renderer::access($object,[false,'fuga'],[false,'x'])),"\n"?>
    埋め込み構文も使えます1:<?=\ryunosuke\NightDragon\Renderer::html("prefix-".\ryunosuke\NightDragon\Renderer::access($object,[false,'fuga'],[false,'x'])."-suffix"),"\n"?>
    埋め込み構文も使えます2<?=\ryunosuke\NightDragon\Renderer::html("prefix-{$closure(strtolower(\ryunosuke\NightDragon\Renderer::access($object,[false,'fuga'],[false,'x'])))}-suffix"),"\n"?>
    このように ?? 演算子とも併用できます:<?=\ryunosuke\NightDragon\Renderer::html(@\ryunosuke\NightDragon\Renderer::access($array,[false,'undefined']) ?? 'default'),"\n"?>
    オブジェクトも可能できます。共にネストも出来ます:<?=\ryunosuke\NightDragon\Renderer::html(strtoupper(@\ryunosuke\NightDragon\Renderer::access($object,[false,'undefined1'],[false,'undefined2']) ?? 'default')),"\n"?>

    上記2つの機能は「配列アクセス -> 修飾子」のときのみ組み合わせ可能です:<?=\ryunosuke\NightDragon\Renderer::html(implode(',',\ryunosuke\NightDragon\Renderer::access($array,[false,'fuga']))),"\n"?>
    右記のような順番の組み合わせはできません:<?="<?="?> $string | str_split->z <?="?>","\n"?>
</section>

<section>
    <h2>埋め込み構文</h2>
    `` の中で ${} を使用するとその中で式の展開や定数の埋め込みが可能になります。
    式の展開:<?=\ryunosuke\NightDragon\Renderer::html("this is $float / 1000 + 5:".($float / 1000 + 5).""),"\n"?>
    定数の埋め込み:<?=\ryunosuke\NightDragon\Renderer::html("ArrayObject::ARRAY_AS_PROPS:".(\ArrayObject::ARRAY_AS_PROPS).""),"\n"?>
</section>

<section>
    <h2>キャプチャ・サブテンプレート</h2>
    Smarty でいう capture のような機能はありませんが、所詮素の php なので ob_ 系を使用することで簡単に模倣できます。
    <?php ob_start() ?>
    <blockquote>
        これはキャプチャー中の変数表示です:<?=\ryunosuke\NightDragon\Renderer::html(strtoupper($string)),"\n"?>
    </blockquote>
    <?php $buffer = ob_get_clean() ?>
    キャプチャ結果を呼び出します。<?=$buffer,"\n"?>

    Smarty でいう function のような機能はありませんが、所詮素の php なのでクロージャを使用することで簡単に模倣できます。
    <?php $template = function ($arg1, $arg2) { ?>
        <blockquote>
            arg1+arg2: <?=\ryunosuke\NightDragon\Renderer::html(strtoupper($arg1))?> <?=\ryunosuke\NightDragon\Renderer::html(strtolower($arg2)),"\n"?>
        </blockquote>
    <?php } ?>
    テンプレートを引数付きで呼び出します。<?=$template("Hello's", 'World'),"\n"?>

    上記2つの機能は利便性が高く、使用頻度もそれなりのため、 組み込みの機能として実装する予定はあります。
</section>

<section>
    <h2>カスタムタグ</h2>
    タグのコールバックを登録すると特定タグに対してコールバックが実行されます。
    例えばデフォルトでは strip タグが登録されていて、空白を除去できます(Smarty の {strip} に相当) 。
    このタグ内の空白はすべて除去されます。ただし、変数の中身には関与しません。 <div id="stripping" class="hoge fuga piyo"><?=\ryunosuke\NightDragon\Renderer::html($multiline)?></div>
</section>

<section>
    <h2>ショートタグ</h2>
    上記の構文の一部はショートオープンタグ内でも限定的に使えます。
    これらの機能は compatibleShortTag を true にすると php 本体で short_open_tag が無効にされていても使用できます(詳細は README にて)。

    foreach でアクセス子が使える
    <?php foreach (\ryunosuke\NightDragon\Renderer::access($array,[false,'fuga']) as $key => $value): ?>
        <?=\ryunosuke\NightDragon\Renderer::html($value),"\n"?>
    <?php endforeach ?>

    if で アクセス子が使える
    <?php if (!@boolval(\ryunosuke\NightDragon\Renderer::access($array,[false,'undefined']))): ?>
        $object.undefined is undefined
    <?php endif ?>
</section>

おまけ:所詮素の php なのであらゆる表現が可能です。
<?php foreach ($array as $key => $value): ?>
    <?php if ($key === 'hoge'): ?>
        <?php echo "$value です"; ?>
        <?php echo "ショートタグが使いたいなぁ"; ?>
    <?php endif ?>
<?php endforeach ?>

<?php $this->end() ?>

另外,如果存在 <?php # meta template data ?> 这样的注释,则会将模板本身也进行修改(后述)。具体来说,将“使用的变量信息”“常数信息”等作为元信息写入。这样可以最大限度地利用phpstorm的跳转和补全功能。

此外,上述内容被包含后,最终的渲染结果如下。

渲染结果
<html lang="ja">
<head>
    <title>PageTitle - サイト名</title>
</head>
<body>
<section>
    <h2>親・子コンテンツの関係</h2>
    親テンプレートに渡した変数は使えません:not defined
    parent メソッドを使用して親コンテンツを表示します。
    これは子テンプレートから渡された変数です:ChildVar

    これは子供コンテンツです。
    色々変数を表示しています。
</section>

<section>
    <h2>オートエスケープ</h2>
    これはただの文字列表示です(デフォルトで html エスケープされます):this is This&#039;s Title
    ショート echo タグではなく、php タグはエスケープされません:this is This's Title    ただの php タグは改行もされません(php のデフォルトです。ショート echo タグを使うとその挙動を抑制できます)。
    これは自動エスケープの無効化です(@をつけると生出力になります):this is This's Title
</section>

<section>
    <h2>修飾子機能</h2>
    これは修飾子機能です("|" でパイプ演算子のような挙動になります):THIS&#039;S TITLE
    修飾子は繋げられるし、 $_ という特殊変数を使うと任意引数位置に適用できます:THIS&#039;S subject
    登録しておけば静的メソッドも呼べます:12,346
    引数付きです:12,345.679

    & は基本的に | と同じですが、値が null の場合にスルーされます(ぼっち演算子みたいなものです):default
    引数付きです:12,345.679

    | と & は混在可能です:true
</section>

<section>
    <h2>配列・オブジェクトアクセス</h2>
    これは配列のアクセスです("->" で配列アクセスできます):HOGE
    配列アクセスはネストできます:X
    "?->" で nullsafe アクセスができます:default
    オブジェクトもアクセスできます:HOGE
    配列とオブジェクトは混在して OK です:X
    埋め込み構文も使えます1:prefix-X-suffix
    埋め込み構文も使えます2:prefix-closurex-suffix
    このように ?? 演算子とも併用できます:default
    オブジェクトも可能できます。共にネストも出来ます:DEFAULT

    上記2つの機能は「配列アクセス -> 修飾子」のときのみ組み合わせ可能です:X,Y,Z
    右記のような順番の組み合わせはできません:<?= $string | str_split->z ?>
</section>

<section>
    <h2>埋め込み構文</h2>
    `` の中で ${} を使用するとその中で式の展開や定数の埋め込みが可能になります。
    式の展開:this is 12345.6789 / 1000 + 5:17.3456789
    定数の埋め込み:ArrayObject::ARRAY_AS_PROPS:2
</section>

<section>
    <h2>キャプチャ・サブテンプレート</h2>
    Smarty でいう capture のような機能はありませんが、所詮素の php なので ob_ 系を使用することで簡単に模倣できます。
        キャプチャ結果を呼び出します。    <blockquote>
        これはキャプチャー中の変数表示です:THIS&#039;S TITLE
    </blockquote>
    

    Smarty でいう function のような機能はありませんが、所詮素の php なのでクロージャを使用することで簡単に模倣できます。
        テンプレートを引数付きで呼び出します。        <blockquote>
            arg1+arg2: HELLO&#039;S world
        </blockquote>
    

    上記2つの機能は利便性が高く、使用頻度もそれなりのため、 組み込みの機能として実装する予定はあります。
</section>

<section>
    <h2>カスタムタグ</h2>
    タグのコールバックを登録すると特定タグに対してコールバックが実行されます。
    例えばデフォルトでは strip タグが登録されていて、空白を除去できます(Smarty の {strip} に相当) 。
    このタグ内の空白はすべて除去されます。ただし、変数の中身には関与しません。 <div id="stripping" class="hoge fuga piyo">line1
line2
line3</div>
</section>

<section>
    <h2>ショートタグ</h2>
    上記の構文の一部はショートオープンタグ内でも限定的に使えます。
    これらの機能は compatibleShortTag を true にすると php 本体で short_open_tag が無効にされていても使用できます(詳細は README にて)。

    foreach でアクセス子が使える
            X
            Y
            Z
    
    if で アクセス子が使える
            $object.undefined is undefined
    </section>

おまけ:所詮素の php なのであらゆる表現が可能です。
            HOGE です        ショートタグが使いたいなぁ        
</body>
</html>

功能

原则上将 <?= ?> 作为目标进行替换。另外,为了便利性,也将 <? ?> 标签作为替换目标。

不会替换 <?php ?>

<? ?> 具体来说,可以写入 <? foreach ($array->member as $key => $value): ?><? if ($array->flag): ?>。但是,这是一个实验性功能,目前只有这种数组访问功能有效(修饰符无法使用)。

此外,<? ?> 也是一个不推荐的功能。这是因为 <? ?> 本身有在php本体中变得不被推荐的趋势,因此处理依赖于 token_get_all,如果将来被废弃,则可能无法正常工作。另外,token_get_all 受ini中 short_open_tag 设置的影响,因此根据环境的不同,其行为也会有所不同。

可以通过后面的选项不依赖于 short_open_tag 设置来使用短标签,但这是一个相当激进的特性。

访问键

可以使用 <?= $array->key > 这种形式来访问数组或对象。这是可以嵌套的,也可以混合使用数组或对象。

key 部分可以使用的是字面字符串、字面数字、单个变量。不能使用表达式或通常的 [] 访问。

还可以混合使用 ?? 运算符。但是,如果指定了 defaultGetter,则将使用基于函数的访问,因此将回退到使用 @ 进行错误抑制(因为无法在语法层面上模仿基于函数的 ??,所以行为本身没有变化)。动作本身没有变化。

  • OK
    • <?= $array->key1->key2 ?> (简单的嵌套)
    • <?= $array->3 ?> (字面数字)
    • <?= $array->$key ?> (单个变量)
    • <?= "prefix-{$array->key1->key2}-suffix" ?> (嵌入结构)
    • <?= $array?->undefined ?? 'default' ?> (?->, ?? 运算符的混合使用)
  • NG
    • <?= $array->PATHINFO_EXTENSION ?> (常数)
    • <?= $array['key1']->key2 ?> ([] 混合使用)
    • <?= $array->strtoupper($key) ?> (表达式)

修饰符

可以使用 <?= $value | funcname ?> 这种形式进行函数应用,就像管道一样。这是可以嵌套的,也可以控制应用到的参数位置。

可以使用 $_ 这个接收器变量来指定参数位置。 $_ 会表现出将左边的值赋入的行为。

如果没有 $_,则将其应用到第一个参数上。如果只需应用到第一个参数上,则可以省略函数调用的 ()

原则上,修饰符只能使用单个函数。虽然有限制,但支持命名空间函数和静态方法。不能使用除了字符串以外的可调用形式。

也可以使用 & 代替 |。使用 & 时,如果值是 null,则不会应用,并保留 null。例如,如果 <?= $null | number_format ?? "null です" ?>,则输出将是“0”。这通常是希望避免的情况。相反,如果使用 <?= $null & number_format ?? "null です" ?>,则输出将是“null です”。

  • OK
    • <?= $value | funcname($_) ?> (=funcname($value): 简单调用)
    • <?= $value | funcname ?> (=funcname($value): 第一个参数,因此可以省略 $_)
    • <?= $value | funcname(3) ?> (=funcname($value, 3): $_ 会自动应用到第一个参数上)
    • <?= $value | funcname(3, $_) ?> (即 funcname(3, $value):将第2个参数应用给 $value)
    • <?= $value | funcname($_, $_) ?> (即 funcname($value, $value)$_ 可重复使用)
    • <?= $value | funcname(3, "pre-{$_}-fix") ?> (即 funcname(3, "pre-{$value}-fix")$_ 可像普通变量一样使用)
    • <?= $value | funcname1 | funcname2 | funcname3 ?> (即 funcname3(funcname2(funcname1($value))):修饰符可嵌套,每个阶段都可以使用上述所有语法)
    • <?= $value | funcname1 & funcname2 & funcname3 ?> (即 funcname3nully(funcname2nully(funcname1($value)))|& 可混用)
    • <?= $value | \namespace\func ?> (即 \namespace\func($value):绝对指定命名空间函数调用)
    • <?= $value | subspace\func ?> (即 \filespace\subspace\func($value):文件命名空间中的相对调用)
    • <?= $value | func ?> (即 \defaultNamespace\func($value):使用 defaultNamespace 预先注册的命名空间中的函数调用)
    • <?= $value | method ?> (即 \defaultClass::method($value):使用 defaultClass 预先注册的类的静态方法调用)
  • NG
    • <?= $value | undefined ?> (未定义函数。语法上无错误,但不能调用)
    • <?= $value | class::method ?> (方法形式不可调用)
    • <?= $value | $callable ?> (变量中存储的 callable 可调用,但不是正式规范)

自动过滤

<?= $string ?> 会自动应用 HTML 转义。这仅针对短 echo 标签。后面的 nofilter 选项也可以表示不转义的短标签。

另外,<?= ?> 后的换行符将被保留(PHP 的标准行为是删除换行符(确切地说,换行符包含在结束标签中))。

  • 转义
    • <?= $string ?> (普通标签)
    • <?= ucfirst($string) ?> (表达式)
    • <?= $item->children | implode(',', $_) ?> (与键访问或修饰符结合使用)
  • 不转义
    • <?php echo $string ?><?php 标签)
    • <?= @$string ?>nofilter 抑制)

展开变量

` 内可以使用 ${} 嵌入语法。

PHP 的变量展开可以做很多事情,但有一个奇怪的规范,那就是“输出表达式的结果”而不是“输出表达式结果变量的值”。这种规范在很多情况下都是不必要的(或者说是不方便的),因此可以抑制它。此外,${} 内可以使用上述修饰符或数组访问语法。

  • 表达式嵌入
    • <?= `this is n + 1: ${$n + 1}` ?> (计算表达式)
    • <?= `this is class constant: ${Class::constant}` ?> (类常量)
    • <?= `this is json value: ${json_encode([1,2,3])}` ?> (函数调用)
    • <?= `this is json value: ${[1,2,3] | json_encode}` ?> (修饰符)

然而,这种仅仅用于显示的用法,只需将文本部分直接放在 PHP 标签外部即可轻松实现。真正发挥作用的是以下变量声明或参数的情况。

  • 变量代入或参数使用
    • <? $tmp = `this is n + 1: ${$n + 1}` ?> (变量代入)
    • <?= ucfirst(`this is n + 1: ${$n + 1}`) ?> (函数的参数)

此外,此功能不仅针对短 echo 标签,也针对 <? ?>。由于在模板文件中使用反引号(shell 调用)的情况几乎为零,“存在反引号则展开表达式”的假设几乎成立。

用法

选项

$renderer = new \ryunosuke\NightDragon\Renderer([
    // デバッグ系
    'debug'              => $debug,
    'errorHandling'      => $debug,
    'gatherVariable'     => $debug ? self::DECLARED | self::FIXED | self::GLOBAL | self::ASSIGNED | self::USING : 0,
    'gatherModifier'     => $debug,
    'gatherAccessor'     => $debug,
    'constFilename'      => null,
    'typeMapping'        => [],
    'specialVariable'    => [],
    // インジェクション系
    'wrapperProtocol'    => Renderer::DEFAULT_PROTOCOL,
    'templateClass'      => Template::class,
    // ディレクトリ系
    'compileDir'         => null,
    // コンパイルオプション系
    'customTagHandler'   => [
        'strip' => Renderer::class . '::strip',
    ],
    'compatibleShortTag' => false,
    'defaultNamespace'   => '\\',
    'defaultClass'       => '',
    'defaultFilter'      => Renderer::class . '::html',
    'defaultGetter'      => Renderer::class . '::access',
    'defaultCloser'      => "\n",
    'nofilter'           => '@',
    'varModifier'        => ['|', '&'],
    'varReceiver'        => '$_',
    'varAccessor'        => '->',
    'varExpander'        => '`',
]);

$renderer->assign([
    // グローバルにアサインする変数
    'SiteName' => 'サイト名',
]);

echo $renderer->render(__DIR__ . '/action.phtml', [
    // テンプレートにアサインする変数
    'null'      => null,
    'float'     => 12345.6789,
    'string'    => "This's Title",
    'multiline' => "line1\nline2\nline3",
    'array'     => ['hoge' => 'HOGE', 'fuga' => ['x' => 'X', 'y' => 'Y', 'z' => 'Z']],
    'object'    => (object) ['hoge' => 'HOGE', 'fuga' => ['x' => 'X', 'y' => 'Y', 'z' => 'Z']],
    'closure'   => function ($arg) { return 'closure' . $arg; },
]);

基本上只有上述用法。这里仅描述用法,详细功能请参考源代码。

调试

调试标志。影响范围较广,请参考源代码获取详细信息。原则上,“开发时为 true,运行时为 false”。

错误处理

使用布尔值指定在模板内发生错误或异常时是否进行处理。启用处理将使模板的前后行显示或更易于阅读。

gatherVariable, gatherModifier, gatherAccessor

模板文件替换选项。在模板文件中插入 <?php # meta template data ?> 这样的注释,将在该位置插入以下此类元信息。

<?php
# meta template data
// @formatter:off
// using variables:
/** @var \ryunosuke\NightDragon\Template $this */
/** @var mixed $_ */
/** @var null $null */
/** @var float $float */
/** @var string $string */
/** @var string $multiline */
/** @var array $array */
/** @var \stdClass $object */
/** @var \Closure $closure */
// using modifier functions:
if (false) {function strtoupper(...$args){define('strtoupper', \strtoupper(...[]));return \strtoupper(...$args);}}
if (false) {function str_replace(...$args){define('str_replace', \str_replace(...[]));return \str_replace(...$args);}}
if (false) {function number(...$args){define('number', \Modifier::number(...[]));return \Modifier::number(...$args);}}
if (false) {function is_null(...$args){define('is_null', \is_null(...[]));return \is_null(...$args);}}
if (false) {function var_export(...$args){define('var_export', \var_export(...[]));return \var_export(...$args);}}
if (false) {function implode(...$args){define('implode', \implode(...[]));return \implode(...$args);}}
if (false) {function strtolower(...$args){define('strtolower', \strtolower(...[]));return \strtolower(...$args);}}
// using array keys:
true or define('hoge', 'hoge');
true or define('fuga', 'fuga');
true or define('undefined', 'undefined');
true or define('undefined1', 'undefined1');
true or define('undefined2', 'undefined2');
// @formatter:on
?>

术语分散,容易混淆,但插入对象是 模板文件。重写后的文件或 compileDir 输出的文件不是。

将 gatherVariable 设置为 true,则将根据模板内使用的变量的“实际赋值类型”动态插入 /** @var Hoge $hoge */。这使得 IDE 的自动完成或跳转可以利用。仅插入已赋值且正在使用的变量(不是随意定义的或未使用的变量)。将不是 true 而是Renderer中定义的常量的位和传递给Renderer,则可以指定类别进行嵌入(此细节将省略)。

将 gatherModifier 设置为 true,则将 <?= $string | strtoupper ?> 中的 strtoupper 声明为常量。这是为了抑制 phpstorm 的警告并定义跳转(实际上调用的是语法,因此可以跳转)。本身没有具体意义。

将 gatherAccessor 设置为 true,则将 <?= $array->key ?> 中的 key 声明为常量。这是为了抑制 phpstorm 的警告。本身没有具体意义。

constFilename

指定文件名,则 gatherModifier 和 gatherAccessor 中输出的常量声明将写入指定的文件。如前所述,常量声明是为了抑制 phpstorm 的警告,不需要与同一文件相同,并且将它们嵌入到活代码中会让人感到不舒服。使用此选项可以将所有常量定义汇总到一个文件中。

内容是追加型的。渲染完项目中的所有模板文件后,应该没有出现未定义常量的警告的模板文件。此外,与完全无关的文件输出,因此不会执行。

主要用于调试。由于项目成员之间容易产生差异,因此建议将其配置在项目中的某个位置,并使用 gitignore 忽略它。

typeMapping

在此处指定 [original => alias] 这样的数组,则可以覆盖 gatherVariable 中插入的类型信息。例如,“实际上类型为 array,但出于某种深奥的原因想将其作为 ArrayObject 处理”。

特殊变量

在这里指定 [$varname => typename] 这种数组,无论模板分配的类型如何,都会强制以指定的类型输出变量名。在公共模板分配的类型可能不同的情况下,可以固定类型。

wrapperProtocol

指定用于php源代码替换的定制流包装器名称。默认为 RewriteWrapper。基本上不需要指定(在必要时指定)。

templateClass

可以从外部注入模板类。 …就像是一个礼物,实用性没有考虑。

在特别想扩展的、不可或缺的情况下使用。

compileDir

替换后的源代码将被存储在编译后的目录中。这不是必需的。未指定时,将使用 sys_get_temp_dir 设置。

如果不存在,则将自动创建。

customTagHandler

html源代码替换的选项。

指定类似于 [tagname => callable] 的数组,则在遇到字符串形式的该标签(数组的键)时将执行回调。回调将接受 (tag content, tag attribute object) 作为参数。

默认已注册 strip。这是一个删除html中的空白(基本上是1行化)的回调。

注册其他示例,如haml或markdown之类的标签,可以在html中部分使用haml或markdown。对于静态部分较多的页面(如FAQ或指南)非常有用。

compatibleShortTag

源代码替换的选项。

将此键设置为true时,无论ini的short_open_tag设置如何,<? ?>标签都将有效。考虑到ini无法更改或short_open_tag被废弃的情况,这是一个安慰选项,因此原则上请设置为false。

defaultNamespace, defaultClass

同样,这是源代码替换的选项。

defaultNamespace是在模板内进行名称解析时指定要搜索和分配的名称空间。始终搜索文件名称空间声明。目前,这仅在搜索<?= $array | hoge ?>中的hoge时使用。如果指定了defaultNamespace并且在该名称空间中定义了hoge函数,则这将被解释为<?= $array | \namespace\hoge ?>

defaultClass是在模板内进行名称解析时指定要搜索和分配的类。目前,这仅在搜索<?= $array | hoge ?>中的hoge时使用。如果指定了defaultClass并且在该类中定义了hoge静态方法,则这将被解释为<?= $array | \classname::hoge ?>

这些可以多次指定。此时的搜索顺序是指定顺序。此外,defaultNamespace和defaultClass的搜索顺序是defaultClass -> defaultNamespace。

defaultFilter, defaultGetter, defaultCloser

同样,这是源代码替换的选项。

defaultFilter是<?= $string ?>中转换的默认过滤器。它必须是一个可变参数的callable,并且必须是字符串(不可为闭包)。

defaultGetter是<?= $array->key ?>时指定键检索转换函数名称。它必须是一个接受array或object作为第一个参数,接受[nullsafe-flag, key string]的字符串callable(不可为闭包)。

defaultCloser是<?= $string ?>时插入的换行符。很少指定,但如果要禁用换行插入,则指定空字符串会很好。

nofilter, varModifier, varReceiver, varAccessor, varExpander

同样,这是源代码替换的选项。

nofilter是<?= $string ?>中转换的默认过滤器无效化的指定字符。例如,指定@<?= @$string ?>中默认过滤器将无效。将空字符串指定为无效功能无效,始终进行转换。

varModifier是<?= $array | implode(',', $_) ?>中的|指定。例如,指定>>则将其指定为<?= $array >> implode(',', $_) ?>。为了保持兼容性,当前默认为|,但本应指定数组。顺序是“简单的管道修饰符符号”“null则不管道修饰符符号”。未来的版本中,['|', '&']将是默认值。将空字符串指定为修饰符功能无效,转换本身将不再进行。

varReceiver是<?= $array | implode(',', $_) ?>中的$_指定。例如,指定$__var则将其指定为<?= $array | implode(',', $__var) ?>。必须指定有效的变量名。

varAccessor是<?= $array->key ?>中的->指定。例如,指定.则将其指定为<?= $array.key ?>。将空字符串指定为访问器功能无效,转换本身将不再进行。

varExpander是<?= `${expression}` ?>中的`指定。由于实现上的原因,可以指定的只有backquote和doublequote。例如,指定"则将其指定为<?= "${expression}" ?>。将空字符串指定为嵌入功能无效,转换本身将不再进行。

Methods

模板内的$this指向自身的模板对象。

在模板内调用$this->extend("parent/template/file")可以执行模板继承。在模板继承中,可以参考父内容的同时部分替换(这是一个常见功能,因此省略了概念说明)。

子模板中,除了块以外的所有顶级声明都被忽略。

下面的描述相当简单,具体来说,我认为最好是看到实际的内容,因此请参考前面的layout.phtml和action.phtml。

$this->extend(string $filename[, array $vars])

指定父以声明模板继承。只有一个父,不能在同一模板中多次继承。

但是,“继承正在继承的模板”是可能的。这就是所谓的“父-子-孙子”的多级继承。

$this->begin(string $blockname)

定义块。定义的块可以在子模板中引用。

$this->end()

声明块的结束。

$this->block(string $blockname[, string $contents])

同时进行块的定义和结束。与“中空的 begin ~ end”同义。

但是,可以提供字符串形式的文本内容。

$this->parent()

在子模板中参考父的内容。

$this->import(string $filename[, array $vars])

读取指定的模板。此方法以模板文件的形式读取。

也就是说,“读取模板文件结果”的方法。

$this->include(string $filename[, array $vars])

读取指定的模板。此方法以模板文件的形式嵌入。

也就是说,“以原样读取模板文件”的方法。(从极端的角度来看,“将其内容粘贴到那里”会产生相同的结果)。

$this->content(string $filename)

读取指定的文件。此方法不进行渲染(用于嵌入js,css等)。

$this->load(string $filename)

根据扩展名指定文件进行读取。例如,指定js文件,则会通过 <script> 标签读取,而php则简单通过 require 读取。如果扩展名与自身相同,则与 import 有相同的行为。

$filename 可以使用 glob 语法指定,所有匹配的文件都会被读取。

注意事项

  • include, extend 等读取会继承调用源变量
    • 反之则不行。另外,include 可以传递赋值变量,但其优先级较高
  • 模板文件名中不能使用 ;
    • 因为它用于替换
  • src/functions.php 中的函数群在调用方中禁止使用
    • 因为它们经常随着版本更新而更改。这些函数群仅适用于此包
  • 使用 use function hoge as fuga 给函数命名别名时,不能调用 | 修饰符的函数名
    • 这是解析的原因
  • | 的原始用法(OR 运算符)在短标签内不能使用
    • 这是解析的原因

问答

  • 问:“不知道怎么用”
    • 答:基本上非常简单,请尝试操作
  • 问:“有很多bug”
    • 答:因为是灵机一动突然创作的…如果有 issue 提交,我会处理
  • 问:“名字听起来很糟糕”
    • 答:好看吗?:-D

自言自语

虽然有些语法上的改变,但“IDE 的亲和性”是指导思想。

例如,将修饰符符号从 | 改为 |> 也不会影响功能,但可能会在 phpstorm 中出现警告。或者,将结构修改为 {} 语法并更改开始和结束标签,功能也会运行,但无法充分利用定义跳转等功能,也无法检测语法错误(如果这么做的话,用 Smarty 会更好)。

在充分利用原生 php 功能的同时,尽量避免引入太多自定义语法是基本概念。

另外,由于最近的 php 本身就是模板引擎,所以我想这种引擎也很有用,这就是开发动机。具体来说

  • ASP 标签已经消失(虽然没怎么用…)
  • 短标签会消失?(最后被否决了,但经常被当作靶子)
  • 默认转义 RFC 被否决(被否决的语法确实很糟糕,但我觉得应该有类似的功能)

等等。

如果宣称是“适合 Web 开发的语言”,并且有简单的模板语法的话,我认为可以引入类似 <?- "html's string" ?> 的语法。相反,如果 php 本体引入了自动转义或管道线运算符,那么这个引擎就不再需要(尽管很遗憾失去了布局功能)。

另外,众所周知的一些模板引擎功能非常强大,但如果只是“想用原生 php 的样子稍微渲染一下”的话,可能就有点过度了。此外,在这种情况下,经常会在视图文件中编写逻辑性 php 代码,因此使用自定义语法可能有些不便。

许可证

MIT