[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @see https://github.com/laminas/laminas-zendframework-bridge for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-zendframework-bridge/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-zendframework-bridge/blob/master/LICENSE.md New BSD License 7 */ 8 9 namespace Laminas\ZendFrameworkBridge; 10 11 use function array_intersect_key; 12 use function array_key_exists; 13 use function array_pop; 14 use function array_push; 15 use function count; 16 use function in_array; 17 use function is_array; 18 use function is_callable; 19 use function is_int; 20 use function is_string; 21 22 class ConfigPostProcessor 23 { 24 /** @internal */ 25 const SERVICE_MANAGER_KEYS_OF_INTEREST = [ 26 'aliases' => true, 27 'factories' => true, 28 'invokables' => true, 29 'services' => true, 30 ]; 31 32 /** @var array String keys => string values */ 33 private $exactReplacements = [ 34 'zend-expressive' => 'mezzio', 35 'zf-apigility' => 'api-tools', 36 ]; 37 38 /** @var Replacements */ 39 private $replacements; 40 41 /** @var callable[] */ 42 private $rulesets; 43 44 public function __construct() 45 { 46 $this->replacements = new Replacements(); 47 48 /* Define the rulesets for replacements. 49 * 50 * Each ruleset has the following signature: 51 * 52 * @param mixed $value 53 * @param string[] $keys Full nested key hierarchy leading to the value 54 * @return null|callable 55 * 56 * If no match is made, a null is returned, allowing it to fallback to 57 * the next ruleset in the list. If a match is made, a callback is returned, 58 * and that will be used to perform the replacement on the value. 59 * 60 * The callback should have the following signature: 61 * 62 * @param mixed $value 63 * @param string[] $keys 64 * @return mixed The transformed value 65 */ 66 $this->rulesets = [ 67 // Exact values 68 function ($value) { 69 return is_string($value) && isset($this->exactReplacements[$value]) 70 ? [$this, 'replaceExactValue'] 71 : null; 72 }, 73 74 // Router (MVC applications) 75 // We do not want to rewrite these. 76 function ($value, array $keys) { 77 $key = array_pop($keys); 78 // Only worried about a top-level "router" key. 79 return $key === 'router' && count($keys) === 0 && is_array($value) 80 ? [$this, 'noopReplacement'] 81 : null; 82 }, 83 84 // service- and pluginmanager handling 85 function ($value) { 86 return is_array($value) && array_intersect_key(self::SERVICE_MANAGER_KEYS_OF_INTEREST, $value) !== [] 87 ? [$this, 'replaceDependencyConfiguration'] 88 : null; 89 }, 90 91 // Array values 92 function ($value, array $keys) { 93 return 0 !== count($keys) && is_array($value) 94 ? [$this, '__invoke'] 95 : null; 96 }, 97 ]; 98 } 99 100 /** 101 * @param string[] $keys Hierarchy of keys, for determining location in 102 * nested configuration. 103 * @return array 104 */ 105 public function __invoke(array $config, array $keys = []) 106 { 107 $rewritten = []; 108 109 foreach ($config as $key => $value) { 110 // Determine new key from replacements 111 $newKey = is_string($key) ? $this->replace($key, $keys) : $key; 112 113 // Keep original values with original key, if the key has changed, but only at the top-level. 114 if (empty($keys) && $newKey !== $key) { 115 $rewritten[$key] = $value; 116 } 117 118 // Perform value replacements, if any 119 $newValue = $this->replace($value, $keys, $newKey); 120 121 // Key does not already exist and/or is not an array value 122 if (! array_key_exists($newKey, $rewritten) || ! is_array($rewritten[$newKey])) { 123 // Do not overwrite existing values with null values 124 $rewritten[$newKey] = array_key_exists($newKey, $rewritten) && null === $newValue 125 ? $rewritten[$newKey] 126 : $newValue; 127 continue; 128 } 129 130 // New value is null; nothing to do. 131 if (null === $newValue) { 132 continue; 133 } 134 135 // Key already exists as an array value, but $value is not an array 136 if (! is_array($newValue)) { 137 $rewritten[$newKey][] = $newValue; 138 continue; 139 } 140 141 // Key already exists as an array value, and $value is also an array 142 $rewritten[$newKey] = static::merge($rewritten[$newKey], $newValue); 143 } 144 145 return $rewritten; 146 } 147 148 /** 149 * Perform substitutions as needed on an individual value. 150 * 151 * The $key is provided to allow fine-grained selection of rewrite rules. 152 * 153 * @param mixed $value 154 * @param string[] $keys Key hierarchy 155 * @param null|int|string $key 156 * @return mixed 157 */ 158 private function replace($value, array $keys, $key = null) 159 { 160 // Add new key to the list of keys. 161 // We do not need to remove it later, as we are working on a copy of the array. 162 array_push($keys, $key); 163 164 // Identify rewrite strategy and perform replacements 165 $rewriteRule = $this->replacementRuleMatch($value, $keys); 166 return $rewriteRule($value, $keys); 167 } 168 169 /** 170 * Merge two arrays together. 171 * 172 * If an integer key exists in both arrays, the value from the second array 173 * will be appended to the first array. If both values are arrays, they are 174 * merged together, else the value of the second array overwrites the one 175 * of the first array. 176 * 177 * Based on zend-stdlib Zend\Stdlib\ArrayUtils::merge 178 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 179 * 180 * @return array 181 */ 182 public static function merge(array $a, array $b) 183 { 184 foreach ($b as $key => $value) { 185 if (! isset($a[$key]) && ! array_key_exists($key, $a)) { 186 $a[$key] = $value; 187 continue; 188 } 189 190 if (null === $value && array_key_exists($key, $a)) { 191 // Leave as-is if value from $b is null 192 continue; 193 } 194 195 if (is_int($key)) { 196 $a[] = $value; 197 continue; 198 } 199 200 if (is_array($value) && is_array($a[$key])) { 201 $a[$key] = static::merge($a[$key], $value); 202 continue; 203 } 204 205 $a[$key] = $value; 206 } 207 208 return $a; 209 } 210 211 /** 212 * @param mixed $value 213 * @param null|int|string $key 214 * @return callable Callable to invoke with value 215 */ 216 private function replacementRuleMatch($value, $key = null) 217 { 218 foreach ($this->rulesets as $ruleset) { 219 $result = $ruleset($value, $key); 220 if (is_callable($result)) { 221 return $result; 222 } 223 } 224 return [$this, 'fallbackReplacement']; 225 } 226 227 /** 228 * Replace a value using the translation table, if the value is a string. 229 * 230 * @param mixed $value 231 * @return mixed 232 */ 233 private function fallbackReplacement($value) 234 { 235 return is_string($value) 236 ? $this->replacements->replace($value) 237 : $value; 238 } 239 240 /** 241 * Replace a value matched exactly. 242 * 243 * @param mixed $value 244 * @return mixed 245 */ 246 private function replaceExactValue($value) 247 { 248 return $this->exactReplacements[$value]; 249 } 250 251 private function replaceDependencyConfiguration(array $config) 252 { 253 $aliases = isset($config['aliases']) && is_array($config['aliases']) 254 ? $this->replaceDependencyAliases($config['aliases']) 255 : []; 256 257 if ($aliases) { 258 $config['aliases'] = $aliases; 259 } 260 261 $config = $this->replaceDependencyInvokables($config); 262 $config = $this->replaceDependencyFactories($config); 263 $config = $this->replaceDependencyServices($config); 264 265 $keys = self::SERVICE_MANAGER_KEYS_OF_INTEREST; 266 foreach ($config as $key => $data) { 267 if (isset($keys[$key])) { 268 continue; 269 } 270 271 $config[$key] = is_array($data) ? $this->__invoke($data, [$key]) : $data; 272 } 273 274 return $config; 275 } 276 277 /** 278 * Rewrite dependency aliases array 279 * 280 * In this case, we want to keep the alias as-is, but rewrite the target. 281 * 282 * We need also provide an additional alias if the alias key is a legacy class. 283 * 284 * @return array 285 */ 286 private function replaceDependencyAliases(array $aliases) 287 { 288 foreach ($aliases as $alias => $target) { 289 if (! is_string($alias) || ! is_string($target)) { 290 continue; 291 } 292 293 $newTarget = $this->replacements->replace($target); 294 $newAlias = $this->replacements->replace($alias); 295 296 $notIn = [$newTarget]; 297 $name = $newTarget; 298 while (isset($aliases[$name])) { 299 $notIn[] = $aliases[$name]; 300 $name = $aliases[$name]; 301 } 302 303 if ($newAlias === $alias && ! in_array($alias, $notIn, true)) { 304 $aliases[$alias] = $newTarget; 305 continue; 306 } 307 308 if (isset($aliases[$newAlias])) { 309 continue; 310 } 311 312 if (! in_array($newAlias, $notIn, true)) { 313 $aliases[$alias] = $newAlias; 314 $aliases[$newAlias] = $newTarget; 315 } 316 } 317 318 return $aliases; 319 } 320 321 /** 322 * Rewrite dependency invokables array 323 * 324 * In this case, we want to keep the alias as-is, but rewrite the target. 325 * 326 * We need also provide an additional alias if invokable is defined with 327 * an alias which is a legacy class. 328 * 329 * @return array 330 */ 331 private function replaceDependencyInvokables(array $config) 332 { 333 if (empty($config['invokables']) || ! is_array($config['invokables'])) { 334 return $config; 335 } 336 337 foreach ($config['invokables'] as $alias => $target) { 338 if (! is_string($alias)) { 339 continue; 340 } 341 342 $newTarget = $this->replacements->replace($target); 343 $newAlias = $this->replacements->replace($alias); 344 345 if ($alias === $target || isset($config['aliases'][$newAlias])) { 346 $config['invokables'][$alias] = $newTarget; 347 continue; 348 } 349 350 $config['invokables'][$newAlias] = $newTarget; 351 352 if ($newAlias === $alias) { 353 continue; 354 } 355 356 $config['aliases'][$alias] = $newAlias; 357 358 unset($config['invokables'][$alias]); 359 } 360 361 return $config; 362 } 363 364 /** 365 * @param mixed $value 366 * @return mixed Returns $value verbatim. 367 */ 368 private function noopReplacement($value) 369 { 370 return $value; 371 } 372 373 private function replaceDependencyFactories(array $config) 374 { 375 if (empty($config['factories']) || ! is_array($config['factories'])) { 376 return $config; 377 } 378 379 foreach ($config['factories'] as $service => $factory) { 380 if (! is_string($service)) { 381 continue; 382 } 383 384 $replacedService = $this->replacements->replace($service); 385 $factory = is_string($factory) ? $this->replacements->replace($factory) : $factory; 386 $config['factories'][$replacedService] = $factory; 387 388 if ($replacedService === $service) { 389 continue; 390 } 391 392 unset($config['factories'][$service]); 393 if (isset($config['aliases'][$service])) { 394 continue; 395 } 396 397 $config['aliases'][$service] = $replacedService; 398 } 399 400 return $config; 401 } 402 403 private function replaceDependencyServices(array $config) 404 { 405 if (empty($config['services']) || ! is_array($config['services'])) { 406 return $config; 407 } 408 409 foreach ($config['services'] as $service => $serviceInstance) { 410 if (! is_string($service)) { 411 continue; 412 } 413 414 $replacedService = $this->replacements->replace($service); 415 $serviceInstance = is_array($serviceInstance) ? $this->__invoke($serviceInstance) : $serviceInstance; 416 417 $config['services'][$replacedService] = $serviceInstance; 418 419 if ($service === $replacedService) { 420 continue; 421 } 422 423 unset($config['services'][$service]); 424 425 if (isset($config['aliases'][$service])) { 426 continue; 427 } 428 429 $config['aliases'][$service] = $replacedService; 430 } 431 432 return $config; 433 } 434 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |