1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core;
4:
5: use Salient\Contract\Core\GraphInterface;
6: use Salient\Utility\Exception\InvalidArgumentTypeException;
7: use OutOfRangeException;
8: use ReturnTypeWillChange;
9:
10: /**
11: * @api
12: */
13: class Graph implements GraphInterface
14: {
15: protected object $Object;
16: /** @var mixed[] */
17: protected array $Array;
18: protected bool $IsObject;
19: protected bool $AddMissingProperties;
20: protected bool $AddMissingKeys;
21: /** @var string[] */
22: protected array $Path = [];
23:
24: /**
25: * Creates a new Graph object
26: */
27: public function __construct(
28: &$value = [],
29: bool $addMissingKeys = false,
30: bool $addMissingProperties = false
31: ) {
32: if (is_object($value)) {
33: $this->Object = $value;
34: $this->IsObject = true;
35: } elseif (is_array($value)) {
36: $this->Array = &$value;
37: $this->IsObject = false;
38: } else {
39: throw new InvalidArgumentTypeException(1, 'value', 'mixed[]|object', $value);
40: }
41: $this->AddMissingProperties = $addMissingProperties;
42: $this->AddMissingKeys = $addMissingKeys;
43: }
44:
45: /**
46: * @inheritDoc
47: */
48: public function getValue()
49: {
50: return $this->IsObject ? $this->Object : $this->Array;
51: }
52:
53: /**
54: * @inheritDoc
55: */
56: public function getPath(): array
57: {
58: return $this->Path;
59: }
60:
61: /**
62: * @param array-key $offset
63: */
64: public function offsetExists($offset): bool
65: {
66: return $this->IsObject
67: ? property_exists($this->Object, (string) $offset)
68: : array_key_exists($offset, $this->Array);
69: }
70:
71: /**
72: * @param array-key $offset
73: * @disregard P1038
74: */
75: #[ReturnTypeWillChange]
76: public function offsetGet($offset)
77: {
78: if (!$this->offsetExists($offset)) {
79: if (
80: ($this->IsObject && !$this->AddMissingProperties)
81: || (!$this->IsObject && !$this->AddMissingKeys)
82: ) {
83: throw new OutOfRangeException(sprintf('Offset not found: %s', $offset));
84: }
85: if ($this->IsObject) {
86: $this->Object->$offset = [];
87: } else {
88: $this->Array[$offset] = [];
89: }
90: }
91:
92: $value = $this->IsObject
93: ? $this->Object->$offset
94: : $this->Array[$offset];
95:
96: if (!is_object($value) && !is_array($value)) {
97: return $value;
98: }
99:
100: $value = $this->IsObject
101: ? new static($this->Object->$offset)
102: : new static($this->Array[$offset]);
103:
104: $value->AddMissingProperties = $this->AddMissingProperties;
105: $value->AddMissingKeys = $this->AddMissingKeys;
106: $value->Path = $this->Path;
107: $value->Path[] = $offset;
108: return $value;
109: }
110:
111: /**
112: * @param array-key|null $offset
113: */
114: public function offsetSet($offset, $value): void
115: {
116: if ($this->IsObject) {
117: if ($offset === null) {
118: throw new OutOfRangeException('Invalid offset');
119: }
120: $this->Object->$offset = $value;
121: } elseif ($offset === null) {
122: $this->Array[] = $value;
123: } else {
124: $this->Array[$offset] = $value;
125: }
126: }
127:
128: /**
129: * @param array-key $offset
130: */
131: public function offsetUnset($offset): void
132: {
133: if ($this->IsObject) {
134: unset($this->Object->$offset);
135: } else {
136: unset($this->Array[$offset]);
137: }
138: }
139: }
140: