Skip to content

Instantly share code, notes, and snippets.

@eghojansu
Created January 1, 2025 15:11
Show Gist options
  • Select an option

  • Save eghojansu/24041d5f73132a1a4f186c7be31514ab to your computer and use it in GitHub Desktop.

Select an option

Save eghojansu/24041d5f73132a1a4f186c7be31514ab to your computer and use it in GitHub Desktop.
PHP Array Looper
<?php
// php >= 8.1
class Loop
{
private bool $store = true;
public array $result = [];
public bool $stopped = false;
public mixed $key;
public iterable $data;
public function __construct(
public mixed $origin,
public bool $storing = true,
public mixed $value = null,
public array $carry = [],
public string $delimiter = ',;|',
)
{}
public static function it(mixed ...$items): static
{
return new static(isset($items[1]) ? $items : ($items[0] ?? null));
}
public static function wrap(callable $cb = null): callable
{
return match(true) {
null === $cb => static fn(mixed $value) => $value,
(new \ReflectionFunction($cb))->isInternal() => static fn(mixed $value) => $cb($value),
default => $cb,
};
}
public function apply(array $options): static
{
array_walk(
$options,
function($value, $key) {
if (isset($this->$key)) {
$this->$key = $value;
}
},
);
return $this;
}
public function __invoke(callable $cb, bool $assoc = true): static
{
$this->data = match(true) {
is_iterable($this->origin) => $this->origin,
is_string($this->origin) => preg_split(
'/[' . preg_quote($this->delimiter, '/') . ']/',
$this->origin,
0,
PREG_SPLIT_NO_EMPTY,
),
default => (array) $this->origin,
};
foreach ($this->data as $key => $value) {
$result = $cb($value, $key, $this);
// last executed value
if ($this->store) {
$this->value = $result === $this ? $value : $result;
}
if ($this->storing) {
if ($assoc) {
$this->result[$key] = $this->value;
} else {
$this->result[] = $this->value;
}
}
$this->key = $key;
if ($this->stopped) {
break;
}
$this->store = true;
}
return $this;
}
public function stop(bool $stopped = true): static
{
$this->stopped = $stopped;
return $this;
}
public function store(bool $storing = true): static
{
$this->storing = $storing;
return $this;
}
public function last(mixed $value): static
{
$this->value = $value;
$this->store = false;
return $this;
}
public function add(string $name, mixed $value): static
{
$this->carry[$name] = $value;
return $this;
}
public function flatten(): array
{
return $this->reduce(
fn(array $flatten, $value, $key) => [
...$flatten,
...(is_iterable($value) ? static::it($value)->flatten() : [$key => $value]),
],
);
}
public function compare(callable $cb, bool $inverse = false): static
{
$compare = static::wrap($cb);
$expected = !$inverse;
return $this->store(false)(
static fn($value, $key, Loop $loop) => $loop->stop(
$compare($value, $key, $loop) === $expected
),
);
}
public function some(callable $cb, bool $inverse = false): bool
{
return $this->compare($cb, $inverse)->stopped;
}
public function every(callable $cb, bool $inverse = false): bool
{
return !$this->some($cb, $inverse);
}
public function map(callable $cb): array
{
return $this->__invoke(static::wrap($cb))->result;
}
public function reduce(callable $cb, mixed $carry = []): mixed
{
return $this->apply(array(
'value' => $carry,
'storing' => false,
))(
static fn($value, $key, Loop $loop) => $cb(
$loop->value,
$value,
$key,
$loop,
),
)->value;
}
public function coalesce(callable|null $cb = null): mixed
{
$call = $cb ?? static::wrap();
return $this->store(false)(
static fn($value, $key, Loop $loop) => $loop->last(
$call($value, $key, $loop)
)->stop(null !== $loop->value),
)->value;
}
public function filter(
callable $cb,
bool $inverse = false,
bool $byKey = false,
): array {
$compare = static::wrap($cb);
$expected = !$inverse;
return $this->__invoke(
static fn($value, $key, Loop $loop) => $loop->store(
($byKey ? $compare($key, $loop) : $compare($value, $key, $loop)) === $expected
),
)->result;
}
public function only(array $keys, bool $inverse = false): array
{
return $this->filter(
static fn($key) => in_array($key, $keys),
$inverse,
true,
);
}
public function except(array $keys): array
{
return $this->only($keys, true);
}
public function find(callable $cb, bool $inverse = false, bool $key = false): mixed
{
return $this->compare($cb, $inverse)->stopped ? ($key ? $this->key : $this->value) : null;
}
public function findIndex(callable $cb, bool $inverse = false): mixed
{
return $this->find($cb, $inverse, true);
}
public function each(callable $cb, mixed $tap = null): mixed
{
$value = $this->store(false)($cb);
return is_callable($tap) ? $tap($value, $this) : ($tap ?? $value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment