diff --git a/.gitattributes b/.gitattributes index c4ff8b1..fdfe866 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,5 @@ phpunit.php export-ignore phpunit.xml.dist export-ignore phpunit.xml export-ignore .php_cs export-ignore +pint.json export-ignore +phpstan.neon.dist export-ignore \ No newline at end of file diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 5b4c473..4783104 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -1,8 +1,7 @@ name: codestyle on: pull_request: - paths: - - "!*.md" + workflow_dispatch: jobs: code-coverage: name: Code Coverage @@ -37,9 +36,6 @@ jobs: - name: Install dependencies with composer run: composer update --no-ansi --no-interaction --no-progress - - name: Install Laravel Illuminate Contracts - run: composer require "illuminate/contracts" - - name: Code Style run: vendor/bin/pint --test --config ./pint.json diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b6f8a8d..28eaa0b 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,13 +1,12 @@ name: static analysis on: + workflow_dispatch: push: branches: - master - '*.x' pull_request: - paths: - - "!*.md" permissions: contents: read @@ -17,7 +16,10 @@ jobs: runs-on: ubuntu-22.04 strategy: - fail-fast: true + fail-fast: false + matrix: + php-version: + - 8.2 name: Static Analysis @@ -31,6 +33,7 @@ jobs: php-version: 8.2 tools: composer:v2 coverage: none + extensions: swoole, redis, pcntl - name: Install dependencies uses: nick-fields/retry@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24d128a..49e9972 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,10 @@ name: test on: + workflow_dispatch: pull_request: - paths: - - "!*.md" + push: + branches: + - master jobs: phptests: runs-on: ${{ matrix.operating-system }} @@ -35,9 +37,6 @@ jobs: - name: Install dependencies run: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - name: Install Laravel Illuminate Contracts - run: composer require "illuminate/contracts" - - name: PHPUnit Test run: vendor/bin/phpunit --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings env: diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9b6595c..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changelog - -All notable changes to godruoyi/php-snowflake are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. - -## [1.1.0] - 09.04.2021 - -### Added - -- [#19](https://github.com/godruoyi/php-snowflake/issues/19): Added support for sonyflake Snowflake algorithm. Supported values are: - -### Changed - -- Add sonyflake support. -- [#19](https://github.com/godruoyi/php-snowflake/issues/19): 能否增加索尼雪花算法。 diff --git a/README-zh_CN.md b/README-zh_CN.md index 197927c..24c7cd3 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -46,6 +46,7 @@ Snowflake 是 Twitter 内部的一个 ID 生算法,可以通过一些简单的 * RandomSequenceResolver(随机生成) * RedisSequenceResolver (基于 redis psetex 和 incrby 生成) +* PredisSequenceResolver (基于 redis psetex 和 incrby 生成) * LaravelSequenceResolver(基于 redis psetex 和 incrby 生成) * SwooleSequenceResolver(基于 swoole_lock 锁) * FileLockResolver(基于 PHP 文件锁) diff --git a/README.md b/README.md index f2aa8a2..32bfb9c 100644 --- a/README.md +++ b/README.md @@ -28,31 +28,22 @@ Snowflake & Sonyflake algorithm PHP implementation [中文文档](https://github ![file](https://images.godruoyi.com/logos/201908/13/_1565672621_LPW65Pi8cG.png) -Snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees. +Snowflake is a network service that generates unique ID numbers at high scale with simple guarantees. -* The first bit is unused sign bit. -* The second part consists of a 41-bit timestamp (milliseconds) whose value is the offset of the current time relative to a certain time. -* The 5 bits of the third and fourth parts represent data center and worker, and max value is 2^5 -1 = 31. -* The last part consists of 12 bits, its means the length of the serial number generated per millisecond per working node, a maximum of 2^12 -1 = 4095 IDs can be generated in the same millisecond. -* In a distributed environment, five-bit datacenter and worker mean that can deploy 31 datacenters, and each datacenter can deploy up to 31 nodes. -* The binary length of 41 bits is at most 2^41 -1 millisecond = 69 years. So the snowflake algorithm can be used for up to 69 years, In order to maximize the use of the algorithm, you should specify a start time for it. +1. The first bit is an unused sign bit. +2. The second part consists of a 41-bit timestamp (in milliseconds) representing the offset of the current time relative to a certain reference time. +3. The third and fourth parts are represented by 5 bits each, indicating the data centerID and workerID. The maximum value for both is 31 (2^5 -1). +4. The last part consists of 12 bits, which represents the length of the serial number generated per millisecond per working node. A maximum of 4095 IDs can be generated in the same millisecond (2^12 -1). -> You must know, The ID generated by the snowflake algorithm is not guaranteed to be unique. -> For example, when two different requests enter the same node of the same data center at the same time, and the sequence generated by the node is the same, the generated ID will be duplicated. +If you want to generate unique IDs using the snowflake algorithm, you must ensure that sequence numbers generated within the same millisecond on the same node are unique. +Based on this requirement, we have created this package which integrates multiple sequence number providers. -If you want to use the snowflake algorithm to generate unique ID, You must ensure: The sequence-number generated in the same millisecond of the same node is unique. -Based on this, we created this package and integrated multiple sequence-number providers into it. - -* RandomSequenceResolver (Random) -* FileLockResolver(PHP file lock `fopen/flock`, **Concurrency Safety**) -* RedisSequenceResolver (based on redis psetex and incrby, **Concurrency Safety**) -* LaravelSequenceResolver (based on redis psetex and incrby) -* SwooleSequenceResolver (based on swoole_lock) - -Each provider only needs to ensure that the serial number generated in the same millisecond is different. You can get a unique ID. - -> **Warning** -> The RandomSequenceResolver does not guarantee that the generated IDs are unique, If you want to generate a unique ID, please use another resolver instead. +* RandomSequenceResolver (Random Sequence Number, UnSafe) +* FileLockResolver (Uses PHP file lock `fopen/flock`, **Concurrency Safety**) +* RedisSequenceResolver (Redis psetex and incrby, **Concurrency safety**) +* PredisSequenceResolver (redis psetex and incrby, **Concurrency Safety**) +* LaravelSequenceResolver (Laravel Cache [add](https://github.com/laravel/framework/blob/11.x/src/Illuminate/Contracts/Cache/Repository.php#L39) lock mechanism) +* SwooleSequenceResolver (swoole_lock for **Concurrency Safety**) ## Requirement @@ -63,6 +54,15 @@ Each provider only needs to ensure that the serial number generated in the same ```shell $ composer require godruoyi/php-snowflake -vvv + +# Install `predis/predis` package if you are using PredisSequenceResolver +$ composer require "predis/predis" + +# Install `Redis` extensions if you are using RedisSequenceResolver +$ pecl install redis + +# Install `Swoole` extensions if you are using SwooleSequenceResolver +$ pecl install swoole ``` ## Usage @@ -93,6 +93,8 @@ $snowflake->setStartTimeStamp(strtotime('2019-09-09')*1000); // millisecond $snowflake->id(); ``` +> The maximum value of a 41-bit timestamp (in milliseconds) can represent up to 69 years, so the Snowflake algorithm can run safely for 69 years. In order to make the most of it, we recommend setting a start time. + 4. Use Sonyflake ```php @@ -104,7 +106,7 @@ $sonyflake->id(); 1. Used in Laravel. -Because the SDK is relatively simple, we don't provide an extension for Laravel. You can quickly integrate it into Laravel in the following way. +Since the SDK is quite straightforward, we do not offer a specific extension for Laravel. However, you can easily integrate it into your Laravel project by following these steps. ```php // App\Providers\AppServiceProvider @@ -132,7 +134,7 @@ class AppServiceProvider extends ServiceProvider 2. Custom -You can customize the sequence-number resolver by implementing the Godruoyi\Snowflake\SequenceResolver interface. +To customize the sequence number resolver, you need to implement the Godruoyi\Snowflake\SequenceResolver interface. ```php class YourSequence implements SequenceResolver @@ -153,7 +155,7 @@ $snowflake->setSequenceResolver(new YourSequence); $snowflake->id(); ``` -And you can use closure: +And you also can use the Closure: ```php $snowflake = new \Godruoyi\Snowflake\Snowflake; diff --git a/composer.json b/composer.json index 8c1a4c6..ac5e8b7 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,11 @@ "require-dev": { "phpunit/phpunit": "^10", "laravel/pint": "^1.10", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.10", + "illuminate/contracts": "^10.0 || ^11.0", + "predis/predis": "^2.0", + "ext-redis": "*", + "ext-swoole": "*" }, "autoload-dev": { "psr-4": { @@ -38,7 +42,8 @@ }, "scripts": { "test": "vendor/bin/phpunit", - "pint": "vendor/bin/pint" + "phpstan": "vendor/bin/phpstan", + "pint": "vendor/bin/pint --config pint.json" }, "config": { "allow-plugins": { diff --git a/pint.json b/pint.json index 6e60942..0b31a88 100644 --- a/pint.json +++ b/pint.json @@ -7,6 +7,9 @@ "php_unit_method_casing": { "case": "snake_case" }, - "no_superfluous_phpdoc_tags": false + "no_superfluous_phpdoc_tags": false, + "heredoc_indentation": { + "indentation": "start_plus_one" + } } } \ No newline at end of file diff --git a/src/FileLockResolver.php b/src/FileLockResolver.php index f63ffd9..9e77084 100644 --- a/src/FileLockResolver.php +++ b/src/FileLockResolver.php @@ -114,7 +114,6 @@ protected function unlock($f): void /** * @param array $contents * @param resource $f - * @return bool */ public function updateContents(array $contents, $f): bool { diff --git a/src/LaravelSequenceResolver.php b/src/LaravelSequenceResolver.php index 45e29dc..f93a30b 100644 --- a/src/LaravelSequenceResolver.php +++ b/src/LaravelSequenceResolver.php @@ -24,7 +24,7 @@ class LaravelSequenceResolver implements SequenceResolver /** * Init resolve instance, must be connected. */ - public function __construct(protected Repository $cache) // @phpstan-ignore-line + public function __construct(protected Repository $cache) { } @@ -32,13 +32,11 @@ public function sequence(int $currentTime): int { $key = $this->prefix.$currentTime; - // @phpstan-ignore-next-line if ($this->cache->add($key, 1, 10)) { return 0; } - // @phpstan-ignore-next-line - return $this->cache->increment($key, 1); + return $this->cache->increment($key) | 0; } public function setCachePrefix(string $prefix): self diff --git a/src/PredisSequenceResolver.php b/src/PredisSequenceResolver.php new file mode 100644 index 0000000..18add13 --- /dev/null +++ b/src/PredisSequenceResolver.php @@ -0,0 +1,53 @@ + + * + * This source file is subject to the MIT license that is bundled. + */ + +namespace Godruoyi\Snowflake; + +use Predis\Client as PredisClient; + +class PredisSequenceResolver implements SequenceResolver +{ + /** + * The cache prefix. + */ + protected string $prefix = ''; + + /** + * The default redis lua script + */ + protected static string $script = <<<'LUA' + if redis.call('set', KEYS[1], ARGV[1], "EX", ARGV[2], "NX") then + return 0 + else + return redis.call('incr', KEYS[1]) + end + LUA; + + public function __construct(protected PredisClient $predisClient) + { + } + + public function sequence(int $currentTime): int + { + return $this->predisClient->eval(self::$script, 1, $this->prefix.$currentTime, '0', '10') | 0; + } + + /** + * Set cache prefix. + */ + public function setCachePrefix(string $prefix): self + { + $this->prefix = $prefix; + + return $this; + } +} diff --git a/src/RedisSequenceResolver.php b/src/RedisSequenceResolver.php index a0737ba..66f8b88 100644 --- a/src/RedisSequenceResolver.php +++ b/src/RedisSequenceResolver.php @@ -22,6 +22,17 @@ class RedisSequenceResolver implements SequenceResolver */ protected string $prefix = ''; + /** + * The default redis lua script + */ + protected static string $script = <<<'LUA' + if redis.call('set', KEYS[1], ARGV[1], "EX", ARGV[2], "NX") then + return 0 + else + return redis.call('incr', KEYS[1]) + end + LUA; + /** * Init resolve instance, must be connected. * @@ -39,16 +50,8 @@ public function __construct(protected Redis $redis) */ public function sequence(int $currentTime): int { - $lua = <<<'LUA' -if redis.call('set', KEYS[1], ARGV[1], "EX", ARGV[2], "NX") then - return 0 -else - return redis.call('incr', KEYS[1]) -end -LUA; - // 10 seconds - return $this->redis->eval($lua, [$this->prefix.$currentTime, '0', '10'], 1) | 0; + return $this->redis->eval(self::$script, [$this->prefix.$currentTime, '0', '10'], 1) | 0; } /** diff --git a/src/Snowflake.php b/src/Snowflake.php index c071e0e..9b9fcd7 100644 --- a/src/Snowflake.php +++ b/src/Snowflake.php @@ -38,8 +38,6 @@ class Snowflake /** * The Sequence Resolver instance. - * - * @var Closure|SequenceResolver|null */ protected SequenceResolver|null|Closure $sequence = null; diff --git a/src/Sonyflake.php b/src/Sonyflake.php index 71c7da0..4db96eb 100644 --- a/src/Sonyflake.php +++ b/src/Sonyflake.php @@ -25,7 +25,7 @@ class Sonyflake extends Snowflake /** * Build Sonyflake Instance. * - * @param int $machineId machine ID 0 ~ 65535 (2^16)-1 + * @param int $machineId machine ID 0 ~ 65535 (2^16)-1 */ public function __construct(protected int $machineId = 0) { diff --git a/tests/BatchSnowflakeIDTest.php b/tests/BatchSnowflakeIDTest.php index ab728bf..626e2a8 100644 --- a/tests/BatchSnowflakeIDTest.php +++ b/tests/BatchSnowflakeIDTest.php @@ -13,8 +13,10 @@ namespace Tests; use Godruoyi\Snowflake\FileLockResolver; +use Godruoyi\Snowflake\PredisSequenceResolver; use Godruoyi\Snowflake\RedisSequenceResolver; use Godruoyi\Snowflake\Snowflake; +use Predis\Client; use Throwable; class BatchSnowflakeIDTest extends TestCase @@ -36,16 +38,19 @@ public function test_batch_for_same_instance_with_default_driver(): void public function test_batch_for_diff_instance_with_default_driver(): void { $ids = []; - $count = 100000; // 10w + $count = 100_000; // 100k for ($i = 0; $i < $count; $i++) { $ids[(new Snowflake())->id()] = 1; } - $this->assertNotCount($count, $ids); + // This pattern will result in generating duplicate IDs. $this->assertGreaterThan(90000, count($ids)); } + /** + * @throws Throwable + */ public function test_batch_for_diff_instance_with_redis_driver() { if (! extension_loaded('redis') @@ -58,38 +63,73 @@ public function test_batch_for_diff_instance_with_redis_driver() $this->markTestSkipped('The pcntl extension is not installed.'); } - $this->parallelRun(function () { + $results = $this->parallelRun(function () { $redis = new \Redis(); $redis->connect(getenv('REDIS_HOST'), getenv('REDIS_PORT') | 0); return new RedisSequenceResolver($redis); }, 100, 1000); + + // Should generate 100k unique IDs + $this->assertResults($results, 100, 1000); } + public function test_batch_for_diff_instance_with_predis_driver() + { + if (! class_exists('Predis\\Client') + || ! getenv('REDIS_HOST') + || ! getenv('REDIS_PORT')) { + $this->markTestSkipped('Redis extension is not installed or not configured.'); + } + + if (! extension_loaded('pcntl')) { + $this->markTestSkipped('The pcntl extension is not installed.'); + } + + $results = $this->parallelRun(function () { + $client = new Client([ + 'scheme' => 'tcp', + 'host' => getenv('REDIS_HOST'), + 'port' => getenv('REDIS_PORT') | 0, + ]); + + $client->ping(); + + return new PredisSequenceResolver($client); + }, 100, 1000); + + // Should generate 100k unique IDs + $this->assertResults($results, 100, 1000); + } + + /** + * @throws Throwable + */ public function test_batch_for_diff_instance_with_file_driver() { $fileResolver = new FileLockResolver(__DIR__); - $this->parallelRun(function () use ($fileResolver) { + $results = $this->parallelRun(function () use ($fileResolver) { return $fileResolver; }, 100, 1000); + // Should generate 100k unique IDs + $this->assertResults($results, 100, 1000); + $fileResolver->cleanAllLocksFile(); } /** * Runs the given function in parallel using the specified number of processes. * - * @param callable $resolver - * @param int $parallel The number of processes to run in parallel. - * @param int $count The number of times to run the function. - * @return void + * @param int $parallel The number of processes to run in parallel. + * @param int $count The number of times to run the function. * * @throws Throwable */ - protected function parallelRun(callable $resolver, int $parallel, int $count): void + protected function parallelRun(callable $resolver, int $parallel, int $count): array { - $results = Support\Parallel::run(function () use ($resolver, $count) { + return Support\Parallel::run(function () use ($resolver, $count) { $snowflake = (new Snowflake(0, 0)) ->setSequenceResolver($resolver()) ->setStartTimeStamp(strtotime('2022-12-14') * 1000); @@ -101,17 +141,14 @@ protected function parallelRun(callable $resolver, int $parallel, int $count): v return $ids; }, $parallel); - - $this->assertResults($results, $parallel, $count); } /** * Asserts the results of a parallel execution. * - * @param array $results The array of results. - * @param int $parallel The number of parallel executions. - * @param int $count The expected count for each execution. - * @return void + * @param array $results The array of results. + * @param int $parallel The number of parallel executions. + * @param int $count The expected count for each execution. */ private function assertResults(array $results, int $parallel, int $count): void { diff --git a/tests/DiffWorkIdBatchTest.php b/tests/DiffWorkIdBatchTest.php index dd63a75..3e1e615 100644 --- a/tests/DiffWorkIdBatchTest.php +++ b/tests/DiffWorkIdBatchTest.php @@ -36,6 +36,6 @@ public function test_diss_work_id(): void $ids[$id] = 1; } - $this->assertTrue(20000 === count($ids)); + $this->assertTrue(count($ids) === 20000); } } diff --git a/tests/FileLockResolverTest.php b/tests/FileLockResolverTest.php index 0ab9171..b36706b 100644 --- a/tests/FileLockResolverTest.php +++ b/tests/FileLockResolverTest.php @@ -369,9 +369,8 @@ private function touch($content = '') private function prepareLockPath(): array { $dir = dirname(__DIR__).'/.locks'; - if (! is_dir($dir)) { - mkdir($dir, 0777); - } + rmdir($dir); + mkdir($dir, 0777); return [$dir, fn () => rmdir($dir)]; } diff --git a/tests/PredisSequenceResolverTest.php b/tests/PredisSequenceResolverTest.php new file mode 100644 index 0000000..e0ef02f --- /dev/null +++ b/tests/PredisSequenceResolverTest.php @@ -0,0 +1,90 @@ + + * + * This source file is subject to the MIT license that is bundled. + */ + +namespace Tests; + +use Godruoyi\Snowflake\PredisSequenceResolver; +use PHPUnit\Framework\MockObject\Exception; +use Predis\Client; +use ReflectionException; + +class PredisSequenceResolverTest extends TestCase +{ + public function setUp(): void + { + if (! class_exists('Predis\\Client')) { + $this->markTestSkipped('Predis extension is not installed'); + } + } + + /** + * @throws ReflectionException + * @throws Exception + */ + public function test_set_cache_prefix(): void + { + $redis = $this->createMock(Client::class); + $snowflake = new PredisSequenceResolver($redis); + $snowflake->setCachePrefix('foo'); + + $this->assertEquals('foo', $this->invokeProperty($snowflake, 'prefix')); + } + + /** + * Test order sequence + * + * @throws Exception + */ + public function test_predis_sequence(): void + { + $redis = $this->createMock(Client::class); + $redis->expects($this->exactly(4)) + ->method('__call') + ->withAnyParameters() + ->willReturn(1, 2, 3, 4); + + $snowflake = new PredisSequenceResolver($redis); + + $this->assertEquals(1, $snowflake->sequence(1)); + $this->assertEquals(2, $snowflake->sequence(1)); + $this->assertEquals(3, $snowflake->sequence(1)); + $this->assertEquals(4, $snowflake->sequence(1)); + } + + public function test_real_redis_connect(): void + { + if (! ($host = getenv('REDIS_HOST')) || ! ($port = getenv('REDIS_PORT'))) { + $this->markTestSkipped('Redis host or port is not set, skip real redis test.'); + } + + $client = new Client([ + 'scheme' => 'tcp', + 'host' => $host, + 'port' => $port | 0, + ]); + + $client->ping(); + + $randomKey = random_int(0, 99999); + + $redisResolver = new PredisSequenceResolver($client); + + $this->assertEquals(0, $redisResolver->sequence($randomKey)); + $this->assertEquals(1, $redisResolver->sequence($randomKey)); + $this->assertEquals(2, $redisResolver->sequence($randomKey)); + $this->assertEquals(3, $redisResolver->sequence($randomKey)); + + sleep(11); + + $this->assertEquals(0, $redisResolver->sequence($randomKey)); + } +} diff --git a/tests/RedisSequenceResolverTest.php b/tests/RedisSequenceResolverTest.php index 2f19ed0..f318fd9 100644 --- a/tests/RedisSequenceResolverTest.php +++ b/tests/RedisSequenceResolverTest.php @@ -35,10 +35,10 @@ public function test_sequence(): void $snowflake = new RedisSequenceResolver($redis); - $this->assertTrue(0 == $snowflake->sequence(1)); - $this->assertTrue(1 == $snowflake->sequence(1)); - $this->assertTrue(2 == $snowflake->sequence(1)); - $this->assertTrue(3 == $snowflake->sequence(1)); + $this->assertTrue($snowflake->sequence(1) == 0); + $this->assertTrue($snowflake->sequence(1) == 1); + $this->assertTrue($snowflake->sequence(1) == 2); + $this->assertTrue($snowflake->sequence(1) == 3); } public function test_set_cache_prefix(): void @@ -68,15 +68,17 @@ public function test_real_redis(): void $redis = new \Redis(); $redis->connect($host, $port | 0); + $randomKey = random_int(0, 99999); + $redisResolver = new RedisSequenceResolver($redis); - $this->assertEquals(0, $redisResolver->sequence(1)); - $this->assertEquals(1, $redisResolver->sequence(1)); - $this->assertEquals(2, $redisResolver->sequence(1)); - $this->assertEquals(3, $redisResolver->sequence(1)); + $this->assertEquals(0, $redisResolver->sequence($randomKey)); + $this->assertEquals(1, $redisResolver->sequence($randomKey)); + $this->assertEquals(2, $redisResolver->sequence($randomKey)); + $this->assertEquals(3, $redisResolver->sequence($randomKey)); - sleep(10); + sleep(11); - $this->assertEquals(0, $redisResolver->sequence(1)); + $this->assertEquals(0, $redisResolver->sequence($randomKey)); } } diff --git a/tests/SnowflakeTest.php b/tests/SnowflakeTest.php index f533873..bcfb4d0 100644 --- a/tests/SnowflakeTest.php +++ b/tests/SnowflakeTest.php @@ -66,14 +66,14 @@ public function test_work_id_and_data_center_id(): void $this->assertTrue(! empty($snowflake->id())); $this->assertTrue(strlen($id = $snowflake->id()) <= 19); - $this->assertTrue(1 === $snowflake->parseId($id, true)['datacenter']); - $this->assertTrue(2 === $snowflake->parseId($id, true)['workerid']); + $this->assertTrue($snowflake->parseId($id, true)['datacenter'] === 1); + $this->assertTrue($snowflake->parseId($id, true)['workerid'] === 2); $snowflake = new Snowflake(999, 20); $id = $snowflake->id(); - $this->assertTrue(999 !== $snowflake->parseId($id, true)['datacenter']); - $this->assertTrue(20 === $snowflake->parseId($id, true)['workerid']); + $this->assertTrue($snowflake->parseId($id, true)['datacenter'] !== 999); + $this->assertTrue($snowflake->parseId($id, true)['workerid'] === 20); } public function test_extends(): void @@ -85,9 +85,9 @@ public function test_extends(): void $id = $snowflake->id(); - $this->assertTrue(999 !== $snowflake->parseId($id, true)['datacenter']); - $this->assertTrue(999 === $snowflake->parseId($id, true)['sequence']); - $this->assertTrue(20 === $snowflake->parseId($id, true)['workerid']); + $this->assertTrue($snowflake->parseId($id, true)['datacenter'] !== 999); + $this->assertTrue($snowflake->parseId($id, true)['sequence'] === 999); + $this->assertTrue($snowflake->parseId($id, true)['workerid'] === 20); } public function test_batch(): void @@ -116,7 +116,7 @@ public function test_batch(): void $datas[$id] = 1; } - $this->assertTrue(10000 === count($datas)); + $this->assertTrue(count($datas) === 10000); } public function test_parse_id(): void @@ -130,21 +130,21 @@ public function test_parse_id(): void $data = $snowflake->parseId('1537200202186752', true); - $this->assertTrue(0 === $data['workerid']); - $this->assertTrue(0 === $data['datacenter']); - $this->assertTrue(0 === $data['sequence']); + $this->assertTrue($data['workerid'] === 0); + $this->assertTrue($data['datacenter'] === 0); + $this->assertTrue($data['sequence'] === 0); $this->assertTrue($data['timestamp'] > 0); $snowflake = new Snowflake(2, 3); $id = $snowflake->id(); $payloads = $snowflake->parseId($id, true); - $this->assertTrue(2 === $payloads['datacenter']); - $this->assertTrue(3 === $payloads['workerid']); + $this->assertTrue($payloads['datacenter'] === 2); + $this->assertTrue($payloads['workerid'] === 3); $this->assertLessThan(Snowflake::MAX_SEQUENCE_SIZE, $payloads['sequence']); $payloads = $snowflake->parseId('0'); - $this->assertTrue('' == $payloads['timestamp'] || false == $payloads['timestamp']); + $this->assertTrue($payloads['timestamp'] == '' || $payloads['timestamp'] == false); $this->assertSame($payloads['workerid'], '0'); $this->assertSame($payloads['datacenter'], '0'); $this->assertSame($payloads['sequence'], '0'); @@ -164,7 +164,7 @@ public function test_set_start_time_stamp(): void $snowflake = new Snowflake(999, 20); $snowflake->setStartTimeStamp(1); - $this->assertTrue(1 === $snowflake->getStartTimeStamp()); + $this->assertTrue($snowflake->getStartTimeStamp() === 1); } public function test_set_start_time_stamp_max_value_is_over(): void @@ -193,7 +193,7 @@ public function test_get_start_time_stamp(): void $this->assertTrue($snowflake->getStartTimeStamp() === (strtotime($defaultTime) * 1000)); $snowflake->setStartTimeStamp(1); - $this->assertTrue(1 === $snowflake->getStartTimeStamp()); + $this->assertTrue($snowflake->getStartTimeStamp() === 1); } public function testcall_resolver(): void @@ -207,7 +207,7 @@ public function testcall_resolver(): void $seq = $snowflake->getSequenceResolver(); $this->assertTrue($seq instanceof Closure); - $this->assertTrue(999 === $seq(0)); + $this->assertTrue($seq(0) === 999); } public function test_get_sequence_resolver(): void diff --git a/tests/SonyflakeTest.php b/tests/SonyflakeTest.php index 07785d8..b886d68 100644 --- a/tests/SonyflakeTest.php +++ b/tests/SonyflakeTest.php @@ -87,7 +87,7 @@ public function test_parse_id(): void $this->assertArrayHasKey('sequence', $dumps); $this->assertArrayHasKey('machineid', $dumps); $this->assertArrayHasKey('timestamp', $dumps); - $this->assertTrue(110 == $dumps['machineid']); + $this->assertTrue($dumps['machineid'] == 110); } public function test_id(): void @@ -102,7 +102,7 @@ public function test_id(): void // $this->assertArrayNotHasKey($id, $datas); $datas[$id] = 1; } - $this->assertTrue(10000 === count($datas)); + $this->assertTrue(count($datas) === 10000); } /** @@ -169,15 +169,15 @@ public function test_get_start_time_stamp(): void $this->assertTrue($snowflake->getStartTimeStamp() === (strtotime($defaultTime) * 1000)); $snowflake->setStartTimeStamp(1); - $this->assertTrue(1 === $snowflake->getStartTimeStamp()); + $this->assertTrue($snowflake->getStartTimeStamp() === 1); } public function testget_current_millisecond(): void { $snowflake = new Sonyflake(9990); - $now = floor(microtime(true) * 1000) | 0; - $time = $snowflake->getCurrentMillisecond(); + $first = $snowflake->getCurrentMillisecond(); + $second = $snowflake->getCurrentMillisecond(); - $this->assertTrue($now - $time >= 0); + $this->assertTrue($second - $first >= 0); } } diff --git a/tests/Support/Parallel.php b/tests/Support/Parallel.php index ef84b39..c2381fd 100644 --- a/tests/Support/Parallel.php +++ b/tests/Support/Parallel.php @@ -20,9 +20,6 @@ final class Parallel /** * Run specified callback in parallel. * - * @param callable $callback - * @param int $parallel - * @return array * * @throws RuntimeException|Throwable */ @@ -46,8 +43,8 @@ public static function run(callable $callback, int $parallel = 100): array /** * Creates child processes to execute a callback function in parallel. * - * @param callable $callback The callback function to execute in each child process. - * @param int $parallel The number of child processes to create (default: 100). + * @param callable $callback The callback function to execute in each child process. + * @param int $parallel The number of child processes to create (default: 100). * @return array An array of child process information, including the process ID and the pipe. * * @throws RuntimeException If a child process cannot be created. @@ -87,9 +84,6 @@ private static function createChildProcess(callable $callback, int $parallel = 1 /** * Create pipelines with specified number, will fire a exception if failed. - * - * @param int $parallel - * @return array */ private static function createPipelines(int $parallel = 100): array { diff --git a/tests/SwooleSequenceResolverTest.php b/tests/SwooleSequenceResolverTest.php index 014b6c7..8614d37 100644 --- a/tests/SwooleSequenceResolverTest.php +++ b/tests/SwooleSequenceResolverTest.php @@ -21,20 +21,24 @@ public function setUp(): void if (version_compare(PHP_VERSION, '8.3') >= 0) { $this->markTestSkipped('Swoole does not yet support PHP 8.3'); } + + if (! extension_loaded('swoole')) { + $this->markTestSkipped('Swoole extension is not installed'); + } } public function test_basic(): void { $snowflake = new SwooleSequenceResolver(); - $this->assertTrue(0 == $snowflake->sequence(0)); - $this->assertTrue(1 == $snowflake->sequence(0)); - $this->assertTrue(2 == $snowflake->sequence(0)); - $this->assertTrue(3 == $snowflake->sequence(0)); + $this->assertTrue($snowflake->sequence(0) == 0); + $this->assertTrue($snowflake->sequence(0) == 1); + $this->assertTrue($snowflake->sequence(0) == 2); + $this->assertTrue($snowflake->sequence(0) == 3); - $this->assertTrue(0 == $snowflake->sequence(1)); - $this->assertTrue(1 == $snowflake->sequence(1)); - $this->assertTrue(2 == $snowflake->sequence(1)); + $this->assertTrue($snowflake->sequence(1) == 0); + $this->assertTrue($snowflake->sequence(1) == 1); + $this->assertTrue($snowflake->sequence(1) == 2); } public function test_reset_lock(): void diff --git a/tests/TestCase.php b/tests/TestCase.php index 5df85d3..04e9f72 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -13,16 +13,19 @@ namespace Tests; use PHPUnit\Framework\TestCase as BaseTestCase; +use ReflectionException; class TestCase extends BaseTestCase { /** * Call protected/private method of a class. * - * @param object &$object Instantiated object that we will run method on. - * @param string $methodName Method name to call - * @param array $parameters Array of parameters to pass into method. + * @param object &$object Instantiated object that we will run method on. + * @param string $methodName Method name to call + * @param array $parameters Array of parameters to pass into method. * @return mixed Method return. + * + * @throws ReflectionException */ public function invokeMethod(&$object, $methodName, array $parameters = []) { @@ -36,9 +39,11 @@ public function invokeMethod(&$object, $methodName, array $parameters = []) /** * Call protected/private method of a class. * - * @param object &$object Instantiated object that we will run method on. - * @param string $propertyName property name to call + * @param object &$object Instantiated object that we will run method on. + * @param string $propertyName property name to call * @return mixed Method return. + * + * @throws ReflectionException */ public function invokeProperty(&$object, string $propertyName) { diff --git a/tests/TimeTest.php b/tests/TimeTest.php index d4cdd36..89868ef 100644 --- a/tests/TimeTest.php +++ b/tests/TimeTest.php @@ -28,6 +28,7 @@ public function test_time(): void $s2 = $s->getCurrentMillisecond(); } $a++; + $this->assertTrue($s1 != $s2); } }