diff --git a/lib/SimpleSAML/Auth/ProcessingChain.php b/lib/SimpleSAML/Auth/ProcessingChain.php index eeba0d6b8f6a3b3d1889b64a19491b9c73db3ca2..1de3dbb43b5d922171e939d7bdc94a16972c32dd 100644 --- a/lib/SimpleSAML/Auth/ProcessingChain.php +++ b/lib/SimpleSAML/Auth/ProcessingChain.php @@ -61,7 +61,7 @@ class ProcessingChain public function __construct(array $idpMetadata, array $spMetadata, string $mode = 'idp') { $config = Configuration::getInstance(); - $configauthproc = $config->getArray('authproc.' . $mode, null); + $configauthproc = $config->getOptionalArray('authproc.' . $mode, null); if (!empty($configauthproc)) { $configfilters = self::parseFilterList($configauthproc); diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index 2f07bd17e370ff13951a02dce6aaf367d27f3739..dfb49e6857b3bb4ef6fa134022e3b665b7494b75 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -348,25 +348,37 @@ class Configuration implements Utils\ClearableState /** * Retrieve a configuration option set in config.php. * - * @param string $name Name of the configuration option. - * @param mixed $default Default value of the configuration option. This parameter will default to null if not - * specified. This can be set to \SimpleSAML\Configuration::REQUIRED_OPTION, which will - * cause an exception to be thrown if the option isn't found. + * @param string $name Name of the configuration option. + * @return mixed The configuration option with name $name. + * + * @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved. + */ + public function getValue(string $name) + { + Assert::true( + $this->hasValue($name), + sprintf('%s: Could not retrieve the required option %s.', $this->location, var_export($name, true)), + ); + + return $this->configuration[$name]; + } + + + /** + * Retrieve an optional configuration option set in config.php. * - * @return mixed The configuration option with name $name, or $default if the option was not found. + * @param string $name Name of the configuration option. + * @param mixed $default Default value of the configuration option. + This parameter will default to null if not specified. * - * @throws \Exception If the required option cannot be retrieved. + * @return mixed The configuration option with name $name, or $default if the option was not found. + * + * @throws \SimpleSAML\Assert\AssertionFailedException If the required option cannot be retrieved. */ - public function getValue(string $name, $default = null) + public function getOptionalValue(string $name, $default) { // return the default value if the option is unset - if (!array_key_exists($name, $this->configuration)) { - if ($default === self::REQUIRED_OPTION) { - throw new \Exception( - $this->location . ': Could not retrieve the required option ' . - var_export($name, true) - ); - } + if (!$this->hasValue($name)) { return $default; } @@ -743,33 +755,48 @@ class Configuration implements Utils\ClearableState /** * This function retrieves an array configuration option. * - * An exception will be thrown if this option isn't an array, or if this option isn't found, and no - * default value is given. + * An exception will be thrown if this option isn't an array, or if this option isn't found. * * @param string $name The name of the option. - * @param mixed $default A default value which will be returned if the option isn't found. The option will be - * required if this parameter isn't given. The default value can be any value, including - * null. + * @return array The option with the given name. + * + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array. + */ + public function getArray(string $name): array + { + $ret = $this->getValue($name); + + Assert::isArray( + $ret, + sprintf('%s: The option %s is not an array.', $this->location, var_export($name, true)), + ); + + return $ret; + } + + + /** + * This function retrieves an optional array configuration option. + * + * An exception will be thrown if this option isn't an array, or if this option isn't found. + * + * @param string $name The name of the option. + * @param array|null $default A default value which will be returned if the option isn't found. + * The default value can be null or an array. * - * @return array|mixed The option with the given name, or $default if the option isn't found and $default is + * @return array|null The option with the given name, or $default if the option isn't found and $default is * specified. * - * @throws \Exception If the option is not an array. + * @throws \SimpleSAML\Assert\AssertionFailedException If the option is not an array. */ - public function getArray(string $name, $default = self::REQUIRED_OPTION) + public function getOptionalArray(string $name, ?array $default): ?array { - $ret = $this->getValue($name, $default); - - if ($ret === $default) { + if (!$this->hasValue($name)) { // the option wasn't found, or it matches the default value. In any case, return this value - return $ret; - } - - if (!is_array($ret)) { - throw new \Exception($this->location . ': The option ' . var_export($name, true) . ' is not an array.'); + return $default; } - return $ret; + return $this->getArray($name); } diff --git a/lib/SimpleSAML/Database.php b/lib/SimpleSAML/Database.php index 01bd51eea20a2d12033d550b1882c27d04bad2f0..df4fa1e720c00ee54c3e0a32e9b5c738dc53bfbd 100644 --- a/lib/SimpleSAML/Database.php +++ b/lib/SimpleSAML/Database.php @@ -84,8 +84,8 @@ class Database */ private function __construct(Configuration $config) { - $driverOptions = $config->getArray('database.driver_options', []); - if ($config->getBoolean('database.persistent', true)) { + $driverOptions = $config->getOptionalArray('database.driver_options', []); + if ($config->getOptionalBoolean('database.persistent', true)) { $driverOptions[PDO::ATTR_PERSISTENT] = true; } @@ -98,14 +98,14 @@ class Database ); // TODO: deprecated: the "database.slave" terminology is preserved here for backwards compatibility. - if ($config->getArray('database.slaves', null) !== null) { + if ($config->getOptionalArray('database.slaves', null) !== null) { Logger::warning( 'The "database.slaves" config option is deprecated. ' . 'Please update your configuration to use "database.secondaries".' ); } // connect to any configured secondaries, preserving legacy config option - $secondaries = $config->getArray('database.secondaries', $config->getArray('database.slaves', [])); + $secondaries = $config->getOptionalArray('database.secondaries', $config->getOptionalArray('database.slaves', [])); foreach ($secondaries as $secondary) { array_push( $this->dbSecondaries, @@ -138,8 +138,9 @@ class Database 'database.prefix' => $config->getString('database.prefix', ''), 'database.persistent' => $config->getBoolean('database.persistent', true), ], + // TODO: deprecated: the "database.slave" terminology is preserved here for backwards compatibility. - 'secondaries' => $config->getArray('database.secondaries', $config->getArray('database.slaves', [])), + 'secondaries' => $config->getOptionalArray('database.secondaries', $config->getOptionalArray('database.slaves', [])), ]; return sha1(serialize($assembledConfig)); diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php index 4291327288cd4bac81a45765a73582091d3e6cf4..c687b01a8dca7c752db20635b8b9fc7815651301 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandler.php @@ -66,7 +66,7 @@ class MetaDataStorageHandler implements ClearableState { $config = Configuration::getInstance(); - $sourcesConfig = $config->getArray('metadata.sources', null); + $sourcesConfig = $config->getOptionalArray('metadata.sources', null); // for backwards compatibility, and to provide a default configuration if ($sourcesConfig === null) { diff --git a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php index ef7ed5c63317baef3cbe81225d15ae25994bf6fd..5f371e28f23da75c6f20da0d01239490a6df0e31 100644 --- a/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php +++ b/lib/SimpleSAML/Metadata/MetaDataStorageHandlerSerialize.php @@ -45,7 +45,7 @@ class MetaDataStorageHandlerSerialize extends MetaDataStorageSource $globalConfig = Configuration::getInstance(); $cfgHelp = Configuration::loadFromArray($config, 'serialize metadata source'); - +var_dump($config); $this->directory = $cfgHelp->getString('directory'); /* Resolve this directory relative to the SimpleSAMLphp directory (unless it is diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php index f0f96a0a31acd96e21fb110f124b868ed6bf7784..4458d679a23adff533dd04469a3c647bee5bd378 100644 --- a/lib/SimpleSAML/Metadata/SAMLBuilder.php +++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php @@ -416,7 +416,7 @@ class SAMLBuilder SPSSODescriptor $spDesc, Configuration $metadata ): void { - $attributes = $metadata->getArray('attributes', []); + $attributes = $metadata->getOptionalArray('attributes', []); $name = $metadata->getLocalizedString('name', null); if ($name === null || count($attributes) == 0) { @@ -424,7 +424,7 @@ class SAMLBuilder return; } - $attributesrequired = $metadata->getArray('attributes.required', []); + $attributesrequired = $metadata->getOptionalArray('attributes.required', []); /* * Add an AttributeConsumingService element with information as name and description and list @@ -534,7 +534,7 @@ class SAMLBuilder $this->entityDescriptor->addRoleDescriptor($e); - foreach ($metadata->getArray('contacts', []) as $contact) { + foreach ($metadata->getOptionalArray('contacts', []) as $contact) { if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) { $this->addContact(Utils\Config\Metadata::getContact($contact)); } @@ -582,7 +582,7 @@ class SAMLBuilder $this->entityDescriptor->addRoleDescriptor($e); - foreach ($metadata->getArray('contacts', []) as $contact) { + foreach ($metadata->getOptionalArray('contacts', []) as $contact) { if (array_key_exists('contactType', $contact) && array_key_exists('emailAddress', $contact)) { $this->addContact(Utils\Config\Metadata::getContact($contact)); } @@ -604,7 +604,7 @@ class SAMLBuilder $metadata = Configuration::loadFromArray($metadata, $metadata['entityid']); $e = new AttributeAuthorityDescriptor(); - $e->setProtocolSupportEnumeration($metadata->getArray('protocols', [Constants::NS_SAMLP])); + $e->setProtocolSupportEnumeration($metadata->getOptionalArray('protocols', [Constants::NS_SAMLP])); $this->addExtensions($metadata, $e); $this->addCertificate($e, $metadata); diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php index 7d84ddd15d68a08c25a648444cff4921a2162ade..440466898b067054ccfce66be632edbd3a0d63e0 100644 --- a/lib/SimpleSAML/Module.php +++ b/lib/SimpleSAML/Module.php @@ -117,7 +117,7 @@ class Module public static function isModuleEnabled(string $module): bool { $config = Configuration::getOptionalConfig(); - return self::isModuleEnabledWithConf($module, $config->getArray('module.enable', self::$core_modules)); + return self::isModuleEnabledWithConf($module, $config->getOptionalArray('module.enable', self::$core_modules)); } @@ -522,7 +522,7 @@ class Module public static function callHooks(string $hook, &$data = null): void { $modules = self::getModules(); - $config = Configuration::getOptionalConfig()->getArray('module.enable', []); + $config = Configuration::getOptionalConfig()->getOptionalArray('module.enable', []); sort($modules); foreach ($modules as $module) { if (!self::isModuleEnabledWithConf($module, $config)) { diff --git a/lib/SimpleSAML/Stats.php b/lib/SimpleSAML/Stats.php index 4b3d020accd5a0f6fc0769244ddf1dd0c9a8f62b..1ce45cce678f86f49be9e5645640f04093095c33 100644 --- a/lib/SimpleSAML/Stats.php +++ b/lib/SimpleSAML/Stats.php @@ -56,7 +56,7 @@ class Stats private static function initOutputs(): void { $config = Configuration::getInstance(); - $outputCfgs = $config->getArray('statistics.out', []); + $outputCfgs = $config->getOptionalArray('statistics.out', []); self::$outputs = []; foreach ($outputCfgs as $cfg) { diff --git a/lib/SimpleSAML/Store/SQLStore.php b/lib/SimpleSAML/Store/SQLStore.php index eef08f25bf542b72a7b1d433804e64439b3a5271..3bdf83dcc85811712cfef54681ff1c15b4a072c8 100644 --- a/lib/SimpleSAML/Store/SQLStore.php +++ b/lib/SimpleSAML/Store/SQLStore.php @@ -55,10 +55,10 @@ class SQLStore implements StoreInterface $config = Configuration::getInstance(); $dsn = $config->getString('store.sql.dsn'); - $username = $config->getString('store.sql.username', null); - $password = $config->getString('store.sql.password', null); - $options = $config->getArray('store.sql.options', null); - $this->prefix = $config->getString('store.sql.prefix', 'simpleSAMLphp'); + $username = $config->getOptionalString('store.sql.username', null); + $password = $config->getOptionalString('store.sql.password', null); + $options = $config->getOptionalArray('store.sql.options', null); + $this->prefix = $config->getOptionalString('store.sql.prefix', 'simpleSAMLphp'); try { $this->pdo = new PDO($dsn, $username, $password, $options); } catch (PDOException $e) { diff --git a/lib/SimpleSAML/Utils/XML.php b/lib/SimpleSAML/Utils/XML.php index 51928aaffe7f7fdf041e0b5e4082cfe2806d5291..a77d9892cd5231f517497dd576a4f4ead96e1d68 100644 --- a/lib/SimpleSAML/Utils/XML.php +++ b/lib/SimpleSAML/Utils/XML.php @@ -52,7 +52,7 @@ class XML } // see if debugging is enabled for XML validation - $debug = Configuration::getInstance()->getArray('debug', ['validatexml' => false]); + $debug = Configuration::getInstance()->getOptionalArray('debug', ['validatexml' => false]); if ( !( @@ -100,7 +100,7 @@ class XML } // see if debugging is enabled for SAML messages - $debug = Configuration::getInstance()->getArray('debug', ['saml' => false]); + $debug = Configuration::getInstance()->getOptionalArray('debug', ['saml' => false]); if ( !( diff --git a/modules/multiauth/lib/Auth/Source/MultiAuth.php b/modules/multiauth/lib/Auth/Source/MultiAuth.php index 8af4fa0372630fdd6028fcb2a1e66594c3772cd7..922c6381f2f242deaab8ca845cc266bbf22ddcfa 100644 --- a/modules/multiauth/lib/Auth/Source/MultiAuth.php +++ b/modules/multiauth/lib/Auth/Source/MultiAuth.php @@ -108,7 +108,7 @@ class MultiAuth extends Auth\Source $css_class = $info['css-class']; } else { // Use the authtype as the css class - $authconfig = $authsources->getArray($source, null); + $authconfig = $authsources->getOptionalArray($source, null); if (!array_key_exists(0, $authconfig) || !is_string($authconfig[0])) { $css_class = ""; } else { diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php index 34986aeecde77d5cdeb65e85466f0d5ff13bfeaa..efdb5de6a59cae37183824ed16827bc6022f05a5 100644 --- a/modules/saml/lib/Auth/Source/SP.php +++ b/modules/saml/lib/Auth/Source/SP.php @@ -152,7 +152,7 @@ class SP extends \SimpleSAML\Auth\Source // add attributes $name = $this->metadata->getLocalizedString('name', null); - $attributes = $this->metadata->getArray('attributes', []); + $attributes = $this->metadata->getOptionalArray('attributes', []); if ($name !== null) { if (!empty($attributes)) { $metadata['name'] = $name; @@ -189,7 +189,7 @@ class SP extends \SimpleSAML\Auth\Source } // add contacts - $contacts = $this->metadata->getArray('contacts', []); + $contacts = $this->metadata->getOptionalArray('contacts', []); foreach ($contacts as $contact) { $metadata['contacts'][] = Utils\Config\Metadata::getContact($contact); } @@ -343,7 +343,7 @@ class SP extends \SimpleSAML\Auth\Source $default[] = Constants::BINDING_HOK_SSO; } - $bindings = $this->metadata->getArray('acs.Bindings', $default); + $bindings = $this->metadata->getOptionalArray('acs.Bindings', $default); $index = 0; foreach ($bindings as $service) { switch ($service) { @@ -390,7 +390,7 @@ class SP extends \SimpleSAML\Auth\Source $storeType = $config->getString('store.type', 'phpsession'); $store = StoreFactory::getInstance($storeType); - $bindings = $this->metadata->getArray( + $bindings = $this->metadata->getOptionalArray( 'SingleLogoutServiceBinding', [ Constants::BINDING_HTTP_REDIRECT, @@ -560,8 +560,8 @@ class SP extends \SimpleSAML\Auth\Source $ar->setIDPList( array_unique( array_merge( - $this->metadata->getArray('IDPList', []), - $idpMetadata->getArray('IDPList', []), + $this->metadata->getOptionalArray('IDPList', []), + $idpMetadata->getOptionalArray('IDPList', []), (array) $IDPList ) ) @@ -573,7 +573,7 @@ class SP extends \SimpleSAML\Auth\Source // Otherwise use extensions that might be defined in the local SP (only makes sense in a proxy scenario) if (isset($state['saml:Extensions']) && count($state['saml:Extensions']) > 0) { $ar->setExtensions($state['saml:Extensions']); - } elseif ($this->metadata->getArray('saml:Extensions', null) !== null) { + } elseif ($this->metadata->getOptionalArray('saml:Extensions', null) !== null) { $ar->setExtensions($this->metadata->getArray('saml:Extensions')); } @@ -990,7 +990,7 @@ class SP extends \SimpleSAML\Auth\Source if (isset($state['saml:logout:Extensions']) && count($state['saml:logout:Extensions']) > 0) { $lr->setExtensions($state['saml:logout:Extensions']); - } elseif ($this->metadata->getArray('saml:logout:Extensions', null) !== null) { + } elseif ($this->metadata->getOptionalArray('saml:logout:Extensions', null) !== null) { $lr->setExtensions($this->metadata->getArray('saml:logout:Extensions')); } diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index f5bbb817d54ef0ace446afa37c9c529a25ee1bde..cb8c5c6152e65e9ded23b70d598ff2d06ff4e6d0 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -1012,8 +1012,8 @@ class SAML2 $defaultEncoding = 'string'; } - $srcEncodings = $idpMetadata->getArray('attributeencodings', []); - $dstEncodings = $spMetadata->getArray('attributeencodings', []); + $srcEncodings = $idpMetadata->getOptionalArray('attributeencodings', []); + $dstEncodings = $spMetadata->getOptionalArray('attributeencodings', []); /* * Merge the two encoding arrays. Encodings specified in the target metadata @@ -1146,7 +1146,7 @@ class SAML2 $issuer->setFormat(Constants::NAMEID_ENTITY); $a->setIssuer($issuer); - $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getArray('audience', [])); + $audience = array_merge([$spMetadata->getString('entityid')], $spMetadata->getOptionalArray('audience', [])); $a->setValidAudiences($audience); $a->setNotBefore($now - 30); diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php index d69935c5cc92be7759a0701d66ea1c3998a035ef..22b3ab99daadb585a2b92480d6330ab9f3043c0f 100644 --- a/modules/saml/lib/Message.php +++ b/modules/saml/lib/Message.php @@ -320,9 +320,9 @@ class Message Configuration $srcMetadata, Configuration $dstMetadata ): array { - $blacklist = $srcMetadata->getArray('encryption.blacklisted-algorithms', null); + $blacklist = $srcMetadata->getOptionalArray('encryption.blacklisted-algorithms', null); if ($blacklist === null) { - $blacklist = $dstMetadata->getArray('encryption.blacklisted-algorithms', [XMLSecurityKey::RSA_1_5]); + $blacklist = $dstMetadata->getOptionalArray('encryption.blacklisted-algorithms', [XMLSecurityKey::RSA_1_5]); } return $blacklist; } diff --git a/tests/lib/SimpleSAML/ConfigurationTest.php b/tests/lib/SimpleSAML/ConfigurationTest.php index 7950667680e9eeb135ad10c7f5d9845716d3f35b..cee693667784ea6917e28cc359e11d3618e9fb39 100644 --- a/tests/lib/SimpleSAML/ConfigurationTest.php +++ b/tests/lib/SimpleSAML/ConfigurationTest.php @@ -6,6 +6,7 @@ namespace SimpleSAML\Test; use Exception; use SAML2\Constants; +use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\TestUtils\ClearStateTestCase; @@ -68,25 +69,31 @@ class ConfigurationTest extends ClearStateTestCase 'exists_true' => true, 'exists_null' => null, ]); - $this->assertEquals($c->getValue('missing'), null); - $this->assertEquals($c->getValue('missing', true), true); - $this->assertEquals($c->getValue('missing', true), true); - $this->assertEquals($c->getValue('exists_true'), true); + // Normal use + $this->assertTrue($c->getValue('exists_true')); + $this->assertNull($c->getValue('exists_null')); - $this->assertEquals($c->getValue('exists_null'), null); - $this->assertEquals($c->getValue('exists_null', false), null); + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getValue('missing'); } /** - * Test \SimpleSAML\Configuration::getValue(), REQUIRED_OPTION flag. + * Test \SimpleSAML\Configuration::getOptionalValue(). */ - public function testGetValueRequired(): void + public function testGetOptionalValue(): void { - $this->expectException(Exception::class); - $c = Configuration::loadFromArray([]); - $c->getValue('missing', Configuration::REQUIRED_OPTION); + $c = Configuration::loadFromArray([ + 'exists_true' => true, + ]); + + // Normal use + $this->assertTrue($c->getOptionalValue('exists_true', 'something else')); + + // Missing option + $this->assertNull($c->getOptionalValue('missing', null)); } @@ -426,22 +433,41 @@ class ConfigurationTest extends ClearStateTestCase { $c = Configuration::loadFromArray([ 'opt' => ['a', 'b', 'c'], + 'wrong_opt' => false, ]); - $this->assertEquals($c->getArray('missing_opt', '--missing--'), '--missing--'); + + // Normal use $this->assertEquals($c->getArray('opt'), ['a', 'b', 'c']); + + // Missing option + $this->expectException(AssertionFailedException::class); + $c->getArray('missing_opt'); + + // Value not allowed + $this->expectException(AssertionFailedException::class); + $c->getArray('wrong_opt'); } /** - * Test \SimpleSAML\Configuration::getArray() wrong option + * Test \SimpleSAML\Configuration::getOptionalArray() */ - public function testGetArrayWrong(): void + public function testGetOptionalArray(): void { - $this->expectException(Exception::class); $c = Configuration::loadFromArray([ - 'opt' => 'not_an_array', + 'opt' => ['a', 'b', 'c'], + 'wrong_opt' => false, ]); - $c->getArray('opt'); + + // Normal use + $this->assertEquals($c->getOptionalArray('opt', ['d', 'e', 'f']), ['a', 'b', 'c']); + + // Missing option + $this->assertEquals($c->getOptionalArray('missing_opt', ['d', 'e', 'f']), ['d', 'e', 'f']); + + // Value not allowed + $this->expectException(AssertionFailedException::class); + $c->getArray('wrong_opt'); } @@ -924,7 +950,7 @@ class ConfigurationTest extends ClearStateTestCase $virtualFile = 'nonexistent-preload.php'; Configuration::setPreLoadedConfig($c, $virtualFile); $nc = Configuration::getConfig($virtualFile); - $this->assertEquals('value', $nc->getValue('key', null)); + $this->assertEquals('value', $nc->getOptionalValue('key', null)); } @@ -940,10 +966,10 @@ class ConfigurationTest extends ClearStateTestCase ]; // test loading a custom instance Configuration::loadFromArray($c, '', 'dummy'); - $this->assertEquals('value', Configuration::getInstance('dummy')->getValue('key', null)); + $this->assertEquals('value', Configuration::getInstance('dummy')->getOptionalValue('key', null)); // test loading the default instance Configuration::loadFromArray($c, '', 'simplesaml'); - $this->assertEquals('value', Configuration::getInstance()->getValue('key', null)); + $this->assertEquals('value', Configuration::getInstance()->getOptionalValue('key', null)); } }