Sonata Admin与文件上传的一对多关系(appendFormFieldElement)

tho*_*rad 8 php symfony symfony-sonata sonata-admin

我目前正面临着SonataAdminBundle的挑战,一对多的关系和文件上传.我有一个实体叫Client,一个叫ExchangeFile.一个人Client可以有几个ExchangeFile,所以我们在这里有一对多的关系.我正在使用VichUploaderBundle进行文件上传.

这是Client班级:

/**
 * @ORM\Table(name="client")
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks
 */
class Client extends BaseUser
{    
    // SNIP

    /**
     * @ORM\OneToMany(targetEntity="ExchangeFile", mappedBy="client", orphanRemoval=true, cascade={"persist", "remove"})
     */
    protected $exchangeFiles;

    // SNIP
}
Run Code Online (Sandbox Code Playgroud)

这是ExchangeFile班级:

/**
 * @ORM\Table(name="exchange_file")
 * @ORM\Entity
 * @Vich\Uploadable
 */
class ExchangeFile
{
    // SNIP

    /**
     * @Assert\File(
     *     maxSize="20M"
     * )
     * @Vich\UploadableField(mapping="exchange_file", fileNameProperty="fileName")
     */
    protected $file;

    /**
     * @ORM\Column(name="file_name", type="string", nullable=true)
     */
    protected $fileName;

    /**
     * @ORM\ManyToOne(targetEntity="Client", inversedBy="exchangeFiles")
     * @ORM\JoinColumn(name="client_id", referencedColumnName="id")
     */
    protected $client;

    // SNIP
}
Run Code Online (Sandbox Code Playgroud)

在我的ClientAdmin班上,我exchangeFiles按以下方式添加了字段:

protected function configureFormFields(FormMapper $formMapper)
{
    $formMapper
        // SNIP
        ->with('Files')
            ->add('exchangeFiles', 'sonata_type_collection', array('by_reference' => false), array(
                    'edit' => 'inline',
                    'inline' => 'table',
                ))
        // SNIP
}
Run Code Online (Sandbox Code Playgroud)

这允许在客户端编辑表单中内联编辑各种交换文件.它到目前为止运作良好:Sonata Admin具有一对多关系和文件上传.

问题

但是有一个ceveat:当我点击绿色"+"符号一次(添加一个新的交换文件格式行),然后在我的文件系统中选择一个文件,然后再次点击"+"符号(通过Ajax添加一个新的表单行) ),选择另一个文件,然后点击"更新"(保存当前客户端),然后第一个文件不会保留.只能在数据库和文件系统中找到第二个文件.

据我所知,这有以下原因:当第二次点击绿色"+"符号时,当前表单将发布到Web服务器,包括当前表单中的数据(客户端和所有交换文件) ).创建一个新表单并将请求绑定到表单中(这发生在AdminHelper位于其中的类中Sonata\AdminBundle\Admin):

public function appendFormFieldElement(AdminInterface $admin, $subject, $elementId)
{
    // retrieve the subject
    $formBuilder = $admin->getFormBuilder();

    $form = $formBuilder->getForm();
    $form->setData($subject);
    $form->bind($admin->getRequest()); // <-- here
    // SNIP
}
Run Code Online (Sandbox Code Playgroud)

因此整个表单被绑定,附加表单行,表单被发送回浏览器,整个表单被新表单覆盖.但由于<input type="file" />出于安全原因无法预先填充文件输入(),因此第一个文件将丢失.当实体被持久化时,文件只存储在文件系统上(我认为VichUploaderBundle使用Doctrine prePersist),但是当附加表单字段行时,这种情况还没有发生.

我的第一个问题是:我如何解决这个问题,或者我应该走哪个方向?我希望以下用例工作:我想创建一个新客户端,我知道我将上传三个文件.我点击"新客户端",输入客户端数据,点击绿色"+"按钮一次,选择第一个文件.然后我再次点击"+"符号,然后选择第二个文件.对于第三个文件也是如此.所有三个文件都应该保留.

第二个问题:当我只想在一对多关系中添加单个表单行时,为什么Sonata Admin会发布整个表单?这真的有必要吗?这意味着如果我有文件输入,则每次添加新表单行时都会上载表单中存在的所有文件.

在此先感谢您的帮助.如果您需要任何细节,请告诉我.

Rob*_*ers 3

进一步我对SonataMediaBundle的评论......

如果您确实走这条路线,那么您需要创建一个类似于以下内容的新实体:

/**
 * @ORM\Table
 * @ORM\Entity
 */
class ClientHasFile
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Client $client
     *
     * @ORM\ManyToOne(targetEntity="Story", inversedBy="clientHasFiles")
     */
    private $client;

    /**
     * @var Media $media
     *
     * @ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media")
     */
    private $media;

    // SNIP
}
Run Code Online (Sandbox Code Playgroud)

然后,在您的 Client 实体中:

class Client
{
    // SNIP

    /**
     * @var \Doctrine\Common\Collections\ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="ClientHasFile", mappedBy="client", cascade={"persist", "remove"}, orphanRemoval=true)
     */
    protected $clientHasFiles;


    public function __construct()
    {
        $this->clientHasFiles = new ArrayCollection();
    }

    // SNIP
}
Run Code Online (Sandbox Code Playgroud)

...以及您的 ClientAdmin 的 configureFormFields:

protected function configureFormFields(FormMapper $form)
{
    $form

    // SNIP

    ->add('clientHasFiles', 'sonata_type_collection', array(
        'required' => false,
        'by_reference' => false,
        'label' => 'Media items'
    ), array(
        'edit' => 'inline',
        'inline' => 'table'
    )
    )
;
}
Run Code Online (Sandbox Code Playgroud)

...最后但并非最不重要的一点是,您的 ClientHasFileAdmin 类:

class ClientHasFileAdmin extends Admin
{
    /**
     * @param \Sonata\AdminBundle\Form\FormMapper $form
     */
    protected function configureFormFields(FormMapper $form)
    {
        $form
            ->add('media', 'sonata_type_model_list', array(), array(
                'link_parameters' => array('context' => 'default')
            ))
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function configureListFields(ListMapper $list)
    {
        $list
            ->add('client')
            ->add('media')
        ;
    }
}
Run Code Online (Sandbox Code Playgroud)