*/ abstract class AbstractRecordCollection implements RecordCollectionInterface, PersistableCollectionInterface { /** * The table name collections are stored to * * @var string */ protected static $storageItemsField = 'items'; /** * The table name collections are stored to, must be defined in the subclass * * @var string */ protected static $storageTableName = ''; /** * Uid of the storage * * @var int */ protected $uid = 0; /** * Collection title * * @var string */ protected $title; /** * Collection description * * @var string */ protected $description; /** * Table name of the records stored in this collection * * @var string */ protected $itemTableName; /** * The local storage * * @var \SplDoublyLinkedList */ protected $storage; /** * Creates this object. */ public function __construct() { $this->storage = new \SplDoublyLinkedList(); } /** * Return the current element * * @return T|null */ public function current(): mixed { return $this->storage->current(); } /** * Move forward to next element */ public function next(): void { $this->storage->next(); } /** * Return the key of the current element * * @return int|string 0 on failure. */ public function key(): mixed { $currentRecord = $this->storage->current(); return $currentRecord['uid'] ?? 0; } /** * Checks if current position is valid * * @return bool The return value will be cast to boolean and then evaluated. */ public function valid(): bool { return $this->storage->valid(); } /** * Rewind the Iterator to the first element */ public function rewind(): void { $this->storage->rewind(); } /** * Returns class state to be serialized. */ public function __serialize(): array { return [ 'uid' => $this->getIdentifier(), ]; } /** * Load records with the given serialized information */ public function __unserialize(array $arrayRepresentation): void { self::load($arrayRepresentation['uid']); } /** * Count elements of an object * * @return int The custom count as an integer. */ public function count(): int { return $this->storage->count(); } /** * Getter for the title * * @return string */ public function getTitle() { return $this->title; } /** * Getter for the UID * * @return int */ public function getUid() { return $this->uid; } /** * Getter for the description * * @return string */ public function getDescription() { return $this->description; } /** * Setter for the title * * @param string $title */ public function setTitle($title) { $this->title = $title; } /** * Setter for the description * * @param string $desc */ public function setDescription($desc) { $this->description = $desc; } /** * Setter for the name of the data-source table * * @return string */ public function getItemTableName() { return $this->itemTableName; } /** * Setter for the name of the data-source table * * @param string $tableName */ public function setItemTableName($tableName) { $this->itemTableName = $tableName; } /** * Returns the uid of the collection * * @return int */ public function getIdentifier() { return $this->uid; } /** * Sets the identifier of the collection * * @param int $id */ public function setIdentifier($id) { $this->uid = (int)$id; } /** * Loads the collections with the given id from persistence * * For memory reasons, per default only f.e. title, database-table, * identifier (what ever static data is defined) is loaded. * Entries can be load on first access. * * @param int $id Id of database record to be loaded * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections * @return CollectionInterface */ public static function load($id, $fillItems = false) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(static::getCollectionDatabaseTable()); $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); $collectionRecord = $queryBuilder->select('*') ->from(static::getCollectionDatabaseTable()) ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, Connection::PARAM_INT))) ->executeQuery() ->fetchAssociative(); return self::create($collectionRecord ?: [], $fillItems); } /** * Creates a new collection objects and reconstitutes the * given database record to the new object. * * @param array $collectionRecord Database record * @param bool $fillItems Populates the entries directly on load, might be bad for memory on large collections * @return CollectionInterface */ public static function create(array $collectionRecord, $fillItems = false) { // [phpstan] Unsafe usage of new static() // todo: Either mark this class or its constructor final or use new self instead. $collection = new static(); $collection->fromArray($collectionRecord); if ($fillItems) { $collection->loadContents(); } return $collection; } /** * Persists current collection state to underlying storage */ public function persist() { $uid = $this->getIdentifier() == 0 ? 'NEW' . random_int(100000, 999999) : $this->getIdentifier(); $data = [ trim(static::getCollectionDatabaseTable()) => [ $uid => $this->getPersistableDataArray(), ], ]; // New records always must have a pid if ($this->getIdentifier() == 0) { $data[trim(static::getCollectionDatabaseTable())][$uid]['pid'] = 0; } $tce = GeneralUtility::makeInstance(DataHandler::class); $tce->start($data, []); $tce->process_datamap(); } /** * Returns an array of the persistable properties and contents * which are processable by DataHandler. * * For internal usage in persist only. * * @return array */ abstract protected function getPersistableDataArray(); /** * Generates comma-separated list of entry uids for usage in DataHandler * * also allow to add table name, if it might be needed by DataHandler for * storing the relation * * @param bool $includeTableName * @return string */ protected function getItemUidList($includeTableName = true) { $list = []; foreach ($this->storage as $entry) { $list[] = ($includeTableName ? $this->getItemTableName() . '_' : '') . $entry['uid']; } return implode(',', $list); } /** * Builds an array representation of this collection * * @return array */ public function toArray() { $itemArray = []; foreach ($this->storage as $item) { $itemArray[] = $item; } return [ 'uid' => $this->getIdentifier(), 'title' => $this->getTitle(), 'description' => $this->getDescription(), 'table_name' => $this->getItemTableName(), 'items' => $itemArray, ]; } /** * Loads the properties of this collection from an array */ public function fromArray(array $array) { $this->uid = $array['uid']; $this->title = $array['title']; $this->description = $array['description']; $this->itemTableName = $array['table_name']; } protected static function getCollectionDatabaseTable(): string { if (!empty(static::$storageTableName)) { return static::$storageTableName; } throw new \RuntimeException('No storage table name was defined the class "' . static::class . '".', 1592207959); } }