move repositories and models into own namespace, create interfaces

This commit is contained in:
Art4 2025-05-20 07:32:04 +00:00
parent e66568b78f
commit febcdd72c7
13 changed files with 301 additions and 194 deletions

View file

@ -11,7 +11,7 @@ use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Cache\Enum; use Friendica\Core\Cache\Enum;
use Friendica\Core\Cache\Exception\CachePersistenceException; use Friendica\Core\Cache\Exception\CachePersistenceException;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\Repository\CacheRepository; use Friendica\Repository\CacheRepository;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
/** /**

View file

@ -10,7 +10,8 @@ declare(strict_types=1);
namespace Friendica\Database; namespace Friendica\Database;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\Repository\UserDeletedRepository; use Friendica\Database\Repository\DeletedUserRepository;
use Friendica\Repository\UserdRepository;
final class DatabaseService final class DatabaseService
{ {
@ -21,8 +22,8 @@ final class DatabaseService
$this->database = $database; $this->database = $database;
} }
public function getUserDeletedRepository(): UserDeletedRepository public function getDeletedUserRepository(): DeletedUserRepository
{ {
return new UserDeletedRepository($this->database); return new UserdRepository($this->database);
} }
} }

View file

@ -9,74 +9,13 @@ declare(strict_types=1);
namespace Friendica\Database\Entity; namespace Friendica\Database\Entity;
use Exception;
use Friendica\Database\DBA;
/** /**
* Entity for a row in the cache table * Entity for a row in the cache table
*/ */
final class CacheEntity interface CacheEntity
{ {
/**
* Validates the row array and creates the entity
*
* @throws Exception If $data['v'] contains invalid data that could not unserialize.
*/
public static function createFromArray(array $data): self
{
$rawValue = array_key_exists('v', $data) ? (string) $data['v'] : '';
$value = @unserialize($rawValue);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($value === false && $rawValue !== 'b:0;' ) {
throw new Exception(sprintf('Invalid value data for cache object.'));
}
$entity = new self();
array_key_exists('k', $data) ?? $entity->k = (string) $data['k'];
$entity->v = $rawValue;
$entity->value = $value;
array_key_exists('expired', $data) ?? $entity->expired = (string) $data['expired'];
array_key_exists('updated', $data) ?? $entity->updated = (string) $data['updated'];
return $entity;
}
/**
* cache key
*/
private string $k = '';
/**
* cached serialized value
*/
private string $v = '';
/**
*
* @var mixed $value cached unserialized value
*/
private $value;
/**
* datetime of cache expiration
*/
private string $expired = DBA::NULL_DATETIME;
/**
* datetime of cache insertion
*/
private string $updated = DBA::NULL_DATETIME;
private function __construct() {}
/** /**
* @return mixed * @return mixed
*/ */
public function getValue() public function getValue();
{
return $this->value;
}
} }

View file

@ -9,125 +9,32 @@ declare(strict_types=1);
namespace Friendica\Database\Repository; namespace Friendica\Database\Repository;
use Friendica\Database\Database;
use Friendica\Database\DatabaseException; use Friendica\Database\DatabaseException;
use Friendica\Database\Entity\CacheEntity; use Friendica\Database\Entity\CacheEntity;
use Throwable;
/** /**
* Repository for cache table * Interface for a cache repository
*/ */
final class CacheRepository interface CacheRepository
{ {
private Database $database; /**
* @throws DatabaseException
public function __construct(Database $database) *
{ * @return array<string>
$this->database = $database; */
} public function getAllKeysValidUntil(string $expires): array;
/** /**
* @throws DatabaseException * @throws DatabaseException
* *
* @return array<string> * @return array<string>
*/ */
public function getAllKeysValidUntil(string $expires): array public function getAllKeysValidUntilWithPrefix(string $expires, string $prefix): array;
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
return $this->getAllKeys($expires, null);
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException('Cannot fetch all keys without prefix', 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntilWithPrefix(string $expires, string $prefix): array
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
return $this->getAllKeys($expires, $prefix);
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException(sprintf('Cannot fetch all keys with prefix `%s`', $prefix), 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/** /**
* @throws DatabaseException * @throws DatabaseException
* *
* @return CacheEntity|null * @return CacheEntity|null
*/ */
public function findOneByKeyValidUntil(string $key, string $expires) public function findOneByKeyValidUntil(string $key, string $expires);
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
$cacheArray = $this->database->selectFirst(
'cache',
['v'],
['`k` = ? AND (`expires` >= ? OR `expires` = -1)', $key, $expires]
);
if (!$this->database->isResult($cacheArray)) {
return null;
}
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException(sprintf('Cannot get cache entry with key `%s`', $key), 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
try {
$entity = CacheEntity::createFromArray($cacheArray);
} catch (Throwable $th) {
return null;
}
return $entity;
}
private function getAllKeys(string $expires, ?string $prefix = null): array
{
if ($prefix === null) {
$where = ['`expires` >= ?', $expires];
} else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', $expires, $prefix];
}
$stmt = $this->database->select('cache', ['k'], $where);
$keys = [];
try {
while ($key = $this->database->fetch($stmt)) {
array_push($keys, $key['k']);
}
} finally {
$this->database->close($stmt);
}
return $keys;
}
} }

View file

@ -0,0 +1,33 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Database\Repository;
use Exception;
use Friendica\Database\DatabaseException;
/**
* Interface for a repository for deleted users
*/
interface DeletedUserRepository
{
/**
* Insert a deleted user by username.
*
* @throws DatabaseException If the username could not be inserted
*/
public function insertByUsername(string $username): void;
/**
* Check if a deleted username exists.
*
* @throws \Exception
*/
public function existsByUsername(string $username): bool;
}

83
src/Model/CacheModel.php Normal file
View file

@ -0,0 +1,83 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Model;
use Exception;
use Friendica\Database\DBA;
use Friendica\Database\Entity\CacheEntity;
/**
* Model for a row in the cache table
*/
final class CacheModel implements CacheEntity
{
/**
* Validates the row array and creates the entity
*
* @throws Exception If $data['v'] contains invalid data that could not unserialize.
*/
public static function createFromArray(array $data): self
{
$rawValue = array_key_exists('v', $data) ? (string) $data['v'] : '';
$value = @unserialize($rawValue);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($value === false && $rawValue !== 'b:0;' ) {
throw new Exception(sprintf('Invalid value data for cache object.'));
}
$entity = new self();
array_key_exists('k', $data) ?? $entity->k = (string) $data['k'];
$entity->v = $rawValue;
$entity->value = $value;
array_key_exists('expired', $data) ?? $entity->expired = (string) $data['expired'];
array_key_exists('updated', $data) ?? $entity->updated = (string) $data['updated'];
return $entity;
}
/**
* cache key
*/
private string $k = '';
/**
* cached serialized value
*/
private string $v = '';
/**
*
* @var mixed $value cached unserialized value
*/
private $value;
/**
* datetime of cache expiration
*/
private string $expired = DBA::NULL_DATETIME;
/**
* datetime of cache insertion
*/
private string $updated = DBA::NULL_DATETIME;
private function __construct() {}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
}

View file

@ -266,7 +266,7 @@ class User
return $system_actor_name; return $system_actor_name;
} }
$userDeletedRepository = DI::databaseService()->getUserDeletedRepository(); $userDeletedRepository = DI::databaseService()->getDeletedUserRepository();
// List of possible actor names // List of possible actor names
$possible_accounts = ['friendica', 'actor', 'system', 'internal']; $possible_accounts = ['friendica', 'actor', 'system', 'internal'];
@ -1301,7 +1301,7 @@ class User
throw new Exception(DI::l10n()->t('Your nickname can only contain a-z, 0-9 and _.')); throw new Exception(DI::l10n()->t('Your nickname can only contain a-z, 0-9 and _.'));
} }
$userDeletedRepository = DI::databaseService()->getUserDeletedRepository(); $userDeletedRepository = DI::databaseService()->getDeletedUserRepository();
// Check existing and deleted accounts for this nickname. // Check existing and deleted accounts for this nickname.
if ( if (
@ -1816,7 +1816,7 @@ class User
$user = $hook_data['user'] ?? $user; $user = $hook_data['user'] ?? $user;
$userDeletedRepository = DI::databaseService()->getUserDeletedRepository(); $userDeletedRepository = DI::databaseService()->getDeletedUserRepository();
// save username (actually the nickname as it is guaranteed // save username (actually the nickname as it is guaranteed
// unique), so it cannot be re-registered in the future. // unique), so it cannot be re-registered in the future.

View file

@ -23,7 +23,7 @@ use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\Repository\UserDeletedRepository; use Friendica\Database\Repository\DeletedUserRepository;
use Friendica\Event\HtmlFilterEvent; use Friendica\Event\HtmlFilterEvent;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Profile as ProfileModel; use Friendica\Model\Profile as ProfileModel;
@ -48,7 +48,7 @@ class Profile extends BaseProfile
{ {
/** @var Database */ /** @var Database */
private $database; private $database;
private UserDeletedRepository $userDeletedRepository; private DeletedUserRepository $deletedUserRepository;
/** @var AppHelper */ /** @var AppHelper */
private $appHelper; private $appHelper;
/** @var IHandleUserSessions */ /** @var IHandleUserSessions */
@ -68,7 +68,7 @@ class Profile extends BaseProfile
IHandleUserSessions $session, IHandleUserSessions $session,
AppHelper $appHelper, AppHelper $appHelper,
Database $database, Database $database,
UserDeletedRepository $userDeletedRepository, DeletedUserRepository $deletedUserRepository,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
L10n $l10n, L10n $l10n,
BaseURL $baseUrl, BaseURL $baseUrl,
@ -82,7 +82,7 @@ class Profile extends BaseProfile
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->database = $database; $this->database = $database;
$this->userDeletedRepository = $userDeletedRepository; $this->deletedUserRepository = $deletedUserRepository;
$this->appHelper = $appHelper; $this->appHelper = $appHelper;
$this->session = $session; $this->session = $session;
$this->config = $config; $this->config = $config;
@ -106,7 +106,7 @@ class Profile extends BaseProfile
} }
} }
if ($this->userDeletedRepository->existsByUsername($this->parameters['nickname'])) { if ($this->deletedUserRepository->existsByUsername($this->parameters['nickname'])) {
// Known deleted user // Known deleted user
$data = ActivityPub\Transmitter::getDeletedUser($this->parameters['nickname']); $data = ActivityPub\Transmitter::getDeletedUser($this->parameters['nickname']);

View file

@ -20,7 +20,7 @@ use Friendica\Core\Worker;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\DBStructure; use Friendica\Database\DBStructure;
use Friendica\Database\Repository\UserDeletedRepository; use Friendica\Database\Repository\DeletedUserRepository;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Module\Response; use Friendica\Module\Response;
@ -49,7 +49,7 @@ class Import extends \Friendica\BaseModule
/** @var Database */ /** @var Database */
private $database; private $database;
private UserDeletedRepository $userDeletedRepository; private DeletedUserRepository $deletedUserRepository;
/** @var PermissionSet */ /** @var PermissionSet */
private $permissionSet; private $permissionSet;
@ -62,7 +62,7 @@ class Import extends \Friendica\BaseModule
PermissionSet $permissionSet, PermissionSet $permissionSet,
IManagePersonalConfigValues $pconfig, IManagePersonalConfigValues $pconfig,
Database $database, Database $database,
UserDeletedRepository $userDeletedRepository, DeletedUserRepository $deletedUserRepository,
SystemMessages $systemMessages, SystemMessages $systemMessages,
IManageConfigValues $config, IManageConfigValues $config,
L10n $l10n, L10n $l10n,
@ -80,7 +80,7 @@ class Import extends \Friendica\BaseModule
$this->pconfig = $pconfig; $this->pconfig = $pconfig;
$this->systemMessages = $systemMessages; $this->systemMessages = $systemMessages;
$this->database = $database; $this->database = $database;
$this->userDeletedRepository = $userDeletedRepository; $this->deletedUserRepository = $deletedUserRepository;
$this->permissionSet = $permissionSet; $this->permissionSet = $permissionSet;
$this->session = $session; $this->session = $session;
} }
@ -233,7 +233,7 @@ class Import extends \Friendica\BaseModule
// check for username // check for username
// check if username matches deleted account // check if username matches deleted account
if ($this->database->exists('user', ['nickname' => $account['user']['nickname']]) if ($this->database->exists('user', ['nickname' => $account['user']['nickname']])
|| $this->userDeletedRepository->existsByUsername($account['user']['nickname'])) { || $this->deletedUserRepository->existsByUsername($account['user']['nickname'])) {
$this->systemMessages->addNotice($this->t("User '%s' already exists on this server!", $account['user']['nickname'])); $this->systemMessages->addNotice($this->t("User '%s' already exists on this server!", $account['user']['nickname']));
return; return;
} }

View file

