nextcloud/lib/private/BackgroundJob/JobClassesRegistry.php
Benjamin Gaussorgues 8db8776f2d
feat(jobs): introduce background job classes registry
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
2026-05-29 09:14:48 +02:00

99 lines
2.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\BackgroundJob;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use InvalidArgumentException;
use OCP\BackgroundJob\IJob;
use OCP\IDBConnection;
use OCP\Snowflake\ISnowflakeGenerator;
/**
* Map background job classes and their ID in database
*/
final class JobClassesRegistry {
/**
* @var array<string,string>
*/
private array $registry = [];
private const TABLE = 'job_classes_registry';
public function __construct(
private readonly IDBConnection $connection,
private readonly ISnowflakeGenerator $snowflakeGenerator,
) {
}
private function loadRegistry(): void {
if ($this->registry !== []) {
return;
}
$qb = $this->connection->getQueryBuilder();
$result = $qb->select('class_id', 'class_name')->from(self::TABLE)->executeQuery();
foreach ($result->iterateAssociative() as $row) {
$this->registry[$row['class_name']] = (string)$row['class_id'];
}
}
/**
* Resolve current ID or generates a new one
*/
public function getId(string $className): string {
$this->loadRegistry();
if (isset($this->registry[$className])) {
return $this->registry[$className];
}
if (!class_exists($className)) {
throw new InvalidArgumentException('Class ' . $className . ' doesnt exists');
}
if (!is_a($className, IJob::class, true)) {
throw new InvalidArgumentException('Class ' . $className . ' isnt an instance of ' . IJob::class);
}
$qb = $this->connection->getQueryBuilder();
try {
$classId = $this->snowflakeGenerator->nextId();
$qb
->insert(self::TABLE)
->values([
'class_id' => $qb->createNamedParameter($classId),
'class_name' => $qb->createNamedParameter($className),
])
->executeStatement();
$this->registry[$className] = $classId;
return $classId;
} catch (UniqueConstraintViolationException $e) {
// Class was probably added by a concurrent process
// Try to load it
$result = $qb->select('class_id')->from(self::TABLE)->where($qb->expr()->eq('class_name', $className))->executeQuery();
if ($classId = $result->fetchOne()) {
$classId = (string)$classId;
$this->registry[$className] = $classId;
return $classId;
}
}
throw new \Exception('Fail to retrieve ' . $className . ' ID', previous: $e);
}
public function getName(string|int $classId): string {
$this->loadRegistry();
$classId = (string)$classId;
$className = array_search($classId, $this->registry, true);
if ($className === false) {
throw new InvalidArgumentException('Class ID ' . $classId . ' doesnt match any class name');
}
return $className;
}
}