You are here

class FileStorage in Drupal 9

Same name in this branch
  1. 9 core/modules/file/src/FileStorage.php \Drupal\file\FileStorage
  2. 9 core/lib/Drupal/Core/Config/FileStorage.php \Drupal\Core\Config\FileStorage
  3. 9 core/lib/Drupal/Component/PhpStorage/FileStorage.php \Drupal\Component\PhpStorage\FileStorage
Same name and namespace in other branches
  1. 8 core/lib/Drupal/Core/Config/FileStorage.php \Drupal\Core\Config\FileStorage

Defines the file storage.

Hierarchy

Expanded class hierarchy of FileStorage

17 files declare their use of FileStorage
CachedStorageTest.php in core/tests/Drupal/KernelTests/Core/Config/Storage/CachedStorageTest.php
ConfigAfterInstallerTestBase.php in core/tests/Drupal/FunctionalTests/Installer/ConfigAfterInstallerTestBase.php
ConfigFileContentTest.php in core/tests/Drupal/KernelTests/Core/Config/ConfigFileContentTest.php
ConfigInstallProfileOverrideTest.php in core/modules/config/tests/src/Functional/ConfigInstallProfileOverrideTest.php
ConfigSchemaTest.php in core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php

... See full list

1 string reference to 'FileStorage'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses FileStorage
config.storage.sync in core/core.services.yml
Drupal\Core\Config\FileStorage

File

core/lib/Drupal/Core/Config/FileStorage.php, line 14

Namespace

Drupal\Core\Config
View source
class FileStorage implements StorageInterface {

  /**
   * The storage collection.
   *
   * @var string
   */
  protected $collection;

  /**
   * The filesystem path for configuration objects.
   *
   * @var string
   */
  protected $directory = '';

  /**
   * The file cache object.
   *
   * @var \Drupal\Component\FileCache\FileCacheInterface
   */
  protected $fileCache;

  /**
   * Constructs a new FileStorage.
   *
   * @param string $directory
   *   A directory path to use for reading and writing of configuration files.
   * @param string $collection
   *   (optional) The collection to store configuration in. Defaults to the
   *   default collection.
   */
  public function __construct($directory, $collection = StorageInterface::DEFAULT_COLLECTION) {
    $this->directory = $directory;
    $this->collection = $collection;

    // Use a NULL File Cache backend by default. This will ensure only the
    // internal static caching of FileCache is used and thus avoids blowing up
    // the APCu cache.
    $this->fileCache = FileCacheFactory::get('config', [
      'cache_backend_class' => NULL,
    ]);
  }

  /**
   * Returns the path to the configuration file.
   *
   * @return string
   *   The path to the configuration file.
   */
  public function getFilePath($name) {
    return $this
      ->getCollectionDirectory() . '/' . $name . '.' . static::getFileExtension();
  }

  /**
   * Returns the file extension used by the file storage for all configuration files.
   *
   * @return string
   *   The file extension.
   */
  public static function getFileExtension() {
    return 'yml';
  }

