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?
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 other words:
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:
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 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());