Add queue trait
This commit is contained in:
parent
2006073f94
commit
ad37644ff0
|
@ -6,7 +6,8 @@
|
||||||
"trait",
|
"trait",
|
||||||
"getter",
|
"getter",
|
||||||
"setter",
|
"setter",
|
||||||
"singleton"
|
"singleton",
|
||||||
|
"queue"
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/opencultureconsulting/php-traits",
|
"homepage": "https://github.com/opencultureconsulting/php-traits",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue