簡體   English   中英

具有Symfony2的表單 - 具有一些不可變構造函數參數和OneToMany關聯的Doctrine實體

[英]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()來連接nameipAddress成員以在表單下拉列表中顯示:

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類型。但它們只是文本字段。 我該怎么做正確的方法?

我努力了:

  • 在port,authUsername等上設置['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)。

Twig Csrf

根據文檔,您可以在表單選項中設置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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM