aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/SimpleORMapCollection.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/classes/SimpleORMapCollection.php
parentda0022e5c1abbf9825ae76debaabdff7e8623bb4 (diff)
parent97a188592c679890a25c37ab78463add76a52ff7 (diff)
Merge branch 'main' into issue-3911issue-3911
Diffstat (limited to 'lib/classes/SimpleORMapCollection.php')
-rw-r--r--lib/classes/SimpleORMapCollection.php258
1 files changed, 258 insertions, 0 deletions
diff --git a/lib/classes/SimpleORMapCollection.php b/lib/classes/SimpleORMapCollection.php
new file mode 100644
index 0000000..20162e5
--- /dev/null
+++ b/lib/classes/SimpleORMapCollection.php
@@ -0,0 +1,258 @@
+<?php
+/**
+ * SimpleORMapCollection.php
+ * simple object-relational mapping
+ *
+ * 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 2012 Stud.IP Core-Group
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ *
+ * @extends SimpleCollection<SimpleORMap>
+ *
+ * @template T of SimpleORMap
+ */
+class SimpleORMapCollection extends SimpleCollection
+{
+ /**
+ * @var int Exception error code denoting a wrong type of objects.
+ */
+ const WRONG_OBJECT_TYPE = 1;
+
+ /**
+ * @var int Exception error code denoting that an object of this `id` already exists.
+ */
+ const OBJECT_EXISTS = 2;
+
+ /**
+ * the record object this collection belongs to
+ *
+ * @var ?SimpleORMap
+ */
+ protected $related_record;
+
+ /**
+ * relation options
+ * @var array
+ */
+ protected $relation_options = [];
+
+ /**
+ * creates a collection from an array of objects
+ * all objects should be of the same type
+ *
+ * @throws InvalidArgumentException if first entry is not SimpleOrMap
+ * @param T[] $data array with SimpleORMap objects
+ * @param bool $strict check every element for correct type and unique pk
+ * @return SimpleORMapCollection<T>
+ */
+ public static function createFromArray(array $data, $strict = true)
+ {
+ $ret = new SimpleORMapCollection();
+ if (count($data)) {
+ $first = current($data);
+ if ($first instanceof SimpleORMap) {
+ $ret->setClassName(get_class($first));
+ if ($strict) {
+ foreach ($data as $one) {
+ $ret[] = $one;
+ }
+ } else {
+ $ret->exchangeArray($data);
+ }
+ } else {
+ throw new InvalidArgumentException('This collection only accepts objects derived from SimpleORMap', self::WRONG_OBJECT_TYPE);
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param ?Closure $finder callable to fill collection
+ * @param ?array $options relationship options
+ * @param SimpleORMap|null $record related record
+ */
+ public function __construct(Closure $finder = null, array $options = null, SimpleORMap $record = null)
+ {
+ $this->relation_options = $options;
+ $this->related_record = $record;
+ parent::__construct($finder === null ? [] : $finder);
+ }
+
+ /**
+ * Sets the value at the specified index
+ * checks if the value is an object of specified class
+ *
+ * @see ArrayObject::offsetSet()
+ * @throws InvalidArgumentException if the given model does not fit (wrong type or id)
+ */
+ public function offsetSet($index, $newval): void
+ {
+ if (!is_null($index)) {
+ $index = (int)$index;
+ }
+ if (!is_a($newval, $this->getClassName())) {
+ throw new InvalidArgumentException('This collection only accepts objects of type: ' . $this->getClassName(), self::WRONG_OBJECT_TYPE);
+ }
+ if ($this->related_record && $this->relation_options['type'] === 'has_many') {
+ $foreign_key_value = call_user_func($this->relation_options['assoc_func_params_func'], $this->related_record);
+ call_user_func($this->relation_options['assoc_foreign_key_setter'], $newval, $foreign_key_value);
+ }
+ if ($newval->id !== null) {
+ $exists = $this->find($newval->id);
+ if ($exists) {
+ throw new InvalidArgumentException('Element could not be appended, element with id: ' . $exists->id . ' is in the way', self::OBJECT_EXISTS);
+ }
+ }
+ parent::offsetSet($index, $newval);
+ }
+
+ /**
+ * sets the allowed class name
+ * @param class-string $class_name
+ * @return void
+ */
+ public function setClassName($class_name)
+ {
+ $this->relation_options['class_name'] = strtolower($class_name);
+ $this->deleted->relation_options['class_name'] = strtolower($class_name);
+ }
+
+ /**
+ * sets the related record
+ *
+ * @param SimpleORMap $record
+ * @return void
+ */
+ public function setRelatedRecord(SimpleORMap $record)
+ {
+ $this->related_record = $record;
+ }
+
+ /**
+ * gets the allowed classname
+ *
+ * @return string
+ */
+ public function getClassName()
+ {
+ return strtolower($this->relation_options['class_name']);
+ }
+
+ /**
+ * reloads the elements of the collection
+ * by calling the finder function
+ *
+ * @throws InvalidArgumentException
+ * @return ?int number of records after refresh
+ */
+ public function refresh()
+ {
+ if (is_callable($this->finder)) {
+ $data = call_user_func($this->finder, $this->related_record);
+ foreach ($data as $one) {
+ if (!is_a($one, $this->getClassName())) {
+ throw new InvalidArgumentException('This collection only accepts objects of type: ' . $this->getClassName(), self::WRONG_OBJECT_TYPE);
+ }
+ }
+ $this->exchangeArray($data);
+ $this->deleted->exchangeArray([]);
+ return $this->last_count = $this->count();
+ }
+
+ return null;
+ }
+
+ /**
+ * returns element with given primary key value
+ *
+ * @param string $value primary key value to search for
+ * @return ?T
+ */
+ public function find($value)
+ {
+ return $this->findOneBy('id', $value);
+ }
+
+ /**
+ * 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 mixed $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 = [];
+ foreach ($this as $record) {
+ $key = $record->getValue($group_by);
+ if (is_array($key)) {
+ $key = join('_', $key);
+ }
+ $result[$key][] = $record->toArray($only_these_fields);
+ }
+ if ($group_func === null) {
+ $group_func = 'current';
+ }
+ return array_map($group_func, $result);
+ }
+
+ /**
+ * mark element(s) for deletion
+ * element(s) with given primary key are moved to
+ * internal deleted collection
+ *
+ * @param string $id primary key of element
+ * @return int number of unsetted elements
+ */
+ public function unsetByPk($id)
+ {
+ return $this->unsetBy('id', $id);
+ }
+
+ /**
+ * merge in another collection, elements must be of
+ * the same type, if an element already exists it is
+ * replaced or ignored depending on second param
+ *
+ * @param SimpleORMapCollection $a_collection
+ * @param string $mode 'replace' or 'ignore'
+ * @return void
+ */
+ public function merge(SimpleCollection $a_collection, string $mode = 'ignore')
+ {
+ $mode = func_get_arg(1);
+ foreach ($a_collection as $element) {
+ try {
+ /**
+ * @throws InvalidArgumentException
+ * @see SimpleORMapCollection::offsetSet()
+ */
+ $this[] = $element;
+ } catch (InvalidArgumentException $e) {
+ if ($e->getCode() === self::OBJECT_EXISTS) {
+ if ($mode === 'replace') {
+ $this->unsetByPk($element->id);
+ $this[] = $element;
+ } // else $mode means 'ignore'
+ } else {
+ throw $e;
+ }
+ }
+ }
+ $this->storage = array_values($this->storage);
+ }
+}