2 namespace TYPO3\CMS\Core\Configuration\Loader
;
5 * This file is part of the TYPO3 CMS project.
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use Symfony\Component\Yaml\Yaml
;
18 use TYPO3\CMS\Core\Utility\GeneralUtility
;
21 * A Yaml file loader that allows to load YAML files, based on the Symfony/Yaml component
23 * In addition to just load a yaml file, it adds some special functionality.
25 * - A special "imports" key in the yaml file allows to include other yaml files recursively
26 * where the actual yaml file gets loaded after the import statements, which are interpreted at the very beginning
28 * - Merging configuration options of import files when having simple "lists" will add items to the list instead
29 * of overwriting them.
31 * - Special placeholder values set via %optionA.suboptionB% replace the value with the named path of the configuration
32 * The placeholders will act as a full replacement of this value.
38 * Loads and parses a YAML file, and returns an array with the found data
40 * @param string $fileName either relative to PATH_site or prefixed with EXT:...
41 * @return array the configuration as array
42 * @throws \RuntimeException when the file is empty or is of invalid format
44 public function load(string $fileName): array
46 $content = $this->getFileContents($fileName);
47 $content = Yaml
::parse($content);
49 if (!is_array($content)) {
50 throw new \
RuntimeException('YAML file "' . $fileName . '" could not be parsed into valid syntax, probably empty?', 1497332874);
53 $content = $this->processImports($content);
55 // Check for "%" placeholders
56 $content = $this->processPlaceholders($content, $content);
62 * Put into a separate method to ease the pains with unit tests
64 * @param string $fileName either relative to PATH_site or prefixed with EXT:...
66 * @return string the contents of the file
67 * @throws \RuntimeException when the file was not accessible
69 protected function getFileContents(string $fileName): string
71 $streamlinedFileName = GeneralUtility
::getFileAbsFileName($fileName);
72 if (!$streamlinedFileName) {
73 throw new \
RuntimeException('YAML File "' . $fileName . '" could not be loaded', 1485784246);
75 return file_get_contents($streamlinedFileName);
79 * Checks for the special "imports" key on the main level of a file,
80 * which calls "load" recursively.
81 * @param array $content
85 protected function processImports(array $content): array
87 if (isset($content['imports']) && is_array($content['imports'])) {
88 foreach ($content['imports'] as $import) {
89 $importedContent = $this->load($import['resource']);
90 // override the imported content with the one from the current file
91 $content = $this->merge($importedContent, $content);
93 unset($content['imports']);
99 * Main function that gets called recursively to check for %...% placeholders
102 * @param array $content the current sub-level content array
103 * @param array $referenceArray the global configuration array
105 * @return array the modified sub-level content array
107 protected function processPlaceholders(array $content, array $referenceArray): array
109 foreach ($content as $k => $v) {
110 if ($this->isPlaceholder($v)) {
111 $content[$k] = $this->getValueFromReferenceArray($v, $referenceArray);
112 } elseif (is_array($v)) {
113 $content[$k] = $this->processPlaceholders($v, $referenceArray);
120 * Returns the value for a placeholder as fetched from the referenceArray
122 * @param string $placeholder the string to search for
123 * @param array $referenceArray the main configuration array where to look up the data
125 * @return array|mixed|string
127 protected function getValueFromReferenceArray(string $placeholder, array $referenceArray)
129 $pointer = trim($placeholder, '%');
130 $parts = explode('.', $pointer);
131 $referenceData = $referenceArray;
132 foreach ($parts as $part) {
133 if (isset($referenceData[$part])) {
134 $referenceData = $referenceData[$part];
136 // return unsubstituted placeholder
140 if ($this->isPlaceholder($referenceData)) {
141 $referenceData = $this->getValueFromReferenceArray($referenceData, $referenceArray);
143 return $referenceData;
147 * Checks if a value is a string and begins and ends with %...%
149 * @param mixed $value the probe to check for
152 protected function isPlaceholder($value): bool
154 return is_string($value) && substr($value, 0, 1) === '%' && substr($value, -1) === '%';
158 * Same as array_replace_recursive except that when in simple arrays (= yaml lists), the entries are
159 * appended (array_merge)
166 protected function merge(array $val1, array $val2): array
168 // Simple lists get merged / added up
169 if (count(array_filter(array_keys($val1), 'is_int')) === count($val1)) {
170 return array_merge($val1, $val2);
172 foreach ($val1 as $k => $v) {
173 // The key also exists in second array, if it is a simple value
174 // then $val2 will override the value, where an array is calling merge() recursively.
175 if (isset($val2[$k])) {
176 if (is_array($v) && isset($val2[$k])) {
177 if (is_array($val2[$k])) {
178 $val1[$k] = $this->merge($v, $val2[$k]);
180 $val1[$k] = $val2[$k];
183 $val1[$k] = $val2[$k];
188 // If there are properties in the second array left, they are added up
190 foreach ($val2 as $k => $v) {