FileStorage.php 6.51 KB
Newer Older
1 2
<?php

3 4 5 6 7
/**
 * @file
 * Definition of Drupal\Core\Config\FileStorage.
 */

8 9
namespace Drupal\Core\Config;

10
use Drupal\Component\Utility\String;
11
use Symfony\Component\Yaml\Dumper;
12
use Symfony\Component\Yaml\Exception\DumpException;
13
use Symfony\Component\Yaml\Parser;
14

15
/**
16
 * Defines the file storage.
17
 */
18
class FileStorage implements StorageInterface {
19 20

  /**
21
   * The filesystem path for configuration objects.
22
   *
23
   * @var string
24
   */
25
  protected $directory = '';
26

27 28 29
  /**
   * A shared YAML dumper instance.
   *
30
   * @var \Symfony\Component\Yaml\Dumper
31 32 33 34 35 36
   */
  protected $dumper;

  /**
   * A shared YAML parser instance.
   *
37
   * @var \Symfony\Component\Yaml\Parser
38 39 40
   */
  protected $parser;

41
  /**
42
   * Constructs a new FileStorage.
43 44 45
   *
   * @param string $directory
   *   A directory path to use for reading and writing of configuration files.
46
   */
47 48
  public function __construct($directory) {
    $this->directory = $directory;
49 50 51
  }

  /**
52
   * Returns the path to the configuration file.
53
   *
54 55
   * @return string
   *   The path to the configuration file.
56
   */
57
  public function getFilePath($name) {
58
    return $this->directory . '/' . $name . '.' . static::getFileExtension();
59 60 61 62 63 64 65 66 67 68
  }

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

71 72 73 74 75 76 77 78 79 80 81 82
  /**
   * Check if the directory exists and create it if not.
   */
  protected function ensureStorage() {
    $success = file_prepare_directory($this->directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
    $success = $success && file_save_htaccess($this->directory, TRUE, TRUE);
    if (!$success) {
      throw new StorageException("Failed to create config directory {$this->directory}");
    }
    return $this;
  }

83
  /**
84
   * Implements Drupal\Core\Config\StorageInterface::exists().
85
   */
86 87
  public function exists($name) {
    return file_exists($this->getFilePath($name));
88 89 90
  }

  /**
91
   * Implements Drupal\Core\Config\StorageInterface::read().
92
   *
93
   * @throws Symfony\Component\Yaml\Exception\ParseException
94
   */
95 96
  public function read($name) {
    if (!$this->exists($name)) {
97
      return FALSE;
98 99 100 101 102 103
    }
    $data = file_get_contents($this->getFilePath($name));
    // @todo Yaml throws a ParseException on invalid data. Is it expected to be
    //   caught or not?
    $data = $this->decode($data);
    return $data;
104 105
  }

106 107 108 109 110 111 112 113 114 115 116 117 118
  /**
   * {@inheritdoc}
   */
  public function readMultiple(array $names) {
    $list = array();
    foreach ($names as $name) {
      if ($data = $this->read($name)) {
        $list[$name] = $data;
      }
    }
    return $list;
  }

119
  /**
120
   * {@inheritdoc}
121
   */
122
  public function write($name, array $data) {
123 124 125 126
    try {
      $data = $this->encode($data);
    }
    catch(DumpException $e) {
127
      throw new StorageException(String::format('Invalid data type for used in config: @name', array('@name' => $name)));
128 129
    }

130 131
    $target = $this->getFilePath($name);
    $status = @file_put_contents($target, $data);
132 133 134 135 136
    if ($status === FALSE) {
      // Try to make sure the directory exists and try witing again.
      $this->ensureStorage();
      $status = @file_put_contents($target, $data);
    }
137 138
    if ($status === FALSE) {
      throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name));
139
    }
140 141 142
    else {
      drupal_chmod($target);
    }
143
    return TRUE;
144 145 146
  }

  /**
147
   * Implements Drupal\Core\Config\StorageInterface::delete().
148
   */
149 150
  public function delete($name) {
    if (!$this->exists($name)) {
151 152
      if (!file_exists($this->directory)) {
        throw new StorageException($this->directory . '/ not found.');
153 154 155 156
      }
      return FALSE;
    }
    return drupal_unlink($this->getFilePath($name));
157
  }
158

159
  /**
160
   * Implements Drupal\Core\Config\StorageInterface::rename().
161 162 163 164 165 166 167 168 169
   */
  public function rename($name, $new_name) {
    $status = @rename($this->getFilePath($name), $this->getFilePath($new_name));
    if ($status === FALSE) {
      throw new StorageException('Failed to rename configuration file from: ' . $this->getFilePath($name) . ' to: ' . $this->getFilePath($new_name));
    }
    return TRUE;
  }

170 171 172 173 174 175 176 177
  /**
   * Gets the YAML dumper instance.
   *
   * @return Symfony\Component\Yaml\Dumper
   */
  protected function getDumper() {
    if (!isset($this->dumper)) {
      $this->dumper = new Dumper();
178 179 180
      // Set Yaml\Dumper's default indentation for nested nodes/collections to
      // 2 spaces for consistency with Drupal coding standards.
      $this->dumper->setIndentation(2);
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    }
    return $this->dumper;
  }

  /**
   * Gets the YAML parser instance.
   *
   * @return Symfony\Component\Yaml\Parser
   */
  protected function getParser() {
    if (!isset($this->parser)) {
      $this->parser = new Parser();
    }
    return $this->parser;
  }

197
  /**
198 199 200
   * Implements Drupal\Core\Config\StorageInterface::encode().
   *
   * @throws Symfony\Component\Yaml\Exception\DumpException
201
   */
202
  public function encode($data) {
203
    // The level where you switch to inline YAML is set to PHP_INT_MAX to ensure
204 205 206
    // this does not occur. Also set the exceptionOnInvalidType parameter to
    // TRUE, so exceptions are thrown for an invalid data type.
    return $this->getDumper()->dump($data, PHP_INT_MAX, 0, TRUE);
207 208 209
  }

  /**
210 211 212
   * Implements Drupal\Core\Config\StorageInterface::decode().
   *
   * @throws Symfony\Component\Yaml\Exception\ParseException
213
   */
214 215
  public function decode($raw) {
    $data = $this->getParser()->parse($raw);
216 217 218
    // A simple string is valid YAML for any reason.
    if (!is_array($data)) {
      return FALSE;
219
    }
220
    return $data;
221 222 223
  }

  /**
224
   * Implements Drupal\Core\Config\StorageInterface::listAll().
225
   */
226 227 228
  public function listAll($prefix = '') {
    // glob() silently ignores the error of a non-existing search directory,
    // even with the GLOB_ERR flag.
229
    if (!file_exists($this->directory)) {
230
      return array();
231
    }
232
    $extension = '.' . static::getFileExtension();
233 234
    // \GlobIterator on Windows requires an absolute path.
    $files = new \GlobIterator(realpath($this->directory) . '/' . $prefix . '*' . $extension);
235 236 237 238 239 240 241

    $names = array();
    foreach ($files as $file) {
      $names[] = $file->getBasename($extension);
    }

    return $names;
242
  }
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

  /**
   * Implements Drupal\Core\Config\StorageInterface::deleteAll().
   */
  public function deleteAll($prefix = '') {
    $success = TRUE;
    $files = $this->listAll($prefix);
    foreach ($files as $name) {
      if (!$this->delete($name) && $success) {
        $success = FALSE;
      }
    }

    return $success;
  }
258
}