In Symfony 2.8, given this YAML config block
my_bundle:
profiles:
'abc': 123
'def: 456
'...': ...
How can I validate the keys in the key-values under profiles are non-empty string?
In this case 'abc' and 'def' are just examples, these are arbitrary, they cannot be pre-defined.
Here's my Configuration file -
->arrayNode('profiles')
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('integer')
->min(0)
->end()
->end()
The problem is
my_bundle:
profiles:
123
456
is valid when I don't want it to be.
I could not find an easy in-built way to validate keys of prototype arrays.
Possible solutions
One: Seems that I could make it an array of arrays
my_bundle:
profiles:
-
name: abc
value: 123
-
name: def
value: 456
with
->arrayNode('profiles')
->isRequired()
->useAttributeAsKey('name')
->requiresAtLeastOneElement()
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->validate()
->ifTrue(
function($s) {
return !is_string($s);
}
)
->thenInvalid('Profile key must be a non-empty string')
->end()
->end()
->integerNode('value')
->isRequired()
->min(0)
->end()
->end()
->end()
but I'd prefer not to do that as our customers already have the original format in their config, we can more easily update the Configuration than the config files themselves.
Two: Seems that I could do #1 without changing the config by adding some normalisation to convert the old config format to a new format first
->arrayNode('profiles')
->beforeNormalization()
->ifTrue(
function($array) {
$hasSimpleKeyValuePair = false;
foreach ($array as $key => $value) {
if (!is_array($value)) {
$hasSimpleKeyValuePair = true;
break;
}
}
return $hasSimpleKeyValuePair;
}
)
->then(
function($array) {
$newArray = [];
foreach ($array as $key => $value) {
if (!is_array($value)) {
$newArray[] = ['name' => $key, 'value' => $value];
}
}
return $newArray;
}
)
->end()
->isRequired()
->requiresAtLeastOneElement()
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->validate()
->ifTrue(
function($s) {
return !is_string($s);
}
)
->thenInvalid('Profile key must be a non-empty string')
->end()
->end()
->integerNode('value')
->isRequired()
->min(0)
->end()
->end()
->end()
->end()
but that seems like a massive overkill.
(These two options also mean I need to change the receivers of the profile data to know that rather than using the number, they need to use an array key index that points to the number, $profile[$key]['value'] rather than just $profile[$key], which I can do I guess)
Three: Seems that I could add a custom validation on the whole block
->arrayNode('profiles')
->isRequired()
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
->prototype('integer')
->min(0)
->end()
->validate()
->ifTrue(function ($array) {
$notValid = false;
foreach ($array as $key => $value) {
if (!is_string($key) || empty($key)) {
$notValid = true;
break;
}
}
return $notValid;
})
->thenInvalid('Profile keys must be a non-empty string.')
->end()
->end()
which is what I'm going with for now, but still seems unnecessary custom/hacky for something I think would be a common use-case.
I just want to check I've not missed some magical in-built way to do this with Symfony configuration?