本文概述
通常, 许多教程都指示你需要在控制器中检索上载的文件并在那里执行一些逻辑以保存, 更新或删除, 但是对于Symfony 3而言, 如果你的表单基于Doctrine Entity, 则不需要这样做。但是, 等等, Symfony的官方教程已经有一篇文章, 介绍了如何自动上传文件而不用教义实体修改控制器, 为什么我应该读这篇文章呢?好吧, Symfony中的官方教程将按所述方式工作, 问题在于, 每次你编辑(更新)实体时, 上传器都会上传一个新文件, 这会导致你不再需要大量文件, 因为你仅是实体存储单个文件名。如果希望每次更新表单时都将删除旧文件(仅在上载新文件时)或保留旧文件(如果未上载新文件), 则以下实现将引导你完成此过程。
我们将在Symfony的官方文章中以”小册子”字段和”产品”实体的相同示例制作教程。
1.使用FileType配置FormType
你需要做的第一件事是使用FileType定义用于上传文件的表单字段:
注意
将字段的data_class属性设置为null很重要, 否则你将面临一个异常, 即:
表单的视图数据应该是Symfony \ Component \ HttpFoundation \ File \ File类的实例, 但是是一个(n)字符串。
当你尝试使用表单更新你的实体时。
<?php
// src/AppBundle/Form/ProductType.php
namespace AppBundle\Form;
use AppBundle\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('brochure', FileType::class, array(
'label' => 'Brochure (PDF file)', 'data_class' => null, 'required' => false
))
// ...
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Product::class, ));
}
}
2.创建和配置FileUploader服务
配置FileUploader类的第一步是定义一个全局参数, 该参数指定将在何处上传小册子文件。在这种情况下, 我们将在Web中创建两个文件夹, 即上载和内部上载的小册子文件夹, 其中将存储上载的文件。你可以在应用程序的config.yml文件中定义此参数:
# app/config/config.yml
parameters:
brochure_files_directory: '%kernel.project_dir%/web/uploads/brochures'
我们将需要Brochure_files_directory参数将其注入到接下来将要创建的FileUploader服务中。使用下面的代码创建类FileUploader:
<?php
// src/AppBundle/Service/FileUploader.php
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class FileUploader
{
private $targetDir;
public function __construct($targetDir)
{
$this->targetDir = $targetDir;
}
public function upload(UploadedFile $file)
{
$fileName = md5(uniqid()).'.'.$file->guessExtension();
$file->move($this->getTargetDir(), $fileName);
return $fileName;
}
public function getTargetDir()
{
return $this->targetDir;
}
}
作为一个标准, 我们在捆绑包中创建了Service文件夹, 因此名称间隔不仅易于理解, 而且对我们的应用程序而言是易于理解的。无需修改代码, 因为它实现了一个非常基本的文件上传器, 该文件上传器将Providen文件移动到所需路径中, 并在一行代码中生成一个随机名称。该文件存在后, 你需要在Symfony项目的services.yml文件中注册它:
# app/config/services.yml
services:
AppBundle\Service\FileUploader:
arguments:
$targetDir: '%brochure_files_directory%'
请注意, 我们将创建的参数Brochure_files_directory复制为$ targetDir参数的值。侦听器将使用此FileUploader类轻松地操作文件。
3.创建和配置Doctrine Listener
如官方教程中所述, 为了防止控制器中有多余的代码来处理文件上传, 你可以创建Doctrine侦听器以在持久保存实体时自动上传文件。在此示例中, 我们在AppBundle中创建了EventListener文件夹, 该文件夹将放置我们的上载侦听器类。侦听器的代码遵循以下逻辑:
注意
必须根据你的实体的字段来修改此代码。这意味着你可以自己更改宣传册的获取者和安装者。
<?php
// src/AppBundle/EventListener/BrochureUploadListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
// Include Product class and our file uploader
use AppBundle\Entity\Product;
use AppBundle\Service\FileUploader;
class BrochureUploadListener
{
private $uploader;
private $fileName;
public function __construct(FileUploader $uploader)
{
$this->uploader = $uploader;
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$this->uploadFile($entity);
}
public function preUpdate(PreUpdateEventArgs $args)
{
// Retrieve Form as Entity
$entity = $args->getEntity();
// This logic only works for Product entities
if (!$entity instanceof Product) {
return;
}
// Check which fields were changes
$changes = $args->getEntityChangeSet();
// Declare a variable that will contain the name of the previous file, if exists.
$previousFilename = null;
// Verify if the brochure field was changed
if(array_key_exists("brochure", $changes)){
// Update previous file name
$previousFilename = $changes["brochure"][0];
}
// If no new brochure file was uploaded
if(is_null($entity->getBrochure())){
// Let original filename in the entity
$entity->setBrochure($previousFilename);
// If a new brochure was uploaded in the form
}else{
// If some previous file exist
if(!is_null($previousFilename)){
$pathPreviousFile = $this->uploader->getTargetDir(). "/". $previousFilename;
// Remove it
if(file_exists($pathPreviousFile)){
unlink($pathPreviousFile);
}
}
// Upload new file
$this->uploadFile($entity);
}
}
private function uploadFile($entity)
{
// upload only works for Product entities
if (!$entity instanceof Product) {
return;
}
$file = $entity->getBrochure();
// only upload new files
if ($file instanceof UploadedFile) {
$fileName = $this->uploader->upload($file);
$entity->setBrochure($fileName);
}
}
}
当用户为产品创建新的注册(访问表单以创建新产品)时, 该表单允许他在小册子字段中上传文件。如果是Providen, 则将使用随机文件名(名称存储在小册子字段中的数据库中的名称)自动保存在/ uploads / brochures目录中, 如果没有上载任何文件, 则该字段为null。当用户编辑产品时, 如果某人已经上传了小册子文件, 并且用户在不上传新文件的情况下更新了产品, 则旧文件将被保留并且什么也不会发生, 但是如果用户上传新文件, 则旧的将被删除, 新的将被存储(也更新手册字段)。
最后, 你需要在项目的services.yml文件中注册该理论侦听器:
# app/config/services.yml
services:
AppBundle\EventListener\BrochureUploadListener:
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: preUpdate }
保存更改, 清除缓存并测试你的表单。
编码愉快!
评论前必须登录!
注册