Collection.php
PHP
Path: src/Collection.php
<?php
namespace mini;
use ArrayIterator;
use Closure;
use mini\Contracts\CollectionInterface;
use Traversable;
/**
* Immutable collection with functional transformation methods
*
* All transformation methods return new Collection instances,
* leaving the original unchanged.
*
* @template T
* @implements CollectionInterface<T>
*/
final class Collection implements CollectionInterface
{
/**
* @param array<int, T> $items
*/
private function __construct(
private readonly array $items
) {}
/**
* Create a collection from an iterable
*
* @template U
* @param iterable<U> $items
* @return self<U>
*/
public static function from(iterable $items): self
{
if ($items instanceof self) {
return $items;
}
return new self(
$items instanceof Traversable
? iterator_to_array($items, false)
: array_values($items)
);
}
/**
* Create an empty collection
*
* @template U
* @return self<U>
*/
public static function empty(): self
{
return new self([]);
}
/**
* Create a collection from variadic arguments
*
* @template U
* @param U ...$items
* @return self<U>
*/
public static function of(mixed ...$items): self
{
return new self($items);
}
/**
* @template U
* @param Closure(T): U $fn
* @return CollectionInterface<U>
*/
public function map(Closure $fn): CollectionInterface
{
return new self(array_map($fn, $this->items));
}
/**
* @param Closure(T): bool $fn
* @return CollectionInterface<T>
*/
public function filter(Closure $fn): CollectionInterface
{
return new self(array_values(array_filter($this->items, $fn)));
}
/**
* @return T|null
*/
public function first(): mixed
{
return $this->items[0] ?? null;
}
/**
* @return T|null
*/
public function last(): mixed
{
if (empty($this->items)) {
return null;
}
return $this->items[count($this->items) - 1];
}
public function isEmpty(): bool
{
return empty($this->items);
}
/**
* @template U
* @param Closure(U, T): U $fn
* @param U $initial
* @return U
*/
public function reduce(Closure $fn, mixed $initial): mixed
{
return array_reduce($this->items, $fn, $initial);
}
/**
* @return array<int, T>
*/
public function toArray(): array
{
return $this->items;
}
/**
* @param Closure(T): bool $fn
*/
public function any(Closure $fn): bool
{
foreach ($this->items as $item) {
if ($fn($item)) {
return true;
}
}
return false;
}
/**
* @param Closure(T): bool $fn
*/
public function none(Closure $fn): bool
{
foreach ($this->items as $item) {
if ($fn($item)) {
return false;
}
}
return true;
}
/**
* @param Closure(T): bool $fn
*/
public function all(Closure $fn): bool
{
foreach ($this->items as $item) {
if (!$fn($item)) {
return false;
}
}
return true;
}
/**
* @param Closure(T): bool $fn
* @return T|null
*/
public function find(Closure $fn): mixed
{
foreach ($this->items as $item) {
if ($fn($item)) {
return $item;
}
}
return null;
}
/**
* @return Traversable<int, T>
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
public function count(): int
{
return count($this->items);
}
/**
* @return array<int, T>
*/
public function jsonSerialize(): array
{
return $this->items;
}
}