Add queue trait

This commit is contained in:
Sebastian Meyer 2023-10-11 16:36:43 +02:00
parent 2006073f94
commit ad37644ff0
2 changed files with 291 additions and 1 deletions

View File

@ -6,7 +6,8 @@
"trait",
"getter",
"setter",
"singleton"
"singleton",
"queue"
],
"homepage": "https://github.com/opencultureconsulting/php-traits",
"readme": "README.md",

289
src/Traits/Queue.php Normal file
View File

@ -0,0 +1,289 @@
<?php
/**
* Useful PHP Traits
* Copyright (C) 2023 Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace OCC\Traits;
/**
* Handles a queue of items - optionally type-sensitive.
*
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
* @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;
}
}