TYPO3 CMS  TYPO3_8-7
InheritancesResolverService.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
22 
62 {
63 
67  const INHERITANCE_OPERATOR = '__inheritances';
68 
75  protected $referenceConfiguration = [];
76 
83  protected $inheritanceStack = [];
84 
91  protected $inheritancePathToCkeck = '';
92 
101  public static function create(array $configuration = []): InheritancesResolverService
102  {
104  $inheritancesResolverService = GeneralUtility::makeInstance(ObjectManager::class)
105  ->get(self::class);
106  $inheritancesResolverService->setReferenceConfiguration($configuration);
107  return $inheritancesResolverService;
108  }
109 
117  public function reset()
118  {
119  $this->referenceConfiguration = [];
120  $this->inheritanceStack = [];
121  $this->inheritancePathToCkeck = '';
122  return $this;
123  }
124 
133  {
134  $this->referenceConfiguration = $referenceConfiguration;
135  return $this;
136  }
137 
146  public function getResolvedConfiguration(): array
147  {
148  $configuration = $this->resolve($this->referenceConfiguration);
149  $configuration = $this->removeInheritanceOperatorRecursive($configuration);
150  return $configuration;
151  }
152 
167  protected function resolve(
168  array $configuration,
169  array $pathStack = [],
170  bool $setInheritancePathToCkeck = true
171  ): array {
172  foreach ($configuration as $key => $values) {
173  //add current key to pathStack
174  $pathStack[] = $key;
175  $path = implode('.', $pathStack);
176 
177  //check endless loop for current path
178  $this->throwExceptionIfCycleInheritances($path, $path);
179 
180  //overwrite service property 'inheritancePathToCheck' with current path
181  if ($setInheritancePathToCkeck) {
182  $this->inheritancePathToCkeck = $path;
183  }
184 
185  //if value of subnode is an array, perform a deep search iteration step
186  if (is_array($configuration[$key])) {
187  if (isset($configuration[$key][self::INHERITANCE_OPERATOR])) {
188  $inheritances = $this->getValueByPath($this->referenceConfiguration, $path . '.' . self::INHERITANCE_OPERATOR);
189 
190  //and replace the __inheritance operator by the respective partial
191  if (is_array($inheritances)) {
192  $inheritedConfigurations = $this->resolveInheritancesRecursive($inheritances);
193  $configuration[$key] = array_replace_recursive($inheritedConfigurations, $configuration[$key]);
194  }
195 
196  //remove the inheritance operator from configuration
197  unset($configuration[$key][self::INHERITANCE_OPERATOR]);
198  }
199 
200  if (!empty($configuration[$key])) {
201  // resolve subnode of YAML config
202  $configuration[$key] = $this->resolve($configuration[$key], $pathStack);
203  }
204  }
205  array_pop($pathStack);
206  }
207 
208  return $configuration;
209  }
210 
220  protected function resolveInheritancesRecursive(array $inheritances): array
221  {
222  ksort($inheritances);
223  $inheritedConfigurations = [];
224  foreach ($inheritances as $inheritancePath) {
225  $this->throwExceptionIfCycleInheritances($inheritancePath, $inheritancePath);
226  $inheritedConfiguration = $this->getValueByPath($this->referenceConfiguration, $inheritancePath);
227 
228  if (
229  isset($inheritedConfiguration[self::INHERITANCE_OPERATOR])
230  && count($inheritedConfiguration) === 1
231  ) {
232  if ($this->inheritancePathToCkeck === $inheritancePath) {
233  throw new CycleInheritancesException(
234  $this->inheritancePathToCkeck . ' has cycle inheritances',
235  1474900796
236  );
237  }
238 
239  $inheritedConfiguration = $this->resolveInheritancesRecursive(
240  $inheritedConfiguration[self::INHERITANCE_OPERATOR]
241  );
242  } else {
243  $pathStack = explode('.', $inheritancePath);
244  $key = array_pop($pathStack);
245  $newConfiguration = [
246  $key => $inheritedConfiguration
247  ];
248  $inheritedConfiguration = $this->resolve(
249  $newConfiguration,
250  $pathStack,
251  false
252  );
253  $inheritedConfiguration = $inheritedConfiguration[$key];
254  }
255 
256  if ($inheritedConfiguration === null) {
257  throw new CycleInheritancesException(
258  $inheritancePath . ' does not exist within the configuration',
259  1489260796
260  );
261  }
262 
263  $inheritedConfigurations = array_replace_recursive(
264  $inheritedConfigurations,
265  $inheritedConfiguration
266  );
267  }
268 
269  return $inheritedConfigurations;
270  }
271 
279  protected function throwExceptionIfCycleInheritances(string $path, string $pathToCheck)
280  {
281  $configuration = $this->getValueByPath($this->referenceConfiguration, $path);
282 
283  if (isset($configuration[self::INHERITANCE_OPERATOR])) {
284  $inheritances = $this->getValueByPath($this->referenceConfiguration, $path . '.' . self::INHERITANCE_OPERATOR);
285 
286  if (is_array($inheritances)) {
287  foreach ($inheritances as $inheritancePath) {
288  $configuration = $this->getValueByPath($this->referenceConfiguration, $inheritancePath);
289 
290  if (isset($configuration[self::INHERITANCE_OPERATOR])) {
291  $_inheritances = $this->getValueByPath($this->referenceConfiguration, $inheritancePath . '.' . self::INHERITANCE_OPERATOR);
292 
293  foreach ($_inheritances as $_inheritancePath) {
294  if (strpos($pathToCheck, $_inheritancePath) === 0) {
295  throw new CycleInheritancesException(
296  $pathToCheck . ' has cycle inheritances',
297  1474900797
298  );
299  }
300  }
301  }
302 
303  if (
304  is_array($this->inheritanceStack[$pathToCheck])
305  && in_array($inheritancePath, $this->inheritanceStack[$pathToCheck])
306  ) {
307  $this->inheritanceStack[$pathToCheck][] = $inheritancePath;
308  throw new CycleInheritancesException(
309  $pathToCheck . ' has cycle inheritances',
310  1474900799
311  );
312  }
313  $this->inheritanceStack[$pathToCheck][] = $inheritancePath;
314  $this->throwExceptionIfCycleInheritances($inheritancePath, $pathToCheck);
315  }
316  $this->inheritanceStack[$pathToCheck] = null;
317  }
318  }
319  }
320 
327  protected function removeInheritanceOperatorRecursive(array $array): array
328  {
329  $result = $array;
330  foreach ($result as $key => $value) {
331  if ($key === self::INHERITANCE_OPERATOR) {
332  unset($result[$key]);
333  continue;
334  }
335 
336  if (is_array($value)) {
337  $result[$key] = $this->removeInheritanceOperatorRecursive($value);
338  }
339  }
340  return $result;
341  }
342 
352  protected function getValueByPath(array $config, string $path, string $delimiter = '.')
353  {
354  try {
355  return ArrayUtility::getValueByPath($config, $path, $delimiter);
356  } catch (\RuntimeException $exception) {
357  return null;
358  }
359  }
360 }
resolve(array $configuration, array $pathStack=[], bool $setInheritancePathToCkeck=true)
static getValueByPath(array $array, $path, $delimiter='/')
static makeInstance($className,... $constructorArguments)
getValueByPath(array $config, string $path, string $delimiter='.')