506 lines
13 KiB
PHP
506 lines
13 KiB
PHP
<?php
|
|
|
|
namespace Ulmus;
|
|
|
|
use Generator;
|
|
|
|
class EntityCollection extends \ArrayObject implements \JsonSerializable {
|
|
|
|
public ? string $entityClass = null;
|
|
|
|
public static function instance(array $data = [], null|string $entityClass = null) : self
|
|
{
|
|
$instance = new static($data);
|
|
$instance->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);
|
|
}
|
|
}
|