From 9c8713894f6cbb2881ae74bc357ca6d4c678b88f Mon Sep 17 00:00:00 2001 From: Lianbo Date: Tue, 12 Mar 2024 11:56:16 +0800 Subject: [PATCH 1/7] chore: improve unit test (#66) * chore: update laravel-point and reformat * chore: import test --- src/Sonyflake.php | 2 +- tests/BatchSnowflakeIDTest.php | 39 ++++++++++++++++------------ tests/DiffWorkIdBatchTest.php | 2 +- tests/RedisSequenceResolverTest.php | 8 +++--- tests/SnowflakeTest.php | 34 ++++++++++++------------ tests/SonyflakeTest.php | 6 ++--- tests/Support/Parallel.php | 10 ++----- tests/SwooleSequenceResolverTest.php | 14 +++++----- tests/TestCase.php | 15 +++++++---- tests/TimeTest.php | 3 ++- 10 files changed, 70 insertions(+), 63 deletions(-) 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..b17b7a2 100644 --- a/tests/BatchSnowflakeIDTest.php +++ b/tests/BatchSnowflakeIDTest.php @@ -36,16 +36,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 +61,45 @@ 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); } + /** + * @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 +111,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/RedisSequenceResolverTest.php b/tests/RedisSequenceResolverTest.php index 2f19ed0..9ec6150 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 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..63f22ef 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,7 +169,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 testget_current_millisecond(): void 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..f8612e9 100644 --- a/tests/SwooleSequenceResolverTest.php +++ b/tests/SwooleSequenceResolverTest.php @@ -27,14 +27,14 @@ 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..a96bb5a 100644 --- a/tests/TimeTest.php +++ b/tests/TimeTest.php @@ -28,7 +28,8 @@ public function test_time(): void $s2 = $s->getCurrentMillisecond(); } $a++; - $this->assertTrue($s1 != $s2); + + $this->assertEquals($s1, $s2); } } } From f23bf7856ad1f8cfefe9f4974bf3753f42700bb2 Mon Sep 17 00:00:00 2001 From: Lianbo Date: Tue, 12 Mar 2024 12:01:20 +0800 Subject: [PATCH 2/7] fix test workflow should be run in the main branch (#67) * fix test workflow should be run in the main branch * feat: support workflow_dispatch --- .github/workflows/codestyle.yml | 1 + .github/workflows/static-analysis.yml | 1 + .github/workflows/test.yml | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 5b4c473..579023f 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -3,6 +3,7 @@ on: pull_request: paths: - "!*.md" + workflow_dispatch: jobs: code-coverage: name: Code Coverage diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b6f8a8d..7e8f1d5 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,6 +1,7 @@ name: static analysis on: + workflow_dispatch: push: branches: - master diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24d128a..3a39deb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,8 +1,12 @@ name: test on: + workflow_dispatch: pull_request: paths: - "!*.md" + push: + branches: + - master jobs: phptests: runs-on: ${{ matrix.operating-system }} From dec895db6a9577e941ad4acfc1483b6a6c27b9e4 Mon Sep 17 00:00:00 2001 From: Lianbo Date: Tue, 12 Mar 2024 12:18:14 +0800 Subject: [PATCH 3/7] chore: fix test (#68) * chore: fix test * chore: remove filter in workflow --- .github/workflows/static-analysis.yml | 2 -- .github/workflows/test.yml | 2 -- tests/FileLockResolverTest.php | 5 ++--- tests/RedisSequenceResolverTest.php | 7 +++++++ tests/SonyflakeTest.php | 6 +++--- tests/SwooleSequenceResolverTest.php | 4 ++++ tests/TimeTest.php | 2 +- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 7e8f1d5..effeaf4 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -7,8 +7,6 @@ on: - master - '*.x' pull_request: - paths: - - "!*.md" permissions: contents: read diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3a39deb..b724e9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,8 +2,6 @@ name: test on: workflow_dispatch: pull_request: - paths: - - "!*.md" push: branches: - master 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/RedisSequenceResolverTest.php b/tests/RedisSequenceResolverTest.php index 9ec6150..1e7d75d 100644 --- a/tests/RedisSequenceResolverTest.php +++ b/tests/RedisSequenceResolverTest.php @@ -17,6 +17,13 @@ class RedisSequenceResolverTest extends TestCase { + public function setUp(): void + { + if (! extension_loaded('swoole')) { + $this->markTestSkipped('Redis extension is not installed'); + } + } + public function test_invalid_redis_connect(): void { $redis = $this->createMock(\Redis::class); diff --git a/tests/SonyflakeTest.php b/tests/SonyflakeTest.php index 63f22ef..b886d68 100644 --- a/tests/SonyflakeTest.php +++ b/tests/SonyflakeTest.php @@ -175,9 +175,9 @@ public function test_get_start_time_stamp(): void 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/SwooleSequenceResolverTest.php b/tests/SwooleSequenceResolverTest.php index f8612e9..8614d37 100644 --- a/tests/SwooleSequenceResolverTest.php +++ b/tests/SwooleSequenceResolverTest.php @@ -21,6 +21,10 @@ 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 diff --git a/tests/TimeTest.php b/tests/TimeTest.php index a96bb5a..89868ef 100644 --- a/tests/TimeTest.php +++ b/tests/TimeTest.php @@ -29,7 +29,7 @@ public function test_time(): void } $a++; - $this->assertEquals($s1, $s2); + $this->assertTrue($s1 != $s2); } } } From 79d099d178f4998142f4b2f41b043ae1d1e22560 Mon Sep 17 00:00:00 2001 From: Lianbo Date: Tue, 12 Mar 2024 12:31:15 +0800 Subject: [PATCH 4/7] chore: remove codestyle workflow filter (#69) * chore: remove codestyle workflow filter * fix redis test --- .github/workflows/codestyle.yml | 2 -- tests/RedisSequenceResolverTest.php | 15 ++++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 579023f..4f19856 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -1,8 +1,6 @@ name: codestyle on: pull_request: - paths: - - "!*.md" workflow_dispatch: jobs: code-coverage: diff --git a/tests/RedisSequenceResolverTest.php b/tests/RedisSequenceResolverTest.php index 1e7d75d..b58162f 100644 --- a/tests/RedisSequenceResolverTest.php +++ b/tests/RedisSequenceResolverTest.php @@ -13,6 +13,7 @@ namespace Tests; use Godruoyi\Snowflake\RedisSequenceResolver; +use Godruoyi\Snowflake\Snowflake; use RedisException; class RedisSequenceResolverTest extends TestCase @@ -75,15 +76,19 @@ public function test_real_redis(): void $redis = new \Redis(); $redis->connect($host, $port | 0); + // Sometimes running these tests in parallel on Github may cause unexpected errors, + // so we change to use random here. + $key = (new Snowflake())->getCurrentMillisecond(); + $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($key)); + $this->assertEquals(1, $redisResolver->sequence($key)); + $this->assertEquals(2, $redisResolver->sequence($key)); + $this->assertEquals(3, $redisResolver->sequence($key)); sleep(10); - $this->assertEquals(0, $redisResolver->sequence(1)); + $this->assertEquals(0, $redisResolver->sequence($key)); } } From b8dfbc304ce33e9cb513ebfd2e4b9b11d5aa8f02 Mon Sep 17 00:00:00 2001 From: Lianbo Date: Mon, 8 Apr 2024 12:11:37 +0800 Subject: [PATCH 5/7] feat: support predis (#72) * feat: support predis * add test for bulk generate id with predis resolver * Update composer.json Co-authored-by: Bilge * add new phpcsfix rule "heredoc_indentation" * chore: rename PHPREDIS -> Predis * chore: remove predis from composer * chore: update readme * chore: fix test * fix unit test ensure all cache are overdue --------- Co-authored-by: Bilge --- .github/workflows/codestyle.yml | 3 + .github/workflows/static-analysis.yml | 3 + .github/workflows/test.yml | 3 + README.md | 14 ++++- composer.json | 2 +- pint.json | 5 +- src/FileLockResolver.php | 1 - src/PredisSequenceResolver.php | 53 ++++++++++++++++ src/RedisSequenceResolver.php | 21 ++++--- src/Snowflake.php | 2 - tests/BatchSnowflakeIDTest.php | 30 +++++++++ tests/PredisSequenceResolverTest.php | 90 +++++++++++++++++++++++++++ tests/RedisSequenceResolverTest.php | 24 +++---- 13 files changed, 219 insertions(+), 32 deletions(-) create mode 100644 src/PredisSequenceResolver.php create mode 100644 tests/PredisSequenceResolverTest.php diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index 4f19856..b5b83ec 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -39,6 +39,9 @@ jobs: - name: Install Laravel Illuminate Contracts run: composer require "illuminate/contracts" + - name: Install Predis Package + run: composer require "predis/predis:^2.0" + - 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 effeaf4..2f53bee 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -38,5 +38,8 @@ jobs: max_attempts: 3 command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + - name: Install Predis Package + run: composer require "predis/predis:^2.0" + - name: Execute type checking run: vendor/bin/phpstan \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b724e9b..d167cbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,6 +40,9 @@ jobs: - name: Install Laravel Illuminate Contracts run: composer require "illuminate/contracts" + - name: Install Predis Package + run: composer require "predis/predis:^2.0" + - name: PHPUnit Test run: vendor/bin/phpunit --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings env: diff --git a/README.md b/README.md index f2aa8a2..8bb670b 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,23 @@ Based on this, we created this package and integrated multiple sequence-number p * 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) +* LaravelSequenceResolver (based on Laravel Cache [add](https://github.com/laravel/framework/blob/11.x/src/Illuminate/Contracts/Cache/Repository.php#L39) lock) * SwooleSequenceResolver (based on swoole_lock) +* PredisSequenceResolver (based on redis psetex and incrby, **Concurrency Safety**) Each provider only needs to ensure that the serial number generated in the same millisecond is different. You can get a unique ID. + +> [!NOTE] +> If you want to use RedisSequenceResolver, please install the [redis](https://pecl.php.net/package/redis) extension: +> pecl install redis +> +> If you want to use SwooleSequenceResolver, please install the swoole extension: +> pecl install swoole +> +> If you want to use PredisSequenceResolver, please install the [predis/predis](https://github.com/predis/predis) package: +> composer install predis/predis + > **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. diff --git a/composer.json b/composer.json index 8c1a4c6..09d82b5 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ }, "scripts": { "test": "vendor/bin/phpunit", - "pint": "vendor/bin/pint" + "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/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/tests/BatchSnowflakeIDTest.php b/tests/BatchSnowflakeIDTest.php index b17b7a2..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 @@ -72,6 +74,34 @@ public function test_batch_for_diff_instance_with_redis_driver() $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 */ 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 b58162f..f318fd9 100644 --- a/tests/RedisSequenceResolverTest.php +++ b/tests/RedisSequenceResolverTest.php @@ -13,18 +13,10 @@ namespace Tests; use Godruoyi\Snowflake\RedisSequenceResolver; -use Godruoyi\Snowflake\Snowflake; use RedisException; class RedisSequenceResolverTest extends TestCase { - public function setUp(): void - { - if (! extension_loaded('swoole')) { - $this->markTestSkipped('Redis extension is not installed'); - } - } - public function test_invalid_redis_connect(): void { $redis = $this->createMock(\Redis::class); @@ -76,19 +68,17 @@ public function test_real_redis(): void $redis = new \Redis(); $redis->connect($host, $port | 0); - // Sometimes running these tests in parallel on Github may cause unexpected errors, - // so we change to use random here. - $key = (new Snowflake())->getCurrentMillisecond(); + $randomKey = random_int(0, 99999); $redisResolver = new RedisSequenceResolver($redis); - $this->assertEquals(0, $redisResolver->sequence($key)); - $this->assertEquals(1, $redisResolver->sequence($key)); - $this->assertEquals(2, $redisResolver->sequence($key)); - $this->assertEquals(3, $redisResolver->sequence($key)); + $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($key)); + $this->assertEquals(0, $redisResolver->sequence($randomKey)); } } From ee1f3afb3b3d62a066ae3978ca24c2a789e8faec Mon Sep 17 00:00:00 2001 From: Lianbo Date: Mon, 8 Apr 2024 13:24:47 +0800 Subject: [PATCH 6/7] Add missing dependencies to dev (#73) * Add new dev-dependencies to composer.json The dependencies illuminate/contracts, predis/predis, ext-redis, and ext-swoole have been added to the "require-dev" section of composer.json file. This provides additional functionality for development purposes, including dependency injection, connection with Redis, and asynchronous tasks. * Update GitHub workflows and remove unnecessary packages This commit modifies the fail-fast strategy in the static analysis workflow and removes the installation of 'predis/predis' and 'illuminate/contracts' packages across multiple workflow files. This results in a cleaner setup, less dependency on external packages, and a more effective fail-fast configuration. Now, the workflows will use a matrix strategy for the PHP version, targeting specifically PHP * fix missing dependencies in action * fix php stan --- .github/workflows/codestyle.yml | 6 ------ .github/workflows/static-analysis.yml | 9 +++++---- .github/workflows/test.yml | 6 ------ composer.json | 7 ++++++- src/LaravelSequenceResolver.php | 6 ++---- 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index b5b83ec..4783104 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -36,12 +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: Install Predis Package - run: composer require "predis/predis:^2.0" - - 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 2f53bee..28eaa0b 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -16,7 +16,10 @@ jobs: runs-on: ubuntu-22.04 strategy: - fail-fast: true + fail-fast: false + matrix: + php-version: + - 8.2 name: Static Analysis @@ -30,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 @@ -38,8 +42,5 @@ jobs: max_attempts: 3 command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - name: Install Predis Package - run: composer require "predis/predis:^2.0" - - name: Execute type checking run: vendor/bin/phpstan \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d167cbc..49e9972 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,12 +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: Install Predis Package - run: composer require "predis/predis:^2.0" - - name: PHPUnit Test run: vendor/bin/phpunit --display-incomplete --display-skipped --display-deprecations --display-errors --display-notices --display-warnings env: diff --git a/composer.json b/composer.json index 09d82b5..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,6 +42,7 @@ }, "scripts": { "test": "vendor/bin/phpunit", + "phpstan": "vendor/bin/phpstan", "pint": "vendor/bin/pint --config pint.json" }, "config": { 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 From cbf44765679bd1e58f1c2ec0263eb340dd0d6f47 Mon Sep 17 00:00:00 2001 From: Lianbo Date: Mon, 8 Apr 2024 15:08:02 +0800 Subject: [PATCH 7/7] chore: remove files and update readme (#74) * chore: remove riles and update readmd * chore: update readme --- .gitattributes | 2 ++ CHANGELOG.md | 14 ----------- README-zh_CN.md | 1 + README.md | 64 +++++++++++++++++++++---------------------------- 4 files changed, 30 insertions(+), 51 deletions(-) delete mode 100644 CHANGELOG.md 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/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 8bb670b..32bfb9c 100644 --- a/README.md +++ b/README.md @@ -28,43 +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 Laravel Cache [add](https://github.com/laravel/framework/blob/11.x/src/Illuminate/Contracts/Cache/Repository.php#L39) lock) -* SwooleSequenceResolver (based on swoole_lock) -* PredisSequenceResolver (based on redis psetex and incrby, **Concurrency Safety**) - -Each provider only needs to ensure that the serial number generated in the same millisecond is different. You can get a unique ID. - - -> [!NOTE] -> If you want to use RedisSequenceResolver, please install the [redis](https://pecl.php.net/package/redis) extension: -> pecl install redis -> -> If you want to use SwooleSequenceResolver, please install the swoole extension: -> pecl install swoole -> -> If you want to use PredisSequenceResolver, please install the [predis/predis](https://github.com/predis/predis) package: -> composer install predis/predis - -> **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 @@ -75,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 @@ -105,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 @@ -116,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 @@ -144,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 @@ -165,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;