diff --git a/docs/book/v4/migration/v3-to-v4.md b/docs/book/v4/migration/v3-to-v4.md index e45c2ccc..e814477c 100644 --- a/docs/book/v4/migration/v3-to-v4.md +++ b/docs/book/v4/migration/v3-to-v4.md @@ -1,3 +1,41 @@ # Migration from Version 3 to 4 Version 4 of `laminas-stdlib` contains a number of backwards incompatible changes. This guide is intended to help you upgrade from the version 3 series to version 4. + +## New Features + +### Parameter, Property and Return Type Hints Have Been Added Throughout + +All classes have been updated to make use of parameter and return types. In general usage, this should not pose too many problems, providing you have been passing the previously documented types to the methods that have changed, however, it is advisable to audit your existing usage of the library for potential type errors. A static analysis tool like Psalm or PHPStan will help you with this. + +The addition of property, parameter and return types will cause fatal errors for extending classes that re-define those properties or override changed methods. If you have extended from any classes in laminas-stdlib, you should check that property types and method signatures are compatible and update them if they are not aligned. + +## Removed Features + +None. + +## Signature Changes + +### Breaking Changes to Return Types in Iterable Classes + +A number of Queue, Stack and Heap implementations have different return types in order to align with the built-in PHP interfaces that they implement such as `Iterator` or `IteratorAggregate` etc. + +#### `insert()` Signature Change for Iterable Types + +PHP's built-in `\SplPriorityQueue`, `\SplHeap`, `\SplMinHeap` and other similar classes return `true` from the `insert()` method. Classes that either extend from or have similar semantics to these built-in types previously had varying return types such as `void`, `self`. From version 4, the method signature for `insert()` where implemented has been changed to `bool` _(true)_. + +- `Laminas\Stdlib\SplPriorityQueue::insert()` previously returned `void`. This has been changed to `bool` to align with `\SplPriorityQueue` +- `Laminas\Stdlib\PriorityQueue::insert()` previously returned `self`. This has been changed to `bool` for consistency +- `Laminas\Stdlib\PriorityList::insert()` previously returned `void`. This has been changed to `bool` for consistency +- `Laminas\Stdlib\FastPriorityQueue::insert()` previously returned `void`. This has been changed to `bool` for consistency + +#### Other Method Signature Changes + +##### `Laminas\Stdlib\PriorityList` + +- `next()` previously returned `false` or the node value at the next index and now returns `void` to align with the `Iterator` interface. +- `setPriority()` previously returned `self` and now returns `void` for consistency. + +## Deprecations + +None. diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 26e4b69d..10c745af 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -102,20 +102,21 @@ - is_int($priority) - TValue|int|array{data: TValue|false, priority: int|null}|false + TValue|int|array{data: TValue, priority: int}|null - $array - $value + extractFlag) { + self::EXTR_DATA => current($this->values[$this->maxPriority]), + self::EXTR_PRIORITY => $this->maxPriority, + self::EXTR_BOTH => [ + 'data' => current($this->values[$this->maxPriority]), + 'priority' => $this->maxPriority, + ], + }]]> - - serialize - unserialize - @@ -129,8 +130,7 @@ maxPriority]]> - TValue|int|array{data: TValue, priority: int}|false - ]]> + TValue|int|array{data: TValue, priority: int}|null priorities]]> @@ -139,8 +139,6 @@ values]]> values]]> values]]> - values]]> - values]]> @@ -181,52 +179,17 @@ setMetadata - - - - - - current - - - - - - next - - - (int) $priority - (int) $priority - - - - - serialize - unserialize - - + + + is_array($item) is_array($priority) - - void - - - $priority - $priority - - - public function insert($datum, $priority) - - - insert - serialize - unserialize - - - - + + $toUnserialize + @@ -365,14 +328,12 @@ - $datum $item $test[] $test[] $test[] $test[] $test[] - $test[] $value $value $value @@ -397,4 +358,9 @@ foof]]> + + + new SplPriorityQueue() + + diff --git a/src/FastPriorityQueue.php b/src/FastPriorityQueue.php index 63488ae8..67d1de7f 100644 --- a/src/FastPriorityQueue.php +++ b/src/FastPriorityQueue.php @@ -6,15 +6,14 @@ use Countable; use Iterator; -use ReturnTypeWillChange; use Serializable; use SplPriorityQueue as PhpSplPriorityQueue; use UnexpectedValueException; +use function assert; use function current; use function in_array; use function is_array; -use function is_int; use function key; use function max; use function next; @@ -41,63 +40,61 @@ class FastPriorityQueue implements Iterator, Countable, Serializable public const EXTR_BOTH = PhpSplPriorityQueue::EXTR_BOTH; /** @var self::EXTR_* */ - protected $extractFlag = self::EXTR_DATA; + protected int $extractFlag = self::EXTR_DATA; /** * Elements of the queue, divided by priorities * * @var array> */ - protected $values = []; + protected array $values = []; /** * Array of priorities * * @var array */ - protected $priorities = []; + protected array $priorities = []; /** * Array of priorities used for the iteration * * @var array */ - protected $subPriorities = []; + protected array $subPriorities = []; /** * Max priority - * - * @var int|null */ - protected $maxPriority; + protected ?int $maxPriority = null; /** * Total number of elements in the queue - * - * @var int */ - protected $count = 0; + protected int $count = 0; /** * Index of the current element in the queue - * - * @var int */ - protected $index = 0; + protected int $index = 0; /** * Sub index of the current element in the same priority level - * - * @var int */ - protected $subIndex = 0; + protected int $subIndex = 0; + /** @return list */ public function __serialize(): array { $clone = clone $this; $clone->setExtractFlags(self::EXTR_BOTH); $data = []; + /** + * Forcing the type - we have explicitly set EXTR_BOTH + * + * @psalm-var array{data: TValue, priority: int} $item + */ foreach ($clone as $item) { $data[] = $item; } @@ -116,20 +113,18 @@ public function __unserialize(array $data): void * Insert an element in the queue with a specified priority * * @param TValue $value - * @param int $priority - * @return void + * @return true */ - public function insert(mixed $value, $priority) + public function insert(mixed $value, int $priority): bool { - if (! is_int($priority)) { - throw new Exception\InvalidArgumentException('The priority must be an integer'); - } $this->values[$priority][] = $value; if (! isset($this->priorities[$priority])) { $this->priorities[$priority] = $priority; $this->maxPriority = $this->maxPriority === null ? $priority : max($priority, $this->maxPriority); } ++$this->count; + + return true; } /** @@ -138,7 +133,7 @@ public function insert(mixed $value, $priority) * * @return TValue|int|array{data: TValue, priority: int}|false */ - public function extract() + public function extract(): mixed { if (! $this->valid()) { return false; @@ -160,7 +155,7 @@ public function extract() * * @return bool False if the item was not found, true otherwise. */ - public function remove(mixed $datum) + public function remove(mixed $datum): bool { $currentIndex = $this->index; $currentSubIndex = $this->subIndex; @@ -201,11 +196,8 @@ public function remove(mixed $datum) /** * Get the total number of elements in the queue - * - * @return int */ - #[ReturnTypeWillChange] - public function count() + public function count(): int { return $this->count; } @@ -213,31 +205,30 @@ public function count() /** * Get the current element in the queue * - * @return TValue|int|array{data: TValue|false, priority: int|null}|false + * @return TValue|int|array{data: TValue, priority: int}|null */ - #[ReturnTypeWillChange] - public function current() + public function current(): mixed { - switch ($this->extractFlag) { - case self::EXTR_DATA: - return current($this->values[$this->maxPriority]); - case self::EXTR_PRIORITY: - return $this->maxPriority; - case self::EXTR_BOTH: - return [ - 'data' => current($this->values[$this->maxPriority]), - 'priority' => $this->maxPriority, - ]; + if ($this->isEmpty()) { + return null; } + + assert(isset($this->values[$this->maxPriority])); + + return match ($this->extractFlag) { + self::EXTR_DATA => current($this->values[$this->maxPriority]), + self::EXTR_PRIORITY => $this->maxPriority, + self::EXTR_BOTH => [ + 'data' => current($this->values[$this->maxPriority]), + 'priority' => $this->maxPriority, + ], + }; } /** * Get the index of the current element in the queue - * - * @return int */ - #[ReturnTypeWillChange] - public function key() + public function key(): int { return $this->index; } @@ -245,10 +236,8 @@ public function key() /** * Set the iterator pointer to the next element in the queue * removing the previous element - * - * @return void */ - protected function nextAndRemove() + protected function nextAndRemove(): void { $key = key($this->values[$this->maxPriority]); @@ -269,8 +258,7 @@ protected function nextAndRemove() * Set the iterator pointer to the next element in the queue * without removing the previous element */ - #[ReturnTypeWillChange] - public function next() + public function next(): void { if (false === next($this->values[$this->maxPriority])) { unset($this->subPriorities[$this->maxPriority]); @@ -284,11 +272,8 @@ public function next() /** * Check if the current iterator is valid - * - * @return bool */ - #[ReturnTypeWillChange] - public function valid() + public function valid(): bool { return isset($this->values[$this->maxPriority]); } @@ -296,8 +281,7 @@ public function valid() /** * Rewind the current iterator */ - #[ReturnTypeWillChange] - public function rewind() + public function rewind(): void { $this->subPriorities = $this->priorities; $this->maxPriority = empty($this->priorities) ? 0 : max($this->priorities); @@ -312,7 +296,7 @@ public function rewind() * * @return list */ - public function toArray() + public function toArray(): array { $array = []; foreach (clone $this as $item) { @@ -321,23 +305,12 @@ public function toArray() return $array; } - /** - * Serialize - * - * @return string - */ - public function serialize() + public function serialize(): string { return serialize($this->__serialize()); } - /** - * Deserialize - * - * @param string $data - * @return void - */ - public function unserialize($data) + public function unserialize(string $data): void { $toUnserialize = unserialize($data); if (! is_array($toUnserialize)) { @@ -354,9 +327,8 @@ public function unserialize($data) * Set the extract flag * * @param self::EXTR_* $flag - * @return void */ - public function setExtractFlags($flag) + public function setExtractFlags(int $flag): void { $this->extractFlag = match ($flag) { self::EXTR_DATA, self::EXTR_PRIORITY, self::EXTR_BOTH => $flag, @@ -366,20 +338,16 @@ public function setExtractFlags($flag) /** * Check if the queue is empty - * - * @return bool */ - public function isEmpty() + public function isEmpty(): bool { return empty($this->values); } /** * Does the queue contain the given datum? - * - * @return bool */ - public function contains(mixed $datum) + public function contains(mixed $datum): bool { foreach ($this->values as $values) { if (in_array($datum, $values)) { @@ -391,11 +359,8 @@ public function contains(mixed $datum) /** * Does the queue have an item with the given priority? - * - * @param int $priority - * @return bool */ - public function hasPriority($priority) + public function hasPriority(int $priority): bool { return isset($this->values[$priority]); } diff --git a/src/PriorityList.php b/src/PriorityList.php index 8e2c6e53..6c834668 100644 --- a/src/PriorityList.php +++ b/src/PriorityList.php @@ -7,7 +7,7 @@ use Countable; use Exception; use Iterator; -use ReturnTypeWillChange; +use Traversable; use function array_map; use function current; @@ -32,49 +32,42 @@ class PriorityList implements Iterator, Countable * * @var array */ - protected $items = []; + protected array $items = []; /** * Serial assigned to items to preserve LIFO. * * @var positive-int|0 */ - protected $serial = 0; + protected int $serial = 0; // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCapsProperty /** * Serial order mode - * - * @var integer */ - protected $isLIFO = 1; + protected int $isLIFO = 1; // phpcs:enable /** * Internal counter to avoid usage of count(). - * - * @var int */ - protected $count = 0; + protected int $count = 0; /** * Whether the list was already sorted. - * - * @var bool */ - protected $sorted = false; + protected bool $sorted = false; /** * Insert a new item. * * @param TKey $name * @param TValue $value - * @param int $priority - * @return void + * @return true */ - public function insert($name, mixed $value, $priority = 0) + public function insert($name, mixed $value, int $priority = 0): bool { if (! isset($this->items[$name])) { $this->count++; @@ -84,36 +77,33 @@ public function insert($name, mixed $value, $priority = 0) $this->items[$name] = [ 'data' => $value, - 'priority' => (int) $priority, + 'priority' => $priority, 'serial' => $this->serial++, ]; + + return true; } /** - * @param TKey $name - * @param int $priority - * @return $this + * @param TKey $name * @throws Exception */ - public function setPriority($name, $priority) + public function setPriority($name, int $priority): void { if (! isset($this->items[$name])) { throw new Exception("item $name not found"); } - $this->items[$name]['priority'] = (int) $priority; + $this->items[$name]['priority'] = $priority; $this->sorted = false; - - return $this; } /** * Remove a item. * * @param TKey $name - * @return void */ - public function remove($name) + public function remove($name): void { if (isset($this->items[$name])) { $this->count--; @@ -124,10 +114,8 @@ public function remove($name) /** * Remove all items. - * - * @return void */ - public function clear() + public function clear(): void { $this->items = []; $this->serial = 0; @@ -141,10 +129,10 @@ public function clear() * @param TKey $name * @return TValue|null */ - public function get($name) + public function get($name): mixed { if (! isset($this->items[$name])) { - return; + return null; } return $this->items[$name]['data']; @@ -152,10 +140,8 @@ public function get($name) /** * Sort all items. - * - * @return void */ - protected function sort() + protected function sort(): void { if (! $this->sorted) { uasort($this->items, [$this, 'compare']); @@ -167,9 +153,8 @@ protected function sort() * Compare the priority of two items. * * @param array $item1, - * @return int */ - protected function compare(array $item1, array $item2) + protected function compare(array $item1, array $item2): int { return $item1['priority'] === $item2['priority'] ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO @@ -178,11 +163,8 @@ protected function compare(array $item1, array $item2) /** * Get/Set serial order mode - * - * @param bool|null $flag - * @return bool */ - public function isLIFO($flag = null) + public function isLIFO(?bool $flag = null): bool { if ($flag !== null) { $isLifo = $flag === true ? 1 : -1; @@ -199,68 +181,51 @@ public function isLIFO($flag = null) /** * {@inheritDoc} */ - #[ReturnTypeWillChange] - public function rewind() + public function rewind(): void { $this->sort(); reset($this->items); } /** - * {@inheritDoc} + * @return TValue|null */ - #[ReturnTypeWillChange] - public function current() + public function current(): mixed { $this->sorted || $this->sort(); $node = current($this->items); - return $node ? $node['data'] : false; + return $node ? $node['data'] : null; } /** - * {@inheritDoc} + * @return TKey|null */ - #[ReturnTypeWillChange] - public function key() + public function key(): int|string|null { $this->sorted || $this->sort(); return key($this->items); } - /** - * {@inheritDoc} - */ - #[ReturnTypeWillChange] - public function next() + public function next(): void { - $node = next($this->items); - - return $node ? $node['data'] : false; + next($this->items); } - /** - * {@inheritDoc} - */ - #[ReturnTypeWillChange] - public function valid() + public function valid(): bool { return current($this->items) !== false; } /** - * @return self + * @return Traversable */ - public function getIterator() + public function getIterator(): Traversable { return clone $this; } - /** - * {@inheritDoc} - */ - #[ReturnTypeWillChange] - public function count() + public function count(): int { return $this->count; } @@ -268,10 +233,10 @@ public function count() /** * Return list as array * - * @param int $flag + * @param self::EXTR_* $flag * @return array */ - public function toArray($flag = self::EXTR_DATA) + public function toArray(int $flag = self::EXTR_DATA): array { $this->sort(); diff --git a/src/PriorityQueue.php b/src/PriorityQueue.php index e39be491..3b19136d 100644 --- a/src/PriorityQueue.php +++ b/src/PriorityQueue.php @@ -6,8 +6,9 @@ use Countable; use IteratorAggregate; -use ReturnTypeWillChange; use Serializable; +use SplPriorityQueue as PhpSplPriorityQueue; +use Traversable; use UnexpectedValueException; use function array_map; @@ -30,7 +31,6 @@ * the actual iteration. * * @template TValue - * @template TPriority of int * @implements IteratorAggregate */ class PriorityQueue implements Countable, IteratorAggregate, Serializable @@ -42,44 +42,42 @@ class PriorityQueue implements Countable, IteratorAggregate, Serializable /** * Inner queue class to use for iteration * - * @var class-string<\SplPriorityQueue> + * @var class-string */ - protected $queueClass = SplPriorityQueue::class; + protected string $queueClass = SplPriorityQueue::class; /** * Actual items aggregated in the priority queue. Each item is an array * with keys "data" and "priority". * - * @var list + * @var list */ - protected $items = []; + protected array $items = []; /** * Inner queue object * - * @var \SplPriorityQueue|null + * @var PhpSplPriorityQueue|null */ - protected $queue; + protected ?PhpSplPriorityQueue $queue = null; /** * Insert an item into the queue * * Priority defaults to 1 (low priority) if none provided. * - * @param TValue $data - * @param TPriority $priority - * @return $this + * @param TValue $data + * @return true */ - public function insert($data, $priority = 1) + public function insert(mixed $data, int $priority = 1): bool { - /** @psalm-var TPriority $priority */ - $priority = (int) $priority; $this->items[] = [ 'data' => $data, 'priority' => $priority, ]; $this->getQueue()->insert($data, $priority); - return $this; + + return true; } /** @@ -122,23 +120,12 @@ public function remove(mixed $datum) return false; } - /** - * Is the queue empty? - * - * @return bool - */ - public function isEmpty() + public function isEmpty(): bool { return 0 === $this->count(); } - /** - * How many items are in the queue? - * - * @return int - */ - #[ReturnTypeWillChange] - public function count() + public function count(): int { return count($this->items); } @@ -148,7 +135,7 @@ public function count() * * @return TValue */ - public function top() + public function top(): mixed { $queue = clone $this->getQueue(); @@ -202,21 +189,15 @@ public function extract() * retrieves the inner queue object, and clones it for purposes of * iteration. * - * @return \SplPriorityQueue + * @return PhpSplPriorityQueue */ - #[ReturnTypeWillChange] - public function getIterator() + public function getIterator(): Traversable { $queue = $this->getQueue(); return clone $queue; } - /** - * Serialize the data structure - * - * @return string - */ - public function serialize() + public function serialize(): string { return serialize($this->__serialize()); } @@ -224,9 +205,9 @@ public function serialize() /** * Magic method used for serializing of an instance. * - * @return list + * @return list */ - public function __serialize() + public function __serialize(): array { return $this->items; } @@ -235,11 +216,8 @@ public function __serialize() * Unserialize a string into a PriorityQueue object * * Serialization format is compatible with {@link SplPriorityQueue} - * - * @param string $data - * @return void */ - public function unserialize($data) + public function unserialize(string $data): void { $toUnserialize = unserialize($data); if (! is_array($toUnserialize)) { @@ -249,7 +227,7 @@ public function unserialize($data) )); } - /** @psalm-var list $toUnserialize */ + /** @psalm-var list $toUnserialize */ $this->__unserialize($toUnserialize); } @@ -257,10 +235,9 @@ public function unserialize($data) /** * Magic method used to rebuild an instance. * - * @param list $data Data array. - * @return void + * @param list $data Data array */ - public function __unserialize($data) + public function __unserialize(array $data): void { foreach ($data as $item) { $this->insert($item['data'], $item['priority']); @@ -273,16 +250,16 @@ public function __unserialize($data) * sorted). You may provide one of the EXTR_* flags as an argument, allowing * the ability to return priorities or both data and priority. * - * @param int $flag + * @param self::EXTR_* $flag * @return array * @psalm-return ($flag is self::EXTR_BOTH - * ? list + * ? list * : $flag is self::EXTR_PRIORITY - * ? list + * ? list * : list * ) */ - public function toArray($flag = self::EXTR_DATA) + public function toArray(int $flag = self::EXTR_DATA): array { return match ($flag) { self::EXTR_BOTH => $this->items, @@ -297,13 +274,12 @@ public function toArray($flag = self::EXTR_DATA) * Please see {@link getIterator()} for details on the necessity of an * internal queue class. The class provided should extend SplPriorityQueue. * - * @param class-string<\SplPriorityQueue> $class + * @param class-string $class * @return $this */ - public function setInternalQueueClass($class) + public function setInternalQueueClass(string $class): self { - /** @psalm-suppress RedundantCastGivenDocblockType */ - $this->queueClass = (string) $class; + $this->queueClass = $class; return $this; } @@ -311,9 +287,8 @@ public function setInternalQueueClass($class) * Does the queue contain the given datum? * * @param TValue $datum - * @return bool */ - public function contains($datum) + public function contains(mixed $datum): bool { foreach ($this->items as $item) { if ($item['data'] === $datum) { @@ -325,11 +300,8 @@ public function contains($datum) /** * Does the queue have an item with the given priority? - * - * @param TPriority $priority - * @return bool */ - public function hasPriority($priority) + public function hasPriority(int $priority): bool { foreach ($this->items as $item) { if ($item['priority'] === $priority) { @@ -342,19 +314,19 @@ public function hasPriority($priority) /** * Get the inner priority queue instance * - * @throws Exception\DomainException - * @return \SplPriorityQueue + * @return PhpSplPriorityQueue * @psalm-assert !null $this->queue + * @throws Exception\DomainException */ - protected function getQueue() + protected function getQueue(): PhpSplPriorityQueue { if (null === $this->queue) { /** @psalm-suppress UnsafeInstantiation */ $queue = new $this->queueClass(); - /** @psalm-var \SplPriorityQueue $queue */ + /** @psalm-var PhpSplPriorityQueue $queue */ $this->queue = $queue; /** @psalm-suppress DocblockTypeContradiction */ - if (! $this->queue instanceof \SplPriorityQueue) { + if (! $this->queue instanceof PhpSplPriorityQueue) { throw new Exception\DomainException(sprintf( 'PriorityQueue expects an internal queue of type SplPriorityQueue; received "%s"', $this->queue::class @@ -365,15 +337,8 @@ protected function getQueue() return $this->queue; } - /** - * Add support for deep cloning - * - * @return void - */ public function __clone() { - if (null !== $this->queue) { - $this->queue = clone $this->queue; - } + $this->queue = clone $this->getQueue(); } } diff --git a/src/SplPriorityQueue.php b/src/SplPriorityQueue.php index 82abcbb8..ac822246 100644 --- a/src/SplPriorityQueue.php +++ b/src/SplPriorityQueue.php @@ -8,8 +8,10 @@ use UnexpectedValueException; use function array_key_exists; +use function count; use function get_debug_type; use function is_array; +use function min; use function serialize; use function sprintf; use function unserialize; @@ -24,12 +26,13 @@ * * @template TValue * @template TPriority of int - * @extends \SplPriorityQueue + * @psalm-type InternalPriority = array{0: mixed, 1: int} + * @extends \SplPriorityQueue */ class SplPriorityQueue extends \SplPriorityQueue implements Serializable { - /** @var int Seed used to ensure queue order for items of the same priority */ - protected $serial = PHP_INT_MAX; + /** Seed used to ensure queue order for items of the same priority */ + private int $serial = PHP_INT_MAX; /** * Insert a value with a given priority @@ -37,17 +40,19 @@ class SplPriorityQueue extends \SplPriorityQueue implements Serializable * Utilizes {@var $serial} to ensure that values of equal priority are * emitted in the same order in which they are inserted. * - * @param TValue $datum - * @param TPriority $priority - * @return void + * @param TValue $value + * @param TPriority|InternalPriority $priority + * @return true */ - public function insert($datum, $priority) + public function insert(mixed $value, mixed $priority): bool { if (! is_array($priority)) { $priority = [$priority, $this->serial--]; } - parent::insert($datum, $priority); + parent::insert($value, $priority); + + return true; } /** @@ -55,9 +60,9 @@ public function insert($datum, $priority) * * Array will be priority => data pairs * - * @return list + * @return list|list|list */ - public function toArray() + public function toArray(): array { $array = []; foreach (clone $this as $item) { @@ -66,12 +71,7 @@ public function toArray() return $array; } - /** - * Serialize - * - * @return string - */ - public function serialize() + public function serialize(): string { return serialize($this->__serialize()); } @@ -79,27 +79,26 @@ public function serialize() /** * Magic method used for serializing of an instance. * - * @return array + * @return list */ - public function __serialize() + public function __serialize(): array { $clone = clone $this; $clone->setExtractFlags(self::EXTR_BOTH); $data = []; + /** + * The type needs to be forced here because psalm does not know that setExtractFlags() alters the iterable value + * + * @psalm-var array{data: TValue, priority: InternalPriority} $item + */ foreach ($clone as $item) { $data[] = $item; } return $data; } - /** - * Deserialize - * - * @param string $data - * @return void - */ - public function unserialize($data) + public function unserialize(string $data): void { $toUnserialize = unserialize($data); if (! is_array($toUnserialize)) { @@ -115,12 +114,14 @@ public function unserialize($data) /** * Magic method used to rebuild an instance. * - * @param array $data Data array. - * @return void + * @param array $data Data array. */ - public function __unserialize($data) + public function __unserialize(array $data): void { - $this->serial = PHP_INT_MAX; + /** @psalm-var non-empty-list $serials */ + $serials = [ + PHP_INT_MAX, + ]; foreach ($data as $item) { if (! is_array($item)) { @@ -138,12 +139,22 @@ public function __unserialize($data) )); } - $priority = 1; - if (array_key_exists('priority', $item)) { - $priority = (int) $item['priority']; + if ( + ! array_key_exists('priority', $item) + || ! is_array($item['priority']) + || count($item['priority']) !== 2 + ) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance: corrupt item; missing or invalid "priority" element', + self::class + )); } - $this->insert($item['data'], $priority); + $serials[] = $item['priority'][1]; + + $this->insert($item['data'], $item['priority']); } + + $this->serial = min($serials) - 1; } } diff --git a/src/SplQueue.php b/src/SplQueue.php index 2656a856..40729910 100644 --- a/src/SplQueue.php +++ b/src/SplQueue.php @@ -4,7 +4,6 @@ namespace Laminas\Stdlib; -use ReturnTypeWillChange; use Serializable; use UnexpectedValueException; @@ -27,7 +26,7 @@ class SplQueue extends \SplQueue implements Serializable * * @return list */ - public function toArray() + public function toArray(): array { $array = []; foreach ($this as $item) { @@ -36,13 +35,7 @@ public function toArray() return $array; } - /** - * Serialize - * - * @return string - */ - #[ReturnTypeWillChange] - public function serialize() + public function serialize(): string { return serialize($this->__serialize()); } @@ -52,20 +45,12 @@ public function serialize() * * @return list */ - #[ReturnTypeWillChange] - public function __serialize() + public function __serialize(): array { return $this->toArray(); } - /** - * Unserialize - * - * @param string $data - * @return void - */ - #[ReturnTypeWillChange] - public function unserialize($data) + public function unserialize(string $data): void { $toUnserialize = unserialize($data); if (! is_array($toUnserialize)) { @@ -82,10 +67,8 @@ public function unserialize($data) * Magic method used to rebuild an instance. * * @param array $data Data array. - * @return void */ - #[ReturnTypeWillChange] - public function __unserialize($data) + public function __unserialize(array $data): void { foreach ($data as $item) { $this->push($item); diff --git a/src/SplStack.php b/src/SplStack.php index 52564fde..c5f20686 100644 --- a/src/SplStack.php +++ b/src/SplStack.php @@ -4,7 +4,6 @@ namespace Laminas\Stdlib; -use ReturnTypeWillChange; use Serializable; use UnexpectedValueException; @@ -26,7 +25,7 @@ class SplStack extends \SplStack implements Serializable * * @return list */ - public function toArray() + public function toArray(): array { $array = []; foreach ($this as $item) { @@ -35,13 +34,7 @@ public function toArray() return $array; } - /** - * Serialize - * - * @return string - */ - #[ReturnTypeWillChange] - public function serialize() + public function serialize(): string { return serialize($this->__serialize()); } @@ -51,20 +44,12 @@ public function serialize() * * @return list */ - #[ReturnTypeWillChange] - public function __serialize() + public function __serialize(): array { return $this->toArray(); } - /** - * Unserialize - * - * @param string $data - * @return void - */ - #[ReturnTypeWillChange] - public function unserialize($data) + public function unserialize(string $data): void { $toUnserialize = unserialize($data); if (! is_array($toUnserialize)) { @@ -81,10 +66,8 @@ public function unserialize($data) * Magic method used to rebuild an instance. * * @param array $data Data array. - * @return void */ - #[ReturnTypeWillChange] - public function __unserialize($data) + public function __unserialize(array $data): void { foreach ($data as $item) { $this->unshift($item); diff --git a/test/FastPriorityQueueTest.php b/test/FastPriorityQueueTest.php index 5f918de0..aa33d08a 100644 --- a/test/FastPriorityQueueTest.php +++ b/test/FastPriorityQueueTest.php @@ -4,8 +4,8 @@ namespace LaminasTest\Stdlib; -use Laminas\Stdlib\Exception\InvalidArgumentException; use Laminas\Stdlib\FastPriorityQueue; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; @@ -99,6 +99,7 @@ public function testMaintainsInsertOrderForDataOfEqualPriority(): void $expected = ['foo', 'bar', 'baz', 'bat']; $test = []; foreach ($queue as $datum) { + self::assertIsString($datum); $test[] = $datum; } self::assertEquals($expected, $test); @@ -137,6 +138,42 @@ public function testCanRetrieveQueueAsArray(): void self::assertSame($this->expected, $test, var_export($test, true)); } + public function testToArrayWithExtractDefaultListsOnlyValues(): void + { + $queue = new FastPriorityQueue(); + $queue->insert('bar', 100); + $queue->insert('foo', 200); + + self::assertSame(['foo', 'bar'], $queue->toArray()); + } + + public function testToArrayWithExtractPriorityListsOnlyPriorities(): void + { + $queue = new FastPriorityQueue(); + $queue->insert('bar', 100); + $queue->insert('foo', 200); + + $queue->setExtractFlags(FastPriorityQueue::EXTR_PRIORITY); + + self::assertSame([200, 100], $queue->toArray()); + } + + public function testToArrayWithExtractBothReturnsExpectedValues(): void + { + $queue = new FastPriorityQueue(); + $queue->insert('bar', 100); + $queue->insert('foo', 200); + + $queue->setExtractFlags(FastPriorityQueue::EXTR_BOTH); + + $expect = [ + ['data' => 'foo', 'priority' => 200], + ['data' => 'bar', 'priority' => 100], + ]; + + self::assertSame($expect, $queue->toArray()); + } + public function testIteratorFunctions(): void { self::assertEquals($this->expected, iterator_to_array($this->queue)); @@ -166,14 +203,6 @@ public function testSetExtractFlag(): void self::assertEquals($expected, $this->queue->extract()); } - public function testSetInvalidExtractFlag(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('The extract flag specified is not valid'); - /** @psalm-suppress InvalidArgument */ - $this->queue->setExtractFlags('foo'); - } - public function testIsEmpty(): void { $queue = new FastPriorityQueue(); @@ -348,4 +377,38 @@ public function testZeroPriority(): void } self::assertEquals($expected, $test); } + + public function testCurrentOnAnEmptyQueue(): void + { + $queue = new FastPriorityQueue(); + self::assertNull($queue->current()); + } + + /** @return list */ + public static function currentWithExtractFlags(): array + { + return [ + [FastPriorityQueue::EXTR_PRIORITY, 100], + [FastPriorityQueue::EXTR_DATA, 'foo'], + [FastPriorityQueue::EXTR_BOTH, ['data' => 'foo', 'priority' => 100]], + ]; + } + + /** @param FastPriorityQueue::EXTR_* $flag */ + #[DataProvider('currentWithExtractFlags')] + public function testCurrentWithExtractFlags(int $flag, mixed $expect): void + { + $queue = new FastPriorityQueue(); + $queue->insert('foo', 100); + $queue->setExtractFlags($flag); + + self::assertSame($expect, $queue->current()); + } + + public function testRemoveReturnsFalseWhenTheValueDoesNotExist(): void + { + $queue = new FastPriorityQueue(); + + self::assertFalse($queue->remove('foo')); + } } diff --git a/test/PriorityListTest.php b/test/PriorityListTest.php index 1a15a9c9..2824473d 100644 --- a/test/PriorityListTest.php +++ b/test/PriorityListTest.php @@ -15,7 +15,7 @@ class PriorityListTest extends TestCase { /** @var PriorityList */ - protected $list; + private PriorityList $list; protected function setUp(): void { @@ -79,7 +79,7 @@ public function testClear(): void $this->list->clear(); self::assertCount(0, $this->list); - self::assertSame(false, $this->list->current()); + self::assertNull($this->list->current()); } public function testGet(): void @@ -223,8 +223,11 @@ public function testBooleanValuesAreValid(): void $orders2 = []; foreach ($this->list as $key => $value) { - $orders1[$this->list->key()] = $this->list->current(); - $orders2[$key] = $value; + self::assertNotNull($key); + $currentKey = $this->list->key(); + self::assertNotNull($currentKey); + $orders1[$currentKey] = $this->list->current(); + $orders2[$key] = $value; } self::assertEquals($orders1, $orders2); self::assertEquals( diff --git a/test/PriorityQueueTest.php b/test/PriorityQueueTest.php index bae343f9..b7871d11 100644 --- a/test/PriorityQueueTest.php +++ b/test/PriorityQueueTest.php @@ -19,12 +19,12 @@ #[Group('Laminas_Stdlib')] class PriorityQueueTest extends TestCase { - /** @var PriorityQueue */ + /** @var PriorityQueue */ private PriorityQueue $queue; protected function setUp(): void { - /** @psalm-var PriorityQueue $this->queue */ + /** @psalm-var PriorityQueue $this->queue */ $this->queue = new PriorityQueue(); $this->queue->insert('foo', 3); $this->queue->insert('bar', 4); diff --git a/test/SplPriorityQueueTest.php b/test/SplPriorityQueueTest.php index 00a9c37e..5f8da2c4 100644 --- a/test/SplPriorityQueueTest.php +++ b/test/SplPriorityQueueTest.php @@ -15,11 +15,13 @@ use function unserialize; use function var_export; +use const PHP_INT_MAX; + #[Group('Laminas_Stdlib')] class SplPriorityQueueTest extends TestCase { - /** @var SplPriorityQueue */ - protected $queue; + /** @var SplPriorityQueue */ + private SplPriorityQueue $queue; protected function setUp(): void { @@ -70,4 +72,47 @@ public function testCanRetrieveQueueAsArray(): void $test = $this->queue->toArray(); self::assertSame($expected, $test, var_export($test, true)); } + + public function testThatToArrayWithExtractBothYieldsExpectedValues(): void + { + $queue = new SplPriorityQueue(); + $queue->insert('foo', 10); + $queue->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $values = $queue->toArray(); + + $expect = [ + [ + 'data' => 'foo', + 'priority' => [ + 10, + PHP_INT_MAX, + ], + ], + ]; + + self::assertEquals($expect, $values); + } + + public function testThatSerializeYieldsExpectedValues(): void + { + $queue = new SplPriorityQueue(); + $queue->insert('foo', 10); + $values = $queue->__serialize(); + + self::assertSame('foo', $values[0]['data']); + self::assertSame(10, $values[0]['priority'][0]); + } + + public function testThatSerializationPreservesInsertionOrder(): void + { + $queue = new SplPriorityQueue(); + $queue->insert('a', 100); + $queue->insert('b', 200); + + $serialized = serialize($queue); + $clone = unserialize($serialized); + self::assertInstanceOf(SplPriorityQueue::class, $clone); + + self::assertSame(iterator_to_array($queue), iterator_to_array($clone)); + } } diff --git a/test/StaticAnalysis/PriorityQueueGenericsCanBeUnderstood.php b/test/StaticAnalysis/PriorityQueueGenericsCanBeUnderstood.php index b2ddb94a..7a0ade08 100644 --- a/test/StaticAnalysis/PriorityQueueGenericsCanBeUnderstood.php +++ b/test/StaticAnalysis/PriorityQueueGenericsCanBeUnderstood.php @@ -12,7 +12,7 @@ final class PriorityQueueGenericsCanBeUnderstood { /** - * @param PriorityQueue $laminas + * @param PriorityQueue $laminas */ public function __construct(private PriorityQueue $laminas) {