Update website

This commit is contained in:
Guilhem Lavaux 2024-11-23 20:45:29 +01:00
parent 41ce1aa076
commit ea0eb1c6e0
4222 changed files with 721797 additions and 14 deletions

View file

@ -0,0 +1,19 @@
Copyright (c) 2015-2021 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,84 @@
<h1 align="center">ramsey/collection</h1>
<p align="center">
<strong>A PHP library for representing and manipulating collections.</strong>
</p>
<p align="center">
<a href="https://github.com/ramsey/collection"><img src="http://img.shields.io/badge/source-ramsey/collection-blue.svg?style=flat-square" alt="Source Code"></a>
<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/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://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>
<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://packagist.org/packages/ramsey/collection/stats"><img src="https://img.shields.io/packagist/dt/ramsey/collection.svg?style=flat-square&colorB=darkmagenta" alt="Package downloads on Packagist"></a>
<a href="https://phpc.chat/channel/ramsey"><img src="https://img.shields.io/badge/phpc.chat-%23ramsey-darkslateblue?style=flat-square" alt="Chat with the maintainers"></a>
</p>
## About
ramsey/collection is a PHP 7.2+ library for representing and manipulating collections.
Much inspiration for this library came from the [Java Collections Framework][java].
This project adheres to a [code of conduct](CODE_OF_CONDUCT.md).
By participating in this project and its community, you are expected to
uphold this code.
## Installation
Install this package as a dependency using [Composer](https://getcomposer.org).
``` bash
composer require ramsey/collection
```
## Usage
Examples of how to use this framework can 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.2 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!
## Coordinated Disclosure
Keeping user information safe and secure is a top priority, and we welcome the
contribution of external security researchers. If you believe you've found a
security issue in software that is maintained in this repository, please read
[SECURITY.md][] for instructions on submitting a vulnerability report.
## ramsey/collection for Enterprise
Available as part of the Tidelift Subscription.
The maintainers of ramsey/collection and thousands of other packages are working
with Tidelift to deliver commercial support and maintenance for the open source
packages you use to build your applications. Save time, reduce risk, and improve
code health, while paying the maintainers of the exact packages you use.
[Learn more.](https://tidelift.com/subscription/pkg/packagist-ramsey-collection?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Copyright and License
The ramsey/collection library is copyright © [Ben Ramsey](https://benramsey.com)
and licensed for use under the terms of the
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

View file

@ -0,0 +1,105 @@
{
"name": "ramsey/collection",
"type": "library",
"description": "A PHP 7.2+ library for representing and manipulating collections.",
"keywords": [
"array",
"collection",
"hash",
"map",
"queue",
"set"
],
"license": "MIT",
"authors": [
{
"name": "Ben Ramsey",
"email": "ben@benramsey.com",
"homepage": "https://benramsey.com"
}
],
"require": {
"php": "^7.2 || ^8"
},
"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",
"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
},
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Ramsey\\Console\\": "resources/console/",
"Ramsey\\Collection\\Test\\": "tests/"
},
"files": [
"vendor/hamcrest/hamcrest-php/hamcrest/Hamcrest.php"
]
},
"scripts": {
"post-autoload-dump": "captainhook install --ansi -f -s",
"br:analyze": [
"@br:analyze:phpstan",
"@br:analyze:psalm"
],
"br:analyze:phpstan": "phpstan --memory-limit=1G analyse",
"br:analyze:psalm": "psalm --diff --config=psalm.xml",
"br:build:clean": "git clean -fX build/.",
"br:build:clear-cache": "git clean -fX build/cache/.",
"br:lint": "phpcs --cache=build/cache/phpcs.cache",
"br:lint:fix": "./bin/lint-fix.sh",
"br:repl": [
"echo ; echo 'Type ./bin/repl to start the REPL.'"
],
"br:test": "phpunit",
"br:test:all": [
"@br:lint",
"@br:analyze",
"@br:test"
],
"br:test:coverage:ci": "phpunit --coverage-clover build/logs/clover.xml",
"br:test:coverage:html": "phpunit --coverage-html build/coverage",
"pre-commit": [
"@br:lint:fix",
"@br:lint",
"@br:analyze"
],
"test": "@br:test:all"
},
"scripts-descriptions": {
"br:analyze": "Performs static analysis on the code base.",
"br:analyze:phpstan": "Runs the PHPStan static analyzer.",
"br:analyze:psalm": "Runs the Psalm static analyzer.",
"br:build:clean": "Removes everything not under version control from the build directory.",
"br:build:clear-cache": "Removes everything not under version control from build/cache/.",
"br:lint": "Checks all source code for coding standards issues.",
"br:lint:fix": "Checks source code for coding standards issues and fixes them, if possible.",
"br:repl": "Note: Use ./bin/repl to run the REPL.",
"br:test": "Runs the full unit test suite.",
"br:test:all": "Runs linting, static analysis, and unit tests.",
"br:test:coverage:ci": "Runs the unit test suite and generates a Clover coverage report.",
"br:test:coverage:html": "Runs the unit tests suite and generates an HTML coverage report.",
"pre-commit": "These commands are run as part of a Git pre-commit hook installed using captainhook/captainhook. Each command should be prepared to accept a list of space-separated staged files.",
"test": "Shortcut to run the full test suite."
}
}

View file

@ -0,0 +1,179 @@
<?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;
use ArrayIterator;
use Traversable;
use function serialize;
use function unserialize;
/**
* This class provides a basic implementation of `ArrayInterface`, to minimize
* the effort required to implement this interface.
*
* @template T
* @template-implements ArrayInterface<T>
*/
abstract class AbstractArray implements ArrayInterface
{
/**
* The items of this array.
*
* @var array<array-key, T>
*/
protected $data = [];
/**
* Constructs a new array object.
*
* @param array<array-key, T> $data The initial items to add to this array.
*/
public function __construct(array $data = [])
{
// Invoke offsetSet() for each value added; in this way, sub-classes
// may provide additional logic about values added to the array object.
foreach ($data as $key => $value) {
$this[$key] = $value;
}
}
/**
* Returns an iterator for this array.
*
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php IteratorAggregate::getIterator()
*
* @return Traversable<T>
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->data);
}
/**
* Returns `true` if the given offset exists in this array.
*
* @link http://php.net/manual/en/arrayaccess.offsetexists.php ArrayAccess::offsetExists()
*
* @param array-key $offset The offset to check.
*/
public function offsetExists($offset): bool
{
return isset($this->data[$offset]);
}
/**
* Returns the value at the specified offset.
*
* @link http://php.net/manual/en/arrayaccess.offsetget.php ArrayAccess::offsetGet()
*
* @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
* does not exist.
*/
public function offsetGet($offset)
{
return $this->data[$offset] ?? null;
}
/**
* Sets the given value to the given offset in the array.
*
* @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 T $value The value to set at the given offset.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offsetSet($offset, $value): void
{
if ($offset === null) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
/**
* Removes the given offset and its value from the array.
*
* @link http://php.net/manual/en/arrayaccess.offsetunset.php ArrayAccess::offsetUnset()
*
* @param array-key $offset The offset to remove from the array.
*/
public function offsetUnset($offset): void
{
unset($this->data[$offset]);
}
/**
* Returns a serialized string representation of this array object.
*
* @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);
}
/**
* Converts a serialized string representation into an instance object.
*
* @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;
}
/**
* Returns the number of items in this array.
*
* @link http://php.net/manual/en/countable.count.php Countable::count()
*/
public function count(): int
{
return count($this->data);
}
public function clear(): void
{
$this->data = [];
}
/**
* @inheritDoc
*/
public function toArray(): array
{
return $this->data;
}
public function isEmpty(): bool
{
return count($this->data) === 0;
}
}

View file

@ -0,0 +1,316 @@
<?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;
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\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueExtractorTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
use function array_filter;
use function array_map;
use function array_merge;
use function array_search;
use function array_udiff;
use function array_uintersect;
use function current;
use function end;
use function in_array;
use function reset;
use function sprintf;
use function unserialize;
use function usort;
/**
* This class provides a basic implementation of `CollectionInterface`, to
* minimize the effort required to implement this interface
*
* @template T
* @template-extends AbstractArray<T>
* @template-implements CollectionInterface<T>
*/
abstract class AbstractCollection extends AbstractArray implements CollectionInterface
{
use TypeTrait;
use ValueToStringTrait;
use ValueExtractorTrait;
/**
* @inheritDoc
*/
public function add($element): bool
{
$this[] = $element;
return true;
}
/**
* @inheritDoc
*/
public function contains($element, bool $strict = true): bool
{
return in_array($element, $this->data, $strict);
}
/**
* @inheritDoc
*/
public function offsetSet($offset, $value): void
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($value)
);
}
if ($offset === null) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
/**
* @inheritDoc
*/
public function remove($element): bool
{
if (($position = array_search($element, $this->data, true)) !== false) {
unset($this->data[$position]);
return true;
}
return false;
}
/**
* @inheritDoc
*/
public function column(string $propertyOrMethod): array
{
$temp = [];
foreach ($this->data as $item) {
/** @var mixed $value */
$value = $this->extractValue($item, $propertyOrMethod);
/** @psalm-suppress MixedAssignment */
$temp[] = $value;
}
return $temp;
}
/**
* @inheritDoc
*/
public function first()
{
if ($this->isEmpty()) {
throw new OutOfBoundsException('Can\'t determine first item. Collection is empty');
}
reset($this->data);
/** @var T $first */
$first = current($this->data);
return $first;
}
/**
* @inheritDoc
*/
public function last()
{
if ($this->isEmpty()) {
throw new OutOfBoundsException('Can\'t determine last item. Collection is empty');
}
/** @var T $item */
$item = end($this->data);
reset($this->data);
return $item;
}
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): CollectionInterface
{
if (!in_array($order, [self::SORT_ASC, self::SORT_DESC], true)) {
throw new InvalidSortOrderException('Invalid sort order given: ' . $order);
}
$collection = clone $this;
usort(
$collection->data,
/**
* @param T $a
* @param T $b
*/
function ($a, $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 $collection;
}
public function filter(callable $callback): CollectionInterface
{
$collection = clone $this;
$collection->data = array_merge([], array_filter($collection->data, $callback));
return $collection;
}
/**
* {@inheritdoc}
*/
public function where(string $propertyOrMethod, $value): CollectionInterface
{
return $this->filter(function ($item) use ($propertyOrMethod, $value) {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $accessorValue === $value;
});
}
public function map(callable $callback): CollectionInterface
{
return new Collection('mixed', array_map($callback, $this->data));
}
public function diff(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
$diffAtoB = array_udiff($this->data, $other->toArray(), $this->getComparator());
$diffBtoA = array_udiff($other->toArray(), $this->data, $this->getComparator());
/** @var array<array-key, T> $diff */
$diff = array_merge($diffAtoB, $diffBtoA);
$collection = clone $this;
$collection->data = $diff;
return $collection;
}
public function intersect(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
/** @var array<array-key, T> $intersect */
$intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator());
$collection = clone $this;
$collection->data = $intersect;
return $collection;
}
public function merge(CollectionInterface ...$collections): CollectionInterface
{
$temp = [$this->data];
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)
);
}
// 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()) {
throw new CollectionMismatchException(
sprintf('Collection items in collection with index %d must be of type %s', $index, $this->getType())
);
}
$temp[] = $collection->toArray();
}
$merge = array_merge(...$temp);
$collection = clone $this;
$collection->data = $merge;
return $collection;
}
/**
* @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
*/
private function compareCollectionTypes(CollectionInterface $other): void
{
if (!$other instanceof static) {
throw new CollectionMismatchException('Collection must be of type ' . static::class);
}
// 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()) {
throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
}
}
private function getComparator(): Closure
{
return /**
* @param T $a
* @param T $b
*/
function ($a, $b): int {
// If the two values are object, we convert them to unique scalars.
// If the collection contains mixed values (unlikely) where some are objects
// and some are not, we leave them as they are.
// The comparator should still work and the result of $a < $b should
// be consistent but unpredictable since not documented.
if (is_object($a) && is_object($b)) {
$a = spl_object_id($a);
$b = spl_object_id($b);
}
return $a === $b ? 0 : ($a < $b ? 1 : -1);
};
}
}

View file

@ -0,0 +1,50 @@
<?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;
/**
* This class contains the basic implementation of a collection that does not
* allow duplicated values (a set), to minimize the effort required to implement
* this specific type of collection.
*
* @template T
* @template-extends AbstractCollection<T>
*/
abstract class AbstractSet extends AbstractCollection
{
/**
* @inheritDoc
*/
public function add($element): bool
{
if ($this->contains($element)) {
return false;
}
return parent::add($element);
}
/**
* @inheritDoc
*/
public function offsetSet($offset, $value): void
{
if ($this->contains($value)) {
return;
}
parent::offsetSet($offset, $value);
}
}

View file

@ -0,0 +1,49 @@
<?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;
use ArrayAccess;
use Countable;
use IteratorAggregate;
use Serializable;
/**
* `ArrayInterface` provides traversable array functionality to data types.
*
* @template T
*/
interface ArrayInterface extends
ArrayAccess,
Countable,
IteratorAggregate,
Serializable
{
/**
* Removes all items from this array.
*/
public function clear(): void;
/**
* Returns a native PHP array representation of this array object.
*
* @return array<array-key, T>
*/
public function toArray(): array;
/**
* Returns `true` if this array is empty.
*/
public function isEmpty(): bool;
}

View file

@ -0,0 +1,106 @@
<?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;
/**
* A collection represents a group of objects.
*
* Each object in the collection is of a specific, defined type.
*
* This is a direct implementation of `CollectionInterface`, provided for
* the sake of convenience.
*
* Example usage:
*
* ``` php
* $collection = new \Ramsey\Collection\Collection('My\\Foo');
* $collection->add(new \My\Foo());
* $collection->add(new \My\Foo());
*
* foreach ($collection as $foo) {
* // Do something with $foo
* }
* ```
*
* It is preferable to subclass `AbstractCollection` to create your own typed
* collections. For example:
*
* ``` php
* namespace My\Foo;
*
* class FooCollection extends \Ramsey\Collection\AbstractCollection
* {
* public function getType()
* {
* return 'My\\Foo';
* }
* }
* ```
*
* And then use it similarly to the earlier example:
*
* ``` php
* $fooCollection = new \My\Foo\FooCollection();
* $fooCollection->add(new \My\Foo());
* $fooCollection->add(new \My\Foo());
*
* foreach ($fooCollection as $foo) {
* // Do something with $foo
* }
* ```
*
* The benefit with this approach is that you may do type-checking on the
* collection object:
*
* ``` php
* if ($collection instanceof \My\Foo\FooCollection) {
* // the collection is a collection of My\Foo objects
* }
* ```
*
* @template T
* @template-extends AbstractCollection<T>
*/
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
* collection.
* @param array<array-key, T> $data The initial items to store in the collection.
*/
public function __construct(string $collectionType, array $data = [])
{
$this->collectionType = $collectionType;
parent::__construct($data);
}
public function getType(): string
{
return $this->collectionType;
}
}

View file

@ -0,0 +1,205 @@
<?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;
/**
* A collection represents a group of objects, known as its elements.
*
* Some collections allow duplicate elements and others do not. Some are ordered
* and others unordered.
*
* @template T
* @template-extends ArrayInterface<T>
*/
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).
*
* Returns `true` if this collection changed as a result of the call.
* (Returns `false` if this collection does not permit duplicates and
* already contains the specified element.)
*
* Collections that support this operation may place limitations on what
* elements may be added to this collection. In particular, some
* collections will refuse to add `null` elements, and others will impose
* restrictions on the type of elements that may be added. Collection
* classes should clearly specify in their documentation any restrictions
* on what elements may be added.
*
* If a collection refuses to add a particular element for any reason other
* than that it already contains the element, it must throw an exception
* (rather than returning `false`). This preserves the invariant that a
* collection always contains the specified element after this call returns.
*
* @param T $element The element to add to the collection.
*
* @return bool `true` if this collection changed as a result of the call.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function add($element): bool;
/**
* Returns `true` if this collection contains the specified element.
*
* @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;
/**
* Returns the type associated with this collection.
*/
public function getType(): string;
/**
* Removes a single instance of the specified element from this collection,
* if it is present.
*
* @param T $element The element to remove from the collection.
*
* @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;
/**
* Returns the values from the given property or method.
*
* @param string $propertyOrMethod The property or method name to filter by.
*
* @return list<mixed>
*/
public function column(string $propertyOrMethod): array;
/**
* Returns the first item of the collection.
*
* @return T
*/
public function first();
/**
* Returns the last item of the collection.
*
* @return T
*/
public function last();
/**
* Sort the collection by a property or method with the given sort order.
*
* 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).
*
* @return CollectionInterface<T>
*/
public function sort(string $propertyOrMethod, string $order = self::SORT_ASC): self;
/**
* Filter out items of the collection which don't match the criteria of
* given callback.
*
* This will always leave the original collection untouched and will return
* a new one.
*
* 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.
*
* @return CollectionInterface<T>
*/
public function filter(callable $callback): self;
/**
* Create a new collection where items match the criteria of given callback.
*
* This will always leave the original collection untouched and will return
* a new one.
*
* @param string $propertyOrMethod The property or method to evaluate.
* @param mixed $value The value to match.
*
* @return CollectionInterface<T>
*/
public function where(string $propertyOrMethod, $value): self;
/**
* Apply a given callback method on each item of the collection.
*
* This will always leave the original collection untouched. The new
* collection is created by mapping the callback to each item of the
* original collection.
*
* 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
* item of the collection.
*
* @return CollectionInterface<TCallbackReturn>
*
* @template TCallbackReturn
*/
public function map(callable $callback): self;
/**
* Create a new collection with divergent items between current and given
* collection.
*
* @param CollectionInterface<T> $other The collection to check for divergent
* items.
*
* @return CollectionInterface<T>
*/
public function diff(CollectionInterface $other): self;
/**
* Create a new collection with intersecting item between current and given
* collection.
*
* @param CollectionInterface<T> $other The collection to check for
* intersecting items.
*
* @return CollectionInterface<T>
*/
public function intersect(CollectionInterface $other): self;
/**
* Merge current items and items of given collections into a new one.
*
* @param CollectionInterface<T> ...$collections The collections to merge.
*
* @return CollectionInterface<T>
*/
public function merge(CollectionInterface ...$collections): self;
}

