first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

19
vendor/ramsey/collection/LICENSE vendored Normal file
View File

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

70
vendor/ramsey/collection/README.md vendored Normal file
View File

@@ -0,0 +1,70 @@
<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/blob/master/LICENSE"><img src="https://img.shields.io/packagist/l/ramsey/collection.svg?style=flat-square&colorB=darkcyan" alt="Read License"></a>
<a href="https://github.com/ramsey/collection/actions/workflows/continuous-integration.yml"><img src="https://img.shields.io/github/actions/workflow/status/ramsey/collection/continuous-integration.yml?branch=main&logo=github&style=flat-square" alt="Build Status"></a>
<a href="https://codecov.io/gh/ramsey/collection"><img src="https://img.shields.io/codecov/c/gh/ramsey/collection?label=codecov&logo=codecov&style=flat-square" alt="Codecov Code Coverage"></a>
<a href="https://shepherd.dev/github/ramsey/collection"><img src="https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fshepherd.dev%2Fgithub%2Framsey%2Fcollection%2Fcoverage" alt="Psalm Type Coverage"></a>
</p>
## About
ramsey/collection is a PHP 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 library may be found in the
[Wiki pages](https://github.com/ramsey/collection/wiki/Examples).
## 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/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/main/SECURITY.md

169
vendor/ramsey/collection/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,169 @@
<!--
This policy template was created using the HackerOne Policy Builder [1],
with guidance from the National Telecommunications and Information
Administration Coordinated Vulnerability Disclosure Template [2].
-->
# Vulnerability Disclosure Policy (VDP)
## Brand Promise
<!--
This is your brand promise. Its objective is to "demonstrate a clear, good
faith commitment to customers and other stakeholders potentially impacted by
security vulnerabilities" [2].
-->
Keeping user information safe and secure is a top priority, and we welcome the
contribution of external security researchers.
## Scope
<!--
This is your initial scope. It tells vulnerability finders and reporters
"which systems and capabilities are 'fair game' versus 'off limits'" [2].
For software packages, this is often a list of currently maintained versions
of the package.
-->
If you believe you've found a security issue in software that is maintained in
this repository, we encourage you to notify us.
| Version | In scope | Source code |
| ------- | :------: | ----------- |
| latest | ✅ | https://github.com/ramsey/collection |
## How to Submit a Report
<!--
This is your communication process. It tells security researchers how to
contact you to report a vulnerability. It may be a link to a web form that
uses HTTPS for secure communication, or it may be an email address.
Optionally, you may choose to include a PGP public key, so that researchers
may send you encrypted messages.
-->
To submit a vulnerability report, please contact us at security@ramsey.dev.
Your submission will be reviewed and validated by a member of our team.
## Safe Harbor
<!--
This section assures vulnerability finders and reporters that they will
receive good faith responses to their good faith acts. In other words,
"we will not take legal action if..." [2].
-->
We support safe harbor for security researchers who:
* Make a good faith effort to avoid privacy violations, destruction of data, and
interruption or degradation of our services.
* Only interact with accounts you own or with explicit permission of the account
holder. If you do encounter Personally Identifiable Information (PII) contact
us immediately, do not proceed with access, and immediately purge any local
information.
* Provide us with a reasonable amount of time to resolve vulnerabilities prior
to any disclosure to the public or a third party.
We will consider activities conducted consistent with this policy to constitute
"authorized" conduct and will not pursue civil action or initiate a complaint to
law enforcement. We will help to the extent we can if legal action is initiated
by a third party against you.
Please submit a report to us before engaging in conduct that may be inconsistent
with or unaddressed by this policy.
## Preferences
<!--
The preferences section sets expectations based on priority and submission
volume, rather than legal objection or restriction [2].
According to the NTIA [2]:
This section is a living document that sets expectations for preferences
and priorities, typically maintained by the support and engineering
team. This can outline classes of vulnerabilities, reporting style
(crash dumps, CVSS scoring, proof-of-concept, etc.), tools, etc. Too
many preferences can set the wrong tone or make reporting findings
difficult to navigate. This section also sets expectations to the
researcher community for what types of issues are considered important
or not.
-->
* Please provide detailed reports with reproducible steps and a clearly defined
impact.
* Include the version number of the vulnerable package in your report
* Social engineering (e.g. phishing, vishing, smishing) is prohibited.
<!--
References
[1] HackerOne. Policy builder. Retrieved from https://hackerone.com/policy-builder/
[2] NTIA Safety Working Group. 2016. "Early stage" coordinated vulnerability
disclosure template: Version 1.1. (15 December 2016). Retrieved from
https://www.ntia.doc.gov/files/ntia/publications/ntia_vuln_disclosure_early_stage_template.pdf
-->
## Encryption Key for security@ramsey.dev
For increased privacy when reporting sensitive issues, you may encrypt your
message using the following public key:
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBF+Z9gEBEACbT/pIx8RR0K18t8Z2rDnmEV44YdT7HNsMdq+D6SAlx8UUb6AU
jGIbV9dgBgGNtOLU1pxloaJwL9bWIRbj+X/Qb2WNIP//Vz1Y40ox1dSpfCUrizXx
kb4p58Xml0PsB8dg3b4RDUgKwGC37ne5xmDnigyJPbiB2XJ6Xc46oPCjh86XROTK
wEBB2lY67ClBlSlvC2V9KmbTboRQkLdQDhOaUosMb99zRb0EWqDLaFkZVjY5HI7i
0pTveE6dI12NfHhTwKjZ5pUiAZQGlKA6J1dMjY2unxHZkQj5MlMfrLSyJHZxccdJ
xD94T6OTcTHt/XmMpI2AObpewZDdChDQmcYDZXGfAhFoJmbvXsmLMGXKgzKoZ/ls
RmLsQhh7+/r8E+Pn5r+A6Hh4uAc14ApyEP0ckKeIXw1C6pepHM4E8TEXVr/IA6K/
z6jlHORixIFX7iNOnfHh+qwOgZw40D6JnBfEzjFi+T2Cy+JzN2uy7I8UnecTMGo3
5t6astPy6xcH6kZYzFTV7XERR6LIIVyLAiMFd8kF5MbJ8N5ElRFsFHPW+82N2HDX
c60iSaTB85k6R6xd8JIKDiaKE4sSuw2wHFCKq33d/GamYezp1wO+bVUQg88efljC
2JNFyD+vl30josqhw1HcmbE1TP3DlYeIL5jQOlxCMsgai6JtTfHFM/5MYwARAQAB
tBNzZWN1cml0eUByYW1zZXkuZGV2iQJUBBMBCAA+FiEE4drPD+/ofZ570fAYq0bv
vXQCywIFAl+Z9gECGwMFCQeGH4AFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ
q0bvvXQCywIkEA//Qcwv8MtTCy01LHZd9c7VslwhNdXQDYymcTyjcYw8x7O22m4B
3hXE6vqAplFhVxxkqXB2ef0tQuzxhPHNJgkCE4Wq4i+V6qGpaSVHQT2W6DN/NIhL
vS8OdScc6zddmIbIkSrzVVAtjwehFNEIrX3DnbbbK+Iku7vsKT5EclOluIsjlYoX
goW8IeReyDBqOe2H3hoCGw6EA0D/NYV2bJnfy53rXVIyarsXXeOLp7eNEH6Td7aW
PVSrMZJe1t+knrEGnEdrXWzlg4lCJJCtemGv+pKBUomnyISXSdqyoRCCzvQjqyig
2kRebUX8BXPW33p4OXPj9sIboUOjZwormWwqqbFMO+J4TiVCUoEoheI7emPFRcNN
QtPJrjbY1++OznBc0GRpfeUkGoU1cbRl1bnepnFIZMTDLkrVW6I1Y4q8ZVwX3BkE
N81ctFrRpHBlU36EdHvjPQmGtuiL77Qq3fWmMv7yTvK1wHJAXfEb0ZJWHZCbck3w
l0CVq0Z+UUAOM8Rp1N0N8m92xtapav0qCFU9qzf2J5qX6GRmWv+d29wPgFHzDWBm
nnrYYIA4wJLx00U6SMcVBSnNe91B+RfGY5XQhbWPjQQecOGCSDsxaFAq2MeOVJyZ
bIjLYfG9GxoLKr5R7oLRJvZI4nKKBc1Kci/crZbdiSdQhSQGlDz88F1OHeCIdQQQ
EQgAHRYhBOhdAxHd+lus86YQ57Atl5icjAcbBQJfmfdIAAoJELAtl5icjAcbFVcA
/1LqB3ZjsnXDAvvAXZVjSPqofSlpMLeRQP6IM/A9Odq0AQCZrtZc1knOMGEcjppK
Rk+sy/R0Mshy8TDuaZIRgh2Ux7kCDQRfmfYBARAAmchKzzVz7IaEq7PnZDb3szQs
T/+E9F3m39yOpV4fEB1YzObonFakXNT7Gw2tZEx0eitUMqQ/13jjfu3UdzlKl2bR
qA8LrSQRhB+PTC9A1XvwxCUYhhjGiLzJ9CZL6hBQB43qHOmE9XJPme90geLsF+gK
u39Waj1SNWzwGg+Gy1Gl5f2AJoDTxznreCuFGj+Vfaczt/hlfgqpOdb9jsmdoE7t
3DSWppA9dRHWwQSgE6J28rR4QySBcqyXS6IMykqaJn7Z26yNIaITLnHCZOSY8zhP
ha7GFsN549EOCgECbrnPt9dmI2+hQE0RO0e7SOBNsIf5sz/i7urhwuj0CbOqhjc2
X1AEVNFCVcb6HPi/AWefdFCRu0gaWQxn5g+9nkq5slEgvzCCiKYzaBIcr8qR6Hb4
FaOPVPxO8vndRouq57Ws8XpAwbPttioFuCqF4u9K+tK/8e2/R8QgRYJsE3Cz/Fu8
+pZFpMnqbDEbK3DL3ss+1ed1sky+mDV8qXXeI33XW5hMFnk1JWshUjHNlQmE6ftC
U0xSTMVUtwJhzH2zDp8lEdu7qi3EsNULOl68ozDr6soWAvCbHPeTdTOnFySGCleG
/3TonsoZJs/sSPPJnxFQ1DtgQL6EbhIwa0ZwU4eKYVHZ9tjxuMX3teFzRvOrJjgs
+ywGlsIURtEckT5Y6nMAEQEAAYkCPAQYAQgAJhYhBOHazw/v6H2ee9HwGKtG7710
AssCBQJfmfYBAhsMBQkHhh+AAAoJEKtG7710AssC8NcP/iDAcy1aZFvkA0EbZ85p
i7/+ywtE/1wF4U4/9OuLcoskqGGnl1pJNPooMOSBCfreoTB8HimT0Fln0CoaOm4Q
pScNq39JXmf4VxauqUJVARByP6zUfgYarqoaZNeuFF0S4AZJ2HhGzaQPjDz1uKVM
PE6tQSgQkFzdZ9AtRA4vElTH6yRAgmepUsOihk0b0gUtVnwtRYZ8e0Qt3ie97a73
DxLgAgedFRUbLRYiT0vNaYbainBsLWKpN/T8odwIg/smP0Khjp/ckV60cZTdBiPR
szBTPJESMUTu0VPntc4gWwGsmhZJg/Tt/qP08XYo3VxNYBegyuWwNR66zDWvwvGH
muMv5UchuDxp6Rt3JkIO4voMT1JSjWy9p8krkPEE4V6PxAagLjdZSkt92wVLiK5x
y5gNrtPhU45YdRAKHr36OvJBJQ42CDaZ6nzrzghcIp9CZ7ANHrI+QLRM/csz+AGA
szSp6S4mc1lnxxfbOhPPpebZPn0nIAXoZnnoVKdrxBVedPQHT59ZFvKTQ9Fs7gd3
sYNuc7tJGFGC2CxBH4ANDpOQkc5q9JJ1HSGrXU3juxIiRgfA26Q22S9c71dXjElw
Ri584QH+bL6kkYmm8xpKF6TVwhwu5xx/jBPrbWqFrtbvLNrnfPoapTihBfdIhkT6
nmgawbBHA02D5xEqB5SU3WJu
=eJNx
-----END PGP PUBLIC KEY BLOCK-----
```

119
vendor/ramsey/collection/composer.json vendored Normal file
View File

@@ -0,0 +1,119 @@
{
"name": "ramsey/collection",
"description": "A PHP library for representing and manipulating collections.",
"license": "MIT",
"type": "library",
"keywords": [
"array",
"collection",
"hash",
"map",
"queue",
"set"
],
"authors": [
{
"name": "Ben Ramsey",
"email": "ben@benramsey.com",
"homepage": "https://benramsey.com"
}
],
"require": {
"php": "^8.1"
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
},
"minimum-stability": "RC",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Ramsey\\Collection\\Test\\": "tests/",
"Ramsey\\Test\\Generics\\": "tests/generics/"
},
"files": [
"vendor/hamcrest/hamcrest-php/hamcrest/Hamcrest.php"
]
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true,
"phpstan/extension-installer": true,
"captainhook/plugin-composer": true
},
"sort-packages": true
},
"extra": {
"captainhook": {
"force-install": true
},
"ramsey/conventional-commits": {
"configFile": "conventional-commits.json"
}
},
"scripts": {
"dev:analyze": [
"@dev:analyze:phpstan",
"@dev:analyze:psalm"
],
"dev:analyze:phpstan": "phpstan analyse --ansi --memory-limit=1G",
"dev:analyze:psalm": "psalm",
"dev:build:clean": "git clean -fX build/",
"dev:lint": [
"@dev:lint:syntax",
"@dev:lint:style"
],
"dev:lint:fix": "phpcbf",
"dev:lint:style": "phpcs --colors",
"dev:lint:syntax": "parallel-lint --colors src/ tests/",
"dev:test": [
"@dev:lint",
"@dev:analyze",
"@dev:test:unit"
],
"dev:test:coverage:ci": "phpunit --colors=always --coverage-text --coverage-clover build/coverage/clover.xml --coverage-cobertura build/coverage/cobertura.xml --coverage-crap4j build/coverage/crap4j.xml --coverage-xml build/coverage/coverage-xml --log-junit build/junit.xml",
"dev:test:coverage:html": "phpunit --colors=always --coverage-html build/coverage/coverage-html/",
"dev:test:unit": "phpunit --colors=always",
"test": "@dev:test"
},
"scripts-descriptions": {
"dev:analyze": "Runs all static analysis checks.",
"dev:analyze:phpstan": "Runs the PHPStan static analyzer.",
"dev:analyze:psalm": "Runs the Psalm static analyzer.",
"dev:build:clean": "Cleans the build/ directory.",
"dev:lint": "Runs all linting checks.",
"dev:lint:fix": "Auto-fixes coding standards issues, if possible.",
"dev:lint:style": "Checks for coding standards issues.",
"dev:lint:syntax": "Checks for syntax errors.",
"dev:test": "Runs linting, static analysis, and unit tests.",
"dev:test:coverage:ci": "Runs unit tests and generates CI coverage reports.",
"dev:test:coverage:html": "Runs unit tests and generates HTML coverage report.",
"dev:test:unit": "Runs unit tests.",
"test": "Runs linting, static analysis, and unit tests."
}
}

View File

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

View File

@@ -0,0 +1,171 @@
<?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 count;
/**
* This class provides a basic implementation of `ArrayInterface`, to minimize
* the effort required to implement this interface.
*
* @template T
* @implements ArrayInterface<T>
*/
abstract class AbstractArray implements ArrayInterface
{
/**
* The items of this array.
*
* @var array<array-key, T>
*/
protected array $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<array-key, 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(mixed $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 the value stored at the offset, or null if the offset
* does not exist.
*/
public function offsetGet(mixed $offset): mixed
{
return $this->data[$offset];
}
/**
* 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.
*/
public function offsetSet(mixed $offset, mixed $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(mixed $offset): void
{
unset($this->data[$offset]);
}
/**
* Returns data suitable for PHP serialization.
*
* @link https://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.serialize
* @link https://www.php.net/serialize
*
* @return array<array-key, T>
*/
public function __serialize(): array
{
return $this->data;
}
/**
* Adds unserialized data to the object.
*
* @param array<array-key, T> $data
*/
public function __unserialize(array $data): void
{
$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 $this->data === [];
}
}

View File

@@ -0,0 +1,393 @@
<?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\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Exception\UnsupportedOperationException;
use Ramsey\Collection\Tool\TypeTrait;
use Ramsey\Collection\Tool\ValueExtractorTrait;
use Ramsey\Collection\Tool\ValueToStringTrait;
use function array_filter;
use function array_key_first;
use function array_key_last;
use function array_map;
use function array_merge;
use function array_reduce;
use function array_search;
use function array_udiff;
use function array_uintersect;
use function in_array;
use function is_int;
use function is_object;
use function spl_object_id;
use function sprintf;
use function usort;
/**
* This class provides a basic implementation of `CollectionInterface`, to
* minimize the effort required to implement this interface
*
* @template T
* @extends AbstractArray<T>
* @implements CollectionInterface<T>
*/
abstract class AbstractCollection extends AbstractArray implements CollectionInterface
{
use TypeTrait;
use ValueToStringTrait;
use ValueExtractorTrait;
/**
* @throws InvalidArgumentException if $element is of the wrong type.
*/
public function add(mixed $element): bool
{
$this[] = $element;
return true;
}
public function contains(mixed $element, bool $strict = true): bool
{
return in_array($element, $this->data, $strict);
}
/**
* @throws InvalidArgumentException if $element is of the wrong type.
*/
public function offsetSet(mixed $offset, mixed $value): void
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($value),
);
}
if ($offset === null) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
public function remove(mixed $element): bool
{
if (($position = array_search($element, $this->data, true)) !== false) {
unset($this[$position]);
return true;
}
return false;
}
/**
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call column() on this
* collection.
*
* @inheritDoc
*/
public function column(string $propertyOrMethod): array
{
$temp = [];
foreach ($this->data as $item) {
/** @psalm-suppress MixedAssignment */
$temp[] = $this->extractValue($item, $propertyOrMethod);
}
return $temp;
}
/**
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function first(): mixed
{
$firstIndex = array_key_first($this->data);
if ($firstIndex === null) {
throw new NoSuchElementException('Can\'t determine first item. Collection is empty');
}
return $this->data[$firstIndex];
}
/**
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function last(): mixed
{
$lastIndex = array_key_last($this->data);
if ($lastIndex === null) {
throw new NoSuchElementException('Can\'t determine last item. Collection is empty');
}
return $this->data[$lastIndex];
}
/**
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call sort() on this
* collection.
*/
public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): CollectionInterface
{
$collection = clone $this;
usort(
$collection->data,
/**
* @param T $a
* @param T $b
*/
function (mixed $a, mixed $b) use ($propertyOrMethod, $order): int {
/** @var mixed $aValue */
$aValue = $this->extractValue($a, $propertyOrMethod);
/** @var mixed $bValue */
$bValue = $this->extractValue($b, $propertyOrMethod);
return ($aValue <=> $bValue) * ($order === Sort::Descending ? -1 : 1);
},
);
return $collection;
}
/**
* @param callable(T): bool $callback A callable to use for filtering elements.
*
* @return CollectionInterface<T>
*/
public function filter(callable $callback): CollectionInterface
{
$collection = clone $this;
$collection->data = array_merge([], array_filter($collection->data, $callback));
return $collection;
}
/**
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call where() on this
* collection.
*/
public function where(?string $propertyOrMethod, mixed $value): CollectionInterface
{
return $this->filter(
/**
* @param T $item
*/
function (mixed $item) use ($propertyOrMethod, $value): bool {
/** @var mixed $accessorValue */
$accessorValue = $this->extractValue($item, $propertyOrMethod);
return $accessorValue === $value;
},
);
}
/**
* @param callable(T): TCallbackReturn $callback A callable to apply to each
* item of the collection.
*
* @return CollectionInterface<TCallbackReturn>
*
* @template TCallbackReturn
*/
public function map(callable $callback): CollectionInterface
{
/** @var Collection<TCallbackReturn> */
return new Collection('mixed', array_map($callback, $this->data));
}
/**
* @param callable(TCarry, T): TCarry $callback A callable to apply to each
* item of the collection to reduce it to a single value.
* @param TCarry $initial This is the initial value provided to the callback.
*
* @return TCarry
*
* @template TCarry
*/
public function reduce(callable $callback, mixed $initial): mixed
{
/** @var TCarry */
return array_reduce($this->data, $callback, $initial);
}
/**
* @param CollectionInterface<T> $other The collection to check for divergent
* items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function diff(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
$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;
}
/**
* @param CollectionInterface<T> $other The collection to check for
* intersecting items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function intersect(CollectionInterface $other): CollectionInterface
{
$this->compareCollectionTypes($other);
/** @var array<array-key, T> $intersect */
$intersect = array_uintersect($this->data, $other->toArray(), $this->getComparator());
$collection = clone $this;
$collection->data = $intersect;
return $collection;
}
/**
* @param CollectionInterface<T> ...$collections The collections to merge.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if unable to merge any of the given
* collections or items within the given collections due to type
* mismatch errors.
*/
public function merge(CollectionInterface ...$collections): CollectionInterface
{
$mergedCollection = clone $this;
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 ($this->getUniformType($collection) !== $this->getUniformType($this)) {
throw new CollectionMismatchException(
sprintf(
'Collection items in collection with index %d must be of type %s',
$index,
$this->getType(),
),
);
}
foreach ($collection as $key => $value) {
if (is_int($key)) {
$mergedCollection[] = $value;
} else {
$mergedCollection[$key] = $value;
}
}
}
return $mergedCollection;
}
/**
* @param CollectionInterface<T> $other
*
* @throws CollectionMismatchException
*/
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 ($this->getUniformType($other) !== $this->getUniformType($this)) {
throw new CollectionMismatchException('Collection items must be of type ' . $this->getType());
}
}
private function getComparator(): Closure
{
return /**
* @param T $a
* @param T $b
*/
function (mixed $a, mixed $b): int {
// If the two values are object, we convert them to unique scalars.
// If the collection contains mixed values (unlikely) where some are objects
// and some are not, we leave them as they are.
// The comparator should still work and the result of $a < $b should
// be consistent but unpredictable since not documented.
if (is_object($a) && is_object($b)) {
$a = spl_object_id($a);
$b = spl_object_id($b);
}
return $a === $b ? 0 : ($a < $b ? 1 : -1);
};
}
/**
* @param CollectionInterface<mixed> $collection
*/
private function getUniformType(CollectionInterface $collection): string
{
return match ($collection->getType()) {
'integer' => 'int',
'boolean' => 'bool',
'double' => 'float',
default => $collection->getType(),
};
}
}

View File

@@ -0,0 +1,44 @@
<?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
* @extends AbstractCollection<T>
*/
abstract class AbstractSet extends AbstractCollection
{
public function add(mixed $element): bool
{
if ($this->contains($element)) {
return false;
}
return parent::add($element);
}
public function offsetSet(mixed $offset, mixed $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;
/**
* `ArrayInterface` provides traversable array functionality to data types.
*
* @template T
* @extends ArrayAccess<array-key, T>
* @extends IteratorAggregate<array-key, T>
*/
interface ArrayInterface extends
ArrayAccess,
Countable,
IteratorAggregate
{
/**
* 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,95 @@
<?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
* @extends AbstractCollection<T>
*/
class Collection extends AbstractCollection
{
/**
* Constructs a collection object of the specified type, optionally with the
* specified data.
*
* @param string $collectionType The type or class name associated with this
* collection.
* @param array<array-key, T> $data The initial items to store in the collection.
*/
public function __construct(private readonly string $collectionType, array $data = [])
{
parent::__construct($data);
}
public function getType(): string
{
return $this->collectionType;
}
}

View File

@@ -0,0 +1,253 @@
<?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\CollectionMismatchException;
use Ramsey\Collection\Exception\InvalidArgumentException;
use Ramsey\Collection\Exception\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\NoSuchElementException;
use Ramsey\Collection\Exception\UnsupportedOperationException;
/**
* A collection represents a group of values, known as its elements.
*
* Some collections allow duplicate elements and others do not. Some are ordered
* and others unordered.
*
* @template T
* @extends ArrayInterface<T>
*/
interface CollectionInterface extends ArrayInterface
{
/**
* 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.
*
* @throws InvalidArgumentException if the collection refuses to add the
* $element for any reason other than that it already contains the element.
*/
public function add(mixed $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.
*/
public function contains(mixed $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.
*/
public function remove(mixed $element): bool;
/**
* Returns the values from the given property, method, or array key.
*
* @param string $propertyOrMethod The name of the property, method, or
* array key to evaluate and return.
*
* @return array<int, mixed>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call column() on this
* collection.
*/
public function column(string $propertyOrMethod): array;
/**
* Returns the first item of the collection.
*
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function first(): mixed;
/**
* Returns the last item of the collection.
*
* @return T
*
* @throws NoSuchElementException if this collection is empty.
*/
public function last(): mixed;
/**
* Sort the collection by a property, method, or array key with the given
* sort order.
*
* If $propertyOrMethod is `null`, this will sort by comparing each element.
*
* This will always leave the original collection untouched and will return
* a new one.
*
* @param string | null $propertyOrMethod The property, method, or array key
* to sort by.
* @param Sort $order The sort order for the resulting collection.
*
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call sort() on this
* collection.
*/
public function sort(?string $propertyOrMethod = null, Sort $order = Sort::Ascending): 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 the result of the given property, method,
* or array key of each item in the collection equals the given value.
*
* This will always leave the original collection untouched and will return
* a new one.
*
* @param string | null $propertyOrMethod The property, method, or array key
* to evaluate. If `null`, the element itself is compared to $value.
* @param mixed $value The value to match.
*
* @return CollectionInterface<T>
*
* @throws InvalidPropertyOrMethod if the $propertyOrMethod does not exist
* on the elements in this collection.
* @throws UnsupportedOperationException if unable to call where() on this
* collection.
*/
public function where(?string $propertyOrMethod, mixed $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;
/**
* Apply a given callback method on each item of the collection
* to reduce it to a single value.
*
* See the {@link http://php.net/manual/en/function.array-reduce.php PHP array_reduce() documentation}
* for examples of how the `$callback` and `$initial` parameters work.
*
* @param callable(TCarry, T): TCarry $callback A callable to apply to each
* item of the collection to reduce it to a single value.
* @param TCarry $initial This is the initial value provided to the callback.
*
* @return TCarry
*
* @template TCarry
*/
public function reduce(callable $callback, mixed $initial): mixed;
/**
* Create a new collection with divergent items between current and given
* collection.
*
* @param CollectionInterface<T> $other The collection to check for divergent
* items.
*
* @return CollectionInterface<T>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
public function diff(CollectionInterface $other): 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>
*
* @throws CollectionMismatchException if the compared collections are of
* differing types.
*/
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>
*
* @throws CollectionMismatchException if unable to merge any of the given
* collections or items within the given collections due to type
* mismatch errors.
*/
public function merge(CollectionInterface ...$collections): self;
}

View File

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

View File

@@ -0,0 +1,313 @@
<?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;
use RuntimeException;
/**
* 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
* @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`.
*/
public function addFirst(mixed $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`.
*/
public function addLast(mixed $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`.
*/
public function offerFirst(mixed $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`.
*/
public function offerLast(mixed $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(): mixed;
/**
* 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(): mixed;
/**
* Retrieves and removes the head of this queue, or returns `null` if this
* queue is empty.
*
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function pollFirst(): mixed;
/**
* Retrieves and removes the tail of this queue, or returns `null` if this
* queue is empty.
*
* @return T | null the tail of this queue, or `null` if this queue is empty.
*/
public function pollLast(): mixed;
/**
* 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(): mixed;
/**
* 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(): mixed;
/**
* Retrieves, but does not remove, the head of this queue, or returns `null`
* if this queue is empty.
*
* @return T | null the head of this queue, or `null` if this queue is empty.
*/
public function peekFirst(): mixed;
/**
* Retrieves, but does not remove, the tail of this queue, or returns `null`
* if this queue is empty.
*
* @return T | null the tail of this queue, or `null` if this queue is empty.
*/
public function peekLast(): mixed;
}

View File

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

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\Exception;
use RuntimeException;
/**
* Thrown when attempting to operate on collections of differing types.
*/
class CollectionMismatchException extends RuntimeException implements CollectionException
{
}

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\Exception;
use InvalidArgumentException as PhpInvalidArgumentException;
/**
* Thrown to indicate an argument is not of the expected type.
*/
class InvalidArgumentException extends PhpInvalidArgumentException implements CollectionException
{
}

View File

@@ -0,0 +1,26 @@
<?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;
use RuntimeException;
/**
* Thrown when attempting to evaluate a property, method, or array key
* that doesn't exist on an element or cannot otherwise be evaluated in the
* current context.
*/
class InvalidPropertyOrMethod extends RuntimeException implements CollectionException
{
}

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\Exception;
use RuntimeException;
/**
* Thrown when attempting to access an element that does not exist.
*/
class NoSuchElementException extends RuntimeException implements CollectionException
{
}

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\Exception;
use OutOfBoundsException as PhpOutOfBoundsException;
/**
* Thrown when attempting to access an element out of the range of the collection.
*/
class OutOfBoundsException extends PhpOutOfBoundsException implements CollectionException
{
}

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\Exception;
use RuntimeException;
/**
* Thrown to indicate that the requested operation is not supported.
*/
class UnsupportedOperationException extends RuntimeException implements CollectionException
{
}

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.
*
* @extends AbstractArray<mixed>
*/
class GenericArray extends AbstractArray
{
}

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

View File

@@ -0,0 +1,60 @@
<?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 of array-key
* @template T
* @extends AbstractMap<K, T>
* @implements TypedMapInterface<K, T>
*/
abstract class AbstractTypedMap extends AbstractMap implements TypedMapInterface
{
use TypeTrait;
use ValueToStringTrait;
/**
* @param K $offset
* @param T $value
*
* @inheritDoc
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function offsetSet(mixed $offset, mixed $value): void
{
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),
);
}
parent::offsetSet($offset, $value);
}
}

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\Map;
/**
* `AssociativeArrayMap` represents a standard associative array object.
*
* @extends AbstractMap<string, mixed>
*/
class AssociativeArrayMap extends AbstractMap
{
}

View File

@@ -0,0 +1,142 @@
<?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 K of array-key
* @template T
* @extends ArrayInterface<T>
*/
interface MapInterface extends ArrayInterface
{
/**
* Returns `true` if this map contains a mapping for the specified key.
*
* @param K $key The key to check in the map.
*/
public function containsKey(int | string $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.
*/
public function containsValue(mixed $value): bool;
/**
* Return an array of the keys contained in this map.
*
* @return list<K>
*/
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 K $key The key to return from the map.
* @param T | null $defaultValue The default value to use if `$key` is not found.
*
* @return T | null the value or `null` if the key could not be found.
*/
public function get(int | string $key, mixed $defaultValue = null): mixed;
/**
* 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 K $key The key to put or replace in the map.
* @param T $value The value to store at `$key`.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function put(int | string $key, mixed $value): mixed;
/**
* 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 K $key The key to put in the map.
* @param T $value The value to store at `$key`.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function putIfAbsent(int | string $key, mixed $value): mixed;
/**
* Removes the mapping for a key from this map if it is present.
*
* @param K $key The key to remove from the map.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function remove(int | string $key): mixed;
/**
* 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 K $key The key to remove from the map.
* @param T $value The value to match.
*
* @return bool true if the value was removed.
*/
public function removeIf(int | string $key, mixed $value): bool;
/**
* Replaces the entry for the specified key only if it is currently mapped
* to some value.
*
* @param K $key The key to replace.
* @param T $value The value to set at `$key`.
*
* @return T | null the previous value associated with key, or `null` if
* there was no mapping for `$key`.
*/
public function replace(int | string $key, mixed $value): mixed;
/**
* 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 K $key The key to remove from the map.
* @param T $oldValue The value to match.
* @param T $newValue The value to use as a replacement.
*
* @return bool true if the value was replaced.
*/
public function replaceIf(int | string $key, mixed $oldValue, mixed $newValue): bool;
}

View File

@@ -0,0 +1,110 @@
<?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
*
* @extends AbstractMap<string, mixed>
*/
class NamedParameterMap extends AbstractMap
{
use TypeTrait;
use ValueToStringTrait;
/**
* Named parameters defined for this map.
*
* @var array<string, string>
*/
private readonly array $namedParameters;
/**
* Constructs a new `NamedParameterMap`.
*
* @param array<array-key, string> $namedParameters The named parameters defined for this map.
* @param array<string, 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;
}
public function offsetSet(mixed $offset, mixed $value): void
{
if (!array_key_exists($offset, $this->namedParameters)) {
throw new InvalidArgumentException(
'Attempting to set value for unconfigured parameter \''
. $this->toolValueToString($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,112 @@
<?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 `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 of array-key
* @template T
* @extends AbstractTypedMap<K, T>
*/
class TypedMap extends AbstractTypedMap
{
/**
* 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(
private readonly string $keyType,
private readonly string $valueType,
array $data = [],
) {
parent::__construct($data);
}
public function getKeyType(): string
{
return $this->keyType;
}
public function getValueType(): string
{
return $this->valueType;
}
}

View File

@@ -0,0 +1,36 @@
<?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 K of array-key
* @template T
* @extends MapInterface<K, 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;
}

148
vendor/ramsey/collection/src/Queue.php vendored Normal file
View File

@@ -0,0 +1,148 @@
<?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;
use function array_key_first;
/**
* This class provides a basic implementation of `QueueInterface`, to minimize
* the effort required to implement this interface.
*
* @template T
* @extends AbstractArray<T>
* @implements QueueInterface<T>
*/
class Queue extends AbstractArray implements QueueInterface
{
use TypeTrait;
use ValueToStringTrait;
/**
* Constructs a queue object of the specified type, optionally with the
* specified data.
*
* @param string $queueType The type or class name associated with this queue.
* @param array<array-key, T> $data The initial items to store in the queue.
*/
public function __construct(private readonly string $queueType, array $data = [])
{
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.
*
* @throws InvalidArgumentException if $value is of the wrong type.
*/
public function offsetSet(mixed $offset, mixed $value): void
{
if ($this->checkType($this->getType(), $value) === false) {
throw new InvalidArgumentException(
'Value must be of type ' . $this->getType() . '; value is '
. $this->toolValueToString($value),
);
}
$this->data[] = $value;
}
/**
* @throws InvalidArgumentException if $value is of the wrong type.
*/
public function add(mixed $element): bool
{
$this[] = $element;
return true;
}
/**
* @return T
*
* @throws NoSuchElementException if this queue is empty.
*/
public function element(): mixed
{
return $this->peek() ?? throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.',
);
}
public function offer(mixed $element): bool
{
try {
return $this->add($element);
} catch (InvalidArgumentException) {
return false;
}
}
/**
* @return T | null
*/
public function peek(): mixed
{
$index = array_key_first($this->data);
if ($index === null) {
return null;
}
return $this[$index];
}
/**
* @return T | null
*/
public function poll(): mixed
{
$index = array_key_first($this->data);
if ($index === null) {
return null;
}
$head = $this[$index];
unset($this[$index]);
return $head;
}
/**
* @return T
*
* @throws NoSuchElementException if this queue is empty.
*/
public function remove(): mixed
{
return $this->poll() ?? throw new NoSuchElementException(
'Can\'t return element from Queue. Queue is empty.',
);
}
public function getType(): string
{
return $this->queueType;
}
}

View File

@@ -0,0 +1,202 @@
<?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;
use RuntimeException;
/**
* 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
* @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`.
*/
public function add(mixed $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(): mixed;
/**
* 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`.
*/
public function offer(mixed $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(): mixed;
/**
* 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(): mixed;
/**
* 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(): mixed;
/**
* Returns the type associated with this queue.
*/
public function getType(): string;
}

59
vendor/ramsey/collection/src/Set.php vendored Normal file
View File

@@ -0,0 +1,59 @@
<?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 doesn't exist
* $set->add($foo); // returns FALSE, the element already exists
*
* $bar = new \My\Foo();
* $set->add($bar); // returns TRUE, $bar !== $foo
* ```
*
* @template T
* @extends AbstractSet<T>
*/
class Set extends AbstractSet
{
/**
* Constructs a set object of the specified type, optionally with the
* specified data.
*
* @param string $setType The type or class name associated with this set.
* @param array<array-key, T> $data The initial items to store in the set.
*/
public function __construct(private readonly string $setType, array $data = [])
{
parent::__construct($data);
}
public function getType(): string
{
return $this->setType;
}
}

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

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

View File

@@ -0,0 +1,57 @@
<?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, mixed $value): bool
{
return match ($type) {
'array' => is_array($value),
'bool', 'boolean' => is_bool($value),
'callable' => is_callable($value),
'float', 'double' => is_float($value),
'int', 'integer' => is_int($value),
'null' => $value === null,
'numeric' => is_numeric($value),
'object' => is_object($value),
'resource' => is_resource($value),
'scalar' => is_scalar($value),
'string' => is_string($value),
'mixed' => true,
default => $value instanceof $type,
};
}
}

View File

@@ -0,0 +1,81 @@
<?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\InvalidPropertyOrMethod;
use Ramsey\Collection\Exception\UnsupportedOperationException;
use function is_array;
use function is_object;
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, method, or array key from the
* element.
*
* If `$propertyOrMethod` is `null`, we return the element as-is.
*
* @param mixed $element The element to extract the value from.
* @param string | null $propertyOrMethod The property or method for which the
* value should be extracted.
*
* @return mixed the value extracted from the specified property, method,
* or array key, or the element itself.
*
* @throws InvalidPropertyOrMethod
* @throws UnsupportedOperationException
*/
protected function extractValue(mixed $element, ?string $propertyOrMethod): mixed
{
if ($propertyOrMethod === null) {
return $element;
}
if (!is_object($element) && !is_array($element)) {
throw new UnsupportedOperationException(sprintf(
'The collection type "%s" does not support the $propertyOrMethod parameter',
$this->getType(),
));
}
if (is_array($element)) {
return $element[$propertyOrMethod] ?? throw new InvalidPropertyOrMethod(sprintf(
'Key or index "%s" not found in collection elements',
$propertyOrMethod,
));
}
if (property_exists($element, $propertyOrMethod)) {
return $element->$propertyOrMethod;
}
if (method_exists($element, $propertyOrMethod)) {
return $element->{$propertyOrMethod}();
}
throw new InvalidPropertyOrMethod(sprintf(
'Method or property "%s" not defined in %s',
$propertyOrMethod,
$element::class,
));
}
}

View File

@@ -0,0 +1,91 @@
<?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 assert;
use function get_resource_type;
use function is_array;
use function is_bool;
use function is_callable;
use function is_object;
use function is_resource;
use function is_scalar;
/**
* 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(mixed $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 . ')';
}
// From here, $value should be an object.
assert(is_object($value));
// __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 '(' . $value::class . ' Object)';
}
}