Files
FendxPHP/fendx-framework/fendx-i18n/src/Config/I18nConfigManager.php

825 lines
22 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Fendx\I18n\Config;
use Fendx\I18n\Config\Loader\ConfigLoader;
use Fendx\I18n\Config\Validator\ConfigValidator;
use Fendx\I18n\Config\Cache\ConfigCache;
class I18nConfigManager
{
protected ConfigLoader $loader;
protected ConfigValidator $validator;
protected ConfigCache $cache;
protected array $config = [];
protected array $defaults = [];
protected array $environments = [];
protected string $currentEnvironment = 'production';
protected bool $loaded = false;
public function __construct(array $config = [])
{
$this->defaults = $this->getDefaultConfig();
$this->config = array_merge($this->defaults, $config);
$this->loader = new ConfigLoader($this->config);
$this->validator = new ConfigValidator($this->config);
$this->cache = new ConfigCache($this->config);
$this->currentEnvironment = $this->config['environment'] ?? $this->detectEnvironment();
}
/**
* Load I18n configuration.
*/
public function load(): void
{
if ($this->loaded) {
return;
}
// Load base configuration
$baseConfig = $this->loader->loadBase();
// Load environment-specific configuration
$envConfig = $this->loader->loadEnvironment($this->currentEnvironment);
// Load local configuration
$localConfig = $this->loader->loadLocal();
// Merge configurations
$this->config = array_merge_recursive(
$this->defaults,
$baseConfig,
$envConfig,
$localConfig
);
// Validate configuration
$validation = $this->validator->validate($this->config);
if (!$validation['valid']) {
throw new \InvalidArgumentException(
'Invalid I18n configuration: ' . implode(', ', $validation['errors'])
);
}
// Process configuration
$this->processConfig();
$this->loaded = true;
}
/**
* Get configuration value.
*/
public function get(string $key, mixed $default = null): mixed
{
$this->ensureLoaded();
return $this->getNestedValue($this->config, $key, $default);
}
/**
* Set configuration value.
*/
public function set(string $key, mixed $value): void
{
$this->ensureLoaded();
$this->setNestedValue($this->config, $key, $value);
$this->cache->clear();
}
/**
* Check if configuration key exists.
*/
public function has(string $key): bool
{
$this->ensureLoaded();
return $this->hasNestedValue($this->config, $key);
}
/**
* Get all configuration.
*/
public function all(): array
{
$this->ensureLoaded();
return $this->config;
}
/**
* Get supported languages.
*/
public function getSupportedLanguages(): array
{
return $this->get('supported_languages', []);
}
/**
* Get default language.
*/
public function getDefaultLanguage(): string
{
return $this->get('default_language', 'en');
}
/**
* Get fallback language.
*/
public function getFallbackLanguage(): string
{
return $this->get('fallback_language', 'en');
}
/**
* Get current language.
*/
public function getCurrentLanguage(): string
{
return $this->get('current_language', $this->getDefaultLanguage());
}
/**
* Set current language.
*/
public function setCurrentLanguage(string $language): void
{
if (!in_array($language, $this->getSupportedLanguages())) {
throw new \InvalidArgumentException("Language '{$language}' is not supported");
}
$this->set('current_language', $language);
}
/**
* Get timezone configuration.
*/
public function getTimezoneConfig(): array
{
return $this->get('timezone', [
'default' => 'UTC',
'allow_user_override' => true,
'supported_timezones' => []
]);
}
/**
* Get currency configuration.
*/
public function getCurrencyConfig(): array
{
return $this->get('currency', [
'default' => 'USD',
'allow_user_override' => true,
'supported_currencies' => [],
'precision' => 2,
'decimal_separator' => '.',
'thousands_separator' => ','
]);
}
/**
* Get date/time format configuration.
*/
public function getDateTimeConfig(): array
{
return $this->get('datetime', [
'date_format' => 'Y-m-d',
'time_format' => 'H:i:s',
'datetime_format' => 'Y-m-d H:i:s',
'timezone' => 'UTC',
'locale' => 'en'
]);
}
/**
* Get number format configuration.
*/
public function getNumberConfig(): array
{
return $this->get('number', [
'decimal_separator' => '.',
'thousands_separator' => ',',
'precision' => 2
]);
}
/**
* Get translation paths.
*/
public function getTranslationPaths(): array
{
return $this->get('translation_paths', []);
}
/**
* Get cache configuration.
*/
public function getCacheConfig(): array
{
return $this->get('cache', [
'enabled' => true,
'driver' => 'file',
'prefix' => 'i18n',
'ttl' => 3600
]);
}
/**
* Get language switcher configuration.
*/
public function getSwitcherConfig(): array
{
return $this->get('switcher', [
'strategies' => ['url', 'parameter', 'header', 'session', 'cookie'],
'url_parameter' => 'lang',
'cookie_name' => 'language',
'cookie_expires' => 86400 * 30
]);
}
/**
* Get fallback configuration.
*/
public function getFallbackConfig(): array
{
return $this->get('fallback', [
'enabled' => true,
'chains' => [],
'max_depth' => 5
]);
}
/**
* Get validation configuration.
*/
public function getValidationConfig(): array
{
return $this->get('validation', [
'strict_mode' => false,
'log_missing' => true,
'throw_on_missing' => false
]);
}
/**
* Add supported language.
*/
public function addSupportedLanguage(string $code, string $name, array $options = []): void
{
$languages = $this->getSupportedLanguages();
$languages[$code] = array_merge([
'name' => $name,
'native_name' => $name,
'direction' => 'ltr',
'enabled' => true
], $options);
$this->set('supported_languages', $languages);
}
/**
* Remove supported language.
*/
public function removeSupportedLanguage(string $code): void
{
$languages = $this->getSupportedLanguages();
unset($languages[$code]);
$this->set('supported_languages', $languages);
}
/**
* Add translation path.
*/
public function addTranslationPath(string $path, int $priority = 0): void
{
$paths = $this->getTranslationPaths();
$paths[] = ['path' => $path, 'priority' => $priority];
// Sort by priority (higher first)
usort($paths, fn($a, $b) => $b['priority'] - $a['priority']);
$this->set('translation_paths', $paths);
}
/**
* Remove translation path.
*/
public function removeTranslationPath(string $path): void
{
$paths = $this->getTranslationPaths();
$paths = array_filter($paths, fn($p) => $p['path'] !== $path);
$this->set('translation_paths', array_values($paths));
}
/**
* Set timezone configuration.
*/
public function setTimezoneConfig(array $config): void
{
$this->set('timezone', array_merge($this->getTimezoneConfig(), $config));
}
/**
* Set currency configuration.
*/
public function setCurrencyConfig(array $config): void
{
$this->set('currency', array_merge($this->getCurrencyConfig(), $config));
}
/**
* Set date/time format configuration.
*/
public function setDateTimeConfig(array $config): void
{
$this->set('datetime', array_merge($this->getDateTimeConfig(), $config));
}
/**
* Set number format configuration.
*/
public function setNumberConfig(array $config): void
{
$this->set('number', array_merge($this->getNumberConfig(), $config));
}
/**
* Enable/disable cache.
*/
public function setCacheEnabled(bool $enabled): void
{
$this->set('cache.enabled', $enabled);
}
/**
* Set cache TTL.
*/
public function setCacheTtl(int $ttl): void
{
$this->set('cache.ttl', $ttl);
}
/**
* Enable/disable strict validation.
*/
public function setStrictValidation(bool $strict): void
{
$this->set('validation.strict_mode', $strict);
}
/**
* Get environment-specific configuration.
*/
public function getEnvironmentConfig(string $environment): array
{
return $this->environments[$environment] ?? [];
}
/**
* Set environment-specific configuration.
*/
public function setEnvironmentConfig(string $environment, array $config): void
{
$this->environments[$environment] = $config;
}
/**
* Get current environment.
*/
public function getCurrentEnvironment(): string
{
return $this->currentEnvironment;
}
/**
* Set current environment.
*/
public function setCurrentEnvironment(string $environment): void
{
$this->currentEnvironment = $environment;
$this->loaded = false; // Force reload with new environment
}
/**
* Detect current environment.
*/
protected function detectEnvironment(): string
{
// Check environment variable
$env = getenv('APP_ENV') ?: getenv('ENVIRONMENT');
if ($env) {
return $env;
}
// Check server variable
if (isset($_SERVER['APP_ENV'])) {
return $_SERVER['APP_ENV'];
}
// Check for common indicators
if (isset($_SERVER['SERVER_NAME'])) {
$serverName = $_SERVER['SERVER_NAME'];
if (str_contains($serverName, 'localhost') || str_contains($serverName, 'dev')) {
return 'development';
} elseif (str_contains($serverName, 'staging') || str_contains($serverName, 'test')) {
return 'staging';
}
}
// Default to production
return 'production';
}
/**
* Process configuration after loading.
*/
protected function processConfig(): void
{
// Set default timezone
$timezone = $this->get('timezone.default', 'UTC');
date_default_timezone_set($timezone);
// Process supported languages
$languages = $this->getSupportedLanguages();
foreach ($languages as $code => $info) {
if (!isset($info['name'])) {
$languages[$code]['name'] = $code;
}
if (!isset($info['native_name'])) {
$languages[$code]['native_name'] = $info['name'];
}
if (!isset($info['direction'])) {
$languages[$code]['direction'] = 'ltr';
}
if (!isset($info['enabled'])) {
$languages[$code]['enabled'] = true;
}
}
$this->set('supported_languages', $languages);
// Process translation paths
$paths = $this->getTranslationPaths();
foreach ($paths as $index => $path) {
if (is_string($path)) {
$paths[$index] = ['path' => $path, 'priority' => 0];
}
}
usort($paths, fn($a, $b) => $b['priority'] - $a['priority']);
$this->set('translation_paths', $paths);
// Validate current language
$currentLanguage = $this->getCurrentLanguage();
if (!in_array($currentLanguage, array_keys($languages))) {
$this->setCurrentLanguage($this->getDefaultLanguage());
}
}
/**
* Get nested value from array.
*/
protected function getNestedValue(array $array, string $key, mixed $default = null): mixed
{
$keys = explode('.', $key);
$current = $array;
foreach ($keys as $k) {
if (!is_array($current) || !array_key_exists($k, $current)) {
return $default;
}
$current = $current[$k];
}
return $current;
}
/**
* Set nested value in array.
*/
protected function setNestedValue(array &$array, string $key, mixed $value): void
{
$keys = explode('.', $key);
$current = &$array;
foreach ($keys as $k) {
if (!is_array($current)) {
$current = [];
}
if (!array_key_exists($k, $current)) {
$current[$k] = [];
}
$current = &$current[$k];
}
$current = $value;
}
/**
* Check if nested value exists.
*/
protected function hasNestedValue(array $array, string $key): bool
{
$keys = explode('.', $key);
$current = $array;
foreach ($keys as $k) {
if (!is_array($current) || !array_key_exists($k, $current)) {
return false;
}
$current = $current[$k];
}
return true;
}
/**
* Ensure configuration is loaded.
*/
protected function ensureLoaded(): void
{
if (!$this->loaded) {
$this->load();
}
}
/**
* Reload configuration.
*/
public function reload(): void
{
$this->loaded = false;
$this->cache->clear();
$this->load();
}
/**
* Save configuration to file.
*/
public function save(string $filename = null): bool
{
$this->ensureLoaded();
$filename = $filename ?? $this->get('config_file', 'i18n.php');
$content = '<?php return ' . var_export($this->config, true) . ';';
return file_put_contents($filename, $content) !== false;
}
/**
* Export configuration.
*/
public function export(string $format = 'php'): string
{
$this->ensureLoaded();
return match ($format) {
'php' => '<?php return ' . var_export($this->config, true) . ';',
'json' => json_encode($this->config, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
'yaml' => $this->toYaml($this->config),
default => throw new \InvalidArgumentException("Unsupported export format: {$format}")
};
}
/**
* Import configuration.
*/
public function import(string $data, string $format = 'php'): void
{
$config = match ($format) {
'php' => include 'data://text/plain,' . urlencode($data),
'json' => json_decode($data, true),
'yaml' => $this->fromYaml($data),
default => throw new \InvalidArgumentException("Unsupported import format: {$format}")
};
if (!is_array($config)) {
throw new \InvalidArgumentException('Invalid configuration data');
}
$this->config = array_merge($this->defaults, $config);
$this->processConfig();
$this->cache->clear();
}
/**
* Convert to YAML.
*/
protected function toYaml(array $data, int $depth = 0): string
{
$yaml = '';
$indent = str_repeat(' ', $depth);
foreach ($data as $key => $value) {
if (is_array($value)) {
$yaml .= "{$indent}{$key}:\n";
$yaml .= $this->toYaml($value, $depth + 1);
} else {
$escapedValue = str_replace(["\n", "\r"], ['\\n', '\\r'], (string) $value);
$yaml .= "{$indent}{$key}: \"{$escapedValue}\"\n";
}
}
return $yaml;
}
/**
* Parse from YAML.
*/
protected function fromYaml(string $yaml): array
{
$data = [];
$lines = explode("\n", $yaml);
$stack = [&$data];
$currentIndent = 0;
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || str_starts_with($line, '#')) {
continue;
}
$indent = strlen($line) - strlen(ltrim($line));
$line = trim($line);
if (preg_match('/^(\w+):\s*$/', $line, $matches)) {
$key = $matches[1];
$newArray = [];
// Adjust stack depth
while ($indent < $currentIndent) {
array_pop($stack);
$currentIndent -= 2;
}
$current = &$stack[count($stack) - 1];
$current[$key] = &$newArray;
$stack[] = &$newArray;
$currentIndent = $indent;
} elseif (preg_match('/^(\w+):\s*"(.*)"$/', $line, $matches)) {
$key = $matches[1];
$value = stripslashes($matches[2]);
// Adjust stack depth
while ($indent < $currentIndent) {
array_pop($stack);
$currentIndent -= 2;
}
$current = &$stack[count($stack) - 1];
$current[$key] = $value;
}
}
return $data;
}
/**
* Validate configuration.
*/
public function validate(): array
{
$this->ensureLoaded();
return $this->validator->validate($this->config);
}
/**
* Get configuration summary.
*/
public function getSummary(): array
{
$this->ensureLoaded();
return [
'environment' => $this->currentEnvironment,
'default_language' => $this->getDefaultLanguage(),
'current_language' => $this->getCurrentLanguage(),
'fallback_language' => $this->getFallbackLanguage(),
'supported_languages' => count($this->getSupportedLanguages()),
'translation_paths' => count($this->getTranslationPaths()),
'cache_enabled' => $this->get('cache.enabled', false),
'timezone' => $this->get('timezone.default', 'UTC'),
'currency' => $this->get('currency.default', 'USD')
];
}
/**
* Get default configuration.
*/
protected function getDefaultConfig(): array
{
return [
'environment' => 'production',
'default_language' => 'en',
'fallback_language' => 'en',
'current_language' => 'en',
'supported_languages' => [
'en' => [
'name' => 'English',
'native_name' => 'English',
'direction' => 'ltr',
'enabled' => true
]
],
'translation_paths' => [
['path' => 'resources/lang', 'priority' => 100]
],
'cache' => [
'enabled' => true,
'driver' => 'file',
'prefix' => 'i18n',
'ttl' => 3600
],
'switcher' => [
'strategies' => ['url', 'parameter', 'header', 'session', 'cookie'],
'url_parameter' => 'lang',
'cookie_name' => 'language',
'cookie_expires' => 86400 * 30,
'track_switches' => true
],
'fallback' => [
'enabled' => true,
'chains' => [],
'max_depth' => 5
],
'timezone' => [
'default' => 'UTC',
'allow_user_override' => true,
'supported_timezones' => []
],
'currency' => [
'default' => 'USD',
'allow_user_override' => true,
'supported_currencies' => [],
'precision' => 2,
'decimal_separator' => '.',
'thousands_separator' => ','
],
'datetime' => [
'date_format' => 'Y-m-d',
'time_format' => 'H:i:s',
'datetime_format' => 'Y-m-d H:i:s',
'timezone' => 'UTC',
'locale' => 'en'
],
'number' => [
'decimal_separator' => '.',
'thousands_separator' => ',',
'precision' => 2
],
'validation' => [
'strict_mode' => false,
'log_missing' => true,
'throw_on_missing' => false
]
];
}
/**
* Create I18n config manager instance.
*/
public static function create(array $config = []): self
{
return new self($config);
}
/**
* Create for development environment.
*/
public static function forDevelopment(): self
{
return new self([
'environment' => 'development',
'cache' => ['enabled' => false],
'validation' => ['strict_mode' => true, 'log_missing' => true]
]);
}
/**
* Create for testing environment.
*/
public static function forTesting(): self
{
return new self([
'environment' => 'testing',
'cache' => ['enabled' => false],
'validation' => ['strict_mode' => true]
]);
}
/**
* Create for production environment.
*/
public static function forProduction(): self
{
return new self([
'environment' => 'production',
'cache' => ['enabled' => true, 'ttl' => 7200],
'validation' => ['strict_mode' => false, 'log_missing' => false]
]);
}
}