@ -0,0 +1,135 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Repository;
use Friendica\Database\Database;
use Friendica\Database\DatabaseException;
use Friendica\Database\Entity\CacheEntity;
use Friendica\Database\Repository\CacheRepository as CacheRepositoryInterface;
use Friendica\Model\CacheModel;
use Throwable;
/**
* Repository for cache table
*/
final class CacheRepository implements CacheRepositoryInterface
{
private Database $database;
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntil(string $expires): array
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
return $this->getAllKeys($expires, null);
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException('Cannot fetch all keys without prefix', 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntilWithPrefix(string $expires, string $prefix): array
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
return $this->getAllKeys($expires, $prefix);
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException(sprintf('Cannot fetch all keys with prefix `%s`', $prefix), 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/**
* @throws DatabaseException
*
* @return CacheEntity|null
*/
public function findOneByKeyValidUntil(string $key, string $expires)
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
$cacheArray = $this->database->selectFirst(
'cache',
['v'],
['`k` = ? AND (`expires` >= ? OR `expires` = -1)', $key, $expires]
);
if (!$this->database->isResult($cacheArray)) {
return null;
}
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException(sprintf('Cannot get cache entry with key `%s`', $key), 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
try {
$entity = CacheModel::createFromArray($cacheArray);
} catch (Throwable $th) {
return null;
}
return $entity;
}
private function getAllKeys(string $expires, ?string $prefix = null): array
{
if ($prefix === null) {
$where = ['`expires` >= ?', $expires];
} else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', $expires, $prefix];
}
$stmt = $this->database->select('cache', ['k'], $where);
$keys = [];
try {
while ($key = $this->database->fetch($stmt)) {
array_push($keys, $key['k']);
}
} finally {
$this->database->close($stmt);
}
return $keys;
}
}

View file

@ -7,16 +7,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace Friendica\Database\Repository; namespace Friendica\Repository;
use Exception; use Exception;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DatabaseException; use Friendica\Database\DatabaseException;
use Friendica\Database\Repository\DeletedUserRepository;
/** /**
* Repository for deleted users * Repository for deleted users
*/ */
final class UserDeletedRepository final class UserdRepository implements DeletedUserRepository
{ {
private Database $database; private Database $database;

View file

@ -7,11 +7,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace Friendica\Test\Unit\Database\Repository; namespace Friendica\Test\Unit\Repository;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DatabaseException; use Friendica\Database\DatabaseException;
use Friendica\Database\Repository\CacheRepository; use Friendica\Repository\CacheRepository;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Throwable; use Throwable;

View file

@ -7,15 +7,23 @@
declare(strict_types=1); declare(strict_types=1);
namespace Friendica\Test\Unit\Database\Repository; namespace Friendica\Test\Unit\Repository;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DatabaseException; use Friendica\Database\DatabaseException;
use Friendica\Database\Repository\UserDeletedRepository; use Friendica\Database\Repository\DeletedUserRepository;
use Friendica\Repository\UserdRepository;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class UserDeletedRepositoryTest extends TestCase class UserdRepositoryTest extends TestCase
{ {
public function testImplementationOfInterfaces(): void
{
$repo = new UserdRepository($this->createMock(Database::class));
$this->assertInstanceOf(DeletedUserRepository::class, $repo);
}
public function testInsertByUsernameCallsDatabase(): void public function testInsertByUsernameCallsDatabase(): void
{ {
$database = $this->createMock(Database::class); $database = $this->createMock(Database::class);
@ -23,7 +31,7 @@ class UserDeletedRepositoryTest extends TestCase
['userd', ['username' => 'test'], 0, true], ['userd', ['username' => 'test'], 0, true],
]); ]);
$repo = new UserDeletedRepository($database); $repo = new UserdRepository($database);
$repo->insertByUsername('test'); $repo->insertByUsername('test');
} }
@ -36,7 +44,7 @@ class UserDeletedRepositoryTest extends TestCase
new DatabaseException('An error occured.', 0, 'SQL query') new DatabaseException('An error occured.', 0, 'SQL query')
); );
$repo = new UserDeletedRepository($database); $repo = new UserdRepository($database);
$this->expectException(DatabaseException::class); $this->expectException(DatabaseException::class);
@ -50,7 +58,7 @@ class UserDeletedRepositoryTest extends TestCase
['userd', ['username' => 'test'], true], ['userd', ['username' => 'test'], true],
]); ]);
$repo = new UserDeletedRepository($database); $repo = new UserdRepository($database);
$this->assertTrue($repo->existsByUsername('test')); $this->assertTrue($repo->existsByUsername('test'));
} }
@ -62,7 +70,7 @@ class UserDeletedRepositoryTest extends TestCase
['userd', ['username' => 'test'], false], ['userd', ['username' => 'test'], false],
]); ]);
$repo = new UserDeletedRepository($database); $repo = new UserdRepository($database);
$this->assertFalse($repo->existsByUsername('test')); $this->assertFalse($repo->existsByUsername('test'));
} }