123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- <?php
- declare(strict_types=1);
- /*
- * This file is a part of dflydev/dot-access-data.
- *
- * (c) Dragonfly Development Inc.
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Dflydev\DotAccessData;
- use ArrayAccess;
- use Dflydev\DotAccessData\Exception\DataException;
- use Dflydev\DotAccessData\Exception\InvalidPathException;
- use Dflydev\DotAccessData\Exception\MissingPathException;
- /**
- * @implements ArrayAccess<string, mixed>
- */
- class Data implements DataInterface, ArrayAccess
- {
- private const DELIMITERS = ['.', '/'];
- /**
- * Internal representation of data data
- *
- * @var array<string, mixed>
- */
- protected $data;
- /**
- * Constructor
- *
- * @param array<string, mixed> $data
- */
- public function __construct(array $data = [])
- {
- $this->data = $data;
- }
- /**
- * {@inheritdoc}
- */
- public function append(string $key, $value = null): void
- {
- $currentValue =& $this->data;
- $keyPath = self::keyToPathArray($key);
- $endKey = array_pop($keyPath);
- foreach ($keyPath as $currentKey) {
- if (! isset($currentValue[$currentKey])) {
- $currentValue[$currentKey] = [];
- }
- $currentValue =& $currentValue[$currentKey];
- }
- if (!isset($currentValue[$endKey])) {
- $currentValue[$endKey] = [];
- }
- if (!is_array($currentValue[$endKey])) {
- // Promote this key to an array.
- // TODO: Is this really what we want to do?
- $currentValue[$endKey] = [$currentValue[$endKey]];
- }
- $currentValue[$endKey][] = $value;
- }
- /**
- * {@inheritdoc}
- */
- public function set(string $key, $value = null): void
- {
- $currentValue =& $this->data;
- $keyPath = self::keyToPathArray($key);
- $endKey = array_pop($keyPath);
- foreach ($keyPath as $currentKey) {
- if (!isset($currentValue[$currentKey])) {
- $currentValue[$currentKey] = [];
- }
- if (!is_array($currentValue[$currentKey])) {
- throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key)));
- }
- $currentValue =& $currentValue[$currentKey];
- }
- $currentValue[$endKey] = $value;
- }
- /**
- * {@inheritdoc}
- */
- public function remove(string $key): void
- {
- $currentValue =& $this->data;
- $keyPath = self::keyToPathArray($key);
- $endKey = array_pop($keyPath);
- foreach ($keyPath as $currentKey) {
- if (!isset($currentValue[$currentKey])) {
- return;
- }
- $currentValue =& $currentValue[$currentKey];
- }
- unset($currentValue[$endKey]);
- }
- /**
- * {@inheritdoc}
- *
- * @psalm-mutation-free
- */
- public function get(string $key, $default = null)
- {
- /** @psalm-suppress ImpureFunctionCall */
- $hasDefault = \func_num_args() > 1;
- $currentValue = $this->data;
- $keyPath = self::keyToPathArray($key);
- foreach ($keyPath as $currentKey) {
- if (!is_array($currentValue) || !array_key_exists($currentKey, $currentValue)) {
- if ($hasDefault) {
- return $default;
- }
- throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath)));
- }
- $currentValue = $currentValue[$currentKey];
- }
- return $currentValue === null ? $default : $currentValue;
- }
- /**
- * {@inheritdoc}
- *
- * @psalm-mutation-free
- */
- public function has(string $key): bool
- {
- $currentValue = $this->data;
- foreach (self::keyToPathArray($key) as $currentKey) {
- if (
- !is_array($currentValue) ||
- !array_key_exists($currentKey, $currentValue)
- ) {
- return false;
- }
- $currentValue = $currentValue[$currentKey];
- }
- return true;
- }
- /**
- * {@inheritdoc}
- *
- * @psalm-mutation-free
- */
- public function getData(string $key): DataInterface
- {
- $value = $this->get($key);
- if (is_array($value) && Util::isAssoc($value)) {
- return new Data($value);
- }
- throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key)));
- }
- /**
- * {@inheritdoc}
- */
- public function import(array $data, int $mode = self::REPLACE): void
- {
- $this->data = Util::mergeAssocArray($this->data, $data, $mode);
- }
- /**
- * {@inheritdoc}
- */
- public function importData(DataInterface $data, int $mode = self::REPLACE): void
- {
- $this->import($data->export(), $mode);
- }
- /**
- * {@inheritdoc}
- *
- * @psalm-mutation-free
- */
- public function export(): array
- {
- return $this->data;
- }
- /**
- * {@inheritdoc}
- *
- * @return bool
- */
- #[\ReturnTypeWillChange]
- public function offsetExists($key)
- {
- return $this->has($key);
- }
- /**
- * {@inheritdoc}
- *
- * @return mixed
- */
- #[\ReturnTypeWillChange]
- public function offsetGet($key)
- {
- return $this->get($key, null);
- }
- /**
- * {@inheritdoc}
- *
- * @param string $key
- * @param mixed $value
- *
- * @return void
- */
- #[\ReturnTypeWillChange]
- public function offsetSet($key, $value)
- {
- $this->set($key, $value);
- }
- /**
- * {@inheritdoc}
- *
- * @return void
- */
- #[\ReturnTypeWillChange]
- public function offsetUnset($key)
- {
- $this->remove($key);
- }
- /**
- * @param string $path
- *
- * @return string[]
- *
- * @psalm-return non-empty-list<string>
- *
- * @psalm-pure
- */
- protected static function keyToPathArray(string $path): array
- {
- if (\strlen($path) === 0) {
- throw new InvalidPathException('Path cannot be an empty string');
- }
- $path = \str_replace(self::DELIMITERS, '.', $path);
- return \explode('.', $path);
- }
- /**
- * @param string|string[] $path
- *
- * @return string
- *
- * @psalm-pure
- */
- protected static function formatPath($path): string
- {
- if (is_string($path)) {
- $path = self::keyToPathArray($path);
- }
- return implode(' » ', $path);
- }
- }
|