【问题标题】:Forms with Symfony2 - Doctrine Entity with some immutable constructor parameters and OneToMany association带有 Symfony2 的表单 - 带有一些不可变构造函数参数和 OneToMany 关联的 Doctrine Entity
【发布时间】:2014-08-23 05:22:45
【问题描述】:

我在数据库中的Server 实体和Client 实体之间有一个OneToMany 关联。 一台服务器可以有多个客户端。我想制作一个表单,用户可以从下拉列表中选择服务器,为新客户端填写一些详细信息,然后提交。

目标

要创建一个表单,用户可以将数据输入到 Client 的字段中,请从下拉列表中选择 Server,然后单击提交并通过 Doctrine 保留此数据(和关联)。

很简单,对吧?一定不行。我们会解决的。这是目前的漂亮形式:

注意事项:

  • 服务器由 Server 实体 (EntityRepository::findAll()) 填充
  • 客户端是带有硬编码值的下拉菜单
  • 端口、端点、用户名和密码都是文本字段

客户实体

以我的无限智慧,我声明我的Client 实体具有以下构造函数签名:

class Client
{
    /** -- SNIP -- **/
    public function __construct($type, $port, $endPoint, $authPassword, $authUsername);
    /** -- SNIP -- **/
}

这不会改变。要创建有效的Client 对象,存在上述构造函数参数。它们是可选的,如果没有在对象实例化时给出上述参数,则无法创建此对象。

潜在问题

  • type 属性是不可变的。创建客户端后,您无法更改类型。

  • 我没有type 的设置器。它只是一个构造函数参数。这是因为一旦创建了客户端,您就无法更改类型。因此,我在实体级别执行此操作。结果,没有setType()changeType() 方法。

  • 我没有标准的setObject 命名约定。我声明要更改端口,例如,方法名称是changePort() 而不是setPort()。这就是我在使用 ORM 之前要求我的对象 API 发挥作用的方式。

服务器实体

我使用__toString() 连接nameipAddress 成员以显示在表单下拉列表中:

class Server 
{
    /** -- SNIP -- **/
    public function __toString()
    {
        return sprintf('%s - %s', $this->name, $this->ipAddress);
    }
    /** -- SNIP -- **/
}

自定义表单类型

我使用Building Forms with Entities 作为我的代码的基线。

这是我为构建表单而创建的ClientType

class ClientType extends AbstractType
{
    /**
     * @var UrlGenerator
     */
    protected $urlGenerator;

    /**
     * @constructor
     *
     * @param UrlGenerator $urlGenerator
     */
    public function __construct(UrlGenerator $urlGenerator)
    {
        $this->urlGenerator = $urlGenerator;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        /** Dropdown box containing the server name **/
        $builder->add('server', 'entity', [
            'class' => 'App\Model\Entity\Server',
            'query_builder' => function(ServerRepository $serverRepository) {
                return $serverRepository->createQueryBuilder('s');
            },
            'empty_data' => '--- NO SERVERS ---'
        ]);

        /** Dropdown box containing the client names **/
        $builder->add('client', 'choice', [
            'choices' => [
                'transmission' => 'transmission',
                'deluge'       => 'deluge'
            ],
            'mapped' => false
        ]);

        /** The rest of the form elements **/
        $builder->add('port')
                ->add('authUsername')
                ->add('authPassword')
                ->add('endPoint')
                ->add('addClient', 'submit');

        $builder->setAction($this->urlGenerator->generate('admin_servers_add_client'))->setMethod('POST');
    }

    /**
     * {@inheritdoc}
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'App\Model\Entity\Client',
            'empty_data' => function(FormInterface $form) {
                return new Client(
                    $form->getData()['client'],
                    $form->getData()['port'],
                    $form->getData()['endPoint'],
                    $form->getData()['authPassword'],
                    $form->getData()['authUsername']
                );
            }
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'client';
    }
}

上面的代码实际上是生成要在客户端使用的表单(通过 twig)。

问题

首先,使用上面的代码,提交表单给我:

NoSuchPropertyExceptionPropertyAccessor.php 第 456 行: 属性“port”和“addPort()”/“removePort()”、“setPort()”、“port()”、“__set()”或“__call()”方法之一都不存在并且具有公共在“App\Model\Entity\Client”类中访问。

所以它找不到port 方法。那是因为它是changePort(),正如我之前解释的那样。我如何告诉它应该使用 changePort() 代替?根据docs,我必须将entity 类型用于端口、端点等。但它们只是文本字段。我该如何以正确的方式解决这个问题?

我试过了:

  • 在端口、authUsername 等上设置['mapped' => false]。这给了我所有客户端字段的null,但它确实似乎有相关的服务器详细信息。不管怎样,$form->isValid() 返回 false。这是var_dump() 向我展示的内容:

  • 其他事物的组合,包括将每个 on 字段设置为“实体”等等。

基本上,“它不起作用”。但这是我所拥有的。 我做错了什么?我一遍又一遍地阅读手册,但一切都相距甚远,我不知道我是否应该使用DataTransformerEntity Field Type 或其他方式。我已经接近完全放弃 Symfony/Forms 并且只用十分之一的时间自己编写这个。

有人可以给我一个关于如何到达我想去的地方的可靠答案吗?这也可能对未来的用户有所帮助:-)

【问题讨论】:

    标签: php symfony doctrine-orm symfony-forms


    【解决方案1】:

    上述解决方案存在一些问题,所以我是这样解决的!

    空值

    事实证明,在setDefaultOptions() 中,代码:$form->getData['key'] 返回 null,因此屏幕截图中的所有 null。这个需要改成$form->get('key')->getData()

    return new Client(
        $form->get('client')->getData(),
        $form->get('port')->getData(),
        $form->get('endPoint')->getData(),
        $form->get('authPassword')->getData(),
        $form->get('authUsername')->getData()
    );
    

    因此,数据按预期通过,所有值都完好无损(除了 id)。

    Twig Csrf

    根据documentation,您可以在表单选项中设置csrf_protection => false。如果不这样做,则需要在表单中呈现隐藏的 csrf 字段:

    {{ form_rest(form) }}
    

    这会为您呈现其余的表单字段,包括隐藏的 _token 一个:

    Symfony2 有一种机制可以帮助防止跨站脚本:它们生成一个 CSRF 令牌,该令牌必须用于表单验证。在这里,在您的示例中,您没有使用 form_rest(form) 显示(因此不提交)它。基本上 form_rest(form) 将“渲染”您之前未渲染但包含在您已传递给视图的表单对象中的每个字段。 CSRF 令牌就是这些值之一。

    硅胶

    这是我在解决上述问题后遇到的错误:

    CSRF 令牌无效。请尝试重新提交表单。

    我正在使用 Silex,在注册 FormServiceProvider 时,我有以下信息:

    $app->register(new FormServiceProvider, [
        'form.secret' => uniqid(rand(), true)
    ]);
    

    This Post 展示了 Silex 如何为您提供一些已弃用的 CsrfProvider 代码:

    原来这不是因为我的 ajax,而是因为 Silex 为您提供了一个已弃用的 DefaultCsrfProvider,它使用会话 ID 本身作为令牌的一部分,为了安全起见,我随机更改了 ID。相反,明确告诉它使用新的 CsrfTokenManager 来修复它,因为它会生成一个令牌并将其存储在会话中,这样会话 ID 可以更改而不会影响令牌的有效性。

    因此,我必须删除 form.secret 选项,并将以下内容添加到我的应用程序引导程序中,在注册表单提供程序之前

    /** Use a CSRF provider that does not depend on the session ID being constant. We change the session ID randomly */
    $app['form.csrf_provider'] = $app->share(function ($app) {
        $storage = new Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage($app['session']);
        return new Symfony\Component\Security\Csrf\CsrfTokenManager(null, $storage);
    });
    

    通过上述修改,表单现在可以发布并且数据正确地保存在数据库中,包括教义关联!

    【讨论】:

      猜你喜欢
      • 2022-01-17
      • 1970-01-01
      • 2011-10-18
      • 1970-01-01
      • 2016-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-25
      相关资源
      最近更新 更多