Jim*_*mbo 7 php symfony-forms symfony doctrine-orm
我在数据库中
OneToMany的Server实体和Client实体之间有关联.一台服务器可以有很多客户端.我想创建一个表单,用户可以从下拉列表中选择服务器,填写新客户端的一些详细信息,然后提交.
要创建用户可以将数据输入到字段中的表单,请从下拉列表中Client选择a Server,然后单击"提交"并通过Doctrine 保留此数据(和关联).
简单吧?一定不行.我们会做到这一点.这是现在的漂亮形式:

注意事项:
Server实体(EntityRepository::findAll())填充的在我的无限智慧中,我宣称我的Client实体具有以下构造函数签名:
class Client
{
/** -- SNIP -- **/
public function __construct($type, $port, $endPoint, $authPassword, $authUsername);
/** -- SNIP -- **/
}
Run Code Online (Sandbox Code Playgroud)
这不会改变.要创建有效Client对象,请存在上述构造函数参数.它们不是可选的,如果没有在对象实例化时给出上述参数,则无法创建此对象.
潜在问题:
该type属性是不可变的.创建客户端后,无法更改类型.
我没有一个二传手type.它只是一个构造函数参数.这是因为一旦创建了客户端,就无法更改类型.因此,我在实体层面强制执行此操作.结果,没有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 -- **/
}
Run Code Online (Sandbox Code Playgroud)
我使用带有实体的构建表单作为我的代码的基线.
这是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';
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码实际上是生成客户端使用的表单(通过twig).
首先,通过上面的代码,提交表单给了我:
NoSuchPropertyException在PropertyAccessor.php线456:无论是在属性"端口",也不的方法的一个"添加端口()"/ "removePort()", "setPort()", "端口()", "__set()"或" __call()"存在并在类"App\Model\Entity\Client"中具有公共访问权限.
所以找不到port方法.那是因为它changePort()就像我之前解释的那样.我怎么告诉它应该使用changePort()呢?根据文档,我将不得不使用entity端口类型,endPoint等.但它们只是文本字段.我该怎么做正确的方法?
我试过了:
['mapped' => false]端口,authUsername等.这为我null提供了所有客户端字段,但它似乎确实具有相关的服务器详细信息.无论如何,$form->isValid()返回虚假.这是var_dump()给我看的:
基本上,"它不起作用".但这就是我所拥有的.我做错了什么?我正在一遍又一遍地阅读手册,但一切都相距甚远,以至于我不知道我是否应该使用DataTransformer,实体字段类型或其他方式.我已经接近完全废弃Symfony/Forms,只是在十分之一的时间里自己写这个.
有人可以给我一个如何到达我想要的地方的坚实答案吗?这也可以帮助未来的用户:-)
上述解决方案存在一些问题,所以这就是我的工作方式!
事实证明,在 中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()
);
Run Code Online (Sandbox Code Playgroud)
结果,数据按预期通过,所有值都完好无损(除了 id 之外)。
根据文档,您可以csrf_protection => false在表单选项中设置。如果不这样做,您将需要在表单中呈现隐藏的 csrf 字段:
{{ form_rest(form) }}
Run Code Online (Sandbox Code Playgroud)
这将为您呈现其余的表单字段,包括隐藏的_token字段:
Symfony2 有一种机制可以帮助防止跨站点脚本:它们生成必须用于表单验证的 CSRF 令牌。在这里,在您的示例中,您没有使用 form_rest(form) 显示(因此没有提交)它。基本上, form_rest(form) 将“渲染”您之前未渲染但包含在您传递给视图的表单对象中的每个字段。CSRF 代币就是其中之一。
这是我解决上述问题后遇到的错误:
CSRF 令牌无效。请尝试重新提交表格。
我正在使用 Silex,在注册 FormServiceProvider 时,我有以下内容:
{{ form_rest(form) }}
Run Code Online (Sandbox Code Playgroud)
这篇文章展示了 Silex 如何为您提供一些已弃用的 CsrfProvider 代码:
事实证明,这不是由于我的 ajax,而是因为 Silex 为您提供了一个已弃用的 DefaultCsrfProvider,它使用会话 ID 本身作为令牌的一部分,并且为了安全起见,我随机更改了 ID。相反,明确告诉它使用新的 CsrfTokenManager 可以修复此问题,因为它会生成一个令牌并将其存储在会话中,这样会话 ID 就可以更改,而不会影响令牌的有效性。
因此,在注册表单提供程序之前,我必须删除 form.secret 选项,并将以下内容添加到我的应用程序引导程序中:
$app->register(new FormServiceProvider, [
'form.secret' => uniqid(rand(), true)
]);
Run Code Online (Sandbox Code Playgroud)
通过上述修改,表单现在已发布,并且数据已正确保存在数据库中,包括学说关联!