View file

@ -0,0 +1,187 @@
<?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;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\NoSuchElementException;
/**
* This class provides a basic implementation of `DoubleEndedQueueInterface`, to
* minimize the effort required to implement this interface.
*
* @template T
* @template-extends Queue<T>
* @template-implements DoubleEndedQueueInterface<T>
*/
class DoubleEndedQueue extends Queue implements DoubleEndedQueueInterface
{
/**
* Index of the last element in the queue.
*
* @var int
*/
private $tail = -1;
/**
* @inheritDoc
*/
public function offsetSet($offset, $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->tail++;
$this->data[$this->tail] = $value;
}
/**
* @inheritDoc
*/
public function addFirst($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->index--;
$this->data[$this->index] = $element;
return true;
}
/**
* @inheritDoc
*/
public function addLast($element): bool
{
return $this->add($element);
}
/**
* @inheritDoc
*/
public function offerFirst($element): bool
{
try {
return $this->addFirst($element);
} catch (InvalidArgumentException $e) {
return false;
}
}
/**
* @inheritDoc
*/
public function offerLast($element): bool
{
return $this->offer($element);
}
/**
* @inheritDoc
*/
public function removeFirst()
{
return $this->remove();
}
/**
* @inheritDoc
*/
public function removeLast()
{
$tail = $this->pollLast();
if ($tail === null) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
return $tail;
}
/**
* @inheritDoc
*/
public function pollFirst()
{
return $this->poll();
}
/**
* @inheritDoc
*/
public function pollLast()
{
if ($this->count() === 0) {
return null;
}
$tail = $this[$this->tail];
unset($this[$this->tail]);
$this->tail--;
return $tail;
}
/**
* @inheritDoc
*/
public function firstElement()
{
return $this->element();
}
/**
* @inheritDoc
*/
public function lastElement()
{
if ($this->count() === 0) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
return $this->data[$this->tail];
}
/**
* @inheritDoc
*/
public function peekFirst()
{
return $this->peek();
}
/**
* @inheritDoc
*/
public function peekLast()
{
if ($this->count() === 0) {
return null;
}
return $this->data[$this->tail];
}
}

View file

@ -0,0 +1,316 @@
<?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;
use Ramsey\Collection\Exception\NoSuchElementException;
/**
* A linear collection that supports element insertion and removal at both ends.
*
* Most `DoubleEndedQueueInterface` implementations place no fixed limits on the
* number of elements they may contain, but this interface supports
* capacity-restricted double-ended queues as well as those with no fixed size
* limit.
*
* This interface defines methods to access the elements at both ends of the
* double-ended queue. Methods are provided to insert, remove, and examine the
* element. Each of these methods exists in two forms: one throws an exception
* if the operation fails, the other returns a special value (either `null` or
* `false`, depending on the operation). The latter form of the insert operation
* is designed specifically for use with capacity-restricted implementations; in
* most implementations, insert operations cannot fail.
*
* The twelve methods described above are summarized in the following table:
*
* <table>
* <caption>Summary of DoubleEndedQueueInterface methods</caption>
* <thead>
* <tr>
* <th></th>
* <th colspan=2>First Element (Head)</th>
* <th colspan=2>Last Element (Tail)</th>
* </tr>
* <tr>
* <td></td>
* <td><em>Throws exception</em></td>
* <td><em>Special value</em></td>
* <td><em>Throws exception</em></td>
* <td><em>Special value</em></td>
* </tr>
* </thead>
* <tbody>
* <tr>
* <th>Insert</th>
* <td><code>addFirst()</code></td>
* <td><code>offerFirst()</code></td>
* <td><code>addLast()</code></td>
* <td><code>offerLast()</code></td>
* </tr>
* <tr>
* <th>Remove</th>
* <td><code>removeFirst()</code></td>
* <td><code>pollFirst()</code></td>
* <td><code>removeLast()</code></td>
* <td><code>pollLast()</code></td>
* </tr>
* <tr>
* <th>Examine</th>
* <td><code>firstElement()</code></td>
* <td><code>peekFirst()</code></td>
* <td><code>lastElement()</code></td>
* <td><code>peekLast()</code></td>
* </tr>
* </tbody>
* </table>
*
* This interface extends the `QueueInterface`. When a double-ended queue is
* used as a queue, FIFO (first-in-first-out) behavior results. Elements are
* added at the end of the double-ended queue and removed from the beginning.
* The methods inherited from the `QueueInterface` are precisely equivalent to
* `DoubleEndedQueueInterface` methods as indicated in the following table:
*
* <table>
* <caption>Comparison of QueueInterface and DoubleEndedQueueInterface methods</caption>
* <thead>
* <tr>
* <th>QueueInterface Method</th>
* <th>DoubleEndedQueueInterface Method</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><code>add()</code></td>
* <td><code>addLast()</code></td>
* </tr>
* <tr>
* <td><code>offer()</code></td>
* <td><code>offerLast()</code></td>
* </tr>
* <tr>
* <td><code>remove()</code></td>
* <td><code>removeFirst()</code></td>
* </tr>
* <tr>
* <td><code>poll()</code></td>
* <td><code>pollFirst()</code></td>
* </tr>
* <tr>
* <td><code>element()</code></td>
* <td><code>firstElement()</code></td>
* </tr>
* <tr>
* <td><code>peek()</code></td>
* <td><code>peekFirst()</code></td>
* </tr>
* </tbody>
* </table>
*
* Double-ended queues can also be used as LIFO (last-in-first-out) stacks. When
* a double-ended queue is used as a stack, elements are pushed and popped from
* the beginning of the double-ended queue. Stack concepts are precisely
* equivalent to `DoubleEndedQueueInterface` methods as indicated in the table
* below:
*
* <table>
* <caption>Comparison of stack concepts and DoubleEndedQueueInterface methods</caption>
* <thead>
* <tr>
* <th>Stack concept</th>
* <th>DoubleEndedQueueInterface Method</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><em>push</em></td>
* <td><code>addFirst()</code></td>
* </tr>
* <tr>
* <td><em>pop</em></td>
* <td><code>removeFirst()</code></td>
* </tr>
* <tr>
* <td><em>peek</em></td>
* <td><code>peekFirst()</code></td>
* </tr>
* </tbody>
* </table>
*
* Note that the `peek()` method works equally well when a double-ended queue is
* used as a queue or a stack; in either case, elements are drawn from the
* beginning of the double-ended queue.
*
* While `DoubleEndedQueueInterface` implementations are not strictly required
* to prohibit the insertion of `null` elements, they are strongly encouraged to
* do so. Users of any `DoubleEndedQueueInterface` implementations that do allow
* `null` elements are strongly encouraged *not* to take advantage of the
* ability to insert nulls. This is so because `null` is used as a special
* return value by various methods to indicated that the double-ended queue is
* empty.
*
* @template T
* @template-extends QueueInterface<T>
*/
interface DoubleEndedQueueInterface extends QueueInterface
{
/**
* Inserts the specified element at the front of this queue if it is
* possible to do so immediately without violating capacity restrictions.
*
* When using a capacity-restricted double-ended queue, it is generally
* preferable to use the `offerFirst()` method.
*
* @param T $element The element to add to the front of this queue.
*
* @return bool `true` if this queue changed as a result of the call.
*
* @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;
/**
* Inserts the specified element at the end of this queue if it is possible
* to do so immediately without violating capacity restrictions.
*
* When using a capacity-restricted double-ended queue, it is generally
* preferable to use the `offerLast()` method.
*
* This method is equivalent to `add()`.
*
* @param T $element The element to add to the end of this queue.
*
* @return bool `true` if this queue changed as a result of the call.
*
* @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;
/**
* Inserts the specified element at the front of this queue if it is
* possible to do so immediately without violating capacity restrictions.
*
* When using a capacity-restricted queue, this method is generally
* preferable to `addFirst()`, which can fail to insert an element only by
* throwing an exception.
*
* @param T $element The element to add to the front of this queue.
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offerFirst($element): bool;
/**
* Inserts the specified element at the end of this queue if it is possible
* to do so immediately without violating capacity restrictions.
*
* When using a capacity-restricted queue, this method is generally
* preferable to `addLast()` which can fail to insert an element only by
* throwing an exception.
*
* @param T $element The element to add to the end of this queue.
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offerLast($element): bool;
/**
* Retrieves and removes the head of this queue.
*
* This method differs from `pollFirst()` only in that it throws an
* exception if this queue is empty.
*
* @return T the first element in this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function removeFirst();
/**
* Retrieves and removes the tail of this queue.
*
* This method differs from `pollLast()` only in that it throws an exception
* if this queue is empty.
*
* @return T the last element in this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function removeLast();
/**
* 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.
*/
public function pollFirst();
/**
* 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.
*/
public function pollLast();
/**
* Retrieves, but does not remove, the head of this queue.
*
* This method differs from `peekFirst()` only in that it throws an
* exception if this queue is empty.
*
* @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function firstElement();
/**
* Retrieves, but does not remove, the tail of this queue.
*
* This method differs from `peekLast()` only in that it throws an exception
* if this queue is empty.
*
* @return T the tail of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function lastElement();
/**
* 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.
*/
public function peekFirst();
/**
* 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.
*/
public function peekLast();
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown when attempting to operate on collections of differing types.
*/
class CollectionMismatchException extends \RuntimeException
{
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown to indicate an argument is not of the expected type.
*/
class InvalidArgumentException extends \InvalidArgumentException
{
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown when attempting to use a sort order that is not recognized.
*/
class InvalidSortOrderException extends \RuntimeException
{
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown when attempting to access an element that does not exist.
*/
class NoSuchElementException extends \RuntimeException
{
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown when attempting to access an element out of the range of the collection.
*/
class OutOfBoundsException extends \OutOfBoundsException
{
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown to indicate that the requested operation is not supported.
*/
class UnsupportedOperationException extends \RuntimeException
{
}

View file

@ -0,0 +1,22 @@
<?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\Exception;
/**
* Thrown when attempting to extract a value for a method or property that does not exist.
*/
class ValueExtractionException extends \RuntimeException
{
}

View file

@ -0,0 +1,24 @@
<?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;
/**
* `GenericArray` represents a standard array object.
*
* @template-extends AbstractArray<mixed>
*/
class GenericArray extends AbstractArray
{
}

View file

@ -0,0 +1,162 @@
<?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\Map;
use Ramsey\Collection\AbstractArray;
use Ramsey\Collection\Exception\InvalidArgumentException;
use function array_key_exists;
use function array_keys;
use function in_array;
/**
* This class provides a basic implementation of `MapInterface`, to minimize the
* effort required to implement this interface.
*
* @template T
* @template-extends AbstractArray<T>
* @template-implements MapInterface<T>
*/
abstract class AbstractMap extends AbstractArray implements MapInterface
{
/**
* @inheritDoc
*/
public function offsetSet($offset, $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)
);
}
$this->data[$offset] = $value;
}
/**
* @inheritDoc
*/
public function containsKey($key): bool
{
return array_key_exists($key, $this->data);
}
/**
* @inheritDoc
*/
public function containsValue($value): bool
{
return in_array($value, $this->data, true);
}
/**
* @inheritDoc
*/
public function keys(): array
{
return array_keys($this->data);
}
/**
* @inheritDoc
*/
public function get($key, $defaultValue = null)
{
if (!$this->containsKey($key)) {
return $defaultValue;
}
return $this[$key];
}
/**
* @inheritDoc
*/
public function put($key, $value)
{
$previousValue = $this->get($key);
$this[$key] = $value;
return $previousValue;
}
/**
* @inheritDoc
*/
public function putIfAbsent($key, $value)
{
$currentValue = $this->get($key);
if ($currentValue === null) {
$this[$key] = $value;
}
return $currentValue;
}
/**
* @inheritDoc
*/
public function remove($key)
{
$previousValue = $this->get($key);
unset($this[$key]);
return $previousValue;
}
/**
* @inheritDoc
*/
public function removeIf($key, $value): bool
{
if ($this->get($key) === $value) {
unset($this[$key]);
return true;
}
return false;
}
/**
* @inheritDoc
*/
public function replace($key, $value)
{
$currentValue = $this->get($key);
if ($this->containsKey($key)) {
$this[$key] = $value;
}
return $currentValue;
}
/**
* @inheritDoc
*/
public function replaceIf($key, $oldValue, $newValue): bool
{
if ($this->get($key) === $oldValue) {
$this[$key] = $newValue;
return true;
}
return false;
}
}

View file

@ -0,0 +1,69 @@
<?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\Map;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Tool\TypeTrait;
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 T
* @template-extends AbstractMap<T>
* @template-implements TypedMapInterface<T>
*/
abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
{
use TypeTrait;
use ValueToStringTrait;
/**
* @param K|null $offset
* @param T $value
*
* @inheritDoc
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function offsetSet($offset, $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)
);
}
if ($this->checkType($this->getValueType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getValueType() . '; value is '
. $this->toolValueToString($value)
);
}
/** @psalm-suppress MixedArgumentTypeCoercion */
parent::offsetSet($offset, $value);
}
}

View file

@ -0,0 +1,25 @@
<?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\Map;
/**
* `AssociativeArrayMap` represents a standard associative array object.
*
* @template T
* @template-extends AbstractMap<T>
*/
class AssociativeArrayMap extends AbstractMap
{
}

View file

@ -0,0 +1,149 @@
<?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\Map;
use Ramsey\Collection\ArrayInterface;
/**
* An object that maps keys to values.
*
* A map cannot contain duplicate keys; each key can map to at most one value.
*
* @template T
* @template-extends ArrayInterface<T>
*/
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.
*/
public function containsKey($key): bool;
/**
* Returns `true` if this map maps one or more keys to the specified value.
*
* This performs a strict type check on the value.
*
* @param T $value The value to check in the map.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function containsValue($value): bool;
/**
* Return an array of the keys contained in this map.
*
* @return list<array-key>
*/
public function keys(): array;
/**
* Returns the value to which the specified key is mapped, `null` if this
* 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.
*
* @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);
/**
* Associates the specified value with the specified key in this map.
*
* 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 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`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function put($key, $value);
/**
* Associates the specified value with the specified key in this map only if
* it is not already set.
*
* 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 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`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function putIfAbsent($key, $value);
/**
* Removes the mapping for a key from this map if it is present.
*
* @param array-key $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`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function remove($key);
/**
* Removes the entry for the specified key only if it is currently mapped to
* the specified value.
*
* This performs a strict type check on the value.
*
* @param array-key $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;
/**
* 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 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`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function replace($key, $value);
/**
* Replaces the entry for the specified key only if currently mapped to the
* specified value.
*
* This performs a strict type check on the value.
*
* @param array-key $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;
}

View file

@ -0,0 +1,120 @@
<?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\Map;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
use function array_combine;
use function array_key_exists;
use function is_int;
/**
* `NamedParameterMap` represents a mapping of values to a set of named keys
* that may optionally be typed
*
* @template-extends AbstractMap<mixed>
*/
class NamedParameterMap extends AbstractMap
{
use TypeTrait;
use ValueToStringTrait;
/**
* Named parameters defined for this map.
*
* @var array<string, string>
*/
protected $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.
*/
public function __construct(array $namedParameters, array $data = [])
{
$this->namedParameters = $this->filterNamedParameters($namedParameters);
parent::__construct($data);
}
/**
* Returns named parameters set for this `NamedParameterMap`.
*
* @return array<string, string>
*/
public function getNamedParameters(): array
{
return $this->namedParameters;
}
/**
* @inheritDoc
*/
public function offsetSet($offset, $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 . '\''
);
}
if ($this->checkType($this->namedParameters[$offset], $value) === false) {
throw new InvalidArgumentException(
'Value for \'' . $offset . '\' must be of type '
. $this->namedParameters[$offset] . '; value is '
. $this->toolValueToString($value)
);
}
$this->data[$offset] = $value;
}
/**
* Given an array of named parameters, constructs a proper mapping of
* named parameters to types.
*
* @param array<array-key, string> $namedParameters The named parameters to filter.
*
* @return array<string, string>
*/
protected function filterNamedParameters(array $namedParameters): array
{
$names = [];
$types = [];
foreach ($namedParameters as $key => $value) {
if (is_int($key)) {
$names[] = $value;
$types[] = 'mixed';
} else {
$names[] = $key;
$types[] = $value;
}
}
return array_combine($names, $types) ?: [];
}
}

View file

