<?php
declare(strict_types=1);
namespace MotorORM;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use Traversable;
/**
* Collection
*
* @license Code and contributions have MIT License
* @link https://visavi.net
* @author Alexander Grigorev <[email protected]>
* @version 2.0
*/
class Collection implements Countable, IteratorAggregate, ArrayAccess
{
/**
* Initializes a new collection.
*/
public function __construct(
protected array $items = [],
) {}
/**
* Gets a native PHP array representation of the collection.
*
* @return array
*/
public function all(): array
{
return $this->items;
}
/**
* Gets the item at the specified key/index.
*
* @param string|int $key
* @param mixed $default
*
* @return mixed|null
*/
public function get(string|int $key, mixed $default = null): mixed
{
return $this->items[$key] ?? $default;
}
/**
* Sets the internal iterator to the first item in the collection and returns this item.
*
* @param callable|null $callback
*
* @return mixed
*/
public function first(callable $callback = null): mixed
{
if (is_null($callback)) {
return empty($this->items) ? null : reset($this->items);
}
foreach ($this->items as $key => $value) {
if ($callback($value, $key)) {
return $value;
}
}
return null;
}
/**
* Sets the internal iterator to the last item in the collection and returns this item.
*
* @param callable|null $callback
*
* @return mixed
*/
public function last(callable $callback = null): mixed
{
if (is_null($callback)) {
return empty($this->items) ? null : end($this->items);
}
$this->items = array_reverse($this->items, true);
return $this->first($callback);
}
/**
* Removes and returns the item at the specified index from the collection
*
* @param string|int $key
*
* @return mixed|null
*/
public function pull(string|int $key): mixed
{
if (! array_key_exists($key, $this->items)) {
return null;
}
$removed = $this->items[$key];
unset($this->items[$key]);
return $removed;
}
/**
* Removes the item at the specified index from the collection
*
* @param string|int $key
*
* @return void
*/
public function forget(string|int $key): void
{
$this->pull($key);
}
/**
* Gets all keys/indices of the collection.
*
* @return array
*/
public function keys(): array
{
return array_keys($this->items);
}
/**
* Gets all values of the collection.
*
* @return array
*/
public function values(): array
{
return array_values($this->items);
}
/**
* Checks whether the collection contains an item with the specified key/index.
*
* @param string|int $key
*
* @return bool
*/
public function has(string|int $key): bool
{
return array_key_exists($key, $this->items);
}
/**
* Checks whether an item is contained in the collection.
* This is an O(n) operation, where n is the size of the collection.
*
* @param mixed $value
* @param bool $strict
*
* @return bool
*/
public function contains(mixed $value, bool $strict = false): bool
{
if (is_callable($value)) {
return $this->first($value) !== null;
}
return in_array($value, $this->items, $strict);
}
/**
* The search method searches the collection for the given value and returns its key if found.
*
* @param mixed $value
* @param bool $strict
*
* @return bool|int|string
*/
public function search(mixed $value, bool $strict = false): bool|int|string
{
if (! is_callable($value)) {
return array_search($value, $this->items, $strict);
}
foreach ($this->items as $key => $item) {
if ($value($item, $key)) {
return $key;
}
}
return false;
}
/**
* @return int
*/
public function count(): int
{
return count($this->items);
}
/**
* Sets an item in the collection at the specified key/index.
*
* @param string|int $key
* @param mixed $value
*
* @return void
*/
public function put(string|int $key, mixed $value): void
{
$this->items[$key] = $value;
}
/**
* Adds an item at the end of the collection.
*
* @param mixed $item
*
* @return bool
*/
public function push(mixed $item): bool
{
$this->items[] = $item;
return true;
}
/**
* Checks whether the collection is empty (contains no items).
*
* @return bool
*/
public function isEmpty(): bool
{
return empty($this->items);
}
/**
* Checks whether the collection is not empty.
*
* @return bool
*/
public function isNotEmpty(): bool
{
return ! empty($this->items);
}
/**
* Clears the collection, removing all items.
*
* @return void
*/
public function clear(): void
{
$this->items = [];
}
/**
* Extracts a slice of $length items starting at position $offset from the Collection.
*
* If $length is null it returns all items from $offset to the end of the Collection.
* Keys have to be preserved by this method. Calling this method will only return the
* selected slice and NOT change the items contained in the collection slice is called on.
*
* @param int $offset
* @param int|null $length
*
* @return self
*/
public function slice(int $offset, ?int $length = null): self
{
return new self(array_slice($this->items, $offset, $length, true));
}
/**
* Pluck
*
* @param string $value
* @param string|null $key
*
* @return self
*/
public function pluck(string $value, ?string $key = null): self
{
if ($key === null) {
return new self(array_column($this->items, $value));
}
return new self(array_column($this->items, $value, $key));
}
/**
* Filter
*
* @param callable|null $callback
*
* @return self
*/
public function filter(callable $callback = null): self
{
if ($callback) {
return new self(array_filter($this->items, $callback, ARRAY_FILTER_USE_BOTH));
}
return new self(array_filter($this->items));
}
/**
* Gets the key/index of the item at the current iterator position.
*
* @return int|string|null
*/
public function key(): int|string|null
{
return key($this->items);
}
/**
* Moves the internal iterator position to the next item and returns this item.
*
* @return false|mixed
*/
public function next(): mixed
{
return next($this->items);
}
/**
* Gets the item of the collection at the current iterator position.
*
* @return false|mixed
*/
public function current(): mixed
{
return current($this->items);
}
/**
* Required by interface ArrayAccess.
*
* @param $offset
*
* @return mixed
*/
public function offsetGet($offset): mixed
{
return $this->get($offset);
}
/**
* Required by interface ArrayAccess.
*
* @param $offset
* @param $value
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (! isset($offset)) {
$this->push($value);
return;
}
$this->put($offset, $value);
}
/**
* Required by interface ArrayAccess.
*
* @param $offset
*
* @return bool
*/
public function offsetExists($offset): bool
{
return $this->has($offset);
}
/**
* Required by interface ArrayAccess.
*
* @param $offset
*
* @return void
*/
public function offsetUnset($offset): void
{
$this->forget($offset);
}
/**
* @return Traversable<int|string, mixed>
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
/**
* Returns a string representation of this object.
*
* @return string
*/
public function __toString()
{
return self::class . '@' . spl_object_hash($this);
}
}