Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuration of additional replacements #97

Merged
merged 2 commits into from Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 46 additions & 0 deletions README.md
Expand Up @@ -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/)
Expand Down
11 changes: 8 additions & 3 deletions psalm-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.21.0@d8bec4c7aaee111a532daec32fb09de5687053d1">
<files psalm-version="4.9.3@4c262932602b9bbab5020863d1eb22d49de0dbf4">
<file src="config/replacements.php">
<DuplicateArrayKey occurrences="3">
<code>'ZendAcl' =&gt; 'LaminasAcl'</code>
Expand All @@ -8,7 +8,10 @@
</DuplicateArrayKey>
</file>
<file src="src/Autoloader.php">
<MixedArgumentTypeCoercion occurrences="2"/>
<MixedArgumentTypeCoercion occurrences="2">
<code>RewriteRules::namespaceReverse()</code>
<code>RewriteRules::namespaceRewrite()</code>
</MixedArgumentTypeCoercion>
</file>
<file src="src/ConfigPostProcessor.php">
<InvalidArgument occurrences="1">
Expand Down Expand Up @@ -59,7 +62,7 @@
<MixedArrayTypeCoercion occurrences="1">
<code>$aliases[$name]</code>
</MixedArrayTypeCoercion>
<MixedAssignment occurrences="25">
<MixedAssignment occurrences="27">
<code>$a[$key]</code>
<code>$a[$key]</code>
<code>$a[]</code>
Expand All @@ -75,9 +78,11 @@
<code>$key</code>
<code>$name</code>
<code>$newKey</code>
<code>$newValue</code>
<code>$notIn[]</code>
<code>$result</code>
<code>$rewritten[$key]</code>
<code>$rewritten[$key]</code>
<code>$rewritten[$newKey]</code>
<code>$rewritten[$newKey][]</code>
<code>$serviceInstance</code>
Expand Down
40 changes: 39 additions & 1 deletion src/ConfigPostProcessor.php
Expand Up @@ -2,6 +2,8 @@

namespace Laminas\ZendFrameworkBridge;

use RuntimeException;

use function array_intersect_key;
use function array_key_exists;
use function array_pop;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
107 changes: 107 additions & 0 deletions test/ConfigPostProcessorTest.php
Expand Up @@ -4,6 +4,7 @@

use Laminas\ZendFrameworkBridge\ConfigPostProcessor;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use stdClass;

use function sprintf;
Expand Down Expand Up @@ -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<string, array{0: array, 1: string}> */
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);
}
}