@ -0,0 +1,137 @@
<?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\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=
* 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
* this type of keys.
*
* This is a direct implementation of `TypedMapInterface`, provided for the sake
* of convenience.
*
* Example usage:
*
* ```php
* $map = new TypedMap('string', Foo::class);
* $map['x'] = new Foo();
* foreach ($map as $key => $value) {
* // do something with $key, it will be a Foo::class
* }
*
* // this will throw an exception since key must be string
* $map[10] = new Foo();
*
* // this will throw an exception since value must be a Foo
* $map['bar'] = 'bar';
*
* // initialize map with contents
* $map = new TypedMap('string', Foo::class, [
* new Foo(), new Foo(), new Foo()
* ]);
* ```
*
* It is preferable to subclass `AbstractTypedMap` to create your own typed map
* implementation:
*
* ```php
* class FooTypedMap extends AbstractTypedMap
* {
* public function getKeyType()
* {
* return 'int';
* }
*
* public function getValueType()
* {
* return Foo::class;
* }
* }
* ```
*
* but you also may use the `TypedMap` class:
*
* ```php
* class FooTypedMap extends TypedMap
* {
* public function __constructor(array $data = [])
* {
* parent::__construct('int', Foo::class, $data);
* }
* }
* ```
*
* @template K
* @template T
* @template-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.
*
* @param string $keyType The data type of the map's keys.
* @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 */
parent::__construct($data);
}
public function getKeyType(): string
{
return $this->keyType;
}
public function getValueType(): string
{
return $this->valueType;
}
}

View file

@ -0,0 +1,35 @@
<?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\Map;
/**
* A `TypedMapInterface` represents a map of elements where key and value are
* typed.
*
* @template T
* @template-extends MapInterface<T>
*/
interface TypedMapInterface extends MapInterface
{
/**
* Return the type used on the key.
*/
public function getKeyType(): string;
/**
* Return the type forced on the values.
*/
public function getValueType(): string;
}

View file

@ -0,0 +1,169 @@
<?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;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
/**
* This class provides a basic implementation of `QueueInterface`, to minimize
* the effort required to implement this interface.
*
* @template T
* @template-extends AbstractArray<T>
* @template-implements QueueInterface<T>
*/
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.
*/
public function __construct(string $queueType, array $data = [])
{
$this->queueType = $queueType;
parent::__construct($data);
}
/**
* {@inheritDoc}
*
* 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.
*/
public function offsetSet($offset, $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->data[] = $value;
}
/**
* @inheritDoc
*/
public function add($element): bool
{
$this[] = $element;
return true;
}
/**
* @inheritDoc
*/
public function element()
{
$element = $this->peek();
if ($element === null) {
throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.'
);
}
return $element;
}
/**
* @inheritDoc
*/
public function offer($element): bool
{
try {
return $this->add($element);
} catch (InvalidArgumentException $e) {
return false;
}
}
/**
* @inheritDoc
*/
public function peek()
{
if ($this->count() === 0) {
return null;
}
return $this[$this->index];
}
/**
* @inheritDoc
*/
public function poll()
{
if ($this->count() === 0) {
return null;
}
$head = $this[$this->index];
unset($this[$this->index]);
$this->index++;
return $head;
}
/**
* @inheritDoc
*/
public function remove()
{
$head = $this->poll();
if ($head === null) {
throw new NoSuchElementException('Can\'t return element from Queue. Queue is empty.');
}
return $head;
}
public function getType(): string
{
return $this->queueType;
}
}

View file

@ -0,0 +1,203 @@
<?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;
use Ramsey\Collection\Exception\NoSuchElementException;
/**
* A queue is a collection in which the entities in the collection are kept in
* order.
*
* The principal operations on the queue are the addition of entities to the end
* (tail), also known as *enqueue*, and removal of entities from the front
* (head), also known as *dequeue*. This makes the queue a first-in-first-out
* (FIFO) data structure.
*
* Besides basic array operations, queues provide additional insertion,
* extraction, and inspection operations. Each of these methods exists in two
* forms: one throws an exception if the operation fails, the other returns a
* special value (either `null` or `false`, depending on the operation). The
* latter form of the insert operation is designed specifically for use with
* capacity-restricted `QueueInterface` implementations; in most
* implementations, insert operations cannot fail.
*
* <table>
* <caption>Summary of QueueInterface methods</caption>
* <thead>
* <tr>
* <td></td>
* <td><em>Throws exception</em></td>
* <td><em>Returns special value</em></td>
* </tr>
* </thead>
* <tbody>
* <tr>
* <th>Insert</th>
* <td><code>add()</code></td>
* <td><code>offer()</code></td>
* </tr>
* <tr>
* <th>Remove</th>
* <td><code>remove()</code></td>
* <td><code>poll()</code></td>
* </tr>
* <tr>
* <th>Examine</th>
* <td><code>element()</code></td>
* <td><code>peek()</code></td>
* </tr>
* </tbody>
* </table>
*
* Queues typically, but do not necessarily, order elements in a FIFO
* (first-in-first-out) manner. Among the exceptions are priority queues, which
* order elements according to a supplied comparator, or the elements' natural
* ordering, and LIFO queues (or stacks) which order the elements LIFO
* (last-in-first-out). Whatever the ordering used, the head of the queue is
* that element which would be removed by a call to remove() or poll(). In a
* FIFO queue, all new elements are inserted at the tail of the queue. Other
* kinds of queues may use different placement rules. Every `QueueInterface`
* implementation must specify its ordering properties.
*
* The `offer()` method inserts an element if possible, otherwise returning
* `false`. This differs from the `add()` method, which can fail to add an
* element only by throwing an unchecked exception. The `offer()` method is
* designed for use when failure is a normal, rather than exceptional
* occurrence, for example, in fixed-capacity (or "bounded") queues.
*
* The `remove()` and `poll()` methods remove and return the head of the queue.
* Exactly which element is removed from the queue is a function of the queue's
* ordering policy, which differs from implementation to implementation. The
* `remove()` and `poll()` methods differ only in their behavior when the queue
* is empty: the `remove()` method throws an exception, while the `poll()`
* method returns `null`.
*
* The `element()` and `peek()` methods return, but do not remove, the head of
* the queue.
*
* `QueueInterface` implementations generally do not allow insertion of `null`
* elements, although some implementations do not prohibit insertion of `null`.
* Even in the implementations that permit it, `null` should not be inserted
* into a queue, as `null` is also used as a special return value by the
* `poll()` method to indicate that the queue contains no elements.
*
* @template T
* @template-extends ArrayInterface<T>
*/
interface QueueInterface extends ArrayInterface
{
/**
* Ensures that this queue contains the specified element (optional
* operation).
*
* Returns `true` if this queue changed as a result of the call. (Returns
* `false` if this queue does not permit duplicates and already contains the
* specified element.)
*
* Queues that support this operation may place limitations on what elements
* may be added to this queue. In particular, some queues will refuse to add
* `null` elements, and others will impose restrictions on the type of
* elements that may be added. Queue classes should clearly specify in their
* documentation any restrictions on what elements may be added.
*
* If a queue refuses to add a particular element for any reason other than
* that it already contains the element, it must throw an exception (rather
* than returning `false`). This preserves the invariant that a queue always
* contains the specified element after this call returns.
*
* @see self::offer()
*
* @param T $element The element to add to this queue.
*
* @return bool `true` if this queue changed as a result of the call.
*
* @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;
/**
* Retrieves, but does not remove, the head of this queue.
*
* This method differs from `peek()` only in that it throws an exception if
* this queue is empty.
*
* @see self::peek()
*
* @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function element();
/**
* Inserts the specified element into this queue if it is possible to do so
* immediately without violating capacity restrictions.
*
* When using a capacity-restricted queue, this method is generally
* preferable to `add()`, which can fail to insert an element only by
* throwing an exception.
*
* @see self::add()
*
* @param T $element The element to add to this queue.
*
* @return bool `true` if the element was added to this queue, else `false`.
*/
// phpcs:ignore SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
public function offer($element): bool;
/**
* Retrieves, but does not remove, the head of this queue, or returns `null`
* if this queue is empty.
*
* @see self::element()
*
* @return T|null the head of this queue, or `null` if this queue is empty.
*/
public function peek();
/**
* Retrieves and removes the head of this queue, or returns `null`
* if this queue is empty.
*
* @see self::remove()
*
* @return T|null the head of this queue, or `null` if this queue is empty.
*/
public function poll();
/**
* Retrieves and removes the head of this queue.
*
* This method differs from `poll()` only in that it throws an exception if
* this queue is empty.
*
* @see self::poll()
*
* @return T the head of this queue.
*
* @throws NoSuchElementException if this queue is empty.
*/
public function remove();
/**
* Returns the type associated with this queue.
*/
public function getType(): string;
}

View file

@ -0,0 +1,69 @@
<?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;
/**
* A set is a collection that contains no duplicate elements.
*
* Great care must be exercised if mutable objects are used as set elements.
* The behavior of a set is not specified if the value of an object is changed
* in a manner that affects equals comparisons while the object is an element in
* the set.
*
* Example usage:
*
* ``` php
* $foo = new \My\Foo();
* $set = new Set(\My\Foo::class);
*
* $set->add($foo); // returns TRUE, the element don't exists
* $set->add($foo); // returns FALSE, the element already exists
*
* $bar = new \My\Foo();
* $set->add($bar); // returns TRUE, $bar !== $foo
* ```
*
* @template T
* @template-extends AbstractSet<T>
*/
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 array<array-key, T> $data The initial items to store in the set.
*/
public function __construct(string $setType, array $data = [])
{
$this->setType = $setType;
parent::__construct($data);
}
public function getType(): string
{
return $this->setType;
}
}

View file

@ -0,0 +1,73 @@
<?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\Tool;
use function is_array;
use function is_bool;
use function is_callable;
use function is_float;
use function is_int;
use function is_numeric;
use function is_object;
use function is_resource;
use function is_scalar;
use function is_string;
/**
* Provides functionality to check values for specific types.
*/
trait TypeTrait
{
/**
* Returns `true` if value is of the specified type.
*
* @param string $type The type to check the value against.
* @param mixed $value The value to check.
*/
protected function checkType(string $type, $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;
}
}
}

View file

@ -0,0 +1,58 @@
<?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\Tool;
use Ramsey\Collection\Exception\ValueExtractionException;
use function get_class;
use function method_exists;
use function property_exists;
use function sprintf;
/**
* Provides functionality to extract the value of a property or method from an object.
*/
trait ValueExtractorTrait
{
/**
* Extracts the value of the given property or method from the object.
*
* @param mixed $object The object to extract the value from.
* @param string $propertyOrMethod The property or method for which the
* value should be extracted.
*
* @return mixed the value extracted from the specified property or method.
*
* @throws ValueExtractionException if the method or property is not defined.
*/
protected function extractValue($object, string $propertyOrMethod)
{
if (!is_object($object)) {
throw new ValueExtractionException('Unable to extract a value from a non-object');
}
if (property_exists($object, $propertyOrMethod)) {
return $object->$propertyOrMethod;
}
if (method_exists($object, $propertyOrMethod)) {
return $object->{$propertyOrMethod}();
}
throw new ValueExtractionException(
sprintf('Method or property "%s" not defined in %s', $propertyOrMethod, get_class($object))
);
}
}

View file

@ -0,0 +1,94 @@
<?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\Tool;
use DateTimeInterface;
use function get_class;
use function get_resource_type;
use function is_array;
use function is_bool;
use function is_callable;
use function is_resource;
use function is_scalar;
/**
* Provides functionality to express a value as string
*/
trait ValueToStringTrait
{
/**
* Returns a string representation of the value.
*
* - null value: `'NULL'`
* - boolean: `'TRUE'`, `'FALSE'`
* - array: `'Array'`
* - scalar: converted-value
* - resource: `'(type resource #number)'`
* - object with `__toString()`: result of `__toString()`
* - object DateTime: ISO 8601 date
* - object: `'(className Object)'`
* - anonymous function: same as object
*
* @param mixed $value the value to return as a string.
*/
protected function toolValueToString($value): string
{
// null
if ($value === null) {
return 'NULL';
}
// boolean constants
if (is_bool($value)) {
return $value ? 'TRUE' : 'FALSE';
}
// array
if (is_array($value)) {
return 'Array';
}
// scalar types (integer, float, string)
if (is_scalar($value)) {
return (string) $value;
}
// resource
if (is_resource($value)) {
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.
// __toString() is implemented
if (is_callable([$value, '__toString'])) {
return (string) $value->__toString();
}
// object of type \DateTime
if ($value instanceof DateTimeInterface) {
return $value->format('c');
}
// unknown type
return '(' . get_class($value) . ' Object)';
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2012-2021 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,83 @@
<h1 align="center">ramsey/uuid</h1>
<p align="center">
<strong>A PHP library for generating and working with UUIDs.</strong>
</p>
<p align="center">
<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://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>
ramsey/uuid is a PHP library for generating and working with universally unique
identifiers (UUIDs).
This project adheres to a [code of conduct](CODE_OF_CONDUCT.md).
By participating in this project and its community, you are expected to
uphold this code.
Much inspiration for this library came from the [Java][javauuid] and
[Python][pyuuid] UUID libraries.
## Installation
The preferred method of installation is via [Composer][]. Run the following
command to install the package and add it as a requirement to your project's
`composer.json`:
```bash
composer require ramsey/uuid
```
## Upgrading to Version 4
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)
## Documentation
Please see <https://uuid.ramsey.dev> for documentation, tips, examples, and
frequently asked questions.
## Contributing
Contributions are welcome! To contribute, please familiarize yourself with
[CONTRIBUTING.md](CONTRIBUTING.md).
## Coordinated Disclosure
Keeping user information safe and secure is a top priority, and we welcome the
contribution of external security researchers. If you believe you've found a
security issue in software that is maintained in this repository, please read
[SECURITY.md][] for instructions on submitting a vulnerability report.
## ramsey/uuid for Enterprise
Available as part of the Tidelift Subscription.
The maintainers of ramsey/uuid and thousands of other packages are working with
Tidelift to deliver commercial support and maintenance for the open source
packages you use to build your applications. Save time, reduce risk, and improve
code health, while paying the maintainers of the exact packages you use.
[Learn more.](https://tidelift.com/subscription/pkg/packagist-ramsey-uuid?utm_source=undefined&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Copyright and License
The ramsey/uuid library is copyright © [Ben Ramsey](https://benramsey.com/) and
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
[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

View file

@ -0,0 +1,107 @@
{
"name": "ramsey/uuid",
"type": "library",
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"keywords": [
"uuid",
"identifier",
"guid"
],
"license": "MIT",
"require": {
"php": "^7.2 || ^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"
},
"require-dev": {
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"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",
"phpunit/phpunit": "^8.5 || ^9",
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.9"
},
"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
}
},
"autoload": {
"psr-4": {
"Ramsey\\Uuid\\": "src/"
},
"files": [
"src/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Ramsey\\Uuid\\Benchmark\\": "tests/benchmark/",
"Ramsey\\Uuid\\StaticAnalysis\\": "tests/static-analysis/",
"Ramsey\\Uuid\\Test\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"analyze": [
"@phpstan",
"@psalm"
],
"build:clean": "git clean -fX build/",
"lint": "parallel-lint src tests",
"lint:paths": "parallel-lint",
"phpbench": "phpbench run",
"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"
],
"phpunit": "phpunit --verbose --colors=always",
"phpunit-coverage": "phpunit --verbose --colors=always --coverage-html build/coverage",
"psalm": "psalm --show-info=false --config=psalm.xml",
"test": [
"@lint",
"@phpbench",
"@phpcs",
"@phpstan",
"@psalm",
"@phpunit"
]
}
}

View file

@ -0,0 +1,63 @@
<?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;
/**
* Provides binary math utilities
*/
class BinaryUtils
{
/**
* Applies the RFC 4122 variant field to the 16-bit clock sequence
*
* @link http://tools.ietf.org/html/rfc4122#section-4.1.1 RFC 4122, § 4.1.1: Variant
*
* @param int $clockSeq The 16-bit clock sequence value before the RFC 4122
* variant is applied
*
* @return int The 16-bit clock sequence multiplexed with the UUID variant
*
* @psalm-pure
*/
public static function applyVariant(int $clockSeq): int
{
$clockSeq = $clockSeq & 0x3fff;
$clockSeq |= 0x8000;
return $clockSeq;
}
/**
* Applies the RFC 4122 version number to the 16-bit `time_hi_and_version` field
*
* @link http://tools.ietf.org/html/rfc4122#section-4.1.3 RFC 4122, § 4.1.3: Version
*
* @param int $timeHi The value of the 16-bit `time_hi_and_version` field
* before the RFC 4122 version is applied
* @param int $version The RFC 4122 version to apply to the `time_hi` field
*
* @return int The 16-bit time_hi field of the timestamp multiplexed with
* the UUID version number
*
* @psalm-pure
*/
public static function applyVersion(int $timeHi, int $version): int
{
$timeHi = $timeHi & 0x0fff;
$timeHi |= $version << 12;
return $timeHi;
}
}

View file

@ -0,0 +1,80 @@
<?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\Builder;
use Ramsey\Collection\AbstractCollection;
use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
use Ramsey\Uuid\Converter\Time\PhpTimeConverter;
use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Nonstandard\UuidBuilder as NonstandardUuidBuilder;
use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder;
use Traversable;
/**
* A collection of UuidBuilderInterface objects
*
* @extends AbstractCollection<UuidBuilderInterface>
*/
class BuilderCollection extends AbstractCollection
{
public function getType(): string
{
return UuidBuilderInterface::class;
}
/**
* @psalm-mutation-free
* @psalm-suppress ImpureMethodCall
* @psalm-suppress InvalidTemplateParam
*/
public function getIterator(): Traversable
{
return parent::getIterator();
}
/**
* Re-constructs the object from its serialized form
*
* @param string $serialized The serialized PHP string to unserialize into
* a UuidInterface instance
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress RedundantConditionGivenDocblockType
*/
public function unserialize($serialized): void
{
/** @var array<array-key, UuidBuilderInterface> $data */
$data = unserialize($serialized, [
'allowed_classes' => [
BrickMathCalculator::class,
GenericNumberConverter::class,
GenericTimeConverter::class,
GuidBuilder::class,
NonstandardUuidBuilder::class,
PhpTimeConverter::class,
Rfc4122UuidBuilder::class,
],
]);
$this->data = array_filter(
$data,
function ($unserialized): bool {
return $unserialized instanceof UuidBuilderInterface;
}
);
}
}

View file

@ -0,0 +1,26 @@
<?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\Builder;
use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder;
/**
* @deprecated Transition to {@see Rfc4122UuidBuilder}.
*
* @psalm-immutable
*/
class DefaultUuidBuilder extends Rfc4122UuidBuilder
{
}

View file

@ -0,0 +1,76 @@
<?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\Builder;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\DegradedTimeConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\DegradedUuid;
use Ramsey\Uuid\Rfc4122\Fields as Rfc4122Fields;
use Ramsey\Uuid\UuidInterface;
/**
* @deprecated DegradedUuid instances are no longer necessary to support 32-bit
* systems. Transition to {@see DefaultUuidBuilder}.
*
* @psalm-immutable
*/
class DegradedUuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @param NumberConverterInterface $numberConverter The number converter to
* use when constructing the DegradedUuid
* @param TimeConverterInterface|null $timeConverter The time converter to use
* for converting timestamps extracted from a UUID to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
?TimeConverterInterface $timeConverter = null
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter ?: new DegradedTimeConverter();
}
/**
* Builds and returns a DegradedUuid
*
* @param CodecInterface $codec The codec to use for building this DegradedUuid instance
* @param string $bytes The byte string from which to construct a UUID
*
* @return DegradedUuid The DegradedUuidBuild returns an instance of Ramsey\Uuid\DegradedUuid
*
* @psalm-pure
*/
public function build(CodecInterface $codec, string $bytes): UuidInterface
{
return new DegradedUuid(
new Rfc4122Fields($bytes),
$this->numberConverter,
$codec,
$this->timeConverter
);
}
}

View file

@ -0,0 +1,74 @@
<?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\Builder;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Exception\BuilderNotFoundException;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Ramsey\Uuid\UuidInterface;
/**
* FallbackBuilder builds a UUID by stepping through a list of UUID builders
* until a UUID can be constructed without exceptions
*
* @psalm-immutable
*/
class FallbackBuilder implements UuidBuilderInterface
{
/**
* @var BuilderCollection
*/
private $builders;
/**
* @param BuilderCollection $builders An array of UUID builders
*/
public function __construct(BuilderCollection $builders)
{
$this->builders = $builders;
}
/**
* Builds and returns a UuidInterface instance using the first builder that
* succeeds
*
* @param CodecInterface $codec The codec to use for building this instance
* @param string $bytes The byte string from which to construct a UUID
*
* @return UuidInterface an instance of a UUID object
*
* @psalm-pure
*/
public function build(CodecInterface $codec, string $bytes): UuidInterface
{
$lastBuilderException = null;
foreach ($this->builders as $builder) {
try {
return $builder->build($codec, $bytes);
} catch (UnableToBuildUuidException $exception) {
$lastBuilderException = $exception;
continue;
}
}
throw new BuilderNotFoundException(
'Could not find a suitable builder for the provided codec and fields',
0,
$lastBuilderException
);
}
}

View file

@ -0,0 +1,39 @@
<?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\Builder;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\UuidInterface;
/**
* A UUID builder builds instances of UuidInterface
*
* @psalm-immutable
*/
interface UuidBuilderInterface
{
/**
* Builds and returns a UuidInterface
*
* @param CodecInterface $codec The codec to use for building this UuidInterface instance
* @param string $bytes The byte string from which to construct a UUID
*
* @return UuidInterface Implementations may choose to return more specific
* instances of UUIDs that implement UuidInterface
*
* @psalm-pure
*/
public function build(CodecInterface $codec, string $bytes): UuidInterface;
}

View file

@ -0,0 +1,71 @@
<?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\Codec;
use Ramsey\Uuid\UuidInterface;
/**
* A codec encodes and decodes a UUID according to defined rules
*
* @psalm-immutable
*/
interface CodecInterface
{
/**
* Returns a hexadecimal string representation of a UuidInterface
*
* @param UuidInterface $uuid The UUID for which to create a hexadecimal
* string representation
*
* @return string Hexadecimal string representation of a UUID
*
* @psalm-return non-empty-string
*/
public function encode(UuidInterface $uuid): string;
/**
* Returns a binary string representation of a UuidInterface
*
* @param UuidInterface $uuid The UUID for which to create a binary string
* representation
*
* @return string Binary string representation of a UUID
*
* @psalm-return non-empty-string
*/
public function encodeBinary(UuidInterface $uuid): string;
/**
* Returns a UuidInterface derived from a hexadecimal string representation
*
* @param string $encodedUuid The hexadecimal string representation to
* convert into a UuidInterface instance
*
* @return UuidInterface An instance of a UUID decoded from a hexadecimal
* string representation
*/
public function decode(string $encodedUuid): UuidInterface;
/**
* Returns a UuidInterface derived from a binary string representation
*
* @param string $bytes The binary string representation to convert into a
* UuidInterface instance
*
* @return UuidInterface An instance of a UUID decoded from a binary string
* representation
*/
public function decodeBytes(string $bytes): 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\Codec;
use Ramsey\Uuid\Guid\Guid;
use Ramsey\Uuid\UuidInterface;
use function bin2hex;
use function substr;
/**
* GuidStringCodec encodes and decodes globally unique identifiers (GUID)
*
* @see Guid
*
* @psalm-immutable
*/
class GuidStringCodec extends StringCodec
{
public function decode(string $encodedUuid): UuidInterface
{
$bytes = $this->getBytes($encodedUuid);
return $this->getBuilder()->build($this, $this->swapBytes($bytes));
}
public function decodeBytes(string $bytes): UuidInterface
{
// Specifically call parent::decode to preserve correct byte order
return parent::decode(bin2hex($bytes));
}
/**
* Swaps bytes according to the GUID rules
*/
private function swapBytes(string $bytes): string
{
return $bytes[3] . $bytes[2] . $bytes[1] . $bytes[0]
. $bytes[5] . $bytes[4]
. $bytes[7] . $bytes[6]
. substr($bytes, 8);
}
}

View file

@ -0,0 +1,113 @@
<?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\Codec;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Exception\UnsupportedOperationException;
use Ramsey\Uuid\Rfc4122\FieldsInterface as Rfc4122FieldsInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use function strlen;
use function substr;
/**
* OrderedTimeCodec encodes and decodes a UUID, optimizing the byte order for
* more efficient storage
*
* For binary representations of version 1 UUID, this codec may be used to
* reorganize the time fields, making the UUID closer to sequential when storing
* the bytes. According to Percona, this optimization can improve database
* INSERTs and SELECTs using the UUID column as a key.
*
* The string representation of the UUID will remain unchanged. Only the binary
* representation is reordered.
*
* **PLEASE NOTE:** Binary representations of UUIDs encoded with this codec must
* be decoded with this codec. Decoding using another codec can result in
* malformed UUIDs.
*
* @link https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ Storing UUID Values in MySQL
*
* @psalm-immutable
*/
class OrderedTimeCodec extends StringCodec
{
/**
* Returns a binary string representation of a UUID, with the timestamp
* fields rearranged for optimized storage
*
* @inheritDoc
* @psalm-return non-empty-string
* @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
* @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
*/
public function encodeBinary(UuidInterface $uuid): string
{
if (
!($uuid->getFields() instanceof Rfc4122FieldsInterface)
|| $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME
) {
throw new InvalidArgumentException(
'Expected RFC 4122 version 1 (time-based) UUID'
);
}
$bytes = $uuid->getFields()->getBytes();
/** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */
return $bytes[6] . $bytes[7]
. $bytes[4] . $bytes[5]
. $bytes[0] . $bytes[1] . $bytes[2] . $bytes[3]
. substr($bytes, 8);
}
/**
* Returns a UuidInterface derived from an ordered-time binary string
* representation
*
* @throws InvalidArgumentException if $bytes is an invalid length
*
* @inheritDoc
*/
public function decodeBytes(string $bytes): UuidInterface
{
if (strlen($bytes) !== 16) {
throw new InvalidArgumentException(
'$bytes string should contain 16 characters.'
);
}
// Rearrange the bytes to their original order.
$rearrangedBytes = $bytes[4] . $bytes[5] . $bytes[6] . $bytes[7]
. $bytes[2] . $bytes[3]
. $bytes[0] . $bytes[1]
. substr($bytes, 8);
$uuid = parent::decodeBytes($rearrangedBytes);
if (
!($uuid->getFields() instanceof Rfc4122FieldsInterface)
|| $uuid->getFields()->getVersion() !== Uuid::UUID_TYPE_TIME
) {
throw new UnsupportedOperationException(
'Attempting to decode a non-time-based UUID using '
. 'OrderedTimeCodec'
);
}
return $uuid;
}
}

View file

@ -0,0 +1,138 @@
<?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\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 hex2bin;
use function implode;
use function str_replace;
use function strlen;
use function substr;
/**
* StringCodec encodes and decodes RFC 4122 UUIDs
*
* @link http://tools.ietf.org/html/rfc4122
*
* @psalm-immutable
*/
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)
{
$this->builder = $builder;
}
public function encode(UuidInterface $uuid): string
{
/** @var FieldsInterface $fields */
$fields = $uuid->getFields();
return $fields->getTimeLow()->toString()
. '-'
. $fields->getTimeMid()->toString()
. '-'
. $fields->getTimeHiAndVersion()->toString()
. '-'
. $fields->getClockSeqHiAndReserved()->toString()
. $fields->getClockSeqLow()->toString()
. '-'
. $fields->getNode()->toString();
}
/**
* @psalm-return non-empty-string
* @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
* @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
*/
public function encodeBinary(UuidInterface $uuid): string
{
/** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */
return $uuid->getFields()->getBytes();
}
/**
* @throws InvalidUuidStringException
*
* @inheritDoc
*/
public function decode(string $encodedUuid): UuidInterface
{
return $this->builder->build($this, $this->getBytes($encodedUuid));
}
public function decodeBytes(string $bytes): UuidInterface
{
if (strlen($bytes) !== 16) {
throw new InvalidArgumentException(
'$bytes string should contain 16 characters.'
);
}
return $this->builder->build($this, $bytes);
}
/**
* Returns the UUID builder
*/
protected function getBuilder(): UuidBuilderInterface
{
return $this->builder;
}
/**
* Returns a byte string of the UUID
*/
protected function getBytes(string $encodedUuid): string
{
$parsedUuid = str_replace(
['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}', '-'],
'',
$encodedUuid
);
$components = [
substr($parsedUuid, 0, 8),
substr($parsedUuid, 8, 4),
substr($parsedUuid, 12, 4),
substr($parsedUuid, 16, 4),
substr($parsedUuid, 20),
];
if (!Uuid::isValid(implode('-', $components))) {
throw new InvalidUuidStringException(
'Invalid UUID string: ' . $encodedUuid
);
}
return (string) hex2bin($parsedUuid);
}
}

View file

@ -0,0 +1,113 @@
<?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\Codec;
use Ramsey\Uuid\Exception\InvalidUuidStringException;
use Ramsey\Uuid\UuidInterface;
use function bin2hex;
use function sprintf;
use function substr;
use function substr_replace;
/**
* TimestampFirstCombCodec encodes and decodes COMBs, with the timestamp as the
* first 48 bits
*
* In contrast with the TimestampLastCombCodec, the TimestampFirstCombCodec
* adds the timestamp to the first 48 bits of the COMB. To generate a
* timestamp-first COMB, set the TimestampFirstCombCodec as the codec, along
* with the CombGenerator as the random generator.
*
* ``` php
* $factory = new UuidFactory();
*
* $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder()));
*
* $factory->setRandomGenerator(new CombGenerator(
* $factory->getRandomGenerator(),
* $factory->getNumberConverter()
* ));
*
* $timestampFirstComb = $factory->uuid4();
* ```
*
* @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys
*
* @psalm-immutable
*/
class TimestampFirstCombCodec extends StringCodec
{
/**
* @psalm-return non-empty-string
* @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
* @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
*/
public function encode(UuidInterface $uuid): string
{
$bytes = $this->swapBytes($uuid->getFields()->getBytes());
return sprintf(
'%08s-%04s-%04s-%04s-%012s',
bin2hex(substr($bytes, 0, 4)),
bin2hex(substr($bytes, 4, 2)),
bin2hex(substr($bytes, 6, 2)),
bin2hex(substr($bytes, 8, 2)),
bin2hex(substr($bytes, 10))
);
}
/**
* @psalm-return non-empty-string
* @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
* @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
*/
public function encodeBinary(UuidInterface $uuid): string
{
/** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */
return $this->swapBytes($uuid->getFields()->getBytes());
}
/**
* @throws InvalidUuidStringException
*
* @inheritDoc
*/
public function decode(string $encodedUuid): UuidInterface
{
$bytes = $this->getBytes($encodedUuid);
return $this->getBuilder()->build($this, $this->swapBytes($bytes));
}
public function decodeBytes(string $bytes): UuidInterface
{
return $this->getBuilder()->build($this, $this->swapBytes($bytes));
}
/**
* Swaps bytes according to the timestamp-first COMB rules
*/
private function swapBytes(string $bytes): string
{
$first48Bits = substr($bytes, 0, 6);
$last48Bits = substr($bytes, -6);
$bytes = substr_replace($bytes, $last48Bits, 0, 6);
$bytes = substr_replace($bytes, $first48Bits, -6);
return $bytes;
}
}

View file

@ -0,0 +1,51 @@
<?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\Codec;
/**
* TimestampLastCombCodec encodes and decodes COMBs, with the timestamp as the
* last 48 bits
*
* The CombGenerator when used with the StringCodec (and, by proxy, the
* TimestampLastCombCodec) adds the timestamp to the last 48 bits of the COMB.
* The TimestampLastCombCodec is provided for the sake of consistency. In
* practice, it is identical to the standard StringCodec but, it may be used
* with the CombGenerator for additional context when reading code.
*
* Consider the following code. By default, the codec used by UuidFactory is the
* StringCodec, but here, we explicitly set the TimestampLastCombCodec. It is
* redundant, but it is clear that we intend this COMB to be generated with the
* timestamp appearing at the end.
*
* ``` php
* $factory = new UuidFactory();
*
* $factory->setCodec(new TimestampLastCombCodec($factory->getUuidBuilder()));
*
* $factory->setRandomGenerator(new CombGenerator(
* $factory->getRandomGenerator(),
* $factory->getNumberConverter()
* ));
*
* $timestampLastComb = $factory->uuid4();
* ```
*
* @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys
*
* @psalm-immutable
*/
class TimestampLastCombCodec extends StringCodec
{
}

View file

@ -0,0 +1,57 @@
<?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\Number;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Math\BrickMathCalculator;
/**
* Previously used to integrate moontoast/math as a bignum arithmetic library,
* BigNumberConverter is deprecated in favor of GenericNumberConverter
*
* @deprecated Transition to {@see GenericNumberConverter}.
*
* @psalm-immutable
*/
class BigNumberConverter implements NumberConverterInterface
{
/**
* @var NumberConverterInterface
*/
private $converter;
public function __construct()
{
$this->converter = new GenericNumberConverter(new BrickMathCalculator());
}
/**
* @inheritDoc
* @psalm-pure
*/
public function fromHex(string $hex): string
{
return $this->converter->fromHex($hex);
}
/**
* @inheritDoc
* @psalm-pure
*/
public function toHex(string $number): string
{
return $this->converter->toHex($number);
}
}

View file

@ -0,0 +1,25 @@
<?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\Number;
/**
* @deprecated DegradedNumberConverter is no longer necessary for converting
* numbers on 32-bit systems. Transition to {@see GenericNumberConverter}.
*
* @psalm-immutable
*/
class DegradedNumberConverter extends BigNumberConverter
{
}

View file

@ -0,0 +1,63 @@
<?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\Number;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Math\CalculatorInterface;
use Ramsey\Uuid\Type\Integer as IntegerObject;
/**
* GenericNumberConverter uses the provided calculator to convert decimal
* numbers to and from hexadecimal values
*
* @psalm-immutable
*/
class GenericNumberConverter implements NumberConverterInterface
{
/**
* @var CalculatorInterface
*/
private $calculator;
public function __construct(CalculatorInterface $calculator)
{
$this->calculator = $calculator;
}
/**
* @inheritDoc
* @psalm-pure
* @psalm-return numeric-string
* @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
* @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
*/
public function fromHex(string $hex): string
{
return $this->calculator->fromBase($hex, 16)->toString();
}
/**
* @inheritDoc
* @psalm-pure
* @psalm-return non-empty-string
* @psalm-suppress MoreSpecificReturnType we know that the retrieved `string` is never empty
* @psalm-suppress LessSpecificReturnStatement we know that the retrieved `string` is never empty
*/
public function toHex(string $number): string
{
/** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */
return $this->calculator->toBase(new IntegerObject($number), 16);
}
}

View file

@ -0,0 +1,57 @@
<?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;
/**
* A number converter converts UUIDs from hexadecimal characters into
* representations of integers and vice versa
*
* @psalm-immutable
*/
interface NumberConverterInterface
{
/**
* Converts a hexadecimal number into an string integer representation of
* the number
*
* The integer representation returned is a string representation of the
* integer, to accommodate unsigned integers greater than PHP_INT_MAX.
*
* @param string $hex The hexadecimal string representation to convert
*
* @return string String representation of an integer
*
* @psalm-return numeric-string
*
* @psalm-pure
*/
public function fromHex(string $hex): string;
/**
* Converts a string integer representation into a hexadecimal string
* representation of the number
*
* @param string $number A string integer representation to convert; this
* must be a numeric string to accommodate unsigned integers greater
* than PHP_INT_MAX.
*
* @return string Hexadecimal string
*
* @psalm-return non-empty-string
*
* @psalm-pure
*/
public function toHex(string $number): string;
}

View file

@ -0,0 +1,51 @@
<?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\BrickMathCalculator;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Time;
/**
* Previously used to integrate moontoast/math as a bignum arithmetic library,
* BigNumberTimeConverter is deprecated in favor of GenericTimeConverter
*
* @deprecated Transition to {@see GenericTimeConverter}.
*
* @psalm-immutable
*/
class BigNumberTimeConverter implements TimeConverterInterface
{
/**
* @var TimeConverterInterface
*/
private $converter;
public function __construct()
{
$this->converter = new GenericTimeConverter(new BrickMathCalculator());
}
public function calculateTime(string $seconds, string $microseconds): Hexadecimal
{
return $this->converter->calculateTime($seconds, $microseconds);
}
public function convertTime(Hexadecimal $uuidTimestamp): Time
{
return $this->converter->convertTime($uuidTimestamp);
}
}

View file

@ -0,0 +1,25 @@
<?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;
/**
* @deprecated DegradedTimeConverter is no longer necessary for converting
* time on 32-bit systems. Transition to {@see GenericTimeConverter}.
*
* @psalm-immutable
*/
class DegradedTimeConverter extends BigNumberTimeConverter
{
}

View file

@ -0,0 +1,124 @@
<?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;
/**
* GenericTimeConverter uses the provided calculator to calculate and convert
* time values
*
* @psalm-immutable
*/
class GenericTimeConverter implements TimeConverterInterface
{
/**
* The number of 100-nanosecond intervals from the Gregorian calendar epoch
* to the Unix epoch.
*/
private const GREGORIAN_TO_UNIX_INTERVALS = '122192928000000000';
/**
* The number of 100-nanosecond intervals in one second.
*/
private const SECOND_INTERVALS = '10000000';
/**
* The number of 100-nanosecond intervals in one microsecond.
*/
private const MICROSECOND_INTERVALS = '10';
/**
* @var CalculatorInterface
*/
private $calculator;
public function __construct(CalculatorInterface $calculator)
{
$this->calculator = $calculator;
}
public function calculateTime(string $seconds, string $microseconds): Hexadecimal
{
$timestamp = new Time($seconds, $microseconds);
// Convert the seconds into a count of 100-nanosecond intervals.
$sec = $this->calculator->multiply(
$timestamp->getSeconds(),
new IntegerObject(self::SECOND_INTERVALS)
);
// Convert the microseconds into a count of 100-nanosecond intervals.
$usec = $this->calculator->multiply(
$timestamp->getMicroseconds(),
new IntegerObject(self::MICROSECOND_INTERVALS)
);
// Combine the seconds and microseconds intervals and add the count of
// 100-nanosecond intervals from the Gregorian calendar epoch to the
// Unix epoch. This gives us the correct count of 100-nanosecond
// intervals since the Gregorian calendar epoch for the given seconds
// and microseconds.
/** @var IntegerObject $uuidTime */
$uuidTime = $this->calculator->add(
$sec,
$usec,
new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS)
);
$uuidTimeHex = str_pad(
$this->calculator->toHexadecimal($uuidTime)->toString(),
16,
'0',
STR_PAD_LEFT
);
return new Hexadecimal($uuidTimeHex);
}
public function convertTime(Hexadecimal $uuidTimestamp): Time
{
// From the total, subtract the number of 100-nanosecond intervals from
// the Gregorian calendar epoch to the Unix epoch. This gives us the
// number of 100-nanosecond intervals from the Unix epoch, which also
// includes the microtime.
$epochNanoseconds = $this->calculator->subtract(
$this->calculator->toInteger($uuidTimestamp),
new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS)
);
// Convert the 100-nanosecond intervals into seconds and microseconds.
$unixTimestamp = $this->calculator->divide(
RoundingMode::HALF_UP,
6,
$epochNanoseconds,
new IntegerObject(self::SECOND_INTERVALS)
);
$split = explode('.', (string) $unixTimestamp, 2);
return new Time($split[0], $split[1] ?? 0);
}
}

View file

@ -0,0 +1,183 @@
<?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\BrickMathCalculator;
use Ramsey\Uuid\Math\CalculatorInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Type\Time;
use function count;
use function dechex;
use function explode;
use function is_float;
use function is_int;
use function str_pad;
use function strlen;
use function substr;
use const STR_PAD_LEFT;
use const STR_PAD_RIGHT;
/**
* PhpTimeConverter uses built-in PHP functions and standard math operations
* available to the PHP programming language to provide facilities for
* converting parts of time into representations that may be used in UUIDs
*
* @psalm-immutable
*/
class PhpTimeConverter implements TimeConverterInterface
{
/**
* The number of 100-nanosecond intervals from the Gregorian calendar epoch
* to the Unix epoch.
*/
private const GREGORIAN_TO_UNIX_INTERVALS = 0x01b21dd213814000;
/**
* The number of 100-nanosecond intervals in one second.
*/
private const SECOND_INTERVALS = 10000000;
/**
* The number of 100-nanosecond intervals in one microsecond.
*/
private const MICROSECOND_INTERVALS = 10;
/**
* @var CalculatorInterface
*/
private $calculator;
/**
* @var TimeConverterInterface
*/
private $fallbackConverter;
/**
* @var int
*/
private $phpPrecision;
public function __construct(
?CalculatorInterface $calculator = null,
?TimeConverterInterface $fallbackConverter = null
) {
if ($calculator === null) {
$calculator = new BrickMathCalculator();
}
if ($fallbackConverter === null) {
$fallbackConverter = new GenericTimeConverter($calculator);
}
$this->calculator = $calculator;
$this->fallbackConverter = $fallbackConverter;
$this->phpPrecision = (int) ini_get('precision');
}
public function calculateTime(string $seconds, string $microseconds): Hexadecimal
{
$seconds = new IntegerObject($seconds);
$microseconds = new IntegerObject($microseconds);
// Calculate the count of 100-nanosecond intervals since the Gregorian
// calendar epoch for the given seconds and microseconds.
$uuidTime = ((int) $seconds->toString() * self::SECOND_INTERVALS)
+ ((int) $microseconds->toString() * self::MICROSECOND_INTERVALS)
+ self::GREGORIAN_TO_UNIX_INTERVALS;
// Check to see whether we've overflowed the max/min integer size.
// If so, we will default to a different time converter.
/** @psalm-suppress RedundantCondition */
if (!is_int($uuidTime)) {
return $this->fallbackConverter->calculateTime(
$seconds->toString(),
$microseconds->toString()
);
}
return new Hexadecimal(str_pad(dechex($uuidTime), 16, '0', STR_PAD_LEFT));
}
public function convertTime(Hexadecimal $uuidTimestamp): Time
{
$timestamp = $this->calculator->toInteger($uuidTimestamp);
// Convert the 100-nanosecond intervals into seconds and microseconds.
$splitTime = $this->splitTime(
((int) $timestamp->toString() - self::GREGORIAN_TO_UNIX_INTERVALS)
/ self::SECOND_INTERVALS
);
if (count($splitTime) === 0) {
return $this->fallbackConverter->convertTime($uuidTimestamp);
}
return new Time($splitTime['sec'], $splitTime['usec']);
}
/**
* @param int|float $time The time to split into seconds and microseconds
*
* @return string[]
*/
private function splitTime($time): array
{
$split = explode('.', (string) $time, 2);
// If the $time value is a float but $split only has 1 element, then the
// float math was rounded up to the next second, so we want to return
// an empty array to allow use of the fallback converter.
if (is_float($time) && count($split) === 1) {
return [];
}
if (count($split) === 1) {
return [
'sec' => $split[0],
'usec' => '0',
];
}
// If the microseconds are less than six characters AND the length of
// the number is greater than or equal to the PHP precision, then it's
// possible that we lost some precision for the microseconds. Return an
// empty array, so that we can choose to use the fallback converter.
if (strlen($split[1]) < 6 && strlen((string) $time) >= $this->phpPrecision) {
return [];
}
$microseconds = $split[1];
// Ensure the microseconds are no longer than 6 digits. If they are,
// truncate the number to the first 6 digits and round up, if needed.
if (strlen($microseconds) > 6) {
$roundingDigit = (int) substr($microseconds, 6, 1);
$microseconds = (int) substr($microseconds, 0, 6);
if ($roundingDigit >= 5) {
$microseconds++;
}
}
return [
'sec' => $split[0],
'usec' => str_pad((string) $microseconds, 6, '0', STR_PAD_RIGHT),
];
}
}

View file

@ -0,0 +1,58 @@
<?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;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Time;
/**
* A time converter converts timestamps into representations that may be used
* in UUIDs
*
* @psalm-immutable
*/
interface TimeConverterInterface
{
/**
* Uses the provided seconds and micro-seconds to calculate the count of
* 100-nanosecond intervals since UTC 00:00:00.00, 15 October 1582, for
* RFC 4122 variant UUIDs
*
* @link http://tools.ietf.org/html/rfc4122#section-4.2.2 RFC 4122, § 4.2.2: Generation Details
*
* @param string $seconds A string representation of the number of seconds
* since the Unix epoch for the time to calculate
* @param string $microseconds A string representation of the micro-seconds
* associated with the time to calculate
*
* @return Hexadecimal The full UUID timestamp as a Hexadecimal value
*
* @psalm-pure
*/
public function calculateTime(string $seconds, string $microseconds): Hexadecimal;
/**
* Converts a timestamp extracted from a UUID to a Unix timestamp
*
* @param Hexadecimal $uuidTimestamp A hexadecimal representation of a UUID
* timestamp; a UUID timestamp is a count of 100-nanosecond intervals
* since UTC 00:00:00.00, 15 October 1582.
*
* @return Time An instance of {@see Time}
*
* @psalm-pure
*/
public function convertTime(Hexadecimal $uuidTimestamp): Time;
}

View file

@ -0,0 +1,25 @@
<?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;
/**
* @deprecated DegradedUuid is no longer necessary to represent UUIDs on 32-bit
* systems. Transition typehints to {@see UuidInterface}.
*
* @psalm-immutable
*/
class DegradedUuid extends Uuid
{
}

View file

@ -0,0 +1,147 @@
<?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;
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.
*
* @psalm-immutable
*/
interface DeprecatedUuidInterface
{
/**
* @deprecated This method will be removed in 5.0.0. There is no alternative
* recommendation, so plan accordingly.
*/
public function getNumberConverter(): NumberConverterInterface;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance.
*
* @return string[]
*/
public function getFieldsHex(): array;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated In ramsey/uuid version 5.0.0, this will be removed from the
* interface. It is available at {@see UuidV1::getDateTime()}.
*/
public function getDateTime(): DateTimeInterface;
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*/
public function getLeastSignificantBitsHex(): string;
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*/
public function getMostSignificantBitsHex(): string;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see 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;
/**
* @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
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}.
*/
public function getVariant(): ?int;
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}.
*/
public function getVersion(): ?int;
}

View file

