class FileUploadResource
File upload resource.
This is implemented as a field-level resource for the following reasons:
- Validation for uploaded files is tied to fields (allowed extensions, max size, etc..).
- The actual files do not need to be stored in another temporary location, to be later moved when they are referenced from a file field.
- Permission to upload a file can be determined by a users field level create access to the file field.
Attributes
#[RestResource(id: "file:upload", label: new TranslatableMarkup("File Upload"), serialization_class: File::class, uri_paths: [
"create" => "/file/upload/{entity_type_id}/{bundle}/{field_name}",
])]
  Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements \Drupal\Component\Plugin\PluginInspectionInterface, \Drupal\Component\Plugin\DerivativeInspectionInterface- class \Drupal\Core\Plugin\PluginBase uses \Drupal\Core\DependencyInjection\AutowiredInstanceTrait, \Drupal\Core\StringTranslation\StringTranslationTrait, \Drupal\Core\DependencyInjection\DependencySerializationTrait, \Drupal\Core\Messenger\MessengerTrait extends \Drupal\Component\Plugin\PluginBase- class \Drupal\rest\Plugin\ResourceBase implements \Drupal\Core\Plugin\ContainerFactoryPluginInterface, \Drupal\rest\Plugin\ResourceInterface extends \Drupal\Core\Plugin\PluginBase- class \Drupal\file\Plugin\rest\resource\FileUploadResource uses \Drupal\file\Validation\FileValidatorSettingsTrait, \Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait, \Drupal\file\Upload\FileUploadLocationTrait extends \Drupal\rest\Plugin\ResourceBase
 
 
- class \Drupal\rest\Plugin\ResourceBase implements \Drupal\Core\Plugin\ContainerFactoryPluginInterface, \Drupal\rest\Plugin\ResourceInterface extends \Drupal\Core\Plugin\PluginBase
 
- class \Drupal\Core\Plugin\PluginBase uses \Drupal\Core\DependencyInjection\AutowiredInstanceTrait, \Drupal\Core\StringTranslation\StringTranslationTrait, \Drupal\Core\DependencyInjection\DependencySerializationTrait, \Drupal\Core\Messenger\MessengerTrait extends \Drupal\Component\Plugin\PluginBase
Expanded class hierarchy of FileUploadResource
File
- 
              core/modules/ file/ src/ Plugin/ rest/ resource/ FileUploadResource.php, line 51 
