Skip to content

Instantly share code, notes, and snippets.

@kmuenkel
Last active June 23, 2022 07:04
Show Gist options
  • Save kmuenkel/ffcf8e0c2bad29ba1a201014e2321dc7 to your computer and use it in GitHub Desktop.
Save kmuenkel/ffcf8e0c2bad29ba1a201014e2321dc7 to your computer and use it in GitHub Desktop.
Dot notation with query support and without full-path recursive data retrieval
<?php
namespace App\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\{Collection, ServiceProvider};
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
$this->collectionMacros();
}
protected function collectionMacros()
{
$name = 'getPath';
$getPath = function ($index, ?bool $keepKeys = null, $default = null) use ($name) {
/** @var Collection|Request $this */
$operators = [
'~=' => fn ($value, string $expression) => fnmatch("*$expression", $value),
'=~' => fn ($value, string $expression) => fnmatch("$expression*", $value),
'<=' => fn ($value, string $expression) => $value <= $expression,
'>=' => fn ($value, string $expression) => $value >= $expression,
'=' => fn ($value, string $expression) => $expression == $value,
'<' => fn ($value, string $expression) => $value < $expression,
'>' => fn ($value, string $expression) => $value > $expression
];
$indexes = array_map(
fn (string $index): string => preg_replace('~\\\\\.~', '.', $index),
preg_split('~(?<!\\\)\.~', $index)
);
return ($self = function (&$indexes, $items, $default = null) use (&$self, $operators, $keepKeys, $name) {
$notFound = uniqid();
while (!is_null($index = array_shift($indexes))) {
if (!is_array($items) && !($items instanceof \stdClass)) {
$indexes = [];
$items = $default;
} elseif (!preg_match('/^(\[)(.+)(])$/', $index, $matches)) {
if (preg_match('~(?<!\\\)\*~', $index)) {
$findMatch = fn ($value, $key): bool => fnmatch($index, (string)$key);
$items = collect($items)->filter($findMatch)->map(function ($subset) use (
$self,
$indexes,
$notFound
) {
$subIndexes = $indexes;
return $self($subIndexes, $subset, $notFound);
})->filter(fn ($item) => $item !== $notFound);
$items = ($keepKeys ? $items : $items->values())->all() ?: $default;
$indexes = [];
} else {
$index = preg_replace('~\\\\\*~', '*', $index);
$items = collect($items)->get($index, $notFound);
if ($items === $notFound) {
$items = $default;
$indexes = [];
}
}
} else {
$operatorPatterns = array_map('preg_quote', array_keys($operators));
$byOperator = '/^(.+)(' . implode('|', $operatorPatterns) . ')(.+)$/';
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
$defaults = ['*', '~=', ''];
[$field, $operator, $expression] = preg_split($byOperator, $matches[2], 3, $flags) + $defaults;
$findMatches = function ($element) use (
$field,
$operator,
$expression,
$operators,
$notFound,
$keepKeys,
$name
): bool {
$keepKeys = !is_null($keepKeys) ? $keepKeys : true;
$value = collect((array)$element)->$name($field, $keepKeys, $notFound);
$conditionMet = fn ($value) => ($operators[$operator])($value, $expression);
return $value !== $notFound && (is_array($value) ? (bool)array_filter($value, $conditionMet)
: $conditionMet($value));
};
$items = collect($items)->filter($findMatches);
$items = ($keepKeys ? $items : $items->values())->all();
}
}
return $items;
})($indexes, $this->all(), $default);
};
Collection::macro($name, $getPath);
Request::macro($name, $getPath);
$groupRecursive = function (array $groups, callable $groupBy): Collection {
/** @var Collection $this */
return ($map = fn (array $groups, callable $groupBy): Closure
=> function (Collection $items) use ($groups, $groupBy, &$map): Collection {
$items = ($field = array_shift($groups))
? $items->groupBy($groupBy($field)) : $items;
return $groups ? $items->map($map($groups)) : $items;
})($groups, $groupBy)($this->all());
};
Collection::macro('groupRecursive', $groupRecursive);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment