使用 Symfony 2.8 生成表单会抛出 Twig_Error_Runtime

Generating forms with Symfony 2.8 throws a Twig_Error_Runtime

自从几天前(2015 年 11 月 30 日)发布了 Symfony 的最后一个 LTS 版本后,我就开始使用它了。不幸的是,我无法使用在 Symfony 2.7.7 中运行良好的相同代码生成具有写入操作的 CRUD。

首先,我使用 Linux Mint 17.2 下的 bash 创建一个新的 Symfony 项目:

symfony new tasks lts

创建新目录 tasks,其中包含一个新的 Symfony 2.8.0 项目。

app/config/parameters.yml 中调整数据库凭据后,我创建了数据库:

app/console doctrine:database:create

并生成一个新包:

app/console generate:bundle --namespace=Acme/TasksBundle --format=yml

然后我创建一个新目录 src/Acme/TasksBundle/Resources/config/doctrine 并在其中放置两个我的模型文件。它们是:

Task.orm.yml

Acme\TasksBundle\Entity\Task:
    type: entity
    repositoryClass: Acme\TasksBundle\Repository\TaskRepository
    table: task
    id:
        id:
            type: integer
            generator: { strategy : AUTO }
    fields:
        description:
            type: text
    manyToMany:
        tags:
            targetEntity: Tag
            inversedBy: tasks
            cascade: [ "persist" ]
            joinTable:
                name: task_tag
                joinColumns:
                    task_id:
                        referencedColumnName: id
                inverseJoinColumns:
                    tag_id:
                        referencedColumnName: id

Tag.orm.yml

Acme\TasksBundle\Entity\Tag:
    type: entity
    repositoryClass: Acme\TasksBundle\Repository\TagRepository
    table: tag
    id:
        id:
            type: integer
            generator: { strategy : AUTO }
    fields:
        name:
            type: string
            length: 50
    manyToMany:
        tasks:
            targetEntity: Task
            mappedBy: tags

数据库模式应该是这样的:

+----------------+     +--------------+
| task           |     | task_tag     |     +---------+
+----------------+     +--------------+     | tag     |
|   id           |<--->|   task_id    |     +---------+
|   description  |     |   tag_id     |<--->|   id    |
+----------------+     +--------------+     |   name  |
                                            +---------+

现在我可以生成实体了:

app/console generate:doctrine:entities AcmeTasksBundle

这工作正常,因此可以更新数据库:

app/console doctrine:schema:update --force

到目前为止一切正常。这些表在数据库中。现在我想用写操作生成 CRUD:

app/console generate:doctrine:crud --entity=AcmeTasksBundle:Task --with-write --format=yml

确认几个问题后,它生成 CRUD 并打印出来:

Generating the CRUD code: OK

然后抛出这个错误:

[Twig_Error_Runtime]                                                                                    
Key "tags" for array with keys "id, description" does not exist in "form/FormType.php.twig" at line 29

创建了控制器,但没有创建表单。

生成不带写入选项的 CRUD 工作正常。完全相同的代码可以完美地与 Symfony 2.7.7 一起使用。

我检查了文件 form/FormType.php.twig 中版本之间的差异,以下是相关部分:

Symfony 2.7.7
vendor/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle/Resources/skeleton/form/FormType.php.twig

{%- if fields|length > 0 %}
/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
    {%- for field in fields %}

        ->add('{{ field }}')
    {%- endfor %}

    ;
}
{% endif %}

Symfony 2.8.0
vendor/sensio/generator-bundle/Resources/skeleton/form/FormType.php.twig

{%- if fields|length > 0 %}
/**
 * @param FormBuilderInterface $builder
 * @param array $options
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder

    {%- for field in fields -%}
        {%- if fields_mapping[field]['type'] in ['date', 'time', 'datetime'] %}

        ->add('{{ field }}', '{{ fields_mapping[field]['type'] }}')

        {%- else %}

        ->add('{{ field }}')

        {%- endif -%}
    {%- endfor %}

    ;
}
{% endif %}

据我所知,for循环中的if条件就是错误发生的地方。 (我假设表达式 fields_mapping[field]['type'] 导致问题,因为多对多字段 (tag) 没有属性 type。)

我做错了什么?我怎么解决这个问题?非常感谢您的帮助。

编辑: Symfony 3.0.0 也会出现同样的问题。文件 form/FormType.php.twig 自版本 2.8 以来已更改。

看起来像是生成器包中 datetime fix 之后的回归。

一个快速的解决方案是在您的 composer.json:

中恢复到 v2.*
"sensio/generator-bundle": "^2.5",

最好的解决方案是分叉存储库、修复错误并创建拉取请求以回馈社区。

由于您已经完成了隔离错误的所有工作,因此修复很简单:检查 type 是否存在于 Resources/skeleton/form/FormType.php.twig 中。像

{%- if fields_mapping[field]['type'] is defined and fields_mapping[field]['type'] in ['date', 'time', 'datetime'] %}

除非该错误基于相同的假设掩盖了更多隐藏的错误。

我稍微研究了一下并尝试调试错误。

正如我上面提到的,文件 form/FormType.php.twig 自版本 2.8.0 以来已更改。

显然,Symfony 的开发者想要增强表单并自动解析类型 datetimedatetime。这发生在行中:

{%- if fields_mapping[field]['type'] in ['date', 'time', 'datetime'] %}

这应该借助数组fields_mapping.

来实现

通过一些快速而肮脏的解决方法,我试图找出 fields_mapping 中隐藏的内容。这是我的模型的结果:

任务

{
    id => {
        id => 1,
        fieldName => id,
        type => integer,
        columnName => id
    },
    description => {
        fieldName => description,
        type => text,
        columnName => description
    }
}

当遍历 Task 的字段时,在最后一步它会遍历字段 tags。 if 子句中的表达式如下所示:

fields_mapping['tags']['type']

正如我们在前面的例子中看到的,fields_mapping 中没有键 tags for Task,只有 iddescription。由于密钥 tags 不存在,因此抛出错误。

我将文件 form/FormType.php.twig 中的相关行更改为如下所示:

{%- if fields_mapping[field] is defined and fields_mapping[field]['type'] in ['date', 'time', 'datetime'] %}

现在我们可以使用新功能,我们通过检查数组中是否存在键来防止错误。

我不知道这是一个错误还是我的特定情况有问题。现在距离 2.8.0 和 3.0.0 版本发布已经一周了,所以可能有成千上万的用户在使用它们。我简直不敢相信,如果这是一个错误,没有人会注意到这一点。

编辑:

我在 GitHub 上发布了一个问题:

https://github.com/sensiolabs/SensioGeneratorBundle/issues/443

这是一个错误,已经以同样的方式解决了,正如我在上面所想和写的:

https://github.com/Maff-/SensioGeneratorBundle/commit/205f64e96a94759f795271cb00fc86fb03b1fd4a

即使更新固定包后问题仍然存在,有时解决问题的最简单方法是删除 vendor 目录,然后更新 composer。