Created
January 1, 2025 15:11
-
-
Save eghojansu/24041d5f73132a1a4f186c7be31514ab to your computer and use it in GitHub Desktop.
PHP Array Looper
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 | |
| // 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