function aGetByKey(&$array, $key, $value = null) {
    list($index, $key) = explode('.', $key, 2);
    if (!isset($array[$index])) throw new Exception("No such key: " . $index);
    if(strlen($key) > 0)
        return aGetByKey(&$array[$index], $key, $value);
    $old = $array[$index];
    if ($value !== null) $array[$index] = $value;
    return $old;    
}
function conf($key, $value = null) {
    global $CONF;
    return aGetByKey(&$CONF, $key, $value);
}
Хотелось-бы чтобы она работала так:
echo conf('category.name'); //Hello
echo conf('category.name', 'World'); //true
echo conf('category.name'); // World
echo conf('category.data', ['amount' => 158, 'kind' => 36]); //true
echo conf('category.data')['amount']; //158 
  
  class Config {
	protected static array $register = [];
	public static function get(string $name, $default = null) {
		$keys = explode('.', $name);
		$pointer = self::$register;
		foreach ($keys as $key) {
			if (!array_key_exists($key, $pointer)) {
				/*Если ключа нет, выставить его*/
				//self::set($name, $default);
				/*Бросить исключение*/
				//throw new Exception("No such key: {$name}");
				/*Вернуть значение по умолчанию*/
				return $default;
			}
			$pointer = &$pointer[$key];
		}
		return $pointer;
	}
	public static function set(string $name, $value = null) {
		$arr = &self::$register;
		$keys = explode('.', $name);
		foreach ($keys as $key) {
			$arr = &$arr[$key];
		}
		if (is_array($arr) && is_array($value)) {
			$arr = array_replace_recursive($arr, $value);
		} else {
			$arr = $value;
		}
	}
	public static function getTree() {
		return self::$register;
	}
}
Config::set("config", [
	"name" => "test",
]);
print_r(Config::get("config"));
echo "(config)\n\n";
Config::set("config.class.name", "A");
print_r(Config::get("config.class.name"));
echo "\n(config.class.name)\n\n";
Config::set("config", [
	"class" => [
		"test" => "B",
	],
]);
print_r(Config::get("config.class"));
echo "\n(config.class)\n\n";
Config::set("config.class", null);
print_r(Config::get("config.class"));
echo "\n(config.class)\n\n";
print_r(Config::get("config.fake.key.without.value", "UNDEFINED"));
echo "\n(config.fake.key.without.value)\n\n";
Config::set("config.nullable", null);
print_r(Config::get("config.nullable", "NULL"));
echo "\n(config.nullable)\n\n";
print_r(Config::getTree());
echo "\n(all)\n";$CONF = array('category' => array('name' => "Hello", 'data' => array()));
function aGetByKey(&$array, $index, $value = null) {
    if (strpos($index, "."))
        list($index, $key) = explode('.', $index, 2);
    if (!isset($index) OR !isset($array[$index]))
        throw new Exception("No such key: " . $index);
    if(!empty($key))
        return aGetByKey($array[$index], $key, $value);
    $old = $array[$index];
    if ($value !== null)
        $array[$index] = $value;
    return $old;
}
function conf($key, $value = null) {
    global $CONF;
    return aGetByKey($CONF, $key, $value);
}
//Хотелось-бы чтобы она работала так:
echo conf('category.name'); //Hello
echo conf('category.name', 'World'); //Hello, потому что возвращается предыдущее значение $old
echo conf('category.name'); // World
conf('category.data', ['amount' => 158, 'kind' => 36]); // echo() нельзя, потому что вернётся массив
echo conf('category.data')['amount']; //158