Files
awo-hamburg-intranet/mysyde/common/traits/genericCollectionTrait.php
2026-02-17 14:56:23 +01:00

878 lines
31 KiB
PHP

<?php
namespace DynCom\mysyde\common\traits;
use DynCom\mysyde\common\interfaces\CriteriaHelperInterface;
use DynCom\mysyde\common\interfaces\GenericCollectionInterface;
use DynCom\mysyde\common\interfaces\ModelDBConfigInterface;
/**
* Created by PhpStorm.
* User: Micha
* Date: 14.01.2015
* Time: 17:38
* @TODO: Rewrite from getByCriteria to end for SplObjectStorage
*/
trait genericCollectionTrait
{
protected $allowedComparisonOperations = array('IS NULL', 'IS NOT NULL', '=', '!=', '<', '>', '>=', '<=', 'IN', 'LIKE');
protected $criteriaValidationService;
/**
* @var ModelDBConfigInterface
*/
protected $config;
protected $elements;
protected $hashObjectMap = [];
protected $hashesByID = [];
protected $hashesByAltPrimary = [];
protected $hashList = [];
protected $fields;
protected $altPrimary;
protected $entryClassName;
protected $currIndex = '';
protected $currIndexPos = null;
/**
* @param ModelDBConfigInterface $config
* @param CriteriaHelperInterface $criteriaValidationService
*/
public function __construct(ModelDBConfigInterface $config, CriteriaHelperInterface $criteriaValidationService)
{
$this->elements = new \SplObjectStorage();
$this->config = $config;
$this->criteriaValidationService = $criteriaValidationService;
$this->entryClassName = $config->getModelClassName();
$this->altPrimary = $config->getAltPrimary();
}
/**
* @return string
*/
public function getBaseTableName()
{
return $this->config->getBaseTableName();
}
/**
* @return array Array of fields (optionally with DB-Type) of the class for entries in the collection
*/
public function getFieldsArray()
{
return $this->config->getMappedFields();
}
/**
* @return int Implements Countable interface
*/
public function count()
{
//return count($this->elements);
return count($this->hashObjectMap);
}
/**
* @return \IteratorIterator
*/
public function getIterator()
{
return new \IteratorIterator(new \ArrayIterator($this->hashObjectMap));
}
/**
* @param mixed $instance An instance of the collection-entry class
* @param bool $idCheck Flag whether comparisons should be id-only, default FALSE
*
* @return bool Success or failure
*/
public function remove($instance, $idCheck = false)
{
return $this->_remove($instance, $idCheck);
}
/**
* @param mixed $instance An instance of the collection-entry class
* @param bool $idCheck Flag whether comparisons should be id-only, default FALSE
*
* @return bool Success or failure
*/
protected function _remove($instance, $idCheck = false)
{
$info = $this->getIndexInfo($instance);
if (
($idCheck && (empty($info['id']) || !array_key_exists((string)$info['id'], $this->hashesByID)))
|| (!array_key_exists($info['hash'], $this->hashObjectMap))
) {
return false;
}
$this->removeInstance($instance);
return true;
if (!$idCheck && $this->elements->contains($instance)) {
$this->elements->detach($instance);
return true;
}
$c = $this->elements;
$c->rewind();
$found = false;
while ($c->valid() && !$found) {
$object = $c->current();
$currID = $object->id;
if ($instance->id === $currID) {
$found = true;
$foundEntity = $object;
}
$c->next();
}
$c->rewind();
if ($found) {
$this->elements->detach($foundEntity);
return true;
}
$c->rewind();
return false;
}
/**
* @return array The entire collection as array
*/
public function getAllAsArray()
{
return array_values($this->hashObjectMap);
$newArray = array();
$this->elements->rewind();
while ($this->elements->valid()) {
$newArray[] = $this->elements->current();
$this->elements->next();
}
$this->elements->rewind();
return $newArray;
}
/**
* @param array $array Array containing instances of the class for entries in the collection
*
* @param bool $idCheck
* @return bool Success or failure
*/
protected function _resetEntriesFromArray(array $array, $idCheck = false)
{
$this->hashesByID = [];
$this->hashesByAltPrimary = [];
$this->hashObjectMap = [];
$className = $this->getEntryClassName();
//$newCollection = new SplObjectStorage();
foreach ($array as $instance) {
if ($instance instanceof $className) {
//$newCollection->attach($instance);
$this->add($instance, $idCheck);
} else {
return false;
}
}
//$this->elements = $newCollection;
return true;
}
/**
* @return string
*/
public function getEntryClassName()
{
return $this->entryClassName;
}
/**
* @param int $id
*
* @return mixed An instance of the collection-entry class (or Null-Object)
*/
public function getByID($id)
{
return $this->_getByID($id);
}
/**
* @param int $id
*
* @return mixed An instance of the collection-entry class
*/
protected function _getByID($id)
{
$obj = $this->getNullObject();
if (!$this->hasInstanceForID($id)) {
return $obj;
}
return $this->getInstanceByID($id);
foreach ($this->elements as $instance) {
if (isset($instance->id) && ($instance->id == (int)$id)) {
return $instance;
}
}
return $this->getNullObject();
}
/**
* @return mixed Empty (unset) instance of the class for entries in the collection
* !MUST! be overriden in classes with different constructor
*/
public function getNullObject()
{
return new $this->entryClassName($this->config);
}
/**
* @param array $uniqueKeyArray An array specifying an alternative primary key, of the form:
* array('keyField1Name'=>'keyField1Value','keyField2Name'=>'keyField2Value'...);
*
* @return mixed An instance of the collection-entry class
*/
public function getByAltPrimary(array $uniqueKeyArray)
{
return $this->getInstanceByAltPrimary($uniqueKeyArray);
return $this->_getByAltPrimary($uniqueKeyArray);
}
/**
* @param array $uniqueKeyArray
* @return mixed
* @throws \DomainException
*/
protected function _getByAltPrimary(array $uniqueKeyArray)
{
$altPrimaryValues = array();
foreach ($this->altPrimary as $name) {
if (isset($uniqueKeyArray[$name])) {
$altPrimaryValues[$name] = $uniqueKeyArray[$name];
} else {
throw new \DomainException('Paramter enthält keinen Wert für Schlüsselfeld \'' . $name . '\'');
return $this->getNullObject();
}
}
$all = $this->getAllAsArray();
foreach ($all as $instance) {
$instanceAltPrimary = array();
foreach ($this->altPrimary as $key => $value) {
$instanceAltPrimary[$key] = $instance->$key;
}
if ($instanceAltPrimary == $uniqueKeyArray) {
return $instance;
}
}
return $this->getNullObject();
}
/**
* @param array $criteria Filtering-criteria as array in disjunctive normal form:
* array(
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue')
* ...
* ),
* **OR**
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue')
* ...
* )
* ...
* )
*
* @return GenericCollectionInterface $collection Subset of the current collection as instance of own concrete
* collection class
*/
public function getByCriteria(array $criteria)
{
return $this->_getByCriteria($criteria);
}
/**
* @param array $criteria Filtering-criteria as array in disjunctive normal form:
* array(
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue')
* ...
* ),
* **OR**
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue')
* ...
* )
* ...
* )
*
* @return GenericCollectionInterface $collection Subset of the current collection as instance of own concrete
* collection class
*/
protected function _getByCriteria(array $criteria)
{
$resultCollection = new static($this->config, $this->criteriaValidationService);
if (!$this->criteriaValidationService->validateCriteria($this->config, $criteria)) {
return $resultCollection;
}
//foreach ($this->elements as $instance) {
foreach ($this->hashObjectMap as $instance) {
foreach ($criteria as $disjunctCriteria) {
$isValidDisjunct = true;
foreach ($disjunctCriteria as $conjunctCriterion) {
$fieldName = $conjunctCriterion[0];
$comparator = $conjunctCriterion[1];
$compareValue = isset($conjunctCriterion[2]) ? $conjunctCriterion[2] : '';
if ($comparator === 'IS NULL') {
if (!(is_null($instance->$fieldName))) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === 'IS NOT NULL') {
if (is_null($instance->$fieldName)) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === '=') {
if (!(isset($instance->$fieldName) && $instance->$fieldName == $compareValue)) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === '!=') {
if (!($instance->$fieldName != $compareValue)) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === '>') {
if (!(isset($instance->$fieldName) && ($instance->$fieldName > $compareValue))) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === '<') {
if (!(isset($instance->$fieldName) && ($instance->$fieldName < $compareValue))) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === '<=') {
if (!(isset($instance->$fieldName) && !($instance->$fieldName <= $compareValue))) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === '>=') {
if (!(isset($instance->$fieldName) || ($instance->$fieldName >= $compareValue))) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === 'IN') {
if (!(in_array($instance->$fieldName, $compareValue))) {
$isValidDisjunct = false;
break;
}
} elseif ($comparator === 'LIKE') {
//SQL-Wildcard durch fnmatch-Wildcard ersetzen
$compareValue = str_replace('%', '*', $compareValue);
if (!(fnmatch($compareValue, $instance->$fieldName))) {
$isValidDisjunct = false;
break;
}
}
}
if ($isValidDisjunct) {
$resultCollection->add($instance, true);
}
}
}
return $resultCollection;
}
/**
* @param mixed $instance An instance of the collection-entry class
* @param bool $idCheck Flag whether comparisons should be id-only, default FALSE
*
* @return bool Success or failure
*/
public function add($instance, $idCheck = false)
{
return $this->_add($instance, $idCheck);
}
/**
* @param mixed $instance An instance of the collection-entry class
* @param bool $idCheck Flag whether comparisons should be id-only, default FALSE
*
* @return bool Success or failure
* @throws \DomainException
*/
protected function _add($instance, $idCheck = false)
{
$info = $this->getIndexInfo($instance);
if ((!$idCheck && array_key_exists($info['hash'],$this->hashObjectMap)) || $this->hasInstanceForID($info['id'])) {
$this->updateByID($instance);
} else {
$this->addNewInstance($instance);
}
return true;
$className = $this->getEntryClassName();
$id = null;
$hash = $this->elements->getHash($instance);
if (!($instance instanceof $className)) {
throw new \DomainException(
'Parameter \'instance\' is not an instance of class \'' . $className . '\', instead is ' . get_class(
$instance
)
);
}
if (property_exists($instance, 'id')) {
$idRefl = new \ReflectionProperty($instance, 'id');
$idRefl->setAccessible(true);
$id = $idRefl->getValue($instance);
}
if (!$idCheck) {
if (!($this->elements->offsetExists($instance))) {
$this->elements->attach($instance);
return true;
}
return false;
}
if (count($this->elements) > 0) {
$this->elements->rewind();
if (!(property_exists($this->config->getModelClassName(), 'id') && isset($instance->id))) {
throw new \DomainException(
'idCheck is Set but either given instance or collection content doesn\'t have id-field'
);
}
$c = $this->elements;
$c->rewind();
$found = false;
while ($c->valid() && !$found) {
$object = $c->current();
$currID = $object->id;
if ($instance->id === $currID) {
$found = true;
$foundEntity = $object;
}
$c->next();
}
$c->rewind();
if ($found) {
$this->elements->detach($foundEntity);
$this->elements->attach($instance);
return true;
}
}
$this->elements->attach($instance);
$this->elements->rewind();
return true;
}
/**
* @param array $criteria Filtering-criteria as array in disjunctive normal form:
* array(
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue'),
* ...
* ),
* **OR**
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue'),
* ...
* ),
* ...
* )
*
* @return array $collection Subset of the current collection as instance of own concrete
* collection class
*/
public function getByCriteriaAsArray(array $criteria)
{
return $this->_getByCriteriaAsArray($criteria);
}
/**
* @param array $criteria Filtering-criteria as array in disjunctive normal form:
* array(
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue'),
* ...
* ),
* **OR**
* array(
* array('fieldName','comparator','fieldValue'),
* **AND**
* array('fieldName','comparator','fieldValue'),
* ...
* ),
* ...
* )
*
* @return array $collection Subset of the current collection as instance of own concrete
* collection class
*/
protected function _getByCriteriaAsArray(array $criteria)
{
return $this->_getByCriteria($criteria)->getAllAsArray();
}
/**
* @return static
*/
public function getEmptyCollection()
{
return new static($this->config, $this->criteriaValidationService);
}
/**
* @param array $arr
*
* @return genericCollectionTrait
* @throws \DomainException
*/
public function mapArrayToNewCollection(array $arr)
{
$newCollection = $this->getEmptyCollection();
foreach ($arr as $instanceRow) {
$newInstance = $this->getNullObject();
if (!$newInstance->isArrayPartiallyMappable($instanceRow)) {
throw new \DomainException(
'At least one entry in the array cannot be mapped to an object of the given instance.'
);
}
$newInstance->mapArray($instanceRow);
$newCollection->add($newInstance, true);
}
return $newCollection;
}
/**
* @param array $fields
* @return array
* @throws \InvalidArgumentException
*/
public function getFieldData(array $fields)
{
$validFieldsArr = [];
foreach ($fields as $index => $fieldName) {
if (!is_string($fieldName)) {
throw new \InvalidArgumentException(
"Only strings are allowed as fieldsNames. Array-index $index is not a string."
);
}
if ($this->config->fieldExists($fieldName)) $validFieldsArr[] = $fieldName;
}
$dataArr = [];
$i = 0;
//for ($this->elements->rewind(); $this->elements->valid(); $this->elements->next()) {
// $el = $this->elements->current();
foreach ($this->hashObjectMap as $el) {
foreach ($validFieldsArr as $validFieldName) {
$dataArr[$i][$validFieldName] = $el->{$validFieldName};
}
++$i;
}
return $dataArr;
}
public function rewind()
{
if($this->count() > 0) {
$this->currIndex = array_keys($this->hashObjectMap)[0];
$this->currIndexPos = 0;
} else {
$this->currIndex = '';
$this->currIndexPos = null;
}
}
/**
* @return bool
*/
public function valid()
{
return array_key_exists($this->currIndex,$this->hashObjectMap) && !empty($this->hashObjectMap[$this->currIndex]);
}
/**
* @return mixed
*/
public function current()
{
return $this->hashObjectMap[$this->currIndex];
}
public function next()
{
if ($this->count() > 0) {
$this->currIndexPos++;
if ($this->currIndexPos > (count($this->hashList) - 1)) {
$this->currIndexPos = null;
$this->currIndex = '';
} else {
$this->currIndex = $this->hashList[$this->currIndexPos];
}
}
}
/**
* @return mixed|object
*/
public function getFirst()
{
$this->rewind();
if (!empty($this->hashObjectMap)) {
return $this->hashObjectMap[$this->hashList[0]];
}
return $this->getNullObject();
$this->elements->rewind();
return $this->elements->valid() ? $this->elements->current() : $this->getNullObject();
}
/**
* @return mixed|object
*/
public function getNext()
{
$this->next();
return $this->valid() ? $this->current() : $this->getNullObject();
$this->elements->next();
return $this->elements->valid() ? $this->elements->current() : $this->getNullObject();
}
/**
* @return bool
*/
public function isCurrValid()
{
return $this->valid();
return $this->elements->valid();
}
/**
* @param $instance
*/
private function addNewInstance($instance)
{
$initEmpty = (0 === count($this->hashObjectMap));
$info = $this->getIndexInfo($instance);
if (!isset($info['id']) || $info['id'] === 0 || $info['id'] === '0') {
throw new \InvalidArgumentException('Cannot update by id if instance has no non-null, non-zero id-value');
}
if (array_key_exists((string)$info['id'], $this->hashesByID)) {
throw new \InvalidArgumentException(
'Cannot add. Instance with id ' . $info['id'] . ' already exists in collection.'
);
}
$hash = (string)$info['hash'];
$hashedAltPrimary = (string)$info['hashed_alt_primary'];
$this->hashObjectMap[$hash] = $instance;
$this->hashesByID[(string)$info['id']] = $info['hash'];
if ($info['hashed_alt_primary'] !== '') {
$this->hashesByAltPrimary[$hashedAltPrimary] = $info['hash'];
}
$this->hashList[] = $info['hash'];
if ($initEmpty) {
$this->currIndexPos = 0;
$this->currIndex = $info['hash'];
}
}
/**
* @param $instance
*/
private function updateByID($instance)
{
$info = $this->getIndexInfo($instance);
if (!isset($info['id']) || $info['id'] === 0 || $info['id'] === '0') {
throw new \InvalidArgumentException('Cannot update by id if instance has no non-null, non-zero id-value');
}
if (!array_key_exists((string)$info['id'], $this->hashesByID)) {
throw new \InvalidArgumentException(
'Cannot update by id. No instance with id ' . $info['id'] . ' in collection.'
);
}
$oldHash = $this->hashesByID[(string)$info['id']];
$newHash = (string)$info['hash'];
$hashedAltPrimary = (string)$info['hashed_alt_primary'];
if($oldHash !== $newHash) {
$pos = array_search($oldHash,$this->hashList,true);
$this->hashList[$pos] = $newHash;
if ($pos === $this->currIndexPos) {
$this->currIndex = $newHash;
}
unset($this->hashObjectMap[$oldHash]);
}
$this->hashObjectMap[$newHash] = $instance;
$this->hashesByID[(string)$info['id']] = $info['hash'];
if ($hashedAltPrimary !== '') {
$this->hashesByAltPrimary[$hashedAltPrimary] = $info['hash'];
}
}
/**
* @param $id
*/
private function removeByID($id)
{
if (!array_key_exists((string)$id, $this->hashesByID)) {
throw new \InvalidArgumentException('Cannot remove by id. No instance with id ' . $id . ' in collection.');
}
$instance = $this->hashObjectMap[$this->hashesByID[$id]];
$this->removeInstance($instance);
}
/**
* @param array $altPrimary
*/
private function removeByAltPrimary(array $altPrimary)
{
if (!$this->hasInstanceForAltPrimary($altPrimary)) {
throw new \InvalidArgumentException(
'Cannot remove instance. No instance with given alt_primary in collection.'
);
}
$instance = $this->getInstanceByAltPrimary($altPrimary);
$this->removeInstance($instance);
}
/**
* @param $instance
*/
private function removeInstance($instance)
{
$info = $this->getIndexInfo($instance);
unset($this->hashObjectMap[(string)$info['hash']], $this->hashesByID[(string)$info['id']], $this->hashesByAltPrimary[(string)$info['hashed_alt_primary']]);
$hashListPos = array_search((string)$info['hash'],$this->hashList,true);
unset($this->hashList[$hashListPos]);
$this->hashList = array_values($this->hashList);
if ($this->currIndexPos >= $hashListPos) {
$this->currIndexPos--;
if ($this->currIndexPos < 0) {
$this->currIndexPos = 0;
}
}
if (array_key_exists($this->currIndexPos,$this->hashList) && !empty($this->hashList[$this->currIndexPos])) {
$this->currIndex = $this->hashList[$this->currIndexPos];
} else {
$this->currIndex = '';
$this->currIndexPos = null;
}
}
/**
* @param $id
* @return bool
*/
private function hasInstanceForID($id)
{
return array_key_exists((string)$id, $this->hashesByID);
}
/**
* @param array $altPrimary
* @return bool
*/
private function hasInstanceForAltPrimary(array $altPrimary)
{
$altPrimaryHash = $this->hashAltPrimary($altPrimary);
return array_key_exists($altPrimaryHash, $this->hashesByAltPrimary);
}
/**
* @param $instance
* @return bool
*/
private function hasInstance($instance)
{
$info = $this->getIndexInfo($instance);
return array_key_exists($info['hash'], $this->hashObjectMap);
}
/**
* @param $id
* @return mixed
*/
private function getInstanceByID($id)
{
if (!$this->hasInstanceForID($id)) {
throw new \InvalidArgumentException('Cannot get Instance. No instance with id ' . $id . ' in collection.');
}
return $this->hashObjectMap[$this->hashesByID[(string)$id]];
}
/**
* @param array $altPrimary
* @return mixed
*/
private function getInstanceByAltPrimary(array $altPrimary)
{
if (!$this->hasInstanceForAltPrimary($altPrimary)) {
throw new \InvalidArgumentException(
'Cannot get Instance. No instance with given alt_primary in collection.'
);
}
$hashedAltPrimary = $this->hashAltPrimary($altPrimary);
return $this->hashObjectMap[$this->hashesByAltPrimary[$hashedAltPrimary]];
}
/**
* @param $instance
* @return array
*/
private function getIndexInfo($instance)
{
$hash = spl_object_hash($instance);
$id = null;
$altPrimary = [];
$hashedAltPrimary = '';
if (property_exists($instance, 'id')) {
$idRefl = new \ReflectionProperty($instance, 'id');
$idRefl->setAccessible(true);
$id = $idRefl->getValue($instance);
}
if (null === $id) {
$id = $hash;
}
if (is_array($this->altPrimary)) {
foreach ($this->altPrimary as $fieldName) {
if (property_exists($instance, $fieldName)) {
$propRefl = new \ReflectionProperty($instance, $fieldName);
$propRefl->setAccessible(true);
$altPrimary[$fieldName] = $propRefl->getValue($instance);
}
}
$hashedAltPrimary = $this->hashAltPrimary($altPrimary);
}
$info = ['hash' => $hash, 'id' => $id, 'alt_primary' => $altPrimary, 'hashed_alt_primary' => $hashedAltPrimary];
return $info;
}
/**
* @param array $altPrimary
* @return string
*/
private function hashAltPrimary(array $altPrimary)
{
return md5(json_encode($altPrimary));
}
}