From ed68ef0a0113e327640c6a954405dc96f0342b5b Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jul 2022 10:41:29 -0500 Subject: [PATCH 1/2] feat: allow configuring the ConfigPostProcessor Per a suggestion in #94, this patch modifies the `ConfigPostProcessor` such that it now examines the merged configuration passed to it for its own configuration, specifically for replacement rules. These may be provided via the following configuration: ```php return [ 'laminas-zendframework-bridge' => [ 'replacements' => [ 'to-replace' => 'replacement', // ... ], ], ]; ``` This configuration itself will NEVER be rewritten. However, the rules will now be used as additional replacements when running the processor. Signed-off-by: Matthew Weier O'Phinney --- psalm-baseline.xml | 11 +++- src/ConfigPostProcessor.php | 40 +++++++++++- test/ConfigPostProcessorTest.php | 107 +++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 8c5247a..eacb57b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + 'ZendAcl' => 'LaminasAcl' @@ -8,7 +8,10 @@ - + + RewriteRules::namespaceReverse() + RewriteRules::namespaceRewrite() + @@ -59,7 +62,7 @@ $aliases[$name] - + $a[$key] $a[$key] $a[] @@ -75,9 +78,11 @@ $key $name $newKey + $newValue $notIn[] $result $rewritten[$key] + $rewritten[$key] $rewritten[$newKey] $rewritten[$newKey][] $serviceInstance diff --git a/src/ConfigPostProcessor.php b/src/ConfigPostProcessor.php index c3b601a..f76c7ae 100644 --- a/src/ConfigPostProcessor.php +++ b/src/ConfigPostProcessor.php @@ -2,6 +2,8 @@ namespace Laminas\ZendFrameworkBridge; +use RuntimeException; + use function array_intersect_key; use function array_key_exists; use function array_pop; @@ -35,6 +37,7 @@ class ConfigPostProcessor public function __construct() { + // This value will be reset during __invoke(); setting here to prevent Psalm errors. $this->replacements = new Replacements(); /* Define the rulesets for replacements. @@ -96,9 +99,16 @@ function ($value, array $keys) { */ public function __invoke(array $config, array $keys = []) { - $rewritten = []; + $this->replacements = $this->initializeReplacements($config); + $rewritten = []; foreach ($config as $key => $value) { + // Do not rewrite configuration for the bridge + if ($key === 'laminas-zendframework-bridge') { + $rewritten[$key] = $value; + continue; + } + // Determine new key from replacements $newKey = is_string($key) ? $this->replace($key, $keys) : $key; @@ -423,4 +433,32 @@ private function replaceDependencyServices(array $config) return $config; } + + private function initializeReplacements(array $config): Replacements + { + $replacements = $config['laminas-zendframework-bridge']['replacements'] ?? []; + if (! is_array($replacements)) { + throw new RuntimeException(sprintf( + 'Invalid laminas-zendframework-bridge.replacements configuration;' + . ' value MUST be an array; received %s', + is_object($replacements) ? get_class($replacements) : gettype($replacements) + )); + } + + foreach ($replacements as $lookup => $replacement) { + if ( + ! is_string($lookup) + || ! is_string($replacement) + || preg_match('/^\s*$/', $lookup) + || preg_match('/^\s*$/', $replacement) + ) { + throw new RuntimeException( + 'Invalid lookup or replacement in laminas-zendframework-bridge.replacements configuration;' + . ' all keys and values MUST be non-empty strings.' + ); + } + } + + return new Replacements($replacements); + } } diff --git a/test/ConfigPostProcessorTest.php b/test/ConfigPostProcessorTest.php index 4d4738b..7fabe0f 100644 --- a/test/ConfigPostProcessorTest.php +++ b/test/ConfigPostProcessorTest.php @@ -4,6 +4,7 @@ use Laminas\ZendFrameworkBridge\ConfigPostProcessor; use PHPUnit\Framework\TestCase; +use RuntimeException; use stdClass; use function sprintf; @@ -137,4 +138,110 @@ public function invalidServiceManagerConfiguration() ], ]; } + + public function testWillUseReplacementsFromConfigurationWhenPresent(): void + { + $config = [ + 'laminas-zendframework-bridge' => [ + 'replacements' => [ + 'MyFoo' => 'YourFoo', + 'MyBar' => 'SomeBar', + ], + ], + 'dependencies' => [ + 'factories' => [ + 'MyFoo' => 'MyBar', + 'Zend\Cache\MyClass' => 'My\Factory\For\Caching', + 'ShouldNotRewrite' => 'My\Factory\ShouldNotRewrite', + ], + ], + ]; + + $expected = [ + 'laminas-zendframework-bridge' => [ + 'replacements' => [ + 'MyFoo' => 'YourFoo', + 'MyBar' => 'SomeBar', + ], + ], + 'dependencies' => [ + 'factories' => [ + 'YourFoo' => 'SomeBar', + 'Laminas\Cache\MyClass' => 'My\Factory\For\Caching', + 'ShouldNotRewrite' => 'My\Factory\ShouldNotRewrite', + ], + 'aliases' => [ + 'MyFoo' => 'YourFoo', + 'Zend\Cache\MyClass' => 'Laminas\Cache\MyClass', + ], + ], + ]; + + $processor = new ConfigPostProcessor(); + $result = $processor($config); + + $this->assertEquals($expected, $result); + } + + /** @psalm-return iterable */ + public function invalidReplacementsConfiguration(): iterable + { + yield 'non-array-value' => [ + ['laminas-zendframework-bridge' => ['replacements' => new stdClass()]], + 'MUST be an array', + ]; + + yield 'empty-key' => [ + ['laminas-zendframework-bridge' => ['replacements' => [ + '' => 'Laminas\FooBar', + ]]], + 'MUST be non-empty strings', + ]; + + yield 'whitespace-key' => [ + ['laminas-zendframework-bridge' => ['replacements' => [ + " \t\n" => 'Laminas\FooBar', + ]]], + 'MUST be non-empty strings', + ]; + + yield 'integer-key' => [ + ['laminas-zendframework-bridge' => ['replacements' => [ + 1 => 'Laminas\FooBar', + ]]], + 'MUST be non-empty strings', + ]; + + yield 'non-string-value' => [ + ['laminas-zendframework-bridge' => ['replacements' => [ + 'Zend' => new stdClass(), + ]]], + 'MUST be non-empty strings', + ]; + + yield 'empty-value' => [ + ['laminas-zendframework-bridge' => ['replacements' => [ + 'Zend' => '', + ]]], + 'MUST be non-empty strings', + ]; + + yield 'whitespace-value' => [ + ['laminas-zendframework-bridge' => ['replacements' => [ + 'Zend' => " \t\n", + ]]], + 'MUST be non-empty strings', + ]; + } + + /** @dataProvider invalidReplacementsConfiguration */ + public function testInvalidReplacementsConfigurationWillResultInExceptions( + array $config, + string $expectedExceptionMessage + ): void { + $processor = new ConfigPostProcessor(); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + $processor($config); + } } From a1479a98d88915e9f7d6b156029ea7367dcca664 Mon Sep 17 00:00:00 2001 From: Matthew Weier O'Phinney Date: Thu, 14 Jul 2022 10:54:53 -0500 Subject: [PATCH 2/2] docs: detail how new configuration works Signed-off-by: Matthew Weier O'Phinney --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index ea589e8..5cab71b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,52 @@ Run the following to install this library: $ composer require laminas/laminas-zendframework-bridge ``` +## Configuration + +- Since 1.6.0 + +You may provide additional replacements for the configuration post processor. +This is particularly useful if your application uses third-party components that include class names that the post processor otherwise rewrites, and which you want to never rewrite. + +Configuration is via the following structure: + +```php +return [ + 'laminas-zendframework-bridge' => [ + 'replacements' => [ + 'to-replace' => 'replacement', + // ... + ], + ], +]; +``` + +As an example, if your configuration included the following dependency mapping: + +```php +return [ + 'controller_plugins' => [ + 'factories' => [ + 'customZendFormBinder' => \CustomZendFormBinder\Controller\Plugin\Factory\BinderPluginFactory::class, + ], + ], +]; +``` + +And you wanted the two strings that contain the verbiage `ZendForm` to remain untouched, you could define the following replacements mapping: + +```php +return [ + 'laminas-zendframework-bridge' => [ + 'replacements' => [ + // Never rewrite! + 'customZendFormBinder' => 'customZendFormBinder', + 'CustomZendFormBinder' => 'CustomZendFormBinder', + ], + ], +]; +``` + ## Support * [Issues](https://github.com/laminas/laminas-zendframework-bridge/issues/)