diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-90234-IntroduceCacheHashConfigurationAndMatchingIndicators.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-90234-IntroduceCacheHashConfigurationAndMatchingIndicators.rst new file mode 100644 index 0000000000000000000000000000000000000000..0edb135daababd55c0761d97f3a9602f473030c4 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-90234-IntroduceCacheHashConfigurationAndMatchingIndicators.rst @@ -0,0 +1,78 @@ +.. include:: ../../Includes.txt + +========================================================================== +Feature: #90234 - Introduce CacheHashConfiguration and matching indicators +========================================================================== + +See :issue:`90234` + +Description +=========== + +Settings for `$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']` are modelled +in class `CacheHashConfiguration` which takes care of validating configuration. +It also determines whether corresponding aspects apply to a given URL +parameter. + +Besides exact matches (*equals*) it is possible to apply partial matches at +the beginning of a parameter (*startsWith*) or inline occurrences (*contains*). + +URL parameter names are prefixed with the following indicators: ++ `=` (*equals*): exact match, default behavior if not given ++ `^` (*startsWith*): matching the beginning of a parameter name ++ `~` (*contains*): matching any inline occurrence in a parameter name + +These indicators can be used for all previously existing sub-properties +`cachedParametersWhiteList`, `excludedParameters`, `excludedParametersIfEmpty` +and `requireCacheHashPresenceParameters`. + +Example (excerpt of `LocalConfiguration.php`) +--------------------------------------------- + +.. code-block:: php + $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] = [ + 'excludedParameters' => [ + 'utm_source', + 'utm_medium', + '^utm_', // making previous two obsolete + ], + 'excludedParametersIfEmpty' => [ + '^tx_my_plugin[aspects]', + 'tx_my_plugin[filter]', + ], + ]; + + +Impact +====== + +Configuration related to *cHash* URL parameter supports partial matches which +overcomes the previous necessity to explicitly name all parameter names to be +excluded. + +For instance instead of having exclude items like + +.. code-block:: php + + 'excludedParameters' => [ + 'tx_my[data][uid]', + 'tx_my[data][category]', + 'tx_my[data][order]', + 'tx_my[data][origin]', + ... + ], + +partial matches allow to simply configuration and consider all items having +`tx_my[data]` (or `tx_my[data][` to be more specific) as prefix like + +.. code-block:: php + + 'excludedParameters' => [ + '^tx_my[data][', + ... + ], + +Previous configuration for the `cHash` section is still supported - there is +no syntactical requirement to adjust those changes. + +.. index:: Frontend, LocalConfiguration, ext:frontend diff --git a/typo3/sysext/frontend/Classes/Page/CacheHashCalculator.php b/typo3/sysext/frontend/Classes/Page/CacheHashCalculator.php index 112aaec57cf1deb96802271e5ae8dc449376a603..8c23a7e7cf70a23fa4edb3d25bb21b92b91fcb3b 100644 --- a/typo3/sysext/frontend/Classes/Page/CacheHashCalculator.php +++ b/typo3/sysext/frontend/Classes/Page/CacheHashCalculator.php @@ -23,39 +23,18 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; class CacheHashCalculator implements SingletonInterface { /** - * @var array Parameters that are relevant for cacheHash calculation. Optional. + * @var CacheHashConfiguration */ - protected $cachedParametersWhiteList = []; - - /** - * @var array Parameters that are not relevant for cacheHash calculation. - */ - protected $excludedParameters = []; - - /** - * @var array Parameters that forces a presence of a valid cacheHash. - */ - protected $requireCacheHashPresenceParameters = []; - - /** - * @var array Parameters that need a value to be relevant for cacheHash calculation - */ - protected $excludedParametersIfEmpty = []; - - /** - * @var bool Whether to exclude all empty parameters for cacheHash calculation - */ - protected $excludeAllEmptyParameters = false; + protected $configuration; /** * Initialise class properties by using the relevant TYPO3 configuration * - * @param array $configuration + * @param CacheHashConfiguration|null $configuration */ - public function __construct(array $configuration = null) + public function __construct(CacheHashConfiguration $configuration = null) { - $configuration = $configuration ?? $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] ?? []; - $this->setConfiguration($configuration); + $this->configuration = $configuration ?? GeneralUtility::makeInstance(CacheHashConfiguration::class); } /** @@ -90,18 +69,20 @@ class CacheHashCalculator implements SingletonInterface */ public function doParametersRequireCacheHash($queryString) { - if (empty($this->requireCacheHashPresenceParameters)) { + if (!$this->configuration->hasData(CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS)) { return false; } - $hasRequiredParameter = false; $parameterNames = array_keys($this->splitQueryStringToArray($queryString)); foreach ($parameterNames as $parameterName) { - if (in_array($parameterName, $this->requireCacheHashPresenceParameters, true)) { - $hasRequiredParameter = true; - break; + $hasRequiredParameter = $this->configuration->applies( + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + $parameterName + ); + if ($hasRequiredParameter) { + return true; } } - return $hasRequiredParameter; + return false; } /** @@ -124,7 +105,7 @@ class CacheHashCalculator implements SingletonInterface if ($this->hasCachedParametersWhiteList() && !$this->isInCachedParametersWhiteList($parameterName)) { continue; } - if (($parameterValue === null || $parameterValue === '') && !$this->isAllowedWithEmptyValue($parameterName)) { + if (($parameterValue === null || $parameterValue === '') && $this->isAllowedWithEmptyValue($parameterName)) { continue; } $relevantParameters[$parameterName] = $parameterValue; @@ -201,7 +182,10 @@ class CacheHashCalculator implements SingletonInterface */ protected function isExcludedParameter($key) { - return in_array($key, $this->excludedParameters, true); + return $this->configuration->applies( + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + $key + ); } /** @@ -212,7 +196,10 @@ class CacheHashCalculator implements SingletonInterface */ protected function isInCachedParametersWhiteList($key) { - return in_array($key, $this->cachedParametersWhiteList, true); + return $this->configuration->applies( + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + $key + ); } /** @@ -222,7 +209,9 @@ class CacheHashCalculator implements SingletonInterface */ protected function hasCachedParametersWhiteList() { - return !empty($this->cachedParametersWhiteList); + return $this->configuration->hasData( + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST + ); } /** @@ -233,62 +222,24 @@ class CacheHashCalculator implements SingletonInterface */ protected function isAllowedWithEmptyValue($key) { - return !($this->excludeAllEmptyParameters || in_array($key, $this->excludedParametersIfEmpty, true)); + return $this->configuration->shallExcludeAllEmptyParameters() + || $this->configuration->applies( + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS_IF_EMPTY, + $key + ); } /** - * Loops through the configuration array and calls the accordant - * getters with the value. + * Extends (or overrides) property names of current configuration. * * @param array $configuration */ public function setConfiguration(array $configuration) { - foreach ($configuration as $name => $value) { - $setterName = 'set' . ucfirst($name); - if (method_exists($this, $setterName)) { - $this->{$setterName}($value); - } - } - } - - /** - * @param array $cachedParametersWhiteList - */ - protected function setCachedParametersWhiteList(array $cachedParametersWhiteList) - { - $this->cachedParametersWhiteList = $cachedParametersWhiteList; - } - - /** - * @param bool $excludeAllEmptyParameters - */ - protected function setExcludeAllEmptyParameters($excludeAllEmptyParameters) - { - $this->excludeAllEmptyParameters = $excludeAllEmptyParameters; - } - - /** - * @param array $excludedParameters - */ - protected function setExcludedParameters(array $excludedParameters) - { - $this->excludedParameters = $excludedParameters; - } - - /** - * @param array $excludedParametersIfEmpty - */ - protected function setExcludedParametersIfEmpty(array $excludedParametersIfEmpty) - { - $this->excludedParametersIfEmpty = $excludedParametersIfEmpty; - } - - /** - * @param array $requireCacheHashPresenceParameters - */ - protected function setRequireCacheHashPresenceParameters(array $requireCacheHashPresenceParameters) - { - $this->requireCacheHashPresenceParameters = $requireCacheHashPresenceParameters; + $newConfiguration = GeneralUtility::makeInstance( + CacheHashConfiguration::class, + $configuration ?? $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] ?? [] + ); + $this->configuration = $this->configuration->with($newConfiguration); } } diff --git a/typo3/sysext/frontend/Classes/Page/CacheHashConfiguration.php b/typo3/sysext/frontend/Classes/Page/CacheHashConfiguration.php new file mode 100644 index 0000000000000000000000000000000000000000..550315829f6fe2dd34d5234e11837af040f85d68 --- /dev/null +++ b/typo3/sysext/frontend/Classes/Page/CacheHashConfiguration.php @@ -0,0 +1,205 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Frontend\Page; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Model for configuration properties in $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']. + * + * URL parameter names are prefixed with the following indicators: + * + = (equals): exact match, default behavior if not given + * + + ^ (startsWith): matching the beginning of a parameter name + * + ~ (contains): matching any inline occurrence in a parameter name + * + * Example: + * $configuration = new CacheHashConfiguration([ + * 'excludedParameters' => ['utm_source', '^tx_my_plugin[aspects]', '~[temporary]'], + * ... + * ]); + */ +class CacheHashConfiguration +{ + public const ASPECT_CACHED_PARAMETERS_WHITELIST = 'cachedParametersWhiteList'; + public const ASPECT_EXCLUDED_PARAMETERS = 'excludedParameters'; + public const ASPECT_EXCLUDED_PARAMETERS_IF_EMPTY = 'excludedParametersIfEmpty'; + public const ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS = 'requireCacheHashPresenceParameters'; + + protected const PROPERTY_EXCLUDE_ALL_EMPTY_PARAMETERS = 'excludeAllEmptyParameters'; + protected const INDICATOR_STARTS_WITH = '^'; + protected const INDICATOR_CONTAINS = '~'; + protected const INDICATOR_EQUALS = '='; + + protected const ALLOWED_INDICATORS = [ + self::INDICATOR_STARTS_WITH, + self::INDICATOR_CONTAINS, + self::INDICATOR_EQUALS, + ]; + + protected const ALLOWED_PROPERTY_NAMES = [ + self::PROPERTY_EXCLUDE_ALL_EMPTY_PARAMETERS, + self::ASPECT_CACHED_PARAMETERS_WHITELIST, + self::ASPECT_EXCLUDED_PARAMETERS, + self::ASPECT_EXCLUDED_PARAMETERS_IF_EMPTY, + self::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ]; + + /** + * @var array + */ + protected $configuration; + + /** + * @var array + */ + protected $data = []; + + public function __construct(array $configuration = null) + { + $configuration = $configuration ?? $GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash'] ?? []; + $this->configuration = array_filter($configuration, [$this, 'isAllowedProperty'], ARRAY_FILTER_USE_KEY); + $this->processConfiguration(); + } + + /** + * Merges other configuration property names with current configuration (extends current configuration). + * + * Example: + * $configuration = (new CacheHashConfiguration(['cachedParametersWhiteList' => [...]]) + * ->with(new CacheHashConfiguration(['excludedParameters' => [...]])); + * results in an instance having both aspects 'cachedParametersWhiteList' and 'excludedParameters' defined. + * + * @param CacheHashConfiguration $other + * @return static + */ + public function with(CacheHashConfiguration $other): self + { + $target = clone $this; + $target->configuration = array_merge($this->configuration, $other->configuration); + $target->processConfiguration(); + return $target; + } + + public function shallExcludeAllEmptyParameters(): bool + { + return !empty($this->configuration[self::PROPERTY_EXCLUDE_ALL_EMPTY_PARAMETERS]); + } + + public function applies(string $aspect, string $value): bool + { + return $this->equals($aspect, $value) + || $this->contains($aspect, $value) + || $this->startsWith($aspect, $value); + } + + public function equals(string $aspect, string $value): bool + { + $data = $this->getData($aspect, self::INDICATOR_EQUALS); + return !empty($data) && in_array($value, $data, true); + } + + public function startsWith(string $aspect, string $value): bool + { + $data = $this->getData($aspect, self::INDICATOR_STARTS_WITH); + if (empty($data)) { + return false; + } + foreach ($data as $item) { + if (strpos($value, $item) === 0) { + return true; + } + } + return false; + } + + public function contains(string $aspect, string $value): bool + { + $data = $this->getData($aspect, self::INDICATOR_CONTAINS); + if (empty($data)) { + return false; + } + foreach ($data as $item) { + if (strpos($value, $item) !== false) { + return true; + } + } + return false; + } + + public function hasData(string $aspect): bool + { + return !empty($this->data[$aspect]); + } + + protected function getData(string $aspect, string $indicator): ?array + { + return $this->data[$aspect][$indicator] ?? null; + } + + protected function defineData(string $aspect): void + { + if (empty($this->configuration[$aspect])) { + return; + } + if (!is_array($this->configuration[$aspect])) { + throw new \LogicException( + sprintf('Expected array value, got %s', gettype($this->configuration[$aspect])), + 1580225311 + ); + } + $data = []; + foreach ($this->configuration[$aspect] as $value) { + if (!is_scalar($value)) { + throw new \LogicException( + sprintf('Expected scalar value, got %s', gettype($value)), + 1580225312 + ); + } + if ($value === '') { + continue; + } + $indicator = $value[0] ?? null; + // normalize value to be indicated + if (!in_array($indicator, self::ALLOWED_INDICATORS, true)) { + $indicator = self::INDICATOR_EQUALS; + $value = self::INDICATOR_EQUALS . $value; + } + if (strlen($value) === 1) { + throw new \LogicException( + sprintf('Empty value after %s indicator', $indicator), + 1580225313 + ); + } + $data[$indicator][] = substr($value, 1); + } + if (!empty($data)) { + $this->data[$aspect] = $data; + } + } + + protected function processConfiguration(): void + { + $this->data = []; + $this->defineData(self::ASPECT_CACHED_PARAMETERS_WHITELIST); + $this->defineData(self::ASPECT_EXCLUDED_PARAMETERS); + $this->defineData(self::ASPECT_EXCLUDED_PARAMETERS_IF_EMPTY); + $this->defineData(self::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS); + } + + protected function isAllowedProperty(string $propertyName): bool + { + return in_array($propertyName, self::ALLOWED_PROPERTY_NAMES, true); + } +} diff --git a/typo3/sysext/frontend/Tests/Unit/Page/CacheHashCalculatorTest.php b/typo3/sysext/frontend/Tests/Unit/Page/CacheHashCalculatorTest.php index 8ef00ca5302acf13d3de89d3a99eb1c16120408b..dc33391bb563d19818f4b5b1fe0fa0cda56693dd 100644 --- a/typo3/sysext/frontend/Tests/Unit/Page/CacheHashCalculatorTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Page/CacheHashCalculatorTest.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Page; */ use TYPO3\CMS\Frontend\Page\CacheHashCalculator; +use TYPO3\CMS\Frontend\Page\CacheHashConfiguration; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; /** @@ -43,7 +44,7 @@ class CacheHashCalculatorTest extends UnitTestCase { parent::setUp(); $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = 't3lib_cacheHashTest'; - $this->subject = new CacheHashCalculator($this->configuration); + $this->subject = new CacheHashCalculator(new CacheHashConfiguration($this->configuration)); } protected function tearDown(): void @@ -201,10 +202,9 @@ class CacheHashCalculatorTest extends UnitTestCase */ public function canWhitelistParameters($params, $expected) { - $configuration = array_merge($this->configuration, [ - 'cachedParametersWhiteList' => ['whitep1', 'whitep2'] + $this->subject->setConfiguration([ + 'cachedParametersWhiteList' => ['whitep1', 'whitep2'], ]); - $this->subject = new CacheHashCalculator($configuration); self::assertEquals($expected, $this->subject->generateForParameters($params)); } @@ -246,12 +246,12 @@ class CacheHashCalculatorTest extends UnitTestCase ['excludedParametersIfEmpty' => [], 'excludeAllEmptyParameters' => false], ['encryptionKey', 'id', 'key1', 'key2', 'key3'] ], - 'Due to the empty value, "key2" should be skipped(with equals sign' => [ + 'Due to the empty value, "key2" should be skipped (with equals sign)' => [ '&id=42&key1=v&key2=&key3=', ['excludedParametersIfEmpty' => ['key2'], 'excludeAllEmptyParameters' => false], ['encryptionKey', 'id', 'key1', 'key3'] ], - 'Due to the empty value, "key2" should be skipped(without equals sign)' => [ + 'Due to the empty value, "key2" should be skipped (without equals sign)' => [ '&id=42&key1=v&key2&key3', ['excludedParametersIfEmpty' => ['key2'], 'excludeAllEmptyParameters' => false], ['encryptionKey', 'id', 'key1', 'key3'] @@ -260,7 +260,12 @@ class CacheHashCalculatorTest extends UnitTestCase '&id=42&key1=v&key2=&key3=', ['excludedParametersIfEmpty' => [], 'excludeAllEmptyParameters' => true], ['encryptionKey', 'id', 'key1'] - ] + ], + 'Due to the empty value, "key1", "key2" and "key3" should be skipped (starting with "key")' => [ + '&id=42&key1=v&key2=&key3=', + ['excludedParametersIfEmpty' => ['^key'], 'excludeAllEmptyParameters' => false], + ['encryptionKey', 'id', 'key1'] + ], ]; } } diff --git a/typo3/sysext/frontend/Tests/Unit/Page/CacheHashConfigurationTest.php b/typo3/sysext/frontend/Tests/Unit/Page/CacheHashConfigurationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7251a5c4d458ce5487857e4fd8ad5c8ef033dd6f --- /dev/null +++ b/typo3/sysext/frontend/Tests/Unit/Page/CacheHashConfigurationTest.php @@ -0,0 +1,235 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Frontend\Tests\Unit\Page; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use PHPUnit\Framework\TestCase; +use TYPO3\CMS\Core\Utility\PermutationUtility; +use TYPO3\CMS\Frontend\Page\CacheHashConfiguration; + +class CacheHashConfigurationTest extends TestCase +{ + public function nonArrayValueThrowsExceptionDataProvider(): array + { + return PermutationUtility::meltArrayItems([ + [ + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ], + ['true', true, 1, new \stdClass()], + ]); + } + + /** + * @param string $aspect + * @param mixed $value + * + * @test + * @dataProvider nonArrayValueThrowsExceptionDataProvider + */ + public function nonArrayValueThrowsException(string $aspect, $value): void + { + $this->expectException(\LogicException::class); + $this->expectExceptionCode(1580225311); + new CacheHashConfiguration([$aspect => $value]); + } + + public function nonScalarValueThrowsExceptionDataProvider(): array + { + return PermutationUtility::meltArrayItems([ + [ + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ], + [null, [], new \stdClass()] + ]); + } + + /** + * @param string $aspect + * @param mixed $value + * + * @test + * @dataProvider nonScalarValueThrowsExceptionDataProvider + */ + public function nonScalarValueThrowsException(string $aspect, $value): void + { + $this->expectException(\LogicException::class); + $this->expectExceptionCode(1580225312); + new CacheHashConfiguration([$aspect => [$value]]); + } + + public function emptyIndicatedValueThrowsExceptionDataProvider(): array + { + return PermutationUtility::meltArrayItems([ + [ + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ], + ['=', '^', '~'] + ]); + } + + /** + * @param string $aspect + * @param string $value + * + * @test + * @dataProvider emptyIndicatedValueThrowsExceptionDataProvider + */ + public function emptyIndicatedValueThrowsException(string $aspect, string $value): void + { + $this->expectException(\LogicException::class); + $this->expectExceptionCode(1580225313); + new CacheHashConfiguration([$aspect => [$value]]); + } + + public function equalsResolvesParameterValueDataProvider(): array + { + return PermutationUtility::meltArrayItems([ + [ + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ], + [['equals-a', '=equals-b', '^equals', '~equals']], + [['equals-a', 'equals-b']], + [['eq', 'equals', 'other', 'prefixed-equals-other']] + ]); + } + + /** + * @param string $aspect + * @param array $values + * @param array $positives + * @param array $negatives + * + * @test + * @dataProvider equalsResolvesParameterValueDataProvider + */ + public function equalsResolvesParameterValue(string $aspect, array $values, array $positives, array $negatives): void + { + $configuration = new CacheHashConfiguration([$aspect => $values]); + foreach ($positives as $probe) { + self::assertTrue($configuration->equals($aspect, $probe), $probe); + } + foreach ($negatives as $probe) { + self::assertFalse($configuration->equals($aspect, $probe), $probe); + } + } + + public function startsWithResolvesParameterValueDataProvider(): array + { + return PermutationUtility::meltArrayItems([ + [ + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ], + [['equals-a', '=equals-b', '^equals', '~equals']], + [['equals', 'equals-a', 'equals-b', 'equals-other']], + [['eq', 'other', 'prefixed-equals-other']] + ]); + } + + /** + * @param string $aspect + * @param array $values + * @param array $positives + * @param array $negatives + * + * @test + * @dataProvider startsWithResolvesParameterValueDataProvider + */ + public function startsWithResolvesParameterValue(string $aspect, array $values, array $positives, array $negatives): void + { + $configuration = new CacheHashConfiguration([$aspect => $values]); + foreach ($positives as $probe) { + self::assertTrue($configuration->startsWith($aspect, $probe), $probe); + } + foreach ($negatives as $probe) { + self::assertFalse($configuration->startsWith($aspect, $probe), $probe); + } + } + + public function containsResolvesParameterValueDataProvider(): array + { + return PermutationUtility::meltArrayItems([ + [ + CacheHashConfiguration::ASPECT_CACHED_PARAMETERS_WHITELIST, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_EXCLUDED_PARAMETERS, + CacheHashConfiguration::ASPECT_REQUIRED_CACHE_HASH_PRESENCE_PARAMETERS, + ], + [['equals-a', '=equals-b', '^equals', '~equals']], + [['equals', 'equals-a', 'equals-b', 'equals-other', 'prefixed-equals-other']], + [['eq', 'other']] + ]); + } + + /** + * @param string $aspect + * @param array $values + * @param array $positives + * @param array $negatives + * + * @test + * @dataProvider containsResolvesParameterValueDataProvider + */ + public function containsResolvesParameterValue(string $aspect, array $values, array $positives, array $negatives): void + { + $configuration = new CacheHashConfiguration([$aspect => $values]); + foreach ($positives as $probe) { + self::assertTrue($configuration->contains($aspect, $probe), $probe); + } + foreach ($negatives as $probe) { + self::assertFalse($configuration->contains($aspect, $probe), $probe); + } + } + + public function appliesResolvesParameterValueDataProvider(): array + { + // currently using "contains" data provider, could have own test sets as well + return $this->containsResolvesParameterValueDataProvider(); + } + + /** + * @param string $aspect + * @param array $values + * @param array $positives + * @param array $negatives + * + * @test + * @dataProvider appliesResolvesParameterValueDataProvider + */ + public function appliesResolvesParameterValue(string $aspect, array $values, array $positives, array $negatives): void + { + $configuration = new CacheHashConfiguration([$aspect => $values]); + foreach ($positives as $probe) { + self::assertTrue($configuration->applies($aspect, $probe), $probe); + } + foreach ($negatives as $probe) { + self::assertFalse($configuration->applies($aspect, $probe), $probe); + } + } +}