entityClass = $entityClass; return $instance; } public function filters(Callable $callback, bool $yieldValueOnly = false) : Generator { $idx = 0; foreach($this as $key => $item) { if ( $callback($item, $key, $idx) ) { $idx++; if ( $yieldValueOnly ) { yield $item; } else { yield $key => $item; } } } } public function filtersCollection(Callable $callback, bool $yieldValueOnly = false, bool $replaceCollection = false) : self { $collection = new static(); foreach($this->filters($callback, $yieldValueOnly) as $item) { $collection->append($item); } if ($replaceCollection) { $this->exchangeArray(array_values($collection->getArrayCopy())); return $this; } else { return $collection; } } public function filtersArray(Callable $callback) : array { return $this->filtersCollection($callback, true, false)->toArray(); } public function filtersCount(Callable $callback) : array { return $this->filtersCollection($callback, true, false)->count(); } public function filtersOne(Callable $callback) : ? object { foreach($this->filters($callback, true) as $item) { return $item; } return null; } public function extract(Callable $callback) : self { $idx = 0; $replace = []; $collection = new static(); foreach($this as $key => $item) { if ( $callback($item, $key, $idx++) ) { $collection->append($item); } else { $replace[] = $item; } } $this->replaceWith($replace); return $collection; } public function iterate(Callable $callback) : self { foreach($this as $item) { $callback($item); } return $this; } public function removeOne($value, string $field, bool $strict = true) : ? object { foreach($this->search($value, $field, $strict) as $key => $item) { $this->offsetUnset($key); return $item; } return null; } public function remove(/* mixed */ $value, string $field, bool $strict = true) : array { $removed = []; foreach($this->search($value, $field, $strict) as $key => $item) { $this->offsetUnset($key); $removed[] = $item; } return $removed; } public function clear() : self { $this->exchangeArray([]); return $this; } public function search(mixed $value, string $field, bool $strict = true) : Generator { foreach($this->filters(fn($v) => isset($v->$field) ? ( $strict ? $v->$field === $value : $v->$field == $value ) : false) as $key => $item) { yield $key => $item; } } public function searchOne(mixed $value, string $field, bool $strict = true) : ? object { # Returning first value only foreach($this->search($value, $field, $strict) as $item) { return $item; } return null; } public function searchAll(mixed $values, string $field, bool $strict = true, bool $compareArray = false) : self { $collection = new static(); $values = is_array($values) && $compareArray ? [ $values ] : $values; foreach((array) $values as $value) { foreach ($this->search($value, $field, $strict) as $item) { $collection->append($item); } } return $collection; } public function diffAll(mixed $values, string $field, bool $strict = true, bool $compareArray = false) : self { $obj = new static($this->getArrayCopy()); $values = is_array($values) && $compareArray ? [ $values ] : $values; foreach((array) $values as $value) { foreach($obj->search($value, $field, $strict) as $key => $item) { $obj->offsetUnset($key); } } return $obj; } public function searchInstances(string $className) : self { return $this->filtersCollection(fn($obj) => is_a($obj, $className)); } public function column($field, bool $uniqueValue = false, $keyField = null) : array { return $this->map($field, $keyField, $uniqueValue); } public function map($field, $keyField = null, bool $uniqueValue = false) : array { $list = []; foreach($this as $item) { if ( ! is_string($field) && is_callable($field) ) { $value = call_user_func_array($field, [ $item ]); } else { $value = $item->$field; } if ($uniqueValue && in_array($value, $list, true)) { continue; } if ( $keyField !== null ) { if ( ! is_string($field) && is_callable($keyField) ) { $key = call_user_func_array($keyField, [ $item ]); } else { $key = $item->$keyField; } $list[$key] = $value; } else { $list[] = $value; } } return $list; } public function walk(Callable $callback) : self { return $this->filtersCollection($callback); } public function sum($field) : float|int { return array_sum($this->column($field)); } public function unique(\Stringable|callable|string $field, bool $strict = false) : self { $list = []; $obj = new static(); foreach($this as $item) { if ( $field === null) { $value = $this; } if ( is_callable($field) ) { $value = call_user_func_array($field, [ $item ]); } else { $value = $item->$field; } if ( ! in_array($value, $list, $strict) ) { $list[] = $value; $obj->append($item); } } return $obj; } public function first() : ? object { foreach($this as $item) { return $item; } return null; } public function last() : ? object { foreach($this as $item) { $return = $item; } return $return ?? null; } public function buildArray(/* string|callable|null */ $keyColumn, /* string|callable|null */ $value = null) : array { $list = []; foreach($this as $item) { $key = $keyColumn instanceof \Closure ? $keyColumn($item) : $item->$keyColumn; switch (true) { case is_null($value): $list[] = $key; break; case is_callable($value): $list[$key] = $value($item); break; case is_object($value): case is_string($value): $value = (string) $value; $list[$key] = $item->$value; break; } } return $list; } public function implode(string $glue, ? Callable $callback = null) : string { $values = []; foreach($this as $item) { $values[] = $callback ? $callback($item) : (string) $item; } return implode($glue, $values); } public function toArray(bool $includeRelations = false) : array { $list = []; foreach($this as $entity) { $list[] = $entity->toArray($includeRelations); } return $list; } public function toFilteredArray(Callable $callback) : array { $list = []; foreach($this as $entity) { $list[] = $callback($entity); } return $list; } public function toJsonArray(bool $includeRelations = false) : array { $list = []; foreach($this as $entity) { $list[] = $entity instanceof \JsonSerializable ? $entity->jsonSerialize() : $entity->toArray($includeRelations); } return $list; } public function fromArray(array $datasets, ? string /*stringable*/ $entityClass = null) : self { foreach($datasets as $dataset) { $this->append( $this->arrayToEntity($dataset, $entityClass) ); } return $this; } public function arrayToEntity(array $dataset, ? string /*stringable*/ $entityClass = null) : object { if ( ! ($this->entityClass || $entityClass) ) { throw new \Exception("An entity class name must be provided to be instanciated and populated before insertion into this collection."); } $className = $entityClass ?: $this->entityClass; return ( new $className() )->fromArray($dataset); } public function arrayToEntities(array $list, ? string /*stringable*/ $entityClass = null) : self { $collection = new static(); $collection->entityClass = $entityClass ?? $this->entityClass; foreach($list as $dataset) { $collection->append($this->arrayToEntity($dataset, $entityClass)); } return $collection; } public function jsonSerialize(): mixed { return $this->toJsonArray(true); } public function append($value) : void { if ( is_array($value) ) { $this->append( $this->arrayToEntity($value) ); } else { foreach ($this as $duplicate) { if ($duplicate === $value) { return; } } parent::append($value); } } public function push($value) : self { $this->append($value); return $this; } public function mergeWith( /*array|EntityCollection*/ $datasets ) : self { if ( is_object($datasets) ) { $datasets = $datasets->getArrayCopy(); } $this->exchangeArray( array_merge( $this->getArrayCopy(), $datasets ) ); return $this; } public function replaceWith( /*array|EntityCollection*/ $datasets ) : self { if ( is_object($datasets) ) { $datasets = $datasets->getArrayCopy(); } $this->exchangeArray( $datasets ); return $this; } public function batch(int $size, $fill = null, $preserveKeys = true) : array { $batch = []; $split = ceil( $this->count() / $size ); for($i = 0; $i < $split; $i++) { $slice = array_slice($this->getArrayCopy(), $i * $size, $size, $preserveKeys); $pad = ( count($slice) !== $size ) && ( $fill !== null ); $batch[] = $pad ? array_pad($slice, $size, $fill) : $slice; } return $batch; } public function randomize() : self { $arr = $this->getArrayCopy(); shuffle($arr); $this->exchangeArray($arr); return $this; } public function sort(null|callable $callback = null, $function = "uasort") : self { $callback ??= fn($e1, $e2) => $e1 <=> $e2; call_user_func_array([ $this, $function ], [ $callback ]); return $this; } public function rsort(null|callable $callback = null, $function = "uasort") : self { return $this->sort(...func_get_args())->reverse(); } public function pop() /* : mixed */ { $arr = $this->getArrayCopy(); $pop = array_pop($arr); $this->exchangeArray($arr); return $pop; } public function shift() /* : mixed */ { $arr = $this->getArrayCopy(); $shift = array_shift($arr); $this->exchangeArray($arr); return $shift; } public function reverse() : self { return $this->replaceWith(array_reverse($this->getArrayCopy())); } public function slice(int $offset, ? int $length = null) : self { return new self(array_slice($this->getArrayCopy(), $offset, $length)); } public function sortField(? string $field = null) : self { return $this->sort(fn($e1, $e2) => $field ? $e1->$field <=> $e2->$field : $e1 <=> $e2); } public function rsortField(string $field) : self { return $this->sort(fn($e1, $e2) => $field ? $e2->$field <=> $e1->$field : $e2 <=> $e1); } }