Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Allow processing class constants #37

Merged
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
32 changes: 32 additions & 0 deletions doc/book/processor.md
Expand Up @@ -12,6 +12,22 @@ zend-config provides the following concrete implementations:
- `Zend\Config\Processor\Token`: find and replace specific tokens.
- `Zend\Config\Processor\Translator`: translate configuration values in other languages using `Zend\I18n\Translator`.

> ### What gets processed?
>
> Typically, you will process configuration _values_. However, there are use
> cases for supplying constant and/or token _keys_; one common one is for
> using class-based constants as keys to avoid using magic "strings":
>
> ```json
> {
> "Acme\\Compoment::CONFIG_KEY": {}
> }
> ```
>
> As such, as of version 3.1.0, the `Constant` and `Token` processors can
> optionally also process the keys of the `Config` instance provided to them, by
> calling `enableKeyProcessing()` on their instances.

## Zend\\Config\\Processor\\Constant

### Using Zend\\Config\\Processor\\Constant
Expand All @@ -32,6 +48,14 @@ echo $config->foo;

This example returns the output: `TEST_CONST,bar`.

As of version 3.1.0, you can also tell the `Constant` processor to process keys:

```php
$processor->enableKeyProcessing();
```

When enabled, any constant values found in keys will also be replaced.

## Zend\\Config\\Processor\\Filter

### Using Zend\\Config\\Processor\\Filter
Expand Down Expand Up @@ -107,6 +131,14 @@ echo $config->foo;

This example returns the output: `Value is TOKEN,Value is bar`.

As of version 3.1.0, you can also tell the `Constant` processor to process keys:

```php
$processor->enableKeyProcessing();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A constructor argument would be preferrable

```

When enabled, any token values found in keys will also be replaced.

## Zend\\Config\\Processor\\Translator

### Using Zend\\Config\\Processor\\Translator
Expand Down
64 changes: 56 additions & 8 deletions src/Processor/Constant.php
Expand Up @@ -20,17 +20,23 @@ class Constant extends Token implements ProcessorInterface
* Constant Processor walks through a Config structure and replaces all
* PHP constants with their respective values
*
* @param bool $userOnly True to process only user-defined constants,
* false to process all PHP constants
* @param string $prefix Optional prefix
* @param string $suffix Optional suffix
* @param bool $userOnly True to process only user-defined constants,
* false to process all PHP constants; defaults to true.
* @param string $prefix Optional prefix
* @param string $suffix Optional suffix
* @param bool $enableKeyProcessing Whether or not to enable processing of
* constant values in configuration keys; defaults to false.
* @return \Zend\Config\Processor\Constant
*/
public function __construct($userOnly = true, $prefix = '', $suffix = '')
public function __construct($userOnly = true, $prefix = '', $suffix = '', $enableKeyProcessing = false)
{
$this->setUserOnly($userOnly);
$this->setPrefix($prefix);
$this->setSuffix($suffix);
$this->setUserOnly((bool) $userOnly);
$this->setPrefix((string) $prefix);
$this->setSuffix((string) $suffix);

if (true === $enableKeyProcessing) {
$this->enableKeyProcessing();
}

$this->loadConstants();
}
Expand Down Expand Up @@ -79,4 +85,46 @@ public function getTokens()
{
return $this->tokens;
}

/**
* Override processing of individual value.
*
* If the value is a string and evaluates to a class constant, returns
* the class constant value; otherwise, delegates to the parent.
*
* @param mixed $value
* @param array $replacements
* @return mixed
*/
protected function doProcess($value, array $replacements)
{
if (! is_string($value)) {
return parent::doProcess($value, $replacements);
}

if (false === strpos($value, '::')) {
return parent::doProcess($value, $replacements);
}

// Handle class constants
if (defined($value)) {
return constant($value);
}

// Handle ::class notation
if (! preg_match('/::class$/i', $value)) {
return parent::doProcess($value, $replacements);
}

$class = substr($value, 0, strlen($value) - 7);
if (class_exists($class)) {
return $class;
}

// While we've matched ::class, the class does not exist, and PHP will
// raise an error if you try to define a constant using that notation.
// As such, we have something that cannot possibly be a constant, so we
// can safely return the value verbatim.
return $value;
}
}
38 changes: 33 additions & 5 deletions src/Processor/Token.php
Expand Up @@ -20,6 +20,13 @@ class Token implements ProcessorInterface
*/
protected $prefix = '';

/**
* Whether or not to process keys as well as values.
*
* @var bool
*/
protected $processKeys = false;

/**
* Token suffix.
*
Expand Down Expand Up @@ -49,12 +56,17 @@ class Token implements ProcessorInterface
* value to replace it with
* @param string $prefix
* @param string $suffix
* @param bool $enableKeyProcessing Whether or not to enable processing of
* token values in configuration keys; defaults to false.
*/
public function __construct($tokens = [], $prefix = '', $suffix = '')
public function __construct($tokens = [], $prefix = '', $suffix = '', $enableKeyProcessing = false)
{
$this->setTokens($tokens);
$this->setPrefix($prefix);
$this->setSuffix($suffix);
$this->setPrefix((string) $prefix);
$this->setSuffix((string) $suffix);
if (true === $enableKeyProcessing) {
$this->enableKeyProcessing();
}
}

/**
Expand Down Expand Up @@ -170,6 +182,16 @@ public function setToken($token, $value)
return $this->addToken($token, $value);
}

/**
* Enable processing keys as well as values.
*
* @return void
*/
public function enableKeyProcessing()
{
$this->processKeys = true;
}

/**
* Build replacement map
*
Expand Down Expand Up @@ -231,15 +253,21 @@ public function processValue($value)
*
* @throws Exception\InvalidArgumentException if the provided value is a read-only {@see Config}
*/
private function doProcess($value, array $replacements)
protected function doProcess($value, array $replacements)
{
if ($value instanceof Config) {
if ($value->isReadOnly()) {
throw new Exception\InvalidArgumentException('Cannot process config because it is read-only');
}

foreach ($value as $key => $val) {
$value->$key = $this->doProcess($val, $replacements);
$newKey = $this->processKeys ? $this->doProcess($key, $replacements) : $key;
$value->$newKey = $this->doProcess($val, $replacements);

// If the processed key differs from the original, remove the original
if ($newKey !== $key) {
unset($value->$key);
}
}

return $value;
Expand Down
95 changes: 95 additions & 0 deletions test/Processor/ConstantTest.php
@@ -0,0 +1,95 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/

namespace ZendTest\Config\Processor;

use PHPUnit\Framework\TestCase;
use Zend\Config\Config;
use Zend\Config\Processor\Constant as ConstantProcessor;

class ConstantTest extends TestCase
{
const CONFIG_TEST = 'config';

public function constantProvider()
{
if (! defined('ZEND_CONFIG_PROCESSOR_CONSTANT_TEST')) {
define('ZEND_CONFIG_PROCESSOR_CONSTANT_TEST', 'test-key');
}

// @codingStandardsIgnoreStart
// constantString, constantValue
return [
'constant' => ['ZEND_CONFIG_PROCESSOR_CONSTANT_TEST', ZEND_CONFIG_PROCESSOR_CONSTANT_TEST],
'class-constant' => [__CLASS__ . '::CONFIG_TEST', self::CONFIG_TEST],
'class-pseudo-constant' => [__CLASS__ . '::class', self::class],
'class-pseudo-constant-upper' => [__CLASS__ . '::CLASS', self::class],
];
// @codingStandardsIgnoreEnd
}

/**
* @dataProvider constantProvider
*
* @param string $constantString
* @param string $constantValue
*/
public function testCanResolveConstantValues($constantString, $constantValue)
{
$config = new Config(['test' => $constantString], true);

$processor = new ConstantProcessor();
$processor->process($config);

$this->assertEquals($constantValue, $config->get('test'));
}

/**
* @dataProvider constantProvider
*
* @param string $constantString
* @param string $constantValue
*/
public function testWillNotProcessConstantValuesInKeysByDefault($constantString, $constantValue)
{
$config = new Config([$constantString => 'value'], true);
$processor = new ConstantProcessor();
$processor->process($config);

$this->assertNotEquals('value', $config->get($constantValue));
$this->assertEquals('value', $config->get($constantString));
}

/**
* @dataProvider constantProvider
*
* @param string $constantString
* @param string $constantValue
*/
public function testCanProcessConstantValuesInKeys($constantString, $constantValue)
{
$config = new Config([$constantString => 'value'], true);
$processor = new ConstantProcessor();
$processor->enableKeyProcessing();
$processor->process($config);

$this->assertEquals('value', $config->get($constantValue));
$this->assertNotEquals('value', $config->get($constantString));
}

public function testKeyProcessingDisabledByDefault()
{
$processor = new ConstantProcessor();
$this->assertAttributeSame(false, 'processKeys', $processor);
}

public function testCanEnableKeyProcessingViaConstructorArgument()
{
$processor = new ConstantProcessor(true, '', '', true);
$this->assertAttributeSame(true, 'processKeys', $processor);
}
}
30 changes: 30 additions & 0 deletions test/Processor/TokenTest.php
@@ -0,0 +1,30 @@
<?php
/**
* @see https://github.com/zendframework/zend-config for the canonical source repository
* @copyright Copyright (c) 2017 Zend Technologies USA Inc. (http://www.zend.com)
* @license https://github.com/zendframework/zend-config/blob/master/LICENSE.md New BSD License
*/

namespace ZendTest\Config\Processor;

use PHPUnit\Framework\TestCase;
use Zend\Config\Processor\Token as TokenProcessor;

/**
* Majority of tests are in ZendTest\Config\ProcessorTest; this class contains
* tests covering new functionality and/or specific bugs.
*/
class TokenTest extends TestCase
{
public function testKeyProcessingDisabledByDefault()
{
$processor = new TokenProcessor();
$this->assertAttributeSame(false, 'processKeys', $processor);
}

public function testCanEnableKeyProcessingViaConstructorArgument()
{
$processor = new TokenProcessor([], '', '', true);
$this->assertAttributeSame(true, 'processKeys', $processor);
}
}