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