This commit is contained in:
2025-05-12 14:25:25 +02:00
parent ab2db755ef
commit 9e378ca2b7
2719 changed files with 46505 additions and 60181 deletions

View File

@@ -11,7 +11,6 @@
<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/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>
## About
@@ -49,16 +48,6 @@ contribution of external security researchers. If you believe you've found a
security issue in software that is maintained in this repository, please read
[SECURITY.md][] for instructions on submitting a vulnerability report.
## ramsey/collection for Enterprise
Available as part of the Tidelift Subscription.
The maintainers of ramsey/collection and thousands of other packages are working
with Tidelift to deliver commercial support and maintenance for the open source
packages you use to build your applications. Save time, reduce risk, and improve
code health, while paying the maintainers of the exact packages you use.
[Learn more.](https://tidelift.com/subscription/pkg/packagist-ramsey-collection?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Copyright and License
The ramsey/collection library is copyright © [Ben Ramsey](https://benramsey.com)

View File

@@ -23,27 +23,23 @@
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"ergebnis/composer-normalize": "^2.45",
"fakerphp/faker": "^1.24",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"jangregor/phpstan-prophecy": "^2.1",
"mockery/mockery": "^1.6",
"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.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"
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpspec/prophecy-phpunit": "^2.3",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-mockery": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^10.5",
"ramsey/coding-standard": "^2.3",
"ramsey/conventional-commits": "^1.6",
"roave/security-advisories": "dev-latest"
},
"minimum-stability": "RC",
"prefer-stable": true,
"autoload": {
"psr-4": {
@@ -52,19 +48,15 @@
},
"autoload-dev": {
"psr-4": {
"Ramsey\\Collection\\Test\\": "tests/",
"Ramsey\\Test\\Generics\\": "tests/generics/"
},
"files": [
"vendor/hamcrest/hamcrest-php/hamcrest/Hamcrest.php"
]
"Ramsey\\Collection\\Test\\": "tests/"
}
},
"config": {
"allow-plugins": {
"captainhook/plugin-composer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true,
"phpstan/extension-installer": true,
"captainhook/plugin-composer": true
"phpstan/extension-installer": true
},
"sort-packages": true
},
@@ -78,11 +70,9 @@
},
"scripts": {
"dev:analyze": [
"@dev:analyze:phpstan",
"@dev:analyze:psalm"
"@dev:analyze:phpstan"
],
"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",
@@ -104,7 +94,6 @@
"scripts-descriptions": {
"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": "Cleans the build/ directory.",
"dev:lint": "Runs all linting checks.",
"dev:lint:fix": "Auto-fixes coding standards issues, if possible.",

View File

@@ -1,22 +0,0 @@
{
"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

@@ -42,7 +42,7 @@ abstract class AbstractArray implements ArrayInterface
*/
public function __construct(array $data = [])
{
// Invoke offsetSet() for each value added; in this way, sub-classes
// Invoke offsetSet() for each value added; in this way, subclasses
// may provide additional logic about values added to the array object.
foreach ($data as $key => $value) {
$this[$key] = $value;

View File

@@ -112,7 +112,6 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
$temp = [];
foreach ($this->data as $item) {
/** @psalm-suppress MixedAssignment */
$temp[] = $this->extractValue($item, $propertyOrMethod);
}
@@ -165,15 +164,8 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
usort(
$collection->data,
/**
* @param T $a
* @param T $b
*/
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 === Sort::Descending ? -1 : 1);
@@ -207,15 +199,7 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
public function where(?string $propertyOrMethod, mixed $value): CollectionInterface
{
return $this->filter(
/**
* @param T $item
*/
function (mixed $item) use ($propertyOrMethod, $value): bool {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $accessorValue === $value;
},
fn (mixed $item): bool => $this->extractValue($item, $propertyOrMethod) === $value,
);
}
@@ -229,7 +213,6 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
*/
public function map(callable $callback): CollectionInterface
{
/** @var Collection<TCallbackReturn> */
return new Collection('mixed', array_map($callback, $this->data));
}
@@ -244,7 +227,6 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
*/
public function reduce(callable $callback, mixed $initial): mixed
{
/** @var TCarry */
return array_reduce($this->data, $callback, $initial);
}
@@ -264,11 +246,8 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
$diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator());
$diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator());
/** @var array<array-key, T> $diff */
$diff = array_merge($diffAtoB, $diffBtoA);
$collection = clone $this;
$collection->data = $diff;
$collection->data = array_merge($diffAtoB, $diffBtoA);
return $collection;
}
@@ -286,11 +265,8 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
{
$this->compareCollectionTypes($other);
/** @var array<array-key, T> $intersect */
$intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator());
$collection = clone $this;
$collection->data = $intersect;
$collection->data = array_uintersect($this->data, $other->toArray(), $this->getComparator());
return $collection;
}
@@ -359,23 +335,19 @@ abstract class AbstractCollection extends AbstractArray implements CollectionInt
private function getComparator(): Closure
{
return /**
* @param T $a
* @param T $b
*/
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.
// The comparator should still work and the result of $a < $b should
// be consistent but unpredictable since not documented.
if (is_object($a) && is_object($b)) {
$a = spl_object_id($a);
$b = spl_object_id($b);
}
return 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.
// The comparator should still work and the result of $a < $b should
// be consistent but unpredictable since not documented.
if (is_object($a) && is_object($b)) {
$a = spl_object_id($a);
$b = spl_object_id($b);
}
return $a === $b ? 0 : ($a < $b ? 1 : -1);
};
return $a === $b ? 0 : ($a < $b ? 1 : -1);
};
}
/**

View File

@@ -30,7 +30,14 @@ abstract class AbstractSet extends AbstractCollection
return false;
}
return parent::add($element);
// Call offsetSet() on the parent instead of add(), since calling
// parent::add() will invoke $this->offsetSet(), which will call
// $this->contains() a second time. This can cause performance issues
// with extremely large collections. For more information, see
// https://github.com/ramsey/collection/issues/68.
parent::offsetSet(null, $element);
return true;
}
public function offsetSet(mixed $offset, mixed $value): void

View File

@@ -24,7 +24,7 @@ namespace Ramsey\Collection;
*
* Example usage:
*
* ``` php
* ```
* $collection = new \Ramsey\Collection\Collection('My\\Foo');
* $collection->add(new \My\Foo());
* $collection->add(new \My\Foo());
@@ -37,7 +37,7 @@ namespace Ramsey\Collection;
* It is preferable to subclass `AbstractCollection` to create your own typed
* collections. For example:
*
* ``` php
* ```
* namespace My\Foo;
*
* class FooCollection extends \Ramsey\Collection\AbstractCollection
@@ -51,7 +51,7 @@ namespace Ramsey\Collection;
*
* And then use it similarly to the earlier example:
*
* ``` php
* ```
* $fooCollection = new \My\Foo\FooCollection();
* $fooCollection->add(new \My\Foo());
* $fooCollection->add(new \My\Foo());
@@ -64,7 +64,7 @@ namespace Ramsey\Collection;
* The benefit with this approach is that you may do type-checking on the
* collection object:
*
* ``` php
* ```
* if ($collection instanceof \My\Foo\FooCollection) {
* // the collection is a collection of My\Foo objects
* }

View File

@@ -89,7 +89,7 @@ interface CollectionInterface extends ArrayInterface
* @param string $propertyOrMethod The name of the property, method, or
* array key to evaluate and return.
*
* @return array<int, mixed>
* @return list<mixed>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.

View File

@@ -55,7 +55,6 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
* @param T $value The value to set at the given offset.
*
* @inheritDoc
* @psalm-suppress MoreSpecificImplementedParamType,DocblockTypeContradiction
*/
public function offsetSet(mixed $offset, mixed $value): void
{
@@ -84,6 +83,7 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
*/
public function keys(): array
{
/** @var list<K> */
return array_keys($this->data);
}
@@ -190,6 +190,7 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
*/
public function __serialize(): array
{
/** @var array<K, T> */
return parent::__serialize();
}
@@ -198,6 +199,7 @@ abstract class AbstractMap extends AbstractArray implements MapInterface
*/
public function toArray(): array
{
/** @var array<K, T> */
return parent::toArray();
}
}

