From ad37644ff05d3c7d85dc5c9ac62c2dabb440bc39 Mon Sep 17 00:00:00 2001 From: Sebastian Meyer Date: Wed, 11 Oct 2023 16:36:43 +0200 Subject: [PATCH] Add queue trait --- composer.json | 3 +- src/Traits/Queue.php | 289 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 src/Traits/Queue.php diff --git a/composer.json b/composer.json index 6a5ca3d..8cd77a6 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "trait", "getter", "setter", - "singleton" + "singleton", + "queue" ], "homepage": "https://github.com/opencultureconsulting/php-traits", "readme": "README.md", diff --git a/src/Traits/Queue.php b/src/Traits/Queue.php new file mode 100644 index 0000000..1b174a9 --- /dev/null +++ b/src/Traits/Queue.php @@ -0,0 +1,289 @@ + + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +declare(strict_types=1); + +namespace OCC\Traits; + +/** + * Handles a queue of items - optionally type-sensitive. + * + * @author Sebastian Meyer + * @package opencultureconsulting/traits + * @implements \ArrayAccess + * @implements \Countable + * @implements \SeekableIterator + */ +trait Queue /* implements \ArrayAccess, \Countable, \SeekableIterator */ +{ + use Getter; + + /** + * Holds the queue's elements. + */ + protected array $queue = []; + + /** + * Holds the queue's current index. + */ + protected int $index = 0; + + /** + * Defines the allowed types for the queue's elements. + * If empty, all types are allowed. + * Possible values are: + * - "array" + * - "bool" + * - "callable" + * - "countable" + * - "float" / "double" + * - "int" / "integer" / "long" + * - "iterable" + * - "null" + * - "numeric" + * - "object" / FQCN + * - "resource" + * - "scalar" + * - "string" + * Additionally, fully qualified class names can be specified to restrict + * the types of objects. + */ + protected array $allowedTypes = []; + + /** + * Check if a variable is an allowed type. + * + * @param mixed $var The variable to check + * + * @return bool Whether the variable is an allowed type + */ + final protected function isAllowedType(mixed $var): bool + { + if (empty($this->allowedTypes)) { + return true; + } + foreach ($this->allowedTypes as $type) { + $function = 'is_' . $type; + $fqcn = '\\' . ltrim($type, '\\'); + if (function_exists($function) && $function($var)) { + return true; + } + if (is_object($var) && is_a($var, $fqcn)) { + return true; + } + } + return false; + } + + /** + * Checks if a given index is in the range of valid indexes. + * + * @param int $offset The index to check + * @param bool $allowAppend Should the next free index be valid as well? + * + * @return bool Whether the given index is in valid range + */ + final protected function isIndexInRange(int $offset, bool $allowAppend = false): bool + { + $options = [ + 'options' => [ + 'min_range' => 0, + 'max_range' => count($this->queue) - ($allowAppend ? 0 : 1) + ] + ]; + return (filter_var($offset, FILTER_VALIDATE_INT, $options) !== false); + } + + /** + * Check if a given index exists on the queue. + * @see \ArrayAccess::offsetExists + * + * @param int $offset The queue's index to check + * + * @return bool Whether the given index is valid + */ + final public function offsetExists(mixed $offset): bool + { + return isset($this->queue[$offset]); + } + + /** + * Get the element with given index from the queue. + * @see \ArrayAccess::offsetGet + * + * @param int $offset The queue's index to get + * + * @return ?mixed The queue's element at given index or NULL + */ + final public function offsetGet(mixed $offset): mixed + { + return $this->queue[$offset] ?? null; + } + + /** + * Set the element at given index in the queue. + * @see \ArrayAccess::offsetSet + * + * @param ?int $offset The queue's index to set or NULL to append + * Must be between 0 and the length of the queue + * @param mixed $value The element to set at the given index + * + * @return void + * + * @throws \InvalidArgumentException + * @throws \OutOfRangeException + */ + final public function offsetSet(mixed $offset, mixed $value): void + { + if (is_null($offset)) { + $offset = count($this->queue); + } elseif (!$this->isIndexInRange($offset, true)) { + throw new \OutOfRangeException('Invalid index to set: ' . $offset); + } + if (!$this->isAllowedType($value)) { + throw new \InvalidArgumentException('Invalid type of value: ' . gettype($value)); + } + $this->queue[$offset] = $value; + } + + /** + * Remove the element with given index from the queue. + * @see \ArrayAccess::offsetUnset + * + * @param int $offset The queue's index to unset + * + * @return void + */ + final public function offsetUnset(mixed $offset): void + { + if ($this->isIndexInRange($offset)) { + array_splice($this->queue, $offset, 1, []); + if ($offset <= $this->index) { + --$this->index; + } + } + } + + /** + * Get the number of elements in the queue. + * @see \Countable::count + * + * @return int The number of items in the queue + */ + final public function count(): int + { + return count($this->queue); + } + + /** + * Get the current element from the queue. + * @see \Iterator::current + * + * @return mixed|null The queue's current element or NULL + */ + final public function current(): mixed + { + return $this->queue[$this->index] ?? null; + } + + /** + * Get the current index from the queue. + * @see \Iterator::key + * + * @return int The queue's current index + */ + final public function key(): int + { + return $this->index; + } + + /** + * Move the index to next element of the queue. + * @see \Iterator::next + * + * @return void + */ + final public function next(): void + { + ++$this->index; + } + + /** + * Reset the index to the first element of the queue. + * @see \Iterator::rewind + * + * @return void + */ + final public function rewind(): void + { + $this->index = 0; + } + + /** + * Check if the queue's current index is valid. + * @see \Iterator::valid + * + * @return bool Whether the queue's current index is valid + */ + final public function valid(): bool + { + return isset($this->queue[$this->index]); + } + + /** + * Sets the queue's current index. + * @see \SeekableIterator::seek + * + * @param int $offset The queue's new index + * + * @return void + * + * @throws \OutOfBoundsException + */ + final public function seek(int $offset): void + { + if (!$this->isIndexInRange($offset)) { + throw new \OutOfBoundsException('Invalid index to seek: ' . $offset); + } + $this->index = $offset; + } + + /** + * Magic getter method for $this->allowedTypes. + * @see \OCC\Traits\Getter + * + * @return array The list of the queue's allowed element types + */ + final protected function _getAllowedTypes(): array + { + return $this->allowedTypes; + } + + /** + * Create a (type-sensitive) queue of elements. + * @see \OCC\Traits\Queue::allowedTypes + * + * @param string[] $allowedTypes Allowed types of queue's elements + */ + final public function __construct(array $allowedTypes = []) + { + $this->allowedTypes = $allowedTypes; + } +}