php 52 lines · 8 steps

Layered config merging in PHP

A resolver folds any number of override layers onto defaults with a recursive deep merge and list deduplication.

Explained by highlit
1final class ConfigResolver
2{
3 private array $merged;
4 
5 public function __construct(array $defaults, array ...$overrides)
6 {
7 $this->merged = array_reduce(
8 $overrides,
9 fn (array $carry, array $layer): array => $this->deepMerge($carry, $layer),
10 $defaults
11 );
12 }
13 
14 public function all(): array
15 {
16 return $this->merged;
17 }
18 
19 private function deepMerge(array $base, array $layer): array
20 {
21 foreach ($layer as $key => $value) {
22 if (is_int($key)) {
23 $base[] = $value;
24 continue;
25 }
26 
27 if (is_array($value) && isset($base[$key]) && is_array($base[$key])) {
28 $base[$key] = $this->deepMerge($base[$key], $value);
29 continue;
30 }
31 
32 $base[$key] = $value;
33 }
34 
35 return $this->dedupeLists($base);
36 }
37 
38 private function dedupeLists(array $config): array
39 {
40 foreach ($config as $key => $value) {
41 if (!is_array($value)) {
42 continue;
43 }
44 
45 $config[$key] = array_is_list($value)
46 ? array_values(array_unique($value, SORT_REGULAR))
47 : $this->dedupeLists($value);
48 }
49 
50 return $config;
51 }
52}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Folding layers with array_reduce turns any number of overrides into one clean accumulation.
  2. 2Distinguishing integer keys from string keys lets one merge routine handle both lists and maps.
  3. 3Recursing into nested arrays keeps deep structures merged rather than blindly overwritten.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Layered config merging in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code