![](/img/trans.png)
[英]symfony2 JMSSerializerBundle deserialize entity with OneToMany association
[英]Forms with Symfony2 - Doctrine Entity with some immutable constructor parameters and OneToMany association
我在
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
的setter。 它只是一个构造函数参数 。 这是因为一旦创建了客户端, 就无法更改类型。 因此,我在实体层面强制执行此操作。 因此,没有setType()
或changeType()
方法。
我没有标准的setObject
命名约定。 我声明要更改端口,例如,方法名称是changePort()
而不是setPort()
。 这是我在使用ORM之前需要我的对象API运行的方式。
我正在使用__toString()
来连接name
和ipAddress
成员以在表单下拉列表中显示:
class Server
{
/** -- SNIP -- **/
public function __toString()
{
return sprintf('%s - %s', $this->name, $this->ipAddress);
}
/** -- SNIP -- **/
}
我使用带有实体的构建表单作为我的代码的基线。
这是我为我构建表单而创建的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)。
首先,通过上面的代码,提交表单给了我:
PropertyAccessor.php第456行中的NoSuchPropertyException :属性“port”和方法之一“addPort()”/“removePort()”,“setPort()”,“port()”,“__ set()”或“ __call()“存在并在类”App \\ Model \\ Entity \\ Client“中具有公共访问权限。
所以它找不到port
方法。 那是因为它是我之前解释过的changePort()
。 我怎么告诉它应该使用changePort()
呢? 根据文档,我将不得不使用port,endPoint等entity
类型。但它们只是文本字段。 我该怎么做正确的方法?
我努力了:
['mapped' => false]
。这使得我对所有客户端字段都为null
,但它似乎确实具有相关的服务器详细信息。 无论如何, $form->isValid()
返回false 。 这是var_dump()
向我展示的内容: 基本上,“它不起作用”。 但这就是我所拥有的。 我做错了什么? 我正在一遍又一遍地阅读手册,但一切都相距甚远,以至于我不知道我是否应该使用DataTransformer , 实体字段类型或其他方式。 我已经接近完全废弃Symfony / Forms,只是在十分之一的时间里自己写这个。
有人可以给我一个如何到达我想要的地方的坚实答案吗? 这也可以帮助未来的用户:-)
上述解决方案存在一些问题,所以我的工作方式就是这样!
事实证明,在setDefaultOptions()
,代码: $form->getData['key']
返回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)。
根据文档,您可以在表单选项中设置csrf_protection => false
。 如果不这样做,则需要在表单中呈现隐藏的csrf字段:
{{ form_rest(form) }}
这将为您呈现其余的表单字段,包括隐藏的_token
字段:
Symfony2有一种机制可以帮助防止跨站点脚本:它们生成一个必须用于表单验证的CSRF令牌。 在这里,在您的示例中,您没有使用form_rest(表单)显示(因此不提交)它。 基本上,form_rest(form)将“渲染”您之前未呈现的每个字段,但这些字段包含在您传递给视图的表单对象中。 CSRF令牌是其中一个值。
这是解决上述问题后我遇到的错误:
CSRF令牌无效。 请尝试重新提交表单。
我正在使用Silex,在注册FormServiceProvider时 ,我有以下内容:
$app->register(new FormServiceProvider, [
'form.secret' => uniqid(rand(), true)
]);
这篇文章展示了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);
});
通过上述修改,表单现在发布,数据正确保存在数据库中,包括学说关联!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.