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';
}
// 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
*/
@ -100,16 +113,16 @@ if (APP_ENV === 'local') {
/**
* Server variables initialize
*/
$server_protocol = $bb_cfg['cookie_secure'] ? 'https://' : 'http://';
$server_port = in_array((int)$bb_cfg['server_port'], [80, 443], true) ? '' : ':' . $bb_cfg['server_port'];
define('FORUM_PATH', $bb_cfg['script_path']);
define('FULL_URL', $server_protocol . $bb_cfg['server_name'] . $server_port . $bb_cfg['script_path']);
$server_protocol = config()->get('cookie_secure') ? 'https://' : 'http://';
$server_port = in_array((int)config()->get('server_port'), [80, 443], true) ? '' : ':' . config()->get('server_port');
define('FORUM_PATH', config()->get('script_path'));
define('FULL_URL', $server_protocol . config()->get('server_name') . $server_port . config()->get('script_path'));
unset($server_protocol, $server_port);
/**
* Database
*/
$DBS = new TorrentPier\Legacy\Dbs($bb_cfg);
$DBS = new TorrentPier\Legacy\Dbs(config()->all());
function DB(string $db_alias = 'db')
{
@ -120,7 +133,7 @@ function DB(string $db_alias = 'db')
/**
* Cache
*/
$CACHES = new TorrentPier\Legacy\Caches($bb_cfg);
$CACHES = new TorrentPier\Legacy\Caches(config()->all());
function CACHE(string $cache_name)
{
@ -131,22 +144,22 @@ function CACHE(string $cache_name)
/**
* Datastore
*/
switch ($bb_cfg['datastore_type']) {
switch (config()->get('datastore_type')) {
case 'apcu':
$datastore = new TorrentPier\Legacy\Datastore\APCu($bb_cfg['cache']['prefix']);
$datastore = new TorrentPier\Legacy\Datastore\APCu(config()->get('cache.prefix'));
break;
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;
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;
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;
case 'filecache':
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

View file

@ -68,7 +68,7 @@ class Ajax
*/
public function exec()
{
global $lang, $bb_cfg;
global $lang;
// Exit if we already have errors
if (!empty($this->response['error_code'])) {
@ -89,8 +89,8 @@ class Ajax
}
// Exit if board is disabled via ON/OFF trigger or by admin
if ($bb_cfg['board_disable'] || is_file(BB_DISABLED)) {
if ($bb_cfg['board_disable']) {
if (config()->get('board_disable') || is_file(BB_DISABLED)) {
if (config()->get('board_disable')) {
$this->ajax_die($lang['BOARD_DISABLE']);
} elseif (is_file(BB_DISABLED) && $this->action !== 'manage_admin') {
$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
{
global $bb_cfg;
if (!$template_lang) {
$template_lang = $bb_cfg['default_lang'];
$template_lang = config()->get('default_lang');
}
if (empty($this->tpl_msg[$template_lang . $template_file])) {
$tpl_file = LANG_ROOT_DIR . '/' . $template_lang . '/email/' . $template_file . '.html';
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)) {
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
{
global $bb_cfg, $lang;
global $lang;
if (!$bb_cfg['emailer']['enabled']) {
if (!config()->get('emailer.enabled')) {
return false;
}
@ -142,24 +140,25 @@ class Emailer
$this->subject = !empty($this->subject) ? $this->subject : $lang['EMAILER_SUBJECT']['EMPTY'];
/** Prepare message */
if ($bb_cfg['emailer']['smtp']['enabled']) {
if (!empty($bb_cfg['emailer']['smtp']['host'])) {
if (empty($bb_cfg['emailer']['smtp']['ssl_type'])) {
$bb_cfg['emailer']['smtp']['ssl_type'] = null;
if (config()->get('emailer.smtp.enabled')) {
if (!empty(config()->get('emailer.smtp.host'))) {
$sslType = config()->get('emailer.smtp.ssl_type');
if (empty($sslType)) {
$sslType = null;
}
/** @var EsmtpTransport $transport external SMTP with SSL */
$transport = (new EsmtpTransport(
$bb_cfg['emailer']['smtp']['host'],
$bb_cfg['emailer']['smtp']['port'],
$bb_cfg['emailer']['smtp']['ssl_type']
config()->get('emailer.smtp.host'),
config()->get('emailer.smtp.port'),
$sslType
))
->setUsername($bb_cfg['emailer']['smtp']['username'])
->setPassword($bb_cfg['emailer']['smtp']['password']);
->setUsername(config()->get('emailer.smtp.username'))
->setPassword(config()->get('emailer.smtp.password'));
} else {
$transport = new EsmtpTransport('localhost', 25);
}
} else {
$transport = new SendmailTransport($bb_cfg['emailer']['sendmail_command']);
$transport = new SendmailTransport(config()->get('emailer.sendmail_command'));
}
$mailer = new Mailer($transport);
@ -168,9 +167,9 @@ class Emailer
$message = (new Email())
->subject($this->subject)
->to($this->to)
->from(new Address($bb_cfg['board_email'], $bb_cfg['board_email_sitename']))
->returnPath(new Address($bb_cfg['bounce_email']))
->replyTo($this->reply ?? new Address($bb_cfg['board_email']));
->from(new Address(config()->get('board_email'), config()->get('board_email_sitename')))
->returnPath(new Address(config()->get('bounce_email')))
->replyTo($this->reply ?? new Address(config()->get('board_email')));
/**
* This non-standard header tells compliant autoresponders ("email holiday mode") to not
@ -209,12 +208,10 @@ class Emailer
*/
public function assign_vars($vars): void
{
global $bb_cfg;
$this->vars = array_merge([
'BOARD_EMAIL' => $bb_cfg['board_email'],
'SITENAME' => $bb_cfg['board_email_sitename'],
'EMAIL_SIG' => !empty($bb_cfg['board_email_sig']) ? "-- \n{$bb_cfg['board_email_sig']}" : '',
'BOARD_EMAIL' => config()->get('board_email'),
'SITENAME' => config()->get('board_email_sitename'),
'EMAIL_SIG' => !empty(config()->get('board_email_sig')) ? "-- \n" . config()->get('board_email_sig') : '',
], $vars);
}
}