Vichuploaderbundle: How save in property `$fileName`, filename with path?

Created on 21 Apr 2018  路  10Comments  路  Source: dustin10/VichUploaderBundle

Simple example code.

Config app/config/config.yml:

vich_uploader: 
    db_driver: orm 
    mappings: gallery_image: 
    uri_prefix: /gallery 
    upload_destination: '%kernel.project_dir%/public/gallery'
    directory_namer: 
        service: Vich\UploaderBundle\Naming\SubdirDirectoryNamer 
        options: {chars_per_dir: 1, dirs: 2} # will create directory "a/b" for "abcdef.jpg"

Part code from Entity:

... 
/**  @Vich\UploadableField(mapping="gallery_image", fileNameProperty="imageName")     
* @var File      
*/     
private $imageFile;     
/**      
* @ORM\Column(type="string", length=255)      
*/     
private $imageName;
....

Is it possible to save in $fileName not only the file name but also the path? I mean that in a private $imageName will stored string a/b/abcdef.jpg instead string abcdef.jpg.

If i used custom file namer, witch create file name like i need (some/path/to/filename.jpeg), part with path is not saved after persist entity. I think before saving the file name is filtered to remove any paths in it.

Using custom naming for directories also does not save the path in the $fileName variable and the database.

How save in property $fileName, filename with path?

Question

All 10 comments

No one knows?

Try with a listener

After more than half a day i found a solution.

I used PropertyMappingFactory for get uri_prefix of my entity mapper.

use Vich\UploaderBundle\Mapping\PropertyMappingFactory
$user->uploadPath = $this->propertyMappingFactory->fromField($user, 'avatarFile')->getUriPrefix();

After this, for get the current uploaded fileName id you have to work in your saveFile function. If you don't use the saveFile function, it will not work ( I tried with all doctrine lifecycle ).

So, in my case:

In controller

use Vich\UploaderBundle\Mapping\PropertyMappingFactory;
...

public function __construct(
    PropertyMappingFactory $propertyMappingFactory
)
{
    $this->propertyMappingFactory = $propertyMappingFactory;
}

...

    $user->setAvatarFile($uploadedFile);
    $user->uploadPath = $this->propertyMappingFactory->fromField($user, 'avatarFile')->getUriPrefix();

User entity

/**
 * @ORM\Embedded(class="Vich\UploaderBundle\Entity\File")
 *
 * @ORM\Column(nullable=true)
 */
protected $avatar;

/**
 * @var string|null
 *
 * @ORM\Column(nullable=true)
 */
protected $avatarPath;

/**
 * @Vich\UploadableField(mapping="profile_picture", fileNameProperty="avatar")
 *
 * @var File
 */
protected $avatarFile;

public $uploadPath;

...

public function setAvatarFile($avatarFile = null)
{
    $this->avatarFile = $avatarFile;

    if (null !== $avatarFile) {
        $this->avatarPath = $this->uploadPath . '/' . $this->avatar;
    }
}

I have another solution.

At first time I tried using events, but it didn't work. If you use events, the path to the file is saved in the database, but the file itself is not written to the path. Instead, the file is saved in the path to "upload_destination". This parameter is hard-coded. But I need to save to a dynamic folder by the current date (year-month). To do this, you must use "directory_namer".

public function onVichUploaderPostUpload(Event $event)
{
    /** @var $object Image */
    $object = $event->getObject();
    $mapping = $event->getMapping();

    if ($object instanceof Image) {
        $imageName = [
            $mapping->getUriPrefix(),
            $mapping->getUploadDir($object),
            $object->getImageName(),
        ];

        $object->setImageName(implode('/', $imageName));
    }
}

vich_uploader:
    db_driver: orm
    mappings:
        gallery:
            uri_prefix: /gallery
            upload_destination: '%kernel.project_dir%/public/gallery'

            directory_namer: 
                service: Vich\UploaderBundle\Naming\CurrentDateTimeDirectoryNamer

                options: { date_time_format: 'Y-m' } # will create directory "2019-04" for curent date "2019-04-23"

The problem is that if you try to delete / replace the file, the bundle will not be able to find the file.

