package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -1,4 +1,4 @@
Copyright (c) 2015-2021 Ben Ramsey <ben@benramsey.com>
Copyright (c) 2015-2022 Ben Ramsey <ben@benramsey.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -9,7 +9,7 @@
<a href="https://packagist.org/packages/ramsey/collection"><img src="https://img.shields.io/packagist/v/ramsey/collection.svg?style=flat-square&label=release" alt="Download Package"></a>
<a href="https://php.net"><img src="https://img.shields.io/packagist/php-v/ramsey/collection.svg?style=flat-square&colorB=%238892BF" alt="PHP Programming Language"></a>
<a href="https://github.com/ramsey/collection/blob/master/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/collection.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a>
<a href="https://github.com/ramsey/collection/actions?query=workflow%3ACI"><img src="https://img.shields.io/github/workflow/status/ramsey/collection/CI?label=CI&logo=github&style=flat-square" alt="Build Status"></a>
<a href="https://github.com/ramsey/collection/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/actions/workflow/status/ramsey/collection/continuous-integration.yml?branch=main&logo=github&style=flat-square" alt="Build Status"></a>
<a href="https://codecov.io/gh/ramsey/collection"><img src="https://img.shields.io/codecov/c/gh/ramsey/collection?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a>
<a href="https://shepherd.dev/github/ramsey/collection"><img src="https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fshepherd.dev%2Fgithub%2Framsey%2Fcollection%2Fcoverage" alt="Psalm Type Coverage"></a>
</p>
@@ -34,25 +34,13 @@ composer require ramsey/collection
## Usage
Examples of how to use this framework can be found in the
Examples of how to use this library may be found in the
[Wiki pages](https://github.com/ramsey/collection/wiki/Examples).
## Contributing
Contributions are welcome! Before contributing to this project, familiarize
yourself with [CONTRIBUTING.md](CONTRIBUTING.md).
To develop this project, you will need [PHP](https://www.php.net) 7.3 or greater
and [Composer](https://getcomposer.org).
After cloning this repository locally, execute the following commands:
``` bash
cd /path/to/repository
composer install
```
Now, you are ready to develop!
Contributions are welcome! To contribute, please familiarize yourself with
[CONTRIBUTING.md](CONTRIBUTING.md).
## Coordinated Disclosure
@@ -79,4 +67,4 @@ MIT License (MIT). Please see [LICENSE](LICENSE) for more information.
[java]: http://docs.oracle.com/javase/8/docs/technotes/guides/collections/index.html
[security.md]: https://github.com/ramsey/collection/blob/master/SECURITY.md
[security.md]: https://github.com/ramsey/collection/blob/main/SECURITY.md

View File

@@ -1,29 +1,59 @@
<!--
This policy was created using the HackerOne Policy Builder:
https://hackerone.com/policy-builder/
This policy template was created using the HackerOne Policy Builder [1],
with guidance from the National Telecommunications and Information
Administration Coordinated Vulnerability Disclosure Template [2].
-->
# Vulnerability Disclosure Policy
# Vulnerability Disclosure Policy (VDP)
## Brand Promise
<!--
This is your brand promise. Its objective is to "demonstrate a clear, good
faith commitment to customers and other stakeholders potentially impacted by
security vulnerabilities" [2].
-->
Keeping user information safe and secure is a top priority, and we welcome the
contribution of external security researchers.
## Scope
<!--
This is your initial scope. It tells vulnerability finders and reporters
"which systems and capabilities are 'fair game' versus 'off limits'" [2].
For software packages, this is often a list of currently maintained versions
of the package.
-->
If you believe you've found a security issue in software that is maintained in
this repository, we encourage you to notify us.
| Version | In scope | Source code |
| :-----: | :------: | :---------- |
| ------- | :------: | ----------- |
| latest | ✅ | https://github.com/ramsey/collection |
## How to Submit a Report
To submit a vulnerability report, please contact us at <security@ramsey.dev>.
<!--
This is your communication process. It tells security researchers how to
contact you to report a vulnerability. It may be a link to a web form that
uses HTTPS for secure communication, or it may be an email address.
Optionally, you may choose to include a PGP public key, so that researchers
may send you encrypted messages.
-->
To submit a vulnerability report, please contact us at security@ramsey.dev.
Your submission will be reviewed and validated by a member of our team.
## Safe Harbor
<!--
This section assures vulnerability finders and reporters that they will
receive good faith responses to their good faith acts. In other words,
"we will not take legal action if..." [2].
-->
We support safe harbor for security researchers who:
* Make a good faith effort to avoid privacy violations, destruction of data, and
@@ -33,7 +63,7 @@ We support safe harbor for security researchers who:
us immediately, do not proceed with access, and immediately purge any local
information.
* Provide us with a reasonable amount of time to resolve vulnerabilities prior
to any disclosure to the public or a third-party.
to any disclosure to the public or a third party.
We will consider activities conducted consistent with this policy to constitute
"authorized" conduct and will not pursue civil action or initiate a complaint to
@@ -45,15 +75,41 @@ with or unaddressed by this policy.
## Preferences
<!--
The preferences section sets expectations based on priority and submission
volume, rather than legal objection or restriction [2].
According to the NTIA [2]:
This section is a living document that sets expectations for preferences
and priorities, typically maintained by the support and engineering
team. This can outline classes of vulnerabilities, reporting style
(crash dumps, CVSS scoring, proof-of-concept, etc.), tools, etc. Too
many preferences can set the wrong tone or make reporting findings
difficult to navigate. This section also sets expectations to the
researcher community for what types of issues are considered important
or not.
-->
* Please provide detailed reports with reproducible steps and a clearly defined
impact.
* Include the version number of the vulnerable package in your report
* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
<!--
References
[1] HackerOne. Policy builder. Retrieved from https://hackerone.com/policy-builder/
[2] NTIA Safety Working Group. 2016. "Early stage" coordinated vulnerability
disclosure template: Version 1.1. (15 December 2016). Retrieved from
https://www.ntia.doc.gov/files/ntia/publications/ntia_vuln_disclosure_early_stage_template.pdf
-->
## Encryption Key for security@ramsey.dev
For increased privacy when reporting sensitive issues, you may encrypt your
messages using the following key:
message using the following public key:
```
-----BEGIN PGP PUBLIC KEY BLOCK-----

View File

@@ -1,7 +1,8 @@
{
"name": "ramsey/collection",
"type": "library",
"description": "A PHP library for representing and manipulating collections.",
"license": "MIT",
"type": "library",
"keywords": [
"array",
"collection",
@@ -10,7 +11,6 @@
"queue",
"set"
],
"license": "MIT",
"authors": [
{
"name": "Ben Ramsey",
@@ -19,31 +19,32 @@
}
],
"require": {
"php": "^7.3 || ^8",
"symfony/polyfill-php81": "^1.23"
"php": "^8.1"
},
"require-dev": {
"captainhook/captainhook": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"ergebnis/composer-normalize": "^2.6",
"fakerphp/faker": "^1.5",
"hamcrest/hamcrest-php": "^2",
"jangregor/phpstan-prophecy": "^0.8",
"mockery/mockery": "^1.3",
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1",
"phpstan/phpstan": "^0.12.32",
"phpstan/phpstan-mockery": "^0.12.5",
"phpstan/phpstan-phpunit": "^0.12.11",
"phpunit/phpunit": "^8.5 || ^9",
"psy/psysh": "^0.10.4",
"slevomat/coding-standard": "^6.3",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.4"
},
"config": {
"sort-packages": true
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
},
"minimum-stability": "RC",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
@@ -51,7 +52,6 @@
},
"autoload-dev": {
"psr-4": {
"Ramsey\\Console\\": "resources/console/",
"Ramsey\\Collection\\Test\\": "tests/",
"Ramsey\\Test\\Generics\\": "tests/generics/"
},
@@ -59,44 +59,61 @@
"vendor/hamcrest/hamcrest-php/hamcrest/Hamcrest.php"
]
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true,
"phpstan/extension-installer": true,
"captainhook/plugin-composer": true
},
"sort-packages": true
},
"extra": {
"captainhook": {
"force-install": true
},
"ramsey/conventional-commits": {
"configFile": "conventional-commits.json"
}
},
"scripts": {
"post-autoload-dump": "captainhook install --ansi -f -s",
"dev:analyze": [
"@dev:analyze:phpstan",
"@dev:analyze:psalm"
],
"dev:analyze:phpstan": "phpstan --memory-limit=1G analyse",
"dev:analyze:psalm": "psalm --diff --config=psalm.xml",
"dev:build:clean": "git clean -fX build/.",
"dev:build:clear-cache": "git clean -fX build/cache/.",
"dev:lint": "phpcs --cache=build/cache/phpcs.cache",
"dev:lint:fix": "./bin/lint-fix.sh",
"dev:repl": [
"echo ; echo 'Type ./bin/repl to start the REPL.'"
"dev:analyze:phpstan": "phpstan analyse --ansi --memory-limit=1G",
"dev:analyze:psalm": "psalm",
"dev:build:clean": "git clean -fX build/",
"dev:lint": [
"@dev:lint:syntax",
"@dev:lint:style"
],
"dev:test": "phpunit",
"dev:test:all": [
"dev:lint:fix": "phpcbf",
"dev:lint:style": "phpcs --colors",
"dev:lint:syntax": "parallel-lint --colors src/ tests/",
"dev:test": [
"@dev:lint",
"@dev:analyze",
"@dev:test"
"@dev:test:unit"
],
"dev:test:coverage:ci": "phpunit --coverage-clover build/logs/clover.xml",
"dev:test:coverage:html": "phpunit --coverage-html build/coverage",
"test": "@dev:test:all"
"dev:test:coverage:ci": "phpunit --colors=always --coverage-text --coverage-clover build/coverage/clover.xml --coverage-cobertura build/coverage/cobertura.xml --coverage-crap4j build/coverage/crap4j.xml --coverage-xml build/coverage/coverage-xml --log-junit build/junit.xml",
"dev:test:coverage:html": "phpunit --colors=always --coverage-html build/coverage/coverage-html/",
"dev:test:unit": "phpunit --colors=always",
"test": "@dev:test"
},
"scripts-descriptions": {
"dev:analyze": "Performs static analysis on the code base.",
"dev:analyze": "Runs all static analysis checks.",
"dev:analyze:phpstan": "Runs the PHPStan static analyzer.",
"dev:analyze:psalm": "Runs the Psalm static analyzer.",
"dev:build:clean": "Removes everything not under version control from the build directory.",
"dev:build:clear-cache": "Removes everything not under version control from build/cache/.",
"dev:lint": "Checks all source code for coding standards issues.",
"dev:lint:fix": "Checks source code for coding standards issues and fixes them, if possible.",
"dev:repl": "Note: Use ./bin/repl to run the REPL.",
"dev:test": "Runs the full unit test suite.",
"dev:test:all": "Runs linting, static analysis, and unit tests.",
"dev:test:coverage:ci": "Runs the unit test suite and generates a Clover coverage report.",
"dev:test:coverage:html": "Runs the unit tests suite and generates an HTML coverage report.",
"test": "Shortcut to run the full test suite."
"dev:build:clean": "Cleans the build/ directory.",
"dev:lint": "Runs all linting checks.",
"dev:lint:fix": "Auto-fixes coding standards issues, if possible.",
"dev:lint:style": "Checks for coding standards issues.",
"dev:lint:syntax": "Checks for syntax errors.",
"dev:test": "Runs linting, static analysis, and unit tests.",
"dev:test:coverage:ci": "Runs unit tests and generates CI coverage reports.",
"dev:test:coverage:html": "Runs unit tests and generates HTML coverage report.",
"dev:test:unit": "Runs unit tests.",
"test": "Runs linting, static analysis, and unit tests."
}
}

View File

@@ -0,0 +1,22 @@
{
"typeCase": "kebab",
"types": [
"chore",
"ci",
"docs",
"feat",
"fix",
"refactor",
"security",
"style",
"test"
],
"scopeCase": "kebab",
"scopeRequired": false,
"scopes": [],
"descriptionCase": null,
"descriptionEndMark": "",
"bodyRequired": false,
"bodyWrapWidth": 72,
"requiredFooters": []
}

View File

@@ -17,8 +17,7 @@ namespace Ramsey\Collection;
use ArrayIterator;
use Traversable;
use function serialize;
use function unserialize;
use function count;
/**
* This class provides a basic implementation of `ArrayInterface`, to minimize
@@ -34,7 +33,7 @@ abstract class AbstractArray implements ArrayInterface
*
* @var array<array-key, T>
*/
protected $data = [];
protected array $data = [];
/**
* Constructs a new array object.
@@ -69,7 +68,7 @@ abstract class AbstractArray implements ArrayInterface
*
* @param array-key $offset The offset to check.
*/
public function offsetExists($offset): bool
public function offsetExists(mixed $offset): bool
{
return isset($this->data[$offset]);
}
@@ -81,15 +80,12 @@ abstract class AbstractArray implements ArrayInterface
*
* @param array-key $offset The offset for which a value should be returned.
*
* @return T|null the value stored at the offset, or null if the offset
* @return T the value stored at the offset, or null if the offset
* does not exist.
*
* @psalm-suppress InvalidAttribute
*/
#[\ReturnTypeWillChange] // phpcs:ignore
public function offsetGet($offset)
public function offsetGet(mixed $offset): mixed
{
return $this->data[$offset] ?? null;
return $this->data[$offset];
}
/**
@@ -97,12 +93,11 @@ abstract class AbstractArray implements ArrayInterface
*
* @link http://php.net/manual/en/arrayaccess.offsetset.php ArrayAccess::offsetSet()
*
* @param array-key|null $offset The offset to set. If `null`, the value may be
* set at a numerically-indexed offset.
* @param array-key | null $offset The offset to set. If `null`, the value
* may be set at a numerically-indexed offset.
* @param T $value The value to set at the given offset.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
$this->data[] = $value;
@@ -118,25 +113,11 @@ abstract class AbstractArray implements ArrayInterface
*
* @param array-key $offset The offset to remove from the array.
*/
public function offsetUnset($offset): void
public function offsetUnset(mixed $offset): void
{
unset($this->data[$offset]);
}
/**
* Returns a serialized string representation of this array object.
*
* @deprecated The Serializable interface will go away in PHP 9.
*
* @link http://php.net/manual/en/serializable.serialize.php Serializable::serialize()
*
* @return string a PHP serialized string.
*/
public function serialize(): string
{
return serialize($this->data);
}
/**
* Returns data suitable for PHP serialization.
*
@@ -150,25 +131,6 @@ abstract class AbstractArray implements ArrayInterface
return $this->data;
}
/**
* Converts a serialized string representation into an instance object.
*
* @deprecated The Serializable interface will go away in PHP 9.
*
* @link http://php.net/manual/en/serializable.unserialize.php Serializable::unserialize()
*
* @param string $serialized A PHP serialized string to unserialize.
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function unserialize($serialized): void
{
/** @var array<array-key, T> $data */
$data = unserialize($serialized, ['allowed_classes' => false]);
$this->data = $data;
}
/**
* Adds unserialized data to the object.
*
@@ -204,6 +166,6 @@ abstract class AbstractArray implements ArrayInterface
public function isEmpty(): bool
{
return count($this->data) === 0;
return $this->data === [];
}
}

View File

@@ -17,25 +17,27 @@ namespace Ramsey\Collection;
use Closure;
use Ramsey\Collection\Exception\CollectionMismatchException;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\InvalidSortOrderException;
use Ramsey\Collection\Exception\OutOfBoundsException;
use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Exception\UnsupportedOperationException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueExtractorTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
use function array_filter;
use function array_key_first;
use function array_key_last;
use function array_map;
use function array_merge;
use function array_reduce;
use function array_search;
use function array_udiff;
use function array_uintersect;
use function current;
use function end;
use function in_array;
use function is_int;
use function reset;
use function is_object;
use function spl_object_id;
use function sprintf;
use function unserialize;
use function usort;
/**
@@ -53,32 +55,29 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
use ValueExtractorTrait;
/**
* @inheritDoc
* @throws InvalidArgumentException if $element is of the wrong type.
*/
public function add($element): bool
public function add(mixed $element): bool
{
$this[] = $element;
return true;
}
/**
* @inheritDoc
*/
public function contains($element, bool $strict = true): bool
public function contains(mixed $element, bool $strict = true): bool
{
return in_array($element, $this->data, $strict);
}
/**
* @inheritDoc
* @throws InvalidArgumentException if $element is of the wrong type.
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($value)
. $this->toolValueToString($value),
);
}
@@ -89,13 +88,10 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
}
}
/**
* @inheritDoc
*/
public function remove($element): bool
public function remove(mixed $element): bool
{
if (($position = array_search($element, $this->data, true)) !== false) {
unset($this->data[$position]);
unset($this[$position]);
return true;
}
@@ -104,6 +100,11 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
}
/**
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call column() on this
* collection.
*
* @inheritDoc
*/
public function column(string $propertyOrMethod): array
@@ -111,55 +112,55 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
$temp = [];
foreach ($this->data as $item) {
/** @var mixed $value */
$value = $this->extractValue($item, $propertyOrMethod);
/** @psalm-suppress MixedAssignment */
$temp[] = $value;
$temp[] = $this->extractValue($item, $propertyOrMethod);
}
return $temp;
}
/**
* @inheritDoc
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function first()
public function first(): mixed
{
if ($this->isEmpty()) {
throw new OutOfBoundsException('Can\'t determine first item. Collection is empty');
$firstIndex = array_key_first($this->data);
if ($firstIndex === null) {
throw new NoSuchElementException('Can\'t determine first item. Collection is empty');
}
reset($this->data);
/** @var T $first */
$first = current($this->data);
return $first;
return $this->data[$firstIndex];
}
/**
* @inheritDoc
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function last()
public function last(): mixed
{
if ($this->isEmpty()) {
throw new OutOfBoundsException('Can\'t determine last item. Collection is empty');
$lastIndex = array_key_last($this->data);
if ($lastIndex === null) {
throw new NoSuchElementException('Can\'t determine last item. Collection is empty');
}
/** @var T $item */
$item = end($this->data);
reset($this->data);
return $item;
return $this->data[$lastIndex];
}
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface
/**
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call sort() on this
* collection.
*/
public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): CollectionInterface
{
if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) {
throw new InvalidSortOrderException('Invalid sort order given: ' . $order);
}
$collection = clone $this;
usort(
@@ -168,20 +169,25 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
* @param T $a
* @param T $b
*/
function ($a, $b) use ($propertyOrMethod, $order): int {
function (mixed $a, mixed $b) use ($propertyOrMethod, $order): int {
/** @var mixed $aValue */
$aValue = $this->extractValue($a, $propertyOrMethod);
/** @var mixed $bValue */
$bValue = $this->extractValue($b, $propertyOrMethod);
return ($aValue <=> $bValue) * ($order === self::SORT_DESC ? -1 : 1);
}
return ($aValue <=> $bValue) * ($order === Sort::Descending ? -1 : 1);
},
);
return $collection;
}
/**
* @param callable(T): bool $callback A callable to use for filtering elements.
*
* @return CollectionInterface<T>
*/
public function filter(callable $callback): CollectionInterface
{
$collection = clone $this;
@@ -191,23 +197,66 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
}
/**
* {@inheritdoc}
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call where() on this
* collection.
*/
public function where(string $propertyOrMethod, $value): CollectionInterface
public function where(?string $propertyOrMethod, mixed $value): CollectionInterface
{
return $this->filter(function ($item) use ($propertyOrMethod, $value) {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $this->filter(
/**
* @param T $item
*/
function (mixed $item) use ($propertyOrMethod, $value): bool {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $accessorValue === $value;
});
return $accessorValue === $value;
},
);
}
/**
* @param callable(T): TCallbackReturn $callback A callable to apply to each
* item of the collection.
*
* @return CollectionInterface<TCallbackReturn>
*
* @template TCallbackReturn
*/
public function map(callable $callback): CollectionInterface
{
/** @var Collection<TCallbackReturn> */
return new Collection('mixed', array_map($callback, $this->data));
}
/**
* @param callable(TCarry, T): TCarry $callback A callable to apply to each
* item of the collection to reduce it to a single value.
* @param TCarry $initial This is the initial value provided to the callback.
*
* @return TCarry
*
* @template TCarry
*/
public function reduce(callable $callback, mixed $initial): mixed
{
/** @var TCarry */
return array_reduce($this->data, $callback, $initial);
}
/**
* @param CollectionInterface<T> $other The collection to check for divergent
* items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function diff(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
@@ -224,6 +273,15 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $collection;
}
/**
* @param CollectionInterface<T> $other The collection to check for
* intersecting items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function intersect(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
@@ -237,6 +295,15 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $collection;
}
/**
* @param CollectionInterface<T> ...$collections The collections to merge.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if unable to merge any of the given
* collections or items within the given collections due to type
* mismatch errors.
*/
public function merge(CollectionInterface ...$collections): CollectionInterface
{
$mergedCollection = clone $this;
@@ -244,15 +311,19 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
foreach ($collections as $index => $collection) {
if (!$collection instanceof static) {
throw new CollectionMismatchException(
sprintf('Collection with index %d must be of type %s', $index, static::class)
sprintf('Collection with index %d must be of type %s', $index, static::class),
);
}
// When using generics (Collection.php, Set.php, etc),
// we also need to make sure that the internal types match each other
if ($collection->getType() !== $this->getType()) {
if ($this->getUniformType($collection) !== $this->getUniformType($this)) {
throw new CollectionMismatchException(
sprintf('Collection items in collection with index %d must be of type %s', $index, $this->getType())
sprintf(
'Collection items in collection with index %d must be of type %s',
$index,
$this->getType(),
),
);
}
@@ -268,19 +339,10 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $mergedCollection;
}
/**
* @inheritDoc
*/
public function unserialize($serialized): void
{
/** @var array<array-key, T> $data */
$data = unserialize($serialized, ['allowed_classes' => [$this->getType()]]);
$this->data = $data;
}
/**
* @param CollectionInterface<T> $other
*
* @throws CollectionMismatchException
*/
private function compareCollectionTypes(CollectionInterface $other): void
{
@@ -290,7 +352,7 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
// When using generics (Collection.php, Set.php, etc),
// we also need to make sure that the internal types match each other
if ($other->getType() !== $this->getType()) {
if ($this->getUniformType($other) !== $this->getUniformType($this)) {
throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
}
}
@@ -301,7 +363,7 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
* @param T $a
* @param T $b
*/
function ($a, $b): int {
function (mixed $a, mixed $b): int {
// If the two values are object, we convert them to unique scalars.
// If the collection contains mixed values (unlikely) where some are objects
// and some are not, we leave them as they are.
@@ -315,4 +377,17 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
return $a === $b ? 0 : ($a < $b ? 1 : -1);
};
}
/**
* @param CollectionInterface<mixed> $collection
*/
private function getUniformType(CollectionInterface $collection): string
{
return match ($collection->getType()) {
'integer' => 'int',
'boolean' => 'bool',
'double' => 'float',
default => $collection->getType(),
};
}
}

View File

@@ -24,10 +24,7 @@ namespace Ramsey\Collection;
*/
abstract class AbstractSet extends AbstractCollection
{
/**
* @inheritDoc
*/
public function add($element): bool
public function add(mixed $element): bool
{
if ($this->contains($element)) {
return false;
@@ -36,10 +33,7 @@ abstract class AbstractSet extends AbstractCollection
return parent::add($element);
}
/**
* @inheritDoc
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($this->contains($value)) {
return;

View File

@@ -17,7 +17,6 @@ namespace Ramsey\Collection;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use Serializable;
/**
* `ArrayInterface` provides traversable array functionality to data types.
@@ -29,8 +28,7 @@ use Serializable;
interface ArrayInterface extends
ArrayAccess,
Countable,
IteratorAggregate,
Serializable
IteratorAggregate
{
/**
* Removes all items from this array.

View File

@@ -75,27 +75,16 @@ namespace Ramsey\Collection;
*/
class Collection extends AbstractCollection
{
/**
* The type of elements stored in this collection.
*
* A collection's type is immutable once it is set. For this reason, this
* property is set private.
*
* @var string
*/
private $collectionType;
/**
* Constructs a collection object of the specified type, optionally with the
* specified data.
*
* @param string $collectionType The type (FQCN) associated with this
* @param string $collectionType The type or class name associated with this
* collection.
* @param array<array-key, T> $data The initial items to store in the collection.
*/
public function __construct(string $collectionType, array $data = [])
public function __construct(private readonly string $collectionType, array $data = [])
{
$this->collectionType = $collectionType;
parent::__construct($data);
}

View File

@@ -14,8 +14,14 @@ declare(strict_types=1);
namespace Ramsey\Collection;
use Ramsey\Collection\Exception\CollectionMismatchException;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Exception\UnsupportedOperationException;
/**
* A collection represents a group of objects, known as its elements.
* A collection represents a group of values, known as its elements.
*
* Some collections allow duplicate elements and others do not. Some are ordered
* and others unordered.
@@ -25,16 +31,6 @@ namespace Ramsey\Collection;
*/
interface CollectionInterface extends ArrayInterface
{
/**
* Ascending sort type.
*/
public const SORT_ASC = 'asc';
/**
* Descending sort type.
*/
public const SORT_DESC = 'desc';
/**
* Ensures that this collection contains the specified element (optional
* operation).
@@ -58,9 +54,11 @@ interface CollectionInterface extends ArrayInterface
* @param T $element The element to add to the collection.
*
* @return bool `true` if this collection changed as a result of the call.
*
* @throws InvalidArgumentException if the collection refuses to add the
* $element for any reason other than that it already contains the element.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function add($element): bool;
public function add(mixed $element): bool;
/**
* Returns `true` if this collection contains the specified element.
@@ -68,8 +66,7 @@ interface CollectionInterface extends ArrayInterface
* @param T $element The element to check whether the collection contains.
* @param bool $strict Whether to perform a strict type check on the value.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function contains($element, bool $strict = true): bool;
public function contains(mixed $element, bool $strict = true): bool;
/**
* Returns the type associated with this collection.
@@ -84,15 +81,20 @@ interface CollectionInterface extends ArrayInterface
*
* @return bool `true` if an element was removed as a result of this call.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function remove($element): bool;
public function remove(mixed $element): bool;
/**
* Returns the values from the given property or method.
* Returns the values from the given property, method, or array key.
*
* @param string $propertyOrMethod The property or method name to filter by.
* @param string $propertyOrMethod The name of the property, method, or
* array key to evaluate and return.
*
* @return list<mixed>
* @return array<int, mixed>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call column() on this
* collection.
*/
public function column(string $propertyOrMethod): array;
@@ -100,29 +102,41 @@ interface CollectionInterface extends ArrayInterface
* Returns the first item of the collection.
*
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function first();
public function first(): mixed;
/**
* Returns the last item of the collection.
*
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function last();
public function last(): mixed;
/**
* Sort the collection by a property or method with the given sort order.
* Sort the collection by a property, method, or array key with the given
* sort order.
*
* If $propertyOrMethod is `null`, this will sort by comparing each element.
*
* This will always leave the original collection untouched and will return
* a new one.
*
* @param string $propertyOrMethod The property or method to sort by.
* @param string $order The sort order for the resulting collection (one of
* this interface's `SORT_*` constants).
* @param string | null $propertyOrMethod The property, method, or array key
* to sort by.
* @param Sort $order The sort order for the resulting collection.
*
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call sort() on this
* collection.
*/
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): self;
public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): self;
/**
* Filter out items of the collection which don't match the criteria of
@@ -134,24 +148,31 @@ interface CollectionInterface extends ArrayInterface
* See the {@link http://php.net/manual/en/function.array-filter.php PHP array_filter() documentation}
* for examples of how the `$callback` parameter works.
*
* @param callable(T):bool $callback A callable to use for filtering elements.
* @param callable(T): bool $callback A callable to use for filtering elements.
*
* @return CollectionInterface<T>
*/
public function filter(callable $callback): self;
/**
* Create a new collection where items match the criteria of given callback.
* Create a new collection where the result of the given property, method,
* or array key of each item in the collection equals the given value.
*
* This will always leave the original collection untouched and will return
* a new one.
*
* @param string $propertyOrMethod The property or method to evaluate.
* @param string | null $propertyOrMethod The property, method, or array key
* to evaluate. If `null`, the element itself is compared to $value.
* @param mixed $value The value to match.
*
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call where() on this
* collection.
*/
public function where(string $propertyOrMethod, $value): self;
public function where(?string $propertyOrMethod, mixed $value): self;
/**
* Apply a given callback method on each item of the collection.
@@ -163,7 +184,7 @@ interface CollectionInterface extends ArrayInterface
* See the {@link http://php.net/manual/en/function.array-map.php PHP array_map() documentation}
* for examples of how the `$callback` parameter works.
*
* @param callable(T):TCallbackReturn $callback A callable to apply to each
* @param callable(T): TCallbackReturn $callback A callable to apply to each
* item of the collection.
*
* @return CollectionInterface<TCallbackReturn>
@@ -172,6 +193,23 @@ interface CollectionInterface extends ArrayInterface
*/
public function map(callable $callback): self;
/**
* Apply a given callback method on each item of the collection
* to reduce it to a single value.
*
* See the {@link http://php.net/manual/en/function.array-reduce.php PHP array_reduce() documentation}
* for examples of how the `$callback` and `$initial` parameters work.
*
* @param callable(TCarry, T): TCarry $callback A callable to apply to each
* item of the collection to reduce it to a single value.
* @param TCarry $initial This is the initial value provided to the callback.
*
* @return TCarry
*
* @template TCarry
*/
public function reduce(callable $callback, mixed $initial): mixed;
/**
* Create a new collection with divergent items between current and given
* collection.
@@ -180,6 +218,9 @@ interface CollectionInterface extends ArrayInterface
* items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function diff(CollectionInterface $other): self;
@@ -191,6 +232,9 @@ interface CollectionInterface extends ArrayInterface
* intersecting items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function intersect(CollectionInterface $other): self;
@@ -200,6 +244,10 @@ interface CollectionInterface extends ArrayInterface
* @param CollectionInterface<T> ...$collections The collections to merge.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if unable to merge any of the given
* collections or items within the given collections due to type
* mismatch errors.
*/
public function merge(CollectionInterface ...$collections): self;
}

View File

@@ -17,6 +17,10 @@ namespace Ramsey\Collection;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\NoSuchElementException;
use function array_key_last;
use function array_pop;
use function array_unshift;
/**
* This class provides a basic implementation of `DoubleEndedQueueInterface`, to
* minimize the effort required to implement this interface.
@@ -28,160 +32,135 @@ use Ramsey\Collection\Exception\NoSuchElementException;
class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface
{
/**
* Index of the last element in the queue.
* Constructs a double-ended queue (dequeue) object of the specified type,
* optionally with the specified data.
*
* @var int
* @param string $queueType The type or class name associated with this dequeue.
* @param array<array-key, T> $data The initial items to store in the dequeue.
*/
private $tail = -1;
/**
* @inheritDoc
*/
public function offsetSet($offset, $value): void
public function __construct(private readonly string $queueType, array $data = [])
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($value)
);
}
$this->tail++;
$this->data[$this->tail] = $value;
parent::__construct($this->queueType, $data);
}
/**
* @inheritDoc
* @throws InvalidArgumentException if $element is of the wrong type
*/
public function addFirst($element): bool
public function addFirst(mixed $element): bool
{
if ($this->checkType($this->getType(), $element) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($element)
. $this->toolValueToString($element),
);
}
$this->index--;
$this->data[$this->index] = $element;
array_unshift($this->data, $element);
return true;
}
/**
* @inheritDoc
* @throws InvalidArgumentException if $element is of the wrong type
*/
public function addLast($element): bool
public function addLast(mixed $element): bool
{
return $this->add($element);
}
/**
* @inheritDoc
*/
public function offerFirst($element): bool
public function offerFirst(mixed $element): bool
{
try {
return $this->addFirst($element);
} catch (InvalidArgumentException $e) {
} catch (InvalidArgumentException) {
return false;
}
}
/**
* @inheritDoc
*/
public function offerLast($element): bool
public function offerLast(mixed $element): bool
{
return $this->offer($element);
}
/**
* @inheritDoc
* @return T the first element in this queue.
*
* @throws NoSuchElementException if the queue is empty
*/
public function removeFirst()
public function removeFirst(): mixed
{
return $this->remove();
}
/**
* @inheritDoc
* @return T the last element in this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function removeLast()
public function removeLast(): mixed
{
$tail = $this->pollLast();
if ($tail === null) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
return $tail;
return $this->pollLast() ?? throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.',
);
}
/**
* @inheritDoc
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function pollFirst()
public function pollFirst(): mixed
{
return $this->poll();
}
/**
* @inheritDoc
* @return T | null the tail of this queue, or `null` if this queue is empty.
*/
public function pollLast()
public function pollLast(): mixed
{
if ($this->count() === 0) {
return null;
}
$tail = $this[$this->tail];
unset($this[$this->tail]);
$this->tail--;
return $tail;
return array_pop($this->data);
}
/**
* @inheritDoc
* @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function firstElement()
public function firstElement(): mixed
{
return $this->element();
}
/**
* @inheritDoc
* @return T the tail of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function lastElement()
public function lastElement(): mixed
{
if ($this->count() === 0) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
return $this->data[$this->tail];
return $this->peekLast() ?? throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.',
);
}
/**
* @inheritDoc
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function peekFirst()
public function peekFirst(): mixed
{
return $this->peek();
}
/**
* @inheritDoc
* @return T | null the tail of this queue, or `null` if this queue is empty.
*/
public function peekLast()
public function peekLast(): mixed
{
if ($this->count() === 0) {
$lastIndex = array_key_last($this->data);
if ($lastIndex === null) {
return null;
}
return $this->data[$this->tail];
return $this->data[$lastIndex];
}
}

View File

@@ -15,6 +15,7 @@ declare(strict_types=1);
namespace Ramsey\Collection;
use Ramsey\Collection\Exception\NoSuchElementException;
use RuntimeException;
/**
* A linear collection that supports element insertion and removal at both ends.
@@ -175,13 +176,12 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @return bool `true` if this queue changed as a result of the call.
*
* @throws \RuntimeException if a queue refuses to add a particular element
* @throws RuntimeException if a queue refuses to add a particular element
* for any reason other than that it already contains the element.
* Implementations should use a more-specific exception that extends
* `\RuntimeException`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function addFirst($element): bool;
public function addFirst(mixed $element): bool;
/**
* Inserts the specified element at the end of this queue if it is possible
@@ -196,13 +196,12 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @return bool `true` if this queue changed as a result of the call.
*
* @throws \RuntimeException if a queue refuses to add a particular element
* @throws RuntimeException if a queue refuses to add a particular element
* for any reason other than that it already contains the element.
* Implementations should use a more-specific exception that extends
* `\RuntimeException`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function addLast($element): bool;
public function addLast(mixed $element): bool;
/**
* Inserts the specified element at the front of this queue if it is
@@ -216,8 +215,7 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offerFirst($element): bool;
public function offerFirst(mixed $element): bool;
/**
* Inserts the specified element at the end of this queue if it is possible
@@ -231,8 +229,7 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offerLast($element): bool;
public function offerLast(mixed $element): bool;
/**
* Retrieves and removes the head of this queue.
@@ -244,7 +241,7 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @throws NoSuchElementException if this queue is empty.
*/
public function removeFirst();
public function removeFirst(): mixed;
/**
* Retrieves and removes the tail of this queue.
@@ -256,23 +253,23 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @throws NoSuchElementException if this queue is empty.
*/
public function removeLast();
public function removeLast(): mixed;
/**
* Retrieves and removes the head of this queue, or returns `null` if this
* queue is empty.
*
* @return T|null the head of this queue, or `null` if this queue is empty.
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function pollFirst();
public function pollFirst(): mixed;
/**
* Retrieves and removes the tail of this queue, or returns `null` if this
* queue is empty.
*
* @return T|null the tail of this queue, or `null` if this queue is empty.
* @return T | null the tail of this queue, or `null` if this queue is empty.
*/
public function pollLast();
public function pollLast(): mixed;
/**
* Retrieves, but does not remove, the head of this queue.
@@ -284,7 +281,7 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @throws NoSuchElementException if this queue is empty.
*/
public function firstElement();
public function firstElement(): mixed;
/**
* Retrieves, but does not remove, the tail of this queue.
@@ -296,21 +293,21 @@ interface DoubleEndedQueueInterface extends QueueInterface
*
* @throws NoSuchElementException if this queue is empty.
*/
public function lastElement();
public function lastElement(): mixed;
/**
* Retrieves, but does not remove, the head of this queue, or returns `null`
* if this queue is empty.
*
* @return T|null the head of this queue, or `null` if this queue is empty.
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function peekFirst();
public function peekFirst(): mixed;
/**
* Retrieves, but does not remove, the tail of this queue, or returns `null`
* if this queue is empty.
*
* @return T|null the tail of this queue, or `null` if this queue is empty.
* @return T | null the tail of this queue, or `null` if this queue is empty.
*/
public function peekLast();
public function peekLast(): mixed;
}

View File

@@ -14,9 +14,8 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
/**
* Thrown when attempting to use a sort order that is not recognized.
*/
class InvalidSortOrderException extends \RuntimeException
use Throwable;
interface CollectionException extends Throwable
{
}

View File

@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
use RuntimeException;
/**
* Thrown when attempting to operate on collections of differing types.
*/
class CollectionMismatchException extends \RuntimeException
class CollectionMismatchException extends RuntimeException implements CollectionException
{
}

View File

@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
use InvalidArgumentException as PhpInvalidArgumentException;
/**
* Thrown to indicate an argument is not of the expected type.
*/
class InvalidArgumentException extends \InvalidArgumentException
class InvalidArgumentException extends PhpInvalidArgumentException implements CollectionException
{
}

View File

@@ -14,9 +14,13 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
use RuntimeException;
/**
* Thrown when attempting to extract a value for a method or property that does not exist.
* Thrown when attempting to evaluate a property, method, or array key
* that doesn't exist on an element or cannot otherwise be evaluated in the
* current context.
*/
class ValueExtractionException extends \RuntimeException
class InvalidPropertyOrMethod extends RuntimeException implements CollectionException
{
}

View File

@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
use RuntimeException;
/**
* Thrown when attempting to access an element that does not exist.
*/
class NoSuchElementException extends \RuntimeException
class NoSuchElementException extends RuntimeException implements CollectionException
{
}

View File

@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
use OutOfBoundsException as PhpOutOfBoundsException;
/**
* Thrown when attempting to access an element out of the range of the collection.
*/
class OutOfBoundsException extends \OutOfBoundsException
class OutOfBoundsException extends PhpOutOfBoundsException implements CollectionException
{
}

View File

@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Exception;
use RuntimeException;
/**
* Thrown to indicate that the requested operation is not supported.
*/
class UnsupportedOperationException extends \RuntimeException
class UnsupportedOperationException extends RuntimeException implements CollectionException
{
}

View File

@@ -16,48 +16,65 @@ namespace Ramsey\Collection\Map;
use Ramsey\Collection\AbstractArray;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Traversable;
use function array_key_exists;
use function array_keys;
use function in_array;
use function var_export;
/**
* This class provides a basic implementation of `MapInterface`, to minimize the
* effort required to implement this interface.
*
* @template K of array-key
* @template T
* @extends AbstractArray<T>
* @implements MapInterface<T>
* @implements MapInterface<K, T>
*/
abstract class AbstractMap extends AbstractArray implements MapInterface
{
/**
* @inheritDoc
* @param array<K, T> $data The initial items to add to this map.
*/
public function offsetSet($offset, $value): void
public function __construct(array $data = [])
{
parent::__construct($data);
}
/**
* @return Traversable<K, T>
*/
public function getIterator(): Traversable
{
return parent::getIterator();
}
/**
* @param K $offset The offset to set
* @param T $value The value to set at the given offset.
*
* @inheritDoc
* @psalm-suppress MoreSpecificImplementedParamType,DocblockTypeContradiction
*/
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
throw new InvalidArgumentException(
'Map elements are key/value pairs; a key must be provided for '
. 'value ' . var_export($value, true)
. 'value ' . var_export($value, true),
);
}
$this->data[$offset] = $value;
}
/**
* @inheritDoc
*/
public function containsKey($key): bool
public function containsKey(int | string $key): bool
{
return array_key_exists($key, $this->data);
}
/**
* @inheritDoc
*/
public function containsValue($value): bool
public function containsValue(mixed $value): bool
{
return in_array($value, $this->data, true);
}
@@ -71,21 +88,24 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
}
/**
* @inheritDoc
* @param K $key The key to return from the map.
* @param T | null $defaultValue The default value to use if `$key` is not found.
*
* @return T | null the value or `null` if the key could not be found.
*/
public function get($key, $defaultValue = null)
public function get(int | string $key, mixed $defaultValue = null): mixed
{
if (!$this->containsKey($key)) {
return $defaultValue;
}
return $this[$key];
return $this[$key] ?? $defaultValue;
}
/**
* @inheritDoc
* @param K $key The key to put or replace in the map.
* @param T $value The value to store at `$key`.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function put($key, $value)
public function put(int | string $key, mixed $value): mixed
{
$previousValue = $this->get($key);
$this[$key] = $value;
@@ -94,9 +114,13 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
}
/**
* @inheritDoc
* @param K $key The key to put in the map.
* @param T $value The value to store at `$key`.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function putIfAbsent($key, $value)
public function putIfAbsent(int | string $key, mixed $value): mixed
{
$currentValue = $this->get($key);
@@ -108,9 +132,12 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
}
/**
* @inheritDoc
* @param K $key The key to remove from the map.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function remove($key)
public function remove(int | string $key): mixed
{
$previousValue = $this->get($key);
unset($this[$key]);
@@ -118,10 +145,7 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
return $previousValue;
}
/**
* @inheritDoc
*/
public function removeIf($key, $value): bool
public function removeIf(int | string $key, mixed $value): bool
{
if ($this->get($key) === $value) {
unset($this[$key]);
@@ -133,9 +157,13 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
}
/**
* @inheritDoc
* @param K $key The key to replace.
* @param T $value The value to set at `$key`.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function replace($key, $value)
public function replace(int | string $key, mixed $value): mixed
{
$currentValue = $this->get($key);
@@ -146,10 +174,7 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
return $currentValue;
}
/**
* @inheritDoc
*/
public function replaceIf($key, $oldValue, $newValue): bool
public function replaceIf(int | string $key, mixed $oldValue, mixed $newValue): bool
{
if ($this->get($key) === $oldValue) {
$this[$key] = $newValue;
@@ -159,4 +184,20 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
return false;
}
/**
* @return array<K, T>
*/
public function __serialize(): array
{
return parent::__serialize();
}
/**
* @return array<K, T>
*/
public function toArray(): array
{
return parent::toArray();
}
}

View File

@@ -22,10 +22,10 @@ use Ramsey\Collection\Tool\ValueToStringTrait;
* This class provides a basic implementation of `TypedMapInterface`, to
* minimize the effort required to implement this interface.
*
* @template K
* @template K of array-key
* @template T
* @extends AbstractMap<T>
* @implements TypedMapInterface<T>
* @extends AbstractMap<K, T>
* @implements TypedMapInterface<K, T>
*/
abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
{
@@ -33,37 +33,28 @@ abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
use ValueToStringTrait;
/**
* @param K|null $offset
* @param K $offset
* @param T $value
*
* @inheritDoc
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
throw new InvalidArgumentException(
'Map elements are key/value pairs; a key must be provided for '
. 'value ' . var_export($value, true)
);
}
if ($this->checkType($this->getKeyType(), $offset) === false) {
throw new InvalidArgumentException(
'Key must be of type ' . $this->getKeyType() . '; key is '
. $this->toolValueToString($offset)
. $this->toolValueToString($offset),
);
}
if ($this->checkType($this->getValueType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getValueType() . '; value is '
. $this->toolValueToString($value)
. $this->toolValueToString($value),
);
}
/** @psalm-suppress MixedArgumentTypeCoercion */
parent::offsetSet($offset, $value);
}
}

View File

@@ -17,8 +17,7 @@ namespace Ramsey\Collection\Map;
/**
* `AssociativeArrayMap` represents a standard associative array object.
*
* @template T
* @extends AbstractMap<T>
* @extends AbstractMap<string, mixed>
*/
class AssociativeArrayMap extends AbstractMap
{

View File

@@ -21,6 +21,7 @@ use Ramsey\Collection\ArrayInterface;
*
* A map cannot contain duplicate keys; each key can map to at most one value.
*
* @template K of array-key
* @template T
* @extends ArrayInterface<T>
*/
@@ -29,9 +30,9 @@ interface MapInterface extends ArrayInterface
/**
* Returns `true` if this map contains a mapping for the specified key.
*
* @param array-key $key The key to check in the map.
* @param K $key The key to check in the map.
*/
public function containsKey($key): bool;
public function containsKey(int | string $key): bool;
/**
* Returns `true` if this map maps one or more keys to the specified value.
@@ -40,13 +41,12 @@ interface MapInterface extends ArrayInterface
*
* @param T $value The value to check in the map.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function containsValue($value): bool;
public function containsValue(mixed $value): bool;
/**
* Return an array of the keys contained in this map.
*
* @return list<array-key>
* @return list<K>
*/
public function keys(): array;
@@ -55,13 +55,12 @@ interface MapInterface extends ArrayInterface
* map contains no mapping for the key, or (optionally) `$defaultValue` if
* this map contains no mapping for the key.
*
* @param array-key $key The key to return from the map.
* @param T|null $defaultValue The default value to use if `$key` is not found.
* @param K $key The key to return from the map.
* @param T | null $defaultValue The default value to use if `$key` is not found.
*
* @return T|null the value or `null` if the key could not be found.
* @return T | null the value or `null` if the key could not be found.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function get($key, $defaultValue = null);
public function get(int | string $key, mixed $defaultValue = null): mixed;
/**
* Associates the specified value with the specified key in this map.
@@ -69,14 +68,13 @@ interface MapInterface extends ArrayInterface
* If the map previously contained a mapping for the key, the old value is
* replaced by the specified value.
*
* @param array-key $key The key to put or replace in the map.
* @param K $key The key to put or replace in the map.
* @param T $value The value to store at `$key`.
*
* @return T|null the previous value associated with key, or `null` if
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function put($key, $value);
public function put(int | string $key, mixed $value): mixed;
/**
* Associates the specified value with the specified key in this map only if
@@ -85,25 +83,23 @@ interface MapInterface extends ArrayInterface
* If there is already a value associated with `$key`, this returns that
* value without replacing it.
*
* @param array-key $key The key to put in the map.
* @param K $key The key to put in the map.
* @param T $value The value to store at `$key`.
*
* @return T|null the previous value associated with key, or `null` if
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function putIfAbsent($key, $value);
public function putIfAbsent(int | string $key, mixed $value): mixed;
/**
* Removes the mapping for a key from this map if it is present.
*
* @param array-key $key The key to remove from the map.
* @param K $key The key to remove from the map.
*
* @return T|null the previous value associated with key, or `null` if
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function remove($key);
public function remove(int | string $key): mixed;
/**
* Removes the entry for the specified key only if it is currently mapped to
@@ -111,26 +107,24 @@ interface MapInterface extends ArrayInterface
*
* This performs a strict type check on the value.
*
* @param array-key $key The key to remove from the map.
* @param K $key The key to remove from the map.
* @param T $value The value to match.
*
* @return bool true if the value was removed.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function removeIf($key, $value): bool;
public function removeIf(int | string $key, mixed $value): bool;
/**
* Replaces the entry for the specified key only if it is currently mapped
* to some value.
*
* @param array-key $key The key to replace.
* @param K $key The key to replace.
* @param T $value The value to set at `$key`.
*
* @return T|null the previous value associated with key, or `null` if
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function replace($key, $value);
public function replace(int | string $key, mixed $value): mixed;
/**
* Replaces the entry for the specified key only if currently mapped to the
@@ -138,12 +132,11 @@ interface MapInterface extends ArrayInterface
*
* This performs a strict type check on the value.
*
* @param array-key $key The key to remove from the map.
* @param K $key The key to remove from the map.
* @param T $oldValue The value to match.
* @param T $newValue The value to use as a replacement.
*
* @return bool true if the value was replaced.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function replaceIf($key, $oldValue, $newValue): bool;
public function replaceIf(int | string $key, mixed $oldValue, mixed $newValue): bool;
}

View File

@@ -26,7 +26,7 @@ use function is_int;
* `NamedParameterMap` represents a mapping of values to a set of named keys
* that may optionally be typed
*
* @extends AbstractMap<mixed>
* @extends AbstractMap<string, mixed>
*/
class NamedParameterMap extends AbstractMap
{
@@ -38,13 +38,13 @@ class NamedParameterMap extends AbstractMap
*
* @var array<string, string>
*/
protected $namedParameters;
private readonly array $namedParameters;
/**
* Constructs a new `NamedParameterMap`.
*
* @param array<array-key, string> $namedParameters The named parameters defined for this map.
* @param array<array-key, mixed> $data An initial set of data to set on this map.
* @param array<string, mixed> $data An initial set of data to set on this map.
*/
public function __construct(array $namedParameters, array $data = [])
{
@@ -62,22 +62,12 @@ class NamedParameterMap extends AbstractMap
return $this->namedParameters;
}
/**
* @inheritDoc
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
throw new InvalidArgumentException(
'Map elements are key/value pairs; a key must be provided for '
. 'value ' . var_export($value, true)
);
}
if (!array_key_exists($offset, $this->namedParameters)) {
throw new InvalidArgumentException(
'Attempting to set value for unconfigured parameter \''
. $offset . '\''
. $this->toolValueToString($offset) . '\'',
);
}
@@ -85,7 +75,7 @@ class NamedParameterMap extends AbstractMap
throw new InvalidArgumentException(
'Value for \'' . $offset . '\' must be of type '
. $this->namedParameters[$offset] . '; value is '
. $this->toolValueToString($value)
. $this->toolValueToString($value),
);
}

View File

@@ -14,13 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Map;
use Ramsey\Collection\Tool\TypeTrait;
/**
* A `TypedMap` represents a map of elements where key and value are typed.
*
* Each element is identified by a key with defined type and a value of defined
* type. The keys of the map must be unique. The values on the map can be=
* type. The keys of the map must be unique. The values on the map can be
* repeated but each with its own different key.
*
* The most common case is to use a string type key, but it's not limited to
@@ -80,34 +78,12 @@ use Ramsey\Collection\Tool\TypeTrait;
* }
* ```
*
* @template K
* @template K of array-key
* @template T
* @extends AbstractTypedMap<K, T>
*/
class TypedMap extends AbstractTypedMap
{
use TypeTrait;
/**
* The data type of keys stored in this collection.
*
* A map key's type is immutable once it is set. For this reason, this
* property is set private.
*
* @var string data type of the map key.
*/
private $keyType;
/**
* The data type of values stored in this collection.
*
* A map value's type is immutable once it is set. For this reason, this
* property is set private.
*
* @var string data type of the map value.
*/
private $valueType;
/**
* Constructs a map object of the specified key and value types,
* optionally with the specified data.
@@ -116,12 +92,11 @@ class TypedMap extends AbstractTypedMap
* @param string $valueType The data type of the map's values.
* @param array<K, T> $data The initial data to set for this map.
*/
public function __construct(string $keyType, string $valueType, array $data = [])
{
$this->keyType = $keyType;
$this->valueType = $valueType;
/** @psalm-suppress MixedArgumentTypeCoercion */
public function __construct(
private readonly string $keyType,
private readonly string $valueType,
array $data = [],
) {
parent::__construct($data);
}

View File

@@ -18,8 +18,9 @@ namespace Ramsey\Collection\Map;
* A `TypedMapInterface` represents a map of elements where key and value are
* typed.
*
* @template K of array-key
* @template T
* @extends MapInterface<T>
* @extends MapInterface<K, T>
*/
interface TypedMapInterface extends MapInterface
{

View File

@@ -19,6 +19,8 @@ use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
use function array_key_first;
/**
* This class provides a basic implementation of `QueueInterface`, to minimize
* the effort required to implement this interface.
@@ -32,33 +34,15 @@ class Queue extends AbstractArray implements QueueInterface
use TypeTrait;
use ValueToStringTrait;
/**
* The type of elements stored in this queue.
*
* A queue's type is immutable once it is set. For this reason, this
* property is set private.
*
* @var string
*/
private $queueType;
/**
* The index of the head of the queue.
*
* @var int
*/
protected $index = 0;
/**
* Constructs a queue object of the specified type, optionally with the
* specified data.
*
* @param string $queueType The type (FQCN) associated with this queue.
* @param array<array-key, T> $data The initial items to store in the collection.
* @param string $queueType The type or class name associated with this queue.
* @param array<array-key, T> $data The initial items to store in the queue.
*/
public function __construct(string $queueType, array $data = [])
public function __construct(private readonly string $queueType, array $data = [])
{
$this->queueType = $queueType;
parent::__construct($data);
}
@@ -68,13 +52,15 @@ class Queue extends AbstractArray implements QueueInterface
* Since arbitrary offsets may not be manipulated in a queue, this method
* serves only to fulfill the `ArrayAccess` interface requirements. It is
* invoked by other operations when adding values to the queue.
*
* @throws InvalidArgumentException if $value is of the wrong type.
*/
public function offsetSet($offset, $value): void
public function offsetSet(mixed $offset, mixed $value): void
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($value)
. $this->toolValueToString($value),
);
}
@@ -82,9 +68,9 @@ class Queue extends AbstractArray implements QueueInterface
}
/**
* @inheritDoc
* @throws InvalidArgumentException if $value is of the wrong type.
*/
public function add($element): bool
public function add(mixed $element): bool
{
$this[] = $element;
@@ -92,74 +78,67 @@ class Queue extends AbstractArray implements QueueInterface
}
/**
* @inheritDoc
* @return T
*
* @throws NoSuchElementException if this queue is empty.
*/
public function element()
public function element(): mixed
{
$element = $this->peek();
if ($element === null) {
throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.'
);
}
return $element;
return $this->peek() ?? throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.',
);
}
/**
* @inheritDoc
*/
public function offer($element): bool
public function offer(mixed $element): bool
{
try {
return $this->add($element);
} catch (InvalidArgumentException $e) {
} catch (InvalidArgumentException) {
return false;
}
}
/**
* @inheritDoc
* @return T | null
*/
public function peek()
public function peek(): mixed
{
if ($this->count() === 0) {
$index = array_key_first($this->data);
if ($index === null) {
return null;
}
return $this[$this->index];
return $this[$index];
}
/**
* @inheritDoc
* @return T | null
*/
public function poll()
public function poll(): mixed
{
if ($this->count() === 0) {
$index = array_key_first($this->data);
if ($index === null) {
return null;
}
$head = $this[$this->index];
unset($this[$this->index]);
$this->index++;
$head = $this[$index];
unset($this[$index]);
return $head;
}
/**
* @inheritDoc
* @return T
*
* @throws NoSuchElementException if this queue is empty.
*/
public function remove()
public function remove(): mixed
{
$head = $this->poll();
if ($head === null) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
return $head;
return $this->poll() ?? throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.',
);
}
public function getType(): string

View File

@@ -15,6 +15,7 @@ declare(strict_types=1);
namespace Ramsey\Collection;
use Ramsey\Collection\Exception\NoSuchElementException;
use RuntimeException;
/**
* A queue is a collection in which the entities in the collection are kept in
@@ -123,13 +124,12 @@ interface QueueInterface extends ArrayInterface
*
* @return bool `true` if this queue changed as a result of the call.
*
* @throws \RuntimeException if a queue refuses to add a particular element
* @throws RuntimeException if a queue refuses to add a particular element
* for any reason other than that it already contains the element.
* Implementations should use a more-specific exception that extends
* `\RuntimeException`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function add($element): bool;
public function add(mixed $element): bool;
/**
* Retrieves, but does not remove, the head of this queue.
@@ -143,7 +143,7 @@ interface QueueInterface extends ArrayInterface
*
* @throws NoSuchElementException if this queue is empty.
*/
public function element();
public function element(): mixed;
/**
* Inserts the specified element into this queue if it is possible to do so
@@ -159,8 +159,7 @@ interface QueueInterface extends ArrayInterface
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offer($element): bool;
public function offer(mixed $element): bool;
/**
* Retrieves, but does not remove, the head of this queue, or returns `null`
@@ -168,9 +167,9 @@ interface QueueInterface extends ArrayInterface
*
* @see self::element()
*
* @return T|null the head of this queue, or `null` if this queue is empty.
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function peek();
public function peek(): mixed;
/**
* Retrieves and removes the head of this queue, or returns `null`
@@ -178,9 +177,9 @@ interface QueueInterface extends ArrayInterface
*
* @see self::remove()
*
* @return T|null the head of this queue, or `null` if this queue is empty.
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function poll();
public function poll(): mixed;
/**
* Retrieves and removes the head of this queue.
@@ -194,7 +193,7 @@ interface QueueInterface extends ArrayInterface
*
* @throws NoSuchElementException if this queue is empty.
*/
public function remove();
public function remove(): mixed;
/**
* Returns the type associated with this queue.

View File

@@ -28,7 +28,7 @@ namespace Ramsey\Collection;
* $foo = new \My\Foo();
* $set = new Set(\My\Foo::class);
*
* $set->add($foo); // returns TRUE, the element don't exists
* $set->add($foo); // returns TRUE, the element doesn't exist
* $set->add($foo); // returns FALSE, the element already exists
*
* $bar = new \My\Foo();
@@ -40,25 +40,15 @@ namespace Ramsey\Collection;
*/
class Set extends AbstractSet
{
/**
* The type of elements stored in this set
*
* A set's type is immutable. For this reason, this property is private.
*
* @var string
*/
private $setType;
/**
* Constructs a set object of the specified type, optionally with the
* specified data.
*
* @param string $setType The type (FQCN) associated with this set.
* @param string $setType The type or class name associated with this set.
* @param array<array-key, T> $data The initial items to store in the set.
*/
public function __construct(string $setType, array $data = [])
public function __construct(private readonly string $setType, array $data = [])
{
$this->setType = $setType;
parent::__construct($data);
}

31
vendor/ramsey/collection/src/Sort.php vendored Normal file
View File

@@ -0,0 +1,31 @@
<?php
/**
* This file is part of the ramsey/collection library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Collection;
/**
* Collection sorting
*/
enum Sort: string
{
/**
* Sort items in a collection in ascending order.
*/
case Ascending = 'asc';
/**
* Sort items in a collection in descending order.
*/
case Descending = 'desc';
}

View File

@@ -36,38 +36,22 @@ trait TypeTrait
* @param string $type The type to check the value against.
* @param mixed $value The value to check.
*/
protected function checkType(string $type, $value): bool
protected function checkType(string $type, mixed $value): bool
{
switch ($type) {
case 'array':
return is_array($value);
case 'bool':
case 'boolean':
return is_bool($value);
case 'callable':
return is_callable($value);
case 'float':
case 'double':
return is_float($value);
case 'int':
case 'integer':
return is_int($value);
case 'null':
return $value === null;
case 'numeric':
return is_numeric($value);
case 'object':
return is_object($value);
case 'resource':
return is_resource($value);
case 'scalar':
return is_scalar($value);
case 'string':
return is_string($value);
case 'mixed':
return true;
default:
return $value instanceof $type;
}
return match ($type) {
'array' => is_array($value),
'bool', 'boolean' => is_bool($value),
'callable' => is_callable($value),
'float', 'double' => is_float($value),
'int', 'integer' => is_int($value),
'null' => $value === null,
'numeric' => is_numeric($value),
'object' => is_object($value),
'resource' => is_resource($value),
'scalar' => is_scalar($value),
'string' => is_string($value),
'mixed' => true,
default => $value instanceof $type,
};
}
}

View File

@@ -14,9 +14,11 @@ declare(strict_types=1);
namespace Ramsey\Collection\Tool;
use Ramsey\Collection\Exception\ValueExtractionException;
use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\UnsupportedOperationException;
use function get_class;
use function is_array;
use function is_object;
use function method_exists;
use function property_exists;
use function sprintf;
@@ -27,32 +29,53 @@ use function sprintf;
trait ValueExtractorTrait
{
/**
* Extracts the value of the given property or method from the object.
* Extracts the value of the given property, method, or array key from the
* element.
*
* @param mixed $object The object to extract the value from.
* @param string $propertyOrMethod The property or method for which the
* If `$propertyOrMethod` is `null`, we return the element as-is.
*
* @param mixed $element The element to extract the value from.
* @param string | null $propertyOrMethod The property or method for which the
* value should be extracted.
*
* @return mixed the value extracted from the specified property or method.
* @return mixed the value extracted from the specified property, method,
* or array key, or the element itself.
*
* @throws ValueExtractionException if the method or property is not defined.
* @throws InvalidPropertyOrMethod
* @throws UnsupportedOperationException
*/
protected function extractValue($object, string $propertyOrMethod)
protected function extractValue(mixed $element, ?string $propertyOrMethod): mixed
{
if (!is_object($object)) {
throw new ValueExtractionException('Unable to extract a value from a non-object');
if ($propertyOrMethod === null) {
return $element;
}
if (property_exists($object, $propertyOrMethod)) {
return $object->$propertyOrMethod;
if (!is_object($element) && !is_array($element)) {
throw new UnsupportedOperationException(sprintf(
'The collection type "%s" does not support the $propertyOrMethod parameter',
$this->getType(),
));
}
if (method_exists($object, $propertyOrMethod)) {
return $object->{$propertyOrMethod}();
if (is_array($element)) {
return $element[$propertyOrMethod] ?? throw new InvalidPropertyOrMethod(sprintf(
'Key or index "%s" not found in collection elements',
$propertyOrMethod,
));
}
throw new ValueExtractionException(
sprintf('Method or property "%s" not defined in %s', $propertyOrMethod, get_class($object))
);
if (property_exists($element, $propertyOrMethod)) {
return $element->$propertyOrMethod;
}
if (method_exists($element, $propertyOrMethod)) {
return $element->{$propertyOrMethod}();
}
throw new InvalidPropertyOrMethod(sprintf(
'Method or property "%s" not defined in %s',
$propertyOrMethod,
$element::class,
));
}
}

View File

@@ -16,11 +16,12 @@ namespace Ramsey\Collection\Tool;
use DateTimeInterface;
use function get_class;
use function assert;
use function get_resource_type;
use function is_array;
use function is_bool;
use function is_callable;
use function is_object;
use function is_resource;
use function is_scalar;
@@ -44,7 +45,7 @@ trait ValueToStringTrait
*
* @param mixed $value the value to return as a string.
*/
protected function toolValueToString($value): string
protected function toolValueToString(mixed $value): string
{
// null
if ($value === null) {
@@ -71,12 +72,8 @@ trait ValueToStringTrait
return '(' . get_resource_type($value) . ' resource #' . (int) $value . ')';
}
// If we don't know what it is, use var_export().
if (!is_object($value)) {
return '(' . var_export($value, true) . ')';
}
// From here, $value should be an object.
assert(is_object($value));
// __toString() is implemented
if (is_callable([$value, '__toString'])) {
@@ -89,6 +86,6 @@ trait ValueToStringTrait
}
// unknown type
return '(' . get_class($value) . ' Object)';
return '(' . $value::class . ' Object)';
}
}