@ -0,0 +1,370 @@
<?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;
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;
use function substr;
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.
*
* @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()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
public function getClockSeqHiAndReserved(): string
{
return $this->numberConverter->fromHex($this->fields->getClockSeqHiAndReserved()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()}.
*/
public function getClockSeqHiAndReservedHex(): string
{
return $this->fields->getClockSeqHiAndReserved()->toString();
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
public function getClockSeqLow(): string
{
return $this->numberConverter->fromHex($this->fields->getClockSeqLow()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}.
*/
public function getClockSeqLowHex(): string
{
return $this->fields->getClockSeqLow()->toString();
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
public function getClockSequence(): string
{
return $this->numberConverter->fromHex($this->fields->getClockSeq()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}.
*/
public function getClockSequenceHex(): string
{
return $this->fields->getClockSeq()->toString();
}
/**
* @deprecated This method will be removed in 5.0.0. There is no alternative
* recommendation, so plan accordingly.
*/
public function getNumberConverter(): NumberConverterInterface
{
return $this->numberConverter;
}
/**
* @deprecated In ramsey/uuid version 5.0.0, this will be removed.
* It is available at {@see UuidV1::getDateTime()}.
*
* @return DateTimeImmutable An immutable instance of DateTimeInterface
*
* @throws UnsupportedOperationException if UUID is not time-based
* @throws DateTimeException if DateTime throws an exception/error
*/
public function getDateTime(): DateTimeInterface
{
if ($this->fields->getVersion() !== 1) {
throw new UnsupportedOperationException('Not a time-based UUID');
}
$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);
}
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance.
*
* @return string[]
*/
public function getFieldsHex(): array
{
return [
'time_low' => $this->fields->getTimeLow()->toString(),
'time_mid' => $this->fields->getTimeMid()->toString(),
'time_hi_and_version' => $this->fields->getTimeHiAndVersion()->toString(),
'clock_seq_hi_and_reserved' => $this->fields->getClockSeqHiAndReserved()->toString(),
'clock_seq_low' => $this->fields->getClockSeqLow()->toString(),
'node' => $this->fields->getNode()->toString(),
];
}
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*/
public function getLeastSignificantBits(): string
{
$leastSignificantHex = substr($this->getHex()->toString(), 16);
return $this->numberConverter->fromHex($leastSignificantHex);
}
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*/
public function getLeastSignificantBitsHex(): string
{
return substr($this->getHex()->toString(), 16);
}
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*/
public function getMostSignificantBits(): string
{
$mostSignificantHex = substr($this->getHex()->toString(), 0, 16);
return $this->numberConverter->fromHex($mostSignificantHex);
}
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*/
public function getMostSignificantBitsHex(): string
{
return substr($this->getHex()->toString(), 0, 16);
}
/**
* @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.
*/
public function getNode(): string
{
return $this->numberConverter->fromHex($this->fields->getNode()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getNode()}.
*/
public function getNodeHex(): string
{
return $this->fields->getNode()->toString();
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*/
public function getTimeHiAndVersion(): string
{
return $this->numberConverter->fromHex($this->fields->getTimeHiAndVersion()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}.
*/
public function getTimeHiAndVersionHex(): string
{
return $this->fields->getTimeHiAndVersion()->toString();
}
/**
* @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.
*/
public function getTimeLow(): string
{
return $this->numberConverter->fromHex($this->fields->getTimeLow()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeLow()}.
*/
public function getTimeLowHex(): string
{
return $this->fields->getTimeLow()->toString();
}
/**
* @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.
*/
public function getTimeMid(): string
{
return $this->numberConverter->fromHex($this->fields->getTimeMid()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeMid()}.
*/
public function getTimeMidHex(): string
{
return $this->fields->getTimeMid()->toString();
}
/**
* @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.
*/
public function getTimestamp(): string
{
if ($this->fields->getVersion() !== 1) {
throw new UnsupportedOperationException('Not a time-based UUID');
}
return $this->numberConverter->fromHex($this->fields->getTimestamp()->toString());
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimestamp()}.
*/
public function getTimestampHex(): string
{
if ($this->fields->getVersion() !== 1) {
throw new UnsupportedOperationException('Not a time-based UUID');
}
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\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVariant()}.
*/
public function getVariant(): ?int
{
return $this->fields->getVariant();
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface} instance, you may call
* {@see \Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()}.
*/
public function getVersion(): ?int
{
return $this->fields->getVersion();
}
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that no suitable builder could be found
*/
class BuilderNotFoundException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that the PHP DateTime extension encountered an exception/error
*/
class DateTimeException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,25 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate an exception occurred while dealing with DCE Security
* (version 2) UUIDs
*/
class DceSecurityException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use InvalidArgumentException as PhpInvalidArgumentException;
/**
* Thrown to indicate that the argument received is not valid
*/
class InvalidArgumentException extends PhpInvalidArgumentException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that the bytes being operated on are invalid in some way
*/
class InvalidBytesException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,25 @@
<?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\Exception;
/**
* Thrown to indicate that the string received is not a valid UUID
*
* The InvalidArgumentException that this extends is the ramsey/uuid version
* of this exception. It exists in the same namespace as this class.
*/
class InvalidUuidStringException extends InvalidArgumentException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,25 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that an error occurred while attempting to hash a
* namespace and name
*/
class NameException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that attempting to fetch or create a node ID encountered an error
*/
class NodeException extends PhpRuntimeException implements UuidExceptionInterface
{
}

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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that the source of random data encountered an error
*
* This exception is used mostly to indicate that random_bytes() or random_int()
* threw an exception. However, it may be used for other sources of random data.
*/
class RandomSourceException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that the source of time encountered an error
*/
class TimeSourceException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate a builder is unable to build a UUID
*/
class UnableToBuildUuidException extends PhpRuntimeException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,24 @@
<?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\Exception;
use LogicException as PhpLogicException;
/**
* Thrown to indicate that the requested operation is not supported
*/
class UnsupportedOperationException extends PhpLogicException implements UuidExceptionInterface
{
}

View file

@ -0,0 +1,21 @@
<?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\Exception;
use Throwable;
interface UuidExceptionInterface extends Throwable
{
}

View file

@ -0,0 +1,449 @@
<?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;
use Ramsey\Uuid\Builder\BuilderCollection;
use Ramsey\Uuid\Builder\FallbackBuilder;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Codec\GuidStringCodec;
use Ramsey\Uuid\Codec\StringCodec;
use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
use Ramsey\Uuid\Converter\Time\PhpTimeConverter;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Generator\DceSecurityGenerator;
use Ramsey\Uuid\Generator\DceSecurityGeneratorInterface;
use Ramsey\Uuid\Generator\NameGeneratorFactory;
use Ramsey\Uuid\Generator\NameGeneratorInterface;
use Ramsey\Uuid\Generator\PeclUuidNameGenerator;
use Ramsey\Uuid\Generator\PeclUuidRandomGenerator;
use Ramsey\Uuid\Generator\PeclUuidTimeGenerator;
use Ramsey\Uuid\Generator\RandomGeneratorFactory;
use Ramsey\Uuid\Generator\RandomGeneratorInterface;
use Ramsey\Uuid\Generator\TimeGeneratorFactory;
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
use Ramsey\Uuid\Guid\GuidBuilder;
use Ramsey\Uuid\Math\BrickMathCalculator;
use Ramsey\Uuid\Math\CalculatorInterface;
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;
use Ramsey\Uuid\Provider\Time\SystemTimeProvider;
use Ramsey\Uuid\Provider\TimeProviderInterface;
use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder;
use Ramsey\Uuid\Validator\GenericValidator;
use Ramsey\Uuid\Validator\ValidatorInterface;
use const PHP_INT_SIZE;
/**
* FeatureSet detects and exposes available features in the current environment
*
* A feature set is used by UuidFactory to determine the available features and
* capabilities of the environment.
*/
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;
/**
* @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 $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
* to generate version 1 UUIDs
*/
public function __construct(
bool $useGuids = false,
bool $force32Bit = false,
bool $forceNoBigNumber = false,
bool $ignoreSystemNode = false,
bool $enablePecl = false
) {
$this->disableBigNumber = $forceNoBigNumber;
$this->disable64Bit = $force32Bit;
$this->ignoreSystemNode = $ignoreSystemNode;
$this->enablePecl = $enablePecl;
$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();
}
/**
* Returns the builder configured for this environment
*/
public function getBuilder(): UuidBuilderInterface
{
return $this->builder;
}
/**
* Returns the calculator configured for this environment
*/
public function getCalculator(): CalculatorInterface
{
return $this->calculator;
}
/**
* Returns the codec configured for this environment
*/
public function getCodec(): CodecInterface
{
return $this->codec;
}
/**
* Returns the DCE Security generator configured for this environment
*/
public function getDceSecurityGenerator(): DceSecurityGeneratorInterface
{
return $this->dceSecurityGenerator;
}
/**
* Returns the name generator configured for this environment
*/
public function getNameGenerator(): NameGeneratorInterface
{
return $this->nameGenerator;
}
/**
* Returns the node provider configured for this environment
*/
public function getNodeProvider(): NodeProviderInterface
{
return $this->nodeProvider;
}
/**
* Returns the number converter configured for this environment
*/
public function getNumberConverter(): NumberConverterInterface
{
return $this->numberConverter;
}
/**
* Returns the random generator configured for this environment
*/
public function getRandomGenerator(): RandomGeneratorInterface
{
return $this->randomGenerator;
}
/**
* Returns the time converter configured for this environment
*/
public function getTimeConverter(): TimeConverterInterface
{
return $this->timeConverter;
}
/**
* Returns the time generator configured for this environment
*/
public function getTimeGenerator(): TimeGeneratorInterface
{
return $this->timeGenerator;
}
/**
* Returns the validator configured for this environment
*/
public function getValidator(): ValidatorInterface
{
return $this->validator;
}
/**
* Sets the calculator to use in this environment
*/
public function setCalculator(CalculatorInterface $calculator): void
{
$this->calculator = $calculator;
$this->numberConverter = $this->buildNumberConverter($calculator);
$this->timeConverter = $this->buildTimeConverter($calculator);
/** @psalm-suppress RedundantPropertyInitializationCheck */
if (isset($this->timeProvider)) {
$this->timeGenerator = $this->buildTimeGenerator($this->timeProvider);
}
}
/**
* Sets the DCE Security provider to use in this environment
*/
public function setDceSecurityProvider(DceSecurityProviderInterface $dceSecurityProvider): void
{
$this->dceSecurityGenerator = $this->buildDceSecurityGenerator($dceSecurityProvider);
}
/**
* Sets the node provider to use in this environment
*/
public function setNodeProvider(NodeProviderInterface $nodeProvider): void
{
$this->nodeProvider = $nodeProvider;
$this->timeGenerator = $this->buildTimeGenerator($this->timeProvider);
}
/**
* Sets the time provider to use in this environment
*/
public function setTimeProvider(TimeProviderInterface $timeProvider): void
{
$this->timeProvider = $timeProvider;
$this->timeGenerator = $this->buildTimeGenerator($timeProvider);
}
/**
* Set the validator to use in this environment
*/
public function setValidator(ValidatorInterface $validator): void
{
$this->validator = $validator;
}
/**
* Returns a codec configured for this environment
*
* @param bool $useGuids Whether to build UUIDs using the GuidStringCodec
*/
private function buildCodec(bool $useGuids = false): CodecInterface
{
if ($useGuids) {
return new GuidStringCodec($this->builder);
}
return new StringCodec($this->builder);
}
/**
* Returns a DCE Security generator configured for this environment
*/
private function buildDceSecurityGenerator(
DceSecurityProviderInterface $dceSecurityProvider
): DceSecurityGeneratorInterface {
return new DceSecurityGenerator(
$this->numberConverter,
$this->timeGenerator,
$dceSecurityProvider
);
}
/**
* Returns a node provider configured for this environment
*/
private function buildNodeProvider(): NodeProviderInterface
{
if ($this->ignoreSystemNode) {
return new RandomNodeProvider();
}
return new FallbackNodeProvider(new NodeProviderCollection([
new SystemNodeProvider(),
new RandomNodeProvider(),
]));
}
/**
* Returns a number converter configured for this environment
*/
private function buildNumberConverter(CalculatorInterface $calculator): NumberConverterInterface
{
return new GenericNumberConverter($calculator);
}
/**
* Returns a random generator configured for this environment
*/
private function buildRandomGenerator(): RandomGeneratorInterface
{
if ($this->enablePecl) {
return new PeclUuidRandomGenerator();
}
return (new RandomGeneratorFactory())->getGenerator();
}
/**
* Returns a time generator configured for this environment
*
* @param TimeProviderInterface $timeProvider The time provider to use with
* the time generator
*/
private function buildTimeGenerator(TimeProviderInterface $timeProvider): TimeGeneratorInterface
{
if ($this->enablePecl) {
return new PeclUuidTimeGenerator();
}
return (new TimeGeneratorFactory(
$this->nodeProvider,
$this->timeConverter,
$timeProvider
))->getGenerator();
}
/**
* Returns a name generator configured for this environment
*/
private function buildNameGenerator(): NameGeneratorInterface
{
if ($this->enablePecl) {
return new PeclUuidNameGenerator();
}
return (new NameGeneratorFactory())->getGenerator();
}
/**
* Returns a time converter configured for this environment
*/
private function buildTimeConverter(CalculatorInterface $calculator): TimeConverterInterface
{
$genericConverter = new GenericTimeConverter($calculator);
if ($this->is64BitSystem()) {
return new PhpTimeConverter($calculator, $genericConverter);
}
return $genericConverter;
}
/**
* Returns a UUID builder configured for this environment
*
* @param bool $useGuids Whether to build UUIDs using the GuidStringCodec
*/
private function buildUuidBuilder(bool $useGuids = false): UuidBuilderInterface
{
if ($useGuids) {
return new GuidBuilder($this->numberConverter, $this->timeConverter);
}
/** @psalm-suppress ImpureArgument */
return new FallbackBuilder(new BuilderCollection([
new Rfc4122UuidBuilder($this->numberConverter, $this->timeConverter),
new NonstandardUuidBuilder($this->numberConverter, $this->timeConverter),
]));
}
/**
* Returns true if the PHP build is 64-bit
*/
private function is64BitSystem(): bool
{
return PHP_INT_SIZE === 8 && !$this->disable64Bit;
}
}

View file

@ -0,0 +1,32 @@
<?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\Fields;
use Serializable;
/**
* UUIDs are comprised of unsigned integers, the bytes of which are separated
* into fields and arranged in a particular layout defined by the specification
* for the variant
*
* @psalm-immutable
*/
interface FieldsInterface extends Serializable
{
/**
* Returns the bytes that comprise the fields
*/
public function getBytes(): string;
}

View file

@ -0,0 +1,86 @@
<?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\Fields;
use ValueError;
use function base64_decode;
use function sprintf;
use function strlen;
/**
* Provides common serialization functionality to fields
*
* @psalm-immutable
*/
trait SerializableFieldsTrait
{
/**
* @param string $bytes The bytes that comprise the fields
*/
abstract public function __construct(string $bytes);
/**
* Returns the bytes that comprise the fields
*/
abstract public function getBytes(): string;
/**
* Returns a string representation of object
*/
public function serialize(): string
{
return $this->getBytes();
}
/**
* @return array{bytes: string}
*/
public function __serialize(): array
{
return ['bytes' => $this->getBytes()];
}
/**
* Constructs the object from a serialized string representation
*
* @param string $serialized The serialized string representation of the object
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @psalm-suppress UnusedMethodCall
*/
public function unserialize($serialized): void
{
if (strlen($serialized) === 16) {
$this->__construct($serialized);
} else {
$this->__construct(base64_decode($serialized));
}
}
/**
* @param array{bytes: string} $data
*/
public function __unserialize(array $data): void
{
// @codeCoverageIgnoreStart
if (!isset($data['bytes'])) {
throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__));
}
// @codeCoverageIgnoreEnd
$this->unserialize($data['bytes']);
}
}

View file

@ -0,0 +1,127 @@
<?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 Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use function bin2hex;
use function explode;
use function hex2bin;
use function microtime;
use function str_pad;
use function substr;
use const STR_PAD_LEFT;
/**
* CombGenerator generates COMBs (combined UUID/timestamp)
*
* The CombGenerator, when used with the StringCodec (and, by proxy, the
* TimestampLastCombCodec) or the TimestampFirstCombCodec, combines the current
* timestamp with a UUID (hence the name "COMB"). The timestamp either appears
* as the first or last 48 bits of the COMB, depending on the codec used.
*
* By default, COMBs will have the timestamp set as the last 48 bits of the
* identifier.
*
* ``` php
* $factory = new UuidFactory();
*
* $factory->setRandomGenerator(new CombGenerator(
* $factory->getRandomGenerator(),
* $factory->getNumberConverter()
* ));
*
* $comb = $factory->uuid4();
* ```
*
* To generate a COMB with the timestamp as the first 48 bits, set the
* TimestampFirstCombCodec as the codec.
*
* ``` php
* $factory->setCodec(new TimestampFirstCombCodec($factory->getUuidBuilder()));
* ```
*
* @link https://www.informit.com/articles/printerfriendly/25862 The Cost of GUIDs as Primary Keys
*/
class CombGenerator implements RandomGeneratorInterface
{
public const TIMESTAMP_BYTES = 6;
/**
* @var RandomGeneratorInterface
*/
private $randomGenerator;
/**
* @var NumberConverterInterface
*/
private $converter;
public function __construct(
RandomGeneratorInterface $generator,
NumberConverterInterface $numberConverter
) {
$this->converter = $numberConverter;
$this->randomGenerator = $generator;
}
/**
* @throws InvalidArgumentException if $length is not a positive integer
* greater than or equal to CombGenerator::TIMESTAMP_BYTES
*
* @inheritDoc
*/
public function generate(int $length): string
{
if ($length < self::TIMESTAMP_BYTES || $length < 0) {
throw new InvalidArgumentException(
'Length must be a positive integer greater than or equal to ' . self::TIMESTAMP_BYTES
);
}
$hash = '';
if (self::TIMESTAMP_BYTES > 0 && $length > self::TIMESTAMP_BYTES) {
$hash = $this->randomGenerator->generate($length - self::TIMESTAMP_BYTES);
}
$lsbTime = str_pad(
$this->converter->toHex($this->timestamp()),
self::TIMESTAMP_BYTES * 2,
'0',
STR_PAD_LEFT
);
return (string) hex2bin(
str_pad(
bin2hex($hash),
$length - self::TIMESTAMP_BYTES,
'0'
)
. $lsbTime
);
}
/**
* Returns current timestamp a string integer, precise to 0.00001 seconds
*/
private function timestamp(): string
{
$time = explode(' ', microtime(false));
return $time[1] . substr($time[0], 2, 5);
}
}

View file

@ -0,0 +1,160 @@
<?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 Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Exception\DceSecurityException;
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Uuid;
use function hex2bin;
use function in_array;
use function pack;
use function str_pad;
use function strlen;
use function substr_replace;
use const STR_PAD_LEFT;
/**
* DceSecurityGenerator generates strings of binary data based on a local
* domain, local identifier, node ID, clock sequence, and the current time
*/
class DceSecurityGenerator implements DceSecurityGeneratorInterface
{
private const DOMAINS = [
Uuid::DCE_DOMAIN_PERSON,
Uuid::DCE_DOMAIN_GROUP,
Uuid::DCE_DOMAIN_ORG,
];
/**
* Upper bounds for the clock sequence in DCE Security UUIDs.
*/
private const CLOCK_SEQ_HIGH = 63;
/**
* Lower bounds for the clock sequence in DCE Security UUIDs.
*/
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
) {
$this->numberConverter = $numberConverter;
$this->timeGenerator = $timeGenerator;
$this->dceSecurityProvider = $dceSecurityProvider;
}
public function generate(
int $localDomain,
?IntegerObject $localIdentifier = null,
?Hexadecimal $node = null,
?int $clockSeq = null
): string {
if (!in_array($localDomain, self::DOMAINS)) {
throw new DceSecurityException(
'Local domain must be a valid DCE Security domain'
);
}
if ($localIdentifier && $localIdentifier->isNegative()) {
throw new DceSecurityException(
'Local identifier out of bounds; it must be a value between 0 and 4294967295'
);
}
if ($clockSeq > self::CLOCK_SEQ_HIGH || $clockSeq < self::CLOCK_SEQ_LOW) {
throw new DceSecurityException(
'Clock sequence out of bounds; it must be a value between 0 and 63'
);
}
switch ($localDomain) {
case Uuid::DCE_DOMAIN_ORG:
if ($localIdentifier === null) {
throw new DceSecurityException(
'A local identifier must be provided for the org domain'
);
}
break;
case Uuid::DCE_DOMAIN_PERSON:
if ($localIdentifier === null) {
$localIdentifier = $this->dceSecurityProvider->getUid();
}
break;
case Uuid::DCE_DOMAIN_GROUP:
default:
if ($localIdentifier === null) {
$localIdentifier = $this->dceSecurityProvider->getGid();
}
break;
}
$identifierHex = $this->numberConverter->toHex($localIdentifier->toString());
// The maximum value for the local identifier is 0xffffffff, or
// 4294967295. This is 8 hexadecimal digits, so if the length of
// hexadecimal digits is greater than 8, we know the value is greater
// than 0xffffffff.
if (strlen($identifierHex) > 8) {
throw new DceSecurityException(
'Local identifier out of bounds; it must be a value between 0 and 4294967295'
);
}
$domainByte = pack('n', $localDomain)[1];
$identifierBytes = (string) hex2bin(str_pad($identifierHex, 8, '0', STR_PAD_LEFT));
if ($node instanceof Hexadecimal) {
$node = $node->toString();
}
// Shift the clock sequence 8 bits to the left, so it matches 0x3f00.
if ($clockSeq !== null) {
$clockSeq = $clockSeq << 8;
}
$bytes = $this->timeGenerator->generate($node, $clockSeq);
// 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;
}
}

View file

@ -0,0 +1,53 @@
<?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 Ramsey\Uuid\Rfc4122\UuidV2;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
/**
* A DCE Security generator generates strings of binary data based on a local
* domain, local identifier, node ID, clock sequence, and the current time
*
* @see UuidV2
*/
interface DceSecurityGeneratorInterface
{
/**
* Generate a binary string from a local domain, local identifier, node ID,
* clock sequence, and current time
*
* @param int $localDomain The local domain to use when generating bytes,
* according to DCE Security
* @param IntegerObject|null $localIdentifier The local identifier for the
* given domain; this may be a UID or GID on POSIX systems, if the local
* domain is person or group, or it may be a site-defined identifier
* if the local domain is org
* @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 string A binary string
*/
public function generate(
int $localDomain,
?IntegerObject $localIdentifier = null,
?Hexadecimal $node = null,
?int $clockSeq = null
): string;
}

View file

@ -0,0 +1,48 @@
<?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 Ramsey\Uuid\Exception\NameException;
use Ramsey\Uuid\UuidInterface;
use ValueError;
use function hash;
/**
* DefaultNameGenerator generates strings of binary data based on a namespace,
* name, and hashing algorithm
*/
class DefaultNameGenerator implements NameGeneratorInterface
{
/** @psalm-pure */
public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string
{
try {
/** @var string|bool $bytes */
$bytes = @hash($hashAlgorithm, $ns->getBytes() . $name, true);
} catch (ValueError $e) {
$bytes = false; // keep same behavior than PHP 7
}
if ($bytes === false) {
throw new NameException(sprintf(
'Unable to hash namespace and name with algorithm \'%s\'',
$hashAlgorithm
));
}
return (string) $bytes;
}
}

View file

@ -0,0 +1,147 @@
<?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 Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Exception\RandomSourceException;
use Ramsey\Uuid\Exception\TimeSourceException;
use Ramsey\Uuid\Provider\NodeProviderInterface;
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 sprintf;
use function str_pad;
use function strlen;
use const STR_PAD_LEFT;
/**
* DefaultTimeGenerator generates strings of binary data based on a node ID,
* clock sequence, and the current time
*/
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
) {
$this->nodeProvider = $nodeProvider;
$this->timeConverter = $timeConverter;
$this->timeProvider = $timeProvider;
}
/**
* @throws InvalidArgumentException if the parameters contain invalid values
* @throws RandomSourceException if random_int() throws an exception/error
*
* @inheritDoc
*/
public function generate($node = null, ?int $clockSeq = null): string
{
if ($node instanceof Hexadecimal) {
$node = $node->toString();
}
$node = $this->getValidNode($node);
if ($clockSeq === null) {
try {
// This does not use "stable storage"; see RFC 4122, Section 4.2.1.1.
$clockSeq = random_int(0, 0x3fff);
} catch (Throwable $exception) {
throw new RandomSourceException(
$exception->getMessage(),
(int) $exception->getCode(),
$exception
);
}
}
$time = $this->timeProvider->getTime();
$uuidTime = $this->timeConverter->calculateTime(
$time->getSeconds()->toString(),
$time->getMicroseconds()->toString()
);
$timeHex = str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT);
if (strlen($timeHex) !== 16) {
throw new TimeSourceException(sprintf(
'The generated time of \'%s\' is larger than expected',
$timeHex
));
}
$timeBytes = (string) hex2bin($timeHex);
return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7]
. $timeBytes[2] . $timeBytes[3]
. $timeBytes[0] . $timeBytes[1]
. pack('n*', $clockSeq)
. $node;
}
/**
* 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
*
* @return string 6-byte binary string representation of the node
*
* @throws InvalidArgumentException
*/
private function getValidNode($node): string
{
if ($node === null) {
$node = $this->nodeProvider->getNode();
}
// Convert the node to hex, if it is still an integer.
if (is_int($node)) {
$node = dechex($node);
}
if (!ctype_xdigit((string) $node) || strlen((string) $node) > 12) {
throw new InvalidArgumentException('Invalid node value');
}
return (string) hex2bin(str_pad((string) $node, 12, '0', STR_PAD_LEFT));
}
}

View file

@ -0,0 +1,30 @@
<?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;
/**
* NameGeneratorFactory retrieves a default name generator, based on the
* environment
*/
class NameGeneratorFactory
{
/**
* Returns a default name generator, based on the current environment
*/
public function getGenerator(): NameGeneratorInterface
{
return new DefaultNameGenerator();
}
}

View file

@ -0,0 +1,38 @@
<?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 Ramsey\Uuid\UuidInterface;
/**
* A name generator generates strings of binary data created by hashing together
* a namespace with a name, according to a hashing algorithm
*/
interface NameGeneratorInterface
{
/**
* Generate a binary string from a namespace and name hashed together with
* the specified hashing algorithm
*
* @param UuidInterface $ns The namespace
* @param string $name The name to use for creating a UUID
* @param string $hashAlgorithm The hashing algorithm to use
*
* @return string A binary string
*
* @psalm-pure
*/
public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string;
}

View file

@ -0,0 +1,54 @@
<?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 Ramsey\Uuid\Exception\NameException;
use Ramsey\Uuid\UuidInterface;
use function sprintf;
use function uuid_generate_md5;
use function uuid_generate_sha1;
use function uuid_parse;
/**
* PeclUuidNameGenerator generates strings of binary data from a namespace and a
* name, using ext-uuid
*
* @link https://pecl.php.net/package/uuid ext-uuid
*/
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(
'Unable to hash namespace and name with algorithm \'%s\'',
$hashAlgorithm
));
}
return uuid_parse($uuid);
}
}

