Last active
June 12, 2017 00:13
-
-
Save derekmd/34da3c9861c14a7ebe4dc78582e20a35 to your computer and use it in GitHub Desktop.
data_dot() and data_has() helper function compliments to PHP framework Laravel. Supports stdClass, Collection, or Arrayable objects along with standard PHP arrays. The intention is to allow nested wildcard pattern matching on structured data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "your-project", | |
"autoload": { | |
"psr-4": { | |
"App\\": "app/" | |
}, | |
"files": [ | |
"app/helpers.php", | |
] | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// app/helpers.php | |
// Why don't you let us order files, Gist? | |
use Illuminate\Support\Arr; | |
if (!function_exists('data_dot')) { | |
/** | |
* Flatten a multi-dimensional object with dots. | |
* | |
* @param array|ArrayAccess|object $object | |
* @param string $prepend | |
* | |
* @return array | |
*/ | |
function data_dot($object, $prepend = '') | |
{ | |
$results = []; | |
if (Arr::accessible($object)) { | |
$array = $object; | |
} elseif (is_object($object)) { | |
$array = get_object_vars($object); | |
} else { | |
$array = []; | |
} | |
foreach ($array as $key => $value) { | |
if (is_array($value) && !empty($value)) { | |
$results = array_merge($results, data_dot($value, $prepend . $key . '.')); | |
} elseif ($value instanceof \Illuminate\Support\Collection) { | |
$results = array_merge($results, data_dot($value->all(), $prepend . $key . '.')); | |
} elseif (is_object($value) && !empty($props = get_object_vars($value))) { | |
$results = array_merge($results, data_dot($props, $prepend . $key . '.')); | |
} else { | |
$results[$prepend . $key] = $value; | |
} | |
} | |
return $results; | |
} | |
} | |
if (!function_exists('data_has')) { | |
/** | |
* Find if there is an item in an array or object using "dot" notation. | |
* | |
* @param mixed $target | |
* @param string|array $keys | |
* | |
* @return bool | |
*/ | |
function data_has($target, $keys) | |
{ | |
if (is_null($keys)) { | |
return false; | |
} | |
$keys = (array) $keys; | |
if (!$target) { | |
return false; | |
} | |
if ($keys === []) { | |
return false; | |
} | |
foreach ($keys as $i => $key) { | |
$subKeyTarget = $target; | |
if (Arr::accessible($subKeyTarget) && Arr::exists($subKeyTarget, $key)) { | |
continue; | |
} | |
if (is_object($subKeyTarget) && Arr::exists(get_object_vars($subKeyTarget), $key)) { | |
continue; | |
} | |
foreach (explode('.', $key) as $segment) { | |
if ($segment === '*') { | |
if ($subKeyTarget instanceof Collection) { | |
$subKeyTarget = $subKeyTarget->all(); | |
} elseif (!is_array($subKeyTarget)) { | |
return false; | |
} | |
if (empty($key)) { | |
return true; | |
} | |
return array_reduce($subKeyTarget, function ($present, $item) use ($keys, $i) { | |
return $present || data_has($item, array_slice($keys, $i + 1)); | |
}, false); | |
} | |
if (Arr::accessible($subKeyTarget) && Arr::exists($subKeyTarget, $segment)) { | |
$subKeyTarget = $subKeyTarget[$segment]; | |
} elseif (is_object($subKeyTarget) && isset($subKeyTarget->{$segment})) { | |
$subKeyTarget = $subKeyTarget->{$segment}; | |
} else { | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class HelpersTest extends TestCase | |
{ | |
public function testDataDot() | |
{ | |
$array = data_dot(['foo' => ['bar' => 'baz']]); | |
$this->assertEquals(['foo.bar' => 'baz'], $array); | |
$array = data_dot([]); | |
$this->assertEquals([], $array); | |
$array = data_dot(['foo' => []]); | |
$this->assertEquals(['foo' => []], $array); | |
$array = data_dot(['foo' => ['bar' => []]]); | |
$this->assertEquals(['foo.bar' => []], $array); | |
$array = data_dot((object) ['foo' => (object) ['bar' => 'baz']]); | |
$this->assertEquals(['foo.bar' => 'baz'], $array); | |
$object = data_dot(new stdClass); | |
$this->assertEquals([], $object); | |
$object = data_dot((object) ['foo' => []]); | |
$this->assertEquals(['foo' => []], $object); | |
$object = data_dot((object) ['foo' => (object) ['bar' => []]]); | |
$this->assertEquals(['foo.bar' => []], $object); | |
$object = data_dot((object) ['foo' => ['bar' => [$item = new stdClass]]]); | |
$this->assertEquals(['foo.bar.0' => $item], $object); | |
$collection = collect([1, 2]); | |
$this->assertEquals([1, 2], data_dot($collection)); | |
$collection = collect(['zero' => 1, 'one' => 2]); | |
$this->assertEquals(['zero' => 1, 'one' => 2], data_dot($collection)); | |
} | |
public function testDataHas() | |
{ | |
$array = ['products.desk' => ['price' => 100]]; | |
$this->assertTrue(data_has($array, 'products.desk')); | |
$array = ['products' => ['desk' => ['price' => 100]]]; | |
$this->assertTrue(data_has($array, 'products.desk')); | |
$this->assertTrue(data_has($array, 'products.desk.price')); | |
$this->assertFalse(data_has($array, 'products.foo')); | |
$this->assertFalse(data_has($array, 'products.desk.foo')); | |
$array = ['foo' => null, 'bar' => ['baz' => null]]; | |
$this->assertTrue(data_has($array, 'foo')); | |
$this->assertTrue(data_has($array, 'bar.baz')); | |
$array = new ArrayObject(['foo' => 10, 'bar' => new ArrayObject(['baz' => 10])]); | |
$this->assertTrue(data_has($array, 'foo')); | |
$this->assertTrue(data_has($array, 'bar')); | |
$this->assertTrue(data_has($array, 'bar.baz')); | |
$this->assertFalse(data_has($array, 'xxx')); | |
$this->assertFalse(data_has($array, 'xxx.yyy')); | |
$this->assertFalse(data_has($array, 'foo.xxx')); | |
$this->assertFalse(data_has($array, 'bar.xxx')); | |
$array = new ArrayObject(['foo' => null, 'bar' => new ArrayObject(['baz' => null])]); | |
$this->assertTrue(data_has($array, 'foo')); | |
$this->assertTrue(data_has($array, 'bar.baz')); | |
$array = ['foo', 'bar']; | |
$this->assertFalse(data_has($array, null)); | |
$this->assertFalse(data_has(null, 'foo')); | |
$this->assertFalse(data_has(false, 'foo')); | |
$this->assertFalse(data_has(null, null)); | |
$this->assertFalse(data_has([], null)); | |
$array = ['products' => ['desk' => ['price' => 100]]]; | |
$this->assertTrue(data_has($array, ['products.desk'])); | |
$this->assertTrue(data_has($array, ['products.desk', 'products.desk.price'])); | |
$this->assertTrue(data_has($array, ['products', 'products'])); | |
$this->assertFalse(data_has($array, ['foo'])); | |
$this->assertFalse(data_has($array, [])); | |
$this->assertFalse(data_has($array, ['products.desk', 'products.price'])); | |
$this->assertFalse(data_has([], [null])); | |
$this->assertFalse(data_has(null, [null])); | |
$object = (object) ['products.desk' => ['price' => 100]]; | |
$this->assertTrue(data_has($object, 'products.desk')); | |
$object = (object) ['products' => ['desk' => ['price' => 100]]]; | |
$this->assertTrue(data_has($object, 'products.desk')); | |
$this->assertTrue(data_has($object, 'products.desk.price')); | |
$this->assertFalse(data_has($object, 'products.foo')); | |
$this->assertFalse(data_has($object, 'products.desk.foo')); | |
$object = (object) ['foo' => null, 'bar' => ['baz' => null]]; | |
$this->assertTrue(data_has($object, 'foo')); | |
$this->assertTrue(data_has($object, 'bar.baz')); | |
$collection = collect(['foo' => 10, 'bar' => new ArrayObject(['baz' => 10])]); | |
$this->assertTrue(data_has($collection, 'foo')); | |
$this->assertTrue(data_has($collection, 'bar')); | |
$this->assertTrue(data_has($collection, 'bar.baz')); | |
$this->assertFalse(data_has($collection, 'xxx')); | |
$this->assertFalse(data_has($collection, 'xxx.yyy')); | |
$this->assertFalse(data_has($collection, 'foo.xxx')); | |
$this->assertFalse(data_has($collection, 'bar.xxx')); | |
$collection = collect(['foo' => null, 'bar' => new ArrayObject(['baz' => null])]); | |
$this->assertTrue(data_has($collection, 'foo')); | |
$this->assertTrue(data_has($collection, 'bar.baz')); | |
$object = (object) ['foo', 'bar']; | |
$this->assertFalse(data_has($object, null)); | |
$this->assertFalse(data_has(null, null)); | |
$this->assertFalse(data_has(new stdClass, null)); | |
$object = (object) ['products' => ['desk' => ['price' => 100]]]; | |
$this->assertTrue(data_has($object, ['products.desk'])); | |
$this->assertTrue(data_has($object, ['products.desk', 'products.desk.price'])); | |
$this->assertTrue(data_has($object, ['products', 'products'])); | |
$this->assertFalse(data_has($object, ['foo'])); | |
$this->assertFalse(data_has($object, [])); | |
$this->assertFalse(data_has($object, ['products.desk', 'products.price'])); | |
$this->assertFalse(data_has([], [null])); | |
$this->assertFalse(data_has(null, [null])); | |
$object = (object) ['users' => ['name' => ['Taylor', 'Otwell']]]; | |
$array = [(object) ['users' => [(object) ['name' => 'Taylor']]]]; | |
$dottedArray = ['users.first_name' => 'Taylor', 'users.middle_name' => null]; | |
$arrayAccess = new SupportTestArrayAccess(['price' => 56, 'user' => new SupportTestArrayAccess(['name' => 'John']), 'email' => null]); | |
$this->assertTrue(data_has($object, 'users.name.0')); | |
$this->assertTrue(data_has($array, '0.users.0.name')); | |
$this->assertFalse(data_has($array, '0.users.3')); | |
$this->assertTrue(data_has($dottedArray, ['users.first_name', 'users.middle_name'])); | |
$this->assertFalse(data_has($dottedArray, ['users.first_name', 'users.last_name'])); | |
$this->assertTrue(data_has($arrayAccess, 'price')); | |
$this->assertTrue(data_has($arrayAccess, 'user.name')); | |
$this->assertFalse(data_has($arrayAccess, 'foo')); | |
$this->assertFalse(data_has($arrayAccess, 'user.foo')); | |
$this->assertFalse(data_has($arrayAccess, 'email')); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment