TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
InheritancesResolverService.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Form\Mvc\Configuration;
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 
30 {
31 
35  const INHERITANCE_OPERATOR = '__inheritances';
36 
43  protected $referenceConfiguration = [];
44 
51  protected $inheritanceStack = [];
52 
59  protected $inheritancePathToCkeck = '';
60 
69  public static function create(array $configuration = []): InheritancesResolverService
70  {
72  $inheritancesResolverService = GeneralUtility::makeInstance(ObjectManager::class)
73  ->get(self::class);
74  $inheritancesResolverService->setReferenceConfiguration($configuration);
75  return $inheritancesResolverService;
76  }
77 
85  public function reset()
86  {
87  $this->referenceConfiguration = [];
88  $this->inheritanceStack = [];
89  $this->inheritancePathToCkeck = '';
90  return $this;
91  }
92 
101  {
102  $this->referenceConfiguration = $referenceConfiguration;
103  return $this;
104  }
105 
114  public function getResolvedConfiguration(): array
115  {
116  $configuration = $this->resolve($this->referenceConfiguration);
117  $configuration = $this->removeInheritanceOperatorRecursive($configuration);
118  return $configuration;
119  }
120 
130  protected function resolve(
131  array $configuration,
132  array $pathStack = [],
133  bool $setInheritancePathToCkeck = true
134  ): array {
135  foreach ($configuration as $key => $values) {
136  $pathStack[] = $key;
137  $path = implode('.', $pathStack);
138 
139  $this->throwExceptionIfCycleInheritances($path, $path);
140  if ($setInheritancePathToCkeck) {
141  $this->inheritancePathToCkeck = $path;
142  }
143 
144  if (is_array($configuration[$key])) {
145  if (isset($configuration[$key][self::INHERITANCE_OPERATOR])) {
146  try {
147  $inheritances = ArrayUtility::getValueByPath(
148  $this->referenceConfiguration,
149  $path . '.' . self::INHERITANCE_OPERATOR,
150  '.'
151  );
152  } catch (\RuntimeException $exception) {
153  $inheritances = null;
154  }
155 
156  if (is_array($inheritances)) {
157  $inheritedConfigurations = $this->resolveInheritancesRecursive($inheritances);
158 
159  $configuration[$key] = $this->mergeRecursiveWithOverrule(
160  $inheritedConfigurations,
161  $configuration[$key]
162  );
163  }
164 
165  unset($configuration[$key][self::INHERITANCE_OPERATOR]);
166  }
167 
168  if (!empty($configuration[$key])) {
169  $configuration[$key] = $this->resolve(
170  $configuration[$key],
171  $pathStack
172  );
173  }
174  }
175  array_pop($pathStack);
176  }
177 
178  return $configuration;
179  }
180 
189  protected function resolveInheritancesRecursive(array $inheritances): array
190  {
191  ksort($inheritances);
192  $inheritedConfigurations = [];
193  foreach ($inheritances as $inheritancePath) {
194  $this->throwExceptionIfCycleInheritances($inheritancePath, $inheritancePath);
195  try {
196  $inheritedConfiguration = ArrayUtility::getValueByPath(
197  $this->referenceConfiguration,
198  $inheritancePath,
199  '.'
200  );
201  } catch (\RuntimeException $exception) {
202  $inheritedConfiguration = null;
203  }
204 
205  if (
206  isset($inheritedConfiguration[self::INHERITANCE_OPERATOR])
207  && count($inheritedConfiguration) === 1
208  ) {
209  if ($this->inheritancePathToCkeck === $inheritancePath) {
210  throw new CycleInheritancesException(
211  $this->inheritancePathToCkeck . ' has cycle inheritances',
212  1474900796
213  );
214  }
215 
216  $inheritedConfiguration = $this->resolveInheritancesRecursive(
217  $inheritedConfiguration[self::INHERITANCE_OPERATOR]
218  );
219  } else {
220  $pathStack = explode('.', $inheritancePath);
221  $key = array_pop($pathStack);
222  $newConfiguration = [
223  $key => $inheritedConfiguration
224  ];
225  $inheritedConfiguration = $this->resolve(
226  $newConfiguration,
227  $pathStack,
228  false
229  );
230  $inheritedConfiguration = $inheritedConfiguration[$key];
231  }
232 
233  $inheritedConfigurations = $this->mergeRecursiveWithOverrule(
234  $inheritedConfigurations,
235  $inheritedConfiguration
236  );
237  }
238 
239  return $inheritedConfigurations;
240  }
241 
251  protected function throwExceptionIfCycleInheritances(string $path, string $pathToCheck)
252  {
253  try {
254  $configuration = ArrayUtility::getValueByPath(
255  $this->referenceConfiguration,
256  $path,
257  '.'
258  );
259  } catch (\RuntimeException $exception) {
260  $configuration = null;
261  }
262 
263  if (isset($configuration[self::INHERITANCE_OPERATOR])) {
264  try {
265  $inheritances = ArrayUtility::getValueByPath(
266  $this->referenceConfiguration,
267  $path . '.' . self::INHERITANCE_OPERATOR,
268  '.'
269  );
270  } catch (\RuntimeException $exception) {
271  $inheritances = null;
272  }
273 
274  if (is_array($inheritances)) {
275  foreach ($inheritances as $inheritancePath) {
276  try {
277  $configuration = ArrayUtility::getValueByPath(
278  $this->referenceConfiguration,
279  $inheritancePath,
280  '.'
281  );
282  } catch (\RuntimeException $exception) {
283  $configuration = null;
284  }
285 
286  if (isset($configuration[self::INHERITANCE_OPERATOR])) {
287  try {
288  $_inheritances = ArrayUtility::getValueByPath(
289  $this->referenceConfiguration,
290  $inheritancePath . '.' . self::INHERITANCE_OPERATOR,
291  '.'
292  );
293  } catch (\RuntimeException $exception) {
294  $_inheritances = null;
295  }
296 
297  foreach ($_inheritances as $_inheritancePath) {
298  if (strpos($pathToCheck, $_inheritancePath) === 0) {
299  throw new CycleInheritancesException(
300  $pathToCheck . ' has cycle inheritances',
301  1474900797
302  );
303  }
304  }
305  }
306 
307  if (
308  is_array($this->inheritanceStack[$pathToCheck])
309  && in_array($inheritancePath, $this->inheritanceStack[$pathToCheck])
310  ) {
311  $this->inheritanceStack[$pathToCheck][] = $inheritancePath;
312  throw new CycleInheritancesException(
313  $pathToCheck . ' has cycle inheritances',
314  1474900799
315  );
316  }
317  $this->inheritanceStack[$pathToCheck][] = $inheritancePath;
318  $this->throwExceptionIfCycleInheritances($inheritancePath, $pathToCheck);
319  }
320  $this->inheritanceStack[$pathToCheck] = null;
321  }
322  }
323  }
324 
331  protected function removeInheritanceOperatorRecursive(array $array): array
332  {
333  $result = $array;
334  foreach ($result as $key => $value) {
335  if ($key === self::INHERITANCE_OPERATOR) {
336  unset($result[$key]);
337  continue;
338  }
339 
340  if (is_array($value)) {
341  $result[$key] = $this->removeInheritanceOperatorRecursive($value);
342  }
343  }
344  return $result;
345  }
346 
370  protected function mergeRecursiveWithOverrule(
371  array $firstArray,
372  array $secondArray,
373  bool $dontAddNewKeys = false,
374  bool $emptyValuesOverride = true
375  ): array {
376  foreach ($secondArray as $key => $value) {
377  if (
378  array_key_exists($key, $firstArray)
379  && is_array($firstArray[$key])
380  ) {
381  if (is_array($secondArray[$key])) {
382  $firstArray[$key] = $this->mergeRecursiveWithOverrule(
383  $firstArray[$key],
384  $secondArray[$key],
385  $dontAddNewKeys,
386  $emptyValuesOverride
387  );
388  } else {
389  $firstArray[$key] = $secondArray[$key];
390  }
391  } else {
392  if ($dontAddNewKeys) {
393  if (array_key_exists($key, $firstArray)) {
394  if ($emptyValuesOverride || !empty($value)) {
395  $firstArray[$key] = $value;
396  }
397  }
398  } else {
399  if ($emptyValuesOverride || !empty($value)) {
400  $firstArray[$key] = $value;
401  }
402  }
403  }
404  }
405  reset($firstArray);
406  return $firstArray;
407  }
408 }
static getValueByPath(array $array, $path, $delimiter= '/')
mergeRecursiveWithOverrule(array $firstArray, array $secondArray, bool $dontAddNewKeys=false, bool $emptyValuesOverride=true)
resolve(array $configuration, array $pathStack=[], bool $setInheritancePathToCkeck=true)
static makeInstance($className,...$constructorArguments)