aboutsummaryrefslogtreecommitdiff
path: root/lib/models/SimpleCollection.class.php
diff options
context:
space:
mode:
authorPhilipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de>2024-09-24 10:53:31 +0200
committerPhilipp Schüttlöffel <schuettloeffel@zqs.uni-hannover.de>2024-09-24 10:53:31 +0200
commit4459dd7917f4d1c34f40bb68f0e991e9c3d53e4c (patch)
tree5c07151ae61276d334e88f6309c30d439a85c12e /lib/models/SimpleCollection.class.php
parentda0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff)
parent97a188592c679890a25c37ab78463add76a52ff7 (diff)
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/models/SimpleCollection.class.php')
-rw-r--r--lib/models/SimpleCollection.class.php788
1 files changed, 0 insertions, 788 deletions
diff --git a/lib/models/SimpleCollection.class.php b/lib/models/SimpleCollection.class.php
deleted file mode 100644
index 4d77682..0000000
--- a/lib/models/SimpleCollection.class.php
+++ /dev/null
@@ -1,788 +0,0 @@
-<?php
-if (!defined('SORT_NATURAL')) {
- define('SORT_NATURAL', 6);
-}
-if (!defined('SORT_FLAG_CASE')) {
- define('SORT_FLAG_CASE', 8);
-}
-
-/**
- * SimpleCollection.class.php
- * collection of assoc arrays with convenience
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * @author André Noack <noack@data-quest.de>
- * @copyright 2013 Stud.IP Core-Group
- * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- * @category Stud.IP
- *
- * @template T
- */
-class SimpleCollection extends StudipArrayObject
-{
- /**
- * callable to initialize collection
- *
- * @var ?callable(): array<T>
- */
- protected $finder;
-
- /**
- * number of records after last init
- *
- * @var int
- */
- protected $last_count;
-
- /**
- * collection with deleted records
- * @var static
- */
- protected $deleted;
-
- /**
- * creates a collection from an array of arrays
- * all arrays should contain same keys, but is not enforced
- *
- * @param array<T> $data array containing assoc arrays
- * @return SimpleCollection<T>
- */
- public static function createFromArray(array $data)
- {
- return new self($data);
- }
-
- /**
- * converts arrays or objects to ArrayObject objects
- * if ArrayAccess interface is not available
- *
- * @param mixed $a
- * @return StudipArrayObject|ArrayAccess
- */
- public static function arrayToArrayObject($a)
- {
- if ($a instanceof StudipArrayObject) {
- $a->setFlags(StudipArrayObject::ARRAY_AS_PROPS);
- return $a;
- }
-
- if ($a instanceof ArrayObject) {
- return new StudipArrayObject($a->getArrayCopy(), StudipArrayObject::ARRAY_AS_PROPS);
- }
-
- if ($a instanceof ArrayAccess) {
- return $a;
- }
-
- return new StudipArrayObject((array) $a, StudipArrayObject::ARRAY_AS_PROPS);
- }
-
- /**
- * returns closure to compare a value against given arguments
- * using given operator
- *
- * @param string|callable(mixed, mixed|array): bool $operator
- * @param mixed|array $args
- * @throws InvalidArgumentException
- * @return callable(mixed): bool comparison function
- */
- public static function getCompFunc($operator, $args)
- {
- if (is_callable($operator)) {
- $comp_func = function ($a) use ($args, $operator) {
- return $operator($a, $args);
- };
- } else {
- if (!is_array($args)) {
- $args = [$args];
- }
- switch ($operator) {
- case '==':
- $comp_func = function ($a) use ($args) {
- return in_array($a, $args);
- };
- break;
- case '===':
- $comp_func = function ($a) use ($args) {
- return in_array($a, $args, true);
- };
- break;
- case '!=':
- case '<>':
- $comp_func = function ($a) use ($args) {
- return !in_array($a, $args);
- };
- break;
- case '!==':
- $comp_func = function ($a) use ($args) {
- return !in_array($a, $args, true);
- };
- break;
- case '<':
- case '>':
- case '<=':
- case '>=':
- $op_func = function ($a, $b) use ($operator) {
- if ($operator === '<') {
- return $a < $b;
- } elseif ($operator === '<=') {
- return $a <= $b;
- } elseif ($operator === '>=') {
- return $a >= $b;
- } elseif ($operator === '>') {
- return $a > $b;
- }
- };
- $comp_func = function ($a) use ($op_func, $args) {
- return $op_func($a, $args[0]);
- };
- break;
- case '><':
- $comp_func = function ($a) use ($args) {
- return $a > $args[0] && $a < $args[1];
- };
- break;
- case '>=<=':
- $comp_func = function ($a) use ($args) {
- return $a >= $args[0] && $a <= $args[1];
- };
- break;
- case '%=':
- $comp_func = function ($a) use ($args) {
- $a = mb_strtolower(static::translitLatin1($a));
- $args = array_map([static::class, 'translitLatin1'], $args);
- $args = array_map('mb_strtolower', $args);
- return in_array($a, $args);
- };
- break;
- case '*=':
- $comp_func = function ($a) use ($args) {
- foreach ($args as $arg) {
- if (mb_strpos($a, $arg) !== false) {
- return true;
- }
- }
- return false;
- };
- break;
- case '^=':
- $comp_func = function ($a) use ($args) {
- foreach ($args as $arg) {
- if (mb_strpos($a, $arg) === 0) {
- return true;
- }
- }
- return false;
- };
- break;
- case '$=':
- $comp_func = function ($a) use ($args) {
- foreach ($args as $arg) {
- $found = mb_strrpos($a, $arg);
- if ($found !== false && ($found + mb_strlen($arg)) === mb_strlen($a)) {
- return true;
- }
- }
- return false;
- };
- break;
- case '~=':
- $comp_func = function ($a) use ($args) {
- foreach ($args as $arg) {
- if (preg_match($arg, $a) === 1) {
- return true;
- }
- }
- return false;
- };
- break;
- default:
- throw new InvalidArgumentException('unknown operator: ' . $operator);
- }
- }
- return $comp_func;
- }
-
- /**
- * transliterates latin1 string to ascii
- *
- * @param string $text
- * @return string
- */
- public static function translitLatin1($text)
- {
- if (!preg_match('/[\200-\377]/', $text)) {
- return $text;
- }
- $text = str_replace(['ä','Ä','ö','Ö','ü','Ü','ß'], ['a','A','o','O','u','U','s'], $text);
- $text = str_replace(['À','Á','Â','Ã','Å','Æ'], 'A' , $text);
- $text = str_replace(['à','á','â','ã','å','æ'], 'a' , $text);
- $text = str_replace(['È','É','Ê','Ë'], 'E' , $text);
- $text = str_replace(['è','é','ê','ë'], 'e' , $text);
- $text = str_replace(['Ì','Í','Î','Ï'], 'I' , $text);
- $text = str_replace(['ì','í','î','ï'], 'i' , $text);
- $text = str_replace(['Ò','Ó','Õ','Ô','Ø'], 'O' , $text);
- $text = str_replace(['ò','ó','ô','õ','ø'], 'o' , $text);
- $text = str_replace(['Ù','Ú','Û'], 'U' , $text);
- $text = str_replace(['ù','ú','û'], 'u' , $text);
- $text = str_replace(['Ç','ç','Ð','Ñ','Ý','ñ','ý','ÿ'], ['C','c','D','N','Y','n','y','y'] , $text);
- return $text;
- }
-
- /**
- * Constructor
- *
- * @param array<T>|callable(): array<T> $data array or closure to fill collection
- */
- public function __construct($data = [])
- {
- parent::__construct();
- $this->finder = is_callable($data) ? $data : null;
- $this->deleted = clone $this;
- if (is_callable($data)) {
- $this->refresh();
- } else {
- $this->exchangeArray($data);
- }
- }
-
- /**
- * @param array $input
- * @return array
- */
- public function exchangeArray($input)
- {
- return parent::exchangeArray(array_map(
- [static::class, 'arrayToArrayObject'],
- $input
- ));
- }
-
- /**
- * converts the object and all elements to plain arrays
- *
- * @return array
- */
- public function toArray()
- {
- $args = func_get_args();
- return $this->map(function ($a) use ($args) {
- if (method_exists($a, 'toArray')) {
- return call_user_func_array([$a, 'toArray'], $args);
- }
- if (method_exists($a, 'getArrayCopy')) {
- return $a->getArrayCopy();
- }
- return (array) $a;
- }
- );
- }
-
- /**
- *
- * @see ArrayObject::append()
- */
- public function append($newval)
- {
- parent::append(static::arrayToArrayObject($newval));
- }
-
- /**
- * Sets the value at the specified index
- * ensures the value has ArrayAccess
- *
- * @param mixed $index
- * @param mixed $newval
- *
- * @see ArrayObject::offsetSet()
- * @return void
- *
- * @todo Add void return type when Stud.IP requires PHP8 minimal
- */
- #[ReturnTypeWillChange]
- public function offsetSet($index, $newval)
- {
- if (is_numeric($index)) {
- $index = (int) $index;
- }
- parent::offsetSet($index, static::arrayToArrayObject($newval));
- }
-
- /**
- * Unsets the value at the specified index
- * value is moved to internal deleted collection
- *
- * @see ArrayObject::offsetUnset()
- * @throws InvalidArgumentException
- *
- * @todo Add void return type when Stud.IP requires PHP8 minimal
- */
- #[ReturnTypeWillChange]
- public function offsetUnset($index)
- {
- if ($this->offsetExists($index)) {
- $this->deleted[] = $this->offsetGet($index);
- }
- parent::offsetUnset($index);
- }
-
- /**
- * sets the finder function
- *
- * @param callable(): array<T> $finder
- * @return void
- */
- public function setFinder(callable $finder)
- {
- $this->finder = $finder;
- }
-
- /**
- * get deleted records collection
- * @return SimpleCollection<T>
- */
- public function getDeleted()
- {
- return $this->deleted;
- }
-
- /**
- * reloads the elements of the collection
- * by calling the finder function
- *
- * @return ?int of records after refresh
- */
- public function refresh()
- {
- if (is_callable($this->finder)) {
- $data = call_user_func($this->finder);
- $this->exchangeArray($data);
- $this->deleted->exchangeArray([]);
- return $this->last_count = $this->count();
- }
- }
-
- /**
- * returns a new collection containing all elements
- * where given columns value matches given value(s) using passed operator
- * pass array for multiple values
- *
- * operators:
- * == equal, like php
- * === identical, like php
- * !=,<> not equal, like php
- * !== not identical, like php
- * <,>,<=,>= less,greater,less or equal,greater or equal
- * >< between without borders, needs two arguments
- * >=<= between including borders, needs two arguments
- * %= like string, transliterate to ascii,case insensitive
- * *= contains string
- * ^= begins with string
- * $= ends with string
- * ~= regex
- *
- * @param string $key the column name
- * @param mixed $values value to search for
- * @param string|callable $op operator to find
- * @return SimpleCollection<T> with found records
- */
- public function findBy($key, $values, $op = '==')
- {
- $comp_func = self::getCompFunc($op, $values);
- return $this->filter(function ($record) use ($comp_func, $key) {
- return $comp_func($record[$key]);
- });
- }
-
- /**
- * returns the first element
- * where given column has given value(s)
- * pass array for multiple values
- *
- * @param string $key the column name
- * @param mixed $values value to search for,
- * @param string|callable $op operator to find
- * @return ?T found record
- */
- public function findOneBy($key, $values, $op = '==')
- {
- $comp_func = self::getCompFunc($op, $values);
- return $this->filter(function ($record) use ($comp_func, $key) {
- return $comp_func($record[$key]);
- }, 1)->first();
- }
-
- /**
- * apply given callback to all elements of
- * collection
- *
- * @param callable(T): int $func the function to call
- * @return int|false addition of return values
- */
- public function each(callable $func)
- {
- $result = false;
- foreach ($this->storage as $record) {
- $result += call_user_func($func, $record);
- }
- return $result;
- }
-
- /**
- * apply given callback to all elements of
- * collection and give back array of return values
- *
- * @param callable(T, mixed): mixed $func the function to call
- * @return array<mixed>
- */
- public function map(callable $func)
- {
- $results = [];
- foreach ($this->storage as $key => $value) {
- $results[$key] = call_user_func($func, $value, $key);
- }
- return $results;
- }
-
- /**
- * filter elements
- * if given callback returns true
- *
- * @param ?callable(T, mixed): bool $func the function to call
- * @param ?integer $limit limit number of found records
- * @return SimpleCollection<T> containing filtered elements
- */
- public function filter(callable $func = null, $limit = null)
- {
- $results = [];
- $found = 0;
- foreach ($this->storage as $key => $value) {
- if (call_user_func($func, $value, $key)) {
- $results[$key] = $value;
- if ($limit && (++$found == $limit)) {
- break;
- }
- }
- }
- return self::createFromArray($results);
- }
-
- /**
- * Returns whether any element of the collection returns true for the
- * given callback.
- *
- * @param callable(T, mixed): bool $func the function to call
- * @return bool
- */
- public function any(callable $func)
- {
- foreach ($this->storage as $key => $value) {
- if (call_user_func($func, $value, $key)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns whether every element of the collection returns true for the
- * given callback.
- *
- * @param callable(T, mixed): bool $func the function to call
- * @return bool
- */
- public function every(callable $func)
- {
- foreach ($this->storage as $key => $value) {
- if (!call_user_func($func, $value, $key)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * extract array of columns values
- * pass array or space-delimited string for multiple columns
- *
- * @param string|array $columns the column(s) to extract
- * @return array of extracted values
- */
- public function pluck($columns)
- {
- if (!is_array($columns)) {
- $columns = words($columns);
- }
- $func = function ($r) use ($columns) {
- $result = [];
- foreach ($columns as $c) {
- $result[] = $r[$c];
- }
- return $result;
- };
- $result = $this->map($func);
- return count($columns) === 1 ? array_map('current', $result) : $result;
- }
-
- /**
- * returns the collection as grouped array
- * first param is the column to group by, it becomes the key in
- * the resulting array, default is pk. Limit returned fields with second param
- * The grouped entries can optoionally go through the given
- * callback. If no callback is provided, only the first grouped
- * entry is returned, suitable for grouping by unique column
- *
- * @param string $group_by the column to group by, pk if ommitted
- * @param string|array|null $only_these_fields limit returned fields
- * @param ?callable $group_func closure to aggregate grouped entries
- * @return array assoc array
- */
- public function toGroupedArray($group_by = 'id', $only_these_fields = null, callable $group_func = null)
- {
- $result = [];
- if (is_string($only_these_fields)) {
- $only_these_fields = words($only_these_fields);
- }
- foreach ($this->toArray() as $record) {
- $key = $record[$group_by];
- $ret = [];
- if (is_array($only_these_fields)) {
- $result[$key][] = array_intersect_key($record, array_flip($only_these_fields));
- } else {
- $result[$key][] = $record;
- }
- }
- if ($group_func === null) {
- $group_func = 'current';
- }
- return array_map($group_func, $result);
- }
-
- /**
- * get the first element
- *
- * @return ?T first element or null
- */
- public function first()
- {
- $keys = array_keys($this->storage);
- $first_offset = reset($keys);
- return $this->offsetGet($first_offset ?: 0);
- }
-
- /**
- * get the last element
- *
- * @return ?T last element or null
- */
- public function last()
- {
- $keys = array_keys($this->storage);
- $last_offset = end($keys);
- return $this->offsetGet($last_offset ?: 0);
- }
-
- /**
- * get the the value from given key from first element
- *
- * @param string $key
- * @return mixed
- */
- public function val($key)
- {
- $first = $this->first();
- return $first[$key] ?? null;
- }
-
- /**
- * mark element(s) for deletion
- * where given column has given value(s)
- * element(s) are moved to
- * internal deleted collection
- * pass array for multiple values
- *
- * operators:
- * == equal, like php
- * === identical, like php
- * !=,<> not equal, like php
- * !== not identical, like php
- * <,>,<=,>= less,greater,less or equal,greater or equal
- * >< between without borders, needs two arguments
- * >=<= between including borders, needs two arguments
- * %= like string, transliterate to ascii,case insensitive
- * *= contains string
- * ^= begins with string
- * $= ends with string
- * ~= regex
- *
- * @param string $key
- * @param mixed $values
- * @param string|callable(mixed, mixed|array): bool $op operator to find elements
- * @return int|false number of unsetted elements
- */
- public function unsetBy($key, $values, $op = '==')
- {
- $ret = false;
- $comp_func = self::getCompFunc($op, $values);
- foreach ($this->storage as $k => $record) {
- if ($comp_func($record[$key])) {
- $this->offsetunset($k);
- $ret += 1;
- }
- }
- return $ret;
- }
-
- /**
- * sorts the collection by columns of contained elements and returns it
- *
- * works like sql order by:
- * first param is a string containing combinations of column names
- * and sort direction, separated by comma e.g.
- * 'name asc, nummer desc '
- * sorts first by name ascending and then by nummer descending
- * second param denotes the sort type (using PHP sort constants):
- * SORT_LOCALE_STRING:
- * compare items as strings, transliterate latin1 to ascii, case insensitiv, natural order for numbers
- * SORT_NUMERIC:
- * compare items as integers
- * SORT_STRING:
- * compare items as strings
- * SORT_NATURAL:
- * compare items as strings using "natural ordering"
- * SORT_FLAG_CASE:
- * can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively
- *
- * @param string $order columns to order by
- * @param integer $sort_flags
- * @return $this the sorted collection
- */
- public function orderBy($order, $sort_flags = SORT_LOCALE_STRING)
- {
- //('name asc, nummer desc ')
- $sort_locale = false;
- switch ($sort_flags) {
- case SORT_NATURAL:
- $sort_func = 'strnatcmp';
- break;
- case SORT_NATURAL | SORT_FLAG_CASE:
- $sort_func = 'strnatcasecmp';
- break;
- case SORT_STRING | SORT_FLAG_CASE:
- $sort_func = 'strcasecmp';
- break;
- case SORT_STRING:
- $sort_func = 'strcmp';
- break;
- case SORT_NUMERIC:
- $sort_func = function ($a, $b) {
- return (int) $a - (int) $b;
- };
- break;
- case SORT_LOCALE_STRING:
- default:
- $sort_func = 'strnatcasecmp';
- $sort_locale = true;
- }
-
- $sorter = [];
- foreach (explode(',', $order) as $one) {
- $sorter[] = array_values(array_filter(array_map('trim', explode(' ', $one))));
- }
-
- $func = function ($d1, $d2) use ($sorter, $sort_func, $sort_locale) {
- do {
- $current_sorter = current($sorter);
- $field = $current_sorter[0];
- $dir = $current_sorter[1] ?? '';
- if (!$sort_locale) {
- $value1 = $d1[$field];
- $value2 = $d2[$field];
- } else {
- $value1 = static::translitLatin1(mb_substr($d1[$field], 0, 100));
- $value2 = static::translitLatin1(mb_substr($d2[$field], 0, 100));
- }
- $ret = $sort_func($value1, $value2);
- if (strtolower($dir) == 'desc') $ret = $ret * -1;
- } while ($ret === 0 && next($sorter));
-
- return $ret;
- };
- if (count($sorter)) {
- $this->uasort($func);
- }
- return $this;
- }
-
- /**
- * returns a new collection contaning a sequence of original collection
- * mimics the sql limit constrain:
- * used with one parameter, the first x elements are extracted
- * used with two parameters, the first parameter denotes the offset, the second the
- * number of elements
- *
- * @param integer $arg1
- * @param ?integer $arg2
- * @return SimpleCollection<T>
- */
- public function limit($arg1, $arg2 = null)
- {
- if (is_null($arg2)) {
- if ($arg1 > 0) {
- $row_count = $arg1;
- $offset = 0;
- } else {
- $row_count = abs($arg1);
- $offset = $arg1;
- }
- } else {
- $offset = $arg1;
- $row_count = $arg2;
- }
- return self::createFromArray(array_slice($this->storage, $offset, $row_count, true));
- }
-
- /**
- * calls the given method on all elements
- * of the collection
- * @param literal-string $method methodname to call
- * @param array $params parameters for methodcall
- * @return array of all return values
- */
- public function sendMessage($method, $params = []) {
- $results = [];
- foreach ($this->storage as $record) {
- $results[] = call_user_func_array([$record, $method], $params);
- }
- return $results;
- }
-
- /**
- * magic version of sendMessage
- * calls undefineds methods on all elements of the collection
- * But beware of the dark side...
- *
- * @param literal-string $method methodname to call
- * @param array $params parameters for methodcall
- * @return array of all return values
- */
- public function __call($method, $params)
- {
- return $this->sendMessage($method, $params);
- }
-
- /**
- * merge in another collection, elements are appended
- *
- * @param SimpleCollection<T> $a_collection
- * @return void
- */
- public function merge(SimpleCollection $a_collection)
- {
- $this->storage = array_merge($this->storage, $a_collection->getArrayCopy());
- }
-}