From f546daada7c69f04742c31e62f035984ba848aac Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Thu, 18 Dec 2025 20:25:59 +0100 Subject: [PATCH] refactor: Rename Snowflake Generator and Decoder And introduce the Snowflake DTO Signed-off-by: Anna Larch --- .../lib/OCM/CloudFederationProviderFiles.php | 4 +- apps/files_sharing/lib/External/Manager.php | 4 +- .../Command/CleanupRemoteStoragesTest.php | 4 +- .../tests/External/ManagerTest.php | 20 +++--- core/BackgroundJobs/MovePreviewJob.php | 4 +- core/Command/SnowflakeDecodeId.php | 18 ++--- lib/composer/composer/autoload_classmap.php | 9 +-- lib/composer/composer/autoload_static.php | 9 +-- lib/private/BackgroundJob/JobList.php | 6 +- lib/private/Preview/Db/PreviewMapper.php | 4 +- lib/private/Preview/Generator.php | 4 +- .../Preview/Storage/LocalPreviewStorage.php | 4 +- lib/private/PreviewManager.php | 4 +- lib/private/Server.php | 13 ++-- lib/private/Snowflake/APCuSequence.php | 8 +-- .../{Decoder.php => SnowflakeDecoder.php} | 30 +++++--- .../{Generator.php => SnowflakeGenerator.php} | 10 +-- .../{IDecoder.php => ISnowflakeDecoder.php} | 8 +-- ...IGenerator.php => ISnowflakeGenerator.php} | 2 +- lib/public/Snowflake/Snowflake.php | 72 +++++++++++++++++++ tests/lib/BackgroundJob/DummyJobList.php | 4 +- tests/lib/BackgroundJob/JobListTest.php | 8 +-- tests/lib/Preview/GeneratorTest.php | 6 +- tests/lib/Preview/MovePreviewJobTest.php | 8 +-- tests/lib/Preview/PreviewMapperTest.php | 6 +- tests/lib/Preview/PreviewServiceTest.php | 6 +- tests/lib/Snowflake/DecoderTest.php | 14 ++-- tests/lib/Snowflake/GeneratorTest.php | 34 ++++----- 28 files changed, 201 insertions(+), 122 deletions(-) rename lib/private/Snowflake/{Decoder.php => SnowflakeDecoder.php} (82%) rename lib/private/Snowflake/{Generator.php => SnowflakeGenerator.php} (92%) rename lib/public/Snowflake/{IDecoder.php => ISnowflakeDecoder.php} (70%) rename lib/public/Snowflake/{IGenerator.php => ISnowflakeGenerator.php} (97%) create mode 100644 lib/public/Snowflake/Snowflake.php diff --git a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php index 9121cb1f2a6..7229de5dadd 100644 --- a/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php +++ b/apps/federatedfilesharing/lib/OCM/CloudFederationProviderFiles.php @@ -44,7 +44,7 @@ use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IProviderFactory; use OCP\Share\IShare; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use OCP\Util; use Override; use Psr\Log\LoggerInterface; @@ -70,7 +70,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider { private readonly IFilenameValidator $filenameValidator, private readonly IProviderFactory $shareProviderFactory, private readonly SetupManager $setupManager, - private readonly IGenerator $snowflakeGenerator, + private readonly ISnowflakeGenerator $snowflakeGenerator, private readonly ExternalShareMapper $externalShareMapper, ) { } diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index 0958626b96d..56aac592efc 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -34,7 +34,7 @@ use OCP\IUserSession; use OCP\Notification\IManager; use OCP\OCS\IDiscoveryService; use OCP\Share\IShare; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Psr\Log\LoggerInterface; class Manager { @@ -57,7 +57,7 @@ class Manager { private SetupManager $setupManager, private ICertificateManager $certificateManager, private ExternalShareMapper $externalShareMapper, - private IGenerator $snowflakeGenerator, + private ISnowflakeGenerator $snowflakeGenerator, ) { $this->user = $userSession->getUser(); } diff --git a/apps/files_sharing/tests/Command/CleanupRemoteStoragesTest.php b/apps/files_sharing/tests/Command/CleanupRemoteStoragesTest.php index c105157b1a9..58c27d07c17 100644 --- a/apps/files_sharing/tests/Command/CleanupRemoteStoragesTest.php +++ b/apps/files_sharing/tests/Command/CleanupRemoteStoragesTest.php @@ -14,7 +14,7 @@ use OCP\Federation\ICloudId; use OCP\Federation\ICloudIdManager; use OCP\IDBConnection; use OCP\Server; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Console\Input\InputInterface; @@ -64,7 +64,7 @@ class CleanupRemoteStoragesTest extends TestCase { if (isset($storage['share_token'])) { $externalShare = new ExternalShare(); - $externalShare->setId(Server::get(IGenerator::class)->nextId()); + $externalShare->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $externalShare->setShareToken($storage['share_token']); $externalShare->setRemote($storage['remote']); $externalShare->setName('irrelevant'); diff --git a/apps/files_sharing/tests/External/ManagerTest.php b/apps/files_sharing/tests/External/ManagerTest.php index 64afb91b080..1c19ad8f894 100644 --- a/apps/files_sharing/tests/External/ManagerTest.php +++ b/apps/files_sharing/tests/External/ManagerTest.php @@ -40,7 +40,7 @@ use OCP\IUserSession; use OCP\OCS\IDiscoveryService; use OCP\Server; use OCP\Share\IShare; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Test\Traits\UserTrait; @@ -170,7 +170,7 @@ class ManagerTest extends TestCase { $this->setupManager, $this->certificateManager, $this->externalShareMapper, - Server::get(IGenerator::class), + Server::get(ISnowflakeGenerator::class), ] )->onlyMethods(['tryOCMEndPoint'])->getMock(); } @@ -190,7 +190,7 @@ class ManagerTest extends TestCase { public function testAddUserShare(): void { $userShare = new ExternalShare(); - $userShare->setId(Server::get(IGenerator::class)->nextId()); + $userShare->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $userShare->setRemote('http://localhost'); $userShare->setShareToken('token1'); $userShare->setPassword(''); @@ -205,7 +205,7 @@ class ManagerTest extends TestCase { public function testAddGroupShare(): void { $groupShare = new ExternalShare(); - $groupShare->setId(Server::get(IGenerator::class)->nextId()); + $groupShare->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $groupShare->setRemote('http://localhost'); $groupShare->setOwner('foobar'); $groupShare->setShareType(IShare::TYPE_GROUP); @@ -237,10 +237,10 @@ class ManagerTest extends TestCase { $shareData2 = $shareData1->clone(); $shareData2->setShareToken('token2'); - $shareData2->setId(\OCP\Server::get(IGenerator::class)->nextId()); + $shareData2->setId(\OCP\Server::get(ISnowflakeGenerator::class)->nextId()); $shareData3 = $shareData1->clone(); $shareData3->setShareToken('token3'); - $shareData3->setId(\OCP\Server::get(IGenerator::class)->nextId()); + $shareData3->setId(\OCP\Server::get(ISnowflakeGenerator::class)->nextId()); $this->setupMounts(); $this->assertNotMount('SharedFolder'); @@ -440,7 +440,7 @@ class ManagerTest extends TestCase { $user = $this->createMock(IUser::class); $user->expects($this->any())->method('getUID')->willReturn($userId); $share = new ExternalShare(); - $share->setId(Server::get(IGenerator::class)->nextId()); + $share->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $share->setRemote('http://localhost'); $share->setShareToken('token1'); $share->setPassword(''); @@ -460,7 +460,7 @@ class ManagerTest extends TestCase { */ private function createTestGroupShare(string $groupId = 'group1'): array { $share = new ExternalShare(); - $share->setId(Server::get(IGenerator::class)->nextId()); + $share->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $share->setRemote('http://localhost'); $share->setShareToken('token1'); $share->setPassword(''); @@ -646,7 +646,7 @@ class ManagerTest extends TestCase { // user 2 shares $manager2 = $this->createManagerForUser($user2); $share = new ExternalShare(); - $share->setId(Server::get(IGenerator::class)->nextId()); + $share->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $share->setRemote('http://localhost'); $share->setShareToken('token1'); $share->setPassword(''); @@ -696,7 +696,7 @@ class ManagerTest extends TestCase { $manager2 = $this->createManagerForUser($user); $share = new ExternalShare(); - $share->setId(Server::get(IGenerator::class)->nextId()); + $share->setId(Server::get(ISnowflakeGenerator::class)->nextId()); $share->setRemote('http://localhost'); $share->setShareToken('token1'); $share->setPassword(''); diff --git a/core/BackgroundJobs/MovePreviewJob.php b/core/BackgroundJobs/MovePreviewJob.php index 1442b22b327..6d461b89068 100644 --- a/core/BackgroundJobs/MovePreviewJob.php +++ b/core/BackgroundJobs/MovePreviewJob.php @@ -26,7 +26,7 @@ use OCP\Files\IRootFolder; use OCP\IAppConfig; use OCP\IConfig; use OCP\IDBConnection; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Override; use Psr\Log\LoggerInterface; @@ -45,7 +45,7 @@ class MovePreviewJob extends TimedJob { private readonly IMimeTypeDetector $mimeTypeDetector, private readonly IMimeTypeLoader $mimeTypeLoader, private readonly LoggerInterface $logger, - private readonly IGenerator $generator, + private readonly ISnowflakeGenerator $generator, IAppDataFactory $appDataFactory, ) { parent::__construct($time); diff --git a/core/Command/SnowflakeDecodeId.php b/core/Command/SnowflakeDecodeId.php index 3ce7c038ff7..e9993077aae 100644 --- a/core/Command/SnowflakeDecodeId.php +++ b/core/Command/SnowflakeDecodeId.php @@ -8,7 +8,7 @@ declare(strict_types=1); */ namespace OC\Core\Command; -use OCP\Snowflake\IDecoder; +use OCP\Snowflake\ISnowflakeDecoder; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -16,7 +16,7 @@ use Symfony\Component\Console\Output\OutputInterface; class SnowflakeDecodeId extends Base { public function __construct( - private readonly IDecoder $decoder, + private readonly ISnowflakeDecoder $decoder, ) { parent::__construct(); } @@ -36,13 +36,13 @@ class SnowflakeDecodeId extends Base { $rows = [ ['Snowflake ID', $snowflakeId], - ['Seconds', $data['seconds']], - ['Milliseconds', $data['milliseconds']], - ['Created from CLI', $data['isCli'] ? 'yes' : 'no'], - ['Server ID', $data['serverId']], - ['Sequence ID', $data['sequenceId']], - ['Creation timestamp', $data['createdAt']->format('U.v')], - ['Creation date', $data['createdAt']->format('Y-m-d H:i:s.v')], + ['Seconds', $data->getSeconds()], + ['Milliseconds', $data->getMilliseconds()], + ['Created from CLI', $data->isCli() ? 'yes' : 'no'], + ['Server ID', $data->getServerId()], + ['Sequence ID', $data->getSequenceId()], + ['Creation timestamp', $data->getCreatedAt()->format('U.v')], + ['Creation date', $data->getCreatedAt()->format('Y-m-d H:i:s.v')], ]; $table = new Table($output); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 7cca0c7a414..d71f34469d5 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -832,8 +832,9 @@ return array( 'OCP\\Share_Backend' => $baseDir . '/lib/public/Share_Backend.php', 'OCP\\Share_Backend_Collection' => $baseDir . '/lib/public/Share_Backend_Collection.php', 'OCP\\Share_Backend_File_Dependent' => $baseDir . '/lib/public/Share_Backend_File_Dependent.php', - 'OCP\\Snowflake\\IDecoder' => $baseDir . '/lib/public/Snowflake/IDecoder.php', - 'OCP\\Snowflake\\IGenerator' => $baseDir . '/lib/public/Snowflake/IGenerator.php', + 'OCP\\Snowflake\\ISnowflakeDecoder' => $baseDir . '/lib/public/Snowflake/ISnowflakeDecoder.php', + 'OCP\\Snowflake\\ISnowflakeGenerator' => $baseDir . '/lib/public/Snowflake/ISnowflakeGenerator.php', + 'OCP\\Snowflake\\Snowflake' => $baseDir . '/lib/public/Snowflake/Snowflake.php', 'OCP\\SpeechToText\\Events\\AbstractTranscriptionEvent' => $baseDir . '/lib/public/SpeechToText/Events/AbstractTranscriptionEvent.php', 'OCP\\SpeechToText\\Events\\TranscriptionFailedEvent' => $baseDir . '/lib/public/SpeechToText/Events/TranscriptionFailedEvent.php', 'OCP\\SpeechToText\\Events\\TranscriptionSuccessfulEvent' => $baseDir . '/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php', @@ -2127,10 +2128,10 @@ return array( 'OC\\Share\\Helper' => $baseDir . '/lib/private/Share/Helper.php', 'OC\\Share\\Share' => $baseDir . '/lib/private/Share/Share.php', 'OC\\Snowflake\\APCuSequence' => $baseDir . '/lib/private/Snowflake/APCuSequence.php', - 'OC\\Snowflake\\Decoder' => $baseDir . '/lib/private/Snowflake/Decoder.php', 'OC\\Snowflake\\FileSequence' => $baseDir . '/lib/private/Snowflake/FileSequence.php', - 'OC\\Snowflake\\Generator' => $baseDir . '/lib/private/Snowflake/Generator.php', 'OC\\Snowflake\\ISequence' => $baseDir . '/lib/private/Snowflake/ISequence.php', + 'OC\\Snowflake\\SnowflakeDecoder' => $baseDir . '/lib/private/Snowflake/SnowflakeDecoder.php', + 'OC\\Snowflake\\SnowflakeGenerator' => $baseDir . '/lib/private/Snowflake/SnowflakeGenerator.php', 'OC\\SpeechToText\\SpeechToTextManager' => $baseDir . '/lib/private/SpeechToText/SpeechToTextManager.php', 'OC\\SpeechToText\\TranscriptionJob' => $baseDir . '/lib/private/SpeechToText/TranscriptionJob.php', 'OC\\StreamImage' => $baseDir . '/lib/private/StreamImage.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c9fcc66a253..0f7609d13e1 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -873,8 +873,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Share_Backend' => __DIR__ . '/../../..' . '/lib/public/Share_Backend.php', 'OCP\\Share_Backend_Collection' => __DIR__ . '/../../..' . '/lib/public/Share_Backend_Collection.php', 'OCP\\Share_Backend_File_Dependent' => __DIR__ . '/../../..' . '/lib/public/Share_Backend_File_Dependent.php', - 'OCP\\Snowflake\\IDecoder' => __DIR__ . '/../../..' . '/lib/public/Snowflake/IDecoder.php', - 'OCP\\Snowflake\\IGenerator' => __DIR__ . '/../../..' . '/lib/public/Snowflake/IGenerator.php', + 'OCP\\Snowflake\\ISnowflakeDecoder' => __DIR__ . '/../../..' . '/lib/public/Snowflake/ISnowflakeDecoder.php', + 'OCP\\Snowflake\\ISnowflakeGenerator' => __DIR__ . '/../../..' . '/lib/public/Snowflake/ISnowflakeGenerator.php', + 'OCP\\Snowflake\\Snowflake' => __DIR__ . '/../../..' . '/lib/public/Snowflake/Snowflake.php', 'OCP\\SpeechToText\\Events\\AbstractTranscriptionEvent' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/Events/AbstractTranscriptionEvent.php', 'OCP\\SpeechToText\\Events\\TranscriptionFailedEvent' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/Events/TranscriptionFailedEvent.php', 'OCP\\SpeechToText\\Events\\TranscriptionSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php', @@ -2168,10 +2169,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Share\\Helper' => __DIR__ . '/../../..' . '/lib/private/Share/Helper.php', 'OC\\Share\\Share' => __DIR__ . '/../../..' . '/lib/private/Share/Share.php', 'OC\\Snowflake\\APCuSequence' => __DIR__ . '/../../..' . '/lib/private/Snowflake/APCuSequence.php', - 'OC\\Snowflake\\Decoder' => __DIR__ . '/../../..' . '/lib/private/Snowflake/Decoder.php', 'OC\\Snowflake\\FileSequence' => __DIR__ . '/../../..' . '/lib/private/Snowflake/FileSequence.php', - 'OC\\Snowflake\\Generator' => __DIR__ . '/../../..' . '/lib/private/Snowflake/Generator.php', 'OC\\Snowflake\\ISequence' => __DIR__ . '/../../..' . '/lib/private/Snowflake/ISequence.php', + 'OC\\Snowflake\\SnowflakeDecoder' => __DIR__ . '/../../..' . '/lib/private/Snowflake/SnowflakeDecoder.php', + 'OC\\Snowflake\\SnowflakeGenerator' => __DIR__ . '/../../..' . '/lib/private/Snowflake/SnowflakeGenerator.php', 'OC\\SpeechToText\\SpeechToTextManager' => __DIR__ . '/../../..' . '/lib/private/SpeechToText/SpeechToTextManager.php', 'OC\\SpeechToText\\TranscriptionJob' => __DIR__ . '/../../..' . '/lib/private/SpeechToText/TranscriptionJob.php', 'OC\\StreamImage' => __DIR__ . '/../../..' . '/lib/private/StreamImage.php', diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index a808c8231b1..ab7a1981419 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -16,7 +16,7 @@ use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Override; use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; @@ -34,7 +34,7 @@ class JobList implements IJobList { protected readonly IConfig $config, protected readonly ITimeFactory $timeFactory, protected readonly LoggerInterface $logger, - protected readonly IGenerator $generator, + protected readonly ISnowflakeGenerator $snowflakeGenerator, ) { } @@ -55,7 +55,7 @@ class JobList implements IJobList { if (!$this->has($job, $argument)) { $query->insert('jobs') ->values([ - 'id' => $query->createNamedParameter($this->generator->nextId()), + 'id' => $query->createNamedParameter($this->snowflakeGenerator->nextId()), 'class' => $query->createNamedParameter($class), 'argument' => $query->createNamedParameter($argumentJson), 'argument_hash' => $query->createNamedParameter(hash('sha256', $argumentJson)), diff --git a/lib/private/Preview/Db/PreviewMapper.php b/lib/private/Preview/Db/PreviewMapper.php index 2fe5c5afebf..3b8cdd358e4 100644 --- a/lib/private/Preview/Db/PreviewMapper.php +++ b/lib/private/Preview/Db/PreviewMapper.php @@ -15,7 +15,7 @@ use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\IMimeTypeLoader; use OCP\IDBConnection; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Override; /** @@ -30,7 +30,7 @@ class PreviewMapper extends QBMapper { public function __construct( IDBConnection $db, private readonly IMimeTypeLoader $mimeTypeLoader, - private readonly IGenerator $snowflake, + private readonly ISnowflakeGenerator $snowflake, ) { parent::__construct($db, self::TABLE_NAME, Preview::class); } diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php index 8980e0b2d00..42bcbf9c6e3 100644 --- a/lib/private/Preview/Generator.php +++ b/lib/private/Preview/Generator.php @@ -23,7 +23,7 @@ use OCP\IPreview; use OCP\IStreamImage; use OCP\Preview\BeforePreviewFetchedEvent; use OCP\Preview\IVersionedPreviewFile; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Psr\Log\LoggerInterface; class Generator { @@ -38,7 +38,7 @@ class Generator { private LoggerInterface $logger, private PreviewMapper $previewMapper, private StorageFactory $storageFactory, - private IGenerator $snowflakeGenerator, + private ISnowflakeGenerator $snowflakeGenerator, ) { } diff --git a/lib/private/Preview/Storage/LocalPreviewStorage.php b/lib/private/Preview/Storage/LocalPreviewStorage.php index 3cf626280b2..831382276ea 100644 --- a/lib/private/Preview/Storage/LocalPreviewStorage.php +++ b/lib/private/Preview/Storage/LocalPreviewStorage.php @@ -22,7 +22,7 @@ use OCP\Files\NotPermittedException; use OCP\IAppConfig; use OCP\IConfig; use OCP\IDBConnection; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Override; use Psr\Log\LoggerInterface; use RecursiveDirectoryIterator; @@ -39,7 +39,7 @@ class LocalPreviewStorage implements IPreviewStorage { private readonly IDBConnection $connection, private readonly IMimeTypeDetector $mimeTypeDetector, private readonly LoggerInterface $logger, - private readonly IGenerator $generator, + private readonly ISnowflakeGenerator $generator, ) { $this->instanceId = $this->config->getSystemValueString('instanceid'); $this->rootFolder = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data'); diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index 0e764100ad1..86c86bcbe56 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -23,7 +23,7 @@ use OCP\IBinaryFinder; use OCP\IConfig; use OCP\IPreview; use OCP\Preview\IProviderV2; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; use Psr\Log\LoggerInterface; @@ -142,7 +142,7 @@ class PreviewManager implements IPreview { $this->container->get(LoggerInterface::class), $this->container->get(PreviewMapper::class), $this->container->get(StorageFactory::class), - $this->container->get(IGenerator::class), + $this->container->get(ISnowflakeGenerator::class), ); } return $this->generator; diff --git a/lib/private/Server.php b/lib/private/Server.php index 51682ec3630..a4cf03cdc4f 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -117,10 +117,10 @@ use OC\SetupCheck\SetupCheckManager; use OC\Share20\ProviderFactory; use OC\Share20\ShareHelper; use OC\Snowflake\APCuSequence; -use OC\Snowflake\Decoder; use OC\Snowflake\FileSequence; -use OC\Snowflake\Generator; use OC\Snowflake\ISequence; +use OC\Snowflake\SnowflakeDecoder; +use OC\Snowflake\SnowflakeGenerator; use OC\SpeechToText\SpeechToTextManager; use OC\SystemTag\ManagerFactory as SystemTagManagerFactory; use OC\Talk\Broker; @@ -179,6 +179,7 @@ use OCP\IBinaryFinder; use OCP\ICache; use OCP\ICacheFactory; use OCP\ICertificateManager; +use OCP\IConfig; use OCP\IDateTimeFormatter; use OCP\IDateTimeZone; use OCP\IDBConnection; @@ -230,8 +231,8 @@ use OCP\Settings\IDeclarativeManager; use OCP\SetupCheck\ISetupCheckManager; use OCP\Share\IProviderFactory; use OCP\Share\IShareHelper; -use OCP\Snowflake\IDecoder; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeDecoder; +use OCP\Snowflake\ISnowflakeGenerator; use OCP\SpeechToText\ISpeechToTextManager; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; @@ -1266,7 +1267,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerAlias(ISignatureManager::class, SignatureManager::class); - $this->registerAlias(IGenerator::class, Generator::class); + $this->registerAlias(ISnowflakeGenerator::class, SnowflakeGenerator::class); $this->registerService(ISequence::class, function (ContainerInterface $c): ISequence { if (PHP_SAPI !== 'cli') { $sequence = $c->get(APCuSequence::class); @@ -1277,7 +1278,7 @@ class Server extends ServerContainer implements IServerContainer { return $c->get(FileSequence::class); }, false); - $this->registerAlias(IDecoder::class, Decoder::class); + $this->registerAlias(ISnowflakeDecoder::class, SnowflakeDecoder::class); $this->connectDispatcher(); } diff --git a/lib/private/Snowflake/APCuSequence.php b/lib/private/Snowflake/APCuSequence.php index e5aad4636ef..82cd1c46e8b 100644 --- a/lib/private/Snowflake/APCuSequence.php +++ b/lib/private/Snowflake/APCuSequence.php @@ -26,11 +26,7 @@ class APCuSequence implements ISequence { } $key = 'seq:' . $seconds . ':' . $milliseconds; - $sequenceId = apcu_inc($key, success: $success, ttl: 1); - if ($success === true) { - return $sequenceId; - } - - throw new \Exception('Failed to generate SnowflakeId with APCu'); + $sequenceId = apcu_inc($key, ttl: 1); + return $sequenceId === false ? throw new \Exception('Failed to generate SnowflakeId with APCu') : $sequenceId; } } diff --git a/lib/private/Snowflake/Decoder.php b/lib/private/Snowflake/SnowflakeDecoder.php similarity index 82% rename from lib/private/Snowflake/Decoder.php rename to lib/private/Snowflake/SnowflakeDecoder.php index e4ec72e50d2..565729cf55d 100644 --- a/lib/private/Snowflake/Decoder.php +++ b/lib/private/Snowflake/SnowflakeDecoder.php @@ -9,8 +9,9 @@ declare(strict_types=1); namespace OC\Snowflake; -use OCP\Snowflake\IDecoder; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeDecoder; +use OCP\Snowflake\ISnowflakeGenerator; +use OCP\Snowflake\Snowflake; use Override; /** @@ -20,9 +21,9 @@ use Override; * * @since 33.0.0 */ -final class Decoder implements IDecoder { +final class SnowflakeDecoder implements ISnowflakeDecoder { #[Override] - public function decode(string $snowflakeId): array { + public function decode(string $snowflakeId): Snowflake { if (!ctype_digit($snowflakeId)) { throw new \Exception('Invalid Snowflake ID: ' . $snowflakeId); } @@ -35,12 +36,19 @@ final class Decoder implements IDecoder { $data['createdAt'] = new \DateTimeImmutable( sprintf( '@%d.%03d', - $data['seconds'] + IGenerator::TS_OFFSET + intdiv($data['milliseconds'], 1000), + $data['seconds'] + ISnowflakeGenerator::TS_OFFSET + intdiv($data['milliseconds'], 1000), $data['milliseconds'] % 1000, ) ); - return $data; + return new Snowflake( + $data['serverId'], + $data['sequenceId'], + $data['isCli'], + $data['seconds'], + $data['milliseconds'], + $data['createdAt'], + ); } private function decode64bits(int $snowflakeId): array { @@ -51,11 +59,11 @@ final class Decoder implements IDecoder { $milliseconds = $secondHalf >> 22; return [ - 'seconds' => $seconds, - 'milliseconds' => $milliseconds, 'serverId' => ($secondHalf >> 13) & 0x1FF, 'sequenceId' => $secondHalf & 0xFFF, 'isCli' => (bool)(($secondHalf >> 12) & 0x1), + 'seconds' => $seconds, + 'milliseconds' => $milliseconds, ]; } @@ -88,12 +96,12 @@ final class Decoder implements IDecoder { $hex = ''; $digits = '0123456789ABCDEF'; - while (strlen($decimal) > 0 && $decimal !== '0') { + while ($decimal !== '' && $decimal !== '0') { $remainder = 0; $newDecimal = ''; // Perform division by 16 manually for arbitrary precision - for ($i = 0; $i < strlen($decimal); $i++) { + for ($i = 0, $iMax = strlen($decimal); $i < $iMax; $i++) { $digit = (int)$decimal[$i]; $current = $remainder * 10 + $digit; @@ -104,7 +112,7 @@ final class Decoder implements IDecoder { } else { $remainder = $current; // Only add quotient digit if we already have some digits in result - if (strlen($newDecimal) > 0) { + if ($newDecimal !== '') { $newDecimal .= '0'; } } diff --git a/lib/private/Snowflake/Generator.php b/lib/private/Snowflake/SnowflakeGenerator.php similarity index 92% rename from lib/private/Snowflake/Generator.php rename to lib/private/Snowflake/SnowflakeGenerator.php index 46e6e43b326..fcadc2a2a8c 100644 --- a/lib/private/Snowflake/Generator.php +++ b/lib/private/Snowflake/SnowflakeGenerator.php @@ -11,7 +11,7 @@ namespace OC\Snowflake; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Override; /** @@ -21,11 +21,11 @@ use Override; * * @since 33.0.0 */ -final class Generator implements IGenerator { +final readonly class SnowflakeGenerator implements ISnowflakeGenerator { public function __construct( - private readonly ITimeFactory $timeFactory, - private readonly IConfig $config, - private readonly ISequence $sequenceGenerator, + private ITimeFactory $timeFactory, + private IConfig $config, + private ISequence $sequenceGenerator, ) { } diff --git a/lib/public/Snowflake/IDecoder.php b/lib/public/Snowflake/ISnowflakeDecoder.php similarity index 70% rename from lib/public/Snowflake/IDecoder.php rename to lib/public/Snowflake/ISnowflakeDecoder.php index f75019981b1..020dd1f65d4 100644 --- a/lib/public/Snowflake/IDecoder.php +++ b/lib/public/Snowflake/ISnowflakeDecoder.php @@ -14,11 +14,11 @@ use OCP\AppFramework\Attribute\Consumable; /** * Nextcloud Snowflake ID decoder * - * @see \OCP\Snowflake\IGenerator for format + * @see \OCP\Snowflake\ISnowflakeGenerator for format * @since 33.0.0 */ #[Consumable(since: '33.0.0')] -interface IDecoder { +interface ISnowflakeDecoder { /** * Decode information contained into Snowflake ID * @@ -28,8 +28,8 @@ interface IDecoder { * - createdAt: timestamp at which ID was generated * - isCli: if ID was generated using CLI or not * - * @return array{createdAt: \DateTimeImmutable, serverId: int<0,1023>, sequenceId: int<0,4095>, isCli: bool, seconds: positive-int, milliseconds: int<0,999>} + * @return Snowflake * @since 33.0 */ - public function decode(string $snowflakeId): array; + public function decode(string $snowflakeId): Snowflake; } diff --git a/lib/public/Snowflake/IGenerator.php b/lib/public/Snowflake/ISnowflakeGenerator.php similarity index 97% rename from lib/public/Snowflake/IGenerator.php rename to lib/public/Snowflake/ISnowflakeGenerator.php index 2a465861f8d..8b7e030dbf3 100644 --- a/lib/public/Snowflake/IGenerator.php +++ b/lib/public/Snowflake/ISnowflakeGenerator.php @@ -25,7 +25,7 @@ use OCP\AppFramework\Attribute\Consumable; * @since 33.0.0 */ #[Consumable(since: '33.0.0')] -interface IGenerator { +interface ISnowflakeGenerator { /** * Offset applied on timestamps to keep it short diff --git a/lib/public/Snowflake/Snowflake.php b/lib/public/Snowflake/Snowflake.php new file mode 100644 index 00000000000..9fce0a5aea2 --- /dev/null +++ b/lib/public/Snowflake/Snowflake.php @@ -0,0 +1,72 @@ + $serverId + * @psalm-param int<0,4095> $sequenceId + * @psalm-param non-negative-int $seconds + * @psalm-param int<0,999> $milliseconds + */ + public function __construct( + private int $serverId, + private int $sequenceId, + private bool $isCli, + private int $seconds, + private int $milliseconds, + private \DateTimeImmutable $createdAt, + ) { + } + + /** + * @psalm-return int<0,1023> + */ + public function getServerId(): int { + return $this->serverId; + } + + /** + * @psalm-return int<0,4095> + */ + public function getSequenceId(): int { + return $this->sequenceId; + } + + public function isCli(): bool { + return $this->isCli; + } + + /** + * @psalm-return non-negative-int + */ + public function getSeconds(): int { + return $this->seconds; + } + + /** + * @psalm-return int<0,999> + */ + public function getMilliseconds(): int { + return $this->milliseconds; + } + + public function getCreatedAt(): \DateTimeImmutable { + return $this->createdAt; + } +} diff --git a/tests/lib/BackgroundJob/DummyJobList.php b/tests/lib/BackgroundJob/DummyJobList.php index 0bc64b3ee10..26187d82274 100644 --- a/tests/lib/BackgroundJob/DummyJobList.php +++ b/tests/lib/BackgroundJob/DummyJobList.php @@ -13,7 +13,7 @@ use OC\BackgroundJob\JobList; use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\Job; use OCP\Server; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; /** * Class DummyJobList @@ -46,7 +46,7 @@ class DummyJobList extends JobList { $job = Server::get($job); } $job->setArgument($argument); - $job->setId(Server::get(IGenerator::class)->nextId()); + $job->setId(Server::get(ISnowflakeGenerator::class)->nextId()); if (!$this->has($job, null)) { $this->jobs[] = $job; } diff --git a/tests/lib/BackgroundJob/JobListTest.php b/tests/lib/BackgroundJob/JobListTest.php index 4314bb19aa6..affd97a238a 100644 --- a/tests/lib/BackgroundJob/JobListTest.php +++ b/tests/lib/BackgroundJob/JobListTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016-2025 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -16,7 +16,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IConfig; use OCP\IDBConnection; use OCP\Server; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; @@ -46,7 +46,7 @@ class JobListTest extends TestCase { $this->config, $this->timeFactory, Server::get(LoggerInterface::class), - Server::get(IGenerator::class), + Server::get(ISnowflakeGenerator::class), ); } @@ -146,7 +146,7 @@ class JobListTest extends TestCase { if ($lastChecked === 0) { $lastChecked = time(); } - $id = Server::get(IGenerator::class)->nextId(); + $id = Server::get(ISnowflakeGenerator::class)->nextId(); $query = $this->connection->getQueryBuilder(); $query->insert('jobs') diff --git a/tests/lib/Preview/GeneratorTest.php b/tests/lib/Preview/GeneratorTest.php index 074a3272dbf..ee6b0ccb03a 100644 --- a/tests/lib/Preview/GeneratorTest.php +++ b/tests/lib/Preview/GeneratorTest.php @@ -22,7 +22,7 @@ use OCP\IPreview; use OCP\Preview\BeforePreviewFetchedEvent; use OCP\Preview\IProviderV2; use OCP\Preview\IVersionedPreviewFile; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\MockObject\MockObject; @@ -42,7 +42,7 @@ class GeneratorTest extends TestCase { private LoggerInterface&MockObject $logger; private StorageFactory&MockObject $storageFactory; private PreviewMapper&MockObject $previewMapper; - private IGenerator&MockObject $snowflakeGenerator; + private ISnowflakeGenerator&MockObject $snowflakeGenerator; protected function setUp(): void { parent::setUp(); @@ -54,7 +54,7 @@ class GeneratorTest extends TestCase { $this->logger = $this->createMock(LoggerInterface::class); $this->previewMapper = $this->createMock(PreviewMapper::class); $this->storageFactory = $this->createMock(StorageFactory::class); - $this->snowflakeGenerator = $this->createMock(IGenerator::class); + $this->snowflakeGenerator = $this->createMock(ISnowflakeGenerator::class); $this->generator = new Generator( $this->config, diff --git a/tests/lib/Preview/MovePreviewJobTest.php b/tests/lib/Preview/MovePreviewJobTest.php index ee1bb0f9ab4..b4b47c85983 100644 --- a/tests/lib/Preview/MovePreviewJobTest.php +++ b/tests/lib/Preview/MovePreviewJobTest.php @@ -24,7 +24,7 @@ use OCP\IAppConfig; use OCP\IConfig; use OCP\IDBConnection; use OCP\Server; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; @@ -124,7 +124,7 @@ class MovePreviewJobTest extends TestCase { $this->mimeTypeDetector, $this->mimeTypeLoader, $this->logger, - Server::get(IGenerator::class), + Server::get(ISnowflakeGenerator::class), Server::get(IAppDataFactory::class), ); $this->invokePrivate($job, 'run', [[]]); @@ -157,7 +157,7 @@ class MovePreviewJobTest extends TestCase { $this->mimeTypeDetector, $this->mimeTypeLoader, $this->logger, - Server::get(IGenerator::class), + Server::get(ISnowflakeGenerator::class), Server::get(IAppDataFactory::class) ); $this->invokePrivate($job, 'run', [[]]); @@ -198,7 +198,7 @@ class MovePreviewJobTest extends TestCase { $this->mimeTypeDetector, $this->mimeTypeLoader, $this->logger, - Server::get(IGenerator::class), + Server::get(ISnowflakeGenerator::class), Server::get(IAppDataFactory::class) ); $this->invokePrivate($job, 'run', [[]]); diff --git a/tests/lib/Preview/PreviewMapperTest.php b/tests/lib/Preview/PreviewMapperTest.php index 0a7fc6a5d78..9c379f27b2a 100644 --- a/tests/lib/Preview/PreviewMapperTest.php +++ b/tests/lib/Preview/PreviewMapperTest.php @@ -14,20 +14,20 @@ use OC\Preview\Db\Preview; use OC\Preview\Db\PreviewMapper; use OCP\IDBConnection; use OCP\Server; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use Test\TestCase; #[\PHPUnit\Framework\Attributes\Group('DB')] class PreviewMapperTest extends TestCase { private PreviewMapper $previewMapper; private IDBConnection $connection; - private IGenerator $snowflake; + private ISnowflakeGenerator $snowflake; public function setUp(): void { parent::setUp(); $this->previewMapper = Server::get(PreviewMapper::class); $this->connection = Server::get(IDBConnection::class); - $this->snowflake = Server::get(IGenerator::class); + $this->snowflake = Server::get(ISnowflakeGenerator::class); $qb = $this->connection->getQueryBuilder(); $qb->delete('preview_locations')->executeStatement(); diff --git a/tests/lib/Preview/PreviewServiceTest.php b/tests/lib/Preview/PreviewServiceTest.php index 6106dbb7a04..cd86719d1c4 100644 --- a/tests/lib/Preview/PreviewServiceTest.php +++ b/tests/lib/Preview/PreviewServiceTest.php @@ -14,7 +14,7 @@ use OC\Preview\Db\Preview; use OC\Preview\Db\PreviewMapper; use OC\Preview\PreviewService; use OCP\Server; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\TestCase; #[CoversClass(PreviewService::class)] @@ -22,13 +22,13 @@ use PHPUnit\Framework\TestCase; class PreviewServiceTest extends TestCase { private PreviewService $previewService; private PreviewMapper $previewMapper; - private IGenerator $snowflakeGenerator; + private ISnowflakeGenerator $snowflakeGenerator; protected function setUp(): void { parent::setUp(); $this->previewService = Server::get(PreviewService::class); $this->previewMapper = Server::get(PreviewMapper::class); - $this->snowflakeGenerator = Server::get(IGenerator::class); + $this->snowflakeGenerator = Server::get(ISnowflakeGenerator::class); $this->previewService->deleteAll(); } diff --git a/tests/lib/Snowflake/DecoderTest.php b/tests/lib/Snowflake/DecoderTest.php index 82035320c45..2c111a1b46f 100644 --- a/tests/lib/Snowflake/DecoderTest.php +++ b/tests/lib/Snowflake/DecoderTest.php @@ -7,7 +7,7 @@ namespace Test\Snowflake; -use OC\Snowflake\Decoder; +use OC\Snowflake\SnowflakeDecoder; use PHPUnit\Framework\Attributes\DataProvider; use Test\TestCase; @@ -15,10 +15,10 @@ use Test\TestCase; * @package Test */ class DecoderTest extends TestCase { - private Decoder $decoder; + private SnowflakeDecoder $decoder; public function setUp():void { - $this->decoder = new Decoder(); + $this->decoder = new SnowflakeDecoder(); } #[DataProvider('provideSnowflakeIds')] @@ -31,10 +31,10 @@ class DecoderTest extends TestCase { ): void { $data = $this->decoder->decode($snowflakeId); - $this->assertEquals($timestamp, (float)$data['createdAt']->format('U.v')); - $this->assertEquals($serverId, $data['serverId']); - $this->assertEquals($sequenceId, $data['sequenceId']); - $this->assertEquals($isCli, $data['isCli']); + $this->assertEquals($timestamp, (float)$data->getCreatedAt()->format('U.v')); + $this->assertEquals($serverId, $data->getServerId()); + $this->assertEquals($sequenceId, $data->getSequenceId()); + $this->assertEquals($isCli, $data->isCli()); } public static function provideSnowflakeIds(): array { diff --git a/tests/lib/Snowflake/GeneratorTest.php b/tests/lib/Snowflake/GeneratorTest.php index ce1160b2867..6d073e31343 100644 --- a/tests/lib/Snowflake/GeneratorTest.php +++ b/tests/lib/Snowflake/GeneratorTest.php @@ -10,12 +10,12 @@ declare(strict_types=1); namespace Test\Snowflake; use OC\AppFramework\Utility\TimeFactory; -use OC\Snowflake\Decoder; -use OC\Snowflake\Generator; use OC\Snowflake\ISequence; +use OC\Snowflake\SnowflakeDecoder; +use OC\Snowflake\SnowflakeGenerator; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; -use OCP\Snowflake\IGenerator; +use OCP\Snowflake\ISnowflakeGenerator; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; @@ -24,12 +24,12 @@ use Test\TestCase; * @package Test */ class GeneratorTest extends TestCase { - private Decoder $decoder; + private SnowflakeDecoder $decoder; private IConfig&MockObject $config; private ISequence&MockObject $sequence; public function setUp():void { - $this->decoder = new Decoder(); + $this->decoder = new SnowflakeDecoder(); $this->config = $this->createMock(IConfig::class); $this->config->method('getSystemValueInt') @@ -43,27 +43,27 @@ class GeneratorTest extends TestCase { } public function testGenerator(): void { - $generator = new Generator(new TimeFactory(), $this->config, $this->sequence); + $generator = new SnowflakeGenerator(new TimeFactory(), $this->config, $this->sequence); $snowflakeId = $generator->nextId(); $data = $this->decoder->decode($generator->nextId()); $this->assertIsString($snowflakeId); // Check timestamp - $this->assertGreaterThan(time() - 30, $data['createdAt']->format('U')); + $this->assertGreaterThan(time() - 30, $data->getCreatedAt()->format('U')); // Check serverId - $this->assertGreaterThanOrEqual(0, $data['serverId']); - $this->assertLessThanOrEqual(1023, $data['serverId']); + $this->assertGreaterThanOrEqual(0, $data->getServerId()); + $this->assertLessThanOrEqual(1023, $data->getServerId()); // Check sequenceId - $this->assertGreaterThanOrEqual(0, $data['sequenceId']); - $this->assertLessThanOrEqual(4095, $data['sequenceId']); + $this->assertGreaterThanOrEqual(0, $data->getSequenceId()); + $this->assertLessThanOrEqual(4095, $data->getSequenceId()); // Check CLI - $this->assertTrue($data['isCli']); + $this->assertTrue($data->isCli()); // Check serverId - $this->assertEquals(42, $data['serverId']); + $this->assertEquals(42, $data->getServerId()); } #[DataProvider('provideSnowflakeData')] @@ -72,12 +72,12 @@ class GeneratorTest extends TestCase { $timeFactory = $this->createMock(ITimeFactory::class); $timeFactory->method('now')->willReturn($dt); - $generator = new Generator($timeFactory, $this->config, $this->sequence); + $generator = new SnowflakeGenerator($timeFactory, $this->config, $this->sequence); $data = $this->decoder->decode($generator->nextId()); - $this->assertEquals($expectedSeconds, ($data['createdAt']->format('U') - IGenerator::TS_OFFSET)); - $this->assertEquals($expectedMilliseconds, (int)$data['createdAt']->format('v')); - $this->assertEquals(42, $data['serverId']); + $this->assertEquals($expectedSeconds, ($data->getCreatedAt()->format('U') - ISnowflakeGenerator::TS_OFFSET)); + $this->assertEquals($expectedMilliseconds, (int)$data->getCreatedAt()->format('v')); + $this->assertEquals(42, $data->getServerId()); } public static function provideSnowflakeData(): array {