refactor(config): encapsulate global $bb_cfg array in Config class (#1950)

- Create new TorrentPier\Config singleton class with dot notation support
- Add config() helper function for global access
- Replace direct $bb_cfg access in core files (common.php, Emailer.php, Ajax.php)
- Implement methods: get(), set(), has(), all(), getSection(), merge()
- Add magic methods for property-like access
- Maintain backward compatibility with existing $bb_cfg usage

BREAKING CHANGE: None - maintains full backward compatibility
This commit is contained in:
Yury Pikhtarev 2025-06-17 18:41:45 +04:00 committed by GitHub
commit 5842994782
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 231 additions and 39 deletions

View file

@ -86,6 +86,19 @@ if (is_file(BB_PATH . '/library/config.local.php')) {
require_once BB_PATH . '/library/config.local.php'; require_once BB_PATH . '/library/config.local.php';
} }
// Initialize Config singleton
$config = \TorrentPier\Config::init($bb_cfg);
/**
* Get the Config instance
*
* @return \TorrentPier\Config
*/
function config(): \TorrentPier\Config
{
return \TorrentPier\Config::getInstance();
}
/** /**
* Initialize debug * Initialize debug
*/ */
@ -100,16 +113,16 @@ if (APP_ENV === 'local') {
/** /**
* Server variables initialize * Server variables initialize
*/ */
$server_protocol = $bb_cfg['cookie_secure'] ? 'https://' : 'http://'; $server_protocol = config()->get('cookie_secure') ? 'https://' : 'http://';
$server_port = in_array((int)$bb_cfg['server_port'], [80, 443], true) ? '' : ':' . $bb_cfg['server_port']; $server_port = in_array((int)config()->get('server_port'), [80, 443], true) ? '' : ':' . config()->get('server_port');
define('FORUM_PATH', $bb_cfg['script_path']); define('FORUM_PATH', config()->get('script_path'));
define('FULL_URL', $server_protocol . $bb_cfg['server_name'] . $server_port . $bb_cfg['script_path']); define('FULL_URL', $server_protocol . config()->get('server_name') . $server_port . config()->get('script_path'));
unset($server_protocol, $server_port); unset($server_protocol, $server_port);
/** /**
* Database * Database
*/ */
$DBS = new TorrentPier\Legacy\Dbs($bb_cfg); $DBS = new TorrentPier\Legacy\Dbs(config()->all());
function DB(string $db_alias = 'db') function DB(string $db_alias = 'db')
{ {
@ -120,7 +133,7 @@ function DB(string $db_alias = 'db')
/** /**
* Cache * Cache
*/ */
$CACHES = new TorrentPier\Legacy\Caches($bb_cfg); $CACHES = new TorrentPier\Legacy\Caches(config()->all());
function CACHE(string $cache_name) function CACHE(string $cache_name)
{ {
@ -131,22 +144,22 @@ function CACHE(string $cache_name)
/** /**
* Datastore * Datastore
*/ */
switch ($bb_cfg['datastore_type']) { switch (config()->get('datastore_type')) {
case 'apcu': case 'apcu':
$datastore = new TorrentPier\Legacy\Datastore\APCu($bb_cfg['cache']['prefix']); $datastore = new TorrentPier\Legacy\Datastore\APCu(config()->get('cache.prefix'));
break; break;
case 'memcached': case 'memcached':
$datastore = new TorrentPier\Legacy\Datastore\Memcached($bb_cfg['cache']['memcached'], $bb_cfg['cache']['prefix']); $datastore = new TorrentPier\Legacy\Datastore\Memcached(config()->get('cache.memcached'), config()->get('cache.prefix'));
break; break;
case 'sqlite': case 'sqlite':
$datastore = new TorrentPier\Legacy\Datastore\Sqlite($bb_cfg['cache']['db_dir'] . 'datastore', $bb_cfg['cache']['prefix']); $datastore = new TorrentPier\Legacy\Datastore\Sqlite(config()->get('cache.db_dir') . 'datastore', config()->get('cache.prefix'));
break; break;
case 'redis': case 'redis':
$datastore = new TorrentPier\Legacy\Datastore\Redis($bb_cfg['cache']['redis'], $bb_cfg['cache']['prefix']); $datastore = new TorrentPier\Legacy\Datastore\Redis(config()->get('cache.redis'), config()->get('cache.prefix'));
break; break;
case 'filecache': case 'filecache':
default: default:
$datastore = new TorrentPier\Legacy\Datastore\File($bb_cfg['cache']['db_dir'] . 'datastore/', $bb_cfg['cache']['prefix']); $datastore = new TorrentPier\Legacy\Datastore\File(config()->get('cache.db_dir') . 'datastore/', config()->get('cache.prefix'));
} }
// Functions // Functions

View file

@ -68,7 +68,7 @@ class Ajax
*/ */
public function exec() public function exec()
{ {
global $lang, $bb_cfg; global $lang;
// Exit if we already have errors // Exit if we already have errors
if (!empty($this->response['error_code'])) { if (!empty($this->response['error_code'])) {
@ -89,8 +89,8 @@ class Ajax
} }
// Exit if board is disabled via ON/OFF trigger or by admin // Exit if board is disabled via ON/OFF trigger or by admin
if ($bb_cfg['board_disable'] || is_file(BB_DISABLED)) { if (config()->get('board_disable') || is_file(BB_DISABLED)) {
if ($bb_cfg['board_disable']) { if (config()->get('board_disable')) {
$this->ajax_die($lang['BOARD_DISABLE']); $this->ajax_die($lang['BOARD_DISABLE']);
} elseif (is_file(BB_DISABLED) && $this->action !== 'manage_admin') { } elseif (is_file(BB_DISABLED) && $this->action !== 'manage_admin') {
$this->ajax_die($lang['BOARD_DISABLE_CRON']); $this->ajax_die($lang['BOARD_DISABLE_CRON']);

182
src/Config.php Normal file
View file

@ -0,0 +1,182 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier;
/**
* Configuration management class
*
* Encapsulates the global $bb_cfg array and provides methods to access configuration values
*/
class Config
{
private static ?Config $instance = null;
private array $config = [];
private function __construct(array $config = [])
{
$this->config = $config;
}
/**
* Get the singleton instance of Config
*/
public static function getInstance(array $config = []): Config
{
if (self::$instance === null) {
self::$instance = new self($config);
}
return self::$instance;
}
/**
* Initialize the config with the global $bb_cfg array
*/
public static function init(array $bb_cfg): Config
{
self::$instance = new self($bb_cfg);
return self::$instance;
}
/**
* Get a configuration value by key
* Supports dot notation for nested arrays (e.g., 'db.host')
*/
public function get(string $key, mixed $default = null): mixed
{
if (str_contains($key, '.')) {
return $this->getNestedValue($key, $default);
}
return $this->config[$key] ?? $default;
}
/**
* Set a configuration value by key
* Supports dot notation for nested arrays
*/
public function set(string $key, mixed $value): void
{
if (str_contains($key, '.')) {
$this->setNestedValue($key, $value);
} else {
$this->config[$key] = $value;
}
}
/**
* Check if a configuration key exists
* Supports dot notation for nested arrays
*/
public function has(string $key): bool
{
if (str_contains($key, '.')) {
return $this->getNestedValue($key) !== null;
}
return array_key_exists($key, $this->config);
}
/**
* Get all configuration values
*/
public function all(): array
{
return $this->config;
}
/**
* Get a nested value using dot notation
*/
private function getNestedValue(string $key, mixed $default = null): mixed
{
$keys = explode('.', $key);
$value = $this->config;
foreach ($keys as $k) {
if (!is_array($value) || !array_key_exists($k, $value)) {
return $default;
}
$value = $value[$k];
}
return $value;
}
/**
* Set a nested value using dot notation
*/
private function setNestedValue(string $key, mixed $value): void
{
$keys = explode('.', $key);
$target = &$this->config;
foreach ($keys as $k) {
if (!isset($target[$k]) || !is_array($target[$k])) {
$target[$k] = [];
}
$target = &$target[$k];
}
$target = $value;
}
/**
* Merge additional configuration values
*/
public function merge(array $config): void
{
$this->config = array_merge_recursive($this->config, $config);
}
/**
* Get a section of the configuration
*/
public function getSection(string $section): array
{
return $this->config[$section] ?? [];
}
/**
* Magic method to allow property access
*/
public function __get(string $key): mixed
{
return $this->get($key);
}
/**
* Magic method to allow property setting
*/
public function __set(string $key, mixed $value): void
{
$this->set($key, $value);
}
/**
* Magic method to check if property exists
*/
public function __isset(string $key): bool
{
return $this->has($key);
}
/**
* Prevent cloning of the singleton instance
*/
private function __clone() {}
/**
* Prevent unserialization of the singleton instance
*/
public function __wakeup()
{
throw new \Exception("Cannot unserialize a singleton.");
}
}

View file

@ -87,17 +87,15 @@ class Emailer
*/ */
public function set_template(string $template_file, string $template_lang = ''): void public function set_template(string $template_file, string $template_lang = ''): void
{ {
global $bb_cfg;
if (!$template_lang) { if (!$template_lang) {
$template_lang = $bb_cfg['default_lang']; $template_lang = config()->get('default_lang');
} }
if (empty($this->tpl_msg[$template_lang . $template_file])) { if (empty($this->tpl_msg[$template_lang . $template_file])) {
$tpl_file = LANG_ROOT_DIR . '/' . $template_lang . '/email/' . $template_file . '.html'; $tpl_file = LANG_ROOT_DIR . '/' . $template_lang . '/email/' . $template_file . '.html';
if (!is_file($tpl_file)) { if (!is_file($tpl_file)) {
$tpl_file = LANG_ROOT_DIR . '/' . $bb_cfg['default_lang'] . '/email/' . $template_file . '.html'; $tpl_file = LANG_ROOT_DIR . '/' . config()->get('default_lang') . '/email/' . $template_file . '.html';
if (!is_file($tpl_file)) { if (!is_file($tpl_file)) {
throw new Exception('Could not find email template file: ' . $template_file); throw new Exception('Could not find email template file: ' . $template_file);
@ -125,9 +123,9 @@ class Emailer
*/ */
public function send(string $email_format = 'text/plain'): bool public function send(string $email_format = 'text/plain'): bool
{ {
global $bb_cfg, $lang; global $lang;
if (!$bb_cfg['emailer']['enabled']) { if (!config()->get('emailer.enabled')) {
return false; return false;
} }
@ -142,24 +140,25 @@ class Emailer
$this->subject = !empty($this->subject) ? $this->subject : $lang['EMAILER_SUBJECT']['EMPTY']; $this->subject = !empty($this->subject) ? $this->subject : $lang['EMAILER_SUBJECT']['EMPTY'];
/** Prepare message */ /** Prepare message */
if ($bb_cfg['emailer']['smtp']['enabled']) { if (config()->get('emailer.smtp.enabled')) {
if (!empty($bb_cfg['emailer']['smtp']['host'])) { if (!empty(config()->get('emailer.smtp.host'))) {
if (empty($bb_cfg['emailer']['smtp']['ssl_type'])) { $sslType = config()->get('emailer.smtp.ssl_type');
$bb_cfg['emailer']['smtp']['ssl_type'] = null; if (empty($sslType)) {
$sslType = null;
} }
/** @var EsmtpTransport $transport external SMTP with SSL */ /** @var EsmtpTransport $transport external SMTP with SSL */
$transport = (new EsmtpTransport( $transport = (new EsmtpTransport(
$bb_cfg['emailer']['smtp']['host'], config()->get('emailer.smtp.host'),
$bb_cfg['emailer']['smtp']['port'], config()->get('emailer.smtp.port'),
$bb_cfg['emailer']['smtp']['ssl_type'] $sslType
)) ))
->setUsername($bb_cfg['emailer']['smtp']['username']) ->setUsername(config()->get('emailer.smtp.username'))
->setPassword($bb_cfg['emailer']['smtp']['password']); ->setPassword(config()->get('emailer.smtp.password'));
} else { } else {
$transport = new EsmtpTransport('localhost', 25); $transport = new EsmtpTransport('localhost', 25);
} }
} else { } else {
$transport = new SendmailTransport($bb_cfg['emailer']['sendmail_command']); $transport = new SendmailTransport(config()->get('emailer.sendmail_command'));
} }
$mailer = new Mailer($transport); $mailer = new Mailer($transport);
@ -168,9 +167,9 @@ class Emailer
$message = (new Email()) $message = (new Email())
->subject($this->subject) ->subject($this->subject)
->to($this->to) ->to($this->to)
->from(new Address($bb_cfg['board_email'], $bb_cfg['board_email_sitename'])) ->from(new Address(config()->get('board_email'), config()->get('board_email_sitename')))
->returnPath(new Address($bb_cfg['bounce_email'])) ->returnPath(new Address(config()->get('bounce_email')))
->replyTo($this->reply ?? new Address($bb_cfg['board_email'])); ->replyTo($this->reply ?? new Address(config()->get('board_email')));
/** /**
* This non-standard header tells compliant autoresponders ("email holiday mode") to not * This non-standard header tells compliant autoresponders ("email holiday mode") to not
@ -209,12 +208,10 @@ class Emailer
*/ */
public function assign_vars($vars): void public function assign_vars($vars): void
{ {
global $bb_cfg;
$this->vars = array_merge([ $this->vars = array_merge([
'BOARD_EMAIL' => $bb_cfg['board_email'], 'BOARD_EMAIL' => config()->get('board_email'),
'SITENAME' => $bb_cfg['board_email_sitename'], 'SITENAME' => config()->get('board_email_sitename'),
'EMAIL_SIG' => !empty($bb_cfg['board_email_sig']) ? "-- \n{$bb_cfg['board_email_sig']}" : '', 'EMAIL_SIG' => !empty(config()->get('board_email_sig')) ? "-- \n" . config()->get('board_email_sig') : '',
], $vars); ], $vars);
} }
} }