Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
/*
6
 * This file is a part of dflydev/dot-access-data.
7
 *
8
 * (c) Dragonfly Development Inc.
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
 
14
namespace Dflydev\DotAccessData;
15
 
16
use ArrayAccess;
17
use Dflydev\DotAccessData\Exception\DataException;
18
use Dflydev\DotAccessData\Exception\InvalidPathException;
19
use Dflydev\DotAccessData\Exception\MissingPathException;
20
 
21
/**
22
 * @implements ArrayAccess<string, mixed>
23
 */
24
class Data implements DataInterface, ArrayAccess
25
{
26
    private const DELIMITERS = ['.', '/'];
27
 
28
    /**
29
     * Internal representation of data data
30
     *
31
     * @var array<string, mixed>
32
     */
33
    protected $data;
34
 
35
    /**
36
     * Constructor
37
     *
38
     * @param array<string, mixed> $data
39
     */
40
    public function __construct(array $data = [])
41
    {
42
        $this->data = $data;
43
    }
44
 
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function append(string $key, $value = null): void
49
    {
50
        $currentValue =& $this->data;
51
        $keyPath = self::keyToPathArray($key);
52
 
53
        $endKey = array_pop($keyPath);
54
        foreach ($keyPath as $currentKey) {
55
            if (! isset($currentValue[$currentKey])) {
56
                $currentValue[$currentKey] = [];
57
            }
58
            $currentValue =& $currentValue[$currentKey];
59
        }
60
 
61
        if (!isset($currentValue[$endKey])) {
62
            $currentValue[$endKey] = [];
63
        }
64
 
65
        if (!is_array($currentValue[$endKey])) {
66
            // Promote this key to an array.
67
            // TODO: Is this really what we want to do?
68
            $currentValue[$endKey] = [$currentValue[$endKey]];
69
        }
70
 
71
        $currentValue[$endKey][] = $value;
72
    }
73
 
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function set(string $key, $value = null): void
78
    {
79
        $currentValue =& $this->data;
80
        $keyPath = self::keyToPathArray($key);
81
 
82
        $endKey = array_pop($keyPath);
83
        foreach ($keyPath as $currentKey) {
84
            if (!isset($currentValue[$currentKey])) {
85
                $currentValue[$currentKey] = [];
86
            }
87
            if (!is_array($currentValue[$currentKey])) {
88
                throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key)));
89
            }
90
            $currentValue =& $currentValue[$currentKey];
91
        }
92
        $currentValue[$endKey] = $value;
93
    }
94
 
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function remove(string $key): void
99
    {
100
        $currentValue =& $this->data;
101
        $keyPath = self::keyToPathArray($key);
102
 
103
        $endKey = array_pop($keyPath);
104
        foreach ($keyPath as $currentKey) {
105
            if (!isset($currentValue[$currentKey])) {
106
                return;
107
            }
108
            $currentValue =& $currentValue[$currentKey];
109
        }
110
        unset($currentValue[$endKey]);
111
    }
112
 
113
    /**
114
     * {@inheritdoc}
115
     *
116
     * @psalm-mutation-free
117
     */
118
    public function get(string $key, $default = null)
119
    {
120
        /** @psalm-suppress ImpureFunctionCall */
121
        $hasDefault = \func_num_args() > 1;
122
 
123
        $currentValue = $this->data;
124
        $keyPath = self::keyToPathArray($key);
125
 
126
        foreach ($keyPath as $currentKey) {
127
            if (!is_array($currentValue) || !array_key_exists($currentKey, $currentValue)) {
128
                if ($hasDefault) {
129
                    return $default;
130
                }
131
 
132
                throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath)));
133
            }
134
 
135
            $currentValue = $currentValue[$currentKey];
136
        }
137
 
138
        return $currentValue === null ? $default : $currentValue;
139
    }
140
 
141
    /**
142
     * {@inheritdoc}
143
     *
144
     * @psalm-mutation-free
145
     */
146
    public function has(string $key): bool
147
    {
148
        $currentValue = $this->data;
149
 
150
        foreach (self::keyToPathArray($key) as $currentKey) {
151
            if (
152
                !is_array($currentValue) ||
153
                !array_key_exists($currentKey, $currentValue)
154
            ) {
155
                return false;
156
            }
157
            $currentValue = $currentValue[$currentKey];
158
        }
159
 
160
        return true;
161
    }
162
 
163
    /**
164
     * {@inheritdoc}
165
     *
166
     * @psalm-mutation-free
167
     */
168
    public function getData(string $key): DataInterface
169
    {
170
        $value = $this->get($key);
171
        if (is_array($value) && Util::isAssoc($value)) {
172
            return new Data($value);
173
        }
174
 
175
        throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key)));
176
    }
177
 
178
    /**
179
     * {@inheritdoc}
180
     */
181
    public function import(array $data, int $mode = self::REPLACE): void
182
    {
183
        $this->data = Util::mergeAssocArray($this->data, $data, $mode);
184
    }
185
 
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function importData(DataInterface $data, int $mode = self::REPLACE): void
190
    {
191
        $this->import($data->export(), $mode);
192
    }
193
 
194
    /**
195
     * {@inheritdoc}
196
     *
197
     * @psalm-mutation-free
198
     */
199
    public function export(): array
200
    {
201
        return $this->data;
202
    }
203
 
204
    /**
205
     * {@inheritdoc}
206
     *
207
     * @return bool
208
     */
209
    #[\ReturnTypeWillChange]
210
    public function offsetExists($key)
211
    {
212
        return $this->has($key);
213
    }
214
 
215
    /**
216
     * {@inheritdoc}
217
     *
218
     * @return mixed
219
     */
220
    #[\ReturnTypeWillChange]
221
    public function offsetGet($key)
222
    {
223
        return $this->get($key, null);
224
    }
225
 
226
    /**
227
     * {@inheritdoc}
228
     *
229
     * @param string $key
230
     * @param mixed $value
231
     *
232
     * @return void
233
     */
234
    #[\ReturnTypeWillChange]
235
    public function offsetSet($key, $value)
236
    {
237
        $this->set($key, $value);
238
    }
239
 
240
    /**
241
     * {@inheritdoc}
242
     *
243
     * @return void
244
     */
245
    #[\ReturnTypeWillChange]
246
    public function offsetUnset($key)
247
    {
248
        $this->remove($key);
249
    }
250
 
251
    /**
252
     * @param string $path
253
     *
254
     * @return string[]
255
     *
256
     * @psalm-return non-empty-list<string>
257
     *
258
     * @psalm-pure
259
     */
260
    protected static function keyToPathArray(string $path): array
261
    {
262
        if (\strlen($path) === 0) {
263
            throw new InvalidPathException('Path cannot be an empty string');
264
        }
265
 
266
        $path = \str_replace(self::DELIMITERS, '.', $path);
267
 
268
        return \explode('.', $path);
269
    }
270
 
271
    /**
272
     * @param string|string[] $path
273
     *
274
     * @return string
275
     *
276
     * @psalm-pure
277
     */
278
    protected static function formatPath($path): string
279
    {
280
        if (is_string($path)) {
281
            $path = self::keyToPathArray($path);
282
        }
283
 
284
        return implode(' » ', $path);
285
    }
286
}