Rework datastructures to use trait
This commit is contained in:
parent
a2a0b721be
commit
0bbeec44c9
|
@ -27,35 +27,17 @@ use ArrayAccess;
|
|||
use Countable;
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use OCC\Basics\DataStructures\Exceptions\InvalidDataTypeException;
|
||||
use OCC\Basics\Interfaces\ArrayAccessTrait;
|
||||
use OCC\Basics\Interfaces\CountableTrait;
|
||||
use OCC\Basics\Traits\Getter;
|
||||
use OCC\Basics\Traits\TypeChecker;
|
||||
use OutOfRangeException;
|
||||
use Serializable;
|
||||
|
||||
use function array_is_list;
|
||||
use function array_map;
|
||||
use function array_sum;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function get_debug_type;
|
||||
use function is_a;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_callable;
|
||||
use function is_countable;
|
||||
use function is_double;
|
||||
use function is_float;
|
||||
use function is_int;
|
||||
use function is_integer;
|
||||
use function is_iterable;
|
||||
use function is_long;
|
||||
use function is_null;
|
||||
use function is_numeric;
|
||||
use function is_resource;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function is_object;
|
||||
use function ltrim;
|
||||
use function serialize;
|
||||
use function sprintf;
|
||||
use function unserialize;
|
||||
|
@ -73,8 +55,6 @@ use function unserialize;
|
|||
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
||||
* @package Basics\DataStructures
|
||||
*
|
||||
* @property-read string[] $allowedTypes The allowed data types for items.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @template AllowedType of mixed
|
||||
|
@ -86,35 +66,28 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
use ArrayAccessTrait;
|
||||
/** @use CountableTrait<AllowedType> */
|
||||
use CountableTrait;
|
||||
use Getter;
|
||||
|
||||
/**
|
||||
* The allowed data types for collection items.
|
||||
*
|
||||
* @var string[]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected array $allowedTypes = [];
|
||||
use TypeChecker {
|
||||
setAllowedTypes as protected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the collection's items.
|
||||
*
|
||||
* @var AllowedType[]
|
||||
* @var array<array-key, AllowedType>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected array $_data = [];
|
||||
|
||||
/**
|
||||
* Add/insert a new item at the specified index.
|
||||
* Add/insert a item at the specified index.
|
||||
*
|
||||
* @param array-key $offset The new item's index
|
||||
* @param AllowedType $value The new item
|
||||
* @param array-key $offset The item's index
|
||||
* @param AllowedType $value The item
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$offset` is not of allowed type
|
||||
* @throws InvalidDataTypeException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
|
@ -140,53 +113,25 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
*
|
||||
* @param array-key $offset The item's index
|
||||
*
|
||||
* @return ?AllowedType The item or NULL if key is invalid
|
||||
* @return AllowedType The item
|
||||
*
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function get(int|string $offset): mixed
|
||||
{
|
||||
return $this->offsetGet($offset);
|
||||
if (!$this->offsetExists($offset)) {
|
||||
throw new OutOfRangeException(
|
||||
sprintf(
|
||||
'Offset %s is not a valid index key for this collection.',
|
||||
$offset
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed data types for collection items.
|
||||
*
|
||||
* @return string[] The list of allowed data types
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getAllowedTypes(): array
|
||||
{
|
||||
return $this->allowedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item's data type is allowed in the collection.
|
||||
*
|
||||
* @param AllowedType $value The item to check
|
||||
*
|
||||
* @return bool Whether the item's data type is allowed
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isAllowedType(mixed $value): bool
|
||||
{
|
||||
if (count($this->allowedTypes) === 0) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->allowedTypes as $type) {
|
||||
$function = 'is_' . $type;
|
||||
if (function_exists($function) && $function($value)) {
|
||||
return true;
|
||||
}
|
||||
/** @var class-string $fqcn */
|
||||
$fqcn = ltrim($type, '\\');
|
||||
if (is_object($value) && is_a($value, $fqcn)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
/** @var AllowedType $value */
|
||||
$value = $this->offsetGet($offset);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,7 +171,8 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$offset` or `$value` is not of allowed type
|
||||
* @throws InvalidDataTypeException if `$value` is not of allowed type
|
||||
* @throws InvalidArgumentException if `$offset` is not a valid array key
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
|
@ -240,8 +186,8 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
)
|
||||
);
|
||||
}
|
||||
if (!$this->isAllowedType($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
if (!$this->hasAllowedType($value)) {
|
||||
throw new InvalidDataTypeException(
|
||||
sprintf(
|
||||
'Parameter 2 must be an allowed type, %s given.',
|
||||
get_debug_type($value)
|
||||
|
@ -258,10 +204,20 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function remove(int|string $offset): void
|
||||
{
|
||||
if (!$this->offsetExists($offset)) {
|
||||
throw new OutOfRangeException(
|
||||
sprintf(
|
||||
'Offset %s is not a valid index key for this collection.',
|
||||
$offset
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->offsetUnset($offset);
|
||||
}
|
||||
|
||||
|
@ -283,7 +239,7 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
* @throws InvalidDataTypeException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
|
@ -292,29 +248,10 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
$this->offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set allowed data types of collection items.
|
||||
*
|
||||
* @param string[] $allowedTypes Allowed data types of items
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if any value of `$allowedTypes` is not a string
|
||||
*/
|
||||
protected function setAllowedTypes(array $allowedTypes = []): void
|
||||
{
|
||||
if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Allowed types must be array of strings or empty array.'
|
||||
);
|
||||
}
|
||||
$this->allowedTypes = $allowedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array representation of collection.
|
||||
*
|
||||
* @return AllowedType[] Array of collection items
|
||||
* @return array<array-key, AllowedType> Array of collection items
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
|
@ -338,12 +275,11 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
{
|
||||
if (!$this->isList()) {
|
||||
throw new DomainException(
|
||||
'Cannot convert into list: collection contains non-integer and/or non-consecutive keys.'
|
||||
'Cannot convert into StrictList: collection contains non-integer and/or non-consecutive keys.'
|
||||
);
|
||||
}
|
||||
$strictList = new StrictList($this->allowedTypes);
|
||||
$items = $this->toArray();
|
||||
$strictList->append(...$items);
|
||||
$strictList = new StrictList($this->getAllowedTypes());
|
||||
$strictList->append(...$this->toArray());
|
||||
return $strictList;
|
||||
}
|
||||
|
||||
|
@ -361,18 +297,6 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
$this->__unserialize($dataArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter method for $this->allowedTypes.
|
||||
*
|
||||
* @return string[] The list of allowed data types
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function _magicGetAllowedTypes(): array
|
||||
{
|
||||
return $this->getAllowedTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a type-sensitive collection of items.
|
||||
*
|
||||
|
@ -425,8 +349,8 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'StrictCollection::allowedTypes' => $this->allowedTypes,
|
||||
'StrictCollection::items' => $this->_data
|
||||
'StrictCollection::allowedTypes' => $this->getAllowedTypes(),
|
||||
'StrictCollection::items' => $this->toArray()
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -446,7 +370,7 @@ class StrictCollection implements ArrayAccess, Countable, Serializable
|
|||
/** @var string[] $allowedTypes */
|
||||
$allowedTypes = $data['StrictCollection::allowedTypes'];
|
||||
$this->setAllowedTypes($allowedTypes);
|
||||
/** @var AllowedType[] $items */
|
||||
/** @var array<array-key, AllowedType> $items */
|
||||
$items = $data['StrictCollection::items'];
|
||||
foreach ($items as $offset => $value) {
|
||||
$this->offsetSet($offset, $value);
|
||||
|
|
|
@ -23,46 +23,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCC\Basics\DataStructures;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
use OutOfRangeException;
|
||||
use RangeException;
|
||||
use RuntimeException;
|
||||
use OCC\Basics\DataStructures\Traits\StrictSplDatastructureTrait;
|
||||
use SplDoublyLinkedList;
|
||||
use OCC\Basics\Traits\Getter;
|
||||
use Serializable;
|
||||
|
||||
use function array_map;
|
||||
use function array_sum;
|
||||
use function count;
|
||||
use function function_exists;
|
||||
use function get_debug_type;
|
||||
use function in_array;
|
||||
use function is_a;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_callable;
|
||||
use function is_countable;
|
||||
use function is_double;
|
||||
use function is_float;
|
||||
use function is_int;
|
||||
use function is_integer;
|
||||
use function is_iterable;
|
||||
use function is_long;
|
||||
use function is_null;
|
||||
use function is_numeric;
|
||||
use function is_resource;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function is_object;
|
||||
use function iterator_to_array;
|
||||
use function ltrim;
|
||||
use function range;
|
||||
use function serialize;
|
||||
use function sprintf;
|
||||
use function unserialize;
|
||||
|
||||
/**
|
||||
* A type-sensitive, taversable list.
|
||||
|
@ -74,709 +36,13 @@ use function unserialize;
|
|||
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
||||
* @package Basics\DataStructures
|
||||
*
|
||||
* @property-read string[] $allowedTypes The allowed data types for values.
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @template AllowedType of mixed
|
||||
* @extends SplDoublyLinkedList<AllowedType>
|
||||
* @implements ArrayAccess<int, AllowedType>
|
||||
* @implements Iterator<AllowedType>
|
||||
*/
|
||||
class StrictList extends SplDoublyLinkedList implements ArrayAccess, Countable, Iterator, Serializable
|
||||
class StrictList extends SplDoublyLinkedList
|
||||
{
|
||||
use Getter;
|
||||
|
||||
/**
|
||||
* The allowed data types for list items.
|
||||
*
|
||||
* @var string[]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected array $allowedTypes = [];
|
||||
|
||||
/**
|
||||
* Add/insert a new item at the specified offset.
|
||||
*
|
||||
* @param int $offset The offset where the new item is to be inserted
|
||||
* @param AllowedType $value The new item for the offset
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function add(int $offset, mixed $value): void
|
||||
{
|
||||
$this->offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append items at the end of the list.
|
||||
*
|
||||
* @param AllowedType ...$values One or more items to append
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if any `$values` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function append(mixed ...$values): void
|
||||
{
|
||||
/** @var array<int, AllowedType> $values */
|
||||
foreach ($values as $count => $value) {
|
||||
if (!$this->isAllowedType($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Parameter %d must be an allowed type, %s given.',
|
||||
$count + 1,
|
||||
get_debug_type($value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
parent::push($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek at the item at the beginning of the list.
|
||||
*
|
||||
* @return AllowedType The first item of the list
|
||||
*
|
||||
* @throws RuntimeException if the list is empty
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function bottom(): mixed
|
||||
{
|
||||
return parent::bottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the list of any items.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
while (!$this->isEmpty()) {
|
||||
$this->pop();
|
||||
}
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items on the list.
|
||||
*
|
||||
* @return int The number of items on the list
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return parent::count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current list item.
|
||||
*
|
||||
* @return AllowedType The current item
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function current(): mixed
|
||||
{
|
||||
return parent::current();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item at the specified index.
|
||||
*
|
||||
* @param int $offset The item's index
|
||||
*
|
||||
* @return AllowedType The item
|
||||
*
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function get(int $offset): mixed
|
||||
{
|
||||
return $this->offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed data types for list items.
|
||||
*
|
||||
* @return string[] The list of allowed data types
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getAllowedTypes(): array
|
||||
{
|
||||
return $this->allowedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mode of iteration.
|
||||
*
|
||||
* @return int The set of flags and modes of iteration
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getIteratorMode(): int
|
||||
{
|
||||
return parent::getIteratorMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item's data type is allowed on the list.
|
||||
*
|
||||
* @param AllowedType $value The item to check
|
||||
*
|
||||
* @return bool Whether the item's data type is allowed
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isAllowedType(mixed $value): bool
|
||||
{
|
||||
if (count($this->allowedTypes) === 0) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->allowedTypes as $type) {
|
||||
$function = 'is_' . $type;
|
||||
if (function_exists($function) && $function($value)) {
|
||||
return true;
|
||||
}
|
||||
/** @var class-string $fqcn */
|
||||
$fqcn = ltrim($type, '\\');
|
||||
if (is_object($value) && is_a($value, $fqcn)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if list is empty.
|
||||
*
|
||||
* @return bool Whether the list contains no items
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return parent::isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this can be considered a list.
|
||||
*
|
||||
* @return true Always TRUE (this exists only for compatibility reasons)
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function isList(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current list index.
|
||||
*
|
||||
* @return int The current list index
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return parent::key();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor to the next list index.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
parent::next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the specified index exists and is not empty.
|
||||
*
|
||||
* @param int $offset The index to check
|
||||
*
|
||||
* @return bool Whether the index exists and is not empty
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return parent::offsetExists($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item from the specified index.
|
||||
*
|
||||
* @param int $offset The item's index
|
||||
*
|
||||
* @return AllowedType The item
|
||||
*
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return parent::offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item at the specified offset.
|
||||
*
|
||||
* @param ?int $offset The offset being set or NULL to append
|
||||
* @param AllowedType $value The new item for the offset
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
if (!$this->isAllowedType($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Parameter 2 must be an allowed type, %s given.',
|
||||
get_debug_type($value)
|
||||
)
|
||||
);
|
||||
}
|
||||
/** @psalm-suppress PossiblyNullArgument */
|
||||
parent::offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the item at the specified index.
|
||||
*
|
||||
* @param int $offset The item's index
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
parent::offsetUnset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops an item from the end of the list.
|
||||
*
|
||||
* @return AllowedType The item from the end of the list
|
||||
*
|
||||
* @throws RuntimeException if the list is empty
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function pop(): mixed
|
||||
{
|
||||
return parent::pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend items at the start of the list.
|
||||
*
|
||||
* @param AllowedType ...$values One or more items to prepend
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function prepend(mixed ...$values): void
|
||||
{
|
||||
/** @var array<int, AllowedType> $values */
|
||||
foreach ($values as $count => $value) {
|
||||
if (!$this->isAllowedType($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Parameter %d must be an allowed type, %s given.',
|
||||
$count + 1,
|
||||
get_debug_type($value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
parent::unshift($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor to the previous list index.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function prev(): void
|
||||
{
|
||||
parent::prev();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an item at the end of the list.
|
||||
*
|
||||
* @param AllowedType $value The item to push
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function push(mixed $value): void
|
||||
{
|
||||
if (!$this->isAllowedType($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Parameter 1 must be an allowed type, %s given.',
|
||||
get_debug_type($value)
|
||||
)
|
||||
);
|
||||
}
|
||||
parent::push($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the list.
|
||||
*
|
||||
* @param int $offset The item's index
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws OutOfRangeException when `$offset` is out of bounds
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function remove(int $offset): void
|
||||
{
|
||||
$this->offsetUnset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the iterator's cursor.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
parent::rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string representation of $this.
|
||||
*
|
||||
* @return string The string representation
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function serialize(): string
|
||||
{
|
||||
return serialize($this->__serialize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an item at the specified index.
|
||||
*
|
||||
* @param int $offset The item's index
|
||||
* @param AllowedType $value The item
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function set(int $offset, mixed $value): void
|
||||
{
|
||||
$this->offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set allowed data types of list items.
|
||||
*
|
||||
* @param string[] $allowedTypes Allowed data types of items
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if any value of `$allowedTypes` is not a string
|
||||
*/
|
||||
protected function setAllowedTypes(array $allowedTypes = []): void
|
||||
{
|
||||
if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Allowed types must be array of strings or empty array.'
|
||||
);
|
||||
}
|
||||
$this->allowedTypes = $allowedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of iteration.
|
||||
*
|
||||
* @param int $mode The new iterator mode (0, 1, 2 or 3)
|
||||
*
|
||||
* There are two orthogonal sets of modes that can be set.
|
||||
*
|
||||
* The direction of iteration (either one or the other):
|
||||
* - StrictList::IT_MODE_FIFO (queue style)
|
||||
* - StrictList::IT_MODE_LIFO (stack style)
|
||||
*
|
||||
* The behavior of the iterator (either one or the other):
|
||||
* - StrictList::IT_MODE_DELETE (delete items)
|
||||
* - StrictList::IT_MODE_KEEP (keep items)
|
||||
*
|
||||
* The default mode is: IT_MODE_FIFO | IT_MODE_KEEP
|
||||
*
|
||||
* @return int The set of flags and modes of iteration
|
||||
*
|
||||
* @throws RangeException if an invalid `$mode` is given
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function setIteratorMode(int $mode): int
|
||||
{
|
||||
if (!in_array($mode, range(0, 3), true)) {
|
||||
throw new RangeException(
|
||||
sprintf(
|
||||
'Iterator mode must be an integer in range [0..3], %d given.',
|
||||
$mode
|
||||
)
|
||||
);
|
||||
}
|
||||
return parent::setIteratorMode($mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift an item from the beginning of the list.
|
||||
*
|
||||
* @return AllowedType The first item of the list
|
||||
*
|
||||
* @throws RuntimeException if the list is empty
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function shift(): mixed
|
||||
{
|
||||
return parent::shift();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array representation of list.
|
||||
*
|
||||
* @return AllowedType[] Array of list items
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return iterator_to_array($this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn list into a type-sensitive collection.
|
||||
*
|
||||
* @return StrictCollection<AllowedType> A type-sensitive collection of the list's items
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function toStrictCollection(): StrictCollection
|
||||
{
|
||||
$strictCollection = new StrictCollection($this->allowedTypes);
|
||||
foreach ($this->toArray() as $offset => $value) {
|
||||
$strictCollection[$offset] = $value;
|
||||
}
|
||||
return $strictCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek at the item at the end of the list.
|
||||
*
|
||||
* @return AllowedType The last item of the list
|
||||
*
|
||||
* @throws RuntimeException if the list is empty
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function top(): mixed
|
||||
{
|
||||
return parent::top();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore $this from string representation.
|
||||
*
|
||||
* @param string $data The string representation
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function unserialize($data): void
|
||||
{
|
||||
/** @var mixed[] $dataArray */
|
||||
$dataArray = unserialize($data);
|
||||
$this->__unserialize($dataArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend the list with an item.
|
||||
*
|
||||
* @param AllowedType $value The item to unshift
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function unshift(mixed $value): void
|
||||
{
|
||||
if (!$this->isAllowedType($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Parameter 1 must be an allowed type, %s given.',
|
||||
get_debug_type($value)
|
||||
)
|
||||
);
|
||||
}
|
||||
parent::unshift($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current cursor position is valid.
|
||||
*
|
||||
* @return bool Whether the current cursor position is valid
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return parent::valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter method for $this->allowedTypes.
|
||||
*
|
||||
* @return string[] The list of allowed data types
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function _magicGetAllowedTypes(): array
|
||||
{
|
||||
return $this->getAllowedTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a type-sensitive, traversable list of items.
|
||||
*
|
||||
* @param string[] $allowedTypes Allowed data types of items (optional)
|
||||
*
|
||||
* If empty, all types are allowed.
|
||||
* Possible values are:
|
||||
* - "array"
|
||||
* - "bool"
|
||||
* - "callable"
|
||||
* - "countable"
|
||||
* - "float" or "double"
|
||||
* - "int" or "integer" or "long"
|
||||
* - "iterable"
|
||||
* - "null"
|
||||
* - "numeric"
|
||||
* - "object" or FQCN
|
||||
* - "resource"
|
||||
* - "scalar"
|
||||
* - "string"
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if any value of `$allowedTypes` is not a string
|
||||
*/
|
||||
public function __construct(array $allowedTypes = [])
|
||||
{
|
||||
$this->setAllowedTypes($allowedTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get debug information for $this.
|
||||
*
|
||||
* @return mixed[] The debug information
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->__serialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array representation of $this.
|
||||
*
|
||||
* @return mixed[] The array representation
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [
|
||||
'StrictList::allowedTypes' => $this->allowedTypes,
|
||||
'SplDoublyLinkedList::dllist' => iterator_to_array($this),
|
||||
'SplDoublyLinkedList::flags' => $this->getIteratorMode()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore $this from array representation.
|
||||
*
|
||||
* @param mixed[] $data The array representation
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @psalm-suppress MethodSignatureMismatch
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
/** @var string[] $allowedTypes */
|
||||
$allowedTypes = $data['StrictList::allowedTypes'];
|
||||
$this->setAllowedTypes($allowedTypes);
|
||||
/** @var array<int, AllowedType> $values */
|
||||
$values = $data['SplDoublyLinkedList::dllist'];
|
||||
$this->append(...$values);
|
||||
/** @var int $flags */
|
||||
$flags = $data['SplDoublyLinkedList::flags'];
|
||||
$this->setIteratorMode($flags);
|
||||
}
|
||||
/** @use StrictSplDatastructureTrait<AllowedType> */
|
||||
use StrictSplDatastructureTrait;
|
||||
}
|
||||
|
|
|
@ -23,24 +23,16 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCC\Basics\DataStructures;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
use RangeException;
|
||||
use RuntimeException;
|
||||
use Serializable;
|
||||
|
||||
use function sprintf;
|
||||
use OCC\Basics\DataStructures\Exceptions\InvalidDataTypeException;
|
||||
use OCC\Basics\DataStructures\Traits\StrictSplDatastructureTrait;
|
||||
use SplQueue;
|
||||
|
||||
/**
|
||||
* A type-sensitive, taversable queue (FIFO).
|
||||
*
|
||||
* Extends [\SplDoublyLinkedList](https://www.php.net/spldoublylinkedlist) with
|
||||
* an option to restrict the allowed data types for list items by providing the
|
||||
* constructor with an array of atomic types or fully qualified class names. It
|
||||
* also restricts the iterator direction to first-in, first-out (FIFO) exactly
|
||||
* like [\SplQueue](https://www.php.net/splqueue).
|
||||
* Extends [\SplQueue](https://www.php.net/splqueue) with an option to restrict
|
||||
* the allowed data types for list items by providing the constructor with an
|
||||
* array of atomic types or fully qualified class names.
|
||||
*
|
||||
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
||||
* @package Basics\DataStructures
|
||||
|
@ -48,23 +40,12 @@ use function sprintf;
|
|||
* @api
|
||||
*
|
||||
* @template AllowedType of mixed
|
||||
* @extends StrictList<AllowedType>
|
||||
* @implements ArrayAccess<int, AllowedType>
|
||||
* @implements Iterator<AllowedType>
|
||||
* @extends SplQueue<AllowedType>
|
||||
*/
|
||||
class StrictQueue extends StrictList implements ArrayAccess, Countable, Iterator, Serializable
|
||||
class StrictQueue extends SplQueue
|
||||
{
|
||||
/**
|
||||
* Dequeue an item from the queue.
|
||||
*
|
||||
* @return AllowedType The dequeued item
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function dequeue(): mixed
|
||||
{
|
||||
return parent::shift();
|
||||
}
|
||||
/** @use StrictSplDatastructureTrait<AllowedType> */
|
||||
use StrictSplDatastructureTrait;
|
||||
|
||||
/**
|
||||
* Add an item to the queue.
|
||||
|
@ -73,79 +54,12 @@ class StrictQueue extends StrictList implements ArrayAccess, Countable, Iterator
|
|||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
* @throws InvalidDataTypeException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function enqueue(mixed $value): void
|
||||
{
|
||||
parent::push($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of iteration.
|
||||
*
|
||||
* @param int $mode The new iterator mode (0 or 1)
|
||||
*
|
||||
* There are two orthogonal sets of modes that can be set.
|
||||
*
|
||||
* The direction of iteration (fixed for StrictQueue):
|
||||
* - StrictQueue::IT_MODE_FIFO (queue style)
|
||||
*
|
||||
* The behavior of the iterator (either one or the other):
|
||||
* - StrictQueue::IT_MODE_DELETE (delete items)
|
||||
* - StrictQueue::IT_MODE_KEEP (keep items)
|
||||
*
|
||||
* The default mode is: IT_MODE_FIFO | IT_MODE_KEEP
|
||||
*
|
||||
* @return int The set of flags and modes of iteration
|
||||
*
|
||||
* @throws RangeException if an invalid `$mode` is given
|
||||
* @throws RuntimeException if trying to change iterator direction
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
final public function setIteratorMode(int $mode): int
|
||||
{
|
||||
if ($mode > 1) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'Changing the iterator direction of %s is prohibited.',
|
||||
static::class
|
||||
)
|
||||
);
|
||||
}
|
||||
return parent::setIteratorMode($mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a type-sensitive, traversable queue of items.
|
||||
*
|
||||
* @param string[] $allowedTypes Allowed data types of items (optional)
|
||||
*
|
||||
* If empty, all types are allowed.
|
||||
* Possible values are:
|
||||
* - "array"
|
||||
* - "bool"
|
||||
* - "callable"
|
||||
* - "countable"
|
||||
* - "float" or "double"
|
||||
* - "int" or "integer" or "long"
|
||||
* - "iterable"
|
||||
* - "null"
|
||||
* - "numeric"
|
||||
* - "object" or FQCN
|
||||
* - "resource"
|
||||
* - "scalar"
|
||||
* - "string"
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if any value of `$allowedTypes` is not a string
|
||||
*/
|
||||
public function __construct(array $allowedTypes = [])
|
||||
{
|
||||
parent::__construct($allowedTypes);
|
||||
$this->setIteratorMode(0);
|
||||
$this->push($value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,24 +23,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCC\Basics\DataStructures;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
use RangeException;
|
||||
use OCC\Basics\DataStructures\Exceptions\InvalidDataTypeException;
|
||||
use OCC\Basics\DataStructures\Traits\StrictSplDatastructureTrait;
|
||||
use RuntimeException;
|
||||
use Serializable;
|
||||
|
||||
use function sprintf;
|
||||
use SplStack;
|
||||
|
||||
/**
|
||||
* A type-sensitive, taversable stack (LIFO).
|
||||
*
|
||||
* Extends [\SplDoublyLinkedList](https://www.php.net/spldoublylinkedlist) with
|
||||
* an option to restrict the allowed data types for list items by providing the
|
||||
* constructor with an array of atomic types or fully qualified class names. It
|
||||
* also restricts the iterator direction to last-in, first-out (LIFO) exactly
|
||||
* like [\SplStack](https://www.php.net/splstack).
|
||||
* Extends [\SplStack](https://www.php.net/splstack) with an option to restrict
|
||||
* the allowed data types for list items by providing the constructor with an
|
||||
* array of atomic types or fully qualified class names.
|
||||
*
|
||||
* @author Sebastian Meyer <sebastian.meyer@opencultureconsulting.com>
|
||||
* @package Basics\DataStructures
|
||||
|
@ -48,12 +41,13 @@ use function sprintf;
|
|||
* @api
|
||||
*
|
||||
* @template AllowedType of mixed
|
||||
* @extends StrictList<AllowedType>
|
||||
* @implements ArrayAccess<int, AllowedType>
|
||||
* @implements Iterator<AllowedType>
|
||||
* @extends SplStack<AllowedType>
|
||||
*/
|
||||
class StrictStack extends StrictList implements ArrayAccess, Countable, Iterator, Serializable
|
||||
class StrictStack extends SplStack
|
||||
{
|
||||
/** @use StrictSplDatastructureTrait<AllowedType> */
|
||||
use StrictSplDatastructureTrait;
|
||||
|
||||
/**
|
||||
* Add an item to the stack.
|
||||
*
|
||||
|
@ -61,13 +55,13 @@ class StrictStack extends StrictList implements ArrayAccess, Countable, Iterator
|
|||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if `$value` is not of allowed type
|
||||
* @throws InvalidDataTypeException if `$value` is not of allowed type
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function stack(mixed $value): void
|
||||
{
|
||||
parent::push($value);
|
||||
$this->push($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,77 +69,12 @@ class StrictStack extends StrictList implements ArrayAccess, Countable, Iterator
|
|||
*
|
||||
* @return AllowedType The unstacked item
|
||||
*
|
||||
* @throws RuntimeException if the list is empty
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function unstack(): mixed
|
||||
{
|
||||
return parent::pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode of iteration.
|
||||
*
|
||||
* @param int $mode The new iterator mode (2 or 3)
|
||||
*
|
||||
* There are two orthogonal sets of modes that can be set.
|
||||
*
|
||||
* The direction of iteration (fixed for StrictStack):
|
||||
* - StrictStack::IT_MODE_LIFO (stack style)
|
||||
*
|
||||
* The behavior of the iterator (either one or the other):
|
||||
* - StrictStack::IT_MODE_DELETE (delete items)
|
||||
* - StrictStack::IT_MODE_KEEP (keep items)
|
||||
*
|
||||
* The default mode is: IT_MODE_LIFO | IT_MODE_KEEP
|
||||
*
|
||||
* @return int The set of flags and modes of iteration
|
||||
*
|
||||
* @throws RangeException if an invalid `$mode` is given
|
||||
* @throws RuntimeException if trying to change iterator direction
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
final public function setIteratorMode(int $mode): int
|
||||
{
|
||||
if ($mode < 2) {
|
||||
throw new RuntimeException(
|
||||
sprintf(
|
||||
'Changing the iterator direction of %s is prohibited.',
|
||||
static::class
|
||||
)
|
||||
);
|
||||
}
|
||||
return parent::setIteratorMode($mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a type-sensitive, traversable stack of items.
|
||||
*
|
||||
* @param string[] $allowedTypes Allowed data types of items (optional)
|
||||
*
|
||||
* If empty, all types are allowed.
|
||||
* Possible values are:
|
||||
* - "array"
|
||||
* - "bool"
|
||||
* - "callable"
|
||||
* - "countable"
|
||||
* - "float" or "double"
|
||||
* - "int" or "integer" or "long"
|
||||
* - "iterable"
|
||||
* - "null"
|
||||
* - "numeric"
|
||||
* - "object" or FQCN
|
||||
* - "resource"
|
||||
* - "scalar"
|
||||
* - "string"
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws InvalidArgumentException if any value of `$allowedTypes` is not a string
|
||||
*/
|
||||
public function __construct(array $allowedTypes = [])
|
||||
{
|
||||
parent::__construct($allowedTypes);
|
||||
$this->setIteratorMode(2);
|
||||
return $this->pop();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue