Symfony VichUploaderBundle: не удалось создать имя файла

Я использую VichUploader для загрузки файлов в рамках проекта Symfony. В конфигурации я использую (скопировано из документации):

service: vich_uploader.namer_property
options: { property: 'slug'}

В моей сущности я генерирую слагов автоматически с помощью Gedmo/Sluggable:

/**
 * @Gedmo\Slug(fields={"title"}, updatable=false)
 * @ORM\Column(type="string", length=100, nullable=false)
 */
protected $slug;

Но при попытке сохранить сущность я получаю следующую ошибку 500:

Имя файла не может быть сгенерировано: свойство slug пусто.

Если я установлю свойство на "title", оно будет работать. Я забыл параметр конфигурации или что-то еще, чтобы заставить его работать со слагом Gedmo?

2 ответа

У меня сейчас та же проблема, что и в качестве обходного пути, я немного изменил метод получения слагов в классе сущностей:

use Gedmo\Sluggable\Util\Urlizer;

class Event
{
    // ...

    /**
     * @var string
     *
     * @Gedmo\Slug(fields={"name"})
     * @ORM\Column(name="slug", type="string", length=128, unique=true)
     */
    private $slug;

    // ...

    public function getSlug()
    {
        if (!$this->slug) {
            return Urlizer::urlize($this->getName());
        }

        return $this->slug;
    }
}

Это добилось цели.

К сожалению, есть несколько недостатков:

  1. Если вы когда-нибудь захотите обновить медленное поведение в аннотации, включив в него дополнительные свойства, вам также придется обновить геттер.
  2. Этот метод не проверяет базу данных: если в базе данных уже есть запись с таким же именем urlizer в получателе не сможет добавить приращение к имени файла, ранее сохраненный файл может быть перезаписан! В качестве обходного пути вы можете добавить unique=true к вялым свойствам.

VichUploader слушает события prePersist и preUpdate, тогда как Sluggable слушает событие onFlush. Поскольку prePersist и preUpdate вызываются перед onFlush, это невозможно сделать только с помощью конфигурации.

Однако, если поле вашего файла допускает значение NULL, вы можете обойти это, изменив код контроллера. Когда вы получите отправленную форму с файлом, удалите файл, сохраните объект, затем повторно добавьте файл и снова сохраните объект. При втором сохранении слаг уже будет установлен, поэтому VichUploader сможет сохранить файл нормально.

if ($form->isSubmitted() && $form->isValid()) {
    if ($file = $entity->getFile()) {
        $entity->setFile(null);
    }

    $em = $this->getDoctrine()->getManager();
    $em->persist($entity);
    $em->flush();

    if ($file) {
        $document->setFile($file);
        $em->persist($entity);
        $em->flush();
    }

    // ...
}

Это работает только при добавлении нового файла. Если впоследствии вы измените заголовок и повторно сохраните объект без загрузки нового файла, имя файла не будет обновлено.

У меня возникла та же проблема при загрузке документа, для которого мне нужно, чтобы fileName был слагом.

Я использовал аннотации Gedmo для генерации слага, но это срабатывает только при сбросе, а имя vichUploader срабатывает при сохранении.

Самым простым способом получить эту работу для меня было не использовать аннотацию Gedmo\Sluggable, а создать слушатель prePersist для моего объекта Document и использовать библиотеку Cocur\Slugify.

Итак, вот код.

Мой документ:

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * @ORM\Entity(repositoryClass="App\Repository\DocumentRepository")
 * @Vich\Uploadable
 * @ORM\EntityListeners({"App\Listeners\DocumentListener"})
 */
class Document
{
    use TimestampableEntity;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;


    /**
     * @ORM\Column(type="string", length=100, nullable=false)
     */
    private $fileName;

    /**
     * @Vich\UploadableField(mapping="document", fileNameProperty="fileName")
     * @var File
     */
    private $documentFile;

    /**
     * @ORM\Column(type="string", length=100, unique=true)
     */
    private $slug;

    /**
     */
    public function getDocumentFile(): ?File
    {
        return $this->documentFile;
    }

    /**
     * @param File $documentFile
     * @return Document
     * @throws \Exception
     */
    public function setDocumentFile(File $documentFile = null): Document
    {
        $this->documentFile = $documentFile;
        if($documentFile){
            $this->updatedAt = new \DateTimeImmutable();
        }

        return $this;
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getFileName()
    {
        return $this->fileName;
    }

    /**
     * @param mixed $fileName
     */
    public function setFileName($fileName): void
    {
        $this->fileName = $fileName;
    }
}

И слушатель:

namespace App\Listeners;

use App\Entity\Document;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Cocur\Slugify\Slugify;

class DocumentListener
{
    public function prePersist(Document $document, LifecycleEventArgs $args)
    {
        $slugify = new Slugify();
        if(!empty($document->getDocumentFile())){
            $originalName = pathinfo($document->getDocumentFile()->getClientOriginalName(), PATHINFO_FILENAME);
            $slug = $slugify->slugify($originalName);
            $document->setSlug($slug);
        }
    }
}

До сих пор у меня не было никаких проблем.

Дайте мне знать, если это работает для вас

По умолчанию пакет расширений доктрины не прикрепляет ни одного слушателя: http://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html

Вы должны настроить его так, чтобы он работал медленно:

stof_doctrine_extensions:
    orm:
        default:
            sluggable: true
Другие вопросы по тегам