Newer
Older
<?php
/**
* SimpleORMapCollection.class.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

Marcus Eibrink-Lunzenauer
committed
*
* @extends SimpleCollection<SimpleORMap>
*
* @template T of SimpleORMap

Marcus Eibrink-Lunzenauer
committed
*/
class SimpleORMapCollection extends SimpleCollection
{

Marcus Eibrink-Lunzenauer
committed
/**
* @var int Exception error code denoting a wrong type of objects.
*/

Marcus Eibrink-Lunzenauer
committed
/**
* @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
*

Marcus Eibrink-Lunzenauer
committed
* @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>

Marcus Eibrink-Lunzenauer
committed
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

Marcus Eibrink-Lunzenauer
committed
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);
}
}

Marcus Eibrink-Lunzenauer
committed
parent::offsetSet($index, $newval);
}
/**
* sets the allowed class name

Marcus Eibrink-Lunzenauer
committed
* @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

Marcus Eibrink-Lunzenauer
committed
* @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

Jan-Hendrik Willms
committed
* @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();
}

Jan-Hendrik Willms
committed
return null;
}
/**
* returns element with given primary key value
*
* @param string $value primary key value to search for
*/
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

Marcus Eibrink-Lunzenauer
committed
* @param ?callable $group_func closure to aggregate grouped entries
* @return array assoc array
*/

Marcus Eibrink-Lunzenauer
committed
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'

Marcus Eibrink-Lunzenauer
committed
* @return void

Marcus Eibrink-Lunzenauer
committed
public function merge(SimpleCollection $a_collection, string $mode = 'ignore')
{
$mode = func_get_arg(1);
foreach ($a_collection as $element) {
try {

Marcus Eibrink-Lunzenauer
committed
/**
* @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);
}