zeptech/code-templates

PHP 代码生成支持

3.0.0 2014-08-26 19:36 UTC

README

PHP 代码模板(pct)是一个 PHP 库,它可以解析并执行 PHP 类模板的值替换。

创建模板

定义一个模板涉及到创建一个文件,该文件封装了一个常见的结构,并用标记表示应该替换值的位置,以便创建一个可以执行(在代码的情况下)或提供有用内容的具体文件。PCT 支持简单的标签替换以及条件和重复部分。

替换标签

为了指定模板中要替换值的点,添加一个具有以下语法的标签

SUBSTITUTION_TAG        <- '/*#' SUBSTITUTION_EXPRESSION '#*/'
SUBSTITUTION_EXPRESSION <- (FILTER_EXPRESSION ('-' FILTER_EXPRESSION)* ':')?VARIABLE_NAME
FILTER_EXPRESSION       <- [a-zA-Z]+ ( '(' FILTER_PARAMETER (',' FILTER_PARAMETER)* ')' )?
FILTER_PARAMETER        <- .+
VARIABLE_NAME           <- [a-zA-Z]+ ('[' VARIABLE_INDEX ']')*
VARIABLE_INDEX          <- [a-zA-Z]+

注意,元素之间包含的空白仅用于清晰,不应包含在编写标签时。唯一例外的是,可以在 /*# 开放和 #*/ 关闭之间出现任意数量的空白。

示例

  • /*# var #*/ -- 输出替换值 var
  • /*# php:var #*/ -- 使用 php 过滤器输出替换值 var
  • /*# join(,):var #*/ -- 使用逗号作为粘合剂使用 join 过滤器输出替换值 var
  • /*# each(php)-join(,):var #*/ -- 将 var 的每个值通过 php 过滤器传递,然后使用逗号连接结果值
  • /*# var[idx] #*/ -- 输出数组值 varidx
  • /*# var[idx][subidx] #*/ -- 也支持嵌套索引

过滤器

有一些预定义的过滤器用于输出替换值。

  • each(filter) 将过滤器应用于数组替换值的每个值。
  • json 将替换值编码为 JSON 格式输出。
  • join(glue) 使用 glue 连接数组的所有值,并输出结果。
  • php 使用 var_export 输出替换值。
  • xml 使用编码的 XML 实体输出替换值。注意,这不会将数组或对象序列化为 XML。

可以使用 '-' 字符连接多个过滤器表达式来堆叠过滤器。以这种方式定义的过滤器将从左到右评估,每个过滤器接收前一个过滤器的结果作为输入。

条件部分

模板支持条件部分。只有在替换值的集合与条件表达式匹配时,这些部分才会输出。条件部分有以下的语法

IF_EXPRESSION   <- '#{ if' CONDITIONAL '\n' BLOCK
                   (('#{' / '#}{') ' elseif' CONDITIONAL '\n' BLOCK)*
                   (('#{' / '#}{') ' else' '\n' BLOCK)?
                   '#}'
CONDITIONAL     <- COMPARISON (('and' / 'or') COMPARISON)*
COMPARISON      <- OPERAND (UNARY_OPERATOR / BINARY_OPERATOR OPERAND)?
OPERAND         <- VARIABLE_NAME / NUMBER / STRING
VARIABLE_NAME   <- [a-zA-Z]+ ('[' VARIABLE_INDEX ']')*
VARIABLE_INDEX  <- [a-zA-Z]+
NUMBER          <- Any numeric string*
STRING          <- ('\'' / '"') .* ('\'' / '"')
BINARY_OPERATOR <- '=' / '!=' / '<' / '>' / '<=' / '>=' /
UNARY_OPERATOR  <- 'ISSET' / 'ISNOTSET'
BLOCK           <- PHP code with addition template syntax

当比较定义为没有操作符的单一 OPERAND 时,解析的操作数的布尔值将用于解决 IF_EXPRESSION

is_numeric 定义。

示例

#{ if var[type] = 'list'
    // Handle a list
#}{ elseif var[type] = 'map'
   // Handle a map
#}{ else
   // Handle non-collection type
#}

switch 块

除了 if 风格的条件部分外,还可以使用 switch 部分为相同的替换变量的不同值替换不同的内容。

示例

#{ switch var
#| case 0
    // Handle case when var = 0
#| case > 0
    // Handle case when var > 0
#| case < 0
    // Handle case when var < 0
#}

if 块的示例可以重写为

#{ switch var[type]
#| case 'list'
    // Handle a list
#| case 'map'
    // Handle a map
#| default
    / Handle non-collection type
#}

switch 情况不会 穿透

重复部分

可以按照以下方式指定重复部分

#{ each <var> as <name> [<status>]
  <section>
#}

<var> 必须引用一个数组替换值。在重复部分中,可以使用具有名称 <name> 的替换值来使用数组替换值的当前值。

如果为 <status> 变量提供了名称,则它将被填充为一个包含以下信息的数组

  • index:当前迭代的索引。
  • first:迭代是否在第一个元素上。
  • last:迭代是否在最后一个元素上。
  • has_next:迭代是否在当前元素之后还有其他元素。

示例

此示例是用于在 Clarinet ORM 项目 中为模型类创建持久化对象的模板的一部分。完整的模板可以在 https://github.com/pgraham/Clarinet/blob/master/src/persister/persister.tmlp.php 找到。

class /*# actor #*/ {

    // ...

    public function create(\/*# class #*/ $model) {

      if (!$this->validator->validate($model, $e)) {
        throw $e;
      }

      if ($model->get/*# id_property */() !== null) {
        return $model->get/*# id_property */();
      }

      #{ if beforeCreate
        $model->beforeCreate();
      #}

      try {
        $startTransaction = $this->_pdo->beginTransaction();

        $model->set/*# id_property */(self::CREATE_MARKER);

        $params = Array();
        #{ each properties as prop
          #{ if prop[type] = boolean
            $params['/*# prop[col] #*/'] = $model->get/*# prop[name] #*/() ? 1 : 0;
          #{ else
            $params['/*# prop[col] #*/'] = $model->get/*# prop[name] #*/();
          #}
        #}

        #{ each relationships as rel
          #{ if rel[type] = many-to-one
            // Populate /*# rel[rhs] #*/ parameter --------------------------------
            // ...
            // -------------------------------------------------------------------
          #}
        #}

        $this->_create->execute($params);

        $id = $this->transformer->idFromDb($this->_pdo->lastInsertId());
        $model->set/*# id_property */($id);
        $this->_cache[$id] = $model;

        #{ each collections as col
          $this->insertCollection_/*# col[property] #*/(
            $id,
            $model->get/*# col[property] #*/()
          );
        #}

        #{ each relationships as rel
          $related = $model->get/*# rel[lhsProperty] #*/();
          #{ if rel[type] = many-to-many
            // ...

          #{ elseif rel[type] = one-to-many
            // ...
          #}
        #}

        if ($startTransaction) {
          $this->_pdo->commit();
        }

        #{ if onCreate
          $model->onCreate();
        #} 

        return $id;
      } catch (PDOException $e) {
        $this->_pdo->rollback();
        $model->set/*# id_property */(null);

        $e = new PdoExceptionWrapper($e, '/*# class #*/');
        $e->setSql($sql, $params);
        throw $e;
      }
    }

    // ...

}

值替换

值替换,在此之后称为模板解析(或更简单地说,解析)是指用值替换所有模板标记的过程,以便代码模板成为实际有用的代码片段。

可以使用 zpt\pct\TemplateResolver 实例解析模板,并调用其 resolve($templatePath, $outputpath, $values); 方法。