Namespace
Drupal\file\Plugin\rest\resourceView source
class FileUploadResource extends ResourceBase {
  use FileValidatorSettingsTrait;
  use EntityResourceValidationTrait {
    validate as resourceValidate;
  }
  use FileUploadLocationTrait {
    getUploadLocation as getUploadDestination;
  }
  public function __construct(array $configuration, $plugin_id, $plugin_definition, $serializer_formats, LoggerInterface $logger, protected FileSystemInterface $fileSystem, protected EntityTypeManagerInterface $entityTypeManager, protected EntityFieldManagerInterface $entityFieldManager, protected FileValidatorInterface $fileValidator, protected InputStreamFileWriterInterface $inputStreamFileWriter, protected FileUploadHandler $fileUploadHandler) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container->getParameter('serializer.formats'), $container->get('logger.factory')
      ->get('rest'), $container->get('file_system'), $container->get('entity_type.manager'), $container->get('entity_field.manager'), $container->get('file.validator'), $container->get('file.input_stream_file_writer'), $container->get('file.upload_handler'));
  }
  
  /**
   * {@inheritdoc}
   */
  public function permissions() {
    // Access to this resource depends on field-level access so no explicit
    // permissions are required.
    // @see \Drupal\file\Plugin\rest\resource\FileUploadResource::validateAndLoadFieldDefinition()
    // @see \Drupal\rest\Plugin\rest\resource\EntityResource::permissions()
    return [];
  }
  
  /**
   * Creates a file from an endpoint.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The current request.
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string $bundle
   *   The entity bundle. This will be the same as $entity_type_id for entity
   *   types that don't support bundles.
   * @param string $field_name
   *   The field name.
   *
   * @return \Drupal\rest\ModifiedResourceResponse
   *   A 201 response, on success.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Thrown when temporary files cannot be written, a lock cannot be acquired,
   *   or when temporary files cannot be moved to their new location.
   */
  public function post(Request $request, $entity_type_id, $bundle, $field_name) {
    $field_definition = $this->validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name);
    $destination = $this->getUploadDestination($field_definition);
    // Check the destination file path is writable.
    if (!$this->fileSystem
      ->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY)) {
      throw new HttpException(500, 'Destination file path is not writable');
    }
    $settings = $field_definition->getSettings();
    $validators = $this->getFileUploadValidators($settings);
    if (!array_key_exists('FileExtension', $validators) && $settings['file_extensions'] === '') {
      // An empty string means 'all file extensions' but the FileUploadHandler
      // needs the FileExtension entry to be present and empty in order for this
      // to be respected. An empty array means 'all file extensions'.
      // @see \Drupal\file\Upload\FileUploadHandler::handleExtensionValidation
      $validators['FileExtension'] = [];
    }
    try {
      $filename = ContentDispositionFilenameParser::parseFilename($request);
      $tempPath = $this->inputStreamFileWriter
        ->writeStreamToFile();
      $uploadedFile = new InputStreamUploadedFile($filename, $filename, $tempPath, @filesize($tempPath));
      $result = $this->fileUploadHandler
        ->handleFileUpload($uploadedFile, $validators, $destination, FileExists::Rename, FALSE);
    } catch (LockAcquiringException $e) {
      throw new HttpException(503, $e->getMessage(), NULL, [
        'Retry-After' => 1,
      ]);
    } catch (UploadException $e) {
      $this->logger
        ->error('Input data could not be read');
      throw new HttpException(500, 'Input file data could not be read', $e);
    } catch (CannotWriteFileException $e) {
      $this->logger
        ->error('Temporary file data for could not be written');
      throw new HttpException(500, 'Temporary file data could not be written', $e);
    } catch (NoFileException $e) {
      $this->logger
        ->error('Temporary file could not be opened for file upload');
      throw new HttpException(500, 'Temporary file could not be opened', $e);
    } catch (FileExistsException $e) {
      throw new HttpException(statusCode: 500, message: $e->getMessage(), previous: $e);
    } catch (FileException) {
      throw new HttpException(500, 'Temporary file could not be moved to file location');
    }
    if ($result->hasViolations()) {
      $message = "Unprocessable Entity: file validation failed.\n";
      $errors = [];
      foreach ($result->getViolations() as $violation) {
        $errors[] = PlainTextOutput::renderFromHtml($violation->getMessage());
      }
      $message .= implode("\n", $errors);
      throw new UnprocessableEntityHttpException($message);
    }
    // 201 Created responses return the newly created entity in the response
    // body. These responses are not cacheable, so we add no cacheability
    // metadata here.
    return new ModifiedResourceResponse($result->getFile(), 201);
  }
  
  /**
   * Validates and loads a field definition instance.
   *
   * @param string $entity_type_id
   *   The entity type ID the field is attached to.
   * @param string $bundle
   *   The bundle the field is attached to.
   * @param string $field_name
   *   The field name.
   *
   * @return \Drupal\Core\Field\FieldDefinitionInterface
   *   The field definition.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
   *   Thrown when the field does not exist.
   * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException
   *   Thrown when the target type of the field is not a file, or the current
   *   user does not have 'edit' access for the field.
   */
  protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) {
    $field_definitions = $this->entityFieldManager
      ->getFieldDefinitions($entity_type_id, $bundle);
    if (!isset($field_definitions[$field_name])) {
      throw new NotFoundHttpException(sprintf('Field "%s" does not exist', $field_name));
    }
    /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
    $field_definition = $field_definitions[$field_name];
    if ($field_definition->getSetting('target_type') !== 'file') {
      throw new AccessDeniedHttpException(sprintf('"%s" is not a file field', $field_name));
    }
    $entity_access_control_handler = $this->entityTypeManager
      ->getAccessControlHandler($entity_type_id);
    $bundle = $this->entityTypeManager
      ->getDefinition($entity_type_id)
      ->hasKey('bundle') ? $bundle : NULL;
    $access_result = $entity_access_control_handler->createAccess($bundle, NULL, [], TRUE)
      ->andIf($entity_access_control_handler->fieldAccess('edit', $field_definition, NULL, NULL, TRUE));
    if (!$access_result->isAllowed()) {
      throw new AccessDeniedHttpException($access_result->getReason());
    }
    return $field_definition;
  }
  
  /**
   * {@inheritdoc}
   */
  protected function getBaseRoute($canonical_path, $method) {
    return new Route($canonical_path, [
      '_controller' => RequestHandler::class . '::handleRaw',
    ], $this->getBaseRouteRequirements($method), [], '', [], [
      $method,
    ]);
  }
  
  /**
   * {@inheritdoc}
   */
  protected function getBaseRouteRequirements($method) {
    $requirements = parent::getBaseRouteRequirements($method);
    // Add the content type format access check. This will enforce that all
    // incoming requests can only use the 'application/octet-stream'
    // Content-Type header.
    $requirements['_content_type_format'] = 'bin';
    return $requirements;
  }
  
  /**
   * Generates a lock ID based on the file URI.
   *
   * @param string $file_uri
   *   The file URI.
   *
   * @return string
   *   The generated lock ID.
   */
  protected static function generateLockIdFromFileUri($file_uri) {
    return 'file:rest:' . Crypt::hashBase64($file_uri);
  }
}Members
| Title Sort descending | Deprecated | Modifiers | Object type | Summary | Member alias | Overriden Title | Overrides | 
|---|---|---|---|---|---|---|---|
| AutowiredInstanceTrait::createInstanceAutowired | public static | function | Instantiates a new instance of the implementing class using autowiring. | ||||
| DependencySerializationTrait::$_entityStorages | protected | property | An array of entity type IDs keyed by the property name of their storages. | ||||
| DependencySerializationTrait::$_serviceIds | protected | property | An array of service IDs keyed by property name used for serialization. | ||||
| DependencySerializationTrait::__sleep | public | function | 2 | ||||
| DependencySerializationTrait::__wakeup | public | function | 2 | ||||
| EntityResourceValidationTrait::validate | protected | function | Verifies that an entity does not violate any validation constraints. | Aliased as: resourceValidate | |||
| FileUploadLocationTrait::getUploadLocation | public | function | Resolves the file upload location from a file field definition. | Aliased as: getUploadDestination | |||
| FileUploadResource::create | public static | function | Instantiates a new instance of the implementing class using autowiring. | Overrides ResourceBase::create | |||
| FileUploadResource::generateLockIdFromFileUri | protected static | function | Generates a lock ID based on the file URI. | ||||
| FileUploadResource::getBaseRoute | protected | function | Gets the base route for a particular method. | Overrides ResourceBase::getBaseRoute | |||
| FileUploadResource::getBaseRouteRequirements | protected | function | Gets the base route requirements for a particular method. | Overrides ResourceBase::getBaseRouteRequirements | |||
| FileUploadResource::permissions | public | function | Implements ResourceInterface::permissions(). | Overrides ResourceBase::permissions | |||
| FileUploadResource::post | public | function | Creates a file from an endpoint. | ||||
| FileUploadResource::validateAndLoadFieldDefinition | protected | function | Validates and loads a field definition instance. | ||||
| FileUploadResource::__construct | public | function | Constructs a Drupal\rest\Plugin\ResourceBase object. | Overrides ResourceBase::__construct | |||
| FileValidatorSettingsTrait::getFileUploadValidators | public | function | Gets the upload validators for the specified settings. | ||||
| MessengerTrait::$messenger | protected | property | The messenger. | 25 | |||
| MessengerTrait::messenger | public | function | Gets the messenger. | 25 | |||
| MessengerTrait::setMessenger | public | function | Sets the messenger. | ||||
| PluginBase::$configuration | protected | property | Configuration information passed into the plugin. | 1 | |||
| PluginBase::$pluginDefinition | protected | property | The plugin implementation definition. | 1 | |||
| PluginBase::$pluginId | protected | property | The plugin ID. | ||||
| PluginBase::DERIVATIVE_SEPARATOR | constant | A string which is used to separate base plugin IDs from the derivative ID. | |||||
| PluginBase::getBaseId | public | function | Gets the base_plugin_id of the plugin instance. | Overrides DerivativeInspectionInterface::getBaseId | |||
| PluginBase::getDerivativeId | public | function | Gets the derivative_id of the plugin instance. | Overrides DerivativeInspectionInterface::getDerivativeId | |||
| PluginBase::getPluginDefinition | public | function | Gets the definition of the plugin implementation. | Overrides PluginInspectionInterface::getPluginDefinition | 2 | ||
| PluginBase::getPluginId | public | function | Gets the plugin ID of the plugin instance. | Overrides PluginInspectionInterface::getPluginId | |||
| PluginBase::isConfigurable | Deprecated | public | function | Determines if the plugin is configurable. | |||
| ResourceBase::$logger | protected | property | A logger instance. | ||||
| ResourceBase::$serializerFormats | protected | property | The available serialization formats. | ||||
| ResourceBase::availableMethods | public | function | Returns the available HTTP request methods on this plugin. | Overrides ResourceInterface::availableMethods | 1 | ||
| ResourceBase::requestMethods | protected | function | Provides predefined HTTP request methods. | ||||
| ResourceBase::routes | public | function | Returns a collection of routes with URL path information for the resource. | Overrides ResourceInterface::routes | |||
| StringTranslationTrait::$stringTranslation | protected | property | The string translation service. | 3 | |||
| StringTranslationTrait::formatPlural | protected | function | Formats a string containing a count of items. | ||||
| StringTranslationTrait::getNumberOfPlurals | protected | function | Returns the number of plurals supported by a given language. | ||||
| StringTranslationTrait::getStringTranslation | protected | function | Gets the string translation service. | ||||
| StringTranslationTrait::setStringTranslation | public | function | Sets the string translation service to use. | 2 | |||
| StringTranslationTrait::t | protected | function | Translates a string to the current language or to a given language. | 1 | 
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.
