mirror of
https://github.com/lkeme/BiliHelper-personal.git
synced 2026-01-18 14:20:28 +00:00
Compare commits
No commits in common. "86d0e8983723a119e7babbb8e317cb1275187a7b" and "33fb76a747a0f7552a91389cfb1ad9c41ab7f865" have entirely different histories.
86d0e89837
...
33fb76a747
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<p align=center>
|
<p align=center>
|
||||||
|
|
||||||
<img src="https://img.shields.io/badge/Version-2.4.5.250218-orange.svg?longCache=true&style=for-the-badge" alt="">
|
<img src="https://img.shields.io/badge/Version-2.4.3.241231-orange.svg?longCache=true&style=for-the-badge" alt="">
|
||||||
<img src="https://img.shields.io/badge/PHP-8.1+-green.svg?longCache=true&style=for-the-badge" alt="">
|
<img src="https://img.shields.io/badge/PHP-8.1+-green.svg?longCache=true&style=for-the-badge" alt="">
|
||||||
<img src="https://img.shields.io/badge/Composer-latest-blueviolet.svg?longCache=true&style=for-the-badge" alt="">
|
<img src="https://img.shields.io/badge/Composer-latest-blueviolet.svg?longCache=true&style=for-the-badge" alt="">
|
||||||
<img src="https://img.shields.io/badge/License-mit-blue.svg?longCache=true&style=for-the-badge" alt="">
|
<img src="https://img.shields.io/badge/License-mit-blue.svg?longCache=true&style=for-the-badge" alt="">
|
||||||
|
|||||||
@ -22,32 +22,23 @@
|
|||||||
"source": "https://github.com/lkeme/BiliHelper-personal"
|
"source": "https://github.com/lkeme/BiliHelper-personal"
|
||||||
},
|
},
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
|
||||||
"description": "fire015/flintstone",
|
|
||||||
"type": "path",
|
|
||||||
"url": "packages/flintstone",
|
|
||||||
"canonical": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "php-ds/php-ds",
|
|
||||||
"type": "path",
|
|
||||||
"url": "packages/php-ds",
|
|
||||||
"canonical": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "PhpComposer",
|
"description": "PhpComposer",
|
||||||
"type": "composer",
|
"type": "composer",
|
||||||
"url": "https://packagist.phpcomposer.com"
|
"url": "https://packagist.phpcomposer.com",
|
||||||
|
"canonical": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "TencentCloud",
|
"description": "TencentCloud",
|
||||||
"type": "composer",
|
"type": "composer",
|
||||||
"url": "https://mirrors.tencent.com/composer/"
|
"url": "https://mirrors.tencent.com/composer/",
|
||||||
|
"canonical": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "AliCloud",
|
"description": "AliCloud",
|
||||||
"type": "composer",
|
"type": "composer",
|
||||||
"url": "https://mirrors.aliyun.com/composer/"
|
"url": "https://mirrors.aliyun.com/composer/",
|
||||||
|
"canonical": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
@ -60,18 +51,17 @@
|
|||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"monolog/monolog": "dev-main",
|
"monolog/monolog": "dev-main",
|
||||||
"bramus/monolog-colored-line-formatter": "dev-master",
|
"bramus/monolog-colored-line-formatter": "dev-master",
|
||||||
"symfony/yaml": "^7.0",
|
"symfony/yaml": "^6.0",
|
||||||
"toolkit/stdlib": "*",
|
"toolkit/stdlib": "*",
|
||||||
"adhocore/cli": "^v1.0.0",
|
"adhocore/cli": "^v1.0.0",
|
||||||
"jbzoo/data": "dev-master",
|
"lkeme/data": "4.x-dev",
|
||||||
"grasmash/expander": "dev-main",
|
"grasmash/expander": "dev-main",
|
||||||
"amphp/amp": "3.x-dev",
|
"amphp/amp": "3.x-dev",
|
||||||
|
"fire015/flintstone": "dev-master",
|
||||||
"overtrue/pinyin": "dev-master",
|
"overtrue/pinyin": "dev-master",
|
||||||
"guzzlehttp/guzzle": "^7.0",
|
"guzzlehttp/guzzle": "^7.0",
|
||||||
"toolkit/pflag": "dev-main",
|
"toolkit/pflag": "dev-main",
|
||||||
"symfony/console": "^7.0",
|
"symfony/console": "^6.0",
|
||||||
"fire015/flintstone": "dev-master",
|
|
||||||
"php-ds/php-ds": "dev-master as v1.1",
|
|
||||||
"malios/php-to-ascii-table": "dev-master"
|
"malios/php-to-ascii-table": "dev-master"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
|||||||
@ -8,25 +8,6 @@
|
|||||||
|
|
||||||
[comment]: <> (</details>)
|
[comment]: <> (</details>)
|
||||||
|
|
||||||
## v2.4.5.250218 alpha (2025-02-18)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
-
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 更新设备信息
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 尝试修复(-403)账号异常,操作失败
|
|
||||||
- Make database config type explicitely nullable (fix php84 deprecation)
|
|
||||||
|
|
||||||
### Remarks
|
|
||||||
|
|
||||||
- Goodbye 2024
|
|
||||||
|
|
||||||
## v2.4.3.241231 alpha (2024-12-31)
|
## v2.4.3.241231 alpha (2024-12-31)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
1
packages/flintstone/.github/FUNDING.yml
vendored
1
packages/flintstone/.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
|||||||
github: fire015
|
|
||||||
4
packages/flintstone/.gitignore
vendored
4
packages/flintstone/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
/vendor
|
|
||||||
composer.lock
|
|
||||||
.idea
|
|
||||||
.phpunit.result.cache
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
language: php
|
|
||||||
sudo: false
|
|
||||||
dist: bionic
|
|
||||||
|
|
||||||
php:
|
|
||||||
- 7.3
|
|
||||||
- 7.4
|
|
||||||
- 8.0
|
|
||||||
- 8.1.0
|
|
||||||
|
|
||||||
install:
|
|
||||||
- travis_retry composer install --no-interaction --prefer-dist
|
|
||||||
|
|
||||||
script: vendor/bin/phpunit
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
Change Log
|
|
||||||
==========
|
|
||||||
|
|
||||||
### 19/01/2021 - 2.3
|
|
||||||
* Bump minimum PHP version to 7.3
|
|
||||||
* Update PHPUnit to version 9 (ensure Flintstone is compatible with PHP 8)
|
|
||||||
|
|
||||||
### 12/03/2019 - 2.2
|
|
||||||
* Bump minimum PHP version to 7.0
|
|
||||||
* Update PHPUnit to version 6
|
|
||||||
* Removed data type validation for storing
|
|
||||||
* Added param and return types
|
|
||||||
|
|
||||||
### 09/06/2017 - 2.1.1
|
|
||||||
* Update `Database::writeTempToFile` to correctly close the file pointer and free up memory
|
|
||||||
|
|
||||||
### 24/05/2017 - 2.1
|
|
||||||
* Bump minimum PHP version to 5.6
|
|
||||||
* Tidy up of Flintstone class, moved some code into `Database`
|
|
||||||
* Added `Line` and `Validation` classes
|
|
||||||
* Closed off public methods `Database::openFile` and `Database::closeFile`
|
|
||||||
|
|
||||||
### 20/01/2016 - 2.0
|
|
||||||
* Major refactor, class names have changed and the whole codebase is much more extensible
|
|
||||||
* Removed the static `load` and `unload` methods and the `FlinstoneDB` class
|
|
||||||
* The `replace` method is no longer public
|
|
||||||
* The `getFile` method has been removed
|
|
||||||
* Default swap memory limit has been increased to 2MB
|
|
||||||
* Ability to pass any instance for cache that implements `Flintstone\Cache\CacheInterface`
|
|
||||||
|
|
||||||
### 25/03/2015 - 1.9
|
|
||||||
* Added `getAll` method and some refactoring
|
|
||||||
|
|
||||||
### 15/10/2014 - 1.8
|
|
||||||
* Added formatter option so that you can control how data is encoded/decoded (default is serialize but also ships with json)
|
|
||||||
|
|
||||||
### 09/10/2014 - 1.7
|
|
||||||
* Moved from fopen to SplFileObject
|
|
||||||
* Moved composer loader from PSR-0 to PSR-4
|
|
||||||
* Code is now PSR-2 compliant
|
|
||||||
* Added PHP 5.6 to travis
|
|
||||||
|
|
||||||
### 30/09/2014 - 1.6
|
|
||||||
* Updated limits on valid characters in key name and size
|
|
||||||
* Improved unit tests
|
|
||||||
|
|
||||||
### 29/05/2014 - 1.5
|
|
||||||
* Reduced some internal complexity
|
|
||||||
* Fixed gzip compression
|
|
||||||
* Unit tests now running against all options
|
|
||||||
* Removed `setOptions` method, must be passed into the `load` method
|
|
||||||
|
|
||||||
### 11/03/2014 - 1.4
|
|
||||||
* Now using Composer
|
|
||||||
|
|
||||||
### 16/07/2013 - 1.3
|
|
||||||
* Changed the load method to static so that multiple instances can be loaded without conflict (use Flintstone::load now instead of $db->load)
|
|
||||||
* Exception thrown is now FlintstoneException
|
|
||||||
|
|
||||||
### 23/01/2013 - 1.2
|
|
||||||
* Removed the multibyte unserialize method as it seems to work without
|
|
||||||
|
|
||||||
### 22/06/2012 - 1.1
|
|
||||||
* Added new method getKeys() to return an array of keys in the database
|
|
||||||
|
|
||||||
### 17/06/2011 - 1.0
|
|
||||||
* Initial release
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2010-2017 Jason M
|
|
||||||
|
|
||||||
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.**
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
Flintstone
|
|
||||||
==========
|
|
||||||
|
|
||||||
[](https://packagist.org/packages/fire015/flintstone)
|
|
||||||
[](https://travis-ci.com/github/fire015/flintstone)
|
|
||||||
|
|
||||||
A key/value database store using flat files for PHP.
|
|
||||||
|
|
||||||
Features include:
|
|
||||||
|
|
||||||
* Memory efficient
|
|
||||||
* File locking
|
|
||||||
* Caching
|
|
||||||
* Gzip compression
|
|
||||||
* Easy to use
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
The easiest way to install Flintstone is via [composer](http://getcomposer.org/). Run the following command to install it.
|
|
||||||
|
|
||||||
```
|
|
||||||
composer require fire015/flintstone
|
|
||||||
```
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
|
|
||||||
use Flintstone\Flintstone;
|
|
||||||
|
|
||||||
$users = new Flintstone('users', ['dir' => '/path/to/database/dir/']);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- PHP 7.3+
|
|
||||||
|
|
||||||
### Data types
|
|
||||||
|
|
||||||
Flintstone can store any data type that can be formatted into a string. By default this uses `serialize()`. See [Changing the formatter](#changing-the-formatter) for more details.
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
|Name |Type |Default Value |Description |
|
|
||||||
|--- |--- |--- |--- |
|
|
||||||
|dir |string |the current working directory |The directory where the database files are stored (this should be somewhere that is not web accessible) e.g. /path/to/database/ |
|
|
||||||
|ext |string |.dat |The database file extension to use |
|
|
||||||
|gzip |boolean |false |Use gzip to compress the database |
|
|
||||||
|cache |boolean or object |true |Whether to cache `get()` results for faster data retrieval |
|
|
||||||
|formatter |null or object |null |The formatter class used to encode/decode data |
|
|
||||||
|swap_memory_limit |integer |2097152 |The amount of memory to use before writing to a temporary file |
|
|
||||||
|
|
||||||
|
|
||||||
### Usage examples
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
// Load a database
|
|
||||||
$users = new Flintstone('users', ['dir' => '/path/to/database/dir/']);
|
|
||||||
|
|
||||||
// Set a key
|
|
||||||
$users->set('bob', ['email' => 'bob@site.com', 'password' => '123456']);
|
|
||||||
|
|
||||||
// Get a key
|
|
||||||
$user = $users->get('bob');
|
|
||||||
echo 'Bob, your email is ' . $user['email'];
|
|
||||||
|
|
||||||
// Retrieve all key names
|
|
||||||
$keys = $users->getKeys(); // returns array('bob')
|
|
||||||
|
|
||||||
// Retrieve all data
|
|
||||||
$data = $users->getAll(); // returns array('bob' => array('email' => 'bob@site.com', 'password' => '123456'));
|
|
||||||
|
|
||||||
// Delete a key
|
|
||||||
$users->delete('bob');
|
|
||||||
|
|
||||||
// Flush the database
|
|
||||||
$users->flush();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changing the formatter
|
|
||||||
By default Flintstone will encode/decode data using PHP's serialize functions, however you can override this with your own class if you prefer.
|
|
||||||
|
|
||||||
Just make sure it implements `Flintstone\Formatter\FormatterInterface` and then you can provide it as the `formatter` option.
|
|
||||||
|
|
||||||
If you wish to use JSON as the formatter, Flintstone already ships with this as per the example below:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
|
|
||||||
use Flintstone\Flintstone;
|
|
||||||
use Flintstone\Formatter\JsonFormatter;
|
|
||||||
|
|
||||||
$users = new Flintstone('users', [
|
|
||||||
'dir' => __DIR__,
|
|
||||||
'formatter' => new JsonFormatter()
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Changing the cache
|
|
||||||
To speed up data retrieval Flintstone can store the results of `get()` in a cache store. By default this uses a simple array that only persist's for as long as the `Flintstone` object exists.
|
|
||||||
|
|
||||||
If you want to use your own cache store (such as Memcached) you can pass a class as the `cache` option. Just make sure it implements `Flintstone\Cache\CacheInterface`.
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
Upgrading from version 1.x to 2.x
|
|
||||||
=================================
|
|
||||||
|
|
||||||
As Flintstone is no longer loaded statically the major change required is to switch from using the static `load` method to just instantiating a new instance of Flinstone.
|
|
||||||
|
|
||||||
The `FlinstoneDB` class has also been removed and `Flintstone\FlintstoneException` is now `Flintstone\Exception`.
|
|
||||||
|
|
||||||
### Version 1.x:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
|
|
||||||
use Flintstone\Flintstone;
|
|
||||||
use Flintstone\FlintstoneException;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$users = Flintstone::load('users', array('dir' => '/path/to/database/dir/'));
|
|
||||||
}
|
|
||||||
catch (FlintstoneException $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Version 2.x:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
|
|
||||||
use Flintstone\Flintstone;
|
|
||||||
use Flintstone\Exception;
|
|
||||||
|
|
||||||
try {
|
|
||||||
$users = new Flintstone('users', array('dir' => '/path/to/database/dir/'));
|
|
||||||
}
|
|
||||||
catch (Exception $e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See CHANGELOG.md for further changes.
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "fire015/flintstone",
|
|
||||||
"type": "library",
|
|
||||||
"description": "A key/value database store using flat files for PHP",
|
|
||||||
"keywords": ["flintstone", "database", "cache", "files", "memory"],
|
|
||||||
"homepage": "https://github.com/fire015/flintstone",
|
|
||||||
"license": "MIT",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Jason M",
|
|
||||||
"email": "emailfire@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.3"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Flintstone\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require-dev" : {
|
|
||||||
"phpunit/phpunit": "^9"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" verbose="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
|
|
||||||
<coverage processUncoveredFiles="true">
|
|
||||||
<include>
|
|
||||||
<directory suffix=".php">./src</directory>
|
|
||||||
</include>
|
|
||||||
</coverage>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="Flintstone Test Suite">
|
|
||||||
<directory>./tests</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
</phpunit>
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone\Cache;
|
|
||||||
|
|
||||||
class ArrayCache implements CacheInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Cache data.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $cache = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function contains($key)
|
|
||||||
{
|
|
||||||
return array_key_exists($key, $this->cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function get($key)
|
|
||||||
{
|
|
||||||
return $this->cache[$key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function set($key, $data)
|
|
||||||
{
|
|
||||||
$this->cache[$key] = $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function delete($key)
|
|
||||||
{
|
|
||||||
unset($this->cache[$key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function flush()
|
|
||||||
{
|
|
||||||
$this->cache = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone\Cache;
|
|
||||||
|
|
||||||
interface CacheInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Check if a key exists in the cache.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function contains($key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a key from the cache.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function get($key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a key in the cache.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $data
|
|
||||||
*/
|
|
||||||
public function set($key, $data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a key from the cache.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*/
|
|
||||||
public function delete($key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush the cache.
|
|
||||||
*/
|
|
||||||
public function flush();
|
|
||||||
}
|
|
||||||
@ -1,209 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone;
|
|
||||||
|
|
||||||
use Flintstone\Cache\ArrayCache;
|
|
||||||
use Flintstone\Cache\CacheInterface;
|
|
||||||
use Flintstone\Formatter\FormatterInterface;
|
|
||||||
use Flintstone\Formatter\SerializeFormatter;
|
|
||||||
|
|
||||||
class Config
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Config.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $config = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param array $config
|
|
||||||
*/
|
|
||||||
public function __construct(array $config = [])
|
|
||||||
{
|
|
||||||
$config = $this->normalizeConfig($config);
|
|
||||||
$this->setDir($config['dir']);
|
|
||||||
$this->setExt($config['ext']);
|
|
||||||
$this->setGzip($config['gzip']);
|
|
||||||
$this->setCache($config['cache']);
|
|
||||||
$this->setFormatter($config['formatter']);
|
|
||||||
$this->setSwapMemoryLimit($config['swap_memory_limit']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalize the user supplied config.
|
|
||||||
*
|
|
||||||
* @param array $config
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function normalizeConfig(array $config): array
|
|
||||||
{
|
|
||||||
$defaultConfig = [
|
|
||||||
'dir' => getcwd(),
|
|
||||||
'ext' => '.dat',
|
|
||||||
'gzip' => false,
|
|
||||||
'cache' => true,
|
|
||||||
'formatter' => null,
|
|
||||||
'swap_memory_limit' => 2097152, // 2MB
|
|
||||||
];
|
|
||||||
|
|
||||||
return array_replace($defaultConfig, $config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the dir.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getDir(): string
|
|
||||||
{
|
|
||||||
return $this->config['dir'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the dir.
|
|
||||||
*
|
|
||||||
* @param string $dir
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function setDir(string $dir)
|
|
||||||
{
|
|
||||||
if (!is_dir($dir)) {
|
|
||||||
throw new Exception('Directory does not exist: ' . $dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->config['dir'] = rtrim($dir, '/\\') . DIRECTORY_SEPARATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ext.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getExt(): string
|
|
||||||
{
|
|
||||||
if ($this->useGzip()) {
|
|
||||||
return $this->config['ext'] . '.gz';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->config['ext'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the ext.
|
|
||||||
*
|
|
||||||
* @param string $ext
|
|
||||||
*/
|
|
||||||
public function setExt(string $ext)
|
|
||||||
{
|
|
||||||
if (substr($ext, 0, 1) !== '.') {
|
|
||||||
$ext = '.' . $ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->config['ext'] = $ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use gzip?
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function useGzip(): bool
|
|
||||||
{
|
|
||||||
return $this->config['gzip'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set gzip.
|
|
||||||
*
|
|
||||||
* @param bool $gzip
|
|
||||||
*/
|
|
||||||
public function setGzip(bool $gzip)
|
|
||||||
{
|
|
||||||
$this->config['gzip'] = $gzip;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the cache.
|
|
||||||
*
|
|
||||||
* @return CacheInterface|false
|
|
||||||
*/
|
|
||||||
public function getCache()
|
|
||||||
{
|
|
||||||
return $this->config['cache'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the cache.
|
|
||||||
*
|
|
||||||
* @param mixed $cache
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function setCache($cache)
|
|
||||||
{
|
|
||||||
if (!is_bool($cache) && !$cache instanceof CacheInterface) {
|
|
||||||
throw new Exception('Cache must be a boolean or an instance of Flintstone\Cache\CacheInterface');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($cache === true) {
|
|
||||||
$cache = new ArrayCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->config['cache'] = $cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the formatter.
|
|
||||||
*
|
|
||||||
* @return FormatterInterface
|
|
||||||
*/
|
|
||||||
public function getFormatter(): FormatterInterface
|
|
||||||
{
|
|
||||||
return $this->config['formatter'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the formatter.
|
|
||||||
*
|
|
||||||
* @param FormatterInterface|null $formatter
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function setFormatter($formatter)
|
|
||||||
{
|
|
||||||
if ($formatter === null) {
|
|
||||||
$formatter = new SerializeFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$formatter instanceof FormatterInterface) {
|
|
||||||
throw new Exception('Formatter must be an instance of Flintstone\Formatter\FormatterInterface');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->config['formatter'] = $formatter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the swap memory limit.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getSwapMemoryLimit(): int
|
|
||||||
{
|
|
||||||
return $this->config['swap_memory_limit'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the swap memory limit.
|
|
||||||
*
|
|
||||||
* @param int $limit
|
|
||||||
*/
|
|
||||||
public function setSwapMemoryLimit(int $limit)
|
|
||||||
{
|
|
||||||
$this->config['swap_memory_limit'] = $limit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,255 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone;
|
|
||||||
|
|
||||||
use SplFileObject;
|
|
||||||
use SplTempFileObject;
|
|
||||||
|
|
||||||
class Database
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* File read flag.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const FILE_READ = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File write flag.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const FILE_WRITE = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File append flag.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
const FILE_APPEND = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File access mode.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $fileAccessMode = [
|
|
||||||
self::FILE_READ => [
|
|
||||||
'mode' => 'rb',
|
|
||||||
'operation' => LOCK_SH,
|
|
||||||
],
|
|
||||||
self::FILE_WRITE => [
|
|
||||||
'mode' => 'wb',
|
|
||||||
'operation' => LOCK_EX,
|
|
||||||
],
|
|
||||||
self::FILE_APPEND => [
|
|
||||||
'mode' => 'ab',
|
|
||||||
'operation' => LOCK_EX,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database name.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Config class.
|
|
||||||
*
|
|
||||||
* @var Config
|
|
||||||
*/
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
* @param Config|null $config
|
|
||||||
*/
|
|
||||||
public function __construct(string $name, Config|null $config = null)
|
|
||||||
{
|
|
||||||
$this->setName($name);
|
|
||||||
|
|
||||||
if ($config) {
|
|
||||||
$this->setConfig($config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the database name.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getName(): string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the database name.
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function setName(string $name)
|
|
||||||
{
|
|
||||||
Validation::validateDatabaseName($name);
|
|
||||||
$this->name = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the config.
|
|
||||||
*
|
|
||||||
* @return Config
|
|
||||||
*/
|
|
||||||
public function getConfig(): Config
|
|
||||||
{
|
|
||||||
return $this->config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the config.
|
|
||||||
*
|
|
||||||
* @param Config $config
|
|
||||||
*/
|
|
||||||
public function setConfig(Config $config)
|
|
||||||
{
|
|
||||||
$this->config = $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the path to the database file.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getPath(): string
|
|
||||||
{
|
|
||||||
return $this->config->getDir() . $this->getName() . $this->config->getExt();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the database file.
|
|
||||||
*
|
|
||||||
* @param int $mode
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*
|
|
||||||
* @return SplFileObject
|
|
||||||
*/
|
|
||||||
protected function openFile(int $mode): SplFileObject
|
|
||||||
{
|
|
||||||
$path = $this->getPath();
|
|
||||||
|
|
||||||
if (!is_file($path) && !@touch($path)) {
|
|
||||||
throw new Exception('Could not create file: ' . $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_readable($path) || !is_writable($path)) {
|
|
||||||
throw new Exception('File does not have permission for read and write: ' . $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->getConfig()->useGzip()) {
|
|
||||||
$path = 'compress.zlib://' . $path;
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = $this->fileAccessMode[$mode];
|
|
||||||
$file = new SplFileObject($path, $res['mode']);
|
|
||||||
|
|
||||||
if ($mode === self::FILE_READ) {
|
|
||||||
$file->setFlags(SplFileObject::DROP_NEW_LINE | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->getConfig()->useGzip() && !$file->flock($res['operation'])) {
|
|
||||||
$file = null;
|
|
||||||
throw new Exception('Could not lock file: ' . $path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a temporary file.
|
|
||||||
*
|
|
||||||
* @return SplTempFileObject
|
|
||||||
*/
|
|
||||||
public function openTempFile(): SplTempFileObject
|
|
||||||
{
|
|
||||||
return new SplTempFileObject($this->getConfig()->getSwapMemoryLimit());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the database file.
|
|
||||||
*
|
|
||||||
* @param SplFileObject $file
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
protected function closeFile(SplFileObject &$file)
|
|
||||||
{
|
|
||||||
if (!$this->getConfig()->useGzip() && !$file->flock(LOCK_UN)) {
|
|
||||||
$file = null;
|
|
||||||
throw new Exception('Could not unlock file');
|
|
||||||
}
|
|
||||||
|
|
||||||
$file = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read lines from the database file.
|
|
||||||
*
|
|
||||||
* @return \Generator
|
|
||||||
*/
|
|
||||||
public function readFromFile(): \Generator
|
|
||||||
{
|
|
||||||
$file = $this->openFile(static::FILE_READ);
|
|
||||||
|
|
||||||
try {
|
|
||||||
foreach ($file as $line) {
|
|
||||||
yield new Line($line);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
$this->closeFile($file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a line to the database file.
|
|
||||||
*
|
|
||||||
* @param string $line
|
|
||||||
*/
|
|
||||||
public function appendToFile(string $line)
|
|
||||||
{
|
|
||||||
$file = $this->openFile(static::FILE_APPEND);
|
|
||||||
$file->fwrite($line);
|
|
||||||
$this->closeFile($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush the database file.
|
|
||||||
*/
|
|
||||||
public function flushFile()
|
|
||||||
{
|
|
||||||
$file = $this->openFile(static::FILE_WRITE);
|
|
||||||
$this->closeFile($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write temporary file contents to database file.
|
|
||||||
*
|
|
||||||
* @param SplTempFileObject $tmpFile
|
|
||||||
*/
|
|
||||||
public function writeTempToFile(SplTempFileObject &$tmpFile)
|
|
||||||
{
|
|
||||||
$file = $this->openFile(static::FILE_WRITE);
|
|
||||||
|
|
||||||
foreach ($tmpFile as $line) {
|
|
||||||
$file->fwrite($line);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->closeFile($file);
|
|
||||||
$tmpFile = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone;
|
|
||||||
|
|
||||||
class Exception extends \Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@ -1,285 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone;
|
|
||||||
|
|
||||||
class Flintstone
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Flintstone version.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
const VERSION = '2.3';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Database class.
|
|
||||||
*
|
|
||||||
* @var Database
|
|
||||||
*/
|
|
||||||
protected $database;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Config class.
|
|
||||||
*
|
|
||||||
* @var Config
|
|
||||||
*/
|
|
||||||
protected $config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param Database|string $database
|
|
||||||
* @param Config|array $config
|
|
||||||
*/
|
|
||||||
public function __construct($database, $config)
|
|
||||||
{
|
|
||||||
if (is_string($database)) {
|
|
||||||
$database = new Database($database);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($config)) {
|
|
||||||
$config = new Config($config);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setDatabase($database);
|
|
||||||
$this->setConfig($config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the database.
|
|
||||||
*
|
|
||||||
* @return Database
|
|
||||||
*/
|
|
||||||
public function getDatabase(): Database
|
|
||||||
{
|
|
||||||
return $this->database;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the database.
|
|
||||||
*
|
|
||||||
* @param Database $database
|
|
||||||
*/
|
|
||||||
public function setDatabase(Database $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the config.
|
|
||||||
*
|
|
||||||
* @return Config
|
|
||||||
*/
|
|
||||||
public function getConfig(): Config
|
|
||||||
{
|
|
||||||
return $this->config;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the config.
|
|
||||||
*
|
|
||||||
* @param Config $config
|
|
||||||
*/
|
|
||||||
public function setConfig(Config $config)
|
|
||||||
{
|
|
||||||
$this->config = $config;
|
|
||||||
$this->getDatabase()->setConfig($config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a key from the database.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function get(string $key)
|
|
||||||
{
|
|
||||||
Validation::validateKey($key);
|
|
||||||
|
|
||||||
// Fetch the key from cache
|
|
||||||
if ($cache = $this->getConfig()->getCache()) {
|
|
||||||
if ($cache->contains($key)) {
|
|
||||||
return $cache->get($key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the key from database
|
|
||||||
$file = $this->getDatabase()->readFromFile();
|
|
||||||
$data = false;
|
|
||||||
|
|
||||||
foreach ($file as $line) {
|
|
||||||
/** @var Line $line */
|
|
||||||
if ($line->getKey() == $key) {
|
|
||||||
$data = $this->decodeData($line->getData());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the data to cache
|
|
||||||
if ($cache && $data !== false) {
|
|
||||||
$cache->set($key, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a key in the database.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $data
|
|
||||||
*/
|
|
||||||
public function set(string $key, $data)
|
|
||||||
{
|
|
||||||
Validation::validateKey($key);
|
|
||||||
|
|
||||||
// If the key already exists we need to replace it
|
|
||||||
if ($this->get($key) !== false) {
|
|
||||||
$this->replace($key, $data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the key to the database
|
|
||||||
$this->getDatabase()->appendToFile($this->getLineString($key, $data));
|
|
||||||
|
|
||||||
// Delete the key from cache
|
|
||||||
if ($cache = $this->getConfig()->getCache()) {
|
|
||||||
$cache->delete($key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a key from the database.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*/
|
|
||||||
public function delete(string $key)
|
|
||||||
{
|
|
||||||
Validation::validateKey($key);
|
|
||||||
|
|
||||||
if ($this->get($key) !== false) {
|
|
||||||
$this->replace($key, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush the database.
|
|
||||||
*/
|
|
||||||
public function flush()
|
|
||||||
{
|
|
||||||
$this->getDatabase()->flushFile();
|
|
||||||
|
|
||||||
// Flush the cache
|
|
||||||
if ($cache = $this->getConfig()->getCache()) {
|
|
||||||
$cache->flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all keys from the database.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getKeys(): array
|
|
||||||
{
|
|
||||||
$keys = [];
|
|
||||||
$file = $this->getDatabase()->readFromFile();
|
|
||||||
|
|
||||||
foreach ($file as $line) {
|
|
||||||
/** @var Line $line */
|
|
||||||
$keys[] = $line->getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all data from the database.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getAll(): array
|
|
||||||
{
|
|
||||||
$data = [];
|
|
||||||
$file = $this->getDatabase()->readFromFile();
|
|
||||||
|
|
||||||
foreach ($file as $line) {
|
|
||||||
/** @var Line $line */
|
|
||||||
$data[$line->getKey()] = $this->decodeData($line->getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace a key in the database.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $data
|
|
||||||
*/
|
|
||||||
protected function replace(string $key, $data)
|
|
||||||
{
|
|
||||||
// Write a new database to a temporary file
|
|
||||||
$tmpFile = $this->getDatabase()->openTempFile();
|
|
||||||
$file = $this->getDatabase()->readFromFile();
|
|
||||||
|
|
||||||
foreach ($file as $line) {
|
|
||||||
/** @var Line $line */
|
|
||||||
if ($line->getKey() == $key) {
|
|
||||||
if ($data !== false) {
|
|
||||||
$tmpFile->fwrite($this->getLineString($key, $data));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$tmpFile->fwrite($line->getLine() . "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$tmpFile->rewind();
|
|
||||||
|
|
||||||
// Overwrite the database with the temporary file
|
|
||||||
$this->getDatabase()->writeTempToFile($tmpFile);
|
|
||||||
|
|
||||||
// Delete the key from cache
|
|
||||||
if ($cache = $this->getConfig()->getCache()) {
|
|
||||||
$cache->delete($key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the line string to write.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
* @param mixed $data
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function getLineString(string $key, $data): string
|
|
||||||
{
|
|
||||||
return $key . '=' . $this->encodeData($data) . "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a string into data.
|
|
||||||
*
|
|
||||||
* @param string $data
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
protected function decodeData(string $data)
|
|
||||||
{
|
|
||||||
return $this->getConfig()->getFormatter()->decode($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode data into a string.
|
|
||||||
*
|
|
||||||
* @param mixed $data
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function encodeData($data): string
|
|
||||||
{
|
|
||||||
return $this->getConfig()->getFormatter()->encode($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone\Formatter;
|
|
||||||
|
|
||||||
interface FormatterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Encode data into a string.
|
|
||||||
*
|
|
||||||
* @param mixed $data
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function encode($data): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a string into data.
|
|
||||||
*
|
|
||||||
* @param string $data
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function decode(string $data);
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone\Formatter;
|
|
||||||
|
|
||||||
use Flintstone\Exception;
|
|
||||||
|
|
||||||
class JsonFormatter implements FormatterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $assoc;
|
|
||||||
|
|
||||||
public function __construct(bool $assoc = true)
|
|
||||||
{
|
|
||||||
$this->assoc = $assoc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function encode($data): string
|
|
||||||
{
|
|
||||||
$result = json_encode($data);
|
|
||||||
|
|
||||||
if (json_last_error() === JSON_ERROR_NONE) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception(json_last_error_msg());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function decode(string $data)
|
|
||||||
{
|
|
||||||
$result = json_decode($data, $this->assoc);
|
|
||||||
|
|
||||||
if (json_last_error() === JSON_ERROR_NONE) {
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception(json_last_error_msg());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone\Formatter;
|
|
||||||
|
|
||||||
class SerializeFormatter implements FormatterInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function encode($data): string
|
|
||||||
{
|
|
||||||
return serialize($this->preserveLines($data, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function decode(string $data)
|
|
||||||
{
|
|
||||||
return $this->preserveLines(unserialize($data), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preserve new lines, recursive function.
|
|
||||||
*
|
|
||||||
* @param mixed $data
|
|
||||||
* @param bool $reverse
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
protected function preserveLines($data, bool $reverse)
|
|
||||||
{
|
|
||||||
$search = ["\n", "\r"];
|
|
||||||
$replace = ['\\n', '\\r'];
|
|
||||||
|
|
||||||
if ($reverse) {
|
|
||||||
$search = ['\\n', '\\r'];
|
|
||||||
$replace = ["\n", "\r"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string($data)) {
|
|
||||||
$data = str_replace($search, $replace, $data);
|
|
||||||
} elseif (is_array($data)) {
|
|
||||||
foreach ($data as &$value) {
|
|
||||||
$value = $this->preserveLines($value, $reverse);
|
|
||||||
}
|
|
||||||
unset($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone;
|
|
||||||
|
|
||||||
class Line
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $line;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $pieces = [];
|
|
||||||
|
|
||||||
public function __construct(string $line)
|
|
||||||
{
|
|
||||||
$this->line = $line;
|
|
||||||
$this->pieces = explode('=', $line, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLine(): string
|
|
||||||
{
|
|
||||||
return $this->line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKey(): string
|
|
||||||
{
|
|
||||||
return $this->pieces[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getData(): string
|
|
||||||
{
|
|
||||||
return $this->pieces[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Flintstone;
|
|
||||||
|
|
||||||
class Validation
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Validate the key.
|
|
||||||
*
|
|
||||||
* @param string $key
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static function validateKey(string $key)
|
|
||||||
{
|
|
||||||
if (empty($key) || !preg_match('/^[\w-]+$/', $key)) {
|
|
||||||
throw new Exception('Invalid characters in key');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the database name is valid.
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
*
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static function validateDatabaseName(string $name)
|
|
||||||
{
|
|
||||||
if (empty($name) || !preg_match('/^[\w-]+$/', $name)) {
|
|
||||||
throw new Exception('Invalid characters in database name');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Cache\ArrayCache;
|
|
||||||
|
|
||||||
class ArrayCacheTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var ArrayCache
|
|
||||||
*/
|
|
||||||
private $cache;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->cache = new ArrayCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canGetAndSet()
|
|
||||||
{
|
|
||||||
$this->cache->set('foo', 'bar');
|
|
||||||
$this->assertTrue($this->cache->contains('foo'));
|
|
||||||
$this->assertEquals('bar', $this->cache->get('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canDelete()
|
|
||||||
{
|
|
||||||
$this->cache->set('foo', 'bar');
|
|
||||||
$this->cache->delete('foo');
|
|
||||||
$this->assertFalse($this->cache->contains('foo'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canFlush()
|
|
||||||
{
|
|
||||||
$this->cache->set('foo', 'bar');
|
|
||||||
$this->cache->flush();
|
|
||||||
$this->assertFalse($this->cache->contains('foo'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Cache\ArrayCache;
|
|
||||||
use Flintstone\Config;
|
|
||||||
use Flintstone\Formatter\JsonFormatter;
|
|
||||||
use Flintstone\Formatter\SerializeFormatter;
|
|
||||||
|
|
||||||
class ConfigTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function defaultConfigIsSet()
|
|
||||||
{
|
|
||||||
$config = new Config();
|
|
||||||
$this->assertEquals(getcwd().DIRECTORY_SEPARATOR, $config->getDir());
|
|
||||||
$this->assertEquals('.dat', $config->getExt());
|
|
||||||
$this->assertFalse($config->useGzip());
|
|
||||||
$this->assertInstanceOf(ArrayCache::class, $config->getCache());
|
|
||||||
$this->assertInstanceOf(SerializeFormatter::class, $config->getFormatter());
|
|
||||||
$this->assertEquals(2097152, $config->getSwapMemoryLimit());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function constructorConfigOverride()
|
|
||||||
{
|
|
||||||
$config = new Config([
|
|
||||||
'dir' => __DIR__,
|
|
||||||
'ext' => 'test',
|
|
||||||
'gzip' => true,
|
|
||||||
'cache' => false,
|
|
||||||
'formatter' => null,
|
|
||||||
'swap_memory_limit' => 100,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertEquals(__DIR__.DIRECTORY_SEPARATOR, $config->getDir());
|
|
||||||
$this->assertEquals('.test.gz', $config->getExt());
|
|
||||||
$this->assertTrue($config->useGzip());
|
|
||||||
$this->assertFalse($config->getCache());
|
|
||||||
$this->assertInstanceOf(SerializeFormatter::class, $config->getFormatter());
|
|
||||||
$this->assertEquals(100, $config->getSwapMemoryLimit());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function setValidFormatter()
|
|
||||||
{
|
|
||||||
$config = new Config();
|
|
||||||
$config->setFormatter(new JsonFormatter());
|
|
||||||
$this->assertInstanceOf(JsonFormatter::class, $config->getFormatter());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function setInvalidFormatter()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$config = new Config();
|
|
||||||
$config->setFormatter(new self());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function invalidDirSet()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$config = new Config();
|
|
||||||
$config->setDir('/x/y/z/foo');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function invalidCacheSet()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$config = new Config();
|
|
||||||
$config->setCache(new self());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Config;
|
|
||||||
use Flintstone\Database;
|
|
||||||
use Flintstone\Line;
|
|
||||||
|
|
||||||
class DatabaseTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Database
|
|
||||||
*/
|
|
||||||
private $db;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$config = new Config([
|
|
||||||
'dir' => __DIR__,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->db = new Database('test', $config);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function tearDown(): void
|
|
||||||
{
|
|
||||||
if (is_file($this->db->getPath())) {
|
|
||||||
unlink($this->db->getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function databaseHasInvalidName()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$config = new Config();
|
|
||||||
new Database('test!123', $config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canGetDatabaseAndConfig()
|
|
||||||
{
|
|
||||||
$this->assertEquals('test', $this->db->getName());
|
|
||||||
$this->assertInstanceOf(Config::class, $this->db->getConfig());
|
|
||||||
$this->assertEquals(__DIR__ . DIRECTORY_SEPARATOR . 'test.dat', $this->db->getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canAppendToFile()
|
|
||||||
{
|
|
||||||
$this->db->appendToFile('foo=bar');
|
|
||||||
$this->assertEquals('foo=bar', file_get_contents($this->db->getPath()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canFlushFile()
|
|
||||||
{
|
|
||||||
$this->db->appendToFile('foo=bar');
|
|
||||||
$this->db->flushFile();
|
|
||||||
$this->assertEmpty(file_get_contents($this->db->getPath()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canReadFromFile()
|
|
||||||
{
|
|
||||||
$this->db->appendToFile('foo=bar');
|
|
||||||
$file = $this->db->readFromFile();
|
|
||||||
|
|
||||||
foreach ($file as $line) {
|
|
||||||
$this->assertInstanceOf(Line::class, $line);
|
|
||||||
$this->assertEquals('foo', $line->getKey());
|
|
||||||
$this->assertEquals('bar', $line->getData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canWriteTempToFile()
|
|
||||||
{
|
|
||||||
$tmpFile = new SplTempFileObject();
|
|
||||||
$tmpFile->fwrite('foo=bar');
|
|
||||||
$tmpFile->rewind();
|
|
||||||
|
|
||||||
$this->db->writeTempToFile($tmpFile);
|
|
||||||
$this->assertEquals('foo=bar', file_get_contents($this->db->getPath()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Config;
|
|
||||||
use Flintstone\Database;
|
|
||||||
use Flintstone\Flintstone;
|
|
||||||
use Flintstone\Formatter\JsonFormatter;
|
|
||||||
|
|
||||||
class FlintstoneTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
public function testGetDatabaseAndConfig()
|
|
||||||
{
|
|
||||||
$db = new Flintstone('test', [
|
|
||||||
'dir' => __DIR__,
|
|
||||||
'cache' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertInstanceOf(Database::class, $db->getDatabase());
|
|
||||||
$this->assertInstanceOf(Config::class, $db->getConfig());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function keyHasInvalidName()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$db = new Flintstone('test', []);
|
|
||||||
$db->get('test!123');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canRunAllOperations()
|
|
||||||
{
|
|
||||||
$this->runOperationsTests([
|
|
||||||
'dir' => __DIR__,
|
|
||||||
'cache' => false,
|
|
||||||
'gzip' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->runOperationsTests([
|
|
||||||
'dir' => __DIR__,
|
|
||||||
'cache' => true,
|
|
||||||
'gzip' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->runOperationsTests([
|
|
||||||
'dir' => __DIR__,
|
|
||||||
'cache' => false,
|
|
||||||
'gzip' => false,
|
|
||||||
'formatter' => new JsonFormatter(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function runOperationsTests(array $config)
|
|
||||||
{
|
|
||||||
$db = new Flintstone('test', $config);
|
|
||||||
$arr = ['foo' => "new\nline"];
|
|
||||||
|
|
||||||
$this->assertFalse($db->get('foo'));
|
|
||||||
|
|
||||||
$db->set('foo', 1);
|
|
||||||
$db->set('name', 'john');
|
|
||||||
$db->set('arr', $arr);
|
|
||||||
$this->assertEquals(1, $db->get('foo'));
|
|
||||||
$this->assertEquals('john', $db->get('name'));
|
|
||||||
$this->assertEquals($arr, $db->get('arr'));
|
|
||||||
|
|
||||||
$db->set('foo', 2);
|
|
||||||
$this->assertEquals(2, $db->get('foo'));
|
|
||||||
$this->assertEquals('john', $db->get('name'));
|
|
||||||
$this->assertEquals($arr, $db->get('arr'));
|
|
||||||
|
|
||||||
$db->delete('name');
|
|
||||||
$this->assertFalse($db->get('name'));
|
|
||||||
$this->assertEquals($arr, $db->get('arr'));
|
|
||||||
|
|
||||||
$keys = $db->getKeys();
|
|
||||||
$this->assertEquals(2, count($keys));
|
|
||||||
$this->assertEquals('foo', $keys[0]);
|
|
||||||
$this->assertEquals('arr', $keys[1]);
|
|
||||||
|
|
||||||
$data = $db->getAll();
|
|
||||||
$this->assertEquals(2, count($data));
|
|
||||||
$this->assertEquals(2, $data['foo']);
|
|
||||||
$this->assertEquals($arr, $data['arr']);
|
|
||||||
|
|
||||||
$db->flush();
|
|
||||||
$this->assertFalse($db->get('foo'));
|
|
||||||
$this->assertFalse($db->get('arr'));
|
|
||||||
$this->assertEquals(0, count($db->getKeys()));
|
|
||||||
$this->assertEquals(0, count($db->getAll()));
|
|
||||||
|
|
||||||
unlink($db->getDatabase()->getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Formatter\JsonFormatter;
|
|
||||||
|
|
||||||
class JsonFormatterTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var JsonFormatter
|
|
||||||
*/
|
|
||||||
private $formatter;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->formatter = new JsonFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider validData
|
|
||||||
*/
|
|
||||||
public function encodesValidData($originalValue, $encodedValue)
|
|
||||||
{
|
|
||||||
$this->assertSame($encodedValue, $this->formatter->encode($originalValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider validData
|
|
||||||
*/
|
|
||||||
public function decodesValidData($originalValue, $encodedValue)
|
|
||||||
{
|
|
||||||
$this->assertSame($originalValue, $this->formatter->decode($encodedValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function decodesAnObject()
|
|
||||||
{
|
|
||||||
$originalValue = (object)['foo' => 'bar'];
|
|
||||||
$formatter = new JsonFormatter(false);
|
|
||||||
$encodedValue = $formatter->encode($originalValue);
|
|
||||||
$this->assertEquals($originalValue, $formatter->decode($encodedValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function encodingInvalidDataThrowsException()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$this->formatter->encode(chr(241));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function decodingInvalidDataThrowsException()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
$this->formatter->decode('{');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[null, 'null'],
|
|
||||||
[1, '1'],
|
|
||||||
['foo', '"foo"'],
|
|
||||||
[["test", "new\nline"], '["test","new\nline"]'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Formatter\SerializeFormatter;
|
|
||||||
|
|
||||||
class SerializeFormatterTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var SerializeFormatter
|
|
||||||
*/
|
|
||||||
private $formatter;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->formatter = new SerializeFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider validData
|
|
||||||
*/
|
|
||||||
public function encodesValidData($originalValue, $encodedValue)
|
|
||||||
{
|
|
||||||
$this->assertSame($encodedValue, $this->formatter->encode($originalValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
* @dataProvider validData
|
|
||||||
*/
|
|
||||||
public function decodesValidData($originalValue, $encodedValue)
|
|
||||||
{
|
|
||||||
$this->assertSame($originalValue, $this->formatter->decode($encodedValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function validData(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
[null, 'N;'],
|
|
||||||
[1, 'i:1;'],
|
|
||||||
['foo', 's:3:"foo";'],
|
|
||||||
[["test", "new\nline"], 'a:2:{i:0;s:4:"test";i:1;s:9:"new\nline";}'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Line;
|
|
||||||
|
|
||||||
class LineTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var Line
|
|
||||||
*/
|
|
||||||
private $line;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
$this->line = new Line('foo=bar');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canGetLine()
|
|
||||||
{
|
|
||||||
$this->assertEquals('foo=bar', $this->line->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canGetKey()
|
|
||||||
{
|
|
||||||
$this->assertEquals('foo', $this->line->getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canGetData()
|
|
||||||
{
|
|
||||||
$this->assertEquals('bar', $this->line->getData());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function canGetKeyAndDataWithMultipleEquals()
|
|
||||||
{
|
|
||||||
$line = new Line('foo=bar=baz');
|
|
||||||
$this->assertEquals('foo', $line->getKey());
|
|
||||||
$this->assertEquals('bar=baz', $line->getData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Flintstone\Validation;
|
|
||||||
|
|
||||||
class ValidationTest extends \PHPUnit\Framework\TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function validateKey()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
Validation::validateKey('test!123');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @test
|
|
||||||
*/
|
|
||||||
public function validateDatabaseName()
|
|
||||||
{
|
|
||||||
$this->expectException(\Flintstone\Exception::class);
|
|
||||||
Validation::validateDatabaseName('test!123');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4
packages/php-ds/.gitattributes
vendored
4
packages/php-ds/.gitattributes
vendored
@ -1,4 +0,0 @@
|
|||||||
/.github export-ignore
|
|
||||||
/.gitattributes export-ignore
|
|
||||||
/.gitignore export-ignore
|
|
||||||
/phpunit.xml export-ignore
|
|
||||||
67
packages/php-ds/.github/workflows/ci.yaml
vendored
67
packages/php-ds/.github/workflows/ci.yaml
vendored
@ -1,67 +0,0 @@
|
|||||||
name: "CI"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "master"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
phpunit:
|
|
||||||
name: "PHPUnit"
|
|
||||||
runs-on: "ubuntu-20.04"
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
php-version:
|
|
||||||
- "7.4"
|
|
||||||
- "8.0"
|
|
||||||
- "8.1"
|
|
||||||
- "8.2"
|
|
||||||
- "8.3"
|
|
||||||
dependencies:
|
|
||||||
- "highest"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout"
|
|
||||||
uses: "actions/checkout@v2"
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: "Install PHP"
|
|
||||||
uses: "shivammathur/setup-php@v2"
|
|
||||||
with:
|
|
||||||
php-version: "${{ matrix.php-version }}"
|
|
||||||
coverage: "pcov"
|
|
||||||
ini-values: "zend.assertions=1"
|
|
||||||
|
|
||||||
- name: "Install dependencies with Composer"
|
|
||||||
uses: "ramsey/composer-install@v1"
|
|
||||||
with:
|
|
||||||
dependency-versions: "${{ matrix.dependencies }}"
|
|
||||||
|
|
||||||
- name: "Run PHPUnit"
|
|
||||||
run: "vendor/bin/phpunit"
|
|
||||||
|
|
||||||
upload_coverage:
|
|
||||||
name: "Upload coverage to Codecov"
|
|
||||||
runs-on: "ubuntu-20.04"
|
|
||||||
needs:
|
|
||||||
- "phpunit"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "Checkout"
|
|
||||||
uses: "actions/checkout@v2"
|
|
||||||
with:
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
- name: "Download coverage files"
|
|
||||||
uses: "actions/download-artifact@v2"
|
|
||||||
with:
|
|
||||||
path: "reports"
|
|
||||||
|
|
||||||
- name: "Upload to Codecov"
|
|
||||||
uses: "codecov/codecov-action@v1"
|
|
||||||
with:
|
|
||||||
directory: reports
|
|
||||||
4
packages/php-ds/.gitignore
vendored
4
packages/php-ds/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
.phpunit.result.cache
|
|
||||||
build
|
|
||||||
vendor
|
|
||||||
composer.lock
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
# Change Log
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
### Fixed
|
|
||||||
- `Collection` PHPDoc now correctly states that it extends `IteratorAggregate`, rather than just `Traversable`.
|
|
||||||
|
|
||||||
## [1.4.1] - 2022-03-09
|
|
||||||
|
|
||||||
## [1.4.0] - 2021-11-17
|
|
||||||
|
|
||||||
## [1.3.0] - 2020-10-13
|
|
||||||
### Changed
|
|
||||||
- Implement ArrayAccess consistently
|
|
||||||
### Fixed
|
|
||||||
- Return types were incorrectly nullable in some cases
|
|
||||||
- Deque capacity was inconsistent with the extension
|
|
||||||
|
|
||||||
## [1.2.0] - 2017-08-03
|
|
||||||
### Changed
|
|
||||||
- Minor capacity updates
|
|
||||||
|
|
||||||
## [1.1.1] - 2016-08-09
|
|
||||||
### Fixed
|
|
||||||
- `Stack` and `Queue` array access should throw `OutOfBoundsException`, not `Error`.
|
|
||||||
|
|
||||||
### Improved
|
|
||||||
- Added a lot of docblock comments that were missing.
|
|
||||||
|
|
||||||
## [1.1.0] - 2016-08-04
|
|
||||||
### Added
|
|
||||||
- `Pair::copy`
|
|
||||||
|
|
||||||
## [1.0.3] - 2016-08-01
|
|
||||||
### Added
|
|
||||||
- `Set::merge`
|
|
||||||
|
|
||||||
## [1.0.2] - 2016-07-31
|
|
||||||
### Added
|
|
||||||
- `Map::putAll`
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
Contributions are accepted via [pull requests](https://github.com/php-ds/ext/pulls). If you would like to report a bug, please create an [issue](https://github.com/php-ds/ext/issues) instead.
|
|
||||||
|
|
||||||
## Issues
|
|
||||||
|
|
||||||
- **How to reproduce** - Provide an easy way to reproduce the bug. This makes it easier for others to debug.
|
|
||||||
|
|
||||||
- **Platform details** - Specify your platform and your PHP version, eg. "PHP 7.0.2 on Ubuntu 14.04 64x".
|
|
||||||
|
|
||||||
## Pull Requests
|
|
||||||
|
|
||||||
- **Add tests** - Your patch won't be accepted if it doesn't have tests where appropriate.
|
|
||||||
|
|
||||||
- **Document any change in behaviour** - Make sure the README and any other relevant documentation updated.
|
|
||||||
|
|
||||||
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
|
|
||||||
|
|
||||||
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
|
|
||||||
|
|
||||||
- **Coding style** - Try to match the style of the rest of the source wherever possible. Your patch won't be accepted if the style is significantly different.
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
Copyright (c) 2016 Rudi Theunissen
|
|
||||||
|
|
||||||
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.
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
# Data Structures for PHP
|
|
||||||
|
|
||||||
[](https://github.com/php-ds/polyfill/actions?query=workflow%3A%22CI%22+branch%3Amaster)
|
|
||||||
[](https://packagist.org/packages/php-ds/php-ds)
|
|
||||||
|
|
||||||
This is a compatibility polyfill for the [extension](https://github.com/php-ds/extension). You should include this package as a dependency of your project
|
|
||||||
to ensure that your codebase would still be functional in an environment where the extension is not installed. The polyfill will not be loaded if the extension is installed and enabled.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer require php-ds/php-ds
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also *require* that the extension be installed using `ext-ds`.
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
```
|
|
||||||
composer install
|
|
||||||
composer test
|
|
||||||
```
|
|
||||||
|
|
||||||
Make sure that the *ds* extension is not enabled, as the polyfill will not be loaded if it is.
|
|
||||||
The test output will indicate whether the extension is active.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Please see [CONTRIBUTING](CONTRIBUTING.md) for more information.
|
|
||||||
|
|
||||||
### Credits
|
|
||||||
|
|
||||||
- [Rudi Theunissen](https://github.com/rtheunissen)
|
|
||||||
- [Joe Watkins](https://github.com/krakjoe)
|
|
||||||
|
|
||||||
### License
|
|
||||||
|
|
||||||
The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "php-ds/php-ds",
|
|
||||||
"license": "MIT",
|
|
||||||
"description": "Specialized data structures as alternatives to the PHP array",
|
|
||||||
"keywords": ["php", "ds", "data structures", "polyfill"],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Rudi Theunissen",
|
|
||||||
"email": "rudolf.theunissen@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
|
||||||
"php": ">=7.4",
|
|
||||||
"ext-json": "*"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"php-ds/tests": "^1.5"
|
|
||||||
},
|
|
||||||
"provide": {
|
|
||||||
"ext-ds": "1.5.0"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"ext-ds": "to improve performance and reduce memory usage"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": "phpunit"
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4" : {
|
|
||||||
"Ds\\": "src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
<phpunit
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
|
||||||
beStrictAboutChangesToGlobalState="true"
|
|
||||||
beStrictAboutOutputDuringTests="true"
|
|
||||||
beStrictAboutTodoAnnotatedTests="true"
|
|
||||||
bootstrap="tests/bootstrap.php"
|
|
||||||
colors="true"
|
|
||||||
>
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="DS Tests">
|
|
||||||
<directory>vendor/php-ds/tests/tests</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
<coverage>
|
|
||||||
<include>
|
|
||||||
<directory>src</directory>
|
|
||||||
</include>
|
|
||||||
<report>
|
|
||||||
<clover outputFile="build/logs/coverage.xml" />
|
|
||||||
<html outputDirectory="build/logs/coverage" />
|
|
||||||
<text outputFile="build/logs/coverage.txt" />
|
|
||||||
</report>
|
|
||||||
</coverage>
|
|
||||||
</phpunit>
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collection is the base interface which covers functionality common to all the
|
|
||||||
* data structures in this library. It guarantees that all structures are
|
|
||||||
* traversable, countable, and can be converted to json using json_encode().
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template-covariant TKey
|
|
||||||
* @template-covariant TValue
|
|
||||||
* @extends \IteratorAggregate<TKey, TValue>
|
|
||||||
*/
|
|
||||||
interface Collection extends \IteratorAggregate, \Countable, \JsonSerializable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Removes all values from the collection.
|
|
||||||
*/
|
|
||||||
public function clear();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the size of the collection.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function count(): int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a shallow copy of the collection.
|
|
||||||
*
|
|
||||||
* @return static a copy of the collection.
|
|
||||||
*
|
|
||||||
* @psalm-return static<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function copy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the collection is empty.
|
|
||||||
*
|
|
||||||
* This should be equivalent to a count of zero, but is not required.
|
|
||||||
* Implementations should define what empty means in their own context.
|
|
||||||
*/
|
|
||||||
public function isEmpty(): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array representation of the collection.
|
|
||||||
*
|
|
||||||
* The format of the returned array is implementation-dependent.
|
|
||||||
* Some implementations may throw an exception if an array representation
|
|
||||||
* could not be created.
|
|
||||||
*
|
|
||||||
* @return array<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function toArray(): array;
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Deque (pronounced "deck") is a sequence of values in a contiguous buffer
|
|
||||||
* that grows and shrinks automatically. The name is a common abbreviation of
|
|
||||||
* "double-ended queue".
|
|
||||||
*
|
|
||||||
* While a Deque is very similar to a Vector, it offers constant time operations
|
|
||||||
* at both ends of the buffer, ie. shift, unshift, push and pop are all O(1).
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @implements Sequence<TValue>
|
|
||||||
* @template-use Traits\GenericCollection<int, TValue>
|
|
||||||
* @template-use Traits\GenericSequence<TValue>
|
|
||||||
*/
|
|
||||||
final class Deque implements Sequence
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
use Traits\GenericSequence;
|
|
||||||
use Traits\SquaredCapacity;
|
|
||||||
|
|
||||||
public const MIN_CAPACITY = 8;
|
|
||||||
|
|
||||||
protected function shouldIncreaseCapacity(): bool
|
|
||||||
{
|
|
||||||
return count($this) >= $this->capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hashable is an interface which allows objects to be used as keys.
|
|
||||||
*
|
|
||||||
* It’s an alternative to spl_object_hash(), which determines an object’s hash
|
|
||||||
* based on its handle: this means that two objects that are considered equal
|
|
||||||
* by an implicit definition would not treated as equal because they are not
|
|
||||||
* the same instance.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*/
|
|
||||||
interface Hashable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Produces a scalar value to be used as the object's hash, which determines
|
|
||||||
* where it goes in the hash table. While this value does not have to be
|
|
||||||
* unique, objects which are equal must have the same hash value.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function hash();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if two objects should be considered equal. Both objects will
|
|
||||||
* be instances of the same class but may not be the same instance.
|
|
||||||
*
|
|
||||||
* @param mixed $obj An instance of the same class to compare to.
|
|
||||||
*/
|
|
||||||
public function equals($obj): bool;
|
|
||||||
}
|
|
||||||
@ -1,812 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
use OutOfBoundsException;
|
|
||||||
use OutOfRangeException;
|
|
||||||
use UnderflowException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Map is a sequential collection of key-value pairs, almost identical to an
|
|
||||||
* array used in a similar context. Keys can be any type, but must be unique.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TKey
|
|
||||||
* @template TValue
|
|
||||||
* @implements Collection<TKey, TValue>
|
|
||||||
* @implements \ArrayAccess<TKey, TValue>
|
|
||||||
* @template-use Traits\GenericCollection<TKey, TValue>
|
|
||||||
*/
|
|
||||||
final class Map implements Collection, \ArrayAccess
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
use Traits\SquaredCapacity;
|
|
||||||
|
|
||||||
public const MIN_CAPACITY = 8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array internal array to store pairs
|
|
||||||
*
|
|
||||||
* @psalm-var array<int, Pair>
|
|
||||||
*/
|
|
||||||
private $pairs = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*
|
|
||||||
* @param iterable<mixed, mixed> $values
|
|
||||||
*
|
|
||||||
* @psalm-param iterable<TKey, TValue> $values
|
|
||||||
*/
|
|
||||||
public function __construct(iterable $values = [])
|
|
||||||
{
|
|
||||||
if (func_num_args()) {
|
|
||||||
$this->putAll($values);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates all values by applying a callback function to each value.
|
|
||||||
*
|
|
||||||
* @param callable $callback Accepts two arguments: key and value, should
|
|
||||||
* return what the updated value will be.
|
|
||||||
*
|
|
||||||
* @psalm-param callable(TKey, TValue): TValue $callback
|
|
||||||
*/
|
|
||||||
public function apply(callable $callback)
|
|
||||||
{
|
|
||||||
foreach ($this->pairs as &$pair) {
|
|
||||||
$pair->value = $callback($pair->key, $pair->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->pairs = [];
|
|
||||||
$this->capacity = self::MIN_CAPACITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the first Pair from the Map
|
|
||||||
*
|
|
||||||
* @return Pair
|
|
||||||
*
|
|
||||||
* @throws UnderflowException
|
|
||||||
*
|
|
||||||
* @psalm-return Pair<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function first(): Pair
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->pairs[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the last Pair from the Map
|
|
||||||
*
|
|
||||||
* @return Pair
|
|
||||||
*
|
|
||||||
* @throws UnderflowException
|
|
||||||
*
|
|
||||||
* @psalm-return Pair<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function last(): Pair
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->pairs[count($this->pairs) - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the pair at a specified position in the Map
|
|
||||||
*
|
|
||||||
* @return Pair
|
|
||||||
*
|
|
||||||
* @throws OutOfRangeException
|
|
||||||
*
|
|
||||||
* @psalm-return Pair<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function skip(int $position): Pair
|
|
||||||
{
|
|
||||||
if ($position < 0 || $position >= count($this->pairs)) {
|
|
||||||
throw new OutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->pairs[$position]->copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of associating all keys of a given traversable object
|
|
||||||
* or array with their corresponding values, as well as those of this map.
|
|
||||||
*
|
|
||||||
* @param array|\Traversable $values
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @template TKey2
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param iterable<TKey2, TValue2> $values
|
|
||||||
* @psalm-return Map<TKey|TKey2, TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function merge($values): Map
|
|
||||||
{
|
|
||||||
$merged = new self($this);
|
|
||||||
$merged->putAll($values);
|
|
||||||
return $merged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new map containing the pairs of the current instance whose keys
|
|
||||||
* are also present in the given map. In other words, returns a copy of the
|
|
||||||
* current map with all keys removed that are not also in the other map.
|
|
||||||
*
|
|
||||||
* @param Map $map The other map.
|
|
||||||
*
|
|
||||||
* @return Map A new map containing the pairs of the current instance
|
|
||||||
* whose keys are also present in the given map. In other
|
|
||||||
* words, returns a copy of the current map with all keys
|
|
||||||
* removed that are not also in the other map.
|
|
||||||
*
|
|
||||||
* @template TKey2
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Map<TKey2, TValue2> $map
|
|
||||||
* @psalm-return Map<TKey&TKey2, TValue>
|
|
||||||
*/
|
|
||||||
public function intersect(Map $map): Map
|
|
||||||
{
|
|
||||||
return $this->filter(function ($key) use ($map) {
|
|
||||||
return $map->hasKey($key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of removing all keys from the current instance that
|
|
||||||
* are present in a given map.
|
|
||||||
*
|
|
||||||
* @param Map $map The map containing the keys to exclude.
|
|
||||||
*
|
|
||||||
* @return Map The result of removing all keys from the current instance
|
|
||||||
* that are present in a given map.
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Map<TKey, TValue2> $map
|
|
||||||
* @psalm-return Map<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function diff(Map $map): Map
|
|
||||||
{
|
|
||||||
return $this->filter(function ($key) use ($map) {
|
|
||||||
return !$map->hasKey($key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether two keys are equal.
|
|
||||||
*
|
|
||||||
* @param mixed $a
|
|
||||||
* @param mixed $b
|
|
||||||
*
|
|
||||||
* @psalm-param TKey $a
|
|
||||||
* @psalm-param TKey $b
|
|
||||||
*/
|
|
||||||
private function keysAreEqual($a, $b): bool
|
|
||||||
{
|
|
||||||
if (is_object($a) && $a instanceof Hashable) {
|
|
||||||
return get_class($a) === get_class($b) && $a->equals($b);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $a === $b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to look up a key in the table.
|
|
||||||
*
|
|
||||||
* @param $key
|
|
||||||
*
|
|
||||||
* @return Pair|null
|
|
||||||
*
|
|
||||||
* @psalm-return Pair<TKey, TValue>|null
|
|
||||||
*/
|
|
||||||
private function lookupKey($key)
|
|
||||||
{
|
|
||||||
foreach ($this->pairs as $pair) {
|
|
||||||
if ($this->keysAreEqual($pair->key, $key)) {
|
|
||||||
return $pair;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to look up a value in the table.
|
|
||||||
*
|
|
||||||
* @param $value
|
|
||||||
*
|
|
||||||
* @return Pair|null
|
|
||||||
*
|
|
||||||
* @psalm-return Pair<TKey, TValue>|null
|
|
||||||
*/
|
|
||||||
private function lookupValue($value)
|
|
||||||
{
|
|
||||||
foreach ($this->pairs as $pair) {
|
|
||||||
if ($pair->value === $value) {
|
|
||||||
return $pair;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether an association a given key exists.
|
|
||||||
*
|
|
||||||
* @param mixed $key
|
|
||||||
*
|
|
||||||
* @psalm-param TKey $key
|
|
||||||
*/
|
|
||||||
public function hasKey($key): bool
|
|
||||||
{
|
|
||||||
return $this->lookupKey($key) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether an association for a given value exists.
|
|
||||||
*
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function hasValue($value): bool
|
|
||||||
{
|
|
||||||
return $this->lookupValue($value) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return count($this->pairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new map containing only the values for which a predicate
|
|
||||||
* returns true. A boolean test will be used if a predicate is not provided.
|
|
||||||
*
|
|
||||||
* @param callable|null $callback Accepts a key and a value, and returns:
|
|
||||||
* true : include the value,
|
|
||||||
* false: skip the value.
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TKey, TValue): bool)|null $callback
|
|
||||||
* @psalm-return Map<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function filter(callable|null $callback = null): Map
|
|
||||||
{
|
|
||||||
$filtered = new self();
|
|
||||||
|
|
||||||
foreach ($this as $key => $value) {
|
|
||||||
if ($callback ? $callback($key, $value) : $value) {
|
|
||||||
$filtered->put($key, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value associated with a key, or an optional default if the
|
|
||||||
* key is not associated with a value.
|
|
||||||
*
|
|
||||||
* @param mixed $key
|
|
||||||
* @param mixed $default
|
|
||||||
*
|
|
||||||
* @return mixed The associated value or fallback default if provided.
|
|
||||||
*
|
|
||||||
* @throws OutOfBoundsException if no default was provided and the key is
|
|
||||||
* not associated with a value.
|
|
||||||
*
|
|
||||||
* @template TDefault
|
|
||||||
* @psalm-param TKey $key
|
|
||||||
* @psalm-param TDefault $default
|
|
||||||
* @psalm-return TValue|TDefault
|
|
||||||
*/
|
|
||||||
public function get($key, $default = null)
|
|
||||||
{
|
|
||||||
if (($pair = $this->lookupKey($key))) {
|
|
||||||
return $pair->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a default was provided.
|
|
||||||
if (func_num_args() === 1) {
|
|
||||||
throw new OutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of all the keys in the map.
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @psalm-return Set<TKey>
|
|
||||||
*/
|
|
||||||
public function keys(): Set
|
|
||||||
{
|
|
||||||
$key = function ($pair) {
|
|
||||||
return $pair->key;
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Set(array_map($key, $this->pairs));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new map using the results of applying a callback to each value.
|
|
||||||
*
|
|
||||||
* The keys will be equal in both maps.
|
|
||||||
*
|
|
||||||
* @param callable $callback Accepts two arguments: key and value, should
|
|
||||||
* return what the updated value will be.
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @template TNewValue
|
|
||||||
* @psalm-param callable(TKey, TValue): TNewValue $callback
|
|
||||||
* @psalm-return Map<TKey, TNewValue>
|
|
||||||
*/
|
|
||||||
public function map(callable $callback): Map
|
|
||||||
{
|
|
||||||
$mapped = new self();
|
|
||||||
foreach ($this->pairs as $pair) {
|
|
||||||
$mapped->put($pair->key, $callback($pair->key, $pair->value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sequence of pairs representing all associations.
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @psalm-return Sequence<Pair<TKey, TValue>>
|
|
||||||
*/
|
|
||||||
public function pairs(): Sequence
|
|
||||||
{
|
|
||||||
$copy = function ($pair) {
|
|
||||||
return $pair->copy();
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Vector(array_map($copy, $this->pairs));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Associates a key with a value, replacing a previous association if there
|
|
||||||
* was one.
|
|
||||||
*
|
|
||||||
* @param mixed $key
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @psalm-param TKey $key
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function put($key, $value)
|
|
||||||
{
|
|
||||||
$pair = $this->lookupKey($key);
|
|
||||||
|
|
||||||
if ($pair) {
|
|
||||||
$pair->value = $value;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->checkCapacity();
|
|
||||||
$this->pairs[] = new Pair($key, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates associations for all keys and corresponding values of either an
|
|
||||||
* array or iterable object.
|
|
||||||
*
|
|
||||||
* @param iterable<mixed, mixed> $values
|
|
||||||
*
|
|
||||||
* @psalm-param iterable<TKey, TValue> $values
|
|
||||||
*/
|
|
||||||
public function putAll(iterable $values)
|
|
||||||
{
|
|
||||||
foreach ($values as $key => $value) {
|
|
||||||
$this->put($key, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iteratively reduces the map to a single value using a callback.
|
|
||||||
*
|
|
||||||
* @param callable $callback Accepts the carry, key, and value, and
|
|
||||||
* returns an updated carry value.
|
|
||||||
*
|
|
||||||
* @param mixed|null $initial Optional initial carry value.
|
|
||||||
*
|
|
||||||
* @return mixed The carry value of the final iteration, or the initial
|
|
||||||
* value if the map was empty.
|
|
||||||
*
|
|
||||||
* @template TCarry
|
|
||||||
* @psalm-param callable(TCarry, TKey, TValue): TCarry $callback
|
|
||||||
* @psalm-param TCarry $initial
|
|
||||||
* @psalm-return TCarry
|
|
||||||
*/
|
|
||||||
public function reduce(callable $callback, $initial = null)
|
|
||||||
{
|
|
||||||
$carry = $initial;
|
|
||||||
|
|
||||||
foreach ($this->pairs as $pair) {
|
|
||||||
$carry = $callback($carry, $pair->key, $pair->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $carry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Completely removes a pair from the internal array by position. It is
|
|
||||||
* important to remove it from the array and not just use 'unset'.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
private function delete(int $position)
|
|
||||||
{
|
|
||||||
$pair = $this->pairs[$position];
|
|
||||||
$value = $pair->value;
|
|
||||||
|
|
||||||
array_splice($this->pairs, $position, 1, null);
|
|
||||||
$this->checkCapacity();
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a key's association from the map and returns the associated value
|
|
||||||
* or a provided default if provided.
|
|
||||||
*
|
|
||||||
* @param mixed $key
|
|
||||||
* @param mixed $default
|
|
||||||
*
|
|
||||||
* @return mixed The associated value or fallback default if provided.
|
|
||||||
*
|
|
||||||
* @throws \OutOfBoundsException if no default was provided and the key is
|
|
||||||
* not associated with a value.
|
|
||||||
*
|
|
||||||
* @template TDefault
|
|
||||||
* @psalm-param TKey $key
|
|
||||||
* @psalm-param TDefault $default
|
|
||||||
* @psalm-return TValue|TDefault
|
|
||||||
*/
|
|
||||||
public function remove($key, $default = null)
|
|
||||||
{
|
|
||||||
foreach ($this->pairs as $position => $pair) {
|
|
||||||
if ($this->keysAreEqual($pair->key, $key)) {
|
|
||||||
return $this->delete($position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a default was provided
|
|
||||||
if (func_num_args() === 1) {
|
|
||||||
throw new \OutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the map in-place
|
|
||||||
*/
|
|
||||||
public function reverse()
|
|
||||||
{
|
|
||||||
$this->pairs = array_reverse($this->pairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reversed copy of the map.
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @psalm-return Map<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function reversed(): Map
|
|
||||||
{
|
|
||||||
$reversed = new self();
|
|
||||||
$reversed->pairs = array_reverse($this->pairs);
|
|
||||||
|
|
||||||
return $reversed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sub-sequence of a given length starting at a specified offset.
|
|
||||||
*
|
|
||||||
* @param int $offset If the offset is non-negative, the map will
|
|
||||||
* start at that offset in the map. If offset is
|
|
||||||
* negative, the map will start that far from the
|
|
||||||
* end.
|
|
||||||
*
|
|
||||||
* @param int|null $length If a length is given and is positive, the
|
|
||||||
* resulting set will have up to that many pairs in
|
|
||||||
* it. If the requested length results in an
|
|
||||||
* overflow, only pairs up to the end of the map
|
|
||||||
* will be included.
|
|
||||||
*
|
|
||||||
* If a length is given and is negative, the map
|
|
||||||
* will stop that many pairs from the end.
|
|
||||||
*
|
|
||||||
* If a length is not provided, the resulting map
|
|
||||||
* will contains all pairs between the offset and
|
|
||||||
* the end of the map.
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @psalm-return Map<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function slice(int $offset, int|null $length = null): Map
|
|
||||||
{
|
|
||||||
$map = new self();
|
|
||||||
|
|
||||||
if (func_num_args() === 1) {
|
|
||||||
$slice = array_slice($this->pairs, $offset);
|
|
||||||
} else {
|
|
||||||
$slice = array_slice($this->pairs, $offset, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($slice as $pair) {
|
|
||||||
$map->put($pair->key, $pair->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the map in-place, based on an optional callable comparator.
|
|
||||||
*
|
|
||||||
* The map will be sorted by value.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two values to be compared.
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
*/
|
|
||||||
public function sort(callable|null $comparator = null)
|
|
||||||
{
|
|
||||||
if ($comparator) {
|
|
||||||
usort($this->pairs, function ($a, $b) use ($comparator) {
|
|
||||||
return $comparator($a->value, $b->value);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
usort($this->pairs, function ($a, $b) {
|
|
||||||
return $a->value <=> $b->value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sorted copy of the map, based on an optional callable
|
|
||||||
* comparator. The map will be sorted by value.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two values to be compared.
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
* @psalm-return Map<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function sorted(callable|null $comparator = null): Map
|
|
||||||
{
|
|
||||||
$copy = $this->copy();
|
|
||||||
$copy->sort($comparator);
|
|
||||||
return $copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the map in-place, based on an optional callable comparator.
|
|
||||||
*
|
|
||||||
* The map will be sorted by key.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two keys to be compared.
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TKey, TKey): int)|null $comparator
|
|
||||||
*/
|
|
||||||
public function ksort(callable|null $comparator = null)
|
|
||||||
{
|
|
||||||
if ($comparator) {
|
|
||||||
usort($this->pairs, function ($a, $b) use ($comparator) {
|
|
||||||
return $comparator($a->key, $b->key);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
usort($this->pairs, function ($a, $b) {
|
|
||||||
return $a->key <=> $b->key;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sorted copy of the map, based on an optional callable
|
|
||||||
* comparator. The map will be sorted by key.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two keys to be compared.
|
|
||||||
*
|
|
||||||
* @return Map
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TKey, TKey): int)|null $comparator
|
|
||||||
* @psalm-return Map<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function ksorted(callable|null $comparator = null): Map
|
|
||||||
{
|
|
||||||
$copy = $this->copy();
|
|
||||||
$copy->ksort($comparator);
|
|
||||||
return $copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sum of all values in the map.
|
|
||||||
*
|
|
||||||
* @return int|float The sum of all the values in the map.
|
|
||||||
*/
|
|
||||||
public function sum()
|
|
||||||
{
|
|
||||||
return $this->values()->sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
$array = [];
|
|
||||||
|
|
||||||
foreach ($this->pairs as $pair) {
|
|
||||||
$array[$pair->key] = $pair->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sequence of all the associated values in the Map.
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function values(): Sequence
|
|
||||||
{
|
|
||||||
$value = function ($pair) {
|
|
||||||
return $pair->value;
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Vector(array_map($value, $this->pairs));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new map that contains the pairs of the current instance as well
|
|
||||||
* as the pairs of another map.
|
|
||||||
*
|
|
||||||
* @param Map $map The other map, to combine with the current instance.
|
|
||||||
*
|
|
||||||
* @return Map A new map containing all the pairs of the current
|
|
||||||
* instance as well as another map.
|
|
||||||
*
|
|
||||||
* @template TKey2
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Map<TKey2, TValue2> $map
|
|
||||||
* @psalm-return Map<TKey|TKey2, TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function union(Map $map): Map
|
|
||||||
{
|
|
||||||
return $this->merge($map);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new map using keys of either the current instance or of another
|
|
||||||
* map, but not of both.
|
|
||||||
*
|
|
||||||
* @param Map $map
|
|
||||||
*
|
|
||||||
* @return Map A new map containing keys in the current instance as well
|
|
||||||
* as another map, but not in both.
|
|
||||||
*
|
|
||||||
* @template TKey2
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Map<TKey2, TValue2> $map
|
|
||||||
* @psalm-return Map<TKey|TKey2, TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function xor(Map $map): Map
|
|
||||||
{
|
|
||||||
return $this->merge($map)->filter(function ($key) use ($map) {
|
|
||||||
return $this->hasKey($key) ^ $map->hasKey($key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function getIterator()
|
|
||||||
{
|
|
||||||
foreach ($this->pairs as $pair) {
|
|
||||||
yield $pair->key => $pair->value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a representation to be used for var_dump and print_r.
|
|
||||||
*
|
|
||||||
* @psalm-return array<Pair<TKey, TValue>>
|
|
||||||
*/
|
|
||||||
public function __debugInfo()
|
|
||||||
{
|
|
||||||
return $this->pairs()->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
$this->put($offset, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws OutOfBoundsException
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function &offsetGet($offset)
|
|
||||||
{
|
|
||||||
$pair = $this->lookupKey($offset);
|
|
||||||
|
|
||||||
if ($pair) {
|
|
||||||
return $pair->value;
|
|
||||||
}
|
|
||||||
throw new OutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
$this->remove($offset, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
return $this->get($offset, null) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a representation that can be natively converted to JSON, which is
|
|
||||||
* called when invoking json_encode.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @see \JsonSerializable
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function jsonSerialize()
|
|
||||||
{
|
|
||||||
return (object)$this->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
use OutOfBoundsException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pair which represents a key and an associated value.
|
|
||||||
*
|
|
||||||
* @property mixed $key
|
|
||||||
* @property mixed $value
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template-covariant TKey
|
|
||||||
* @template-covariant TValue
|
|
||||||
*/
|
|
||||||
final class Pair implements \JsonSerializable
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var mixed The pair's key
|
|
||||||
*
|
|
||||||
* @psalm-param TKey $key
|
|
||||||
*/
|
|
||||||
public $key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var mixed The pair's value
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public $value;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*
|
|
||||||
* @param mixed $key
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @psalm-param TKey $key
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function __construct($key = null, $value = null)
|
|
||||||
{
|
|
||||||
$this->key = $key;
|
|
||||||
$this->value = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param mixed $name
|
|
||||||
*
|
|
||||||
* @return mixed|null
|
|
||||||
*/
|
|
||||||
public function __isset($name)
|
|
||||||
{
|
|
||||||
if ($name === 'key' || $name === 'value') {
|
|
||||||
return $this->$name !== null;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This allows unset($pair->key) to not completely remove the property,
|
|
||||||
* but be set to null instead.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __unset(string $name)
|
|
||||||
{
|
|
||||||
if ($name === 'key' || $name === 'value') {
|
|
||||||
$this->$name = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new OutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $name
|
|
||||||
*
|
|
||||||
* @return mixed|null
|
|
||||||
*/
|
|
||||||
public function &__get($name)
|
|
||||||
{
|
|
||||||
if ($name === 'key' || $name === 'value') {
|
|
||||||
return $this->$name;
|
|
||||||
}
|
|
||||||
throw new OutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $name
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @return mixed|null
|
|
||||||
*/
|
|
||||||
public function __set($name, $value)
|
|
||||||
{
|
|
||||||
if ($name === 'key' || $name === 'value') {
|
|
||||||
$this->$name = $value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new OutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a copy of the Pair
|
|
||||||
*
|
|
||||||
* @psalm-return self<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function copy(): self
|
|
||||||
{
|
|
||||||
return new self($this->key, $this->value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a representation to be used for var_dump and print_r.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*
|
|
||||||
* @psalm-return array{key: TKey, value: TValue}
|
|
||||||
*/
|
|
||||||
public function __debugInfo()
|
|
||||||
{
|
|
||||||
return $this->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*
|
|
||||||
* @psalm-return array{key: TKey, value: TValue}
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'key' => $this->key,
|
|
||||||
'value' => $this->value,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*
|
|
||||||
* @psalm-return array{key: TKey, value: TValue}
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function jsonSerialize()
|
|
||||||
{
|
|
||||||
return $this->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the pair.
|
|
||||||
*/
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return 'object(' . get_class($this) . ')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,340 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
use UnderflowException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A PriorityQueue is very similar to a Queue. Values are pushed into the queue
|
|
||||||
* with an assigned priority, and the value with the highest priority will
|
|
||||||
* always be at the front of the queue.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @implements Collection<int, TValue>
|
|
||||||
*/
|
|
||||||
final class PriorityQueue implements Collection
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
use Traits\SquaredCapacity;
|
|
||||||
|
|
||||||
public const MIN_CAPACITY = 8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<int, PriorityNode<TValue>>
|
|
||||||
*/
|
|
||||||
private $heap = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
private $stamp = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->heap = [];
|
|
||||||
$this->stamp = 0;
|
|
||||||
$this->capacity = self::MIN_CAPACITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function copy(): self
|
|
||||||
{
|
|
||||||
$copy = new PriorityQueue();
|
|
||||||
|
|
||||||
$copy->heap = $this->heap;
|
|
||||||
$copy->stamp = $this->stamp;
|
|
||||||
$copy->capacity = $this->capacity;
|
|
||||||
|
|
||||||
return $copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return count($this->heap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value with the highest priority in the priority queue.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throw UnderflowException
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function peek()
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->heap[0]->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of a node's left leaf.
|
|
||||||
*
|
|
||||||
* @param int $index The index of the node.
|
|
||||||
*
|
|
||||||
* @return int The index of the left leaf.
|
|
||||||
*/
|
|
||||||
private function left(int $index): int
|
|
||||||
{
|
|
||||||
return ($index * 2) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of a node's right leaf.
|
|
||||||
*
|
|
||||||
* @param int $index The index of the node.
|
|
||||||
*
|
|
||||||
* @return int The index of the right leaf.
|
|
||||||
*/
|
|
||||||
private function right(int $index): int
|
|
||||||
{
|
|
||||||
return ($index * 2) + 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of a node's parent node.
|
|
||||||
*
|
|
||||||
* @param int $index The index of the node.
|
|
||||||
*
|
|
||||||
* @return int The index of the parent.
|
|
||||||
*/
|
|
||||||
private function parent(int $index): int
|
|
||||||
{
|
|
||||||
return (int) (($index - 1) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two indices of the heap.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
private function compare(int $a, int $b)
|
|
||||||
{
|
|
||||||
$x = $this->heap[$a];
|
|
||||||
$y = $this->heap[$b];
|
|
||||||
|
|
||||||
// Compare priority, using insertion stamp as fallback.
|
|
||||||
return ($x->priority <=> $y->priority) ?: ($y->stamp <=> $x->stamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Swaps the nodes at two indices of the heap.
|
|
||||||
*/
|
|
||||||
private function swap(int $a, int $b)
|
|
||||||
{
|
|
||||||
$temp = $this->heap[$a];
|
|
||||||
$this->heap[$a] = $this->heap[$b];
|
|
||||||
$this->heap[$b] = $temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of a node's largest leaf node.
|
|
||||||
*
|
|
||||||
* @param int $parent the parent node.
|
|
||||||
*
|
|
||||||
* @return int the index of the node's largest leaf node.
|
|
||||||
*/
|
|
||||||
private function getLargestLeaf(int $parent)
|
|
||||||
{
|
|
||||||
$left = $this->left($parent);
|
|
||||||
$right = $this->right($parent);
|
|
||||||
|
|
||||||
if ($right < count($this->heap) && $this->compare($left, $right) < 0) {
|
|
||||||
return $right;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the process of sifting down a given node index to ensure that
|
|
||||||
* the heap's properties are preserved.
|
|
||||||
*/
|
|
||||||
private function siftDown(int $node)
|
|
||||||
{
|
|
||||||
$last = floor(count($this->heap) / 2);
|
|
||||||
|
|
||||||
for ($parent = $node; $parent < $last; $parent = $leaf) {
|
|
||||||
|
|
||||||
// Determine the largest leaf to potentially swap with the parent.
|
|
||||||
$leaf = $this->getLargestLeaf($parent);
|
|
||||||
|
|
||||||
// Done if the parent is not greater than its largest leaf
|
|
||||||
if ($this->compare($parent, $leaf) > 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->swap($parent, $leaf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the root node and sifts it down the heap.
|
|
||||||
*
|
|
||||||
* @param PriorityNode $node
|
|
||||||
*/
|
|
||||||
private function setRoot(PriorityNode $node)
|
|
||||||
{
|
|
||||||
$this->heap[0] = $node;
|
|
||||||
$this->siftDown(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the root node of the heap.
|
|
||||||
*
|
|
||||||
* @return PriorityNode
|
|
||||||
*/
|
|
||||||
private function getRoot(): PriorityNode
|
|
||||||
{
|
|
||||||
return $this->heap[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns and removes the value with the highest priority in the queue.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function pop()
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last leaf of the heap to become the new root.
|
|
||||||
$leaf = array_pop($this->heap);
|
|
||||||
|
|
||||||
if (empty($this->heap)) {
|
|
||||||
return $leaf->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache the current root value to return before replacing with next.
|
|
||||||
$value = $this->getRoot()->value;
|
|
||||||
|
|
||||||
// Replace the root, then sift down.
|
|
||||||
$this->setRoot($leaf);
|
|
||||||
$this->checkCapacity();
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sifts a node up the heap until it's in the right position.
|
|
||||||
*/
|
|
||||||
private function siftUp(int $leaf)
|
|
||||||
{
|
|
||||||
for (; $leaf > 0; $leaf = $parent) {
|
|
||||||
$parent = $this->parent($leaf);
|
|
||||||
|
|
||||||
// Done when parent priority is greater.
|
|
||||||
if ($this->compare($leaf, $parent) < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->swap($parent, $leaf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a value into the queue, with a specified priority.
|
|
||||||
*
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function push($value, int $priority)
|
|
||||||
{
|
|
||||||
$this->checkCapacity();
|
|
||||||
|
|
||||||
// Add new leaf, then sift up to maintain heap,
|
|
||||||
$this->heap[] = new PriorityNode($value, $priority, $this->stamp++);
|
|
||||||
$this->siftUp(count($this->heap) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
$heap = $this->heap;
|
|
||||||
$array = [];
|
|
||||||
|
|
||||||
while ( ! $this->isEmpty()) {
|
|
||||||
$array[] = $this->pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->heap = $heap;
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function getIterator()
|
|
||||||
{
|
|
||||||
while ( ! $this->isEmpty()) {
|
|
||||||
yield $this->pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
*/
|
|
||||||
final class PriorityNode
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var mixed
|
|
||||||
*
|
|
||||||
* @psalm-var TValue
|
|
||||||
*/
|
|
||||||
public $value;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $priority;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
public $stamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed $value
|
|
||||||
* @param int $priority
|
|
||||||
* @param int $stamp
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function __construct($value, int $priority, int $stamp)
|
|
||||||
{
|
|
||||||
$this->value = $value;
|
|
||||||
$this->priority = $priority;
|
|
||||||
$this->stamp = $stamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,197 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
use Error;
|
|
||||||
use OutOfBoundsException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A “first in, first out” or “FIFO” collection that only allows access to the
|
|
||||||
* value at the front of the queue and iterates in that order, destructively.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @implements Collection<int, TValue>
|
|
||||||
* @implements \ArrayAccess<int, TValue>
|
|
||||||
* @template-use Traits\GenericCollection<int, TValue>
|
|
||||||
*/
|
|
||||||
final class Queue implements Collection, \ArrayAccess
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Deque internal deque to store values.
|
|
||||||
*
|
|
||||||
* @psalm-var Deque<TValue>
|
|
||||||
*/
|
|
||||||
private $deque;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance using the values of an array or Traversable object.
|
|
||||||
*
|
|
||||||
* @param iterable<mixed> $values
|
|
||||||
*
|
|
||||||
* @psalm-param iterable<TValue> $values
|
|
||||||
*/
|
|
||||||
public function __construct(iterable $values = [])
|
|
||||||
{
|
|
||||||
$this->deque = new Deque($values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that enough memory is allocated for a specified capacity. This
|
|
||||||
* potentially reduces the number of reallocations as the size increases.
|
|
||||||
*
|
|
||||||
* @param int $capacity The number of values for which capacity should be
|
|
||||||
* allocated. Capacity will stay the same if this value
|
|
||||||
* is less than or equal to the current capacity.
|
|
||||||
*/
|
|
||||||
public function allocate(int $capacity)
|
|
||||||
{
|
|
||||||
$this->deque->allocate($capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current capacity of the queue.
|
|
||||||
*/
|
|
||||||
public function capacity(): int
|
|
||||||
{
|
|
||||||
return $this->deque->capacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->deque->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function copy(): self
|
|
||||||
{
|
|
||||||
return new self($this->deque);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return count($this->deque);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value at the front of the queue without removing it.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function peek()
|
|
||||||
{
|
|
||||||
return $this->deque->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns and removes the value at the front of the Queue.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function pop()
|
|
||||||
{
|
|
||||||
return $this->deque->shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes zero or more values into the back of the queue.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function push(...$values)
|
|
||||||
{
|
|
||||||
$this->deque->push(...$values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return $this->deque->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get iterator
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function getIterator()
|
|
||||||
{
|
|
||||||
while ( ! $this->isEmpty()) {
|
|
||||||
yield $this->pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws OutOfBoundsException
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
if ($offset === null) {
|
|
||||||
$this->push($value);
|
|
||||||
} else {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the internal sequence will be cloned too.
|
|
||||||
*/
|
|
||||||
public function __clone()
|
|
||||||
{
|
|
||||||
$this->deque = clone $this->deque;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,331 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes the behaviour of values arranged in a single, linear dimension.
|
|
||||||
* Some languages refer to this as a "List". It’s similar to an array that uses
|
|
||||||
* incremental integer keys, with the exception of a few characteristics:
|
|
||||||
*
|
|
||||||
* - Values will always be indexed as [0, 1, 2, …, size - 1].
|
|
||||||
* - Only allowed to access values by index in the range [0, size - 1].
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @extends Collection<int, TValue>
|
|
||||||
* @extends \ArrayAccess<int, TValue>
|
|
||||||
*/
|
|
||||||
interface Sequence extends Collection, \ArrayAccess
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Ensures that enough memory is allocated for a required capacity.
|
|
||||||
*
|
|
||||||
* @param int $capacity The number of values for which capacity should be
|
|
||||||
* allocated. Capacity will stay the same if this value
|
|
||||||
* is less than or equal to the current capacity.
|
|
||||||
*/
|
|
||||||
public function allocate(int $capacity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates every value in the sequence by applying a callback, using the
|
|
||||||
* return value as the new value.
|
|
||||||
*
|
|
||||||
* @param callable $callback Accepts the value, returns the new value.
|
|
||||||
*
|
|
||||||
* @psalm-param callable(TValue): TValue $callback
|
|
||||||
*/
|
|
||||||
public function apply(callable $callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current capacity of the sequence.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function capacity(): int;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the sequence contains all of zero or more values.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @return bool true if at least one value was provided and the sequence
|
|
||||||
* contains all given values, false otherwise.
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function contains(...$values): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new sequence containing only the values for which a callback
|
|
||||||
* returns true. A boolean test will be used if a callback is not provided.
|
|
||||||
*
|
|
||||||
* @param callable|null $callback Accepts a value, returns a boolean result:
|
|
||||||
* true : include the value,
|
|
||||||
* false: skip the value.
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue): bool)|null $callback
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function filter(callable|null $callback = null): Sequence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of a given value, or null if it could not be found.
|
|
||||||
*
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @return int|null
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function find($value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first value in the sequence.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \UnderflowException if the sequence is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function first();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value at a given index (position) in the sequence.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, size-1]
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function get(int $index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts zero or more values at a given index.
|
|
||||||
*
|
|
||||||
* Each value after the index will be moved one position to the right.
|
|
||||||
* Values may be inserted at an index equal to the size of the sequence.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, n]
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function insert(int $index, ...$values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins all values of the sequence into a string, adding an optional 'glue'
|
|
||||||
* between them. Returns an empty string if the sequence is empty.
|
|
||||||
*/
|
|
||||||
public function join(string $glue = null): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the last value in the sequence.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \UnderflowException if the sequence is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function last();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new sequence using the results of applying a callback to each
|
|
||||||
* value.
|
|
||||||
*
|
|
||||||
* @param callable $callback
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @template TNewValue
|
|
||||||
* @psalm-param callable(TValue): TNewValue $callback
|
|
||||||
* @psalm-return Sequence<TNewValue>
|
|
||||||
*/
|
|
||||||
public function map(callable $callback): Sequence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of adding all given values to the sequence.
|
|
||||||
*
|
|
||||||
* @param array|\Traversable $values
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param iterable<TValue2> $values
|
|
||||||
* @psalm-return Sequence<TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function merge($values): Sequence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the last value in the sequence, and returns it.
|
|
||||||
*
|
|
||||||
* @return mixed what was the last value in the sequence.
|
|
||||||
*
|
|
||||||
* @throws \UnderflowException if the sequence is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function pop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds zero or more values to the end of the sequence.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function push(...$values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iteratively reduces the sequence to a single value using a callback.
|
|
||||||
*
|
|
||||||
* @param callable $callback Accepts the carry and current value, and
|
|
||||||
* returns an updated carry value.
|
|
||||||
*
|
|
||||||
* @param mixed|null $initial Optional initial carry value.
|
|
||||||
*
|
|
||||||
* @return mixed The carry value of the final iteration, or the initial
|
|
||||||
* value if the sequence was empty.
|
|
||||||
*
|
|
||||||
* @template TCarry
|
|
||||||
* @psalm-param callable(TCarry, TValue): TCarry $callback
|
|
||||||
* @psalm-param TCarry $initial
|
|
||||||
* @psalm-return TCarry
|
|
||||||
*/
|
|
||||||
public function reduce(callable $callback, $initial = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes and returns the value at a given index in the sequence.
|
|
||||||
*
|
|
||||||
* @param int $index this index to remove.
|
|
||||||
*
|
|
||||||
* @return mixed the removed value.
|
|
||||||
*
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, size-1]
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function remove(int $index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the sequence in-place.
|
|
||||||
*/
|
|
||||||
public function reverse();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reversed copy of the sequence.
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function reversed();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates the sequence by a given number of rotations, which is equivalent
|
|
||||||
* to successive calls to 'shift' and 'push' if the number of rotations is
|
|
||||||
* positive, or 'pop' and 'unshift' if negative.
|
|
||||||
*
|
|
||||||
* @param int $rotations The number of rotations (can be negative).
|
|
||||||
*/
|
|
||||||
public function rotate(int $rotations);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the value at a given index in the sequence with a new value.
|
|
||||||
*
|
|
||||||
* @param mixed $value
|
|
||||||
*
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, size-1]
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function set(int $index, $value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes and returns the first value in the sequence.
|
|
||||||
*
|
|
||||||
* @return mixed what was the first value in the sequence.
|
|
||||||
*
|
|
||||||
* @throws \UnderflowException if the sequence was empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function shift();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sub-sequence of a given length starting at a specified index.
|
|
||||||
*
|
|
||||||
* @param int $index If the index is positive, the sequence will start
|
|
||||||
* at that index in the sequence. If index is negative,
|
|
||||||
* the sequence will start that far from the end.
|
|
||||||
*
|
|
||||||
* @param int $length If a length is given and is positive, the resulting
|
|
||||||
* sequence will have up to that many values in it.
|
|
||||||
* If the length results in an overflow, only values
|
|
||||||
* up to the end of the sequence will be included.
|
|
||||||
*
|
|
||||||
* If a length is given and is negative, the sequence
|
|
||||||
* will stop that many values from the end.
|
|
||||||
*
|
|
||||||
* If a length is not provided, the resulting sequence
|
|
||||||
* will contain all values between the index and the
|
|
||||||
* end of the sequence.
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function slice(int $index, int $length = null): Sequence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the sequence in-place, based on an optional callable comparator.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two values to be compared.
|
|
||||||
* Should return the result of a <=> b.
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
*/
|
|
||||||
public function sort(callable $comparator = null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sorted copy of the sequence, based on an optional callable
|
|
||||||
* comparator. Natural ordering will be used if a comparator is not given.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two values to be compared.
|
|
||||||
* Should return the result of a <=> b.
|
|
||||||
*
|
|
||||||
* @return Sequence
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function sorted(callable $comparator = null): Sequence;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sum of all values in the sequence.
|
|
||||||
*
|
|
||||||
* @return int|float The sum of all the values in the sequence.
|
|
||||||
*/
|
|
||||||
public function sum();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*
|
|
||||||
* @return list<TValue>
|
|
||||||
*/
|
|
||||||
function toArray(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds zero or more values to the front of the sequence.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function unshift(...$values);
|
|
||||||
}
|
|
||||||
@ -1,541 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
use Error;
|
|
||||||
use OutOfBoundsException;
|
|
||||||
use OutOfRangeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sequence of unique values.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @implements Collection<int, TValue>
|
|
||||||
* @implements \ArrayAccess<int, TValue>
|
|
||||||
* @template-use Traits\GenericCollection<int, TValue>
|
|
||||||
*/
|
|
||||||
final class Set implements Collection, \ArrayAccess
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
|
|
||||||
public const MIN_CAPACITY = Map::MIN_CAPACITY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Map internal map to store the values.
|
|
||||||
*
|
|
||||||
* @psalm-var Map<int, TValue>
|
|
||||||
*/
|
|
||||||
private $table;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new set using the values of an array or Traversable object.
|
|
||||||
* The keys of either will not be preserved.
|
|
||||||
*
|
|
||||||
* @param iterable $values
|
|
||||||
*
|
|
||||||
* @psalm-param iterable<TValue> $values
|
|
||||||
*/
|
|
||||||
public function __construct(iterable $values = [])
|
|
||||||
{
|
|
||||||
$this->table = new Map();
|
|
||||||
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$this->add($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds zero or more values to the set.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function add(...$values)
|
|
||||||
{
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$this->table->put($value, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that enough memory is allocated for a specified capacity. This
|
|
||||||
* potentially reduces the number of reallocations as the size increases.
|
|
||||||
*
|
|
||||||
* @param int $capacity The number of values for which capacity should be
|
|
||||||
* allocated. Capacity will stay the same if this value
|
|
||||||
* is less than or equal to the current capacity.
|
|
||||||
*/
|
|
||||||
public function allocate(int $capacity)
|
|
||||||
{
|
|
||||||
$this->table->allocate($capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current capacity of the set.
|
|
||||||
*/
|
|
||||||
public function capacity(): int
|
|
||||||
{
|
|
||||||
return $this->table->capacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all elements in the Set
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->table->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether the set contains all of zero or more values.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @return bool true if at least one value was provided and the set
|
|
||||||
* contains all given values, false otherwise.
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function contains(...$values): bool
|
|
||||||
{
|
|
||||||
foreach ($values as $value) {
|
|
||||||
if ( ! $this->table->hasKey($value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function copy(): self
|
|
||||||
{
|
|
||||||
return new self($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of elements in the Stack
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return count($this->table);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new set using values from this set that aren't in another set.
|
|
||||||
*
|
|
||||||
* Formally: A \ B = {x ∈ A | x ∉ B}
|
|
||||||
*
|
|
||||||
* @param Set $set
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Set<TValue2> $set
|
|
||||||
* @psalm-return Set<TValue>
|
|
||||||
*/
|
|
||||||
public function diff(Set $set): Set
|
|
||||||
{
|
|
||||||
return $this->table->diff($set->table)->keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new set using values in either this set or in another set,
|
|
||||||
* but not in both.
|
|
||||||
*
|
|
||||||
* Formally: A ⊖ B = {x : x ∈ (A \ B) ∪ (B \ A)}
|
|
||||||
*
|
|
||||||
* @param Set $set
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Set<TValue2> $set
|
|
||||||
* @psalm-return Set<TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function xor(Set $set): Set
|
|
||||||
{
|
|
||||||
return $this->table->xor($set->table)->keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new set containing only the values for which a callback
|
|
||||||
* returns true. A boolean test will be used if a callback is not provided.
|
|
||||||
*
|
|
||||||
* @param callable|null $callback Accepts a value, returns a boolean:
|
|
||||||
* true : include the value,
|
|
||||||
* false: skip the value.
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue): bool)|null $callback
|
|
||||||
* @psalm-return Set<TValue>
|
|
||||||
*/
|
|
||||||
public function filter(callable|null $callback = null): Set
|
|
||||||
{
|
|
||||||
return new self(array_filter($this->toArray(), $callback ?: 'boolval'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first value in the set.
|
|
||||||
*
|
|
||||||
* @return mixed the first value in the set.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function first()
|
|
||||||
{
|
|
||||||
return $this->table->first()->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value at a specified position in the set.
|
|
||||||
*
|
|
||||||
* @return mixed|null
|
|
||||||
*
|
|
||||||
* @throws OutOfRangeException
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function get(int $position)
|
|
||||||
{
|
|
||||||
return $this->table->skip($position)->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new set using values common to both this set and another set.
|
|
||||||
*
|
|
||||||
* In other words, returns a copy of this set with all values removed that
|
|
||||||
* aren't in the other set.
|
|
||||||
*
|
|
||||||
* Formally: A ∩ B = {x : x ∈ A ∧ x ∈ B}
|
|
||||||
*
|
|
||||||
* @param Set $set
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Set<TValue2> $set
|
|
||||||
* @psalm-return Set<TValue&TValue2>
|
|
||||||
*/
|
|
||||||
public function intersect(Set $set): Set
|
|
||||||
{
|
|
||||||
return $this->table->intersect($set->table)->keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function isEmpty(): bool
|
|
||||||
{
|
|
||||||
return $this->table->isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins all values of the set into a string, adding an optional 'glue'
|
|
||||||
* between them. Returns an empty string if the set is empty.
|
|
||||||
*
|
|
||||||
* @param string|null $glue
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function join(string|null $glue = null): string
|
|
||||||
{
|
|
||||||
return implode($glue ?? '', $this->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the last value in the set.
|
|
||||||
*
|
|
||||||
* @return mixed the last value in the set.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function last()
|
|
||||||
{
|
|
||||||
return $this->table->last()->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new set using the results of applying a callback to each
|
|
||||||
* value.
|
|
||||||
*
|
|
||||||
* @param callable $callback
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @template TNewValue
|
|
||||||
* @psalm-param callable(TValue): TNewValue $callback
|
|
||||||
* @psalm-return Set<TNewValue>
|
|
||||||
*/
|
|
||||||
public function map(callable $callback) {
|
|
||||||
return new self(array_map($callback, $this->toArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iteratively reduces the set to a single value using a callback.
|
|
||||||
*
|
|
||||||
* @param callable $callback Accepts the carry and current value, and
|
|
||||||
* returns an updated carry value.
|
|
||||||
*
|
|
||||||
* @param mixed|null $initial Optional initial carry value.
|
|
||||||
*
|
|
||||||
* @return mixed The carry value of the final iteration, or the initial
|
|
||||||
* value if the set was empty.
|
|
||||||
*
|
|
||||||
* @template TCarry
|
|
||||||
* @psalm-param callable(TCarry, TValue): TCarry $callback
|
|
||||||
* @psalm-param TCarry $initial
|
|
||||||
* @psalm-return TCarry
|
|
||||||
*/
|
|
||||||
public function reduce(callable $callback, $initial = null)
|
|
||||||
{
|
|
||||||
$carry = $initial;
|
|
||||||
|
|
||||||
foreach ($this as $value) {
|
|
||||||
$carry = $callback($carry, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $carry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes zero or more values from the set.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function remove(...$values)
|
|
||||||
{
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$this->table->remove($value, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the set in-place.
|
|
||||||
*/
|
|
||||||
public function reverse()
|
|
||||||
{
|
|
||||||
$this->table->reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a reversed copy of the set.
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @psalm-return Set<TValue>
|
|
||||||
*/
|
|
||||||
public function reversed(): Set
|
|
||||||
{
|
|
||||||
$reversed = $this->copy();
|
|
||||||
$reversed->table->reverse();
|
|
||||||
|
|
||||||
return $reversed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a subset of a given length starting at a specified offset.
|
|
||||||
*
|
|
||||||
* @param int $offset If the offset is non-negative, the set will start
|
|
||||||
* at that offset in the set. If offset is negative,
|
|
||||||
* the set will start that far from the end.
|
|
||||||
*
|
|
||||||
* @param int $length If a length is given and is positive, the resulting
|
|
||||||
* set will have up to that many values in it.
|
|
||||||
* If the requested length results in an overflow, only
|
|
||||||
* values up to the end of the set will be included.
|
|
||||||
*
|
|
||||||
* If a length is given and is negative, the set
|
|
||||||
* will stop that many values from the end.
|
|
||||||
*
|
|
||||||
* If a length is not provided, the resulting set
|
|
||||||
* will contains all values between the offset and the
|
|
||||||
* end of the set.
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @psalm-return Set<TValue>
|
|
||||||
*/
|
|
||||||
public function slice(int $offset, int|null $length = null): Set
|
|
||||||
{
|
|
||||||
$sliced = new self();
|
|
||||||
$sliced->table = $this->table->slice($offset, $length);
|
|
||||||
|
|
||||||
return $sliced;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the set in-place, based on an optional callable comparator.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two values to be compared.
|
|
||||||
* Should return the result of a <=> b.
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
*/
|
|
||||||
public function sort(callable|null $comparator = null)
|
|
||||||
{
|
|
||||||
$this->table->ksort($comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a sorted copy of the set, based on an optional callable
|
|
||||||
* comparator. Natural ordering will be used if a comparator is not given.
|
|
||||||
*
|
|
||||||
* @param callable|null $comparator Accepts two values to be compared.
|
|
||||||
* Should return the result of a <=> b.
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
* @psalm-return Set<TValue>
|
|
||||||
*/
|
|
||||||
public function sorted(callable|null $comparator = null): Set
|
|
||||||
{
|
|
||||||
$sorted = $this->copy();
|
|
||||||
$sorted->table->ksort($comparator);
|
|
||||||
|
|
||||||
return $sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of adding all given values to the set.
|
|
||||||
*
|
|
||||||
* @param array|\Traversable $values
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param iterable<TValue2> $values
|
|
||||||
* @psalm-return Set<TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function merge($values): Set
|
|
||||||
{
|
|
||||||
$merged = $this->copy();
|
|
||||||
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$merged->add($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $merged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return iterator_to_array($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sum of all values in the set.
|
|
||||||
*
|
|
||||||
* @return int|float The sum of all the values in the set.
|
|
||||||
*/
|
|
||||||
public function sum()
|
|
||||||
{
|
|
||||||
return array_sum($this->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new set that contains the values of this set as well as the
|
|
||||||
* values of another set.
|
|
||||||
*
|
|
||||||
* Formally: A ∪ B = {x: x ∈ A ∨ x ∈ B}
|
|
||||||
*
|
|
||||||
* @param Set $set
|
|
||||||
*
|
|
||||||
* @return Set
|
|
||||||
*
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param Set<TValue2> $set
|
|
||||||
* @psalm-return Set<TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function union(Set $set): Set
|
|
||||||
{
|
|
||||||
$union = new self();
|
|
||||||
|
|
||||||
foreach ($this as $value) {
|
|
||||||
$union->add($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($set as $value) {
|
|
||||||
$union->add($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $union;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get iterator
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function getIterator()
|
|
||||||
{
|
|
||||||
foreach ($this->table as $key => $value) {
|
|
||||||
yield $key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws OutOfBoundsException
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
if ($offset === null) {
|
|
||||||
$this->add($value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
return $this->table->skip($offset)->key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the internal table will be cloned too.
|
|
||||||
*/
|
|
||||||
public function __clone()
|
|
||||||
{
|
|
||||||
$this->table = clone $this->table;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
use Error;
|
|
||||||
use OutOfBoundsException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A “last in, first out” or “LIFO” collection that only allows access to the
|
|
||||||
* value at the top of the structure and iterates in that order, destructively.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @implements Collection<int, TValue>
|
|
||||||
* @implements \ArrayAccess<int, TValue>
|
|
||||||
* @template-use Traits\GenericCollection<int, TValue>
|
|
||||||
*/
|
|
||||||
final class Stack implements Collection, \ArrayAccess
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Vector internal vector to store values of the stack.
|
|
||||||
*
|
|
||||||
* @psalm-var Vector<TValue>
|
|
||||||
*/
|
|
||||||
private $vector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance using the values of an array or Traversable object.
|
|
||||||
*
|
|
||||||
* @param iterable<mixed> $values
|
|
||||||
*
|
|
||||||
* @psalm-param iterable<TValue> $values
|
|
||||||
*/
|
|
||||||
public function __construct(iterable $values = [])
|
|
||||||
{
|
|
||||||
$this->vector = new Vector($values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all elements in the Stack
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->vector->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function copy(): self
|
|
||||||
{
|
|
||||||
return new self($this->vector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of elements in the Stack
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return count($this->vector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that enough memory is allocated for a specified capacity. This
|
|
||||||
* potentially reduces the number of reallocations as the size increases.
|
|
||||||
*
|
|
||||||
* @param int $capacity The number of values for which capacity should be
|
|
||||||
* allocated. Capacity will stay the same if this value
|
|
||||||
* is less than or equal to the current capacity.
|
|
||||||
*/
|
|
||||||
public function allocate(int $capacity)
|
|
||||||
{
|
|
||||||
$this->vector->allocate($capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current capacity of the stack.
|
|
||||||
*/
|
|
||||||
public function capacity(): int
|
|
||||||
{
|
|
||||||
return $this->vector->capacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value at the top of the stack without removing it.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \UnderflowException if the stack is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function peek()
|
|
||||||
{
|
|
||||||
return $this->vector->last();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns and removes the value at the top of the stack.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @throws \UnderflowException if the stack is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function pop()
|
|
||||||
{
|
|
||||||
return $this->vector->pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes zero or more values onto the top of the stack.
|
|
||||||
*
|
|
||||||
* @param mixed ...$values
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function push(...$values)
|
|
||||||
{
|
|
||||||
$this->vector->push(...$values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return array_reverse($this->vector->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function getIterator()
|
|
||||||
{
|
|
||||||
while ( ! $this->isEmpty()) {
|
|
||||||
yield $this->pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws OutOfBoundsException
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
if ($offset === null) {
|
|
||||||
$this->push($value);
|
|
||||||
} else {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetGet($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*
|
|
||||||
* @throws Error
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the internal vector will be cloned too.
|
|
||||||
*/
|
|
||||||
public function __clone()
|
|
||||||
{
|
|
||||||
$this->vector = clone $this->vector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds\Traits;
|
|
||||||
|
|
||||||
use Ds\Deque;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common to structures that deal with an internal capacity. While none of the
|
|
||||||
* PHP implementations actually make use of a capacity, it's important to keep
|
|
||||||
* consistent with the extension.
|
|
||||||
*/
|
|
||||||
trait Capacity
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var int internal capacity
|
|
||||||
*/
|
|
||||||
private $capacity = self::MIN_CAPACITY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current capacity.
|
|
||||||
*/
|
|
||||||
public function capacity(): int
|
|
||||||
{
|
|
||||||
return $this->capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that enough memory is allocated for a specified capacity. This
|
|
||||||
* potentially reduces the number of reallocations as the size increases.
|
|
||||||
*
|
|
||||||
* @param int $capacity The number of values for which capacity should be
|
|
||||||
* allocated. Capacity will stay the same if this value
|
|
||||||
* is less than or equal to the current capacity.
|
|
||||||
*/
|
|
||||||
public function allocate(int $capacity)
|
|
||||||
{
|
|
||||||
$this->capacity = max($capacity, $this->capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return float the structures growth factor.
|
|
||||||
*/
|
|
||||||
protected function getGrowthFactor(): float
|
|
||||||
{
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return float to multiply by when decreasing capacity.
|
|
||||||
*/
|
|
||||||
protected function getDecayFactor(): float
|
|
||||||
{
|
|
||||||
return 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return float the ratio between size and capacity when capacity should be
|
|
||||||
* decreased.
|
|
||||||
*/
|
|
||||||
protected function getTruncateThreshold(): float
|
|
||||||
{
|
|
||||||
return 0.25;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks and adjusts capacity if required.
|
|
||||||
*/
|
|
||||||
protected function checkCapacity()
|
|
||||||
{
|
|
||||||
if ($this->shouldIncreaseCapacity()) {
|
|
||||||
$this->increaseCapacity();
|
|
||||||
} else {
|
|
||||||
if ($this->shouldDecreaseCapacity()) {
|
|
||||||
$this->decreaseCapacity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $total
|
|
||||||
*/
|
|
||||||
protected function ensureCapacity(int $total)
|
|
||||||
{
|
|
||||||
if ($total > $this->capacity()) {
|
|
||||||
$this->capacity = max($total, $this->nextCapacity());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool whether capacity should be increased.
|
|
||||||
*/
|
|
||||||
protected function shouldIncreaseCapacity(): bool
|
|
||||||
{
|
|
||||||
return $this->count() >= $this->capacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function nextCapacity(): int
|
|
||||||
{
|
|
||||||
return (int) ($this->capacity() * $this->getGrowthFactor());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when capacity should be increased to accommodate new values.
|
|
||||||
*/
|
|
||||||
protected function increaseCapacity()
|
|
||||||
{
|
|
||||||
$this->capacity = max(
|
|
||||||
$this->count(),
|
|
||||||
$this->nextCapacity()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when capacity should be decrease if it drops below a threshold.
|
|
||||||
*/
|
|
||||||
protected function decreaseCapacity()
|
|
||||||
{
|
|
||||||
$this->capacity = max(
|
|
||||||
self::MIN_CAPACITY,
|
|
||||||
(int) ($this->capacity() * $this->getDecayFactor())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool whether capacity should be increased.
|
|
||||||
*/
|
|
||||||
protected function shouldDecreaseCapacity(): bool
|
|
||||||
{
|
|
||||||
return count($this) <= $this->capacity() * $this->getTruncateThreshold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds\Traits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common to structures that implement the base collection interface.
|
|
||||||
* @template-covariant TKey
|
|
||||||
* @template-covariant TValue
|
|
||||||
*/
|
|
||||||
trait GenericCollection
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Returns whether the collection is empty.
|
|
||||||
*
|
|
||||||
* This should be equivalent to a count of zero, but is not required.
|
|
||||||
* Implementations should define what empty means in their own context.
|
|
||||||
*
|
|
||||||
* @return bool whether the collection is empty.
|
|
||||||
*/
|
|
||||||
public function isEmpty(): bool
|
|
||||||
{
|
|
||||||
return count($this) === 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a representation that can be natively converted to JSON, which is
|
|
||||||
* called when invoking json_encode.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*
|
|
||||||
* @see \JsonSerializable
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function jsonSerialize()
|
|
||||||
{
|
|
||||||
return $this->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a shallow copy of the collection.
|
|
||||||
*
|
|
||||||
* @return static a shallow copy of the collection.
|
|
||||||
*
|
|
||||||
* @psalm-return static<TKey, TValue>
|
|
||||||
*/
|
|
||||||
public function copy(): self
|
|
||||||
{
|
|
||||||
return new static($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an array representation of the collection.
|
|
||||||
*
|
|
||||||
* The format of the returned array is implementation-dependent. Some
|
|
||||||
* implementations may throw an exception if an array representation
|
|
||||||
* could not be created (for example when object are used as keys).
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*
|
|
||||||
* @psalm-return array<TKey, TValue>
|
|
||||||
*/
|
|
||||||
abstract public function toArray(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when calling var_dump.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function __debugInfo()
|
|
||||||
{
|
|
||||||
return $this->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the collection, which is invoked when
|
|
||||||
* the collection is converted to a string.
|
|
||||||
*/
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return 'object(' . get_class($this) . ')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,447 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds\Traits;
|
|
||||||
|
|
||||||
use Ds\Sequence;
|
|
||||||
use OutOfRangeException;
|
|
||||||
use UnderflowException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common functionality of all structures that implement 'Sequence'. Because the
|
|
||||||
* polyfill's only goal is to achieve consistent behaviour, all sequences will
|
|
||||||
* share the same implementation using an array array.
|
|
||||||
*
|
|
||||||
* @package Ds\Traits
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
*/
|
|
||||||
trait GenericSequence
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var array internal array used to store the values of the sequence.
|
|
||||||
*
|
|
||||||
* @psalm-var array<TValue>
|
|
||||||
*/
|
|
||||||
private $array = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param iterable $values
|
|
||||||
*
|
|
||||||
* @psalm-param iterable<TValue> $values
|
|
||||||
*/
|
|
||||||
public function __construct(iterable $values = [])
|
|
||||||
{
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$this->push($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->capacity = max(
|
|
||||||
$values === null ? 0 : count($values),
|
|
||||||
$this::MIN_CAPACITY
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return list<TValue>
|
|
||||||
*/
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
return $this->array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param callable(TValue): TValue $callback
|
|
||||||
*/
|
|
||||||
public function apply(callable $callback)
|
|
||||||
{
|
|
||||||
foreach ($this->array as &$value) {
|
|
||||||
$value = $callback($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TValue2
|
|
||||||
* @psalm-param iterable<TValue2> $values
|
|
||||||
* @psalm-return Sequence<TValue|TValue2>
|
|
||||||
*/
|
|
||||||
public function merge($values): Sequence
|
|
||||||
{
|
|
||||||
$copy = $this->copy();
|
|
||||||
$copy->push(...$values);
|
|
||||||
return $copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function count(): int
|
|
||||||
{
|
|
||||||
return count($this->array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function contains(...$values): bool
|
|
||||||
{
|
|
||||||
foreach ($values as $value) {
|
|
||||||
if ($this->find($value) === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param (callable(TValue): bool)|null $callback
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function filter(callable|null $callback = null): Sequence
|
|
||||||
{
|
|
||||||
return new self(array_filter($this->array, $callback ?: 'boolval'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int|null
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function find($value)
|
|
||||||
{
|
|
||||||
$offset = array_search($value, $this->array, true);
|
|
||||||
|
|
||||||
return $offset === false ? null : $offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \UnderflowException if the sequence is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function first()
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->array[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, size-1]
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function get(int $index)
|
|
||||||
{
|
|
||||||
if ( ! $this->validIndex($index)) {
|
|
||||||
throw new OutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->array[$index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, n]
|
|
||||||
*
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function insert(int $index, ...$values)
|
|
||||||
{
|
|
||||||
if ( ! $this->validIndex($index) && $index !== count($this)) {
|
|
||||||
throw new OutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
array_splice($this->array, $index, 0, $values);
|
|
||||||
$this->checkCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function join(string $glue = null): string
|
|
||||||
{
|
|
||||||
return implode($glue ?? '', $this->array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \UnderflowException if the sequence is empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function last()
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->array[count($this) - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TNewValue
|
|
||||||
* @psalm-param callable(TValue): TNewValue $callback
|
|
||||||
* @psalm-return Sequence<TNewValue>
|
|
||||||
*/
|
|
||||||
public function map(callable $callback): Sequence
|
|
||||||
{
|
|
||||||
return new self(array_map($callback, $this->array));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \UnderflowException if the sequence is empty.
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function pop()
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = array_pop($this->array);
|
|
||||||
$this->checkCapacity();
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function push(...$values)
|
|
||||||
{
|
|
||||||
$this->ensureCapacity($this->count() + count($values));
|
|
||||||
|
|
||||||
foreach ($values as $value) {
|
|
||||||
$this->array[] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template TCarry
|
|
||||||
* @psalm-param callable(TCarry, TValue): TCarry $callback
|
|
||||||
* @psalm-param TCarry $initial
|
|
||||||
* @psalm-return TCarry
|
|
||||||
*/
|
|
||||||
public function reduce(callable $callback, $initial = null)
|
|
||||||
{
|
|
||||||
return array_reduce($this->array, $callback, $initial);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, size-1]
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function remove(int $index)
|
|
||||||
{
|
|
||||||
if ( ! $this->validIndex($index)) {
|
|
||||||
throw new OutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = array_splice($this->array, $index, 1, null)[0];
|
|
||||||
$this->checkCapacity();
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function reverse()
|
|
||||||
{
|
|
||||||
$this->array = array_reverse($this->array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function reversed(): Sequence
|
|
||||||
{
|
|
||||||
return new self(array_reverse($this->array));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts negative or large rotations into the minimum positive number
|
|
||||||
* of rotations required to rotate the sequence by a given $r.
|
|
||||||
*/
|
|
||||||
private function normalizeRotations(int $r)
|
|
||||||
{
|
|
||||||
$n = count($this);
|
|
||||||
|
|
||||||
if ($n < 2) return 0;
|
|
||||||
if ($r < 0) return $n - (abs($r) % $n);
|
|
||||||
|
|
||||||
return $r % $n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function rotate(int $rotations)
|
|
||||||
{
|
|
||||||
for ($r = $this->normalizeRotations($rotations); $r > 0; $r--) {
|
|
||||||
array_push($this->array, array_shift($this->array));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \OutOfRangeException if the index is not in the range [0, size-1]
|
|
||||||
*
|
|
||||||
* @psalm-param TValue $value
|
|
||||||
*/
|
|
||||||
public function set(int $index, $value)
|
|
||||||
{
|
|
||||||
if ( ! $this->validIndex($index)) {
|
|
||||||
throw new OutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->array[$index] = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \UnderflowException if the sequence was empty.
|
|
||||||
*
|
|
||||||
* @psalm-return TValue
|
|
||||||
*/
|
|
||||||
public function shift()
|
|
||||||
{
|
|
||||||
if ($this->isEmpty()) {
|
|
||||||
throw new UnderflowException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$value = array_shift($this->array);
|
|
||||||
$this->checkCapacity();
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function slice(int $offset, int $length = null): Sequence
|
|
||||||
{
|
|
||||||
if (func_num_args() === 1) {
|
|
||||||
$length = count($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new self(array_slice($this->array, $offset, $length));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
*/
|
|
||||||
public function sort(callable $comparator = null)
|
|
||||||
{
|
|
||||||
if ($comparator) {
|
|
||||||
usort($this->array, $comparator);
|
|
||||||
} else {
|
|
||||||
sort($this->array);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param (callable(TValue, TValue): int)|null $comparator
|
|
||||||
* @psalm-return Sequence<TValue>
|
|
||||||
*/
|
|
||||||
public function sorted(callable $comparator = null): Sequence
|
|
||||||
{
|
|
||||||
$copy = $this->copy();
|
|
||||||
$copy->sort($comparator);
|
|
||||||
return $copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return int|float
|
|
||||||
*/
|
|
||||||
public function sum()
|
|
||||||
{
|
|
||||||
return array_sum($this->array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param TValue ...$values
|
|
||||||
*/
|
|
||||||
public function unshift(...$values)
|
|
||||||
{
|
|
||||||
if ($values) {
|
|
||||||
$this->array = array_merge($values, $this->array);
|
|
||||||
$this->checkCapacity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
private function validIndex(int $index)
|
|
||||||
{
|
|
||||||
return $index >= 0 && $index < count($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function getIterator()
|
|
||||||
{
|
|
||||||
foreach ($this->array as $value) {
|
|
||||||
yield $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
public function clear()
|
|
||||||
{
|
|
||||||
$this->array = [];
|
|
||||||
$this->capacity = self::MIN_CAPACITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetSet($offset, $value)
|
|
||||||
{
|
|
||||||
if ($offset === null) {
|
|
||||||
$this->push($value);
|
|
||||||
} else {
|
|
||||||
$this->set($offset, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function &offsetGet($offset)
|
|
||||||
{
|
|
||||||
if ( ! $this->validIndex($offset)) {
|
|
||||||
throw new OutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->array[$offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetUnset($offset)
|
|
||||||
{
|
|
||||||
if (is_integer($offset) && $this->validIndex($offset)) {
|
|
||||||
$this->remove($offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function offsetExists($offset)
|
|
||||||
{
|
|
||||||
return is_integer($offset)
|
|
||||||
&& $this->validIndex($offset)
|
|
||||||
&& $this->get($offset) !== null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds\Traits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common to structures that require a capacity which is a power of two.
|
|
||||||
*/
|
|
||||||
trait SquaredCapacity
|
|
||||||
{
|
|
||||||
use Capacity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rounds an integer to the next power of two if not already a power of two.
|
|
||||||
*
|
|
||||||
* @param int $capacity
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
private function square(int $capacity): int
|
|
||||||
{
|
|
||||||
return pow(2, ceil(log($capacity, 2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that enough memory is allocated for a specified capacity. This
|
|
||||||
* potentially reduces the number of reallocations as the size increases.
|
|
||||||
*
|
|
||||||
* @param int $capacity The number of values for which capacity should be
|
|
||||||
* allocated. Capacity will stay the same if this value
|
|
||||||
* is less than or equal to the current capacity.
|
|
||||||
*/
|
|
||||||
public function allocate(int $capacity)
|
|
||||||
{
|
|
||||||
$this->capacity = max($this->square($capacity), $this->capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when capacity should be increased to accommodate new values.
|
|
||||||
*/
|
|
||||||
protected function increaseCapacity()
|
|
||||||
{
|
|
||||||
$this->capacity = $this->square(
|
|
||||||
max(
|
|
||||||
count($this) + 1,
|
|
||||||
$this->capacity * $this->getGrowthFactor()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $total
|
|
||||||
*/
|
|
||||||
protected function ensureCapacity(int $total)
|
|
||||||
{
|
|
||||||
while ($total > $this->capacity()) {
|
|
||||||
$this->increaseCapacity();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Vector is a sequence of values in a contiguous buffer that grows and
|
|
||||||
* shrinks automatically. It’s the most efficient sequential structure because
|
|
||||||
* a value’s index is a direct mapping to its index in the buffer, and the
|
|
||||||
* growth factor isn't bound to a specific multiple or exponent.
|
|
||||||
*
|
|
||||||
* @package Ds
|
|
||||||
*
|
|
||||||
* @template TValue
|
|
||||||
* @implements Sequence<TValue>
|
|
||||||
* @template-use Traits\GenericCollection<int, TValue>
|
|
||||||
* @template-use Traits\GenericSequence<TValue>
|
|
||||||
*/
|
|
||||||
final class Vector implements Sequence
|
|
||||||
{
|
|
||||||
use Traits\GenericCollection;
|
|
||||||
use Traits\GenericSequence;
|
|
||||||
use Traits\Capacity;
|
|
||||||
|
|
||||||
public const MIN_CAPACITY = 8;
|
|
||||||
|
|
||||||
protected function getGrowthFactor(): float
|
|
||||||
{
|
|
||||||
return 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return bool whether capacity should be increased.
|
|
||||||
*/
|
|
||||||
protected function shouldIncreaseCapacity(): bool
|
|
||||||
{
|
|
||||||
return count($this) > $this->capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Ds;
|
|
||||||
|
|
||||||
require __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
@ -78,27 +78,26 @@ class CheckUpdate extends BasePluginRW
|
|||||||
Log::info('开始检查项目更新');
|
Log::info('开始检查项目更新');
|
||||||
// resource object
|
// resource object
|
||||||
$offline = $this->fetchOfflineVersion();
|
$offline = $this->fetchOfflineVersion();
|
||||||
$offline_version = $offline->get('version');
|
|
||||||
//
|
//
|
||||||
Log::info('拉取线上最新配置');
|
Log::info('拉取线上最新配置');
|
||||||
// object
|
// object
|
||||||
$online = $this->fetchOnlineVersion();
|
$online = $this->fetchOnlineVersion();
|
||||||
$online_version = $online->version;
|
|
||||||
// 网络错误
|
// 网络错误
|
||||||
if ($online->code != 200) {
|
if ($online->code != 200) {
|
||||||
Log::warning('检查更新: 拉取线上失败,网络错误!');
|
Log::warning('检查更新: 拉取线上失败,网络错误!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 比较版本
|
// 比较版本
|
||||||
if ($this->compareVersion($offline_version, $online_version)) {
|
if ($this->compareVersion($offline->get('version'), $online->version)) {
|
||||||
//
|
// TODO 完善消息 支持markdown
|
||||||
$time = $online->update_time;
|
$time = $online->time;
|
||||||
$desc = $online->update_description;
|
$version = $online->version;
|
||||||
$info = "请注意版本变动更新哦~\n\n版本号: $online_version\n\n更新日志: $desc\n\n更新时间: $time\n\n";
|
$des = $online->des;
|
||||||
|
$info = "请注意版本变动更新哦~\n\n版本号: $version\n\n更新日志: $des\n\n更新时间: $time\n\n";
|
||||||
Log::notice($info);
|
Log::notice($info);
|
||||||
Notice::push('update', $info);
|
Notice::push('update', $info);
|
||||||
} else {
|
} else {
|
||||||
Log::info("当前程序版本($offline_version)已是最新");
|
Log::info('程序已是最新版本');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ device_version: 0.0.1
|
|||||||
app:
|
app:
|
||||||
bili_a: # Android
|
bili_a: # Android
|
||||||
package: "tv.danmaku.bili"
|
package: "tv.danmaku.bili"
|
||||||
version: "8.33.0"
|
version: "8.27.0"
|
||||||
build: "8330200"
|
build: "8270400"
|
||||||
channel: "bili"
|
channel: "bili"
|
||||||
device: "phone"
|
device: "phone"
|
||||||
mobi_app: "android"
|
mobi_app: "android"
|
||||||
@ -15,7 +15,7 @@ app:
|
|||||||
secret_key: "NTYwYzUyY2NkMjg4ZmVkMDQ1ODU5ZWQxOGJmZmQ5NzM"
|
secret_key: "NTYwYzUyY2NkMjg4ZmVkMDQ1ODU5ZWQxOGJmZmQ5NzM"
|
||||||
app_key_n: "NzgzYmJiNzI2NDQ1MWQ4Mg=="
|
app_key_n: "NzgzYmJiNzI2NDQ1MWQ4Mg=="
|
||||||
secret_key_n: "MjY1MzU4M2M4ODczZGVhMjY4YWI5Mzg2OTE4YjFkNjU="
|
secret_key_n: "MjY1MzU4M2M4ODczZGVhMjY4YWI5Mzg2OTE4YjFkNjU="
|
||||||
statistics: '{"appId":1,"platform":3,"version":"8.33.0","abtest":""}'
|
statistics: '{"appId":1,"platform":3,"version":"8.27.0","abtest":""}'
|
||||||
bili_i: # IOS
|
bili_i: # IOS
|
||||||
app_key: "MjdlYjUzZmM5MDU4ZjhjMw=="
|
app_key: "MjdlYjUzZmM5MDU4ZjhjMw=="
|
||||||
secret_key: "YzJlZDUzYTc0ZWVlZmUzY2Y5OWZiZDAxZDhjOWMzNzU="
|
secret_key: "YzJlZDUzYTc0ZWVlZmUzY2Y5OWZiZDAxZDhjOWMzNzU="
|
||||||
|
|||||||
@ -8,7 +8,10 @@
|
|||||||
"dev_raw_url": "https://raw.githubusercontent.com/lkeme/BiliHelper-personal/dev/resources/version.json",
|
"dev_raw_url": "https://raw.githubusercontent.com/lkeme/BiliHelper-personal/dev/resources/version.json",
|
||||||
"master_purge_url": "https://cdn.staticaly.com/gh/lkeme/BiliHelper-personal/master/resources/version.json",
|
"master_purge_url": "https://cdn.staticaly.com/gh/lkeme/BiliHelper-personal/master/resources/version.json",
|
||||||
"dev_purge_url": "https://cdn.staticaly.com/gh/lkeme/BiliHelper-personal/dev/resources/version.json",
|
"dev_purge_url": "https://cdn.staticaly.com/gh/lkeme/BiliHelper-personal/dev/resources/version.json",
|
||||||
"version": "2.4.5.250218",
|
"version": "2.4.3.241231",
|
||||||
"update_time": "2025-02-18",
|
"des": "程序有更新,请及时线上查看更新哦~",
|
||||||
"update_description": "程序有更新,请及时线上查看更新哦~"
|
"time": "2024-12-31",
|
||||||
|
"ini_version": "0.0.1",
|
||||||
|
"ini_des": "配置有更新,请及时线上查看更新哦~",
|
||||||
|
"ini_time": "2024-12-31"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,11 +20,6 @@ namespace Bhp\Util\Resource;
|
|||||||
use Grasmash\Expander\Expander;
|
use Grasmash\Expander\Expander;
|
||||||
use Grasmash\Expander\Stringifier;
|
use Grasmash\Expander\Stringifier;
|
||||||
use JBZoo\Data\Data;
|
use JBZoo\Data\Data;
|
||||||
use JBZoo\Data\Ini;
|
|
||||||
use JBZoo\Data\JSON;
|
|
||||||
use JBZoo\Data\PhpArray;
|
|
||||||
use JBZoo\Data\Yml;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use function JBZoo\Data\data;
|
use function JBZoo\Data\data;
|
||||||
use function JBZoo\Data\ini;
|
use function JBZoo\Data\ini;
|
||||||
use function JBZoo\Data\phpArray;
|
use function JBZoo\Data\phpArray;
|
||||||
@ -40,9 +35,9 @@ class Resource extends Collection
|
|||||||
protected const FORMAT_JSON = 'json';
|
protected const FORMAT_JSON = 'json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Ini|JSON|Yml|PhpArray|Data
|
* @var Data
|
||||||
*/
|
*/
|
||||||
protected Ini|JSON|Yml|PhpArray|Data $config;
|
protected Data $config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -78,15 +73,15 @@ class Resource extends Collection
|
|||||||
* 切换解析器
|
* 切换解析器
|
||||||
* @param string $filepath
|
* @param string $filepath
|
||||||
* @param string $format
|
* @param string $format
|
||||||
* @return Ini|JSON|Yml|PhpArray|Data
|
* @return Data
|
||||||
*/
|
*/
|
||||||
protected function switchParser(string $filepath, string $format): Ini|Json|Yml|PhpArray|Data
|
protected function switchParser(string $filepath, string $format): Data
|
||||||
{
|
{
|
||||||
return match ($format) {
|
return match ($format) {
|
||||||
Resource::FORMAT_INI => ini($filepath),
|
Resource::FORMAT_INI => ini($filepath),
|
||||||
Resource::FORMAT_JSON => json($filepath),
|
|
||||||
Resource::FORMAT_YML, Resource::FORMAT_YAML => yml($filepath),
|
|
||||||
Resource::FORMAT_PHP => phpArray($filepath),
|
Resource::FORMAT_PHP => phpArray($filepath),
|
||||||
|
Resource::FORMAT_YML, Resource::FORMAT_YAML => yml($filepath),
|
||||||
|
Resource::FORMAT_JSON => json($filepath),
|
||||||
default => data($filepath),
|
default => data($filepath),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user