![](/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.