You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过扩展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],
        ],
    ];
}

优化建议

  1. 模板片段复用:把列表表头、表单字段等逻辑封装成Twig宏,动态生成时直接调用,更易维护。
  2. 特殊字段处理:针对日期、下拉框等特殊字段,在注解中指定type,生成模板时调用对应的crud.datePicker、crud.select宏。
  3. 自定义Block支持:在customConfig中添加customBlocks字段,允许子类覆盖特定Block的内容,保留灵活性。
  4. 模板缓存:开启Twig缓存,避免每次请求都重新生成编译模板,大幅提升性能。

内容的提问来源于stack exchange,提问作者Kaminari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:56:01