1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core;
4:
5: use Salient\Contract\Core\Pipeline\ArrayMapperInterface;
6: use Salient\Core\Exception\InvalidDataException;
7: use Salient\Utility\Arr;
8: use ValueError;
9:
10: /**
11: * @api
12: */
13: final class ArrayMapper implements ArrayMapperInterface
14: {
15: /** @var array-key[]|null */
16: private ?array $OutputKeys = null;
17: /** @var array<array-key,array-key> */
18: private array $OutputMap;
19: private bool $RemoveNull;
20: private bool $AddUnmapped;
21: private bool $RequireMapped;
22: private bool $AddMissing;
23: /** @var array<array-key,true> */
24: private array $InputKeyIndex;
25:
26: /**
27: * Creates a new array mapper
28: *
29: * Default behaviour:
30: *
31: * - ignore missing values (maps for which the input array has no data)
32: * - keep unmapped values (input that isn't mapped to a different key)
33: * - keep `null` values
34: *
35: * @api
36: *
37: * @param array<array-key,array-key|array-key[]> $keyMap An array that maps
38: * input keys to one or multiple output keys.
39: * @param ArrayMapper::CONFORMITY_* $conformity Use
40: * {@see ArrayMapper::CONFORMITY_COMPLETE} wherever possible to improve
41: * performance.
42: * @param int-mask-of<ArrayMapper::REMOVE_NULL|ArrayMapper::ADD_UNMAPPED|ArrayMapper::ADD_MISSING|ArrayMapper::REQUIRE_MAPPED> $flags
43: */
44: public function __construct(
45: array $keyMap,
46: int $conformity = ArrayMapper::CONFORMITY_NONE,
47: int $flags = ArrayMapper::ADD_UNMAPPED
48: ) {
49: $outKeyMap = [];
50: foreach ($keyMap as $inKey => $outKeys) {
51: foreach ((array) $outKeys as $outKey) {
52: $outKeyMap[$outKey] = $inKey;
53: }
54: }
55:
56: $this->RemoveNull = (bool) ($flags & self::REMOVE_NULL);
57:
58: if (
59: $conformity === self::CONFORMITY_COMPLETE
60: && count($keyMap) === count($outKeyMap)
61: ) {
62: $this->OutputKeys = array_keys($outKeyMap);
63: return;
64: }
65:
66: $this->OutputMap = $outKeyMap;
67: $this->AddUnmapped = (bool) ($flags & self::ADD_UNMAPPED);
68: $this->RequireMapped = (bool) ($flags & self::REQUIRE_MAPPED);
69: $this->AddMissing = !$this->RequireMapped && $flags & self::ADD_MISSING;
70:
71: if ($this->AddUnmapped) {
72: $this->InputKeyIndex = array_fill_keys(array_keys($keyMap), true);
73: }
74: }
75:
76: /**
77: * @inheritDoc
78: */
79: public function map(array $in): array
80: {
81: if ($this->OutputKeys !== null) {
82: try {
83: $out = Arr::combine($this->OutputKeys, $in);
84: } catch (ValueError $ex) {
85: throw new InvalidDataException('Invalid input array', 0, $ex);
86: }
87: } else {
88: $out = [];
89: foreach ($this->OutputMap as $outKey => $inKey) {
90: if (array_key_exists($inKey, $in)) {
91: $out[$outKey] = $in[$inKey];
92: } elseif ($this->AddMissing) {
93: $out[$outKey] = null;
94: } elseif ($this->RequireMapped) {
95: throw new InvalidDataException(sprintf(
96: 'Input key not found: %s',
97: $inKey,
98: ));
99: }
100: }
101:
102: if ($this->AddUnmapped) {
103: $out += array_diff_key($in, $this->InputKeyIndex);
104: }
105: }
106:
107: return $this->RemoveNull
108: ? Arr::whereNotNull($out)
109: : $out;
110: }
111: }
112: