Skip to content

Commit

Permalink
Finish porting expiration commands to use database services. (#3803)
Browse files Browse the repository at this point in the history
- Also includes a fix to login token expiration.

---------

Co-authored-by: Ere Maijala <ere.maijala@helsinki.fi>
  • Loading branch information
demiankatz and EreMaijala committed Jun 14, 2024
1 parent cc1950f commit 9408147
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 75 deletions.
15 changes: 15 additions & 0 deletions module/VuFind/src/VuFind/Db/Service/AccessTokenService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

namespace VuFind\Db\Service;

use DateTime;
use Laminas\Log\LoggerAwareInterface;
use VuFind\Db\Entity\AccessTokenEntityInterface;
use VuFind\Db\Table\AccessToken;
Expand All @@ -45,6 +46,7 @@
*/
class AccessTokenService extends AbstractDbService implements
AccessTokenServiceInterface,
Feature\DeleteExpiredInterface,
LoggerAwareInterface
{
use LoggerAwareTrait;
Expand Down Expand Up @@ -100,4 +102,17 @@ public function getNonce(int $userId): ?string
{
return $this->accessTokenTable->getNonce($userId);
}

/**
* Delete expired records. Allows setting a limit so that rows can be deleted in small batches.
*
* @param DateTime $dateLimit Date threshold of an "expired" record.
* @param ?int $limit Maximum number of rows to delete or null for no limit.
*
* @return int Number of rows deleted
*/
public function deleteExpired(DateTime $dateLimit, ?int $limit = null): int
{
return $this->accessTokenTable->deleteExpired($dateLimit->format('Y-m-d H:i:s'), $limit);
}
}
16 changes: 11 additions & 5 deletions module/VuFind/src/VuFind/Db/Service/LoginTokenService.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:database_gateways Wiki
*/
class LoginTokenService extends AbstractDbService implements LoginTokenServiceInterface, DbTableAwareInterface
class LoginTokenService extends AbstractDbService implements
LoginTokenServiceInterface,
Feature\DeleteExpiredInterface,
DbTableAwareInterface
{
use \VuFind\Db\Table\DbTableAwareTrait;

Expand Down Expand Up @@ -161,12 +164,15 @@ public function getBySeries(string $series): array
}

/**
* Remove expired login tokens.
* Delete expired records. Allows setting a limit so that rows can be deleted in small batches.
*
* @return void
* @param DateTime $dateLimit Date threshold of an "expired" record.
* @param ?int $limit Maximum number of rows to delete or null for no limit.
*
* @return int Number of rows deleted
*/
public function deleteExpired(): void
public function deleteExpired(DateTime $dateLimit, ?int $limit = null): int
{
$this->getDbTable('LoginToken')->deleteExpired();
return $this->getDbTable('LoginToken')->deleteExpired($dateLimit->format('Y-m-d H:i:s'), $limit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,4 @@ public function getByUser(UserEntityInterface|int $userOrId, bool $grouped = tru
* @return LoginTokenEntityInterface[]
*/
public function getBySeries(string $series): array;

/**
* Remove expired login tokens.
*
* @return void
*/
public function deleteExpired(): void;
}
19 changes: 18 additions & 1 deletion module/VuFind/src/VuFind/Db/Service/SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

namespace VuFind\Db\Service;

use DateTime;
use Exception;
use VuFind\Db\Entity\SearchEntityInterface;
use VuFind\Db\Entity\UserEntityInterface;
Expand All @@ -46,7 +47,10 @@
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:database_gateways Wiki
*/
class SearchService extends AbstractDbService implements SearchServiceInterface, DbTableAwareInterface
class SearchService extends AbstractDbService implements
SearchServiceInterface,
Feature\DeleteExpiredInterface,
DbTableAwareInterface
{
use DbTableAwareTrait;

Expand Down Expand Up @@ -243,4 +247,17 @@ public function getSavedSearchesWithMissingChecksums(): array
$searchWhere = ['checksum' => null, 'saved' => 1];
return iterator_to_array($this->getDbTable('search')->select($searchWhere));
}

/**
* Delete expired records. Allows setting a limit so that rows can be deleted in small batches.
*
* @param DateTime $dateLimit Date threshold of an "expired" record.
* @param ?int $limit Maximum number of rows to delete or null for no limit.
*
* @return int Number of rows deleted
*/
public function deleteExpired(DateTime $dateLimit, ?int $limit = null): int
{
return $this->getDbTable('search')->deleteExpired($dateLimit->format('Y-m-d H:i:s'), $limit);
}
}
19 changes: 18 additions & 1 deletion module/VuFind/src/VuFind/Db/Service/SessionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

namespace VuFind\Db\Service;

use DateTime;
use VuFind\Db\Entity\SessionEntityInterface;
use VuFind\Db\Table\DbTableAwareInterface;
use VuFind\Db\Table\DbTableAwareTrait;
Expand All @@ -44,7 +45,10 @@
* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License
* @link https://vufind.org/wiki/development:plugins:database_gateways Wiki
*/
class SessionService extends AbstractDbService implements DbTableAwareInterface, SessionServiceInterface
class SessionService extends AbstractDbService implements
DbTableAwareInterface,
SessionServiceInterface,
Feature\DeleteExpiredInterface
{
use DbTableAwareTrait;

Expand Down Expand Up @@ -123,4 +127,17 @@ public function createEntity(): SessionEntityInterface
{
return $this->getDbTable('Session')->createRow();
}

/**
* Delete expired records. Allows setting a limit so that rows can be deleted in small batches.
*
* @param DateTime $dateLimit Date threshold of an "expired" record.
* @param ?int $limit Maximum number of rows to delete or null for no limit.
*
* @return int Number of rows deleted
*/
public function deleteExpired(DateTime $dateLimit, ?int $limit = null): int
{
return $this->getDbTable('Session')->deleteExpired($dateLimit->format('Y-m-d H:i:s'), $limit);
}
}
18 changes: 12 additions & 6 deletions module/VuFind/src/VuFind/Db/Table/LoginToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
*/
class LoginToken extends Gateway
{
use ExpirationTrait;

/**
* Constructor
*
Expand Down Expand Up @@ -177,15 +179,19 @@ public function getBySeries(string $series): ResultSetInterface
}

/**
* Remove expired login tokens
* Update the select statement to find records to delete.
*
* @param Select $select Select clause
* @param string $dateLimit Date threshold of an "expired" record in format
* 'Y-m-d H:i:s'.
*
* @return void
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function deleteExpired(): void
protected function expirationCallback($select, $dateLimit)
{
$callback = function ($select) {
$select->where->lessThanOrEqualTo('expires', time());
};
$this->delete($callback);
// Date limit ignored since login token already contains an expiration time.
$select->where->lessThan('expires', time());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@

namespace VuFindAdmin\Controller;

use DateTime;
use Laminas\Cache\Psr\SimpleCache\SimpleCacheDecorator;
use Laminas\Log\LoggerInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
use VuFind\Cache\Manager as CacheManager;
use VuFind\Db\Service\Feature\DeleteExpiredInterface;
use VuFind\Db\Service\SearchServiceInterface;
use VuFind\Db\Service\SessionServiceInterface;
use VuFind\Http\GuzzleService;

use function ini_get;
Expand Down Expand Up @@ -188,7 +192,7 @@ public function deleteexpiredsearchesAction()
// database from old search histories that were not caught by the
// session garbage collector.
return $this->expire(
'Search',
SearchServiceInterface::class,
'%%count%% expired searches deleted.',
'No expired searches to delete.'
);
Expand All @@ -204,7 +208,7 @@ public function deleteexpiredsessionsAction()
// Delete the expired sessions--this cleans up any junk left in the
// database by the session garbage collector.
return $this->expire(
'Session',
SessionServiceInterface::class,
'%%count%% expired sessions deleted.',
'No expired sessions to delete.'
);
Expand All @@ -227,15 +231,15 @@ public function updatebrowscapcacheAction()
/**
* Abstract delete method.
*
* @param string $table Table to operate on.
* @param string $serviceName Service to operate on.
* @param string $successString String for reporting success.
* @param string $failString String for reporting failure.
* @param int $minAge Minimum age allowed for expiration (also used
* as default value).
*
* @return mixed
*/
protected function expire($table, $successString, $failString, $minAge = 2)
protected function expire($serviceName, $successString, $failString, $minAge = 2)
{
$daysOld = intval($this->params()->fromQuery('daysOld', $minAge));
if ($daysOld < $minAge) {
Expand All @@ -247,12 +251,11 @@ protected function expire($table, $successString, $failString, $minAge = 2)
)
);
} else {
$search = $this->getTable($table);
if (!method_exists($search, 'deleteExpired')) {
throw new \Exception($table . ' does not support deleteExpired()');
$service = $this->getDbService($serviceName);
if (!$service instanceof DeleteExpiredInterface) {
throw new \Exception("Unsupported service: $serviceName");
}
$threshold = date('Y-m-d H:i:s', time() - $daysOld * 24 * 60 * 60);
$count = $search->deleteExpired($threshold);
$count = $service->deleteExpired(new DateTime("now - $daysOld days"));
if ($count == 0) {
$msg = $failString;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use VuFind\Db\Service\Feature\DeleteExpiredInterface;
use VuFind\Db\Table\Gateway;

use function floatval;

Expand Down Expand Up @@ -83,25 +82,19 @@ class AbstractExpireCommand extends Command
/**
* Table on which to expire rows
*
* @var Gateway|DeleteExpiredInterface
* @var DeleteExpiredInterface
*/
protected $table;

/**
* Constructor
*
* @param Gateway|DeleteExpiredInterface $service Service on which to expire rows
* @param ?string $name The name of the command; passing null means it
* @param DeleteExpiredInterface $service Service on which to expire rows
* @param ?string $name The name of the command; passing null means it
* must be set in configure()
*/
public function __construct(
protected Gateway|DeleteExpiredInterface $service,
?string $name = null
) {
if (!method_exists($service, 'deleteExpired')) {
$serviceName = $service::class;
throw new \Exception("$serviceName does not support deleteExpired()");
}
public function __construct(protected DeleteExpiredInterface $service, ?string $name = null)
{
parent::__construct($name);
}

Expand Down Expand Up @@ -185,11 +178,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
// delete are found.
$total = 0;
do {
$count = $this->service->deleteExpired(
// Format DateTime into string for legacy table Gateway objects:
$this->service instanceof Gateway ? $dateLimit->format('Y-m-d H:i:s') : $dateLimit,
$batchSize
);
$count = $this->service->deleteExpired($dateLimit, $batchSize);
if ($count > 0) {
$output->writeln(
$this->getTimestampedMessage("$count {$this->rowLabel} deleted.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ public function __invoke(
$requestedName,
array $options = null
) {
$tableManager = $container->get(\VuFind\Db\Table\PluginManager::class);
$serviceManager = $container->get(\VuFind\Db\Service\PluginManager::class);
return new $requestedName(
$tableManager->get(\VuFind\Db\Table\AccessToken::class),
$serviceManager->get(\VuFind\Db\Service\AccessTokenServiceInterface::class),
...($options ?? [])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ public function __invoke(
$requestedName,
array $options = null
) {
$tableManager = $container->get(\VuFind\Db\Table\PluginManager::class);
$serviceManager = $container->get(\VuFind\Db\Service\PluginManager::class);
return new $requestedName(
$tableManager->get(\VuFind\Db\Table\LoginToken::class),
$serviceManager->get(\VuFind\Db\Service\LoginTokenServiceInterface::class),
...($options ?? [])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ public function __invoke(
$requestedName,
array $options = null
) {
$tableManager = $container->get(\VuFind\Db\Table\PluginManager::class);
$serviceManager = $container->get(\VuFind\Db\Service\PluginManager::class);
return new $requestedName(
$tableManager->get(\VuFind\Db\Table\Search::class),
$serviceManager->get(\VuFind\Db\Service\SearchServiceInterface::class),
...($options ?? [])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ public function __invoke(
$requestedName,
array $options = null
) {
$tableManager = $container->get(\VuFind\Db\Table\PluginManager::class);
$serviceManager = $container->get(\VuFind\Db\Service\PluginManager::class);
return new $requestedName(
$tableManager->get(\VuFind\Db\Table\Session::class),
$serviceManager->get(\VuFind\Db\Service\SessionServiceInterface::class),
...($options ?? [])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,6 @@ class AbstractExpireCommandTest extends \PHPUnit\Framework\TestCase
*/
protected $expectedMinAge = 2;

/**
* Test an unsupported table class.
*
* @return void
*/
public function testUnsupportedTableClass()
{
$table = $this->getMockBuilder(\VuFind\Db\Table\User::class)
->disableOriginalConstructor()
->getMock();
$this->expectException(\Exception::class);
$this->expectExceptionMessage(
$table::class . ' does not support deleteExpired()'
);
new $this->targetClass($table, 'foo');
}

/**
* Test an illegal age parameter.
*
Expand Down
Loading

0 comments on commit 9408147

Please sign in to comment.