View file

@ -0,0 +1,35 @@
<?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 function uuid_create;
use function uuid_parse;
use const UUID_TYPE_RANDOM;
/**
* PeclUuidRandomGenerator generates strings of random binary data using ext-uuid
*
* @link https://pecl.php.net/package/uuid ext-uuid
*/
class PeclUuidRandomGenerator implements RandomGeneratorInterface
{
public function generate(int $length): string
{
$uuid = uuid_create(UUID_TYPE_RANDOM);
return uuid_parse($uuid);
}
}

View file

@ -0,0 +1,39 @@
<?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 function uuid_create;
use function uuid_parse;
use const UUID_TYPE_TIME;
/**
* PeclUuidTimeGenerator generates strings of binary data for time-base UUIDs,
* using ext-uuid
*
* @link https://pecl.php.net/package/uuid ext-uuid
*/
class PeclUuidTimeGenerator implements TimeGeneratorInterface
{
/**
* @inheritDoc
*/
public function generate($node = null, ?int $clockSeq = null): string
{
$uuid = uuid_create(UUID_TYPE_TIME);
return uuid_parse($uuid);
}
}

View file

@ -0,0 +1,45 @@
<?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 Ramsey\Uuid\Exception\RandomSourceException;
use Throwable;
/**
* RandomBytesGenerator generates strings of random binary data using the
* built-in `random_bytes()` PHP function
*
* @link http://php.net/random_bytes random_bytes()
*/
class RandomBytesGenerator implements RandomGeneratorInterface
{
/**
* @throws RandomSourceException if random_bytes() throws an exception/error
*
* @inheritDoc
*/
public function generate(int $length): string
{
try {
return random_bytes($length);
} catch (Throwable $exception) {
throw new RandomSourceException(
$exception->getMessage(),
(int) $exception->getCode(),
$exception
);
}
}
}

View file

@ -0,0 +1,30 @@
<?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;
/**
* RandomGeneratorFactory retrieves a default random generator, based on the
* environment
*/
class RandomGeneratorFactory
{
/**
* Returns a default random generator, based on the current environment
*/
public function getGenerator(): RandomGeneratorInterface
{
return new RandomBytesGenerator();
}
}

View file

@ -0,0 +1,30 @@
<?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;
/**
* A random generator generates strings of random binary data
*/
interface RandomGeneratorInterface
{
/**
* Generates a string of randomized binary data
*
* @param int $length The number of bytes of random binary data to generate
*
* @return string A binary string
*/
public function generate(int $length): string;
}

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\Generator;
use RandomLib\Factory;
use RandomLib\Generator;
/**
* RandomLibAdapter generates strings of random binary data using the
* paragonie/random-lib library
*
* @link https://packagist.org/packages/paragonie/random-lib paragonie/random-lib
*/
class RandomLibAdapter implements RandomGeneratorInterface
{
/**
* @var Generator
*/
private $generator;
/**
* Constructs a RandomLibAdapter
*
* By default, if no Generator is passed in, this creates a high-strength
* generator to use when generating random binary data.
*
* @param Generator|null $generator The generator to use when generating binary data
*/
public function __construct(?Generator $generator = null)
{
if ($generator === null) {
$factory = new Factory();
$generator = $factory->getHighStrengthGenerator();
}
$this->generator = $generator;
}
public function generate(int $length): string
{
return $this->generator->generate($length);
}
}

View file

@ -0,0 +1,63 @@
<?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 Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Provider\TimeProviderInterface;
/**
* TimeGeneratorFactory retrieves a default time generator, based on the
* environment
*/
class TimeGeneratorFactory
{
/**
* @var NodeProviderInterface
*/
private $nodeProvider;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @var TimeProviderInterface
*/
private $timeProvider;
public function __construct(
NodeProviderInterface $nodeProvider,
TimeConverterInterface $timeConverter,
TimeProviderInterface $timeProvider
) {
$this->nodeProvider = $nodeProvider;
$this->timeConverter = $timeConverter;
$this->timeProvider = $timeProvider;
}
/**
* Returns a default time generator, based on the current environment
*/
public function getGenerator(): TimeGeneratorInterface
{
return new DefaultTimeGenerator(
$this->nodeProvider,
$this->timeConverter,
$this->timeProvider
);
}
}

View file

@ -0,0 +1,38 @@
<?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 Ramsey\Uuid\Type\Hexadecimal;
/**
* A time generator generates strings of binary data based on a node ID,
* clock sequence, and the current time
*/
interface TimeGeneratorInterface
{
/**
* Generate a binary string from a node ID, clock sequence, and 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|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 string A binary string
*/
public function generate($node = null, ?int $clockSeq = null): string;
}

View file

@ -0,0 +1,194 @@
<?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\Guid;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Fields\SerializableFieldsTrait;
use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\Rfc4122\NilTrait;
use Ramsey\Uuid\Rfc4122\VariantTrait;
use Ramsey\Uuid\Rfc4122\VersionTrait;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Uuid;
use function bin2hex;
use function dechex;
use function hexdec;
use function pack;
use function sprintf;
use function str_pad;
use function strlen;
use function substr;
use function unpack;
use const STR_PAD_LEFT;
/**
* GUIDs are comprised of a set of named fields, according to RFC 4122
*
* @see Guid
*
* @psalm-immutable
*/
final class Fields implements FieldsInterface
{
use NilTrait;
use SerializableFieldsTrait;
use VariantTrait;
use VersionTrait;
/**
* @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
* @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)
{
if (strlen($bytes) !== 16) {
throw new InvalidArgumentException(
'The byte string must be 16 bytes long; '
. 'received ' . strlen($bytes) . ' bytes'
);
}
$this->bytes = $bytes;
if (!$this->isCorrectVariant()) {
throw new InvalidArgumentException(
'The byte string received does not conform to the RFC '
. '4122 or Microsoft Corporation variants'
);
}
if (!$this->isCorrectVersion()) {
throw new InvalidArgumentException(
'The byte string received does not contain a valid version'
);
}
}
public function getBytes(): string
{
return $this->bytes;
}
public function getTimeLow(): Hexadecimal
{
// Swap the bytes from little endian to network byte order.
/** @var array $hex */
$hex = unpack(
'H*',
pack(
'v*',
hexdec(bin2hex(substr($this->bytes, 2, 2))),
hexdec(bin2hex(substr($this->bytes, 0, 2)))
)
);
return new Hexadecimal((string) ($hex[1] ?? ''));
}
public function getTimeMid(): Hexadecimal
{
// Swap the bytes from little endian to network byte order.
/** @var array $hex */
$hex = unpack(
'H*',
pack(
'v',
hexdec(bin2hex(substr($this->bytes, 4, 2)))
)
);
return new Hexadecimal((string) ($hex[1] ?? ''));
}
public function getTimeHiAndVersion(): Hexadecimal
{
// Swap the bytes from little endian to network byte order.
/** @var array $hex */
$hex = unpack(
'H*',
pack(
'v',
hexdec(bin2hex(substr($this->bytes, 6, 2)))
)
);
return new Hexadecimal((string) ($hex[1] ?? ''));
}
public function getTimestamp(): Hexadecimal
{
return new Hexadecimal(sprintf(
'%03x%04s%08s',
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
$this->getTimeMid()->toString(),
$this->getTimeLow()->toString()
));
}
public function getClockSeq(): Hexadecimal
{
$clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT));
}
public function getClockSeqHiAndReserved(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1)));
}
public function getClockSeqLow(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1)));
}
public function getNode(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 10)));
}
public function getVersion(): ?int
{
if ($this->isNil()) {
return null;
}
/** @var array $parts */
$parts = unpack('n*', $this->bytes);
return ((int) $parts[4] >> 4) & 0x00f;
}
private function isCorrectVariant(): bool
{
if ($this->isNil()) {
return true;
}
$variant = $this->getVariant();
return $variant === Uuid::RFC_4122 || $variant === Uuid::RESERVED_MICROSOFT;
}
}

View file

@ -0,0 +1,61 @@
<?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\Guid;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Uuid;
/**
* Guid represents a UUID with "native" (little-endian) byte order
*
* From Wikipedia:
*
* > The first three fields are unsigned 32- and 16-bit integers and are subject
* > to swapping, while the last two fields consist of uninterpreted bytes, not
* > subject to swapping. This byte swapping applies even for versions 3, 4, and
* > 5, where the canonical fields do not correspond to the content of the UUID.
*
* The first three fields of a GUID are encoded in little-endian byte order,
* while the last three fields are in network (big-endian) byte order. This is
* according to the history of the Microsoft definition of a GUID.
*
* According to the .NET Guid.ToByteArray method documentation:
*
* > Note that the order of bytes in the returned byte array is different from
* > the string representation of a Guid value. The order of the beginning
* > four-byte group and the next two two-byte groups is reversed, whereas the
* > order of the last two-byte group and the closing six-byte group is the
* > same.
*
* @link https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants UUID Variants on Wikipedia
* @link https://docs.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid Windows GUID structure
* @link https://docs.microsoft.com/en-us/dotnet/api/system.guid .NET Guid Struct
* @link https://docs.microsoft.com/en-us/dotnet/api/system.guid.tobytearray .NET Guid.ToByteArray Method
*
* @psalm-immutable
*/
final class Guid extends Uuid
{
public function __construct(
Fields $fields,
NumberConverterInterface $numberConverter,
CodecInterface $codec,
TimeConverterInterface $timeConverter
) {
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
}

View file

@ -0,0 +1,89 @@
<?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\Guid;
use Ramsey\Uuid\Builder\UuidBuilderInterface;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Exception\UnableToBuildUuidException;
use Ramsey\Uuid\UuidInterface;
use Throwable;
/**
* GuidBuilder builds instances of Guid
*
* @see Guid
*
* @psalm-immutable
*/
class GuidBuilder implements UuidBuilderInterface
{
/**
* @var NumberConverterInterface
*/
private $numberConverter;
/**
* @var TimeConverterInterface
*/
private $timeConverter;
/**
* @param NumberConverterInterface $numberConverter The number converter to
* use when constructing the Guid
* @param TimeConverterInterface $timeConverter The time converter to use
* for converting timestamps extracted from a UUID to Unix timestamps
*/
public function __construct(
NumberConverterInterface $numberConverter,
TimeConverterInterface $timeConverter
) {
$this->numberConverter = $numberConverter;
$this->timeConverter = $timeConverter;
}
/**
* Builds and returns a Guid
*
* @param CodecInterface $codec The codec to use for building this Guid instance
* @param string $bytes The byte string from which to construct a UUID
*
* @return Guid The GuidBuilder returns an instance of Ramsey\Uuid\Guid\Guid
*
* @psalm-pure
*/
public function build(CodecInterface $codec, string $bytes): UuidInterface
{
try {
return new Guid(
$this->buildFields($bytes),
$this->numberConverter,
$codec,
$this->timeConverter
);
} catch (Throwable $e) {
throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e);
}
}
/**
* Proxy method to allow injecting a mock, for testing
*/
protected function buildFields(string $bytes): Fields
{
return new Fields($bytes);
}
}

View file

