Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw exception when accessing non-existent key paths and not providing a default value #29

Merged
merged 8 commits into from
Sep 29, 2020
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ $data->has('a.b.c');

// false
$data->has('a.b.d.j');


// 'some-default-value'
$data->get('some.path.that.does.not.exist', 'some-default-value');

// throws a MissingPathException because no default was given
$data->get('some.path.that.does.not.exist');
```

A more concrete example:
Expand Down Expand Up @@ -121,6 +128,15 @@ isset($data['name']) === $data->has('name');
unset($data['name']);
```

`/` can also be used as a path delimiter:

```php
$data->set('a/b/c', 'd');
echo $data->get('a/b/c'); // "d"

$data->get('a/b/c') === $data->get('a.b.c'); // true
```

License
-------

Expand Down
100 changes: 48 additions & 52 deletions src/Data.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/*
* This file is a part of dflydev/dot-access-data.
*
Expand All @@ -14,6 +16,7 @@
use ArrayAccess;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
use Dflydev\DotAccessData\Exception\MissingPathException;

/**
* @implements ArrayAccess<string, mixed>
Expand Down Expand Up @@ -45,25 +48,10 @@ public function __construct(array $data = [])
public function append(string $key, $value = null): void
{
$currentValue =& $this->data;
$keyPath = $this->keyToPathArray($key);

if (1 == count($keyPath)) {
if (!isset($currentValue[$key])) {
$currentValue[$key] = [];
}
if (!is_array($currentValue[$key])) {
// Promote this key to an array.
// TODO: Is this really what we want to do?
$currentValue[$key] = [$currentValue[$key]];
}
$currentValue[$key][] = $value;

return;
}
$keyPath = self::keyToPathArray($key);

$endKey = array_pop($keyPath);
for ($i = 0; $i < count($keyPath); $i++) {
$currentKey =& $keyPath[$i];
foreach ($keyPath as $currentKey) {
if (! isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
Expand All @@ -73,11 +61,13 @@ public function append(string $key, $value = null): void
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]];
}
// Promote this key to an array.
// TODO: Is this really what we want to do?

$currentValue[$endKey][] = $value;
}

Expand All @@ -87,22 +77,15 @@ public function append(string $key, $value = null): void
public function set(string $key, $value = null): void
{
$currentValue =& $this->data;
$keyPath = $this->keyToPathArray($key);

if (1 == count($keyPath)) {
$currentValue[$key] = $value;

return;
}
$keyPath = self::keyToPathArray($key);

$endKey = array_pop($keyPath);
for ($i = 0; $i < count($keyPath); $i++) {
$currentKey =& $keyPath[$i];
foreach ($keyPath as $currentKey) {
if (!isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
if (!is_array($currentValue[$currentKey])) {
throw new DataException("Key path at $currentKey of $key cannot be indexed into (is not an array)");
throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key)));
}
$currentValue =& $currentValue[$currentKey];
}
Expand All @@ -115,17 +98,10 @@ public function set(string $key, $value = null): void
public function remove(string $key): void
{
$currentValue =& $this->data;
$keyPath = $this->keyToPathArray($key);

if (1 == count($keyPath)) {
unset($currentValue[$key]);

return;
}
$keyPath = self::keyToPathArray($key);

$endKey = array_pop($keyPath);
for ($i = 0; $i < count($keyPath); $i++) {
$currentKey =& $keyPath[$i];
foreach ($keyPath as $currentKey) {
if (!isset($currentValue[$currentKey])) {
return;
}
Expand All @@ -141,17 +117,21 @@ public function remove(string $key): void
*/
public function get(string $key, $default = null)
{
/** @psalm-suppress ImpureFunctionCall */
$hasDefault = \func_num_args() > 1;

$currentValue = $this->data;
$keyPath = $this->keyToPathArray($key);
$keyPath = self::keyToPathArray($key);

for ($i = 0; $i < count($keyPath); $i++) {
$currentKey = $keyPath[$i];
if (!isset($currentValue[$currentKey])) {
return $default;
}
if (!is_array($currentValue)) {
return $default;
foreach ($keyPath as $currentKey) {
if (!is_array($currentValue) || !isset($currentValue[$currentKey])) {
if ($hasDefault) {
return $default;
}

throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath)));
}

$currentValue = $currentValue[$currentKey];
}

Expand All @@ -166,10 +146,8 @@ public function get(string $key, $default = null)
public function has(string $key): bool
{
$currentValue = &$this->data;
$keyPath = $this->keyToPathArray($key);

for ($i = 0; $i < count($keyPath); $i++) {
$currentKey = $keyPath[$i];
foreach (self::keyToPathArray($key) as $currentKey) {
if (
!is_array($currentValue) ||
!array_key_exists($currentKey, $currentValue)
Expand All @@ -194,7 +172,7 @@ public function getData(string $key): DataInterface
return new Data($value);
}

throw new DataException("Value at '$key' could not be represented as a DataInterface");
throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key)));
}

/**
Expand Down Expand Up @@ -236,7 +214,7 @@ public function offsetExists($key)
*/
public function offsetGet($key)
{
return $this->get($key);
return $this->get($key, null);
}

/**
Expand All @@ -258,13 +236,15 @@ public function offsetUnset($key)
}

/**
* @param string $path
*
* @return string[]
*
* @psalm-return non-empty-list<string>
*
* @psalm-pure
*/
protected function keyToPathArray(string $path): array
protected static function keyToPathArray(string $path): array
{
if (\strlen($path) === 0) {
throw new InvalidPathException('Path cannot be an empty string');
Expand All @@ -274,4 +254,20 @@ protected function keyToPathArray(string $path): array

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);
}
}
28 changes: 23 additions & 5 deletions src/DataInterface.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/*
* This file is a part of dflydev/dot-access-data.
*
Expand All @@ -19,41 +21,54 @@ interface DataInterface
/**
* Append a value to a key (assumes key refers to an array value)
*
* If the key does not yet exist it will be created.
* If the key references a non-array it's existing contents will be added into a new array before appending the new value.
*
* @param string $key
* @param mixed $value
*
* @throws InvalidPathException if the given path is empty
* @throws InvalidPathException if the given key is empty
*/
public function append(string $key, $value = null): void;

/**
* Set a value for a key
*
* If the key does not yet exist it will be created.
*
* @param string $key
* @param mixed $value
*
* @throws InvalidPathException if the given path is empty
* @throws DataException if the given path does not target an array
* @throws InvalidPathException if the given key is empty
* @throws DataException if the given key does not target an array
*/
public function set(string $key, $value = null): void;

/**
* Remove a key
*
* No exception will be thrown if the key does not exist
*
* @param string $key
*
* @throws InvalidPathException if the given path is empty
* @throws InvalidPathException if the given key is empty
*/
public function remove(string $key): void;

/**
* Get the raw value for a key
*
* If the key does not exist, an optional default value can be returned instead.
* If no default is provided then an exception will be thrown instead.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*
* @throws InvalidPathException if the given key is empty
* @throws InvalidPathException if the given key does not exist and no default value was given
*
* @psalm-mutation-free
*/
public function get(string $key, $default = null);
Expand All @@ -65,6 +80,8 @@ public function get(string $key, $default = null);
*
* @return bool
*
* @throws InvalidPathException if the given key is empty
*
* @psalm-mutation-free
*/
public function has(string $key): bool;
Expand All @@ -76,7 +93,8 @@ public function has(string $key): bool;
*
* @return DataInterface
*
* @throws DataException if the given path does not reference an array
* @throws InvalidPathException if the given key is empty
* @throws DataException if the given key does not reference an array
*
* @psalm-mutation-free
*/
Expand Down
2 changes: 2 additions & 0 deletions src/Exception/DataException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/*
* This file is a part of dflydev/dot-access-data.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Exception/InvalidPathException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/*
* This file is a part of dflydev/dot-access-data.
*
Expand Down
37 changes: 37 additions & 0 deletions src/Exception/MissingPathException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?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\Exception;

use Throwable;

/**
* Thrown when trying to access a path that does not exist
*/
class MissingPathException extends DataException
{
/** @var string */
protected $path;

public function __construct(string $path, string $message = '', int $code = 0, Throwable $previous = null)
{
$this->path = $path;

parent::__construct($message, $code, $previous);
}

public function getPath(): string
{
return $this->path;
}
}
2 changes: 2 additions & 0 deletions src/Util.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

/*
* This file is a part of dflydev/dot-access-data.
*
Expand Down
Loading