  /**
   * Check if the directory exists and create it if not.
   */
  protected function ensureStorage() {
    $dir = $this
      ->getCollectionDirectory();
    $success = $this
      ->getFileSystem()
      ->prepareDirectory($dir, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);

    // Only create .htaccess file in root directory.
    if ($dir == $this->directory) {
      $success = $success && FileSecurity::writeHtaccess($this->directory);
    }
    if (!$success) {
      throw new StorageException('Failed to create config directory ' . $dir);
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function exists($name) {
    return file_exists($this
      ->getFilePath($name));
  }

  /**
   * Implements Drupal\Core\Config\StorageInterface::read().
   *
   * @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
   */
  public function read($name) {
    if (!$this
      ->exists($name)) {
      return FALSE;
    }
    $filepath = $this
      ->getFilePath($name);
    if ($data = $this->fileCache
      ->get($filepath)) {
      return $data;
    }
    $data = file_get_contents($filepath);
    try {
      $data = $this
        ->decode($data);
    } catch (InvalidDataTypeException $e) {
      throw new UnsupportedDataTypeConfigException('Invalid data type in config ' . $name . ', found in file ' . $filepath . ': ' . $e
        ->getMessage());
    }
    $this->fileCache
      ->set($filepath, $data);
    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public function readMultiple(array $names) {
    $list = [];
    foreach ($names as $name) {
      if ($data = $this
        ->read($name)) {
        $list[$name] = $data;
      }
    }
    return $list;
  }

  /**
   * {@inheritdoc}
   */
  public function write($name, array $data) {
    try {
      $encoded_data = $this
        ->encode($data);
    } catch (InvalidDataTypeException $e) {
      throw new StorageException("Invalid data type in config {$name}: {$e->getMessage()}");
    }
    $target = $this
      ->getFilePath($name);
    $status = @file_put_contents($target, $encoded_data);
    if ($status === FALSE) {

      // Try to make sure the directory exists and try writing again.
      $this
        ->ensureStorage();
      $status = @file_put_contents($target, $encoded_data);
    }
    if ($status === FALSE) {
      throw new StorageException('Failed to write configuration file: ' . $this
        ->getFilePath($name));
    }
    else {
      $this
        ->getFileSystem()
        ->chmod($target);
    }
    $this->fileCache
      ->set($target, $data);
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function delete($name) {
    if (!$this
      ->exists($name)) {
      return FALSE;
    }
    $this->fileCache
      ->delete($this
      ->getFilePath($name));
    return $this
      ->getFileSystem()
      ->unlink($this
      ->getFilePath($name));
  }

  /**
   * {@inheritdoc}
   */
  public function rename($name, $new_name) {
    $status = @rename($this
      ->getFilePath($name), $this
      ->getFilePath($new_name));
    if ($status === FALSE) {
      return FALSE;
    }
    $this->fileCache
      ->delete($this
      ->getFilePath($name));
    $this->fileCache
      ->delete($this
      ->getFilePath($new_name));
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function encode($data) {
    return Yaml::encode($data);
  }

  /**
   * {@inheritdoc}
   */
  public function decode($raw) {
    $data = Yaml::decode($raw);

    // A simple string is valid YAML for any reason.
    if (!is_array($data)) {
      return FALSE;
    }
    return $data;
  }

  /**
   * {@inheritdoc}
   */
  public function listAll($prefix = '') {
    $dir = $this
      ->getCollectionDirectory();
    if (!is_dir($dir)) {
      return [];
    }
    $extension = '.' . static::getFileExtension();

    // glob() directly calls into libc glob(), which is not aware of PHP stream
    // wrappers. Same for \GlobIterator (which additionally requires an absolute
    // realpath() on Windows).
    // @see https://github.com/mikey179/vfsStream/issues/2
    $files = scandir($dir);
    $names = [];
    $pattern = '/^' . preg_quote($prefix, '/') . '.*' . preg_quote($extension, '/') . '$/';
    foreach ($files as $file) {
      if ($file[0] !== '.' && preg_match($pattern, $file)) {
        $names[] = basename($file, $extension);
      }
    }
    return $names;
  }

  /**
   * {@inheritdoc}
   */
  public function deleteAll($prefix = '') {
    $files = $this
      ->listAll($prefix);
    $success = !empty($files);
    foreach ($files as $name) {
      if (!$this
        ->delete($name) && $success) {
        $success = FALSE;
      }
    }
    if ($success && $this->collection != StorageInterface::DEFAULT_COLLECTION) {

      // Remove empty directories.
      if (!(new \FilesystemIterator($this
        ->getCollectionDirectory()))
        ->valid()) {
        $this
          ->getFileSystem()
          ->rmdir($this
          ->getCollectionDirectory());
      }
    }
    return $success;
  }

  /**
   * {@inheritdoc}
   */
  public function createCollection($collection) {
    return new static($this->directory, $collection);
  }

  /**
   * {@inheritdoc}
   */
  public function getCollectionName() {
    return $this->collection;
  }

  /**
   * {@inheritdoc}
   */
  public function getAllCollectionNames() {
    if (!is_dir($this->directory)) {
      return [];
    }
    $collections = $this
      ->getAllCollectionNamesHelper($this->directory);
    sort($collections);
    return $collections;
  }

  /**
   * Helper function for getAllCollectionNames().
   *
   * If the file storage has the following subdirectory structure:
   *   ./another_collection/one
   *   ./another_collection/two
   *   ./collection/sub/one
   *   ./collection/sub/two
   * this function will return:
   * @code
   *   array(
   *     'another_collection.one',
   *     'another_collection.two',
   *     'collection.sub.one',
   *     'collection.sub.two',
   *   );
   * @endcode
   *
   * @param string $directory
   *   The directory to check for sub directories. This allows this
   *   function to be used recursively to discover all the collections in the
   *   storage. It is the responsibility of the caller to ensure the directory
   *   exists.
   *
   * @return array
   *   A list of collection names contained within the provided directory.
   */
  protected function getAllCollectionNamesHelper($directory) {
    $collections = [];
    $pattern = '/\\.' . preg_quote($this
      ->getFileExtension(), '/') . '$/';
    foreach (new \DirectoryIterator($directory) as $fileinfo) {
      if ($fileinfo
        ->isDir() && !$fileinfo
        ->isDot()) {
        $collection = $fileinfo
          ->getFilename();

        // Recursively call getAllCollectionNamesHelper() to discover if there
        // are subdirectories. Subdirectories represent a dotted collection
        // name.
        $sub_collections = $this
          ->getAllCollectionNamesHelper($directory . '/' . $collection);
        if (!empty($sub_collections)) {

          // Build up the collection name by concatenating the subdirectory
          // names with the current directory name.
          foreach ($sub_collections as $sub_collection) {
            $collections[] = $collection . '.' . $sub_collection;
          }
        }

        // Check that the collection is valid by searching it for configuration
        // objects. A directory without any configuration objects is not a valid
        // collection.
        // @see \Drupal\Core\Config\FileStorage::listAll()
        foreach (scandir($directory . '/' . $collection) as $file) {
          if ($file[0] !== '.' && preg_match($pattern, $file)) {
            $collections[] = $collection;
            break;
          }
        }
      }
    }
    return $collections;
  }

  /**
   * Gets the directory for the collection.
   *
   * @return string
   *   The directory for the collection.
   */
  protected function getCollectionDirectory() {
    if ($this->collection == StorageInterface::DEFAULT_COLLECTION) {
      $dir = $this->directory;
    }
    else {
      $dir = $this->directory . '/' . str_replace('.', '/', $this->collection);
    }
    return $dir;
  }

  /**
   * Returns file system service.
   *
   * @return \Drupal\Core\File\FileSystemInterface
   *   The file system service.
   */
  private function getFileSystem() {
    return \Drupal::service('file_system');
  }

}

Members

Namesort descending Modifiers Type Description Overrides
FileStorage::$collection protected property The storage collection.
FileStorage::$directory protected property The filesystem path for configuration objects. 1
FileStorage::$fileCache protected property The file cache object.
FileStorage::createCollection public function Creates a collection on the storage. Overrides StorageInterface::createCollection 1
FileStorage::decode public function Decodes configuration data from the storage-specific format. Overrides StorageInterface::decode
FileStorage::delete public function Deletes a configuration object from the storage. Overrides StorageInterface::delete 2
FileStorage::deleteAll public function Deletes configuration objects whose names start with a given prefix. Overrides StorageInterface::deleteAll 1
FileStorage::encode public function Encodes configuration data into the storage-specific format. Overrides StorageInterface::encode
FileStorage::ensureStorage protected function Check if the directory exists and create it if not.
FileStorage::exists public function Returns whether a configuration object exists. Overrides StorageInterface::exists 2
FileStorage::getAllCollectionNames public function Gets the existing collections. Overrides StorageInterface::getAllCollectionNames
FileStorage::getAllCollectionNamesHelper protected function Helper function for getAllCollectionNames().
FileStorage::getCollectionDirectory protected function Gets the directory for the collection.
FileStorage::getCollectionName public function Gets the name of the current collection the storage is using. Overrides StorageInterface::getCollectionName
FileStorage::getFileExtension public static function Returns the file extension used by the file storage for all configuration files.
FileStorage::getFilePath public function Returns the path to the configuration file. 1
FileStorage::getFileSystem private function Returns file system service.
FileStorage::listAll public function Gets configuration object names starting with a given prefix. Overrides StorageInterface::listAll 1
FileStorage::read public function Implements Drupal\Core\Config\StorageInterface::read(). Overrides StorageInterface::read
FileStorage::readMultiple public function Reads configuration data from the storage. Overrides StorageInterface::readMultiple
FileStorage::rename public function Renames a configuration object in the storage. Overrides StorageInterface::rename 1
FileStorage::write public function Writes configuration data to the storage. Overrides StorageInterface::write 1
FileStorage::__construct public function Constructs a new FileStorage. 2
StorageInterface::DEFAULT_COLLECTION constant The default collection name.