YamlDirectoryDiscovery.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. namespace Drupal\Component\Discovery;
  3. use Drupal\Component\FileSystem\RegexDirectoryIterator;
  4. use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
  5. use Drupal\Component\Serialization\Yaml;
  6. use Drupal\Component\FileCache\FileCacheFactory;
  7. /**
  8. * Discovers multiple YAML files in a set of directories.
  9. */
  10. class YamlDirectoryDiscovery implements DiscoverableInterface {
  11. /**
  12. * Defines the key in the discovered data where the file path is stored.
  13. */
  14. const FILE_KEY = '_discovered_file_path';
  15. /**
  16. * An array of directories to scan, keyed by the provider.
  17. *
  18. * The value can either be a string or an array of strings. The string values
  19. * should be the path of a directory to scan.
  20. *
  21. * @var array
  22. */
  23. protected $directories = [];
  24. /**
  25. * The suffix for the file cache key.
  26. *
  27. * @var string
  28. */
  29. protected $fileCacheKeySuffix;
  30. /**
  31. * The key contained in the discovered data that identifies it.
  32. *
  33. * @var string
  34. */
  35. protected $idKey;
  36. /**
  37. * Constructs a YamlDirectoryDiscovery object.
  38. *
  39. * @param array $directories
  40. * An array of directories to scan, keyed by the provider. The value can
  41. * either be a string or an array of strings. The string values should be
  42. * the path of a directory to scan.
  43. * @param string $file_cache_key_suffix
  44. * The file cache key suffix. This should be unique for each type of
  45. * discovery.
  46. * @param string $key
  47. * (optional) The key contained in the discovered data that identifies it.
  48. * Defaults to 'id'.
  49. */
  50. public function __construct(array $directories, $file_cache_key_suffix, $key = 'id') {
  51. $this->directories = $directories;
  52. $this->fileCacheKeySuffix = $file_cache_key_suffix;
  53. $this->idKey = $key;
  54. }
  55. /**
  56. * {@inheritdoc}
  57. */
  58. public function findAll() {
  59. $all = [];
  60. $files = $this->findFiles();
  61. $file_cache = FileCacheFactory::get('yaml_discovery:' . $this->fileCacheKeySuffix);
  62. // Try to load from the file cache first.
  63. foreach ($file_cache->getMultiple(array_keys($files)) as $file => $data) {
  64. $all[$files[$file]][$this->getIdentifier($file, $data)] = $data;
  65. unset($files[$file]);
  66. }
  67. // If there are files left that were not returned from the cache, load and
  68. // parse them now. This list was flipped above and is keyed by filename.
  69. if ($files) {
  70. foreach ($files as $file => $provider) {
  71. // If a file is empty or its contents are commented out, return an empty
  72. // array instead of NULL for type consistency.
  73. try {
  74. $data = Yaml::decode(file_get_contents($file)) ?: [];
  75. }
  76. catch (InvalidDataTypeException $e) {
  77. throw new DiscoveryException("The $file contains invalid YAML", 0, $e);
  78. }
  79. $data[static::FILE_KEY] = $file;
  80. $all[$provider][$this->getIdentifier($file, $data)] = $data;
  81. $file_cache->set($file, $data);
  82. }
  83. }
  84. return $all;
  85. }
  86. /**
  87. * Gets the identifier from the data.
  88. *
  89. * @param string $file
  90. * The filename.
  91. * @param array $data
  92. * The data from the YAML file.
  93. *
  94. * @return string
  95. * The identifier from the data.
  96. */
  97. protected function getIdentifier($file, array $data) {
  98. if (!isset($data[$this->idKey])) {
  99. throw new DiscoveryException("The $file contains no data in the identifier key '{$this->idKey}'");
  100. }
  101. return $data[$this->idKey];
  102. }
  103. /**
  104. * Returns an array of providers keyed by file path.
  105. *
  106. * @return array
  107. * An array of providers keyed by file path.
  108. */
  109. protected function findFiles() {
  110. $file_list = [];
  111. foreach ($this->directories as $provider => $directories) {
  112. $directories = (array) $directories;
  113. foreach ($directories as $directory) {
  114. if (is_dir($directory)) {
  115. /** @var \SplFileInfo $fileInfo */
  116. foreach ($this->getDirectoryIterator($directory) as $fileInfo) {
  117. $file_list[$fileInfo->getPathname()] = $provider;
  118. }
  119. }
  120. }
  121. }
  122. return $file_list;
  123. }
  124. /**
  125. * Gets an iterator to loop over the files in the provided directory.
  126. *
  127. * This method exists so that it is easy to replace this functionality in a
  128. * class that extends this one. For example, it could be used to make the scan
  129. * recursive.
  130. *
  131. * @param string $directory
  132. * The directory to scan.
  133. *
  134. * @return \Traversable
  135. * An \Traversable object or array where the values are \SplFileInfo
  136. * objects.
  137. */
  138. protected function getDirectoryIterator($directory) {
  139. return new RegexDirectoryIterator($directory, '/\.yml$/i');
  140. }
  141. }