如何通过扩展Twig_Template动态生成Phalcon+AngularJS项目的CRUD/GRID?
基于Phalcon+Twig实现配置驱动的动态CRUD模板
你的思路完全正确——用Phalcon注解读取器提取Model元数据,结合自定义配置动态生成Twig模板,是解决重复模板代码的最优方案。下面针对你的核心疑问给出具体实现方案:
核心疑问解答
1. 能否创建继承Twig_Template的类动态生成模板?
当然可以,但更灵活的方式是使用Twig的StringLoader加载动态生成的模板字符串(无需直接继承底层的Twig_Template)。如果确实需要自定义模板类,也可以继承Twig\Template重写渲染逻辑,但这种方式偏底层,不如字符串模板直观。
2. 如何使用及渲染动态模板?
通过Phalcon注解读取Model元数据→合并自定义配置→生成Twig模板字符串→用StringLoader加载并渲染即可,具体代码见下文。
3. Twig的模板继承功能是否正常工作?
完全没问题!只要动态生成的模板字符串正确使用{% extends %}和{% block %}标签,Twig编译阶段会像处理静态模板一样解析继承关系,和原有功能完全兼容。
具体实现步骤
第一步:给Model添加CRUD相关注解
首先给Model字段添加自定义注解,用来定义列表显示、表单字段等规则:
class InventoryCategory extends ModelBase { /** * @var integer * @Crud\Column(label="ID", sortable=true, visibleInList=true) * @Crud\FormField(type="hidden") */ public $id; /** * @var string * @Crud\Column(label="名称", sortable=true, visibleInList=true) * @Crud\FormField(type="text", required=true, colSize=12) */ public $name; public function initialize() { $this->setSource('inventory_subcategory'); $this::setup(['castOnHydrate' => true]); } }
第二步:编写元数据读取服务
用Phalcon注解读取器提取Model的CRUD配置:
class CrudMetadataService { public function getMetadata(ModelBase $model) { $reader = new \Phalcon\Annotations\Reader(); $reflection = new \Phalcon\Annotations\Reflection($reader->parse(get_class($model))); $metadata = ['columns' => [], 'formFields' => []]; foreach ($model->getProperties() as $property) { $propName = $property->getName(); $annotations = $reflection->getProperty($propName); // 读取列表列配置 if ($annotations->has('Crud\Column')) { $colAnno = $annotations->get('Crud\Column'); $metadata['columns'][$propName] = [ 'label' => $colAnno->getArgument('label'), 'sortable' => $colAnno->getArgument('sortable', true), 'visible' => $colAnno->getArgument('visibleInList', true), ]; } // 读取表单字段配置 if ($annotations->has('Crud\FormField')) { $fieldAnno = $annotations->get('Crud\FormField'); $metadata['formFields'][$propName] = [ 'type' => $fieldAnno->getArgument('type'), 'required' => $fieldAnno->getArgument('required', false), 'colSize' => $fieldAnno->getArgument('colSize', 6), 'label' => $fieldAnno->getArgument('label') ?? $metadata['columns'][$propName]['label'] ?? $propName, ]; } } return $metadata; } }
第三步:在CrudBase控制器中动态生成模板
修改你的CrudBase控制器,实现动态模板生成与渲染:
class CrudBase extends Controller { protected $entityClass; protected $customConfig = []; // 子类可覆盖,自定义CRUD行为 public function indexAction() { // 1. 获取Model元数据与合并自定义配置 $model = new $this->entityClass(); $baseMetadata = $this->crudMetadataService->getMetadata($model); $finalConfig = array_merge_recursive($baseMetadata, $this->customConfig); // 2. 生成Twig模板字符串 $templateStr = $this->buildCrudTemplate($finalConfig); // 3. 用Twig StringLoader渲染模板 $loader = new \Twig\Loader\StringLoader(); $twig = new \Twig\Environment($loader, [ 'cache' => APP_PATH . '/cache/twig', // 开启缓存提升性能 ]); // 4. 渲染并输出结果 $renderedContent = $twig->render($templateStr, [ 'ctrlName' => 'ctrl', 'tableClass' => 'table table-bordered table-striped table-hover', 'xdata' => $this->prepareXdata(), // 你的原有xdata逻辑 'config' => $finalConfig, ]); $this->response->setContent($renderedContent); return $this->response; } /** * 动态构建CRUD模板字符串 */ protected function buildCrudTemplate($config) { // 自动生成列表表头 $tableHeader = ''; foreach ($config['columns'] as $field => $col) { if ($col['visible']) { $tableHeader .= "{{ crud.tableHeader('{$col['label']}', '{$field}') }}"; } } $tableHeader .= '<th class="col-xs-1">Actions</th>'; // 自动生成列表行 $tableRow = ''; foreach ($config['columns'] as $field => $col) { if ($col['visible']) { $tableRow .= "<td ng-bind=\"e.{$field}\"></td>"; } } // 自动生成表单内容 $modalBody = '<div class="row">'; foreach ($config['formFields'] as $field => $formField) { $modalBody .= "{{ crud.input('ctrl.entity.{$field}', '{$formField['label']}', {$formField['colSize']}) }}"; } $modalBody .= '</div>'; // 拼接完整模板 $controllerName = (new \ReflectionClass($this))->getShortName(); return <<<TWIG {% extends 'index.twig' %} {% block containerAttr %}ng-controller="{$controllerName} as ctrl"{% endblock %} {% set ctrlName = 'ctrl' %} {% set columnWidth = 'col-xs-12' %} {% block header %} {% include '@crud/crud/header.twig' %} {% endblock %} {% block content %} {% embed '@crud/crud/main-box.twig' %} {% block tableHeader %} {% autoescape false %} {$tableHeader} {% endautoescape %} {% endblock %} {% block tableRow %} {$tableRow} {% endblock %} {% endembed %} {% embed '@crud/crud/form.twig' %} {% block modalBody %} {% autoescape false %} {$modalBody} {% endautoescape %} {% endblock %} {% endembed %} {% embed '@crud/crud/upload.twig' %}{% endembed %} {% embed '@crud/crud/advancedSearch.twig' %}{% endembed %} <script> window.xdata = {{ xdata | json_encode | raw }}; </script> {% endblock %} TWIG; } // 原有xdata准备逻辑 protected function prepareXdata() { // ... 你的实现 } }
第四步:子类控制器的极简配置
现在新建CRUD控制器只需要几行代码,完全不需要写模板:
class SubCategoryController extends CrudBase { protected $entityClass = InventoryCategory::class; // 可选:自定义配置,比如隐藏某个字段、修改表单布局 protected $customConfig = [ 'columns' => [ 'id' => ['visible' => false], ], ]; }
优化建议
- 模板片段复用:把列表表头、表单字段等逻辑封装成Twig宏,动态生成时直接调用,更易维护。
- 特殊字段处理:针对日期、下拉框等特殊字段,在注解中指定
type,生成模板时调用对应的crud.datePicker、crud.select宏。 - 自定义Block支持:在
customConfig中添加customBlocks字段,允许子类覆盖特定Block的内容,保留灵活性。 - 模板缓存:开启Twig缓存,避免每次请求都重新生成编译模板,大幅提升性能。
内容的提问来源于stack exchange,提问作者Kaminari
相关产品推荐
相关产品推荐