@ -0,0 +1,575 @@
<?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\Lazy;
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\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\UuidFactory;
use Ramsey\Uuid\UuidInterface;
use ValueError;
use function assert;
use function bin2hex;
use function hex2bin;
use function sprintf;
use function str_replace;
use function substr;
/**
* Lazy version of a UUID: its format has not been determined yet, so it is mostly only usable for string/bytes
* conversion. This object optimizes instantiation, serialization and string conversion time, at the cost of
* increased overhead for more advanced UUID operations.
*
* @internal this type is used internally for performance reasons, and is not supposed to be directly referenced
* in consumer libraries.
*
* @psalm-immutable
*
* Note: the {@see FieldsInterface} does not declare methods that deprecated API
* relies upon: the API has been ported from the {@see \Ramsey\Uuid\Uuid} definition,
* and is deprecated anyway.
* Note: the deprecated API from {@see \Ramsey\Uuid\Uuid} is in use here (on purpose): it will be removed
* once the deprecated API is gone from this class too.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
*/
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)
{
$this->uuid = $uuid;
}
/** @psalm-pure */
public static function fromBytes(string $bytes): self
{
$base16Uuid = bin2hex($bytes);
return new self(
substr($base16Uuid, 0, 8)
. '-'
. substr($base16Uuid, 8, 4)
. '-'
. substr($base16Uuid, 12, 4)
. '-'
. substr($base16Uuid, 16, 4)
. '-'
. substr($base16Uuid, 20, 12)
);
}
public function serialize(): string
{
return $this->uuid;
}
/**
* @return array{string: string}
*
* @psalm-return array{string: non-empty-string}
*/
public function __serialize(): array
{
return ['string' => $this->uuid];
}
/**
* {@inheritDoc}
*
* @param string $serialized
*
* @psalm-param non-empty-string $serialized
*/
public function unserialize($serialized): void
{
$this->uuid = $serialized;
}
/**
* @param array{string: string} $data
*
* @psalm-param array{string: non-empty-string} $data
*/
public function __unserialize(array $data): void
{
// @codeCoverageIgnoreStart
if (!isset($data['string'])) {
throw new ValueError(sprintf('%s(): Argument #1 ($data) is invalid', __METHOD__));
}
// @codeCoverageIgnoreEnd
$this->unserialize($data['string']);
}
/** @psalm-suppress DeprecatedMethod */
public function getNumberConverter(): NumberConverterInterface
{
return ($this->unwrapped ?? $this->unwrap())
->getNumberConverter();
}
/**
* {@inheritDoc}
*
* @psalm-suppress DeprecatedMethod
*/
public function getFieldsHex(): array
{
return ($this->unwrapped ?? $this->unwrap())
->getFieldsHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getClockSeqHiAndReservedHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getClockSeqHiAndReservedHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getClockSeqLowHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getClockSeqLowHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getClockSequenceHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getClockSequenceHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getDateTime(): DateTimeInterface
{
return ($this->unwrapped ?? $this->unwrap())
->getDateTime();
}
/** @psalm-suppress DeprecatedMethod */
public function getLeastSignificantBitsHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getLeastSignificantBitsHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getMostSignificantBitsHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getMostSignificantBitsHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getNodeHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getNodeHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getTimeHiAndVersionHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getTimeHiAndVersionHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getTimeLowHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getTimeLowHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getTimeMidHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getTimeMidHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getTimestampHex(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getTimestampHex();
}
/** @psalm-suppress DeprecatedMethod */
public function getUrn(): string
{
return ($this->unwrapped ?? $this->unwrap())
->getUrn();
}
/** @psalm-suppress DeprecatedMethod */
public function getVariant(): ?int
{
return ($this->unwrapped ?? $this->unwrap())
->getVariant();
}
/** @psalm-suppress DeprecatedMethod */
public function getVersion(): ?int
{
return ($this->unwrapped ?? $this->unwrap())
->getVersion();
}
public function compareTo(UuidInterface $other): int
{
return ($this->unwrapped ?? $this->unwrap())
->compareTo($other);
}
public function equals(?object $other): bool
{
if (! $other instanceof UuidInterface) {
return false;
}
return $this->uuid === $other->toString();
}
/**
* {@inheritDoc}
*
* @psalm-suppress MoreSpecificReturnType
* @psalm-suppress LessSpecificReturnStatement we know that {@see self::$uuid} is a non-empty string, so
* we know that {@see hex2bin} will retrieve a non-empty string too.
*/
public function getBytes(): string
{
/** @phpstan-ignore-next-line PHPStan complains that this is not a non-empty-string. */
return (string) hex2bin(str_replace('-', '', $this->uuid));
}
public function getFields(): FieldsInterface
{
return ($this->unwrapped ?? $this->unwrap())
->getFields();
}
public function getHex(): Hexadecimal
{
return ($this->unwrapped ?? $this->unwrap())
->getHex();
}
public function getInteger(): IntegerObject
{
return ($this->unwrapped ?? $this->unwrap())
->getInteger();
}
public function toString(): string
{
return $this->uuid;
}
public function __toString(): string
{
return $this->uuid;
}
public function jsonSerialize(): string
{
return $this->uuid;
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqHiAndReserved()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getClockSeqHiAndReserved(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getClockSeqHiAndReserved()
->toString()
);
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeqLow()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getClockSeqLow(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getClockSeqLow()
->toString()
);
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getClockSeq()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getClockSequence(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getClockSeq()
->toString()
);
}
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getLeastSignificantBits(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(substr($instance->getHex()->toString(), 16));
}
/**
* @deprecated This method will be removed in 5.0.0. There is no direct
* alternative, but the same information may be obtained by splitting
* in half the value returned by {@see UuidInterface::getHex()}.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getMostSignificantBits(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(substr($instance->getHex()->toString(), 0, 16));
}
/**
* @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.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getNode(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getNode()
->toString()
);
}
/**
* @deprecated Use {@see UuidInterface::getFields()} to get a
* {@see FieldsInterface} instance. If it is a {@see Rfc4122FieldsInterface}
* instance, you may call {@see Rfc4122FieldsInterface::getTimeHiAndVersion()}
* and use the arbitrary-precision math library of your choice to
* convert it to a string integer.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getTimeHiAndVersion(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getTimeHiAndVersion()
->toString()
);
}
/**
* @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.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getTimeLow(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getTimeLow()
->toString()
);
}
/**
* @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.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getTimeMid(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
return $instance->getNumberConverter()
->fromHex(
$instance->getFields()
->getTimeMid()
->toString()
);
}
/**
* @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.
*
* @psalm-suppress UndefinedInterfaceMethod
* @psalm-suppress DeprecatedMethod
* @psalm-suppress MixedArgument
* @psalm-suppress MixedMethodCall
*/
public function getTimestamp(): string
{
$instance = ($this->unwrapped ?? $this->unwrap());
$fields = $instance->getFields();
if ($fields->getVersion() !== 1) {
throw new UnsupportedOperationException('Not a time-based UUID');
}
return $instance->getNumberConverter()
->fromHex($fields->getTimestamp()->toString());
}
public function toUuidV1(): UuidV1
{
$instance = ($this->unwrapped ?? $this->unwrap());
if ($instance instanceof UuidV1) {
return $instance;
}
assert($instance instanceof UuidV6);
return $instance->toUuidV1();
}
public function toUuidV6(): UuidV6
{
$instance = ($this->unwrapped ?? $this->unwrap());
assert($instance instanceof UuidV6);
return $instance;
}
/**
* @psalm-suppress ImpureMethodCall the retrieval of the factory is a clear violation of purity here: this is a
* known pitfall of the design of this library, where a value object contains
* a mutable reference to a factory. We use a fixed factory here, so the violation
* will not have real-world effects, as this object is only instantiated with the
* default factory settings/features.
* @psalm-suppress InaccessibleProperty property {@see $unwrapped} is used as a cache: we don't expose it to the
* outside world, so we should be fine here.
*/
private function unwrap(): UuidInterface
{
return $this->unwrapped = (new UuidFactory())
->fromString($this->uuid);
}
}

View file

@ -0,0 +1,144 @@
<?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\Math;
use Brick\Math\BigDecimal;
use Brick\Math\BigInteger;
use Brick\Math\Exception\MathException;
use Brick\Math\RoundingMode as BrickMathRounding;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Type\Decimal;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Type\NumberInterface;
/**
* A calculator using the brick/math library for arbitrary-precision arithmetic
*
* @psalm-immutable
*/
final class BrickMathCalculator implements CalculatorInterface
{
private const ROUNDING_MODE_MAP = [
RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY,
RoundingMode::UP => BrickMathRounding::UP,
RoundingMode::DOWN => BrickMathRounding::DOWN,
RoundingMode::CEILING => BrickMathRounding::CEILING,
RoundingMode::FLOOR => BrickMathRounding::FLOOR,
RoundingMode::HALF_UP => BrickMathRounding::HALF_UP,
RoundingMode::HALF_DOWN => BrickMathRounding::HALF_DOWN,
RoundingMode::HALF_CEILING => BrickMathRounding::HALF_CEILING,
RoundingMode::HALF_FLOOR => BrickMathRounding::HALF_FLOOR,
RoundingMode::HALF_EVEN => BrickMathRounding::HALF_EVEN,
];
public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface
{
$sum = BigInteger::of($augend->toString());
foreach ($addends as $addend) {
$sum = $sum->plus($addend->toString());
}
return new IntegerObject((string) $sum);
}
public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface
{
$difference = BigInteger::of($minuend->toString());
foreach ($subtrahends as $subtrahend) {
$difference = $difference->minus($subtrahend->toString());
}
return new IntegerObject((string) $difference);
}
public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface
{
$product = BigInteger::of($multiplicand->toString());
foreach ($multipliers as $multiplier) {
$product = $product->multipliedBy($multiplier->toString());
}
return new IntegerObject((string) $product);
}
public function divide(
int $roundingMode,
int $scale,
NumberInterface $dividend,
NumberInterface ...$divisors
): NumberInterface {
$brickRounding = $this->getBrickRoundingMode($roundingMode);
$quotient = BigDecimal::of($dividend->toString());
foreach ($divisors as $divisor) {
$quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding);
}
if ($scale === 0) {
return new IntegerObject((string) $quotient->toBigInteger());
}
return new Decimal((string) $quotient);
}
public function fromBase(string $value, int $base): IntegerObject
{
try {
return new IntegerObject((string) BigInteger::fromBase($value, $base));
} catch (MathException | \InvalidArgumentException $exception) {
throw new InvalidArgumentException(
$exception->getMessage(),
(int) $exception->getCode(),
$exception
);
}
}
public function toBase(IntegerObject $value, int $base): string
{
try {
return BigInteger::of($value->toString())->toBase($base);
} catch (MathException | \InvalidArgumentException $exception) {
throw new InvalidArgumentException(
$exception->getMessage(),
(int) $exception->getCode(),
$exception
);
}
}
public function toHexadecimal(IntegerObject $value): Hexadecimal
{
return new Hexadecimal($this->toBase($value, 16));
}
public function toInteger(Hexadecimal $value): IntegerObject
{
return $this->fromBase($value->toString(), 16);
}
/**
* Maps ramsey/uuid rounding modes to those used by brick/math
*/
private function getBrickRoundingMode(int $roundingMode): int
{
return self::ROUNDING_MODE_MAP[$roundingMode] ?? 0;
}
}

View file

@ -0,0 +1,106 @@
<?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\Math;
use Ramsey\Uuid\Type\Hexadecimal;
use Ramsey\Uuid\Type\Integer as IntegerObject;
use Ramsey\Uuid\Type\NumberInterface;
/**
* A calculator performs arithmetic operations on numbers
*
* @psalm-immutable
*/
interface CalculatorInterface
{
/**
* Returns the sum of all the provided parameters
*
* @param NumberInterface $augend The first addend (the integer being added to)
* @param NumberInterface ...$addends The additional integers to a add to the augend
*
* @return NumberInterface The sum of all the parameters
*/
public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface;
/**
* Returns the difference of all the provided parameters
*
* @param NumberInterface $minuend The integer being subtracted from
* @param NumberInterface ...$subtrahends The integers to subtract from the minuend
*
* @return NumberInterface The difference after subtracting all parameters
*/
public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface;
/**
* Returns the product of all the provided parameters
*
* @param NumberInterface $multiplicand The integer to be multiplied
* @param NumberInterface ...$multipliers The factors by which to multiply the multiplicand
*
* @return NumberInterface The product of multiplying all the provided parameters
*/
public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface;
/**
* Returns the quotient of the provided parameters divided left-to-right
*
* @param int $roundingMode The RoundingMode constant to use for this operation
* @param int $scale The scale to use for this operation
* @param NumberInterface $dividend The integer to be divided
* @param NumberInterface ...$divisors The integers to divide $dividend by, in
* the order in which the division operations should take place
* (left-to-right)
*
* @return NumberInterface The quotient of dividing the provided parameters left-to-right
*/
public function divide(
int $roundingMode,
int $scale,
NumberInterface $dividend,
NumberInterface ...$divisors
): NumberInterface;
/**
* Converts a value from an arbitrary base to a base-10 integer value
*
* @param string $value The value to convert
* @param int $base The base to convert from (i.e., 2, 16, 32, etc.)
*
* @return IntegerObject The base-10 integer value of the converted value
*/
public function fromBase(string $value, int $base): IntegerObject;
/**
* Converts a base-10 integer value to an arbitrary base
*
* @param IntegerObject $value The integer value to convert
* @param int $base The base to convert to (i.e., 2, 16, 32, etc.)
*
* @return string The value represented in the specified base
*/
public function toBase(IntegerObject $value, int $base): string;
/**
* Converts an Integer instance to a Hexadecimal instance
*/
public function toHexadecimal(IntegerObject $value): Hexadecimal;
/**
* Converts a Hexadecimal instance to an Integer instance
*/
public function toInteger(Hexadecimal $value): IntegerObject;
}

View file

@ -0,0 +1,146 @@
<?php
/**
* This file was originally part of brick/math
*
* Copyright (c) 2013-present Benjamin Morel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @link https://github.com/brick/math brick/math at GitHub
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Math;
/**
* Specifies a rounding behavior for numerical operations capable of discarding
* precision.
*
* Each rounding mode indicates how the least significant returned digit of a
* rounded result is to be calculated. If fewer digits are returned than the
* digits needed to represent the exact numerical result, the discarded digits
* will be referred to as the discarded fraction regardless the digits'
* contribution to the value of the number. In other words, considered as a
* numerical value, the discarded fraction could have an absolute value greater
* than one.
*/
final class RoundingMode
{
/**
* Private constructor. This class is not instantiable.
*
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Asserts that the requested operation has an exact result, hence no
* rounding is necessary.
*/
public const UNNECESSARY = 0;
/**
* Rounds away from zero.
*
* Always increments the digit prior to a nonzero discarded fraction.
* Note that this rounding mode never decreases the magnitude of the
* calculated value.
*/
public const UP = 1;
/**
* Rounds towards zero.
*
* Never increments the digit prior to a discarded fraction (i.e.,
* truncates). Note that this rounding mode never increases the magnitude of
* the calculated value.
*/
public const DOWN = 2;
/**
* Rounds towards positive infinity.
*
* If the result is positive, behaves as for UP; if negative, behaves as for
* DOWN. Note that this rounding mode never decreases the calculated value.
*/
public const CEILING = 3;
/**
* Rounds towards negative infinity.
*
* If the result is positive, behave as for DOWN; if negative, behave as for
* UP. Note that this rounding mode never increases the calculated value.
*/
public const FLOOR = 4;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant,
* in which case round up.
*
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves
* as for DOWN. Note that this is the rounding mode commonly taught at
* school.
*/
public const HALF_UP = 5;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant,
* in which case round down.
*
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves
* as for DOWN.
*/
public const HALF_DOWN = 6;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant,
* in which case round towards positive infinity.
*
* If the result is positive, behaves as for HALF_UP; if negative, behaves
* as for HALF_DOWN.
*/
public const HALF_CEILING = 7;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant,
* in which case round towards negative infinity.
*
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves
* as for HALF_UP.
*/
public const HALF_FLOOR = 8;
/**
* Rounds towards the "nearest neighbor" unless both neighbors are
* equidistant, in which case rounds towards the even neighbor.
*
* Behaves as for HALF_UP if the digit to the left of the discarded fraction
* is odd; behaves as for HALF_DOWN if it's even.
*
* Note that this is the rounding mode that statistically minimizes
* cumulative error when applied repeatedly over a sequence of calculations.
* It is sometimes known as "Banker's rounding", and is chiefly used in the
* USA.
*/
public const HALF_EVEN = 9;
}

View file

@ -0,0 +1,133 @@
<?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\Nonstandard;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Fields\SerializableFieldsTrait;
use Ramsey\Uuid\Rfc4122\FieldsInterface;
use Ramsey\Uuid\Rfc4122\VariantTrait;
use Ramsey\Uuid\Type\Hexadecimal;
use function bin2hex;
use function dechex;
use function hexdec;
use function sprintf;
use function str_pad;
use function strlen;
use function substr;
use const STR_PAD_LEFT;
/**
* Nonstandard UUID fields do not conform to the RFC 4122 standard
*
* Since some systems may create nonstandard UUIDs, this implements the
* Rfc4122\FieldsInterface, so that functionality of a nonstandard UUID is not
* degraded, in the event these UUIDs are expected to contain RFC 4122 fields.
*
* Internally, this class represents the fields together as a 16-byte binary
* string.
*
* @psalm-immutable
*/
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)
{
if (strlen($bytes) !== 16) {
throw new InvalidArgumentException(
'The byte string must be 16 bytes long; '
. 'received ' . strlen($bytes) . ' bytes'
);
}
$this->bytes = $bytes;
}
public function getBytes(): string
{
return $this->bytes;
}
public function getClockSeq(): Hexadecimal
{
$clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT));
}
public function getClockSeqHiAndReserved(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1)));
}
public function getClockSeqLow(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1)));
}
public function getNode(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 10)));
}
public function getTimeHiAndVersion(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2)));
}
public function getTimeLow(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4)));
}
public function getTimeMid(): Hexadecimal
{
return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2)));
}
public function getTimestamp(): Hexadecimal
{
return new Hexadecimal(sprintf(
'%03x%04s%08s',
hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
$this->getTimeMid()->toString(),
$this->getTimeLow()->toString()
));
}
public function getVersion(): ?int
{
return null;
}
public function isNil(): bool
{
return false;
}
}

View file

@ -0,0 +1,37 @@
<?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\Nonstandard;
use Ramsey\Uuid\Codec\CodecInterface;
use Ramsey\Uuid\Converter\NumberConverterInterface;
use Ramsey\Uuid\Converter\TimeConverterInterface;
use Ramsey\Uuid\Uuid as BaseUuid;
/**
* Nonstandard\Uuid is a UUID that doesn't conform to RFC 4122
*
* @psalm-immutable
*/
final class Uuid extends BaseUuid
{
public function __construct(
Fields $fields,
NumberConverterInterface $numberConverter,
CodecInterface $codec,
TimeConverterInterface $timeConverter
) {
parent::__construct($fields, $numberConverter, $codec, $timeConverter);
}
}

Some files were not shown because too many files have changed in this diff Show more