For example:

  • in DB file with path saved as string /gallery/2019-05/filename.jpeg
  • but bundle try find them in /gallery/2019-05/gallery/2019-05/filename.jpeg OR /gallery/2019-09/gallery/2019-05/filename.jpeg (if current date September for example).

In other words:

  • if you use "directory_namer", you will not be able to delete / replace the file, because "directory_namer" adds "uri_prefix" and date each time.
  • but even without "directory_name" is not possible, because when you save the file to disk will not create all the necessary subfolders.

The filename and path must be controlled by the same service. But as part of using "storage: filesystem" I couldn't find a simple solution.

The solution I was found by using the Flysystem driver. This driver controls the filepaths and the files themselves - you just need to pass a string with the /some/path/filename. Flysystem will create the necessary folders and put the file in them. With this approach, you do not need:

  • use events
  • add methods to the entity
  • change controller
  • or use "directory_namer"

Solution

vich_uploader:
    db_driver: orm
    storage: flysystem
    mappings:
        gallery:
            uri_prefix: /gallery
            upload_destination: public_filesystem
            namer: App\Namer\VichUploadWithDatedPathNamer

oneup_flysystem:
    adapters:
        public_adapter:
            local:
                directory: '%kernel.project_dir%/public'
    filesystems:
        public_filesystem:
            adapter: public_adapter
            mount: public_filesystem
            alias: League\Flysystem\Filesystem

// my namer with dinamical directory name
// src/Namer/VichUploadWithDatedPathNamer.php

namespace App\Namer;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\PropertyMapping;
use Vich\UploaderBundle\Naming\NamerInterface;
use Vich\UploaderBundle\Util\FilenameUtils;
use Vich\UploaderBundle\Util\Transliterator;

class VichUploadWithDatedPathNamer implements NamerInterface
{
    public function name($object, PropertyMapping $mapping): string
    {
        /* @var $file UploadedFile */
        $file = $mapping->getFile($object);

        [$clientOriginalName] = FilenameUtils::spitNameByExtension($file->getClientOriginalName());

        // prepare file original name
        $clientOriginalName = Transliterator::transliterate($clientOriginalName);

        // create path with filename
        $filepath = implode('/', [
            $mapping->getUriPrefix(),
            date('Y-m', time()),
            $clientOriginalName . '_' . uniqid() . '.' . $file->guessExtension()
        ]);

        return $filepath;
    }
}

You should not use CurrentDateTimeDirectoryNamer without a property that store the date in your uploadable object. Indeed, the fact that such namer is allowing an empty property is a bug IMHO and should be fixed.

In any case, without "directory_namer" the required folders are not created if you use the filesystem driver, even if the file name has a path from the folders.

Bundle have different processing behavior "name" and "directory_namer" for different drivers to access storage.

I re-read the entire thread, but still I don't get why you want to store the path with the filename. The path is supposed to be used to store the filename, while the actual path is resolved by configuration (possibly with a directory_namer)

Maybe I think wrong, but I cant see reason to split the filename and path. If we want get file, we will need both parts. For example, if separate file name and path:

  • in Twig need concatenate {{ entity.path ~ entity.filename }}
  • OR use another method in Entity->getImageWithPath() which make such a concatenation. In fact, this is the same, but with the addition of an additional method and field to the entity.

In any case, the file is located in a single path. So why separate them if you need to merge them again to access the file.

What is the advantage of separation?

You must use provided helper, that is abstracting the logic of retrieving path and name

I re-read the entire thread, but still I don't get why you want to store the path with the filename. The path is supposed to be used to store the filename, while the actual path is resolved by configuration (possibly with a directory_namer)

I am going with you, however storage systems like S3 for example refer to the filename as some sort of key. This causes problems when wanting to manipulate the contentUrl to get a signed URL.

This is how I solved:

$mapping = $this->propertyMappingFactory->fromField($fileObject, 'file');
$prefix = $mapping->getDirectoryNamer()->directoryName($fileObject, $mapping);

$filenameWithPath = sprintf('%s/%s', $prefix, $fileObject->getFilePath());
Was this page helpful?
0 / 5 - 0 ratings

Related issues

jcg678 picture jcg678  路  6Comments

nobady90 picture nobady90  路  3Comments

petrjirasek picture petrjirasek  路  7Comments

webkmua picture webkmua  路  4Comments

archie18 picture archie18  路  6Comments