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)';
}
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2012-2021 Ben Ramsey <ben@benramsey.com>
Copyright (c) 2012-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

@@ -8,9 +8,9 @@
<a href="https://github.com/ramsey/uuid"><img src="http://img.shields.io/badge/source-ramsey/uuid-blue.svg?style=flat-square" alt="Source Code"></a>
<a href="https://packagist.org/packages/ramsey/uuid"><img src="https://img.shields.io/packagist/v/ramsey/uuid.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/uuid.svg?style=flat-square&colorB=%238892BF" alt="PHP Programming Language"></a>
<a href="https://github.com/ramsey/uuid/blob/main/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/uuid.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a>
<a href="https://github.com/ramsey/uuid/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/workflow/status/ramsey/uuid/build/main?logo=github&style=flat-square" alt="Build Status"></a>
<a href="https://codecov.io/gh/ramsey/uuid"><img src="https://img.shields.io/codecov/c/gh/ramsey/uuid?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a>
<a href="https://github.com/ramsey/uuid/blob/4.x/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/uuid.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a>
<a href="https://github.com/ramsey/uuid/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/actions/workflow/status/ramsey/uuid/continuous-integration.yml?branch=4.x&logo=github&style=flat-square" alt="Build Status"></a>
<a href="https://app.codecov.io/gh/ramsey/uuid/branch/4.x"><img src="https://img.shields.io/codecov/c/github/ramsey/uuid/4.x?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a>
<a href="https://shepherd.dev/github/ramsey/uuid"><img src="https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fshepherd.dev%2Fgithub%2Framsey%2Fuuid%2Fcoverage" alt="Psalm Type Coverage"></a>
</p>
@@ -38,7 +38,7 @@ composer require ramsey/uuid
See the documentation for a thorough upgrade guide:
* [Upgrading ramsey/uuid Version 3 to 4](https://uuid.ramsey.dev/en/latest/upgrading/3-to-4.html)
* [Upgrading ramsey/uuid Version 3 to 4](https://uuid.ramsey.dev/en/stable/upgrading/3-to-4.html)
## Documentation
@@ -74,10 +74,10 @@ licensed for use under the MIT License (MIT). Please see [LICENSE][] for more
information.
[rfc4122]: http://tools.ietf.org/html/rfc4122
[conduct]: https://github.com/ramsey/uuid/blob/main/CODE_OF_CONDUCT.md
[conduct]: https://github.com/ramsey/uuid/blob/4.x/CODE_OF_CONDUCT.md
[javauuid]: http://docs.oracle.com/javase/6/docs/api/java/util/UUID.html
[pyuuid]: http://docs.python.org/3/library/uuid.html
[composer]: http://getcomposer.org/
[contributing.md]: https://github.com/ramsey/uuid/blob/main/CONTRIBUTING.md
[security.md]: https://github.com/ramsey/uuid/blob/main/SECURITY.md
[license]: https://github.com/ramsey/uuid/blob/main/LICENSE
[contributing.md]: https://github.com/ramsey/uuid/blob/4.x/CONTRIBUTING.md
[security.md]: https://github.com/ramsey/uuid/blob/4.x/SECURITY.md
[license]: https://github.com/ramsey/uuid/blob/4.x/LICENSE

View File

@@ -1,23 +1,18 @@
{
"name": "ramsey/uuid",
"type": "library",
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"license": "MIT",
"type": "library",
"keywords": [
"uuid",
"identifier",
"guid"
],
"license": "MIT",
"require": {
"php": "^7.2 || ^8.0",
"php": "^8.0",
"ext-json": "*",
"brick/math": "^0.8 || ^0.9",
"ramsey/collection": "^1.0",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-php80": "^1.14"
},
"replace": {
"rhumsaa/uuid": "self.version"
"brick/math": "^0.8.8 || ^0.9 || ^0.10",
"ramsey/collection": "^1.2 || ^2.0"
},
"require-dev": {
"captainhook/captainhook": "^5.10",
@@ -26,40 +21,33 @@
"doctrine/annotations": "^1.8",
"ergebnis/composer-normalize": "^2.15",
"mockery/mockery": "^1.3",
"moontoast/math": "^1.1",
"paragonie/random-lib": "^2",
"php-mock/php-mock": "^2.2",
"php-mock/php-mock-mockery": "^1.3",
"php-parallel-lint/php-parallel-lint": "^1.1",
"phpbench/phpbench": "^1.0",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-mockery": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9",
"slevomat/coding-standard": "^7.0",
"ramsey/composer-repl": "^1.4",
"slevomat/coding-standard": "^8.4",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.9"
},
"replace": {
"rhumsaa/uuid": "self.version"
},
"suggest": {
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
"ext-ctype": "Enables faster processing of character classification using ctype functions.",
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
"ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-main": "4.x-dev"
},
"captainhook": {
"force-install": true
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Ramsey\\Uuid\\": "src/"
@@ -75,8 +63,21 @@
"Ramsey\\Uuid\\Test\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"allow-plugins": {
"captainhook/plugin-composer": true,
"ergebnis/composer-normalize": true,
"phpstan/extension-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"ramsey/composer-repl": true
},
"sort-packages": true
},
"extra": {
"captainhook": {
"force-install": true
}
},
"scripts": {
"analyze": [
"@phpstan",
@@ -89,8 +90,8 @@
"phpcbf": "phpcbf -vpw --cache=build/cache/phpcs.cache",
"phpcs": "phpcs --cache=build/cache/phpcs.cache",
"phpstan": [
"phpstan analyse --no-progress",
"phpstan analyse -c phpstan-tests.neon --no-progress"
"phpstan analyse --no-progress --memory-limit=1G",
"phpstan analyse -c phpstan-tests.neon --no-progress --memory-limit=1G"
],
"phpunit": "phpunit --verbose --colors=always",
"phpunit-coverage": "phpunit --verbose --colors=always --coverage-html build/coverage",

View File

@@ -27,6 +27,11 @@ use Traversable;
/**
* A collection of UuidBuilderInterface objects
*
* @deprecated this class has been deprecated, and will be removed in 5.0.0. The use-case for this class comes from
* a pre-`phpstan/phpstan` and pre-`vimeo/psalm` ecosystem, in which type safety had to be mostly enforced
* at runtime: that is no longer necessary, now that you can safely verify your code to be correct, and use
* more generic types like `iterable<T>` instead.
*
* @extends AbstractCollection<UuidBuilderInterface>
*/
class BuilderCollection extends AbstractCollection

View File

@@ -30,15 +30,7 @@ use Ramsey\Uuid\UuidInterface;
*/
class DegradedUuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
private TimeConverterInterface $timeConverter;
/**
* @param NumberConverterInterface $numberConverter The number converter to
@@ -47,10 +39,9 @@ class DegradedUuidBuilder implements UuidBuilderInterface
* for converting timestamps extracted from a UUID to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
private NumberConverterInterface $numberConverter,
?TimeConverterInterface $timeConverter = null
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter ?: new DegradedTimeConverter();
}

View File

@@ -28,16 +28,10 @@ use Ramsey\Uuid\UuidInterface;
class FallbackBuilder implements UuidBuilderInterface
{
/**
* @var BuilderCollection
* @param iterable<UuidBuilderInterface> $builders An array of UUID builders
*/
private $builders;
/**
* @param BuilderCollection $builders An array of UUID builders
*/
public function __construct(BuilderCollection $builders)
public function __construct(private iterable $builders)
{
$this->builders = $builders;
}
/**

View File

@@ -18,6 +18,7 @@ use Ramsey\Uuid\Guid\Guid;
use Ramsey\Uuid\UuidInterface;
use function bin2hex;
use function sprintf;
use function substr;
/**
@@ -29,6 +30,26 @@ use function substr;
*/
class GuidStringCodec extends StringCodec
{
public function encode(UuidInterface $uuid): string
{
$hex = bin2hex($uuid->getFields()->getBytes());
/** @var non-empty-string */
return sprintf(
'%02s%02s%02s%02s-%02s%02s-%02s%02s-%04s-%012s',
substr($hex, 6, 2),
substr($hex, 4, 2),
substr($hex, 2, 2),
substr($hex, 0, 2),
substr($hex, 10, 2),
substr($hex, 8, 2),
substr($hex, 14, 2),
substr($hex, 12, 2),
substr($hex, 16, 4),
substr($hex, 20),
);
}
public function decode(string $encodedUuid): UuidInterface
{
$bytes = $this->getBytes($encodedUuid);

View File

@@ -17,12 +17,13 @@ namespace Ramsey\Uuid\Codec;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Exception\InvalidUuidStringException;
use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use function bin2hex;
use function hex2bin;
use function implode;
use function sprintf;
use function str_replace;
use function strlen;
use function substr;
@@ -36,36 +37,28 @@ use function substr;
*/
class StringCodec implements CodecInterface
{
/**
* @var UuidBuilderInterface
*/
private $builder;
/**
* Constructs a StringCodec
*
* @param UuidBuilderInterface $builder The builder to use when encoding UUIDs
*/
public function __construct(UuidBuilderInterface $builder)
public function __construct(private UuidBuilderInterface $builder)
{
$this->builder = $builder;
}
public function encode(UuidInterface $uuid): string
{
/** @var FieldsInterface $fields */
$fields = $uuid->getFields();
$hex = bin2hex($uuid->getFields()->getBytes());
return $fields->getTimeLow()->toString()
. '-'
. $fields->getTimeMid()->toString()
. '-'
. $fields->getTimeHiAndVersion()->toString()
. '-'
. $fields->getClockSeqHiAndReserved()->toString()
. $fields->getClockSeqLow()->toString()
. '-'
. $fields->getNode()->toString();
/** @var non-empty-string */
return sprintf(
'%08s-%04s-%04s-%04s-%012s',
substr($hex, 0, 8),
substr($hex, 8, 4),
substr($hex, 12, 4),
substr($hex, 16, 4),
substr($hex, 20),
);
}
/**

View File

@@ -27,10 +27,7 @@ use Ramsey\Uuid\Math\BrickMathCalculator;
*/
class BigNumberConverter implements NumberConverterInterface
{
/**
* @var NumberConverterInterface
*/
private $converter;
private NumberConverterInterface $converter;
public function __construct()
{

View File

@@ -26,14 +26,8 @@ use Ramsey\Uuid\Type\Integer as IntegerObject;
*/
class GenericNumberConverter implements NumberConverterInterface
{
/**
* @var CalculatorInterface
*/
private $calculator;
public function __construct(CalculatorInterface $calculator)
public function __construct(private CalculatorInterface $calculator)
{
$this->calculator = $calculator;
}
/**

View File

@@ -29,10 +29,7 @@ use Ramsey\Uuid\Type\Time;
*/
class BigNumberTimeConverter implements TimeConverterInterface
{
/**
* @var TimeConverterInterface
*/
private $converter;
private TimeConverterInterface $converter;
public function __construct()
{

View File

@@ -50,14 +50,8 @@ class GenericTimeConverter implements TimeConverterInterface
*/
private const MICROSECOND_INTERVALS = '10';
/**
* @var CalculatorInterface
*/
private $calculator;
public function __construct(CalculatorInterface $calculator)
public function __construct(private CalculatorInterface $calculator)
{
$this->calculator = $calculator;
}
public function calculateTime(string $seconds, string $microseconds): Hexadecimal

View File

@@ -58,20 +58,9 @@ class PhpTimeConverter implements TimeConverterInterface
*/
private const MICROSECOND_INTERVALS = 10;
/**
* @var CalculatorInterface
*/
private $calculator;
/**
* @var TimeConverterInterface
*/
private $fallbackConverter;
/**
* @var int
*/
private $phpPrecision;
private int $phpPrecision;
private CalculatorInterface $calculator;
private TimeConverterInterface $fallbackConverter;
public function __construct(
?CalculatorInterface $calculator = null,
@@ -132,11 +121,11 @@ class PhpTimeConverter implements TimeConverterInterface
}
/**
* @param int|float $time The time to split into seconds and microseconds
* @param float|int $time The time to split into seconds and microseconds
*
* @return string[]
*/
private function splitTime($time): array
private function splitTime(float | int $time): array
{
$split = explode('.', (string) $time, 2);

View File

@@ -0,0 +1,90 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Converter\Time;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Math\CalculatorInterface;
use Ramsey\Uuid\Math\RoundingMode;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Type\Time;
use function explode;
use function str_pad;
use const STR_PAD_LEFT;
/**
* UnixTimeConverter converts Unix Epoch timestamps to/from hexadecimal values
* consisting of milliseconds elapsed since the Unix Epoch
*
* @psalm-immutable
*/
class UnixTimeConverter implements TimeConverterInterface
{
private const MILLISECONDS = 1000;
public function __construct(private CalculatorInterface $calculator)
{
}
public function calculateTime(string $seconds, string $microseconds): Hexadecimal
{
$timestamp = new Time($seconds, $microseconds);
// Convert the seconds into milliseconds.
$sec = $this->calculator->multiply(
$timestamp->getSeconds(),
new IntegerObject(self::MILLISECONDS),
);
// Convert the microseconds into milliseconds; the scale is zero because
// we need to discard the fractional part.
$usec = $this->calculator->divide(
RoundingMode::DOWN, // Always round down to stay in the previous millisecond.
0,
$timestamp->getMicroseconds(),
new IntegerObject(self::MILLISECONDS),
);
/** @var IntegerObject $unixTime */
$unixTime = $this->calculator->add($sec, $usec);
$unixTimeHex = str_pad(
$this->calculator->toHexadecimal($unixTime)->toString(),
12,
'0',
STR_PAD_LEFT
);
return new Hexadecimal($unixTimeHex);
}
public function convertTime(Hexadecimal $uuidTimestamp): Time
{
$milliseconds = $this->calculator->toInteger($uuidTimestamp);
$unixTimestamp = $this->calculator->divide(
RoundingMode::HALF_UP,
6,
$milliseconds,
new IntegerObject(self::MILLISECONDS)
);
$split = explode('.', (string) $unixTimestamp, 2);
return new Time($split[0], $split[1] ?? '0');
}
}

View File

@@ -18,8 +18,7 @@ use DateTimeInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
/**
* This interface encapsulates deprecated methods for ramsey/uuid; this
* interface and its methods will be removed in ramsey/uuid 5.0.0.
* This interface encapsulates deprecated methods for ramsey/uuid
*
* @psalm-immutable
*/
@@ -123,12 +122,6 @@ interface DeprecatedUuidInterface
*/
public function getTimestampHex(): string;
/**
* @deprecated In ramsey/uuid version 5.0.0, this will be removed from this
* interface. It has moved to {@see \Ramsey\Uuid\Rfc4122\UuidInterface::getUrn()}.
*/
public function getUrn(): string;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a

View File

@@ -17,10 +17,8 @@ namespace Ramsey\Uuid;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\DateTimeException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Throwable;
use function str_pad;
@@ -32,29 +30,17 @@ use const STR_PAD_LEFT;
* This trait encapsulates deprecated methods for ramsey/uuid; this trait and
* its methods will be removed in ramsey/uuid 5.0.0.
*
* @deprecated This trait and its methods will be removed in ramsey/uuid 5.0.0.
*
* @psalm-immutable
*/
trait DeprecatedUuidMethodsTrait
{
/**
* @var Rfc4122FieldsInterface
*/
protected $fields;
/**
* @var NumberConverterInterface
*/
protected $numberConverter;
/**
* @var TimeConverterInterface
*/
protected $timeConverter;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()}
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
@@ -65,8 +51,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqHiAndReserved()}.
*/
public function getClockSeqHiAndReservedHex(): string
{
@@ -75,8 +62,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
@@ -87,8 +75,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeqLow()}.
*/
public function getClockSeqLowHex(): string
{
@@ -97,8 +86,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
@@ -109,8 +99,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getClockSeq()}.
*/
public function getClockSequenceHex(): string
{
@@ -157,7 +148,7 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance.
*
* @return string[]
*/
@@ -219,10 +210,11 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getNode()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()} and use the
* arbitrary-precision math library of your choice to convert it to a
* string integer.
*/
public function getNode(): string
{
@@ -231,8 +223,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getNode()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getNode()}.
*/
public function getNodeHex(): string
{
@@ -241,8 +234,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
@@ -253,8 +247,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeHiAndVersion()}.
*/
public function getTimeHiAndVersionHex(): string
{
@@ -263,10 +258,11 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()} and use the
* arbitrary-precision math library of your choice to convert it to a
* string integer.
*/
public function getTimeLow(): string
{
@@ -275,8 +271,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeLow()}.
*/
public function getTimeLowHex(): string
{
@@ -285,10 +282,11 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()} and use the
* arbitrary-precision math library of your choice to convert it to a
* string integer.
*/
public function getTimeMid(): string
{
@@ -297,8 +295,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimeMid()}.
*/
public function getTimeMidHex(): string
{
@@ -307,10 +306,11 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()} and use
* the arbitrary-precision math library of your choice to convert it to
* a string integer.
*/
public function getTimestamp(): string
{
@@ -323,8 +323,9 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()}.
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getTimestamp()}.
*/
public function getTimestampHex(): string
{
@@ -335,20 +336,9 @@ trait DeprecatedUuidMethodsTrait
return $this->fields->getTimestamp()->toString();
}
/**
* @deprecated This has moved to {@see Rfc4122FieldsInterface::getUrn()} and
* is available on {@see \Ramsey\Uuid\Rfc4122\UuidV1},
* {@see \Ramsey\Uuid\Rfc4122\UuidV3}, {@see \Ramsey\Uuid\Rfc4122\UuidV4},
* and {@see \Ramsey\Uuid\Rfc4122\UuidV5}.
*/
public function getUrn(): string
{
return 'urn:uuid:' . $this->toString();
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}.
*/
@@ -359,7 +349,7 @@ trait DeprecatedUuidMethodsTrait
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Fields\FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}.
*/

View File

@@ -14,7 +14,6 @@ declare(strict_types=1);
namespace Ramsey\Uuid;
use Ramsey\Uuid\Builder\BuilderCollection;
use Ramsey\Uuid\Builder\FallbackBuilder;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
@@ -36,6 +35,7 @@ use Ramsey\Uuid\Generator\RandomGeneratorFactory;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\TimeGeneratorFactory;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Generator\UnixTimeGenerator;
use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Math\CalculatorInterface;
@@ -43,7 +43,6 @@ use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder;
use Ramsey\Uuid\Provider\Dce\SystemDceSecurityProvider;
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
use Ramsey\Uuid\Provider\Node\FallbackNodeProvider;
use Ramsey\Uuid\Provider\Node\NodeProviderCollection;
use Ramsey\Uuid\Provider\Node\RandomNodeProvider;
use Ramsey\Uuid\Provider\Node\SystemNodeProvider;
use Ramsey\Uuid\Provider\NodeProviderInterface;
@@ -63,92 +62,25 @@ use const PHP_INT_SIZE;
*/
class FeatureSet
{
/**
* @var bool
*/
private $disableBigNumber = false;
/**
* @var bool
*/
private $disable64Bit = false;
/**
* @var bool
*/
private $ignoreSystemNode = false;
/**
* @var bool
*/
private $enablePecl = false;
/**
* @var UuidBuilderInterface
*/
private $builder;
/**
* @var CodecInterface
*/
private $codec;
/**
* @var DceSecurityGeneratorInterface
*/
private $dceSecurityGenerator;
/**
* @var NameGeneratorInterface
*/
private $nameGenerator;
/**
* @var NodeProviderInterface
*/
private $nodeProvider;
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @var RandomGeneratorInterface
*/
private $randomGenerator;
/**
* @var TimeGeneratorInterface
*/
private $timeGenerator;
/**
* @var TimeProviderInterface
*/
private $timeProvider;
/**
* @var ValidatorInterface
*/
private $validator;
/**
* @var CalculatorInterface
*/
private $calculator;
private ?TimeProviderInterface $timeProvider = null;
private CalculatorInterface $calculator;
private CodecInterface $codec;
private DceSecurityGeneratorInterface $dceSecurityGenerator;
private NameGeneratorInterface $nameGenerator;
private NodeProviderInterface $nodeProvider;
private NumberConverterInterface $numberConverter;
private RandomGeneratorInterface $randomGenerator;
private TimeConverterInterface $timeConverter;
private TimeGeneratorInterface $timeGenerator;
private TimeGeneratorInterface $unixTimeGenerator;
private UuidBuilderInterface $builder;
private ValidatorInterface $validator;
/**
* @param bool $useGuids True build UUIDs using the GuidStringCodec
* @param bool $force32Bit True to force the use of 32-bit functionality
* (primarily for testing purposes)
* @param bool $forceNoBigNumber True to disable the use of moontoast/math
* (primarily for testing purposes)
* @param bool $forceNoBigNumber (obsolete)
* @param bool $ignoreSystemNode True to disable attempts to check for the
* system node ID (primarily for testing purposes)
* @param bool $enablePecl True to enable the use of the PeclUuidTimeGenerator
@@ -156,25 +88,23 @@ class FeatureSet
*/
public function __construct(
bool $useGuids = false,
bool $force32Bit = false,
private bool $force32Bit = false,
bool $forceNoBigNumber = false,
bool $ignoreSystemNode = false,
bool $enablePecl = false
private bool $ignoreSystemNode = false,
private bool $enablePecl = false
) {
$this->disableBigNumber = $forceNoBigNumber;
$this->disable64Bit = $force32Bit;
$this->ignoreSystemNode = $ignoreSystemNode;
$this->enablePecl = $enablePecl;
$this->randomGenerator = $this->buildRandomGenerator();
$this->setCalculator(new BrickMathCalculator());
$this->builder = $this->buildUuidBuilder($useGuids);
$this->codec = $this->buildCodec($useGuids);
$this->nodeProvider = $this->buildNodeProvider();
$this->nameGenerator = $this->buildNameGenerator();
$this->randomGenerator = $this->buildRandomGenerator();
$this->setTimeProvider(new SystemTimeProvider());
$this->setDceSecurityProvider(new SystemDceSecurityProvider());
$this->validator = new GenericValidator();
assert($this->timeProvider !== null);
$this->unixTimeGenerator = $this->buildUnixTimeGenerator();
}
/**
@@ -257,6 +187,14 @@ class FeatureSet
return $this->timeGenerator;
}
/**
* Returns the Unix Epoch time generator configured for this environment
*/
public function getUnixTimeGenerator(): TimeGeneratorInterface
{
return $this->unixTimeGenerator;
}
/**
* Returns the validator configured for this environment
*/
@@ -294,7 +232,10 @@ class FeatureSet
public function setNodeProvider(NodeProviderInterface $nodeProvider): void
{
$this->nodeProvider = $nodeProvider;
$this->timeGenerator = $this->buildTimeGenerator($this->timeProvider);
if (isset($this->timeProvider)) {
$this->timeGenerator = $this->buildTimeGenerator($this->timeProvider);
}
}
/**
@@ -350,10 +291,10 @@ class FeatureSet
return new RandomNodeProvider();
}
return new FallbackNodeProvider(new NodeProviderCollection([
return new FallbackNodeProvider([
new SystemNodeProvider(),
new RandomNodeProvider(),
]));
]);
}
/**
@@ -395,6 +336,14 @@ class FeatureSet
))->getGenerator();
}
/**
* Returns a Unix Epoch time generator configured for this environment
*/
private function buildUnixTimeGenerator(): TimeGeneratorInterface
{
return new UnixTimeGenerator($this->randomGenerator);
}
/**
* Returns a name generator configured for this environment
*/
@@ -432,11 +381,10 @@ class FeatureSet
return new GuidBuilder($this->numberConverter, $this->timeConverter);
}
/** @psalm-suppress ImpureArgument */
return new FallbackBuilder(new BuilderCollection([
return new FallbackBuilder([
new Rfc4122UuidBuilder($this->numberConverter, $this->timeConverter),
new NonstandardUuidBuilder($this->numberConverter, $this->timeConverter),
]));
]);
}
/**
@@ -444,6 +392,6 @@ class FeatureSet
*/
private function is64BitSystem(): bool
{
return PHP_INT_SIZE === 8 && !$this->disable64Bit;
return PHP_INT_SIZE === 8 && !$this->force32Bit;
}
}

View File

@@ -56,22 +56,23 @@ trait SerializableFieldsTrait
/**
* Constructs the object from a serialized string representation
*
* @param string $serialized The serialized string representation of the object
* @param string $data The serialized string representation of the object
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress UnusedMethodCall
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
if (strlen($serialized) === 16) {
$this->__construct($serialized);
if (strlen($data) === 16) {
$this->__construct($data);
} else {
$this->__construct(base64_decode($serialized));
$this->__construct(base64_decode($data));
}
}
/**
* @param array{bytes: string} $data
* @param array{bytes?: string} $data
*
* @psalm-suppress UnusedMethodCall
*/
public function __unserialize(array $data): void
{

View File

@@ -61,22 +61,10 @@ class CombGenerator implements RandomGeneratorInterface
{
public const TIMESTAMP_BYTES = 6;
/**
* @var RandomGeneratorInterface
*/
private $randomGenerator;
/**
* @var NumberConverterInterface
*/
private $converter;
public function __construct(
RandomGeneratorInterface $generator,
NumberConverterInterface $numberConverter
private RandomGeneratorInterface $generator,
private NumberConverterInterface $numberConverter
) {
$this->converter = $numberConverter;
$this->randomGenerator = $generator;
}
/**
@@ -87,7 +75,7 @@ class CombGenerator implements RandomGeneratorInterface
*/
public function generate(int $length): string
{
if ($length < self::TIMESTAMP_BYTES || $length < 0) {
if ($length < self::TIMESTAMP_BYTES) {
throw new InvalidArgumentException(
'Length must be a positive integer greater than or equal to ' . self::TIMESTAMP_BYTES
);
@@ -95,11 +83,11 @@ class CombGenerator implements RandomGeneratorInterface
$hash = '';
if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) {
$hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES);
$hash = $this->generator->generate($length - self::TIMESTAMP_BYTES);
}
$lsbTime = str_pad(
$this->converter->toHex($this->timestamp()),
$this->numberConverter->toHex($this->timestamp()),
self::TIMESTAMP_BYTES * 2,
'0',
STR_PAD_LEFT

View File

@@ -52,29 +52,11 @@ class DceSecurityGenerator implements DceSecurityGeneratorInterface
*/
private const CLOCK_SEQ_LOW = 0;
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeGeneratorInterface
*/
private $timeGenerator;
/**
* @var DceSecurityProviderInterface
*/
private $dceSecurityProvider;
public function __construct(
NumberConverterInterface $numberConverter,
TimeGeneratorInterface $timeGenerator,
DceSecurityProviderInterface $dceSecurityProvider
private NumberConverterInterface $numberConverter,
private TimeGeneratorInterface $timeGenerator,
private DceSecurityProviderInterface $dceSecurityProvider
) {
$this->numberConverter = $numberConverter;
$this->timeGenerator = $timeGenerator;
$this->dceSecurityProvider = $dceSecurityProvider;
}
public function generate(
@@ -153,8 +135,7 @@ class DceSecurityGenerator implements DceSecurityGeneratorInterface
// Replace bytes in the time-based UUID with DCE Security values.
$bytes = substr_replace($bytes, $identifierBytes, 0, 4);
$bytes = substr_replace($bytes, $domainByte, 9, 1);
return $bytes;
return substr_replace($bytes, $domainByte, 9, 1);
}
}

View File

@@ -23,11 +23,11 @@ use Ramsey\Uuid\Provider\TimeProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use Throwable;
use function ctype_xdigit;
use function dechex;
use function hex2bin;
use function is_int;
use function pack;
use function preg_match;
use function sprintf;
use function str_pad;
use function strlen;
@@ -40,29 +40,11 @@ use const STR_PAD_LEFT;
*/
class DefaultTimeGenerator implements TimeGeneratorInterface
{
/**
* @var NodeProviderInterface
*/
private $nodeProvider;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @var TimeProviderInterface
*/
private $timeProvider;
public function __construct(
NodeProviderInterface $nodeProvider,
TimeConverterInterface $timeConverter,
TimeProviderInterface $timeProvider
private NodeProviderInterface $nodeProvider,
private TimeConverterInterface $timeConverter,
private TimeProviderInterface $timeProvider
) {
$this->nodeProvider = $nodeProvider;
$this->timeConverter = $timeConverter;
$this->timeProvider = $timeProvider;
}
/**
@@ -121,13 +103,13 @@ class DefaultTimeGenerator implements TimeGeneratorInterface
* Uses the node provider given when constructing this instance to get
* the node ID (usually a MAC address)
*
* @param string|int|null $node A node value that may be used to override the node provider
* @param int|string|null $node A node value that may be used to override the node provider
*
* @return string 6-byte binary string representation of the node
*
* @throws InvalidArgumentException
*/
private function getValidNode($node): string
private function getValidNode(int | string | null $node): string
{
if ($node === null) {
$node = $this->nodeProvider->getNode();
@@ -138,7 +120,7 @@ class DefaultTimeGenerator implements TimeGeneratorInterface
$node = dechex($node);
}
if (!ctype_xdigit((string) $node) || strlen((string) $node) > 12) {
if (!preg_match('/^[A-Fa-f0-9]+$/', (string) $node) || strlen((string) $node) > 12) {
throw new InvalidArgumentException('Invalid node value');
}

View File

@@ -33,21 +33,16 @@ class PeclUuidNameGenerator implements NameGeneratorInterface
/** @psalm-pure */
public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string
{
switch ($hashAlgorithm) {
case 'md5':
$uuid = uuid_generate_md5($ns->toString(), $name);
break;
case 'sha1':
$uuid = uuid_generate_sha1($ns->toString(), $name);
break;
default:
throw new NameException(sprintf(
$uuid = match ($hashAlgorithm) {
'md5' => uuid_generate_md5($ns->toString(), $name),
'sha1' => uuid_generate_sha1($ns->toString(), $name),
default => throw new NameException(
sprintf(
'Unable to hash namespace and name with algorithm \'%s\'',
$hashAlgorithm
));
}
)
),
};
return uuid_parse($uuid);
}

View File

@@ -22,7 +22,7 @@ interface RandomGeneratorInterface
/**
* Generates a string of randomized binary data
*
* @param int $length The number of bytes of random binary data to generate
* @param int<1, max> $length The number of bytes of random binary data to generate
*
* @return string A binary string
*/

View File

@@ -21,14 +21,15 @@ use RandomLib\Generator;
* RandomLibAdapter generates strings of random binary data using the
* paragonie/random-lib library
*
* @deprecated This class will be removed in 5.0.0. Use the default
* RandomBytesGenerator or implement your own generator that implements
* RandomGeneratorInterface.
*
* @link https://packagist.org/packages/paragonie/random-lib paragonie/random-lib
*/
class RandomLibAdapter implements RandomGeneratorInterface
{
/**
* @var Generator
*/
private $generator;
private Generator $generator;
/**
* Constructs a RandomLibAdapter

View File

@@ -24,29 +24,11 @@ use Ramsey\Uuid\Provider\TimeProviderInterface;
*/
class TimeGeneratorFactory
{
/**
* @var NodeProviderInterface
*/
private $nodeProvider;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @var TimeProviderInterface
*/
private $timeProvider;
public function __construct(
NodeProviderInterface $nodeProvider,
TimeConverterInterface $timeConverter,
TimeProviderInterface $timeProvider
private NodeProviderInterface $nodeProvider,
private TimeConverterInterface $timeConverter,
private TimeProviderInterface $timeProvider
) {
$this->nodeProvider = $nodeProvider;
$this->timeConverter = $timeConverter;
$this->timeProvider = $timeProvider;
}
/**

View File

@@ -0,0 +1,169 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Generator;
use Brick\Math\BigInteger;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use function hash;
use function pack;
use function str_pad;
use function strlen;
use function substr;
use function substr_replace;
use function unpack;
use const PHP_INT_SIZE;
use const STR_PAD_LEFT;
/**
* UnixTimeGenerator generates bytes that combine a 48-bit timestamp in
* milliseconds since the Unix Epoch with 80 random bits
*
* Code and concepts within this class are borrowed from the symfony/uid package
* and are used under the terms of the MIT license distributed with symfony/uid.
*
* symfony/uid is copyright (c) Fabien Potencier.
*
* @link https://symfony.com/components/Uid Symfony Uid component
* @link https://github.com/symfony/uid/blob/4f9f537e57261519808a7ce1d941490736522bbc/UuidV7.php Symfony UuidV7 class
* @link https://github.com/symfony/uid/blob/6.2/LICENSE MIT License
*/
class UnixTimeGenerator implements TimeGeneratorInterface
{
private static string $time = '';
private static ?string $seed = null;
private static int $seedIndex = 0;
/** @var int[] */
private static array $rand = [];
/** @var int[] */
private static array $seedParts;
public function __construct(
private RandomGeneratorInterface $randomGenerator,
private int $intSize = PHP_INT_SIZE
) {
}
/**
* @param Hexadecimal|int|string|null $node Unused in this generator
* @param int|null $clockSeq Unused in this generator
* @param DateTimeInterface $dateTime A date-time instance to use when
* generating bytes
*
* @inheritDoc
*/
public function generate($node = null, ?int $clockSeq = null, ?DateTimeInterface $dateTime = null): string
{
$time = ($dateTime ?? new DateTimeImmutable('now'))->format('Uv');
if ($time > self::$time || ($dateTime !== null && $time !== self::$time)) {
$this->randomize($time);
} else {
$time = $this->increment();
}
if ($this->intSize >= 8) {
$time = substr(pack('J', (int) $time), -6);
} else {
$time = str_pad(BigInteger::of($time)->toBytes(false), 6, "\x00", STR_PAD_LEFT);
}
/** @var non-empty-string */
return $time . pack('n*', self::$rand[1], self::$rand[2], self::$rand[3], self::$rand[4], self::$rand[5]);
}
private function randomize(string $time): void
{
if (self::$seed === null) {
$seed = $this->randomGenerator->generate(16);
self::$seed = $seed;
} else {
$seed = $this->randomGenerator->generate(10);
}
/** @var int[] $rand */
$rand = unpack('n*', $seed);
$rand[1] &= 0x03ff;
self::$rand = $rand;
self::$time = $time;
}
/**
* Special thanks to Nicolas Grekas for sharing the following information:
*
* Within the same ms, we increment the rand part by a random 24-bit number.
*
* Instead of getting this number from random_bytes(), which is slow, we get
* it by sha512-hashing self::$seed. This produces 64 bytes of entropy,
* which we need to split in a list of 24-bit numbers. unpack() first splits
* them into 16 x 32-bit numbers; we take the first byte of each of these
* numbers to get 5 extra 24-bit numbers. Then, we consume those numbers
* one-by-one and run this logic every 21 iterations.
*
* self::$rand holds the random part of the UUID, split into 5 x 16-bit
* numbers for x86 portability. We increment this random part by the next
* 24-bit number in the self::$seedParts list and decrement
* self::$seedIndex.
*
* @link https://twitter.com/nicolasgrekas/status/1583356938825261061 Tweet from Nicolas Grekas
*/
private function increment(): string
{
if (self::$seedIndex === 0 && self::$seed !== null) {
self::$seed = hash('sha512', self::$seed, true);
/** @var int[] $s */
$s = unpack('l*', self::$seed);
$s[] = ($s[1] >> 8 & 0xff0000) | ($s[2] >> 16 & 0xff00) | ($s[3] >> 24 & 0xff);
$s[] = ($s[4] >> 8 & 0xff0000) | ($s[5] >> 16 & 0xff00) | ($s[6] >> 24 & 0xff);
$s[] = ($s[7] >> 8 & 0xff0000) | ($s[8] >> 16 & 0xff00) | ($s[9] >> 24 & 0xff);
$s[] = ($s[10] >> 8 & 0xff0000) | ($s[11] >> 16 & 0xff00) | ($s[12] >> 24 & 0xff);
$s[] = ($s[13] >> 8 & 0xff0000) | ($s[14] >> 16 & 0xff00) | ($s[15] >> 24 & 0xff);
self::$seedParts = $s;
self::$seedIndex = 21;
}
self::$rand[5] = 0xffff & $carry = self::$rand[5] + (self::$seedParts[self::$seedIndex--] & 0xffffff);
self::$rand[4] = 0xffff & $carry = self::$rand[4] + ($carry >> 16);
self::$rand[3] = 0xffff & $carry = self::$rand[3] + ($carry >> 16);
self::$rand[2] = 0xffff & $carry = self::$rand[2] + ($carry >> 16);
self::$rand[1] += $carry >> 16;
if (0xfc00 & self::$rand[1]) {
$time = self::$time;
$mtime = (int) substr($time, -9);
if ($this->intSize >= 8 || strlen($time) < 10) {
$time = (string) ((int) $time + 1);
} elseif ($mtime === 999999999) {
$time = (1 + (int) substr($time, 0, -9)) . '000000000';
} else {
$mtime++;
$time = substr_replace($time, str_pad((string) $mtime, 9, '0', STR_PAD_LEFT), -9);
}
$this->randomize($time);
}
return self::$time;
}
}

View File

@@ -17,6 +17,7 @@ namespace Ramsey\Uuid\Guid;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Fields\SerializableFieldsTrait;
use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\Rfc4122\MaxTrait;
use Ramsey\Uuid\Rfc4122\NilTrait;
use Ramsey\Uuid\Rfc4122\VariantTrait;
use Ramsey\Uuid\Rfc4122\VersionTrait;
@@ -44,16 +45,12 @@ use const STR_PAD_LEFT;
*/
final class Fields implements FieldsInterface
{
use MaxTrait;
use NilTrait;
use SerializableFieldsTrait;
use VariantTrait;
use VersionTrait;
/**
* @var string
*/
private $bytes;
/**
* @param string $bytes A 16-byte binary string representation of a UUID
*
@@ -61,17 +58,15 @@ final class Fields implements FieldsInterface
* @throws InvalidArgumentException if the byte string does not represent a GUID
* @throws InvalidArgumentException if the byte string does not contain a valid version
*/
public function __construct(string $bytes)
public function __construct(private string $bytes)
{
if (strlen($bytes) !== 16) {
if (strlen($this->bytes) !== 16) {
throw new InvalidArgumentException(
'The byte string must be 16 bytes long; '
. 'received ' . strlen($bytes) . ' bytes'
. 'received ' . strlen($this->bytes) . ' bytes'
);
}
$this->bytes = $bytes;
if (!$this->isCorrectVariant()) {
throw new InvalidArgumentException(
'The byte string received does not conform to the RFC '
@@ -149,7 +144,13 @@ final class Fields implements FieldsInterface
public function getClockSeq(): Hexadecimal
{
$clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
if ($this->isMax()) {
$clockSeq = 0xffff;
} elseif ($this->isNil()) {
$clockSeq = 0x0000;
} else {
$clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
}
return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT));
}
@@ -171,7 +172,7 @@ final class Fields implements FieldsInterface
public function getVersion(): ?int
{
if ($this->isNil()) {
if ($this->isNil() || $this->isMax()) {
return null;
}
@@ -183,7 +184,7 @@ final class Fields implements FieldsInterface
private function isCorrectVariant(): bool
{
if ($this->isNil()) {
if ($this->isNil() || $this->isMax()) {
return true;
}

View File

@@ -31,16 +31,6 @@ use Throwable;
*/
class GuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @param NumberConverterInterface $numberConverter The number converter to
* use when constructing the Guid
@@ -48,11 +38,9 @@ class GuidBuilder implements UuidBuilderInterface
* for converting timestamps extracted from a UUID to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
TimeConverterInterface $timeConverter
private NumberConverterInterface $numberConverter,
private TimeConverterInterface $timeConverter
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter;
}
/**

View File

@@ -18,8 +18,8 @@ use DateTimeInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Fields\FieldsInterface;
use Ramsey\Uuid\Nonstandard\UuidV6;
use Ramsey\Uuid\Rfc4122\UuidV1;
use Ramsey\Uuid\Rfc4122\UuidV6;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\UuidFactory;
@@ -55,18 +55,14 @@ use function substr;
final class LazyUuidFromString implements UuidInterface
{
public const VALID_REGEX = '/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/ms';
/**
* @var string
* @psalm-var non-empty-string
*/
private $uuid;
/** @var UuidInterface|null */
private $unwrapped;
/** @psalm-param non-empty-string $uuid */
public function __construct(string $uuid)
private ?UuidInterface $unwrapped = null;
/**
* @psalm-param non-empty-string $uuid
*/
public function __construct(private string $uuid)
{
$this->uuid = $uuid;
}
/** @psalm-pure */
@@ -105,19 +101,20 @@ final class LazyUuidFromString implements UuidInterface
/**
* {@inheritDoc}
*
* @param string $serialized
* @param string $data
*
* @psalm-param non-empty-string $serialized
* @psalm-param non-empty-string $data
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
$this->uuid = $serialized;
$this->uuid = $data;
}
/**
* @param array{string: string} $data
* @param array{string?: string} $data
*
* @psalm-param array{string: non-empty-string} $data
* @psalm-param array{string?: non-empty-string} $data
* @psalm-suppress UnusedMethodCall
*/
public function __unserialize(array $data): void
{

View File

@@ -47,26 +47,19 @@ final class Fields implements FieldsInterface
use SerializableFieldsTrait;
use VariantTrait;
/**
* @var string
*/
private $bytes;
/**
* @param string $bytes A 16-byte binary string representation of a UUID
*
* @throws InvalidArgumentException if the byte string is not exactly 16 bytes
*/
public function __construct(string $bytes)
public function __construct(private string $bytes)
{
if (strlen($bytes) !== 16) {
if (strlen($this->bytes) !== 16) {
throw new InvalidArgumentException(
'The byte string must be 16 bytes long; '
. 'received ' . strlen($bytes) . ' bytes'
. 'received ' . strlen($this->bytes) . ' bytes'
);
}
$this->bytes = $bytes;
}
public function getBytes(): string
@@ -130,4 +123,9 @@ final class Fields implements FieldsInterface
{
return false;
}
public function isMax(): bool
{
return false;
}
}

View File

@@ -29,16 +29,6 @@ use Throwable;
*/
class UuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @param NumberConverterInterface $numberConverter The number converter to
* use when constructing the Nonstandard\Uuid
@@ -46,11 +36,9 @@ class UuidBuilder implements UuidBuilderInterface
* for converting timestamps extracted from a UUID to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
TimeConverterInterface $timeConverter
private NumberConverterInterface $numberConverter,
private TimeConverterInterface $timeConverter
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter;
}
/**

View File

@@ -14,39 +14,34 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Nonstandard;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\DateTimeException;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Lazy\LazyUuidFromString;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Rfc4122\TimeTrait;
use Ramsey\Uuid\Rfc4122\UuidInterface;
use Ramsey\Uuid\Rfc4122\UuidV1;
use Ramsey\Uuid\Uuid;
use Throwable;
use function hex2bin;
use function str_pad;
use function substr;
use const STR_PAD_LEFT;
/**
* Ordered-time, or version 6, UUIDs include timestamp, clock sequence, and node
* values that are combined into a 128-bit unsigned integer
* Reordered time, or version 6, UUIDs include timestamp, clock sequence, and
* node values that are combined into a 128-bit unsigned integer
*
* @deprecated Use {@see \Ramsey\Uuid\Rfc4122\UuidV6} instead.
*
* @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft
* @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs
*
* @psalm-immutable
*/
final class UuidV6 extends Uuid implements UuidInterface
class UuidV6 extends Uuid implements UuidInterface
{
use TimeTrait;
/**
* Creates a version 6 (time-based) UUID
* Creates a version 6 (reordered time) UUID
*
* @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID
* @param NumberConverterInterface $numberConverter The number converter to use
@@ -62,39 +57,16 @@ final class UuidV6 extends Uuid implements UuidInterface
CodecInterface $codec,
TimeConverterInterface $timeConverter
) {
if ($fields->getVersion() !== Uuid::UUID_TYPE_PEABODY) {
if ($fields->getVersion() !== Uuid::UUID_TYPE_REORDERED_TIME) {
throw new InvalidArgumentException(
'Fields used to create a UuidV6 must represent a '
. 'version 6 (ordered-time) UUID'
. 'version 6 (reordered time) UUID'
);
}
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
/**
* Returns a DateTimeInterface object representing the timestamp associated
* with the UUID
*
* @return DateTimeImmutable A PHP DateTimeImmutable instance representing
* the timestamp of a version 6 UUID
*/
public function getDateTime(): DateTimeInterface
{
$time = $this->timeConverter->convertTime($this->fields->getTimestamp());
try {
return new DateTimeImmutable(
'@'
. $time->getSeconds()->toString()
. '.'
. str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT)
);
} catch (Throwable $e) {
throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e);
}
}
/**
* Converts this UUID into an instance of a version 1 UUID
*/
@@ -116,7 +88,7 @@ final class UuidV6 extends Uuid implements UuidInterface
/**
* Converts a version 1 UUID into an instance of a version 6 UUID
*/
public static function fromUuidV1(UuidV1 $uuidV1): UuidV6
public static function fromUuidV1(UuidV1 $uuidV1): \Ramsey\Uuid\Rfc4122\UuidV6
{
$hex = $uuidV1->getHex()->toString();
$hex = substr($hex, 13, 3)

View File

@@ -21,7 +21,6 @@ use Ramsey\Uuid\Type\Integer as IntegerObject;
use function escapeshellarg;
use function preg_split;
use function str_getcsv;
use function strpos;
use function strrpos;
use function strtolower;
use function strtoupper;
@@ -42,6 +41,7 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
*/
public function getUid(): IntegerObject
{
/** @var int|float|string|IntegerObject|null $uid */
static $uid = null;
if ($uid instanceof IntegerObject) {
@@ -72,6 +72,7 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
*/
public function getGid(): IntegerObject
{
/** @var int|float|string|IntegerObject|null $gid */
static $gid = null;
if ($gid instanceof IntegerObject) {
@@ -104,15 +105,10 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
return '';
}
switch ($this->getOs()) {
case 'WIN':
return $this->getWindowsUid();
case 'DAR':
case 'FRE':
case 'LIN':
default:
return trim((string) shell_exec('id -u'));
}
return match ($this->getOs()) {
'WIN' => $this->getWindowsUid(),
default => trim((string) shell_exec('id -u')),
};
}
/**
@@ -124,15 +120,10 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
return '';
}
switch ($this->getOs()) {
case 'WIN':
return $this->getWindowsGid();
case 'DAR':
case 'FRE':
case 'LIN':
default:
return trim((string) shell_exec('id -g'));
}
return match ($this->getOs()) {
'WIN' => $this->getWindowsGid(),
default => trim((string) shell_exec('id -g')),
};
}
/**
@@ -142,7 +133,7 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
{
$disabledFunctions = strtolower((string) ini_get('disable_functions'));
return strpos($disabledFunctions, 'shell_exec') === false;
return !str_contains($disabledFunctions, 'shell_exec');
}
/**
@@ -150,7 +141,13 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
*/
private function getOs(): string
{
return strtoupper(substr(constant('PHP_OS'), 0, 3));
/**
* @psalm-suppress UnnecessaryVarAnnotation
* @var string $phpOs
*/
$phpOs = constant('PHP_OS');
return strtoupper(substr($phpOs, 0, 3));
}
/**
@@ -229,6 +226,6 @@ class SystemDceSecurityProvider implements DceSecurityProviderInterface
return '';
}
return trim((string) substr($sid, $lastHyphen + 1));
return trim(substr($sid, $lastHyphen + 1));
}
}

View File

@@ -25,23 +25,17 @@ use Ramsey\Uuid\Type\Hexadecimal;
class FallbackNodeProvider implements NodeProviderInterface
{
/**
* @var NodeProviderCollection
* @param iterable<NodeProviderInterface> $providers Array of node providers
*/
private $nodeProviders;
/**
* @param NodeProviderCollection $providers Array of node providers
*/
public function __construct(NodeProviderCollection $providers)
public function __construct(private iterable $providers)
{
$this->nodeProviders = $providers;
}
public function getNode(): Hexadecimal
{
$lastProviderException = null;
foreach ($this->nodeProviders as $provider) {
foreach ($this->providers as $provider) {
try {
return $provider->getNode();
} catch (NodeException $exception) {

View File

@@ -21,6 +21,11 @@ use Ramsey\Uuid\Type\Hexadecimal;
/**
* A collection of NodeProviderInterface objects
*
* @deprecated this class has been deprecated, and will be removed in 5.0.0. The use-case for this class comes from
* a pre-`phpstan/phpstan` and pre-`vimeo/psalm` ecosystem, in which type safety had to be mostly enforced
* at runtime: that is no longer necessary, now that you can safely verify your code to be correct, and use
* more generic types like `iterable<T>` instead.
*
* @extends AbstractCollection<NodeProviderInterface>
*/
class NodeProviderCollection extends AbstractCollection

View File

@@ -32,10 +32,7 @@ use const STR_PAD_LEFT;
*/
class StaticNodeProvider implements NodeProviderInterface
{
/**
* @var Hexadecimal
*/
private $node;
private Hexadecimal $node;
/**
* @param Hexadecimal $node The static node value to use

View File

@@ -27,8 +27,8 @@ use function ob_start;
use function preg_match;
use function preg_match_all;
use function reset;
use function str_contains;
use function str_replace;
use function strpos;
use function strtolower;
use function strtoupper;
use function substr;
@@ -100,12 +100,18 @@ class SystemNodeProvider implements NodeProviderInterface
{
$disabledFunctions = strtolower((string) ini_get('disable_functions'));
if (strpos($disabledFunctions, 'passthru') !== false) {
if (str_contains($disabledFunctions, 'passthru')) {
return '';
}
/**
* @psalm-suppress UnnecessaryVarAnnotation
* @var string $phpOs
*/
$phpOs = constant('PHP_OS');
ob_start();
switch (strtoupper(substr(constant('PHP_OS'), 0, 3))) {
switch (strtoupper(substr($phpOs, 0, 3))) {
case 'WIN':
passthru('ipconfig /all 2>&1');
@@ -127,12 +133,15 @@ class SystemNodeProvider implements NodeProviderInterface
$ifconfig = (string) ob_get_clean();
$node = '';
if (preg_match_all(self::IFCONFIG_PATTERN, $ifconfig, $matches, PREG_PATTERN_ORDER)) {
$node = $matches[1][0] ?? '';
foreach ($matches[1] as $iface) {
if ($iface !== '00:00:00:00:00:00' && $iface !== '00-00-00-00-00-00') {
return $iface;
}
}
}
return $node;
return '';
}
/**
@@ -142,13 +151,20 @@ class SystemNodeProvider implements NodeProviderInterface
{
$mac = '';
if (strtoupper(constant('PHP_OS')) === 'LINUX') {
/**
* @psalm-suppress UnnecessaryVarAnnotation
* @var string $phpOs
*/
$phpOs = constant('PHP_OS');
if (strtoupper($phpOs) === 'LINUX') {
$addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT);
if ($addressPaths === false || count($addressPaths) === 0) {
return '';
}
/** @var array<array-key, string> $macs */
$macs = [];
array_walk($addressPaths, function (string $addressPath) use (&$macs): void {
@@ -157,7 +173,10 @@ class SystemNodeProvider implements NodeProviderInterface
}
});
$macs = array_map('trim', $macs);
/** @var callable $trim */
$trim = 'trim';
$macs = array_map($trim, $macs);
// Remove invalid entries.
$macs = array_filter($macs, function (string $address) {
@@ -165,6 +184,7 @@ class SystemNodeProvider implements NodeProviderInterface
&& preg_match(self::SYSFS_PATTERN, $address);
});
/** @var string|bool $mac */
$mac = reset($macs);
}

View File

@@ -19,21 +19,15 @@ use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Type\Time;
/**
* FixedTimeProvider uses an known time to provide the time
* FixedTimeProvider uses a known time to provide the time
*
* This provider allows the use of a previously-generated, or known, time
* when generating time-based UUIDs.
*/
class FixedTimeProvider implements TimeProviderInterface
{
/**
* @var Time
*/
private $fixedTime;
public function __construct(Time $time)
public function __construct(private Time $time)
{
$this->fixedTime = $time;
}
/**
@@ -43,7 +37,7 @@ class FixedTimeProvider implements TimeProviderInterface
*/
public function setUsec($value): void
{
$this->fixedTime = new Time($this->fixedTime->getSeconds(), $value);
$this->time = new Time($this->time->getSeconds(), $value);
}
/**
@@ -53,11 +47,11 @@ class FixedTimeProvider implements TimeProviderInterface
*/
public function setSec($value): void
{
$this->fixedTime = new Time($value, $this->fixedTime->getMicroseconds());
$this->time = new Time($value, $this->time->getMicroseconds());
}
public function getTime(): Time
{
return $this->fixedTime;
return $this->time;
}
}

View File

@@ -40,16 +40,12 @@ use const STR_PAD_LEFT;
*/
final class Fields implements FieldsInterface
{
use MaxTrait;
use NilTrait;
use SerializableFieldsTrait;
use VariantTrait;
use VersionTrait;
/**
* @var string
*/
private $bytes;
/**
* @param string $bytes A 16-byte binary string representation of a UUID
*
@@ -57,17 +53,15 @@ final class Fields implements FieldsInterface
* @throws InvalidArgumentException if the byte string does not represent an RFC 4122 UUID
* @throws InvalidArgumentException if the byte string does not contain a valid version
*/
public function __construct(string $bytes)
public function __construct(private string $bytes)
{
if (strlen($bytes) !== 16) {
if (strlen($this->bytes) !== 16) {
throw new InvalidArgumentException(
'The byte string must be 16 bytes long; '
. 'received ' . strlen($bytes) . ' bytes'
. 'received ' . strlen($this->bytes) . ' bytes'
);
}
$this->bytes = $bytes;
if (!$this->isCorrectVariant()) {
throw new InvalidArgumentException(
'The byte string received does not conform to the RFC 4122 variant'
@@ -88,7 +82,13 @@ final class Fields implements FieldsInterface
public function getClockSeq(): Hexadecimal
{
$clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
if ($this->isMax()) {
$clockSeq = 0xffff;
} elseif ($this->isNil()) {
$clockSeq = 0x0000;
} else {
$clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
}
return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT));
}
@@ -140,52 +140,53 @@ final class Fields implements FieldsInterface
*/
public function getTimestamp(): Hexadecimal
{
switch ($this->getVersion()) {
case Uuid::UUID_TYPE_DCE_SECURITY:
$timestamp = sprintf(
'%03x%04s%08s',
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
$this->getTimeMid()->toString(),
''
);
break;
case Uuid::UUID_TYPE_PEABODY:
$timestamp = sprintf(
'%08s%04s%03x',
$this->getTimeLow()->toString(),
$this->getTimeMid()->toString(),
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff
);
break;
default:
$timestamp = sprintf(
'%03x%04s%08s',
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
$this->getTimeMid()->toString(),
$this->getTimeLow()->toString()
);
}
$timestamp = match ($this->getVersion()) {
Uuid::UUID_TYPE_DCE_SECURITY => sprintf(
'%03x%04s%08s',
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
$this->getTimeMid()->toString(),
''
),
Uuid::UUID_TYPE_REORDERED_TIME => sprintf(
'%08s%04s%03x',
$this->getTimeLow()->toString(),
$this->getTimeMid()->toString(),
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff
),
// The Unix timestamp in version 7 UUIDs is a 48-bit number,
// but for consistency, we will return a 60-bit number, padded
// to the left with zeros.
Uuid::UUID_TYPE_UNIX_TIME => sprintf(
'%011s%04s',
$this->getTimeLow()->toString(),
$this->getTimeMid()->toString(),
),
default => sprintf(
'%03x%04s%08s',
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
$this->getTimeMid()->toString(),
$this->getTimeLow()->toString()
),
};
return new Hexadecimal($timestamp);
}
public function getVersion(): ?int
{
if ($this->isNil()) {
if ($this->isNil() || $this->isMax()) {
return null;
}
/** @var array $parts */
/** @var int[] $parts */
$parts = unpack('n*', $this->bytes);
return (int) $parts[4] >> 12;
return $parts[4] >> 12;
}
private function isCorrectVariant(): bool
{
if ($this->isNil()) {
if ($this->isNil() || $this->isMax()) {
return true;
}

View File

@@ -103,11 +103,13 @@ interface FieldsInterface extends BaseFieldsInterface
* The version number describes how the UUID was generated and has the
* following meaning:
*
* 1. Time-based UUID
* 1. Gregorian time UUID
* 2. DCE security UUID
* 3. Name-based UUID hashed with MD5
* 4. Randomly generated UUID
* 5. Name-based UUID hashed with SHA-1
* 6. Reordered time UUID
* 7. Unix Epoch time UUID
*
* This returns `null` if the UUID is not an RFC 4122 variant, since version
* is only meaningful for this variant.

View File

@@ -0,0 +1,41 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Rfc4122;
/**
* Provides common functionality for max UUIDs
*
* The max UUID is special form of UUID that is specified to have all 128 bits
* set to one. It is the inverse of the nil UUID.
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.10 Max UUID
*
* @psalm-immutable
*/
trait MaxTrait
{
/**
* Returns the bytes that comprise the fields
*/
abstract public function getBytes(): string;
/**
* Returns true if the byte string represents a max UUID
*/
public function isMax(): bool
{
return $this->getBytes() === "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Rfc4122;
use Ramsey\Uuid\Uuid;
/**
* The max UUID is special form of UUID that is specified to have all 128 bits
* set to one
*
* @psalm-immutable
*/
final class MaxUuid extends Uuid implements UuidInterface
{
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Rfc4122;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Exception\DateTimeException;
use Throwable;
use function str_pad;
use const STR_PAD_LEFT;
/**
* Provides common functionality for getting the time from a time-based UUID
*
* @psalm-immutable
*/
trait TimeTrait
{
/**
* Returns a DateTimeInterface object representing the timestamp associated
* with the UUID
*
* @return DateTimeImmutable A PHP DateTimeImmutable instance representing
* the timestamp of a time-based UUID
*/
public function getDateTime(): DateTimeInterface
{
$time = $this->timeConverter->convertTime($this->fields->getTimestamp());
try {
return new DateTimeImmutable(
'@'
. $time->getSeconds()->toString()
. '.'
. str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT)
);
} catch (Throwable $e) {
throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e);
}
}
}

View File

@@ -17,11 +17,13 @@ namespace Ramsey\Uuid\Rfc4122;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\UnixTimeConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Nonstandard\UuidV6;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Rfc4122\UuidInterface as Rfc4122UuidInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Throwable;
@@ -32,15 +34,7 @@ use Throwable;
*/
class UuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
private TimeConverterInterface $unixTimeConverter;
/**
* Constructs the DefaultUuidBuilder
@@ -48,14 +42,18 @@ class UuidBuilder implements UuidBuilderInterface
* @param NumberConverterInterface $numberConverter The number converter to
* use when constructing the Uuid
* @param TimeConverterInterface $timeConverter The time converter to use
* for converting timestamps extracted from a UUID to Unix timestamps
* for converting Gregorian time extracted from version 1, 2, and 6
* UUIDs to Unix timestamps
* @param TimeConverterInterface|null $unixTimeConverter The time converter
* to use for converter Unix Epoch time extracted from version 7 UUIDs
* to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
TimeConverterInterface $timeConverter
private NumberConverterInterface $numberConverter,
private TimeConverterInterface $timeConverter,
?TimeConverterInterface $unixTimeConverter = null
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter;
$this->unixTimeConverter = $unixTimeConverter ?? new UnixTimeConverter(new BrickMathCalculator());
}
/**
@@ -71,25 +69,34 @@ class UuidBuilder implements UuidBuilderInterface
public function build(CodecInterface $codec, string $bytes): UuidInterface
{
try {
/** @var Fields $fields */
$fields = $this->buildFields($bytes);
if ($fields->isNil()) {
return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter);
}
if ($fields->isMax()) {
return new MaxUuid($fields, $this->numberConverter, $codec, $this->timeConverter);
}
switch ($fields->getVersion()) {
case 1:
case Uuid::UUID_TYPE_TIME:
return new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter);
case 2:
case Uuid::UUID_TYPE_DCE_SECURITY:
return new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter);
case 3:
case Uuid::UUID_TYPE_HASH_MD5:
return new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter);
case 4:
case Uuid::UUID_TYPE_RANDOM:
return new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter);
case 5:
case Uuid::UUID_TYPE_HASH_SHA1:
return new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter);
case 6:
case Uuid::UUID_TYPE_REORDERED_TIME:
return new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter);
case Uuid::UUID_TYPE_UNIX_TIME:
return new UuidV7($fields, $this->numberConverter, $codec, $this->unixTimeConverter);
case Uuid::UUID_TYPE_CUSTOM:
return new UuidV8($fields, $this->numberConverter, $codec, $this->timeConverter);
}
throw new UnsupportedOperationException(

View File

@@ -26,11 +26,4 @@ use Ramsey\Uuid\UuidInterface as BaseUuidInterface;
*/
interface UuidInterface extends BaseUuidInterface
{
/**
* Returns the string standard representation of the UUID as a URN
*
* @link http://en.wikipedia.org/wiki/Uniform_Resource_Name Uniform Resource Name
* @link https://tools.ietf.org/html/rfc4122#section-3 RFC 4122, § 3: Namespace Registration Template
*/
public function getUrn(): string;
}

View File

@@ -14,31 +14,25 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Rfc4122;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\DateTimeException;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Uuid;
use Throwable;
use function str_pad;
use const STR_PAD_LEFT;
/**
* Time-based, or version 1, UUIDs include timestamp, clock sequence, and node
* Gregorian time, or version 1, UUIDs include timestamp, clock sequence, and node
* values that are combined into a 128-bit unsigned integer
*
* @psalm-immutable
*/
final class UuidV1 extends Uuid implements UuidInterface
{
use TimeTrait;
/**
* Creates a version 1 (time-based) UUID
* Creates a version 1 (Gregorian time) UUID
*
* @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID
* @param NumberConverterInterface $numberConverter The number converter to use
@@ -63,30 +57,4 @@ final class UuidV1 extends Uuid implements UuidInterface
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
/**
* Returns a DateTimeInterface object representing the timestamp associated
* with the UUID
*
* The timestamp value is only meaningful in a time-based UUID, which
* has version type 1.
*
* @return DateTimeImmutable A PHP DateTimeImmutable instance representing
* the timestamp of a version 1 UUID
*/
public function getDateTime(): DateTimeInterface
{
$time = $this->timeConverter->convertTime($this->fields->getTimestamp());
try {
return new DateTimeImmutable(
'@'
. $time->getSeconds()->toString()
. '.'
. str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT)
);
} catch (Throwable $e) {
throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e);
}
}
}

View File

@@ -14,28 +14,33 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Rfc4122;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\DateTimeException;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Uuid;
use Throwable;
use function hexdec;
use function str_pad;
use const STR_PAD_LEFT;
/**
* DCE Security version, or version 2, UUIDs include local domain identifier,
* local ID for the specified domain, and node values that are combined into a
* 128-bit unsigned integer
*
* It is important to note that a version 2 UUID suffers from some loss of
* fidelity of the timestamp, due to replacing the time_low field with the
* local identifier. When constructing the timestamp value for date
* purposes, we replace the local identifier bits with zeros. As a result,
* the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7
* minutes, 9 seconds, and 496730 microseconds).
*
* Astute observers might note this value directly corresponds to 2^32 - 1,
* or 0xffffffff. The local identifier is 32-bits, and we have set each of
* these bits to 0, so the maximum range of timestamp drift is 0x00000000
* to 0xffffffff (counted in 100-nanosecond intervals).
*
* @link https://publications.opengroup.org/c311 DCE 1.1: Authentication and Security Services
* @link https://publications.opengroup.org/c706 DCE 1.1: Remote Procedure Call
* @link https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 DCE 1.1: Auth & Sec, §5.2.1.1
@@ -47,6 +52,8 @@ use const STR_PAD_LEFT;
*/
final class UuidV2 extends Uuid implements UuidInterface
{
use TimeTrait;
/**
* Creates a version 2 (DCE Security) UUID
*
@@ -74,41 +81,6 @@ final class UuidV2 extends Uuid implements UuidInterface
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
/**
* Returns a DateTimeInterface object representing the timestamp associated
* with the UUID
*
* It is important to note that a version 2 UUID suffers from some loss of
* fidelity of the timestamp, due to replacing the time_low field with the
* local identifier. When constructing the timestamp value for date
* purposes, we replace the local identifier bits with zeros. As a result,
* the timestamp can be off by a range of 0 to 429.4967295 seconds (or 7
* minutes, 9 seconds, and 496730 microseconds).
*
* Astute observers might note this value directly corresponds to 2^32 - 1,
* or 0xffffffff. The local identifier is 32-bits, and we have set each of
* these bits to 0, so the maximum range of timestamp drift is 0x00000000
* to 0xffffffff (counted in 100-nanosecond intervals).
*
* @return DateTimeImmutable A PHP DateTimeImmutable instance representing
* the timestamp of a version 2 UUID
*/
public function getDateTime(): DateTimeInterface
{
$time = $this->timeConverter->convertTime($this->fields->getTimestamp());
try {
return new DateTimeImmutable(
'@'
. $time->getSeconds()->toString()
. '.'
. str_pad($time->getMicroseconds()->toString(), 6, '0', STR_PAD_LEFT)
);
} catch (Throwable $e) {
throw new DateTimeException($e->getMessage(), (int) $e->getCode(), $e);
}
}
/**
* Returns the local domain used to create this version 2 UUID
*/

View File

@@ -0,0 +1,29 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Rfc4122;
use Ramsey\Uuid\Nonstandard\UuidV6 as NonstandardUuidV6;
/**
* Reordered time, or version 6, UUIDs include timestamp, clock sequence, and
* node values that are combined into a 128-bit unsigned integer
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.6 UUID Version 6
*
* @psalm-immutable
*/
final class UuidV6 extends NonstandardUuidV6 implements UuidInterface
{
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Rfc4122;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Uuid;
/**
* Unix Epoch time, or version 7, UUIDs include a timestamp in milliseconds
* since the Unix Epoch, along with random bytes
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.7 UUID Version 7
*
* @psalm-immutable
*/
final class UuidV7 extends Uuid implements UuidInterface
{
use TimeTrait;
/**
* Creates a version 7 (Unix Epoch time) UUID
*
* @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID
* @param NumberConverterInterface $numberConverter The number converter to use
* for converting hex values to/from integers
* @param CodecInterface $codec The codec to use when encoding or decoding
* UUID strings
* @param TimeConverterInterface $timeConverter The time converter to use
* for converting timestamps extracted from a UUID to unix timestamps
*/
public function __construct(
Rfc4122FieldsInterface $fields,
NumberConverterInterface $numberConverter,
CodecInterface $codec,
TimeConverterInterface $timeConverter
) {
if ($fields->getVersion() !== Uuid::UUID_TYPE_UNIX_TIME) {
throw new InvalidArgumentException(
'Fields used to create a UuidV7 must represent a '
. 'version 7 (Unix Epoch time) UUID'
);
}
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* This file is part of the ramsey/uuid 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\Uuid\Rfc4122;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Uuid;
/**
* Version 8, Custom UUIDs provide an RFC 4122 compatible format for
* experimental or vendor-specific uses
*
* The only requirement for version 8 UUIDs is that the version and variant bits
* must be set. Otherwise, implementations are free to set the other bits
* according to their needs. As a result, the uniqueness of version 8 UUIDs is
* implementation-specific and should not be assumed.
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.8 UUID Version 8
*
* @psalm-immutable
*/
final class UuidV8 extends Uuid implements UuidInterface
{
/**
* Creates a version 8 (custom) UUID
*
* @param Rfc4122FieldsInterface $fields The fields from which to construct a UUID
* @param NumberConverterInterface $numberConverter The number converter to use
* for converting hex values to/from integers
* @param CodecInterface $codec The codec to use when encoding or decoding
* UUID strings
* @param TimeConverterInterface $timeConverter The time converter to use
* for converting timestamps extracted from a UUID to unix timestamps
*/
public function __construct(
Rfc4122FieldsInterface $fields,
NumberConverterInterface $numberConverter,
CodecInterface $codec,
TimeConverterInterface $timeConverter
) {
if ($fields->getVersion() !== Uuid::UUID_TYPE_CUSTOM) {
throw new InvalidArgumentException(
'Fields used to create a UuidV8 must represent a '
. 'version 8 (custom) UUID'
);
}
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
}

View File

@@ -28,7 +28,7 @@ use function str_replace;
final class Validator implements ValidatorInterface
{
private const VALID_PATTERN = '\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-'
. '[1-5]{1}[0-9A-Fa-f]{3}-[ABab89]{1}[0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\z';
. '[1-8][0-9A-Fa-f]{3}-[ABab89][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\z';
/**
* @psalm-return non-empty-string
@@ -43,7 +43,8 @@ final class Validator implements ValidatorInterface
public function validate(string $uuid): bool
{
$uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid);
$uuid = strtolower($uuid);
return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid);
return $uuid === Uuid::NIL || $uuid === Uuid::MAX || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid);
}
}

View File

@@ -19,8 +19,8 @@ use Ramsey\Uuid\Uuid;
use function decbin;
use function str_pad;
use function str_starts_with;
use function strlen;
use function strpos;
use function substr;
use function unpack;
@@ -58,7 +58,13 @@ trait VariantTrait
throw new InvalidBytesException('Invalid number of bytes');
}
/** @var array $parts */
if ($this->isMax() || $this->isNil()) {
// RFC 4122 defines these special types of UUID, so we will consider
// them as belonging to the RFC 4122 variant.
return Uuid::RFC_4122;
}
/** @var int[] $parts */
$parts = unpack('n*', $this->getBytes());
// $parts[5] is a 16-bit, unsigned integer containing the variant bits
@@ -67,7 +73,7 @@ trait VariantTrait
// three characters (three most-significant bits) to determine the
// variant.
$binary = str_pad(
decbin((int) $parts[5]),
decbin($parts[5]),
16,
'0',
STR_PAD_LEFT
@@ -76,15 +82,13 @@ trait VariantTrait
$msb = substr($binary, 0, 3);
if ($msb === '111') {
$variant = Uuid::RESERVED_FUTURE;
return Uuid::RESERVED_FUTURE;
} elseif ($msb === '110') {
$variant = Uuid::RESERVED_MICROSOFT;
} elseif (strpos($msb, '10') === 0) {
$variant = Uuid::RFC_4122;
} else {
$variant = Uuid::RESERVED_NCS;
return Uuid::RESERVED_MICROSOFT;
} elseif (str_starts_with($msb, '10')) {
return Uuid::RFC_4122;
}
return $variant;
return Uuid::RESERVED_NCS;
}
}

View File

@@ -14,6 +14,8 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Rfc4122;
use Ramsey\Uuid\Uuid;
/**
* Provides common functionality for handling the version, as defined by RFC 4122
*
@@ -26,6 +28,11 @@ trait VersionTrait
*/
abstract public function getVersion(): ?int;
/**
* Returns true if these fields represent a max UUID
*/
abstract public function isMax(): bool;
/**
* Returns true if these fields represent a nil UUID
*/
@@ -38,20 +45,16 @@ trait VersionTrait
*/
private function isCorrectVersion(): bool
{
if ($this->isNil()) {
if ($this->isNil() || $this->isMax()) {
return true;
}
switch ($this->getVersion()) {
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
return true;
}
return false;
return match ($this->getVersion()) {
Uuid::UUID_TYPE_TIME, Uuid::UUID_TYPE_DCE_SECURITY,
Uuid::UUID_TYPE_HASH_MD5, Uuid::UUID_TYPE_RANDOM,
Uuid::UUID_TYPE_HASH_SHA1, Uuid::UUID_TYPE_REORDERED_TIME,
Uuid::UUID_TYPE_UNIX_TIME, Uuid::UUID_TYPE_CUSTOM => true,
default => false,
};
}
}

View File

@@ -19,6 +19,7 @@ use ValueError;
use function is_numeric;
use function sprintf;
use function str_starts_with;
/**
* A value object representing a decimal
@@ -34,20 +35,10 @@ use function sprintf;
*/
final class Decimal implements NumberInterface
{
/**
* @var string
*/
private $value;
private string $value;
private bool $isNegative = false;
/**
* @var bool
*/
private $isNegative = false;
/**
* @param mixed $value The decimal value to store
*/
public function __construct($value)
public function __construct(float | int | string | self $value)
{
$value = (string) $value;
@@ -59,7 +50,7 @@ final class Decimal implements NumberInterface
}
// Remove the leading +-symbol.
if (strpos($value, '+') === 0) {
if (str_starts_with($value, '+')) {
$value = substr($value, 1);
}
@@ -68,7 +59,7 @@ final class Decimal implements NumberInterface
$value = '0';
}
if (strpos($value, '-') === 0) {
if (str_starts_with($value, '-')) {
$this->isNegative = true;
}
@@ -111,18 +102,19 @@ final class Decimal implements NumberInterface
/**
* Constructs the object from a serialized string representation
*
* @param string $serialized The serialized string representation of the object
* @param string $data The serialized string representation of the object
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress UnusedMethodCall
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
$this->__construct($serialized);
$this->__construct($data);
}
/**
* @param array{string: string} $data
* @param array{string?: string} $data
*
* @psalm-suppress UnusedMethodCall
*/
public function __unserialize(array $data): void
{

View File

@@ -17,10 +17,8 @@ namespace Ramsey\Uuid\Type;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use ValueError;
use function ctype_xdigit;
use function preg_match;
use function sprintf;
use function strpos;
use function strtolower;
use function substr;
/**
@@ -34,29 +32,14 @@ use function substr;
*/
final class Hexadecimal implements TypeInterface
{
/**
* @var string
*/
private $value;
private string $value;
/**
* @param string $value The hexadecimal value to store
* @param self|string $value The hexadecimal value to store
*/
public function __construct(string $value)
public function __construct(self | string $value)
{
$value = strtolower($value);
if (strpos($value, '0x') === 0) {
$value = substr($value, 2);
}
if (!ctype_xdigit($value)) {
throw new InvalidArgumentException(
'Value must be a hexadecimal number'
);
}
$this->value = $value;
$this->value = $value instanceof self ? (string) $value : $this->prepareValue($value);
}
public function toString(): string
@@ -90,18 +73,17 @@ final class Hexadecimal implements TypeInterface
/**
* Constructs the object from a serialized string representation
*
* @param string $serialized The serialized string representation of the object
* @param string $data The serialized string representation of the object
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress UnusedMethodCall
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
$this->__construct($serialized);
$this->__construct($data);
}
/**
* @param array{string: string} $data
* @param array{string?: string} $data
*/
public function __unserialize(array $data): void
{
@@ -113,4 +95,21 @@ final class Hexadecimal implements TypeInterface
$this->unserialize($data['string']);
}
private function prepareValue(string $value): string
{
$value = strtolower($value);
if (str_starts_with($value, '0x')) {
$value = substr($value, 2);
}
if (!preg_match('/^[A-Fa-f0-9]+$/', $value)) {
throw new InvalidArgumentException(
'Value must be a hexadecimal number'
);
}
return $value;
}
}

View File

@@ -17,10 +17,10 @@ namespace Ramsey\Uuid\Type;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use ValueError;
use function ctype_digit;
use function ltrim;
use function assert;
use function is_numeric;
use function preg_match;
use function sprintf;
use function strpos;
use function substr;
/**
@@ -40,52 +40,13 @@ final class Integer implements NumberInterface
/**
* @psalm-var numeric-string
*/
private $value;
private string $value;
/**
* @var bool
*/
private $isNegative = false;
private bool $isNegative = false;
/**
* @param mixed $value The integer value to store
*/
public function __construct($value)
public function __construct(float | int | string | self $value)
{
$value = (string) $value;
$sign = '+';
// If the value contains a sign, remove it for ctype_digit() check.
if (strpos($value, '-') === 0 || strpos($value, '+') === 0) {
$sign = substr($value, 0, 1);
$value = substr($value, 1);
}
if (!ctype_digit($value)) {
throw new InvalidArgumentException(
'Value must be a signed integer or a string containing only '
. 'digits 0-9 and, optionally, a sign (+ or -)'
);
}
// Trim any leading zeros.
$value = ltrim($value, '0');
// Set to zero if the string is empty after trimming zeros.
if ($value === '') {
$value = '0';
}
// Add the negative sign back to the value.
if ($sign === '-' && $value !== '0') {
$value = $sign . $value;
$this->isNegative = true;
}
/** @psalm-var numeric-string $numericValue */
$numericValue = $value;
$this->value = $numericValue;
$this->value = $value instanceof self ? (string) $value : $this->prepareValue($value);
}
public function isNegative(): bool
@@ -101,6 +62,9 @@ final class Integer implements NumberInterface
return $this->value;
}
/**
* @psalm-return numeric-string
*/
public function __toString(): string
{
return $this->toString();
@@ -127,18 +91,17 @@ final class Integer implements NumberInterface
/**
* Constructs the object from a serialized string representation
*
* @param string $serialized The serialized string representation of the object
* @param string $data The serialized string representation of the object
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress UnusedMethodCall
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
$this->__construct($serialized);
$this->__construct($data);
}
/**
* @param array{string: string} $data
* @param array{string?: string} $data
*/
public function __unserialize(array $data): void
{
@@ -150,4 +113,46 @@ final class Integer implements NumberInterface
$this->unserialize($data['string']);
}
/**
* @return numeric-string
*/
private function prepareValue(float | int | string $value): string
{
$value = (string) $value;
$sign = '+';
// If the value contains a sign, remove it for digit pattern check.
if (str_starts_with($value, '-') || str_starts_with($value, '+')) {
$sign = substr($value, 0, 1);
$value = substr($value, 1);
}
if (!preg_match('/^\d+$/', $value)) {
throw new InvalidArgumentException(
'Value must be a signed integer or a string containing only '
. 'digits 0-9 and, optionally, a sign (+ or -)'
);
}
// Trim any leading zeros.
$value = ltrim($value, '0');
// Set to zero if the string is empty after trimming zeros.
if ($value === '') {
$value = '0';
}
// Add the negative sign back to the value.
if ($sign === '-' && $value !== '0') {
$value = $sign . $value;
/** @psalm-suppress InaccessibleProperty */
$this->isNegative = true;
}
assert(is_numeric($value));
return $value;
}
}

View File

@@ -17,7 +17,6 @@ namespace Ramsey\Uuid\Type;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use ValueError;
use stdClass;
use function json_decode;
use function json_encode;
@@ -34,22 +33,13 @@ use function sprintf;
*/
final class Time implements TypeInterface
{
/**
* @var IntegerObject
*/
private $seconds;
private IntegerObject $seconds;
private IntegerObject $microseconds;
/**
* @var IntegerObject
*/
private $microseconds;
/**
* @param mixed $seconds
* @param mixed $microseconds
*/
public function __construct($seconds, $microseconds = 0)
{
public function __construct(
float | int | string | IntegerObject $seconds,
float | int | string | IntegerObject $microseconds = 0,
) {
$this->seconds = new IntegerObject($seconds);
$this->microseconds = new IntegerObject($microseconds);
}
@@ -66,7 +56,7 @@ final class Time implements TypeInterface
public function toString(): string
{
return $this->seconds->toString() . '.' . $this->microseconds->toString();
return $this->seconds->toString() . '.' . sprintf('%06s', $this->microseconds->toString());
}
public function __toString(): string
@@ -104,27 +94,26 @@ final class Time implements TypeInterface
/**
* Constructs the object from a serialized string representation
*
* @param string $serialized The serialized string representation of the object
* @param string $data The serialized string representation of the object
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress UnusedMethodCall
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
/** @var stdClass $time */
$time = json_decode($serialized);
/** @var array{seconds?: int|float|string, microseconds?: int|float|string} $time */
$time = json_decode($data, true);
if (!isset($time->seconds) || !isset($time->microseconds)) {
if (!isset($time['seconds']) || !isset($time['microseconds'])) {
throw new UnsupportedOperationException(
'Attempted to unserialize an invalid value'
);
}
$this->__construct($time->seconds, $time->microseconds);
$this->__construct($time['seconds'], $time['microseconds']);
}
/**
* @param array{seconds: string, microseconds: string} $data
* @param array{seconds?: string, microseconds?: string} $data
*/
public function __unserialize(array $data): void
{

View File

@@ -14,10 +14,12 @@ declare(strict_types=1);
namespace Ramsey\Uuid;
use BadMethodCallException;
use DateTimeInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Fields\FieldsInterface;
use Ramsey\Uuid\Lazy\LazyUuidFromString;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
@@ -27,6 +29,7 @@ use ValueError;
use function assert;
use function bin2hex;
use function method_exists;
use function preg_match;
use function sprintf;
use function str_replace;
@@ -82,6 +85,14 @@ class Uuid implements UuidInterface
*/
public const NIL = '00000000-0000-0000-0000-000000000000';
/**
* The max UUID is a special form of UUID that is specified to have all 128
* bits set to one
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.10 Max UUID
*/
public const MAX = 'ffffffff-ffff-ffff-ffff-ffffffffffff';
/**
* Variant: reserved, NCS backward compatibility
*
@@ -116,7 +127,7 @@ class Uuid implements UuidInterface
public const VALID_PATTERN = '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$';
/**
* Version 1 (time-based) UUID
* Version 1 (Gregorian time) UUID
*
* @link https://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version
*/
@@ -156,16 +167,29 @@ class Uuid implements UuidInterface
public const UUID_TYPE_HASH_SHA1 = 5;
/**
* Version 6 (ordered-time) UUID
*
* This is named `UUID_TYPE_PEABODY`, since the specification is still in
* draft form, and the primary author/editor's name is Brad Peabody.
*
* @link https://github.com/uuid6/uuid6-ietf-draft UUID version 6 IETF draft
* @link http://gh.peabody.io/uuidv6/ "Version 6" UUIDs
* @deprecated Use {@see Uuid::UUID_TYPE_REORDERED_TIME} instead.
*/
public const UUID_TYPE_PEABODY = 6;
/**
* Version 6 (reordered time) UUID
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.6 UUID Version 6
*/
public const UUID_TYPE_REORDERED_TIME = 6;
/**
* Version 7 (Unix Epoch time) UUID
*
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.7 UUID Version 7
*/
public const UUID_TYPE_UNIX_TIME = 7;
/**
* @link https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis-00#section-5.8 UUID Version 8
*/
public const UUID_TYPE_CUSTOM = 8;
/**
* DCE Security principal domain
*
@@ -198,38 +222,19 @@ class Uuid implements UuidInterface
self::DCE_DOMAIN_ORG => 'org',
];
/**
* @var UuidFactoryInterface|null
*/
private static $factory = null;
private static ?UuidFactoryInterface $factory = null;
/**
* @var bool flag to detect if the UUID factory was replaced internally, which disables all optimizations
* for the default/happy path internal scenarios
* @var bool flag to detect if the UUID factory was replaced internally,
* which disables all optimizations for the default/happy path internal
* scenarios
*/
private static $factoryReplaced = false;
private static bool $factoryReplaced = false;
/**
* @var CodecInterface
*/
protected $codec;
/**
* The fields that make up this UUID
*
* @var Rfc4122FieldsInterface
*/
protected $fields;
/**
* @var NumberConverterInterface
*/
protected $numberConverter;
/**
* @var TimeConverterInterface
*/
protected $timeConverter;
protected CodecInterface $codec;
protected NumberConverterInterface $numberConverter;
protected Rfc4122FieldsInterface $fields;
protected TimeConverterInterface $timeConverter;
/**
* Creates a universally unique identifier (UUID) from an array of fields
@@ -302,19 +307,17 @@ class Uuid implements UuidInterface
/**
* Re-constructs the object from its serialized form
*
* @param string $serialized The serialized PHP string to unserialize into
* @param string $data The serialized PHP string to unserialize into
* a UuidInterface instance
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
public function unserialize($serialized): void
public function unserialize(string $data): void
{
if (strlen($serialized) === 16) {
if (strlen($data) === 16) {
/** @var Uuid $uuid */
$uuid = self::getFactory()->fromBytes($serialized);
$uuid = self::getFactory()->fromBytes($data);
} else {
/** @var Uuid $uuid */
$uuid = self::getFactory()->fromString($serialized);
$uuid = self::getFactory()->fromString($data);
}
$this->codec = $uuid->codec;
@@ -324,7 +327,7 @@ class Uuid implements UuidInterface
}
/**
* @param array{bytes: string} $data
* @param array{bytes?: string} $data
*/
public function __unserialize(array $data): void
{
@@ -384,6 +387,11 @@ class Uuid implements UuidInterface
return new IntegerObject($this->numberConverter->fromHex($this->getHex()->toString()));
}
public function getUrn(): string
{
return 'urn:uuid:' . $this->toString();
}
/**
* @psalm-return non-empty-string
*/
@@ -476,10 +484,11 @@ class Uuid implements UuidInterface
*/
public static function fromString(string $uuid): UuidInterface
{
$uuid = strtolower($uuid);
if (! self::$factoryReplaced && preg_match(LazyUuidFromString::VALID_REGEX, $uuid) === 1) {
assert($uuid !== '');
return new LazyUuidFromString(strtolower($uuid));
return new LazyUuidFromString($uuid);
}
return self::getFactory()->fromString($uuid);
@@ -506,6 +515,33 @@ class Uuid implements UuidInterface
return self::getFactory()->fromDateTime($dateTime, $node, $clockSeq);
}
/**
* Creates a UUID from the Hexadecimal object
*
* @param Hexadecimal $hex Hexadecimal object representing a hexadecimal number
*
* @return UuidInterface A UuidInterface instance created from the Hexadecimal
* object representing a hexadecimal number
*
* @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants,
* but under constant factory setups, this method operates in functionally pure manners
* @psalm-suppress MixedInferredReturnType,MixedReturnStatement
*/
public static function fromHexadecimal(Hexadecimal $hex): UuidInterface
{
$factory = self::getFactory();
if (method_exists($factory, 'fromHexadecimal')) {
/**
* @phpstan-ignore-next-line
* @psalm-suppress UndefinedInterfaceMethod
*/
return self::getFactory()->fromHexadecimal($hex);
}
throw new BadMethodCallException('The method fromHexadecimal() does not exist on the provided factory');
}
/**
* Creates a UUID from a 128-bit integer string
*
@@ -519,6 +555,7 @@ class Uuid implements UuidInterface
*/
public static function fromInteger(string $integer): UuidInterface
{
/** @psalm-suppress ImpureMethodCall */
return self::getFactory()->fromInteger($integer);
}
@@ -531,20 +568,23 @@ class Uuid implements UuidInterface
*
* @psalm-pure note: changing the internal factory is an edge case not covered by purity invariants,
* but under constant factory setups, this method operates in functionally pure manners
*
* @psalm-assert-if-true non-empty-string $uuid
*/
public static function isValid(string $uuid): bool
{
/** @psalm-suppress ImpureMethodCall */
return self::getFactory()->getValidator()->validate($uuid);
}
/**
* Returns a version 1 (time-based) UUID from a host ID, sequence number,
* Returns a version 1 (Gregorian time) UUID from a host ID, sequence number,
* and the current time
*
* @param Hexadecimal|int|string|null $node A 48-bit number representing the
* hardware address; this number may be represented as an integer or a
* hexadecimal string
* @param int $clockSeq A 14-bit number used to help avoid duplicates that
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates that
* could arise when the clock is set backwards in time or if the node ID
* changes
*
@@ -643,12 +683,12 @@ class Uuid implements UuidInterface
}
/**
* Returns a version 6 (ordered-time) UUID from a host ID, sequence number,
* Returns a version 6 (reordered time) UUID from a host ID, sequence number,
* and the current time
*
* @param Hexadecimal|null $node A 48-bit number representing the hardware
* address
* @param int $clockSeq A 14-bit number used to help avoid duplicates that
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates that
* could arise when the clock is set backwards in time or if the node ID
* changes
*
@@ -661,4 +701,58 @@ class Uuid implements UuidInterface
): UuidInterface {
return self::getFactory()->uuid6($node, $clockSeq);
}
/**
* Returns a version 7 (Unix Epoch time) UUID
*
* @param DateTimeInterface|null $dateTime An optional date/time from which
* to create the version 7 UUID. If not provided, the UUID is generated
* using the current date/time.
*
* @return UuidInterface A UuidInterface instance that represents a
* version 7 UUID
*/
public static function uuid7(?DateTimeInterface $dateTime = null): UuidInterface
{
$factory = self::getFactory();
if (method_exists($factory, 'uuid7')) {
/** @var UuidInterface */
return $factory->uuid7($dateTime);
}
throw new UnsupportedOperationException(
'The provided factory does not support the uuid7() method',
);
}
/**
* Returns a version 8 (custom) UUID
*
* The bytes provided may contain any value according to your application's
* needs. Be aware, however, that other applications may not understand the
* semantics of the value.
*
* @param string $bytes A 16-byte octet string. This is an open blob
* of data that you may fill with 128 bits of information. Be aware,
* however, bits 48 through 51 will be replaced with the UUID version
* field, and bits 64 and 65 will be replaced with the UUID variant. You
* MUST NOT rely on these bits for your application needs.
*
* @return UuidInterface A UuidInterface instance that represents a
* version 8 UUID
*/
public static function uuid8(string $bytes): UuidInterface
{
$factory = self::getFactory();
if (method_exists($factory, 'uuid8')) {
/** @var UuidInterface */
return $factory->uuid8($bytes);
}
throw new UnsupportedOperationException(
'The provided factory does not support the uuid8() method',
);
}
}

View File

@@ -24,6 +24,7 @@ use Ramsey\Uuid\Generator\DefaultTimeGenerator;
use Ramsey\Uuid\Generator\NameGeneratorInterface;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Generator\UnixTimeGenerator;
use Ramsey\Uuid\Lazy\LazyUuidFromString;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Provider\Time\FixedTimeProvider;
@@ -45,61 +46,26 @@ use const STR_PAD_LEFT;
class UuidFactory implements UuidFactoryInterface
{
private CodecInterface $codec;
private DceSecurityGeneratorInterface $dceSecurityGenerator;
private NameGeneratorInterface $nameGenerator;
private NodeProviderInterface $nodeProvider;
private NumberConverterInterface $numberConverter;
private RandomGeneratorInterface $randomGenerator;
private TimeConverterInterface $timeConverter;
private TimeGeneratorInterface $timeGenerator;
private TimeGeneratorInterface $unixTimeGenerator;
private UuidBuilderInterface $uuidBuilder;
private ValidatorInterface $validator;
/**
* @var CodecInterface
* @var bool whether the feature set was provided from outside, or we can
* operate under "default" assumptions
*/
private $codec;
private bool $isDefaultFeatureSet;
/**
* @var DceSecurityGeneratorInterface
*/
private $dceSecurityGenerator;
/**
* @var NameGeneratorInterface
*/
private $nameGenerator;
/**
* @var NodeProviderInterface
*/
private $nodeProvider;
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var RandomGeneratorInterface
*/
private $randomGenerator;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @var TimeGeneratorInterface
*/
private $timeGenerator;
/**
* @var UuidBuilderInterface
*/
private $uuidBuilder;
/**
* @var ValidatorInterface
*/
private $validator;
/** @var bool whether the feature set was provided from outside, or we can operate under "default" assumptions */
private $isDefaultFeatureSet;
/**
* @param FeatureSet $features A set of available features in the current environment
* @param FeatureSet|null $features A set of available features in the current environment
*/
public function __construct(?FeatureSet $features = null)
{
@@ -117,6 +83,7 @@ class UuidFactory implements UuidFactoryInterface
$this->timeGenerator = $features->getTimeGenerator();
$this->uuidBuilder = $features->getBuilder();
$this->validator = $features->getValidator();
$this->unixTimeGenerator = $features->getUnixTimeGenerator();
}
/**
@@ -342,7 +309,15 @@ class UuidFactory implements UuidFactoryInterface
$bytes = $timeGenerator->generate($nodeHex, $clockSeq);
return $this->uuidFromBytesAndVersion($bytes, 1);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME);
}
/**
* @psalm-pure
*/
public function fromHexadecimal(Hexadecimal $hex): UuidInterface
{
return $this->codec->decode($hex->__toString());
}
/**
@@ -352,7 +327,7 @@ class UuidFactory implements UuidFactoryInterface
{
$bytes = $this->timeGenerator->generate($node, $clockSeq);
return $this->uuidFromBytesAndVersion($bytes, 1);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_TIME);
}
public function uuid2(
@@ -368,7 +343,7 @@ class UuidFactory implements UuidFactoryInterface
$clockSeq
);
return $this->uuidFromBytesAndVersion($bytes, 2);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_DCE_SECURITY);
}
/**
@@ -377,14 +352,14 @@ class UuidFactory implements UuidFactoryInterface
*/
public function uuid3($ns, string $name): UuidInterface
{
return $this->uuidFromNsAndName($ns, $name, 3, 'md5');
return $this->uuidFromNsAndName($ns, $name, Uuid::UUID_TYPE_HASH_MD5, 'md5');
}
public function uuid4(): UuidInterface
{
$bytes = $this->randomGenerator->generate(16);
return $this->uuidFromBytesAndVersion($bytes, 4);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_RANDOM);
}
/**
@@ -393,7 +368,7 @@ class UuidFactory implements UuidFactoryInterface
*/
public function uuid5($ns, string $name): UuidInterface
{
return $this->uuidFromNsAndName($ns, $name, 5, 'sha1');
return $this->uuidFromNsAndName($ns, $name, Uuid::UUID_TYPE_HASH_SHA1, 'sha1');
}
public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface
@@ -412,7 +387,46 @@ class UuidFactory implements UuidFactoryInterface
$v6Bytes = hex2bin(substr($v6, 1, 12) . '0' . substr($v6, -3));
$v6Bytes .= substr($bytes, 8);
return $this->uuidFromBytesAndVersion($v6Bytes, 6);
return $this->uuidFromBytesAndVersion($v6Bytes, Uuid::UUID_TYPE_REORDERED_TIME);
}
/**
* Returns a version 7 (Unix Epoch time) UUID
*
* @param DateTimeInterface|null $dateTime An optional date/time from which
* to create the version 7 UUID. If not provided, the UUID is generated
* using the current date/time.
*
* @return UuidInterface A UuidInterface instance that represents a
* version 7 UUID
*/
public function uuid7(?DateTimeInterface $dateTime = null): UuidInterface
{
assert($this->unixTimeGenerator instanceof UnixTimeGenerator);
$bytes = $this->unixTimeGenerator->generate(null, null, $dateTime);
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_UNIX_TIME);
}
/**
* Returns a version 8 (Custom) UUID
*
* The bytes provided may contain any value according to your application's
* needs. Be aware, however, that other applications may not understand the
* semantics of the value.
*
* @param string $bytes A 16-byte octet string. This is an open blob
* of data that you may fill with 128 bits of information. Be aware,
* however, bits 48 through 51 will be replaced with the UUID version
* field, and bits 64 and 65 will be replaced with the UUID variant. You
* MUST NOT rely on these bits for your application needs.
*
* @return UuidInterface A UuidInterface instance that represents a
* version 8 UUID
*/
public function uuid8(string $bytes): UuidInterface
{
return $this->uuidFromBytesAndVersion($bytes, Uuid::UUID_TYPE_CUSTOM);
}
/**
@@ -430,6 +444,7 @@ class UuidFactory implements UuidFactoryInterface
*/
public function uuid(string $bytes): UuidInterface
{
/** @psalm-suppress ImpurePropertyFetch */
return $this->uuidBuilder->build($this->codec, $bytes);
}
@@ -447,8 +462,12 @@ class UuidFactory implements UuidFactoryInterface
*
* @psalm-pure
*/
private function uuidFromNsAndName($ns, string $name, int $version, string $hashAlgorithm): UuidInterface
{
private function uuidFromNsAndName(
UuidInterface | string $ns,
string $name,
int $version,
string $hashAlgorithm
): UuidInterface {
if (!($ns instanceof UuidInterface)) {
$ns = $this->fromString($ns);
}
@@ -488,6 +507,7 @@ class UuidFactory implements UuidFactoryInterface
return LazyUuidFromString::fromBytes($bytes);
}
/** @psalm-suppress ImpureVariable */
return $this->uuid($bytes);
}
}

View File

@@ -25,6 +25,61 @@ use Ramsey\Uuid\Validator\ValidatorInterface;
*/
interface UuidFactoryInterface
{
/**
* Creates a UUID from a byte string
*
* @param string $bytes A binary string
*
* @return UuidInterface A UuidInterface instance created from a binary
* string representation
*
* @psalm-pure
*/
public function fromBytes(string $bytes): UuidInterface;
/**
* Creates a UUID from a DateTimeInterface instance
*
* @param DateTimeInterface $dateTime The date and time
* @param Hexadecimal|null $node A 48-bit number representing the hardware
* address
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
* that could arise when the clock is set backwards in time or if the
* node ID changes
*
* @return UuidInterface A UuidInterface instance that represents a
* version 1 UUID created from a DateTimeInterface instance
*/
public function fromDateTime(
DateTimeInterface $dateTime,
?Hexadecimal $node = null,
?int $clockSeq = null
): UuidInterface;
/**
* Creates a UUID from a 128-bit integer string
*
* @param string $integer String representation of 128-bit integer
*
* @return UuidInterface A UuidInterface instance created from the string
* representation of a 128-bit integer
*
* @psalm-pure
*/
public function fromInteger(string $integer): UuidInterface;
/**
* Creates a UUID from the string standard representation
*
* @param string $uuid A hexadecimal string
*
* @return UuidInterface A UuidInterface instance created from a hexadecimal
* string representation
*
* @psalm-pure
*/
public function fromString(string $uuid): UuidInterface;
/**
* Returns the validator to use for the factory
*
@@ -33,7 +88,7 @@ interface UuidFactoryInterface
public function getValidator(): ValidatorInterface;
/**
* Returns a version 1 (time-based) UUID from a host ID, sequence number,
* Returns a version 1 (Gregorian time) UUID from a host ID, sequence number,
* and the current time
*
* @param Hexadecimal|int|string|null $node A 48-bit number representing the
@@ -111,7 +166,7 @@ interface UuidFactoryInterface
public function uuid5($ns, string $name): UuidInterface;
/**
* Returns a version 6 (ordered-time) UUID from a host ID, sequence number,
* Returns a version 6 (reordered time) UUID from a host ID, sequence number,
* and the current time
*
* @param Hexadecimal|null $node A 48-bit number representing the hardware
@@ -124,59 +179,4 @@ interface UuidFactoryInterface
* version 6 UUID
*/
public function uuid6(?Hexadecimal $node = null, ?int $clockSeq = null): UuidInterface;
/**
* Creates a UUID from a byte string
*
* @param string $bytes A binary string
*
* @return UuidInterface A UuidInterface instance created from a binary
* string representation
*
* @psalm-pure
*/
public function fromBytes(string $bytes): UuidInterface;
/**
* Creates a UUID from the string standard representation
*
* @param string $uuid A hexadecimal string
*
* @return UuidInterface A UuidInterface instance created from a hexadecimal
* string representation
*
* @psalm-pure
*/
public function fromString(string $uuid): UuidInterface;
/**
* Creates a UUID from a 128-bit integer string
*
* @param string $integer String representation of 128-bit integer
*
* @return UuidInterface A UuidInterface instance created from the string
* representation of a 128-bit integer
*
* @psalm-pure
*/
public function fromInteger(string $integer): UuidInterface;
/**
* Creates a UUID from a DateTimeInterface instance
*
* @param DateTimeInterface $dateTime The date and time
* @param Hexadecimal|null $node A 48-bit number representing the hardware
* address
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates
* that could arise when the clock is set backwards in time or if the
* node ID changes
*
* @return UuidInterface A UuidInterface instance that represents a
* version 1 UUID created from a DateTimeInterface instance
*/
public function fromDateTime(
DateTimeInterface $dateTime,
?Hexadecimal $node = null,
?int $clockSeq = null
): UuidInterface;
}

View File

@@ -83,6 +83,14 @@ interface UuidInterface extends
*/
public function getInteger(): IntegerObject;
/**
* Returns the string standard representation of the UUID as a URN
*
* @link http://en.wikipedia.org/wiki/Uniform_Resource_Name Uniform Resource Name
* @link https://tools.ietf.org/html/rfc4122#section-3 RFC 4122, § 3: Namespace Registration Template
*/
public function getUrn(): string;
/**
* Returns the string standard representation of the UUID
*

View File

@@ -15,17 +15,18 @@ declare(strict_types=1);
namespace Ramsey\Uuid;
use DateTimeInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
/**
* Returns a version 1 (time-based) UUID from a host ID, sequence number,
* Returns a version 1 (Gregorian time) UUID from a host ID, sequence number,
* and the current time
*
* @param Hexadecimal|int|string|null $node A 48-bit number representing the
* hardware address; this number may be represented as an integer or a
* hexadecimal string
* @param int $clockSeq A 14-bit number used to help avoid duplicates that
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates that
* could arise when the clock is set backwards in time or if the node ID
* changes
*
@@ -106,12 +107,12 @@ function v5($ns, string $name): string
}
/**
* Returns a version 6 (ordered-time) UUID from a host ID, sequence number,
* Returns a version 6 (reordered time) UUID from a host ID, sequence number,
* and the current time
*
* @param Hexadecimal|null $node A 48-bit number representing the hardware
* address
* @param int $clockSeq A 14-bit number used to help avoid duplicates that
* @param int|null $clockSeq A 14-bit number used to help avoid duplicates that
* could arise when the clock is set backwards in time or if the node ID
* changes
*
@@ -121,3 +122,37 @@ function v6(?Hexadecimal $node = null, ?int $clockSeq = null): string
{
return Uuid::uuid6($node, $clockSeq)->toString();
}
/**
* Returns a version 7 (Unix Epoch time) UUID
*
* @param DateTimeInterface|null $dateTime An optional date/time from which
* to create the version 7 UUID. If not provided, the UUID is generated
* using the current date/time.
*
* @return non-empty-string Version 7 UUID as a string
*/
function v7(?DateTimeInterface $dateTime = null): string
{
return Uuid::uuid7($dateTime)->toString();
}
/**
* Returns a version 8 (custom) UUID
*
* The bytes provided may contain any value according to your application's
* needs. Be aware, however, that other applications may not understand the
* semantics of the value.
*
* @param string $bytes A 16-byte octet string. This is an open blob
* of data that you may fill with 128 bits of information. Be aware,
* however, bits 48 through 51 will be replaced with the UUID version
* field, and bits 64 and 65 will be replaced with the UUID variant. You
* MUST NOT rely on these bits for your application needs.
*
* @return non-empty-string Version 7 UUID as a string
*/
function v8(string $bytes): string
{
return Uuid::uuid8($bytes)->toString();
}