View File

@@ -37,7 +37,6 @@ abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
* @param T $value
*
* @inheritDoc
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function offsetSet(mixed $offset, mixed $value): void
{

View File

@@ -29,7 +29,7 @@ namespace Ramsey\Collection\Map;
*
* Example usage:
*
* ```php
* ```
* $map = new TypedMap('string', Foo::class);
* $map['x'] = new Foo();
* foreach ($map as $key => $value) {
@@ -51,7 +51,7 @@ namespace Ramsey\Collection\Map;
* It is preferable to subclass `AbstractTypedMap` to create your own typed map
* implementation:
*
* ```php
* ```
* class FooTypedMap extends AbstractTypedMap
* {
* public function getKeyType()
@@ -68,7 +68,7 @@ namespace Ramsey\Collection\Map;
*
* but you also may use the `TypedMap` class:
*
* ```php
* ```
* class FooTypedMap extends TypedMap
* {
* public function __constructor(array $data = [])

View File

@@ -24,7 +24,7 @@ namespace Ramsey\Collection;
*
* Example usage:
*
* ``` php
* ```
* $foo = new \My\Foo();
* $set = new Set(\My\Foo::class);
*

View File

@@ -16,6 +16,7 @@ namespace Ramsey\Collection\Tool;
use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\UnsupportedOperationException;
use ReflectionProperty;
use function is_array;
use function is_object;
@@ -28,6 +29,11 @@ use function sprintf;
*/
trait ValueExtractorTrait
{
/**
* Returns the type associated with this collection.
*/
abstract public function getType(): string;
/**
* Extracts the value of the given property, method, or array key from the
* element.
@@ -64,6 +70,15 @@ trait ValueExtractorTrait
));
}
if (property_exists($element, $propertyOrMethod) && method_exists($element, $propertyOrMethod)) {
$reflectionProperty = new ReflectionProperty($element, $propertyOrMethod);
if ($reflectionProperty->isPublic()) {
return $element->$propertyOrMethod;
}
return $element->{$propertyOrMethod}();
}
if (property_exists($element, $propertyOrMethod)) {
return $element->$propertyOrMethod;
}
@@ -72,6 +87,10 @@ trait ValueExtractorTrait
return $element->{$propertyOrMethod}();
}
if (isset($element->$propertyOrMethod)) {
return $element->$propertyOrMethod;
}
throw new InvalidPropertyOrMethod(sprintf(
'Method or property "%s" not defined in %s',
$propertyOrMethod,

View File

@@ -77,7 +77,8 @@ trait ValueToStringTrait
// __toString() is implemented
if (is_callable([$value, '__toString'])) {
return (string) $value->__toString();
/** @var string */
return $value->__toString();
}
// object of type \DateTime

View File

@@ -11,7 +11,7 @@
"require": {
"php": "^8.0",
"ext-json": "*",
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11",
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
"ramsey/collection": "^1.2 || ^2.0"
},
"require-dev": {

View File

@@ -136,9 +136,11 @@ final class BrickMathCalculator implements CalculatorInterface
/**
* Maps ramsey/uuid rounding modes to those used by brick/math
*
* @return BrickMathRounding::*
*/
private function getBrickRoundingMode(int $roundingMode): int
private function getBrickRoundingMode(int $roundingMode)
{
return self::ROUNDING_MODE_MAP[$roundingMode] ?? 0;
return self::ROUNDING_MODE_MAP[$roundingMode] ?? BrickMathRounding::UNNECESSARY;
}
}