Compare commits

..

No commits in common. "master" and "33454/feat-site-settings-for-business-settings" have entirely different histories.

8 changed files with 193 additions and 878 deletions

276
bin/cfg
View file

@ -22,12 +22,7 @@ foreach ($autoloadFiles as $autoloadFile) {
} }
} }
function verboseLog(bool $enabled, string $message): void $version = '0.3';
{
if ($enabled) fwrite(STDERR, "[verbose] $message".PHP_EOL);
}
$version = '0.4';
$actions = [ 'show', 'write', 'help' ]; $actions = [ 'show', 'write', 'help' ];
$settings = ['key' => '', 'value' => '']; $settings = ['key' => '', 'value' => ''];
@ -40,55 +35,15 @@ $collection = (new Input\InputCollection())
->description('Display help text') ->description('Display help text')
) // }}} ) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('verbose')->short('v') // {{{ ->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL) ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
->description('Print debug details to STDERR') ->description('Path to a json data file to to read')
) // }}} ) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('in')->short('i') // {{{ ->add( Input\InputTypeFactory::build('LongOption')->name('out')->short('o') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
->description('Path to a JSON data file to read (for write)') ->description('Path to a json file to to write to')
->validator(new Input\Validator( ) // }}}
function (AbstractInputType $input, AbstractInputHandler $context)
{
$path = $context->find('in');
if ($path === null || $path === '') {
return null;
}
if (!is_readable($path))
{
throw new \Exception("Input file is not readable: $path");
}
$content = file_get_contents($path);
if ($content === false)
{
throw new \Exception("Can not read input file: $path");
}
try
{
$data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
}
catch (\JsonException $ex)
{
throw new \Exception(sprintf(
'Input JSON is invalid (%s): %s',
(string)$ex->getCode(),
$ex->getMessage()
));
}
if (!is_array($data))
{
throw new \Exception('Input JSON must decode to an object/array');
}
return $data;
}
))
) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('mode')->short('m') // {{{ ->add( Input\InputTypeFactory::build('LongOption')->name('mode')->short('m') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED) ->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
@ -105,60 +60,6 @@ $collection = (new Input\InputCollection())
->description('Path where the config/ directory of the package conf files is located, defaults to the working dir') ->description('Path where the config/ directory of the package conf files is located, defaults to the working dir')
) // }}} ) // }}}
->add( Input\InputTypeFactory::build('LongOption')->name('siteDir')->short('s') // {{{
->flags(AbstractInputType::FLAG_OPTIONAL | Input\AbstractInputType::FLAG_VALUE_REQUIRED)
->description('Site/instance directory below config/. Accepts owner_xyz or config/owner_xyz')
->validator(new Input\Validator(
function (AbstractInputType $input, AbstractInputHandler $context)
{
$siteInput = $context->find('siteDir');
if ($siteInput === null || $siteInput === false) {
return null;
}
$siteInput = trim((string)$siteInput);
$appPath = $context->find('appPath');
if (!$appPath) {
$appPath = getcwd().'/';
}
$appPath = rtrim($appPath, '/').'/';
// Accept both "owner_xyz" and "config/owner_xyz" and normalize to site key.
if (str_starts_with($siteInput, $appPath.'config/'))
{
$siteInput = substr($siteInput, strlen($appPath.'config/'));
}
elseif (str_starts_with($siteInput, 'config/'))
{
$siteInput = substr($siteInput, strlen('config/'));
}
$siteInput = trim($siteInput, '/');
if ($siteInput === '')
{
throw new \Exception('Option --siteDir is empty.');
}
// Block directory traversal and hidden-dot segments.
if (str_contains($siteInput, '..') || preg_match('~(^|/)\.(?:/|$)~', $siteInput))
{
throw new \Exception("Invalid directory in --siteDir: '$siteInput'.");
}
// Allow only predictable path segments for instance directories.
foreach (explode('/', $siteInput) as $part)
{
if ($part === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $part))
{
throw new \Exception("Invalid directory name segment '$part' in --siteDir.");
}
}
return $siteInput;
}
))
) // }}}
->add( Input\InputTypeFactory::build('Argument')->name('action') // {{{ ->add( Input\InputTypeFactory::build('Argument')->name('action') // {{{
->flags(AbstractInputType::FLAG_REQUIRED) ->flags(AbstractInputType::FLAG_REQUIRED)
->description( ->description(
@ -201,30 +102,18 @@ $collection = (new Input\InputCollection())
->description( ->description(
'the settings you want to work on. With action "write" you can pass the value to set as JSON after an equal sign.' 'the settings you want to work on. With action "write" you can pass the value to set as JSON after an equal sign.'
) )
->validator(new Input\Validator( ->validator(new Input\Validator(
function (AbstractInputType $input, AbstractInputHandler $context) function (AbstractInputType $input, AbstractInputHandler $context)
{ {
$setting = $context->find('setting'); $setting = $context->find('setting');
$action = $context->find('action'); $action = $context->find('action');
$inputPayload = $context->find('in');
if ($action === 'write')
{
if (($setting !== null && $setting !== '') && $inputPayload) {
throw new \Exception('Please use either SETTING or --in, not both.');
}
}
if ($setting === null || $setting === '') {
return ['key' => '', 'value' => null];
}
$setting = explode('=', $setting); $setting = explode('=', $setting);
$settings['key'] = $setting[0]; $settings['key'] = $setting[0];
if ($action === 'write') if ($action === 'write')
{ {
$value = $setting[1] ?? null; $value = $setting[1];
if (! (isset($value) || $context->find('in'))) { if (! (isset($value) || $context->find('data'))) {
throw new \Exception('You need a value to write'); throw new \Exception('You need a value to write');
} }
$specialValues = [ 'true', 'false', 'null' ]; $specialValues = [ 'true', 'false', 'null' ];
@ -250,50 +139,18 @@ $collection = (new Input\InputCollection())
$usage = Cli\manpage( basename(__FILE__), $version, $usage = Cli\manpage( basename(__FILE__), $version,
'read write settings', 'read write settings',
$collection, Colour::FG_GREEN, Colour::FG_WHITE, $collection, Colour::FG_GREEN, Colour::FG_WHITE,
[ [
'Examples' => 'Examples' =>
'Basic usage:' 'cfg show VeruA db:host'.PHP_EOL.
.PHP_EOL 'cfg write VeruA \'db:host="newHost"\''.PHP_EOL
.'cfg show VeruA db:host' ]
.PHP_EOL
.'cfg write VeruA \'db:host="newHost"\''
.PHP_EOL
.PHP_EOL
.'Advance usage:'
.PHP_EOL
.'cfg write prefix \'module:enabled=true\''
.PHP_EOL
.PHP_EOL
.'#Site specific write:'
.PHP_EOL
.'cfg write prefix \'module:enabled=true\' --siteDir=owner_xyz'
.PHP_EOL
.PHP_EOL
.'#Batch write from JSON file:'
.PHP_EOL
.'cfg write prefix --siteDir=owner_xyz -i /tmp/extension.json'
.PHP_EOL
.PHP_EOL
.'#Batch write from JSON string:'
.PHP_EOL
.'cfg write prefix \'module={"enabled":true,"timeout":30,"label":"example"}\' --siteDir=owner_xyz'
.PHP_EOL
.PHP_EOL
.'#Read merged value for a site:'
.PHP_EOL
.'cfg show prefix module:enabled --siteDir=owner_xyz'
.PHP_EOL
]
).PHP_EOL; ).PHP_EOL;
// Get the supplied input. Passing the collection will make the handler bind values // Get the supplied input. Passing the collection will make the handler bind values
// and validate the input according to our collection // and validate the input according to our collection
try try {
{
$argv = Input\InputHandlerFactory::build('Argv', $collection); $argv = Input\InputHandlerFactory::build('Argv', $collection);
} } catch (\Exception $ex) {
catch (\Exception $ex)
{
echo $usage; echo $usage;
if (isset($argv[1])) { if (isset($argv[1])) {
@ -315,41 +172,42 @@ if ($argv->find( 'help' ) || $argv->find('action') == 'help')
$prefix = $argv->find('prefix'); $prefix = $argv->find('prefix');
$verbose = (bool)$argv->find('verbose'); /*
echo $argv->find('action').PHP_EOL;
echo ($prefix).PHP_EOL;
echo $argv->find('setting')['key'].PHP_EOL;
echo $argv->find('setting')['value'].PHP_EOL;
*/
// var_dump($cfg);
$appPath = $argv->find('appPath'); $appPath = $argv->find('appPath');
if (!$appPath) $appPath = getcwd().'/'; if (!$appPath) $appPath = getcwd().'/';
$appPath = rtrim($appPath, '/').'/';
/* $it = new RecursiveDirectoryIterator($appPath);
foreach(new RecursiveIteratorIterator($it) as $file)
{
$configDir = $file->getPath();
if ($file->isDir() && $file->getFilename() == '.' && basename($configDir) == 'config') {
echo "found config dir: $configDir\n";
}
}
*/
$mode = ($argv->find('mode') == '') ? null : $argv->find('mode'); $mode = ($argv->find('mode') == '') ? null : $argv->find('mode');
$cfg = (new Settings([], $mode))->appPath($appPath)->prefix($prefix); $cfg = (new Settings([], $mode))->appPath($appPath)->prefix($prefix);
// pkgPath points to package defaults (e.g. <prefix>.default.conf.php) if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath($pkgPath);
if ($pkgPath = $argv->find('pkgPath')) $cfg->pkgPath(rtrim($pkgPath, '/').'/');
$site = null; try {
$siteFlag = 0x01;
$site = $argv->find('siteDir');
if ($site !== null && $site !== false)
{
// Reuse util-settings site resolution: config/<site>/<prefix>.conf.php
$cfg->site($site);
verboseLog($verbose, "siteDir resolved to '$site'");
}
try
{
if (is_readable($cfg->buildFileName('default'))) { if (is_readable($cfg->buildFileName('default'))) {
$cfg->load(); $cfg->load();
} }
elseif (is_readable($cfgFile = $cfg->buildFileName())) { elseif (is_readable($cfgFile = $cfg->buildFileName())) {
$cfg->load(require($cfgFile)); $cfg->load(require($cfgFile));
} }
} } catch (Exception $e) {
catch (\Throwable $e)
{
fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL); fwrite(STDERR, "Error: ".$e->getMessage().PHP_EOL);
exit(1); exit(1);
} }
verboseLog($verbose, 'config bootstrap loaded'); //var_dump($cfg);
$result = $cfg; $result = $cfg;
$settings = $argv->find('setting') ?? $settings; $settings = $argv->find('setting') ?? $settings;
@ -374,63 +232,33 @@ if ($result instanceof Settings) $result = $result->toArray();
switch ($argv->find('action')) switch ($argv->find('action'))
{ {
case 'show': case 'show':
verboseLog($verbose, "show action for prefix '$prefix'");
$out = (is_string($result)) ? $result : json_encode($result, JSON_PRETTY_PRINT); $out = (is_string($result)) ? $result : json_encode($result, JSON_PRETTY_PRINT);
echo $out.PHP_EOL; echo $out.PHP_EOL;
break; break;
case 'write': case 'write':
$inputPayload = $argv->find('in');
if (!$inputPayload && $settings['key'] === '')
{
fwrite(STDERR, 'Nothing to write: provide SETTING or --in.'.PHP_EOL);
exit(1);
}
$path = ($settings['key'] !== '') ? explode(':', $settings['key']) : []; $path = ($settings['key'] !== '') ? explode(':', $settings['key']) : [];
verboseLog($verbose, 'write source: '.($inputPayload ? '--in' : 'SETTING')); var_dump($path);
$setting2write = $settings['value'];
if ($inputPayload)
{
$setting2write = $inputPayload;
}
else
{
$setting2write = $settings['value'];
}
verboseLog($verbose, 'write path: '.json_encode($path));
while ( ! empty($path)) while ( ! empty($path))
{ {
$setting2write = [array_pop($path) => $setting2write]; $setting2write = [array_pop($path) => $setting2write];
} }
if (is_readable($file = $cfg->buildFileName()))
$writeType = ($site !== null && $site !== false) ? $siteFlag : null;
$file = $cfg->buildFileName($writeType);
verboseLog($verbose, "write target: $file");
if (is_readable($file))
{ {
$setting2write = array_replace_recursive(require($file), $setting2write); $setting2write = array_replace_recursive(require($file), $setting2write);
copy($file, "$file.bak"); copy($file, "$file.bak");
verboseLog($verbose, "existing config merged from: $file");
} }
$targetDir = dirname($file);
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir))
{
fwrite(STDERR, "Can not create directory: $targetDir".PHP_EOL);
exit(1);
}
$writeCfg = $cfg->create($setting2write); $writeCfg = $cfg->create($setting2write);
verboseLog($verbose, 'payload prepared for writer'); // var_dump($writeCfg->toArray());
try try {
{ (new SettingsWriter($writeCfg))->write();
(new SettingsWriter($writeCfg, '', $writeType))->write();
echo "Written modified settings to: $file".PHP_EOL; echo "Written modified settings to: $file".PHP_EOL;
} }
catch (\Exception $e) { catch (\Exception $e) {
fwrite(STDERR, $e->getMessage().PHP_EOL); echo $e->getMessage().PHP_EOL;
exit(1);
} }
break; break;

View file

@ -1,7 +1,6 @@
{ {
"name": "rabe/util-settings", "name": "rabe/util-settings",
"description": "Package for reading and writing configuration settings", "description": "Package for reading and writing configuration settings",
"bin": ["bin/cfg"],
"license": "AGPL3", "license": "AGPL3",
"authors": [ "authors": [
{ {

265
composer.lock generated
View file

@ -498,30 +498,30 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "doctrine/instantiator", "name": "doctrine/instantiator",
"version": "2.0.0", "version": "1.5.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/doctrine/instantiator.git", "url": "https://github.com/doctrine/instantiator.git",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^8.1" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^11", "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*", "ext-pdo": "*",
"ext-phar": "*", "ext-phar": "*",
"phpbench/phpbench": "^1.2", "phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.9.4", "phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1.3", "phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^9.5.27", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^5.4" "vimeo/psalm": "^4.30 || ^5.4"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -548,7 +548,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/doctrine/instantiator/issues", "issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/2.0.0" "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
}, },
"funding": [ "funding": [
{ {
@ -564,20 +564,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2022-12-30T00:23:10+00:00" "time": "2022-12-30T00:15:36+00:00"
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.12.1", "version": "1.11.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -585,12 +585,11 @@
}, },
"conflict": { "conflict": {
"doctrine/collections": "<1.6.8", "doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2" "doctrine/common": "<2.13.3 || >=3,<3.2.2"
}, },
"require-dev": { "require-dev": {
"doctrine/collections": "^1.6.8", "doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2", "doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
}, },
"type": "library", "type": "library",
@ -616,7 +615,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/myclabs/DeepCopy/issues", "issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
}, },
"funding": [ "funding": [
{ {
@ -624,31 +623,29 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-11-08T17:47:46+00:00" "time": "2023-03-08T13:26:56+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v5.4.0", "version": "v4.15.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494" "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494", "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*", "ext-tokenizer": "*",
"php": ">=7.4" "php": ">=7.0"
}, },
"require-dev": { "require-dev": {
"ircmaxell/php-yacc": "^0.0.7", "ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^9.0" "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
}, },
"bin": [ "bin": [
"bin/php-parse" "bin/php-parse"
@ -656,7 +653,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "5.0-dev" "dev-master": "4.9-dev"
} }
}, },
"autoload": { "autoload": {
@ -680,27 +677,26 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
}, },
"time": "2024-12-30T11:07:19+00:00" "time": "2023-03-05T19:49:14+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
"version": "2.0.4", "version": "2.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phar-io/manifest.git", "url": "https://github.com/phar-io/manifest.git",
"reference": "54750ef60c58e43759730615a392c31c80e23176" "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
"reference": "54750ef60c58e43759730615a392c31c80e23176", "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*",
"ext-phar": "*", "ext-phar": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"phar-io/version": "^3.0.1", "phar-io/version": "^3.0.1",
@ -741,15 +737,9 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": { "support": {
"issues": "https://github.com/phar-io/manifest/issues", "issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/2.0.4" "source": "https://github.com/phar-io/manifest/tree/2.0.3"
}, },
"funding": [ "time": "2021-07-20T11:28:43+00:00"
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:33:53+00:00"
}, },
{ {
"name": "phar-io/version", "name": "phar-io/version",
@ -804,35 +794,35 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.32", "version": "9.2.26",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5" "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5", "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"nikic/php-parser": "^4.19.1 || ^5.1.0", "nikic/php-parser": "^4.15",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.6", "phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.4", "phpunit/php-text-template": "^2.0.2",
"sebastian/code-unit-reverse-lookup": "^2.0.3", "sebastian/code-unit-reverse-lookup": "^2.0.2",
"sebastian/complexity": "^2.0.3", "sebastian/complexity": "^2.0",
"sebastian/environment": "^5.1.5", "sebastian/environment": "^5.1.2",
"sebastian/lines-of-code": "^1.0.4", "sebastian/lines-of-code": "^1.0.3",
"sebastian/version": "^3.0.2", "sebastian/version": "^3.0.1",
"theseer/tokenizer": "^1.2.3" "theseer/tokenizer": "^1.2.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.6" "phpunit/phpunit": "^9.3"
}, },
"suggest": { "suggest": {
"ext-pcov": "PHP extension that provides line coverage", "ext-pcov": "PHP extension that provides line coverage",
@ -841,7 +831,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "9.2.x-dev" "dev-master": "9.2-dev"
} }
}, },
"autoload": { "autoload": {
@ -869,8 +859,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
}, },
"funding": [ "funding": [
{ {
@ -878,7 +867,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-08-22T04:23:01+00:00" "time": "2023-03-06T12:58:08+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -1123,45 +1112,45 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.6.22", "version": "9.6.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.5.0 || ^2", "doctrine/instantiator": "^1.3.1 || ^2",
"ext-dom": "*", "ext-dom": "*",
"ext-json": "*", "ext-json": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.12.1", "myclabs/deep-copy": "^1.10.1",
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.0.2",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-code-coverage": "^9.2.32", "phpunit/php-code-coverage": "^9.2.13",
"phpunit/php-file-iterator": "^3.0.6", "phpunit/php-file-iterator": "^3.0.5",
"phpunit/php-invoker": "^3.1.1", "phpunit/php-invoker": "^3.1.1",
"phpunit/php-text-template": "^2.0.4", "phpunit/php-text-template": "^2.0.3",
"phpunit/php-timer": "^5.0.3", "phpunit/php-timer": "^5.0.2",
"sebastian/cli-parser": "^1.0.2", "sebastian/cli-parser": "^1.0.1",
"sebastian/code-unit": "^1.0.8", "sebastian/code-unit": "^1.0.6",
"sebastian/comparator": "^4.0.8", "sebastian/comparator": "^4.0.8",
"sebastian/diff": "^4.0.6", "sebastian/diff": "^4.0.3",
"sebastian/environment": "^5.1.5", "sebastian/environment": "^5.1.3",
"sebastian/exporter": "^4.0.6", "sebastian/exporter": "^4.0.5",
"sebastian/global-state": "^5.0.7", "sebastian/global-state": "^5.0.1",
"sebastian/object-enumerator": "^4.0.4", "sebastian/object-enumerator": "^4.0.3",
"sebastian/resource-operations": "^3.0.4", "sebastian/resource-operations": "^3.0.3",
"sebastian/type": "^3.2.1", "sebastian/type": "^3.2",
"sebastian/version": "^3.0.2" "sebastian/version": "^3.0.2"
}, },
"suggest": { "suggest": {
@ -1205,8 +1194,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
}, },
"funding": [ "funding": [
{ {
@ -1222,20 +1210,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-05T13:48:26+00:00" "time": "2023-03-09T06:34:10+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
"version": "1.0.2", "version": "1.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git", "url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1270,7 +1258,7 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser", "homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues", "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
}, },
"funding": [ "funding": [
{ {
@ -1278,7 +1266,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:27:43+00:00" "time": "2020-09-28T06:08:49+00:00"
}, },
{ {
"name": "sebastian/code-unit", "name": "sebastian/code-unit",
@ -1467,20 +1455,20 @@
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
"version": "2.0.3", "version": "2.0.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git", "url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.18 || ^5.0", "nikic/php-parser": "^4.7",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -1512,7 +1500,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity", "homepage": "https://github.com/sebastianbergmann/complexity",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues", "issues": "https://github.com/sebastianbergmann/complexity/issues",
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
}, },
"funding": [ "funding": [
{ {
@ -1520,20 +1508,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-12-22T06:19:30+00:00" "time": "2020-10-26T15:52:27+00:00"
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
"version": "4.0.6", "version": "4.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/diff.git", "url": "https://github.com/sebastianbergmann/diff.git",
"reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1578,7 +1566,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/diff/issues", "issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
}, },
"funding": [ "funding": [
{ {
@ -1586,7 +1574,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:30:58+00:00" "time": "2020-10-26T13:10:38+00:00"
}, },
{ {
"name": "sebastian/environment", "name": "sebastian/environment",
@ -1653,16 +1641,16 @@
}, },
{ {
"name": "sebastian/exporter", "name": "sebastian/exporter",
"version": "4.0.6", "version": "4.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git", "url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1718,7 +1706,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues", "issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
}, },
"funding": [ "funding": [
{ {
@ -1726,20 +1714,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:33:00+00:00" "time": "2022-09-14T06:03:37+00:00"
}, },
{ {
"name": "sebastian/global-state", "name": "sebastian/global-state",
"version": "5.0.7", "version": "5.0.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git", "url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1782,7 +1770,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues", "issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
}, },
"funding": [ "funding": [
{ {
@ -1790,24 +1778,24 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-02T06:35:11+00:00" "time": "2022-02-14T08:28:10+00:00"
}, },
{ {
"name": "sebastian/lines-of-code", "name": "sebastian/lines-of-code",
"version": "1.0.4", "version": "1.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git", "url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.18 || ^5.0", "nikic/php-parser": "^4.6",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -1839,7 +1827,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code", "homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
}, },
"funding": [ "funding": [
{ {
@ -1847,7 +1835,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-12-22T06:20:34+00:00" "time": "2020-11-28T06:42:11+00:00"
}, },
{ {
"name": "sebastian/object-enumerator", "name": "sebastian/object-enumerator",
@ -2026,16 +2014,16 @@
}, },
{ {
"name": "sebastian/resource-operations", "name": "sebastian/resource-operations",
"version": "3.0.4", "version": "3.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git", "url": "https://github.com/sebastianbergmann/resource-operations.git",
"reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2047,7 +2035,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.0-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -2068,7 +2056,8 @@
"description": "Provides a list of PHP built-in functions that operate on resources", "description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations", "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": { "support": {
"source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
}, },
"funding": [ "funding": [
{ {
@ -2076,7 +2065,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-14T16:00:52+00:00" "time": "2020-09-28T06:45:17+00:00"
}, },
{ {
"name": "sebastian/type", "name": "sebastian/type",
@ -2189,16 +2178,16 @@
}, },
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
"version": "1.2.3", "version": "1.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/theseer/tokenizer.git", "url": "https://github.com/theseer/tokenizer.git",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2227,7 +2216,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": { "support": {
"issues": "https://github.com/theseer/tokenizer/issues", "issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.3" "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
}, },
"funding": [ "funding": [
{ {
@ -2235,7 +2224,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-03T12:36:25+00:00" "time": "2021-07-28T10:34:58+00:00"
} }
], ],
"aliases": [ "aliases": [

View file

@ -159,18 +159,7 @@ class Settings implements \Iterator, \Countable
// if a mode was set in the constructor do not overwrite it // if a mode was set in the constructor do not overwrite it
if (! isset($this->mode)) { if (! isset($this->mode)) {
// if a localConf Mode is set use it, or take the default conf mode // if a localConf Mode is set use it, or take the default conf mode
// if local/default config has no mode, fall back to the first known mode (normally "prod") $this->mode = (isset($localConf['mode'])) ? $localConf['mode'] : $this->settings['mode'];
if (isset($localConf['mode']) && is_string($localConf['mode']) && $localConf['mode'] !== '') {
$this->mode = $localConf['mode'];
}
elseif (isset($this->settings['mode']) && is_string($this->settings['mode']) && $this->settings['mode'] !== '') {
$this->mode = $this->settings['mode'];
}
else {
// Backward-compatible fallback for configs without explicit mode.
$this->mode = (string)array_key_first($this->modes);
$this->settings['mode'] = $this->mode;
}
} else { } else {
$this->settings['mode'] = $this->mode; $this->settings['mode'] = $this->mode;
} }
@ -281,7 +270,7 @@ class Settings implements \Iterator, \Countable
* Rewind the Iterator to the first element * Rewind the Iterator to the first element
* @link https://www.php.net/iterator.rewind * @link https://www.php.net/iterator.rewind
*/ */
public function rewind(): void public function rewind()
{ {
reset( $this->settings); reset( $this->settings);
} }
@ -290,7 +279,7 @@ class Settings implements \Iterator, \Countable
* Return the current element * Return the current element
* @link https://www.php.net/iterator.current * @link https://www.php.net/iterator.current
*/ */
public function current(): mixed public function current()
{ {
return current( $this->settings ); return current( $this->settings );
} }
@ -299,7 +288,7 @@ class Settings implements \Iterator, \Countable
* Return the key of the current element * Return the key of the current element
* @link https://www.php.net/iterator.key * @link https://www.php.net/iterator.key
*/ */
public function key(): mixed public function key()
{ {
return key( $this->settings ); return key( $this->settings );
} }
@ -308,16 +297,16 @@ class Settings implements \Iterator, \Countable
* Move forward to next element * Move forward to next element
* @link https://www.php.net/iterator.next * @link https://www.php.net/iterator.next
*/ */
public function next(): void public function next()
{ {
next( $this->settings ); return next( $this->settings );
} }
/** /**
* Checks if current position is valid * Checks if current position is valid
* @link https://www.php.net/iterator.valid * @link https://www.php.net/iterator.valid
*/ */
public function valid(): bool public function valid()
{ {
$key = key( $this->settings ); $key = key( $this->settings );
return ($key !== null && $key !== false); return ($key !== null && $key !== false);
@ -330,9 +319,9 @@ class Settings implements \Iterator, \Countable
* Count elements * Count elements
* @link https://www.php.net/countable.count.php * @link https://www.php.net/countable.count.php
*/ */
public function count(): int public function count()
{ {
return count($this->settings); count($this->settings);
} }
// }}} // }}}
@ -369,4 +358,4 @@ class Settings implements \Iterator, \Countable
/* jEdit buffer local properties {{{ /* jEdit buffer local properties {{{
* :folding=explicit:collapseFolds=1: * :folding=explicit:collapseFolds=1:
}}}*/ }}}*/

View file

@ -22,8 +22,6 @@
declare(strict_types=1); declare(strict_types=1);
namespace rabe\Util; namespace rabe\Util;
use ErrorException;
/** /**
* Write Settings into a File * Write Settings into a File
* @author Norbert.e.Wagner dev@norb.me * @author Norbert.e.Wagner dev@norb.me
@ -47,16 +45,13 @@ class SettingsWriter
* *
* @param Settings $settings an object of type settings * @param Settings $settings an object of type settings
* @param String $name The name midfix for the settings File * @param String $name The name midfix for the settings File
* @param String|int|null $type Optional type for Settings::buildFileName(), e.g. site flag */
*/ public function __construct( Settings $settings, $name='' )
public function __construct( Settings $settings, $name='', $type=null )
{ {
$this->settings = $settings; $this->settings = $settings;
$file = ( $type === null ) $file = $settings->buildFileName( $name );
? $settings->buildFileName( $name )
: $settings->buildFileName( $type );
if ( ! $this->handle = fopen( $file, 'w' ) ) if ( ! $this->handle = fopen( $file, 'w' ) )
{ {
throw new ErrorException( "Can not open File: $file" ); throw new ErrorException( "Can not open File: $file" );

View file

@ -1,457 +0,0 @@
<?php
declare(strict_types=1);
namespace rabe\Util\tests;
use PHPUnit\Framework\TestCase;
class CfgTest extends TestCase
{
private string $tmpDir;
/* Lifecycle {{{ */
protected function setUp(): void
{
$this->tmpDir = rtrim(sys_get_temp_dir(), '/').'/util-settings-cfg-'.bin2hex(random_bytes(8));
mkdir($this->tmpDir, 0775, true);
mkdir($this->tmpDir.'/config', 0775, true);
file_put_contents(
$this->tmpDir.'/config/Extension.default.conf.php',
"<?php return [\n\t'mode' => 'prod',\n\t'module' => [\n\t\t'code' => '',\n\t\t'label' => '',\n\t\t'flags' => [\n\t\t\t'enabled' => false,\n\t\t],\n\t],\n\t'feature' => [\n\t\t'endpoint' => '',\n\t],\n];\n"
);
}
protected function tearDown(): void
{
$this->removeDir($this->tmpDir);
}
/* }}} */
/* Write Flow {{{ */
public function testWriteWithoutDirectoryNameUsesLocalConfigFile(): void
{
$result = $this->runWrite([
'module:code="X100"',
]);
$this->assertCfgSuccess($result);
$localFile = $this->tmpDir.'/config/Extension.conf.php';
$this->assertFileExists($localFile);
$cfg = require $localFile;
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteWithoutModeFallsBackToDefaultMode(): void
{
file_put_contents(
$this->tmpDir.'/config/Extension.default.conf.php',
"<?php return [\n\t'module' => [\n\t\t'code' => '',\n\t],\n];\n"
);
$result = $this->runWrite([
'module:code="X100"',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/Extension.conf.php');
}
public function testWriteWithInputFileWritesMultipleSettings(): void
{
$inFile = $this->createJsonInputFile([
'module' => [
'code' => 'X100',
'label' => 'demo-module',
],
'feature' => [
'endpoint' => 'https://example.invalid/v1/resource',
],
]);
$result = $this->runWrite([
'--siteDir=owner_xyz',
'-i',
$inFile,
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
$this->assertSame('demo-module', $cfg['module']['label']);
$this->assertSame('https://example.invalid/v1/resource', $cfg['feature']['endpoint']);
}
public function testWriteAcceptsJsonStringValueInSetting(): void
{
$result = $this->runWrite([
'module={"code":"X100","label":"demo-module"}',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
$this->assertSame('demo-module', $cfg['module']['label']);
}
public function testWriteSupportsNestedPathWithColonNotation(): void
{
$result = $this->runWrite([
'module:flags:enabled=true',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertTrue($cfg['module']['flags']['enabled']);
}
public function testWriteCoercesSimpleStringValueToJsonString(): void
{
$result = $this->runWrite([
'module:code=X100',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteRejectsInvalidJsonStringValue(): void
{
$result = $this->runWrite([
'module={"code":',
'--siteDir=owner_xyz',
]);
$this->assertCfgFailure($result, 'The value does not appear to be a valid JSON string.');
}
public function testWriteWithSiteDirCreatesAndWritesSiteConfig(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
$this->assertFileExists($siteFile);
$this->assertFileDoesNotExist($this->tmpDir.'/config/Extension.conf.php');
$cfg = require $siteFile;
$this->assertSame('X100', $cfg['module']['code']);
}
public function testWriteWithSiteDirMergesIntoExistingSiteConfig(): void
{
$firstWrite = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($firstWrite);
$secondWrite = $this->runWrite([
'module:label="demo-module"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($secondWrite);
$cfg = $this->loadWrittenConfig('owner_xyz');
$this->assertSame('X100', $cfg['module']['code']);
$this->assertSame('demo-module', $cfg['module']['label']);
}
public function testWriteWithSiteDirUsingConfigPrefixIsNormalized(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=config/owner_xyz',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
$this->assertFileDoesNotExist($this->tmpDir.'/config/config/owner_xyz/Extension.conf.php');
}
public function testWriteWithSiteDirUsingAbsoluteConfigPrefixIsNormalized(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir='.$this->tmpDir.'/config/owner_xyz',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
}
public function testWriteWithNestedSiteDirWritesToNestedSiteConfig(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz/sub_a',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/sub_a/Extension.conf.php');
}
public function testWriteWithSiteDirWritesToSiteConfig(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($result);
$this->assertFileExists($this->tmpDir.'/config/owner_xyz/Extension.conf.php');
}
public function testWriteToExistingSiteConfigCreatesBackup(): void
{
$firstWrite = $this->runWrite([
'module:code="first"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($firstWrite);
$secondWrite = $this->runWrite([
'module:code="second"',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($secondWrite);
$siteFile = $this->tmpDir.'/config/owner_xyz/Extension.conf.php';
$backupFile = $siteFile.'.bak';
$this->assertFileExists($backupFile);
$current = require $siteFile;
$backup = require $backupFile;
$this->assertSame('second', $current['module']['code']);
$this->assertSame('first', $backup['module']['code']);
}
/* }}} */
/* Show Flow {{{ */
public function testShowWithSiteReturnsSiteSpecificValue(): void
{
$this->runWrite([
'module:code="X100"',
'--siteDir=owner_xyz',
]);
$show = $this->runShow([
'module:code',
'--siteDir=owner_xyz',
]);
$this->assertCfgSuccess($show);
$this->assertStringContainsString('X100', $show['output']);
}
/* }}} */
/* Validation {{{ */
public function testInvalidActionReturnsError(): void
{
$result = $this->runCfg([
'-a',
$this->tmpDir,
'delete',
'Extension',
]);
$this->assertCfgFailure($result, "'delete' not found");
}
public function testSiteDirRejectsEmptyValue(): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir=',
]);
$this->assertCfgFailure($result, 'a value is required for --siteDir');
}
/**
* @dataProvider siteDirValidationDataProvider
*/
public function testSiteDirValidation(string $siteDir, string $expectedMessage): void
{
$result = $this->runWrite([
'module:code="X100"',
'--siteDir='.$siteDir,
]);
$this->assertCfgFailure($result, $expectedMessage);
}
/**
* @dataProvider inputFileValidationDataProvider
*/
public function testInputFileValidation(?string $fileContents, string $pathSuffix, string $expectedMessage): void
{
$inFile = $this->tmpDir.'/'.$pathSuffix;
if ($fileContents !== null) {
file_put_contents($inFile, $fileContents);
}
$result = $this->runWrite([
'-i',
$inFile,
]);
$this->assertCfgFailure($result, $expectedMessage);
}
/**
* @dataProvider writePayloadSourceValidationDataProvider
*/
public function testWritePayloadSourceValidation(array $extraArgs, string $expectedMessage): void
{
$inFile = $this->createJsonInputFile(['module' => ['code' => 'X100']]);
$args = array_merge(
[
'-a',
$this->tmpDir,
'write',
'Extension',
],
array_map(
fn (string $arg): string => $arg === '__INPUT_FILE__' ? $inFile : $arg,
$extraArgs
)
);
$result = $this->runCfg($args);
$this->assertCfgFailure($result, $expectedMessage);
}
public function siteDirValidationDataProvider(): array
{
return [
'traversal' => ['../owner_xyz', 'Invalid directory in --siteDir'],
'invalid segment' => ['owner xyz', "Invalid directory name segment 'owner xyz' in --siteDir."],
'hidden dot segment' => ['owner_xyz/./secret', "Invalid directory in --siteDir: 'owner_xyz/./secret'."],
'config root only' => ['config/', 'Option --siteDir is empty.'],
];
}
public function inputFileValidationDataProvider(): array
{
return [
'missing file' => [null, 'missing.json', 'Input file is not readable'],
'invalid json' => ['{"module":', 'invalid.json', 'Input JSON is invalid'],
'scalar json' => ['true', 'scalar.json', 'Input JSON must decode to an object/array'],
];
}
public function writePayloadSourceValidationDataProvider(): array
{
return [
'missing payload source' => [[], 'Nothing to write: provide SETTING or --in.'],
'both payload sources' => [['module:code="x"', '-i', '__INPUT_FILE__'], 'Please use either SETTING or --in, not both.'],
];
}
/* }}} */
/* Helpers {{{ */
private function runWrite(array $args): array
{
return $this->runCfg(array_merge([
'-a',
$this->tmpDir,
'write',
'Extension',
], $args));
}
private function runShow(array $args): array
{
return $this->runCfg(array_merge([
'-a',
$this->tmpDir,
'show',
'Extension',
], $args));
}
private function createJsonInputFile(array $contents, string $name = 'extension-in.json'): string
{
$path = $this->tmpDir.'/'.$name;
file_put_contents($path, json_encode($contents, JSON_PRETTY_PRINT));
return $path;
}
private function loadWrittenConfig(?string $siteDir = null): array
{
$path = $siteDir === null
? $this->tmpDir.'/config/Extension.conf.php'
: $this->tmpDir.'/config/'.$siteDir.'/Extension.conf.php';
return require $path;
}
private function assertCfgSuccess(array $result): void
{
$this->assertSame(0, $result['code'], $result['output']);
}
private function assertCfgFailure(array $result, string $message): void
{
$this->assertSame(1, $result['code'], $result['output']);
$this->assertStringContainsString($message, $result['output']);
}
private function runCfg(array $args): array
{
$script = realpath(__DIR__.'/../bin/cfg');
$this->assertNotFalse($script);
$command = escapeshellarg(PHP_BINARY).' '.escapeshellarg($script);
foreach ($args as $arg) {
$command .= ' '.escapeshellarg($arg);
}
$command .= ' 2>&1';
$outputLines = [];
$exitCode = 0;
exec($command, $outputLines, $exitCode);
return [
'code' => $exitCode,
'output' => implode(PHP_EOL, $outputLines),
];
}
private function removeDir(string $dir): void
{
if (!is_dir($dir)) {
return;
}
foreach (scandir($dir) ?: [] as $entry) {
if ($entry === '.' || $entry === '..') {
continue;
}
$path = $dir.'/'.$entry;
if (is_dir($path)) {
$this->removeDir($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
/* }}} */
}

View file

@ -192,15 +192,6 @@ class SettingsTest extends TestCase
$cfg = $this->appPath($cfg, 'localOverride')->load(); $cfg = $this->appPath($cfg, 'localOverride')->load();
$this->assertEquals(42, $cfg->answer); $this->assertEquals(42, $cfg->answer);
} }
public function testLoadWithoutModeFallsBackToFirstKnownMode(): void
{
$cfg = new Settings();
$cfg = $this->appPath($cfg, 'noMode')->load();
$this->assertEquals('prod', $cfg->mode);
$this->assertEquals('default', $cfg->testFiles);
}
/** /**
* @dataProvider fileNameData * @dataProvider fileNameData
@ -220,22 +211,6 @@ class SettingsTest extends TestCase
$this->assertEquals($path.$expected, $cfg->buildFileName($type)); $this->assertEquals($path.$expected, $cfg->buildFileName($type));
} }
public function testBuildFileNameWithPrefixAndSite(): void
{
$cfg = new Settings();
$cfg->appPath('./')->prefix('Extension')->site('owner_xyz');
$this->assertEquals('./config/owner_xyz/Extension.conf.php', $cfg->buildFileName(0x01));
}
public function testBuildFileNameWithPrefixAndNestedSite(): void
{
$cfg = new Settings();
$cfg->appPath('./')->prefix('Extension')->site('owner_xyz/sub_a');
$this->assertEquals('./config/owner_xyz/sub_a/Extension.conf.php', $cfg->buildFileName(0x01));
}
public function fileNameData() public function fileNameData()
{ {

View file

@ -1,3 +0,0 @@
<?php return [
'testFiles' => 'default',
];