Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ composer.lock
/phpstan.neon
# PHPStan cache directory
/.phpstan.cache/

# Added by horde-components QC --fix-qc-issues
# Horde installer plugin runtime data
/var/
# Horde installer plugin web-accessible directory
/web/
1 change: 1 addition & 0 deletions .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,4 @@ autoload-dev:
Horde\HashTable\Test\Src\Unit\: test/src/Unit
Horde\HashTable\Test\Src\Integration\: test/src/Integration
vendor: horde
keywords: []
125 changes: 85 additions & 40 deletions bin/horde-redis-tester
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ declare(strict_types=1);
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*/

foreach ([__DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../vendor/autoload.php'] as $autoloader) {
if (file_exists($autoloader)) {
require_once $autoloader;
break;
if (isset($GLOBALS['_composer_autoload_path'])) {
require_once $GLOBALS['_composer_autoload_path'];
} else {
$found = false;
foreach ([__DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php'] as $autoloader) {
if (file_exists($autoloader)) {
require_once $autoloader;
$found = true;
break;
}
}
if (!$found) {
fwrite(STDERR, "Error: Cannot find autoloader. Run 'composer install' first.\n");
exit(1);
}
}

if (!class_exists(\Horde\HashTable\Redis\Diagnostic\RedisTester::class)) {
fwrite(STDERR, "Error: Cannot find autoloader. Run 'composer install' first.\n");
fwrite(STDERR, "Error: RedisTester class not found. Ensure horde/hashtable is installed correctly.\n");
exit(1);
}

Expand All @@ -46,6 +56,10 @@ if (!extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
use Horde\Argv\Option;
use Horde\Argv\Parser;
use Horde\Cli\Cli;
use Horde\HashTable\Redis\Config\RedisConfigMapper;
use Horde\HashTable\Redis\Config\RedisNode;
use Horde\HashTable\Redis\Config\SentinelConfig;
use Horde\HashTable\Redis\Config\SingleNodeConfig;
use Horde\HashTable\Redis\Diagnostic\DiagnosticResult;
use Horde\HashTable\Redis\Diagnostic\RedisDriver;
use Horde\HashTable\Redis\Diagnostic\RedisTester;
Expand Down Expand Up @@ -123,47 +137,69 @@ $parser->addOption(new Option('--driver', [

[$options, $args] = $parser->parseArgs();

$configDefaults = loadConfigFile($options->configFile);

$tls = (bool) $options->tls;
$hostname = $options->hostname ?? $configDefaults['hostname'] ?? '127.0.0.1';
$port = $options->port ?? $configDefaults['port'] ?? ($tls ? 6380 : 6379);
$username = $options->username ?? $configDefaults['username'] ?? null;
$password = $options->password ?? $configDefaults['password'] ?? null;
$prefix = $options->prefix ?? $configDefaults['prefix'] ?? 'hht_';
$database = $options->database ?? $configDefaults['database'] ?? 0;
$driver = RedisDriver::from($options->driver);

$cli = new Cli(['pager' => false]);

$configParams = loadConfigFileParams($options->configFile);

if ($configParams !== [] && $options->hostname === null) {
$prefix = $options->prefix ?? $configParams['prefix'] ?? 'hht_';
if (isset($options->database)) {
$configParams['database'] = $options->database;
}
if ($options->username !== null) {
$configParams['username'] = $options->username;
}
if ($options->password !== null) {
$configParams['password'] = $options->password;
}
$config = RedisConfigMapper::fromArray($configParams, $prefix);
} else {
$configDefaults = extractLegacyDefaults($configParams);
$tls = (bool) $options->tls;
$hostname = $options->hostname ?? $configDefaults['hostname'] ?? '127.0.0.1';
$port = $options->port ?? $configDefaults['port'] ?? ($tls ? 6380 : 6379);
$username = $options->username ?? $configDefaults['username'] ?? null;
$password = $options->password ?? $configDefaults['password'] ?? null;
$prefix = $options->prefix ?? $configDefaults['prefix'] ?? 'hht_';
$database = $options->database ?? $configDefaults['database'] ?? 0;

$config = new SingleNodeConfig(
node: new RedisNode(host: $hostname, port: $port, tls: $tls),
password: ($password !== null && $password !== '') ? $password : null,
username: ($username !== null && $username !== '') ? $username : null,
prefix: $prefix,
database: (int) $database,
);
}

$cli->writeln('');
$cli->header('Horde Redis Diagnostic');
$cli->writeln('Host: ' . $hostname . ':' . $port . ' (TLS: ' . ($tls ? 'yes' : 'no') . ')');
$cli->writeln('Prefix: ' . $prefix);
$cli->writeln('Database: ' . $database);
if ($username) {
$cli->writeln('Username: ' . $username);

if ($config instanceof SentinelConfig) {
$hosts = array_map(fn($n) => $n->host . ':' . $n->port, $config->sentinels);
$cli->writeln('Mode: Sentinel (service: ' . $config->service . ')');
$cli->writeln('Sentinels: ' . implode(', ', $hosts));
} elseif ($config instanceof SingleNodeConfig) {
$tlsLabel = $config->node->tls ? 'yes' : 'no';
$cli->writeln('Host: ' . $config->node->host . ':' . $config->node->port . ' (TLS: ' . $tlsLabel . ')');
}
$cli->writeln('Prefix: ' . $config->prefix());
$cli->writeln('Database: ' . $config->database());
if ($config->username()) {
$cli->writeln('Username: ' . $config->username());
}
$cli->writeln('');

$tester = new RedisTester(
hostname: $hostname,
port: $port,
tls: $tls,
username: $username,
password: $password,
prefix: $prefix,
database: $database,
driver: $driver,
);
$tester = RedisTester::fromConfig($config, $driver);

$result = $tester->run();
printResults($cli, $result);
exit($result->hasErrors() ? 2 : ($result->hasWarnings() ? 1 : 0));

// --- Helper functions ---

function loadConfigFile(?string $path): array
function loadConfigFileParams(?string $path): array
{
if ($path === null) {
return [];
Expand All @@ -185,6 +221,23 @@ function loadConfigFile(?string $path): array
$ht = $conf['hashtable'];
$params = $ht['params'] ?? [];

if (isset($ht['prefix']) && $ht['prefix'] !== '') {
$params['prefix'] = $ht['prefix'];
}

if (isset($ht['driver'])) {
$driver = strtolower($ht['driver']);
if (!in_array($driver, ['predis', 'redis'], true)) {
fwrite(STDERR, "Warning: hashtable.driver is '{$ht['driver']}' (not predis/redis). "
. "This tool tests Redis only.\n");
}
}

return $params;
}

function extractLegacyDefaults(array $params): array
{
$defaults = [];

$defaults['hostname'] = $params['hostspec'][0]
Expand All @@ -205,15 +258,7 @@ function loadConfigFile(?string $path): array
$defaults['database'] = (int) $db;
}

$defaults['prefix'] = $ht['prefix'] ?? null;

if (isset($ht['driver'])) {
$driver = strtolower($ht['driver']);
if (!in_array($driver, ['predis', 'redis'], true)) {
fwrite(STDERR, "Warning: hashtable.driver is '{$ht['driver']}' (not predis/redis). "
. "This tool tests Redis only.\n");
}
}
$defaults['prefix'] = $params['prefix'] ?? null;

return array_filter($defaults, fn($v) => $v !== null && $v !== '');
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Base.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Exception.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Lock.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Memcache.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Memory.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Null.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Predis.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
* Copyright 2013-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
2 changes: 1 addition & 1 deletion lib/Horde/HashTable/Vfs.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2014-2017 Horde LLC (http://www.horde.org/)
* Copyright 2014-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
Expand Down
4 changes: 1 addition & 3 deletions src/ConnectionException.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,4 @@
/**
* Thrown when the underlying storage backend is unreachable.
*/
class ConnectionException extends HashTableException
{
}
class ConnectionException extends HashTableException {}
8 changes: 2 additions & 6 deletions src/Driver/NullDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ public function replace(string $key, mixed $value, ?int $ttl = null): bool
return false;
}

public function delete(string|array $keys): void
{
}
public function delete(string|array $keys): void {}

public function exists(string $key): bool
{
Expand All @@ -76,7 +74,5 @@ public function existsMultiple(array $keys): array
return array_fill_keys($keys, false);
}

public function clear(): void
{
}
public function clear(): void {}
}
4 changes: 1 addition & 3 deletions src/LockTimeoutException.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,4 @@
/**
* Thrown when a lock cannot be acquired within the configured timeout.
*/
class LockTimeoutException extends HashTableException
{
}
class LockTimeoutException extends HashTableException {}
Loading
Loading