Creating a file entity programmatically in Drupal (versions 8, 9, and 10) sounds like a trivial task but there are some "gotchas" to be aware of. Let's dig in and explore tricky little things you need to know about.
Let's say that you want to copy a file from your custom module to the local file system path where public files are stored (usually sites/default/files), and then create a File entity.
The first thing you need to do is to get the file path of your file:
/** @var \Drupal\Core\Extension\ExtensionList $extension_list */
$extension_list = \Drupal::service('extension.list.module');
$filepath = $extension_list->getPath('MY_MODULE') . '/assets/MY_FILE.txt';
As you can see the MY_FILE.txt file is located under MY_MODULE/assets directory and we are using the extension.list.module service to get the file path.
Now let's copy the file to a public directory by using the following code:
use Drupal\Core\File\FileSystemInterface;
$directory = 'public://my-dir';
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = \Drupal::service('file_system');
$file_system->prepareDirectory($directory, FileSystemInterface:: CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
$file_system->copy($filepath, $directory . '/' . basename($filepath), FileSystemInterface::EXISTS_REPLACE);
We have to be sure that the destination directory exists, so we are using the prepareDirectory() method to create it. This method will create the directory if it doesn't exist and it will also make sure that the directory is writable.
If you don't prepare the directory, you might encounter the following error:
The specified file 'modules/custom/MY_MODULE/assets/MY_FILE.txt' could not be copied because the destination directory 'public://my-dir' is not properly configured. This may be caused by a problem with file or directory permissions.
Finally, we can now create a File entity programmatically:
use Drupal\file\Entity\File;
$file = File::create([
'filename' => basename($filepath),
'uri' => 'public://my-dir/' . basename($filepath),
'status' => 1,
'uid' => 1,
]);
$file->save();
But creating a file entity is not enough. You also have to mark the file as used, otherwise, you risk losing it depending on your file settings. To mark the file as used you can use the file.usage service.
/** @var \Drupal\file\FileUsage\DatabaseFileUsageBackend $file_usage */
$file_usage = \Drupal::service('file.usage');
$file_usage->add($file, 'MY_MODULE', 'node', 1);
The 3rd and 4th parameters of the add() method are type and ID, and they can be any string. In our example, we are saying that our file is used by a node with the ID of 1.
To find more information about the possibility of losing your files please read the following change record.
Function file_save_data()
If you don't have a file to copy, but you want to create the file from scratch you can use the file_save_data function. It will save the file to the specified location and create a database entry for it.
use Drupal\Core\File\FileSystemInterface;
$data = 'Text file example content';
$file = file_save_data($data, 'public://my-dir/MY_FILE.txt', FileSystemInterface::EXISTS_REPLACE);
The function will return a fully loaded file entity or FALSE on error.
Function file_save_data() is deprecated in Drupal 9.3 and it'll be removed in drupal 10, so you should use the file.repository service instead of.
use Drupal\Core\File\FileSystemInterface;
$data = 'Text file example content';
/** @var \Drupal\file\FileRepositoryInterface $fileRepository */
$fileRepository = \Drupal::service('file.repository');
$fileRepository->writeData($data, "public://my-dir/MY_FILE.txt", FileSystemInterface::EXISTS_REPLACE);
How to programmatically attach a file to an entity
Let's say that you want to upload a file programmatically to a field.
Just load your node and set the file ID like this:
$entity_storage = \Drupal::entityTypeManager()->getStorage('node');
$entity = $entity_storage->load(1);
$entity->set('field_text_file', ['target_id' => $file->id()]);
$entity->save();
or set the entire file entity object like this:
$entity_storage = \Drupal::entityTypeManager()->getStorage('node');
$entity = $entity_storage->load(1);
$entity->set('field_text_file', $file);
$entity->save();
Note about file_exists() function
Drupal registers a stream wrapper so that URIs such as public://my-dir/MY_FILE.txt are expanded to a normal filesystem path such as sites/default/files/my-dir/MY_FILE.txt. This basically means that even though it might seem that this shouldn't work, it actually works perfectly fine.
file_exists('public://my-dir/MY_FILE.txt')
That's a wrap! Hopefully, this helped you learn more about creating and uploading files programmatically in Drupal. If you are interested I also have an article about creating media entities programmatically in Drupal.