[Security/DependencyInjection] adds support for merging security configurations
The merging is done in three steps: 1. Normalization: ================= All passed config arrays will be transformed into the same structure regardless of what format they come from. 2. Merging: =========== This is the step when the actual merging is performed. Starting at the root the configs will be passed along the tree until a node has no children, or the merging of sub-paths of the current node has been specifically disabled. Left-Side Right-Side Merge Result ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -nothing- array Right-Side will be taken. scalar scalar Right-Side will be taken. array false Right-Side will be taken if ->canBeUnset() was called on the array node. false array Right-Side will be taken. array array Each value in the array will be passed to the specific child node, or the prototype node (whatever is present). 3. Finalization: ================ The normalized, and merged config will be passed through the config tree to perform final validation on the submitted values, and set default values where this has been requested. You can influence this process in various ways, here is a list with some examples. All of these methods must be called on the node on which they should be applied. * isRequired(): Node must be present in at least one config file. * requiresAtLeastOneElement(): PrototypeNode must have at least one element. * treatNullLike($value): Replaces null with $value during normalization. * treatTrueLike($value): Same as above just for true * treatFalseLike($value): Same as above just for false * defaultValue($value): Sets a default value for this node (only for scalars) * addDefaultsIfNotSet(): Whether to add default values of an array which has not been defined in any configuration file. * disallowNewKeysInSubsequentConfigs(): All keys for this array must be defined in one configuration file, subsequent configurations may only overwrite these. * fixXmlConfig($key, $plural = null): Transforms XML config into same structure as YAML, and PHP configurations. * useAttributeAsKey($name): Defines which XML attribute to use as array key. * cannotBeOverwritten(): Declares a certain sub-path as non-overwritable. All configuration for this path must be defined in the same configuration file. * cannotBeEmpty(): If value is set, it must be non-empty. * canBeUnset(): If array values should be unset if false is specified. Architecture: ============= The configuration consists basically out of two different sets of classes. 1. Builder classes: These classes provide the fluent interface and are used to construct the config tree. 2. Node classes: These classes contain the actual logic for normalization, merging, and finalizing configurations. After you have added all the metadata to your builders, the call to ->buildTree() will convert this metadata to actual node classes. Most of the time, you will not have to interact with the config nodes directly, but will delegate this to the Processor class which will call the respective methods on the config node classes.
This commit is contained in:
parent
c5fb96b86b
commit
0b8fef2347
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;
|
||||
|
||||
/**
|
||||
* This class contains the configuration information for the following tags:
|
||||
*
|
||||
* * security.config
|
||||
* * security.acl
|
||||
*
|
||||
* This information is solely responsible for how the different configuration
|
||||
* sections are normalized, and merged.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class Configuration
|
||||
{
|
||||
public function getAclConfigTree()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
|
||||
return $tb
|
||||
->root('security:acl', 'array')
|
||||
->scalarNode('connection')->end()
|
||||
->scalarNode('cache')->end()
|
||||
->end()
|
||||
->buildTree();
|
||||
}
|
||||
|
||||
public function getFactoryConfigTree()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
|
||||
return $tb
|
||||
->root('security:config', 'array')
|
||||
->fixXmlConfig('factory', 'factories')
|
||||
->arrayNode('factories')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->buildTree();
|
||||
}
|
||||
|
||||
public function getMainConfigTree(array $factories)
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
$rootNode = $tb->root('security:config', 'array');
|
||||
|
||||
$rootNode
|
||||
->scalarNode('access_denied_url')->end()
|
||||
->scalarNode('session_fixation_strategy')->cannotBeEmpty()->defaultValue('migrate')->end()
|
||||
;
|
||||
|
||||
$this->addEncodersSection($rootNode);
|
||||
$this->addProvidersSection($rootNode);
|
||||
$this->addFirewallsSection($rootNode, $factories);
|
||||
$this->addAccessControlSection($rootNode);
|
||||
$this->addRoleHierarchySection($rootNode);
|
||||
|
||||
return $tb->buildTree();
|
||||
}
|
||||
|
||||
protected function addRoleHierarchySection($rootNode)
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('role', 'role_hierarchy')
|
||||
->arrayNode('role_hierarchy')
|
||||
->containsNameValuePairsWithKeyAttribute('id')
|
||||
->prototype('array')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return array('value' => $v); })->end()
|
||||
->beforeNormalization()
|
||||
->ifTrue(function($v) { return is_array($v) && isset($v['value']); })
|
||||
->then(function($v) { return preg_split('/\s*,\s*/', $v['value']); })
|
||||
->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
protected function addAccessControlSection($rootNode)
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('rule', 'access_control')
|
||||
->arrayNode('access_control')
|
||||
->cannotBeOverwritten()
|
||||
->prototype('array')
|
||||
->scalarNode('requires_channel')->defaultNull()->end()
|
||||
->scalarNode('path')->defaultNull()->end()
|
||||
->scalarNode('host')->defaultNull()->end()
|
||||
->scalarNode('ip')->defaultNull()->end()
|
||||
->arrayNode('methods')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->fixXmlConfig('role')
|
||||
->arrayNode('roles')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->fixXmlConfig('attribute')
|
||||
->arrayNode('attributes')
|
||||
->containsNameValuePairsWithKeyAttribute('key')
|
||||
->prototype('scalar')
|
||||
->beforeNormalization()
|
||||
->ifTrue(function($v) { return is_array($v) && isset($v['pattern']); })
|
||||
->then(function($v) { return $v['pattern']; })
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
protected function addFirewallsSection($rootNode, array $factories)
|
||||
{
|
||||
$firewallNodeBuilder =
|
||||
$rootNode
|
||||
->fixXmlConfig('firewall')
|
||||
->arrayNode('firewalls')
|
||||
->disallowNewKeysInSubsequentConfigs()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->scalarNode('pattern')->end()
|
||||
->booleanNode('security')->defaultTrue()->end()
|
||||
->scalarNode('request_matcher')->end()
|
||||
->scalarNode('access_denied_url')->end()
|
||||
->scalarNode('access_denied_handler')->end()
|
||||
->scalarNode('entry_point')->end()
|
||||
->scalarNode('provider')->end()
|
||||
->booleanNode('stateless')->defaultFalse()->end()
|
||||
->scalarNode('context')->cannotBeEmpty()->end()
|
||||
->arrayNode('logout')
|
||||
->treatTrueLike(array())
|
||||
->canBeUnset()
|
||||
->scalarNode('path')->defaultValue('/logout')->end()
|
||||
->scalarNode('target')->defaultValue('/')->end()
|
||||
->booleanNode('invalidate_session')->defaultTrue()->end()
|
||||
->fixXmlConfig('delete_cookie')
|
||||
->arrayNode('delete_cookies')
|
||||
->beforeNormalization()
|
||||
->ifTrue(function($v) { return is_array($v) && is_int(key($v)); })
|
||||
->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); })
|
||||
->end()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->scalarNode('path')->defaultNull()->end()
|
||||
->scalarNode('domain')->defaultNull()->end()
|
||||
->end()
|
||||
->end()
|
||||
->fixXmlConfig('handler')
|
||||
->arrayNode('handlers')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->booleanNode('anonymous')->end()
|
||||
->arrayNode('switch_user')
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('parameter')->defaultValue('_switch_user')->end()
|
||||
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
foreach ($factories as $factoriesAtPosition) {
|
||||
foreach ($factoriesAtPosition as $factory) {
|
||||
$factoryNode =
|
||||
$firewallNodeBuilder->arrayNode(str_replace('-', '_', $factory->getKey()))
|
||||
->canBeUnset()
|
||||
;
|
||||
|
||||
$factory->addConfiguration($factoryNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function addProvidersSection($rootNode)
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('provider')
|
||||
->arrayNode('providers')
|
||||
->disallowNewKeysInSubsequentConfigs()
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->scalarNode('id')->end()
|
||||
->fixXmlConfig('provider')
|
||||
->arrayNode('providers')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->fixXmlConfig('user')
|
||||
->arrayNode('users')
|
||||
->useAttributeAsKey('name')
|
||||
->prototype('array')
|
||||
->scalarNode('password')->defaultValue(uniqid())->end()
|
||||
->arrayNode('roles')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return preg_split('/\s*,\s*/', $v); })->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('entity')
|
||||
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('property')->defaultNull()->end()
|
||||
->end()
|
||||
->arrayNode('document')
|
||||
->scalarNode('class')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('property')->defaultNull()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
protected function addEncodersSection($rootNode)
|
||||
{
|
||||
$rootNode
|
||||
->fixXmlConfig('encoder')
|
||||
->arrayNode('encoders')
|
||||
->useAttributeAsKey('class')
|
||||
->prototype('array')
|
||||
->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end()
|
||||
->scalarNode('algorithm')->isRequired()->cannotBeEmpty()->end()
|
||||
->booleanNode('ignore_case')->end()
|
||||
->booleanNode('encode_as_base64')->end()
|
||||
->scalarNode('iterations')->end()
|
||||
->scalarNode('id')->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -38,10 +40,6 @@ abstract class AbstractFactory implements SecurityFactoryInterface
|
||||
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId)
|
||||
{
|
||||
if (!is_array($config)) {
|
||||
$config = array();
|
||||
}
|
||||
|
||||
// authentication provider
|
||||
$authProviderId = $this->createAuthProvider($container, $id, $config, $userProviderId);
|
||||
$container
|
||||
@ -66,6 +64,24 @@ abstract class AbstractFactory implements SecurityFactoryInterface
|
||||
return array($authProviderId, $listenerId, $entryPointId);
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node)
|
||||
{
|
||||
$node
|
||||
->scalarNode('provider')->end()
|
||||
->booleanNode('remember_me')->defaultTrue()->end()
|
||||
->scalarNode('success_handler')->end()
|
||||
->scalarNode('failure_handler')->end()
|
||||
;
|
||||
|
||||
foreach ($this->options as $name => $default) {
|
||||
if (is_bool($default)) {
|
||||
$node->booleanNode($name)->defaultValue($default);
|
||||
} else {
|
||||
$node->scalarNode($name)->defaultValue($default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final function addOption($name, $default = null)
|
||||
{
|
||||
$this->options[$name] = $default;
|
||||
@ -127,18 +143,15 @@ abstract class AbstractFactory implements SecurityFactoryInterface
|
||||
*/
|
||||
protected function isRememberMeAware($config)
|
||||
{
|
||||
return !isset($config['remember_me']) || (Boolean) $config['remember_me'];
|
||||
return $config['remember_me'];
|
||||
}
|
||||
|
||||
protected function createListener($container, $id, $config, $userProvider)
|
||||
{
|
||||
// merge set options with default options
|
||||
$options = $this->getOptionsFromConfig($config);
|
||||
|
||||
$listenerId = $this->getListenerId();
|
||||
$listener = new DefinitionDecorator($listenerId);
|
||||
$listener->setArgument(3, $id);
|
||||
$listener->setArgument(4, $options);
|
||||
$listener->setArgument(4, array_intersect_key($config, $this->options));
|
||||
|
||||
// success handler
|
||||
if (isset($config['success_handler'])) {
|
||||
@ -155,17 +168,4 @@ abstract class AbstractFactory implements SecurityFactoryInterface
|
||||
|
||||
return $listenerId;
|
||||
}
|
||||
|
||||
protected final function getOptionsFromConfig($config)
|
||||
{
|
||||
$options = $this->options;
|
||||
|
||||
foreach (array_keys($options) as $key) {
|
||||
if (array_key_exists($key, $config)) {
|
||||
$options[$key] = $config[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
|
@ -59,14 +59,11 @@ class FormLoginFactory extends AbstractFactory
|
||||
|
||||
protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
|
||||
{
|
||||
// merge set options with default options
|
||||
$options = $this->getOptionsFromConfig($config);
|
||||
|
||||
$entryPointId = 'security.authentication.form_entry_point.'.$id;
|
||||
$container
|
||||
->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point'))
|
||||
->addArgument($options['login_path'])
|
||||
->addArgument($options['use_forward'])
|
||||
->addArgument($config['login_path'])
|
||||
->addArgument($config['use_forward'])
|
||||
;
|
||||
|
||||
return $entryPointId;
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -53,4 +55,11 @@ class HttpBasicFactory implements SecurityFactoryInterface
|
||||
{
|
||||
return 'http-basic';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $builder)
|
||||
{
|
||||
$builder
|
||||
->scalarNode('provider')->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -53,4 +55,11 @@ class HttpDigestFactory implements SecurityFactoryInterface
|
||||
{
|
||||
return 'http-digest';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $builder)
|
||||
{
|
||||
$builder
|
||||
->scalarNode('provider')->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
@ -10,16 +12,19 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
class RememberMeFactory implements SecurityFactoryInterface
|
||||
{
|
||||
protected $options = array(
|
||||
'name' => 'REMEMBERME',
|
||||
'lifetime' => 31536000,
|
||||
'path' => '/',
|
||||
'domain' => null,
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'always_remember_me' => false,
|
||||
'remember_me_parameter' => '_remember_me',
|
||||
);
|
||||
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||
{
|
||||
if (!isset($config['key']) || empty($config['key'])) {
|
||||
throw new \RuntimeException('A "key" must be defined for each remember-me section.');
|
||||
}
|
||||
|
||||
if (isset($config['provider'])) {
|
||||
throw new \RuntimeException('You must not set a user provider for remember-me.');
|
||||
}
|
||||
|
||||
// authentication provider
|
||||
$authProviderId = 'security.authentication.provider.rememberme.'.$id;
|
||||
$container
|
||||
@ -60,22 +65,7 @@ class RememberMeFactory implements SecurityFactoryInterface
|
||||
}
|
||||
|
||||
// remember-me options
|
||||
$options = array(
|
||||
'name' => 'REMEMBERME',
|
||||
'lifetime' => 31536000,
|
||||
'path' => '/',
|
||||
'domain' => null,
|
||||
'secure' => false,
|
||||
'httponly' => true,
|
||||
'always_remember_me' => false,
|
||||
'remember_me_parameter' => '_remember_me',
|
||||
);
|
||||
foreach ($options as $name => $option) {
|
||||
if (array_key_exists($name, $config)) {
|
||||
$options[$name] = $config[$name];
|
||||
}
|
||||
}
|
||||
$rememberMeServices->setArgument(3, $options);
|
||||
$rememberMeServices->setArgument(3, array_intersect_key($config, $this->options));
|
||||
|
||||
// attach to remember-me aware listeners
|
||||
$userProviders = array();
|
||||
@ -118,4 +108,20 @@ class RememberMeFactory implements SecurityFactoryInterface
|
||||
{
|
||||
return 'remember-me';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $node)
|
||||
{
|
||||
$node
|
||||
->scalarNode('key')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('token_provider')->end()
|
||||
;
|
||||
|
||||
foreach ($this->options as $name => $value) {
|
||||
if (is_bool($value)) {
|
||||
$node->booleanNode($name)->defaultValue($value);
|
||||
} else {
|
||||
$node->scalarNode($name)->defaultValue($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
@ -25,4 +26,6 @@ interface SecurityFactoryInterface
|
||||
function getPosition();
|
||||
|
||||
function getKey();
|
||||
|
||||
function addConfiguration(NodeBuilder $builder);
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\NodeBuilder;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@ -50,4 +52,11 @@ class X509Factory implements SecurityFactoryInterface
|
||||
{
|
||||
return 'x509';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeBuilder $builder)
|
||||
{
|
||||
$builder
|
||||
->scalarNode('provider')->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Processor;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
@ -34,19 +36,82 @@ class SecurityExtension extends Extension
|
||||
protected $requestMatchers = array();
|
||||
protected $contextListeners = array();
|
||||
protected $listenerPositions = array('pre_auth', 'form', 'http', 'remember_me');
|
||||
protected $configuration;
|
||||
protected $factories;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->configuration = new Configuration();
|
||||
}
|
||||
|
||||
public function configLoad(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
foreach ($configs as $config) {
|
||||
$this->doConfigLoad($this->normalizeKeys($config), $container);
|
||||
$processor = new Processor();
|
||||
|
||||
// first assemble the factories
|
||||
$factories = $this->createListenerFactories($container, $processor->process($this->configuration->getFactoryConfigTree(), $configs));
|
||||
|
||||
// normalize and merge the actual configuration
|
||||
$tree = $this->configuration->getMainConfigTree($factories);
|
||||
$config = $processor->process($tree, $configs);
|
||||
|
||||
// load services
|
||||
$loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
$loader->load('security.xml');
|
||||
$loader->load('security_listeners.xml');
|
||||
$loader->load('security_rememberme.xml');
|
||||
$loader->load('templating_php.xml');
|
||||
$loader->load('templating_twig.xml');
|
||||
$loader->load('collectors.xml');
|
||||
|
||||
// set some global scalars
|
||||
if (isset($config['access_denied_url'])) {
|
||||
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
|
||||
}
|
||||
if (isset($config['session_fixation_protection'])) {
|
||||
$container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_protection']);
|
||||
}
|
||||
|
||||
$this->createFirewalls($config, $container);
|
||||
$this->createAuthorization($config, $container);
|
||||
$this->createRoleHierarchy($config, $container);
|
||||
}
|
||||
|
||||
public function aclLoad(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
foreach ($configs as $config) {
|
||||
$this->doAclLoad($this->normalizeKeys($config), $container);
|
||||
$processor = new Processor();
|
||||
$config = $processor->process($this->configuration->getAclConfigTree(), $configs);
|
||||
|
||||
$loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
$loader->load('security_acl.xml');
|
||||
|
||||
if (isset($config['connection'])) {
|
||||
$container->setAlias('security.acl.dbal.connection', sprintf('doctrine.dbal.%s_connection', $config['connection']));
|
||||
}
|
||||
|
||||
if (isset($config['cache'])) {
|
||||
$container->setAlias('security.acl.cache', sprintf('security.acl.cache.%s', $config['cache']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base path for the XSD files.
|
||||
*
|
||||
* @return string The XSD base path
|
||||
*/
|
||||
public function getXsdValidationBasePath()
|
||||
{
|
||||
return __DIR__.'/../Resources/config/schema';
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
{
|
||||
return 'http://www.symfony-project.org/schema/dic/security';
|
||||
}
|
||||
|
||||
public function getAlias()
|
||||
{
|
||||
return 'security';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,121 +120,50 @@ class SecurityExtension extends Extension
|
||||
* @param array $config An array of configuration settings
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance
|
||||
*/
|
||||
protected function doConfigLoad($config, ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('security.context')) {
|
||||
$loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
$loader->load('security.xml');
|
||||
$loader->load('security_listeners.xml');
|
||||
$loader->load('security_rememberme.xml');
|
||||
$loader->load('templating_php.xml');
|
||||
$loader->load('templating_twig.xml');
|
||||
$loader->load('collectors.xml');
|
||||
}
|
||||
|
||||
if (isset($config['access_denied_url'])) {
|
||||
$container->setParameter('security.access.denied_url', $config['access_denied_url']);
|
||||
}
|
||||
|
||||
// session fixation protection
|
||||
if (isset($config['session_fixation_protection'])) {
|
||||
$container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_protection']);
|
||||
}
|
||||
|
||||
$this->createFirewalls($config, $container);
|
||||
$this->createAuthorization($config, $container);
|
||||
$this->createRoleHierarchy($config, $container);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
protected function createRoleHierarchy($config, ContainerBuilder $container)
|
||||
{
|
||||
$roles = array();
|
||||
if (isset($config['role_hierarchy'])) {
|
||||
$roles = $config['role_hierarchy'];
|
||||
if (!isset($config['role_hierarchy'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($roles['role']) && is_int(key($roles['role']))) {
|
||||
$roles = $roles['role'];
|
||||
}
|
||||
|
||||
$hierarchy = array();
|
||||
foreach ($roles as $id => $role) {
|
||||
if (is_array($role) && isset($role['id'])) {
|
||||
$id = $role['id'];
|
||||
}
|
||||
|
||||
$value = $role;
|
||||
if (is_array($role) && isset($role['value'])) {
|
||||
$value = $role['value'];
|
||||
}
|
||||
|
||||
$hierarchy[$id] = is_array($value) ? $value : preg_split('/\s*,\s*/', $value);
|
||||
}
|
||||
|
||||
$container->setParameter('security.role_hierarchy.roles', $hierarchy);
|
||||
$container->setParameter('security.role_hierarchy.roles', $config['role_hierarchy']);
|
||||
$container->remove('security.access.simple_role_voter');
|
||||
$container->getDefinition('security.access.role_hierarchy_voter')->addTag('security.voter');
|
||||
}
|
||||
|
||||
protected function createAuthorization($config, ContainerBuilder $container)
|
||||
{
|
||||
$rules = array();
|
||||
if (isset($config['access_control'])) {
|
||||
$rules = $config['access_control'];
|
||||
if (!isset($config['access_control'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($rules['rule']) && is_array($rules['rule'])) {
|
||||
$rules = $rules['rule'];
|
||||
}
|
||||
foreach ($config['access_control'] as $access) {
|
||||
$matcher = $this->createRequestMatcher(
|
||||
$container,
|
||||
$access['path'],
|
||||
$access['host'],
|
||||
count($access['methods']) === 0 ? null : $access['methods'],
|
||||
$access['ip'],
|
||||
$access['attributes']
|
||||
);
|
||||
|
||||
foreach ($rules as $i => $access) {
|
||||
$roles = isset($access['role']) ? (is_array($access['role']) ? $access['role'] : preg_split('/\s*,\s*/', $access['role'])) : array();
|
||||
$channel = null;
|
||||
if (isset($access['requires_channel'])) {
|
||||
$channel = $access['requires_channel'];
|
||||
}
|
||||
|
||||
// matcher
|
||||
$path = $host = $methods = $ip = null;
|
||||
if (isset($access['path'])) {
|
||||
$path = $access['path'];
|
||||
}
|
||||
if (isset($access['host'])) {
|
||||
$host = $access['host'];
|
||||
}
|
||||
if (count($tMethods = $this->normalizeConfig($access, 'method')) > 0) {
|
||||
$methods = $tMethods;
|
||||
}
|
||||
if (isset($access['ip'])) {
|
||||
$ip = $access['ip'];
|
||||
}
|
||||
|
||||
$matchAttributes = array();
|
||||
$attributes = $this->normalizeConfig($access, 'attribute');
|
||||
foreach ($attributes as $key => $attribute) {
|
||||
if (isset($attribute['key'])) {
|
||||
$key = $attribute['key'];
|
||||
}
|
||||
$matchAttributes[$key] = $attribute['pattern'];
|
||||
}
|
||||
$matcher = $this->createRequestMatcher($container, $path, $host, $methods, $ip, $matchAttributes);
|
||||
|
||||
$container->getDefinition('security.access_map')->addMethodCall('add', array($matcher, $roles, $channel));
|
||||
$container->getDefinition('security.access_map')
|
||||
->addMethodCall('add', array($matcher, $access['roles'], $access['requires_channel']));
|
||||
}
|
||||
}
|
||||
|
||||
protected function createFirewalls($config, ContainerBuilder $container)
|
||||
{
|
||||
if (!isset($config['firewalls'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$firewalls = $config['firewalls'];
|
||||
$providerIds = $this->createUserProviders($config, $container);
|
||||
|
||||
$this->createEncoders($config, $container);
|
||||
|
||||
if (!$firewalls = $this->normalizeConfig($config, 'firewall')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make the ContextListener aware of the configured user providers
|
||||
$definition = $container->getDefinition('security.context_listener');
|
||||
$arguments = $definition->getArguments();
|
||||
@ -185,16 +179,8 @@ class SecurityExtension extends Extension
|
||||
|
||||
// load firewall map
|
||||
$mapDef = $container->getDefinition('security.firewall.map');
|
||||
$names = $map = array();
|
||||
$map = array();
|
||||
foreach ($firewalls as $name => $firewall) {
|
||||
if (isset($firewall['name'])) {
|
||||
$name = $firewall['name'];
|
||||
}
|
||||
if (in_array($name, $names)) {
|
||||
throw new \RuntimeException(sprintf('The firewall name must be unique. Duplicate found: "%s"', $name));
|
||||
}
|
||||
$names[] = $name;
|
||||
|
||||
list($matcher, $listeners, $exceptionListener) = $this->createFirewall($container, $name, $firewall, $providerIds, $factories);
|
||||
|
||||
$contextId = 'security.firewall.map.context.'.$name;
|
||||
@ -220,7 +206,7 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
// Security disabled?
|
||||
if (isset($firewall['security']) && !$firewall['security']) {
|
||||
if (false === $firewall['security']) {
|
||||
return array($matcher, array(), null);
|
||||
}
|
||||
|
||||
@ -228,9 +214,6 @@ class SecurityExtension extends Extension
|
||||
if (isset($firewall['provider'])) {
|
||||
$defaultProvider = $this->getUserProviderId($firewall['provider']);
|
||||
} else {
|
||||
if (!$providerIds) {
|
||||
throw new \InvalidArgumentException('You must provide at least one authentication provider.');
|
||||
}
|
||||
$defaultProvider = reset($providerIds);
|
||||
}
|
||||
|
||||
@ -242,7 +225,7 @@ class SecurityExtension extends Extension
|
||||
$listeners[] = new Reference('security.channel_listener');
|
||||
|
||||
// Context serializer listener
|
||||
if (!isset($firewall['stateless']) || !$firewall['stateless']) {
|
||||
if (false === $firewall['stateless']) {
|
||||
$contextKey = $id;
|
||||
if (isset($firewall['context'])) {
|
||||
$contextKey = $firewall['context'];
|
||||
@ -252,44 +235,29 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
// Logout listener
|
||||
if (array_key_exists('logout', $firewall)) {
|
||||
if (isset($firewall['logout'])) {
|
||||
$listenerId = 'security.logout_listener.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.logout_listener'));
|
||||
|
||||
$listener->addArgument($firewall['logout']['path']);
|
||||
$listener->addArgument($firewall['logout']['target']);
|
||||
$listeners[] = new Reference($listenerId);
|
||||
|
||||
if (!is_array($firewall['logout'])) {
|
||||
$firewall['logout'] = array();
|
||||
}
|
||||
|
||||
if (isset($firewall['logout']['path'])) {
|
||||
$listener->setArgument(1, $firewall['logout']['path']);
|
||||
}
|
||||
|
||||
if (isset($firewall['logout']['target'])) {
|
||||
$listener->setArgument(2, $firewall['logout']['target']);
|
||||
}
|
||||
|
||||
// add session logout handler
|
||||
$invalidateSession = true;
|
||||
if (isset($firewall['logout']['invalidate_session'])) {
|
||||
$invalidateSession = (Boolean) $firewall['logout']['invalidate_session'];
|
||||
}
|
||||
if (true === $invalidateSession && (!isset($firewall['stateless']) || !$firewall['stateless'])) {
|
||||
if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
|
||||
$listener->addMethodCall('addHandler', array(new Reference('security.logout.handler.session')));
|
||||
}
|
||||
|
||||
// add cookie logout handler
|
||||
if (count($cookies = $this->normalizeConfig($firewall['logout'], 'cookie')) > 0) {
|
||||
if (count($firewall['logout']['delete_cookies']) > 0) {
|
||||
$cookieHandlerId = 'security.logout.handler.cookie_clearing.'.$id;
|
||||
$cookieHandler = $container->setDefinition($cookieHandlerId, new DefinitionDecorator('security.logout.handler.cookie_clearing'));
|
||||
$cookieHandler->addArgument($cookies);
|
||||
$cookieHandler->addArgument($firewall['logout']['delete_cookies']);
|
||||
|
||||
$listener->addMethodCall('addHandler', array(new Reference($cookieHandlerId)));
|
||||
}
|
||||
|
||||
// add custom handlers
|
||||
foreach ($this->normalizeConfig($firewall['logout'], 'handler') as $handlerId) {
|
||||
foreach ($firewall['logout']['handlers'] as $handlerId) {
|
||||
$listener->addMethodCall('addHandler', array(new Reference($handlerId)));
|
||||
}
|
||||
}
|
||||
@ -303,7 +271,7 @@ class SecurityExtension extends Extension
|
||||
$listeners[] = new Reference('security.access_listener');
|
||||
|
||||
// Switch user listener
|
||||
if (array_key_exists('switch_user', $firewall)) {
|
||||
if (isset($firewall['switch_user'])) {
|
||||
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider));
|
||||
}
|
||||
|
||||
@ -340,13 +308,9 @@ class SecurityExtension extends Extension
|
||||
|
||||
foreach ($this->listenerPositions as $position) {
|
||||
foreach ($factories[$position] as $factory) {
|
||||
$key = $factory->getKey();
|
||||
$keybis = str_replace('-', '_', $key);
|
||||
$key = str_replace('-', '_', $factory->getKey());
|
||||
|
||||
if (array_key_exists($keybis, $firewall)) {
|
||||
$firewall[$key] = $firewall[$keybis];
|
||||
}
|
||||
if (array_key_exists($key, $firewall) && $firewall[$key] !== false) {
|
||||
if (isset($firewall[$key])) {
|
||||
$userProvider = isset($firewall[$key]['provider']) ? $this->getUserProviderId($firewall[$key]['provider']) : $defaultProvider;
|
||||
|
||||
list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);
|
||||
@ -359,7 +323,7 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
// Anonymous
|
||||
if (array_key_exists('anonymous', $firewall)) {
|
||||
if (isset($firewall['anonymous'])) {
|
||||
$listeners[] = new Reference('security.authentication.listener.anonymous');
|
||||
$hasListeners = true;
|
||||
}
|
||||
@ -371,66 +335,14 @@ class SecurityExtension extends Extension
|
||||
return array($listeners, $providers, $defaultEntryPoint);
|
||||
}
|
||||
|
||||
// Parses user providers and returns an array of their ids
|
||||
protected function createUserProviders($config, ContainerBuilder $container)
|
||||
{
|
||||
$providers = $this->normalizeConfig($config, 'provider');
|
||||
if (!$providers) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$providerIds = array();
|
||||
foreach ($providers as $name => $provider) {
|
||||
$id = $this->createUserDaoProvider($name, $provider, $container);
|
||||
|
||||
if (in_array($id, $providerIds, true)) {
|
||||
throw new \RuntimeException(sprintf('Provider names must be unique. Duplicate entry for %s.', $id));
|
||||
}
|
||||
|
||||
$providerIds[] = $id;
|
||||
}
|
||||
|
||||
return $providerIds;
|
||||
}
|
||||
|
||||
protected function createListenerFactories(ContainerBuilder $container, $config)
|
||||
{
|
||||
// load service templates
|
||||
$c = new ContainerBuilder();
|
||||
$parameterBag = $container->getParameterBag();
|
||||
$loader = new XmlFileLoader($c, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
$loader->load('security_factories.xml');
|
||||
|
||||
// load user-created listener factories
|
||||
foreach ($this->normalizeConfig($config, 'factory', 'factories') as $factory) {
|
||||
$loader->load($parameterBag->resolveValue($factory));
|
||||
}
|
||||
|
||||
$tags = $c->findTaggedServiceIds('security.listener.factory');
|
||||
|
||||
$factories = array();
|
||||
foreach ($this->listenerPositions as $position) {
|
||||
$factories[$position] = array();
|
||||
}
|
||||
|
||||
foreach (array_keys($tags) as $tag) {
|
||||
$factory = $c->get($tag);
|
||||
|
||||
$factories[$factory->getPosition()][] = $factory;
|
||||
}
|
||||
|
||||
return $factories;
|
||||
}
|
||||
|
||||
protected function createEncoders($config, ContainerBuilder $container)
|
||||
{
|
||||
$encoders = $this->normalizeConfig($config, 'encoder');
|
||||
if (!$encoders) {
|
||||
return array();
|
||||
if (!isset($config['encoders'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$encoderMap = array();
|
||||
foreach ($encoders as $class => $encoder) {
|
||||
foreach ($config['encoders'] as $class => $encoder) {
|
||||
$encoderMap = $this->createEncoder($encoderMap, $class, $encoder, $container);
|
||||
}
|
||||
|
||||
@ -442,21 +354,6 @@ class SecurityExtension extends Extension
|
||||
|
||||
protected function createEncoder(array $encoderMap, $accountClass, $config, ContainerBuilder $container)
|
||||
{
|
||||
if (is_array($config) && isset($config['class'])) {
|
||||
$accountClass = $config['class'];
|
||||
}
|
||||
|
||||
if (empty($accountClass)) {
|
||||
throw new \RuntimeException('Each encoder needs an account class.');
|
||||
}
|
||||
|
||||
// a minimal message digest, or plaintext encoder
|
||||
if (is_string($config)) {
|
||||
$config = array(
|
||||
'algorithm' => $config,
|
||||
);
|
||||
}
|
||||
|
||||
// a custom encoder service
|
||||
if (isset($config['id'])) {
|
||||
$container
|
||||
@ -467,17 +364,12 @@ class SecurityExtension extends Extension
|
||||
return $encoderMap;
|
||||
}
|
||||
|
||||
// a lazy loaded, message digest or plaintext encoder
|
||||
if (!isset($config['algorithm'])) {
|
||||
throw new \RuntimeException('"algorithm" must be defined.');
|
||||
}
|
||||
|
||||
// plaintext encoder
|
||||
if ('plaintext' === $config['algorithm']) {
|
||||
$arguments = array();
|
||||
|
||||
if (isset($config['ignore_case'])) {
|
||||
$arguments[0] = (Boolean) $config['ignore_case'];
|
||||
$arguments[0] = $config['ignore_case'];
|
||||
}
|
||||
|
||||
$encoderMap[$accountClass] = array(
|
||||
@ -493,7 +385,7 @@ class SecurityExtension extends Extension
|
||||
|
||||
// add optional arguments
|
||||
if (isset($config['encode_as_base64'])) {
|
||||
$arguments[1] = (Boolean) $config['encode_as_base64'];
|
||||
$arguments[1] = $config['encode_as_base64'];
|
||||
} else {
|
||||
$arguments[1] = false;
|
||||
}
|
||||
@ -512,17 +404,23 @@ class SecurityExtension extends Extension
|
||||
return $encoderMap;
|
||||
}
|
||||
|
||||
// Parses user providers and returns an array of their ids
|
||||
protected function createUserProviders($config, ContainerBuilder $container)
|
||||
{
|
||||
$providerIds = array();
|
||||
foreach ($config['providers'] as $name => $provider) {
|
||||
$id = $this->createUserDaoProvider($name, $provider, $container);
|
||||
$providerIds[] = $id;
|
||||
}
|
||||
|
||||
return $providerIds;
|
||||
}
|
||||
|
||||
// Parses a <provider> tag and returns the id for the related user provider service
|
||||
// FIXME: Replace register() calls in this method with DefinitionDecorator
|
||||
// and move the actual definition to an xml file
|
||||
protected function createUserDaoProvider($name, $provider, ContainerBuilder $container, $master = true)
|
||||
{
|
||||
if (isset($provider['name'])) {
|
||||
$name = $provider['name'];
|
||||
}
|
||||
|
||||
if (!$name) {
|
||||
throw new \RuntimeException('You must define a name for each user provider.');
|
||||
}
|
||||
|
||||
$name = $this->getUserProviderId(strtolower($name));
|
||||
|
||||
// Existing DAO service provider
|
||||
@ -533,7 +431,7 @@ class SecurityExtension extends Extension
|
||||
}
|
||||
|
||||
// Chain provider
|
||||
if (isset($provider['provider'])) {
|
||||
if (count($provider['providers']) > 0) {
|
||||
// FIXME
|
||||
throw new \RuntimeException('Not implemented yet.');
|
||||
}
|
||||
@ -546,8 +444,9 @@ class SecurityExtension extends Extension
|
||||
->setArguments(array(
|
||||
new Reference('security.user.entity_manager'),
|
||||
$provider['entity']['class'],
|
||||
isset($provider['entity']['property']) ? $provider['entity']['property'] : null,
|
||||
));
|
||||
$provider['entity']['property'],
|
||||
))
|
||||
;
|
||||
|
||||
return $name;
|
||||
}
|
||||
@ -560,7 +459,7 @@ class SecurityExtension extends Extension
|
||||
->setArguments(array(
|
||||
new Reference('security.user.document_manager'),
|
||||
$provider['document']['class'],
|
||||
isset($provider['document']['property']) ? $provider['document']['property'] : null,
|
||||
$provider['document']['property'],
|
||||
));
|
||||
|
||||
return $name;
|
||||
@ -569,27 +468,8 @@ class SecurityExtension extends Extension
|
||||
// In-memory DAO provider
|
||||
$definition = $container->register($name, '%security.user.provider.in_memory.class%');
|
||||
$definition->setPublic(false);
|
||||
foreach ($this->normalizeConfig($provider, 'user') as $username => $user) {
|
||||
if (isset($user['name'])) {
|
||||
$username = $user['name'];
|
||||
}
|
||||
|
||||
if (!array_key_exists('password', $user)) {
|
||||
// if no password is provided explicitly, it means that
|
||||
// the user will be used with OpenID, X.509 certificates, ...
|
||||
// Let's generate a random password just to be sure this
|
||||
// won't be used accidentally with other authentication schemes.
|
||||
// If you want an empty password, just say so explicitly
|
||||
$user['password'] = uniqid();
|
||||
}
|
||||
|
||||
if (!isset($user['roles'])) {
|
||||
$user['roles'] = array();
|
||||
} else {
|
||||
$user['roles'] = is_array($user['roles']) ? $user['roles'] : preg_split('/\s*,\s*/', $user['roles']);
|
||||
}
|
||||
|
||||
$userId = $name.'_'.md5(serialize(array($username, $user['password'], $user['roles'])));
|
||||
foreach ($provider['users'] as $username => $user) {
|
||||
$userId = $name.'_'.md5(json_encode(array($username, $user['password'], $user['roles'])));
|
||||
|
||||
$container
|
||||
->register($userId, 'Symfony\Component\Security\Core\User\User')
|
||||
@ -632,14 +512,8 @@ class SecurityExtension extends Extension
|
||||
$listener = $container->setDefinition($switchUserListenerId, new DefinitionDecorator('security.authentication.switchuser_listener'));
|
||||
$listener->setArgument(1, new Reference($userProvider));
|
||||
$listener->setArgument(3, $id);
|
||||
|
||||
if (isset($config['parameter'])) {
|
||||
$listener->setArgument(5, $config['parameter']);
|
||||
}
|
||||
|
||||
if (isset($config['role'])) {
|
||||
$listener->setArgument(6, $config['role']);
|
||||
}
|
||||
$listener->addArgument($config['parameter']);
|
||||
$listener->addArgument($config['role']);
|
||||
|
||||
return $switchUserListenerId;
|
||||
}
|
||||
@ -668,42 +542,35 @@ class SecurityExtension extends Extension
|
||||
return $this->requestMatchers[$id] = new Reference($id);
|
||||
}
|
||||
|
||||
protected function doAclLoad(array $config, ContainerBuilder $container)
|
||||
protected function createListenerFactories(ContainerBuilder $container, $config)
|
||||
{
|
||||
if (!$container->hasDefinition('security.acl')) {
|
||||
$loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
$loader->load('security_acl.xml');
|
||||
if (null !== $this->factories) {
|
||||
return $this->factories;
|
||||
}
|
||||
|
||||
if (isset($config['connection'])) {
|
||||
$container->setAlias('security.acl.dbal.connection', sprintf('doctrine.dbal.%s_connection', $config['connection']));
|
||||
// load service templates
|
||||
$c = new ContainerBuilder();
|
||||
$parameterBag = $container->getParameterBag();
|
||||
$loader = new XmlFileLoader($c, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config'));
|
||||
$loader->load('security_factories.xml');
|
||||
|
||||
// load user-created listener factories
|
||||
foreach ($config['factories'] as $factory) {
|
||||
$loader->load($parameterBag->resolveValue($factory));
|
||||
}
|
||||
|
||||
if (isset($config['cache'])) {
|
||||
$container->setAlias('security.acl.cache', sprintf('security.acl.cache.%s', $config['cache']));
|
||||
} else {
|
||||
$container->remove('security.acl.cache.doctrine');
|
||||
$container->removeAlias('security.acl.cache.doctrine.cache_impl');
|
||||
$tags = $c->findTaggedServiceIds('security.listener.factory');
|
||||
|
||||
$factories = array();
|
||||
foreach ($this->listenerPositions as $position) {
|
||||
$factories[$position] = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base path for the XSD files.
|
||||
*
|
||||
* @return string The XSD base path
|
||||
*/
|
||||
public function getXsdValidationBasePath()
|
||||
{
|
||||
return __DIR__.'/../Resources/config/schema';
|
||||
}
|
||||
foreach (array_keys($tags) as $tag) {
|
||||
$factory = $c->get($tag);
|
||||
$factories[$factory->getPosition()][] = $factory;
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
{
|
||||
return 'http://www.symfony-project.org/schema/dic/security';
|
||||
}
|
||||
|
||||
public function getAlias()
|
||||
{
|
||||
return 'security';
|
||||
return $this->factories = $factories;
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,8 @@
|
||||
<parameter key="security.authentication.listener.anonymous.class">Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener</parameter>
|
||||
|
||||
<parameter key="security.authentication.switchuser_listener.class">Symfony\Component\Security\Http\Firewall\SwitchUserListener</parameter>
|
||||
<parameter key="security.authentication.switchuser.role">ROLE_ALLOWED_TO_SWITCH</parameter>
|
||||
<parameter key="security.authentication.switchuser.parameter">_switch_user</parameter>
|
||||
|
||||
<parameter key="security.logout_listener.class">Symfony\Component\Security\Http\Firewall\LogoutListener</parameter>
|
||||
<parameter key="security.logout.path">/logout</parameter>
|
||||
<parameter key="security.logout.target_path">/</parameter>
|
||||
<parameter key="security.logout.handler.session.class">Symfony\Component\Security\Http\Logout\SessionLogoutHandler</parameter>
|
||||
<parameter key="security.logout.handler.cookie_clearing.class">Symfony\Component\Security\Http\Logout\CookieClearingLogoutHandler</parameter>
|
||||
|
||||
@ -83,14 +79,12 @@
|
||||
<service id="security.context_listener" class="%security.context_listener.class%" public="false">
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="collection"></argument>
|
||||
<argument />
|
||||
<argument /> <!-- Provider Key -->
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<service id="security.logout_listener" class="%security.logout_listener.class%" public="false" abstract="true">
|
||||
<argument type="service" id="security.context" />
|
||||
<argument>%security.logout.path%</argument>
|
||||
<argument>%security.logout.target_path%</argument>
|
||||
</service>
|
||||
<service id="security.logout.handler.session" class="%security.logout.handler.session.class%" public="false" />
|
||||
<service id="security.logout.handler.cookie_clearing" class="%security.logout.handler.cookie_clearing.class%" public="false" abstract="true" />
|
||||
@ -162,13 +156,11 @@
|
||||
|
||||
<service id="security.authentication.switchuser_listener" class="%security.authentication.switchuser_listener.class%" public="false" abstract="true">
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="service" id="security.user.provider.in_memory" />
|
||||
<argument /> <!-- User Provider -->
|
||||
<argument type="service" id="security.account_checker" />
|
||||
<argument /> <!-- Provider Key -->
|
||||
<argument type="service" id="security.access.decision_manager" />
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument>%security.authentication.switchuser.parameter%</argument>
|
||||
<argument>%security.authentication.switchuser.role%</argument>
|
||||
</service>
|
||||
|
||||
<service id="security.access_listener" class="%security.access_listener.class%" public="false">
|
||||
|
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
$this->load('merge_import.php', $container);
|
||||
|
||||
$container->loadFromExtension('security', 'config', array(
|
||||
'firewalls' => array(
|
||||
'main' => array(
|
||||
'form_login' => false,
|
||||
'http_basic' => null,
|
||||
),
|
||||
),
|
||||
'role_hierarchy' => array(
|
||||
'FOO' => array('MOO'),
|
||||
)
|
||||
));
|
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
$container->loadFromExtension('security', 'config', array(
|
||||
'firewalls' => array(
|
||||
'main' => array(
|
||||
'form_login' => array(
|
||||
'login_path' => '/login',
|
||||
)
|
||||
)
|
||||
),
|
||||
'role_hierarchy' => array(
|
||||
'FOO' => 'BAR',
|
||||
'ADMIN' => 'USER',
|
||||
),
|
||||
));
|
@ -6,12 +6,9 @@
|
||||
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<access-control>
|
||||
<rule path="/blog/524" role="ROLE_USER" requires-channel="https" />
|
||||
<rule role='IS_AUTHENTICATED_ANONYMOUSLY'>
|
||||
<attribute key="_controller" pattern=".*\\BlogBundle\\.*" />
|
||||
<path pattern="/blog/.*" />
|
||||
</rule>
|
||||
</access-control>
|
||||
<rule path="/blog/524" role="ROLE_USER" requires-channel="https" />
|
||||
<rule role='IS_AUTHENTICATED_ANONYMOUSLY' path="/blog/.*">
|
||||
<attribute key="_controller" pattern=".*\\BlogBundle\\.*" />
|
||||
</rule>
|
||||
</config>
|
||||
</srv:container>
|
||||
|
@ -6,10 +6,8 @@
|
||||
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<role-hierarchy>
|
||||
<role id="ROLE_ADMIN">ROLE_USER</role>
|
||||
<role id="ROLE_SUPER_ADMIN">ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH</role>
|
||||
<role id="ROLE_REMOTE">ROLE_USER,ROLE_ADMIN</role>
|
||||
</role-hierarchy>
|
||||
<role id="ROLE_ADMIN">ROLE_USER</role>
|
||||
<role id="ROLE_SUPER_ADMIN">ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH</role>
|
||||
<role id="ROLE_REMOTE">ROLE_USER,ROLE_ADMIN</role>
|
||||
</config>
|
||||
</srv:container>
|
||||
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<container xmlns="http://www.symfony-project.org/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:sec="http://www.symfony-project.org/schema/dic/security"
|
||||
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<imports>
|
||||
<import resource="merge_import.xml"/>
|
||||
</imports>
|
||||
|
||||
<sec:config>
|
||||
<sec:firewall name="main" form-login="false">
|
||||
<sec:http-basic />
|
||||
</sec:firewall>
|
||||
|
||||
<sec:role id="FOO" value="MOO" />
|
||||
</sec:config>
|
||||
|
||||
</container>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<srv:container xmlns="http://www.symfony-project.org/schema/dic/security"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:srv="http://www.symfony-project.org/schema/dic/services"
|
||||
xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<config>
|
||||
<firewall name="main">
|
||||
<form-login login-path="/login" />
|
||||
</firewall>
|
||||
|
||||
<role id="FOO" value="BAR" />
|
||||
<role id="ADMIN" value="USER" />
|
||||
</config>
|
||||
|
||||
</srv:container>
|
@ -0,0 +1,11 @@
|
||||
imports:
|
||||
- { resource: merge_import.yml }
|
||||
|
||||
security.config:
|
||||
firewalls:
|
||||
main:
|
||||
form_login: false
|
||||
http_basic: ~
|
||||
|
||||
role_hierarchy:
|
||||
FOO: [MOO]
|
@ -0,0 +1,9 @@
|
||||
security.config:
|
||||
firewalls:
|
||||
main:
|
||||
form_login:
|
||||
login_path: /login
|
||||
|
||||
role_hierarchy:
|
||||
FOO: BAR
|
||||
ADMIN: USER
|
@ -28,7 +28,7 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
list($authProviderId,
|
||||
$listenerId,
|
||||
$entryPointId
|
||||
) = $factory->create($container, 'foo', array('use_forward' => true, 'failure_path' => '/foo', 'success_handler' => 'foo'), 'user_provider', 'entry_point');
|
||||
) = $factory->create($container, 'foo', array('use_forward' => true, 'failure_path' => '/foo', 'success_handler' => 'foo', 'remember_me' => true), 'user_provider', 'entry_point');
|
||||
|
||||
// auth provider
|
||||
$this->assertEquals('auth_provider', $authProviderId);
|
||||
@ -41,15 +41,8 @@ class AbstractFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(array(
|
||||
'index_3' => 'foo',
|
||||
'index_4' => array(
|
||||
'check_path' => '/login_check',
|
||||
'login_path' => '/login',
|
||||
'use_forward' => true,
|
||||
'always_use_default_target_path' => false,
|
||||
'default_target_path' => '/',
|
||||
'target_path_parameter' => '_target_path',
|
||||
'use_referer' => false,
|
||||
'failure_path' => '/foo',
|
||||
'failure_forward' => false,
|
||||
),
|
||||
'index_5' => new Reference('foo'),
|
||||
), $definition->getArguments());
|
||||
|
@ -36,10 +36,10 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$expectedProviders = array(
|
||||
'security.authentication.provider.digest',
|
||||
'security.authentication.provider.digest_0ff1b54f2a4b7f71b2b9d6604fcca4b8',
|
||||
'security.authentication.provider.digest_23374fce51fe846516ff85bfa9add8fe',
|
||||
'security.authentication.provider.basic',
|
||||
'security.authentication.provider.basic_b7f0cf21802ffc8b22cadbb255f07213',
|
||||
'security.authentication.provider.basic_98e44377704554700e68c22094b51ca4',
|
||||
'security.authentication.provider.basic_745e8583f784c83c4b4208fd281001f3',
|
||||
'security.authentication.provider.basic_af4bcce7246fb064b8e219034043d88a',
|
||||
'security.authentication.provider.doctrine',
|
||||
'security.authentication.provider.service',
|
||||
'security.authentication.provider.anonymous',
|
||||
@ -109,6 +109,16 @@ abstract class SecurityExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testMerge()
|
||||
{
|
||||
$container = $this->getContainer('merge');
|
||||
|
||||
$this->assertEquals(array(
|
||||
'FOO' => array('MOO'),
|
||||
'ADMIN' => array('USER'),
|
||||
), $container->getParameter('security.role_hierarchy.roles'));
|
||||
}
|
||||
|
||||
protected function getContainer($file)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -2,23 +2,117 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\DuplicateKeyException;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\UnsetKeyException;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Represents an ARRAY node in the config tree.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ArrayNode extends BaseNode implements PrototypeNodeInterface
|
||||
{
|
||||
protected $normalizeTransformations;
|
||||
protected $xmlRemappings;
|
||||
protected $children;
|
||||
protected $prototype;
|
||||
protected $keyAttribute;
|
||||
protected $allowFalse;
|
||||
protected $allowNewKeys;
|
||||
protected $addIfNotSet;
|
||||
protected $minNumberOfElements;
|
||||
protected $performDeepMerging;
|
||||
|
||||
public function __construct($name, NodeInterface $parent = null, array $beforeTransformations = array(), array $afterTransformations = array(), array $normalizeTransformations = array(), $keyAttribute = null)
|
||||
public function __construct($name, NodeInterface $parent = null)
|
||||
{
|
||||
parent::__construct($name, $parent, $beforeTransformations, $afterTransformations);
|
||||
parent::__construct($name, $parent);
|
||||
|
||||
$this->children = array();
|
||||
$this->normalizeTransformations = $normalizeTransformations;
|
||||
$this->keyAttribute = $keyAttribute;
|
||||
$this->xmlRemappings = array();
|
||||
$this->allowFalse = false;
|
||||
$this->addIfNotSet = false;
|
||||
$this->allowNewKeys = true;
|
||||
$this->performDeepMerging = true;
|
||||
$this->minNumberOfElements = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the xml remappings that should be performed.
|
||||
*
|
||||
* @param array $remappings an array of the form array(array(string, string))
|
||||
* @return void
|
||||
*/
|
||||
public function setXmlRemappings(array $remappings)
|
||||
{
|
||||
$this->xmlRemappings = $remappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of elements that a prototype based node must
|
||||
* contain. By default this is zero, meaning no elements.
|
||||
*
|
||||
* @param integer $number
|
||||
* @return void
|
||||
*/
|
||||
public function setMinNumberOfElements($number)
|
||||
{
|
||||
$this->minNumberOfElements = $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the attribute that should be used as key.
|
||||
*
|
||||
* This is only relevant for XML configurations, and only in combination
|
||||
* with a prototype based node.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public function setKeyAttribute($attribute)
|
||||
{
|
||||
$this->keyAttribute = $attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to add default values for this array if it has not been
|
||||
* defined in any of the configuration files.
|
||||
*
|
||||
* @param Boolean $boolean
|
||||
* @return void
|
||||
*/
|
||||
public function setAddIfNotSet($boolean)
|
||||
{
|
||||
$this->addIfNotSet = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether false is allowed as value indicating that the array should
|
||||
* be unset.
|
||||
*
|
||||
* @param Boolean $allow
|
||||
* @return void
|
||||
*/
|
||||
public function setAllowFalse($allow)
|
||||
{
|
||||
$this->allowFalse = (Boolean) $allow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether new keys can be defined in subsequent configurations.
|
||||
*
|
||||
* @param Boolean $allow
|
||||
* @return void
|
||||
*/
|
||||
public function setAllowNewKeys($allow)
|
||||
{
|
||||
$this->allowNewKeys = (Boolean) $allow;
|
||||
}
|
||||
|
||||
public function setPerformDeepMerging($boolean)
|
||||
{
|
||||
$this->performDeepMerging = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
@ -26,10 +120,41 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function hasDefaultValue()
|
||||
{
|
||||
if (null !== $this->prototype) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->addIfNotSet;
|
||||
}
|
||||
|
||||
public function getDefaultValue()
|
||||
{
|
||||
if (!$this->hasDefaultValue()) {
|
||||
throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath()));
|
||||
}
|
||||
|
||||
if (null !== $this->prototype) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$defaults = array();
|
||||
foreach ($this->children as $name => $child) {
|
||||
if (!$child->hasDefaultValue()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$defaults[$name] = $child->getDefaultValue();
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
public function setPrototype(PrototypeNodeInterface $node)
|
||||
{
|
||||
if (count($this->children) > 0) {
|
||||
throw new \RuntimeException('An ARRAY node must either have concrete children, or a prototype node.');
|
||||
throw new \RuntimeException($this->getPath().': An ARRAY node must either have concrete children, or a prototype node.');
|
||||
}
|
||||
|
||||
$this->prototype = $node;
|
||||
@ -51,9 +176,65 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
|
||||
$this->children[$name] = $node;
|
||||
}
|
||||
|
||||
protected function finalizeValue($value)
|
||||
{
|
||||
if (false === $value) {
|
||||
throw new UnsetKeyException(sprintf(
|
||||
'Unsetting key for path "%s", value: %s',
|
||||
$this->getPath(),
|
||||
json_encode($value)
|
||||
));
|
||||
}
|
||||
|
||||
if (null !== $this->prototype) {
|
||||
foreach ($value as $k => $v) {
|
||||
try {
|
||||
$value[$k] = $this->prototype->finalize($v);
|
||||
} catch (UnsetKeyException $unset) {
|
||||
unset($value[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($value) < $this->minNumberOfElements) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'You must define at least %d element(s) for path "%s".',
|
||||
$this->minNumberOfElements,
|
||||
$this->getPath()
|
||||
));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
foreach ($this->children as $name => $child) {
|
||||
if (!array_key_exists($name, $value)) {
|
||||
if ($child->isRequired()) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'The node at path "%s" must be configured.',
|
||||
$this->getPath()
|
||||
));
|
||||
}
|
||||
|
||||
if ($child->hasDefaultValue()) {
|
||||
$value[$name] = $child->getDefaultValue();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$value[$name] = $child->finalize($value[$name]);
|
||||
} catch (UnsetKeyException $unset) {
|
||||
unset($value[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function validateType($value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
if (!is_array($value) && (!$this->allowFalse || false !== $value)) {
|
||||
throw new InvalidTypeException(sprintf(
|
||||
'Invalid type for path "%s". Expected array, but got %s',
|
||||
$this->getPath(),
|
||||
@ -64,7 +245,11 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
|
||||
|
||||
protected function normalizeValue($value)
|
||||
{
|
||||
foreach ($this->normalizeTransformations as $transformation) {
|
||||
if (false === $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
foreach ($this->xmlRemappings as $transformation) {
|
||||
list($singular, $plural) = $transformation;
|
||||
|
||||
if (!isset($value[$singular])) {
|
||||
@ -77,8 +262,24 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
|
||||
if (null !== $this->prototype) {
|
||||
$normalized = array();
|
||||
foreach ($value as $k => $v) {
|
||||
if (null !== $this->keyAttribute && is_array($v) && isset($v[$this->keyAttribute])) {
|
||||
$k = $v[$this->keyAttribute];
|
||||
if (null !== $this->keyAttribute && is_array($v)) {
|
||||
if (!isset($v[$this->keyAttribute]) && is_int($k)) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'You must set a "%s" attribute for path "%s".',
|
||||
$this->keyAttribute,
|
||||
$this->getPath()
|
||||
));
|
||||
} else if (isset($v[$this->keyAttribute])) {
|
||||
$k = $v[$this->keyAttribute];
|
||||
}
|
||||
|
||||
if (array_key_exists($k, $normalized)) {
|
||||
throw new DuplicateKeyException(sprintf(
|
||||
'Duplicate key "%s" for path "%s".',
|
||||
$k,
|
||||
$this->getPath()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$this->prototype->setName($k);
|
||||
@ -103,4 +304,50 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
protected function mergeValues($leftSide, $rightSide)
|
||||
{
|
||||
if (false === $rightSide) {
|
||||
// if this is still false after the last config has been merged the
|
||||
// finalization pass will take care of removing this key entirely
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === $leftSide || !$this->performDeepMerging) {
|
||||
return $rightSide;
|
||||
}
|
||||
|
||||
foreach ($rightSide as $k => $v) {
|
||||
// no conflict
|
||||
if (!array_key_exists($k, $leftSide)) {
|
||||
if (!$this->allowNewKeys) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'You are not allowed to define new elements for path "%s". '
|
||||
.'Please define all elements for this path in one config file.',
|
||||
$this->getPath()
|
||||
));
|
||||
}
|
||||
|
||||
$leftSide[$k] = $v;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (null !== $this->prototype) {
|
||||
$this->prototype->setName($k);
|
||||
$leftSide[$k] = $this->prototype->merge($leftSide[$k], $v);
|
||||
} else {
|
||||
if (!isset($this->children[$k])) {
|
||||
throw new \RuntimeException('merge() expects a normalized config array.');
|
||||
}
|
||||
|
||||
$leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v);
|
||||
}
|
||||
} catch (UnsetKeyException $unset) {
|
||||
unset($leftSide[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $leftSide;
|
||||
}
|
||||
}
|
@ -2,15 +2,25 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\Exception;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\ForbiddenOverwriteException;
|
||||
|
||||
/**
|
||||
* The base node class
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
abstract class BaseNode implements NodeInterface
|
||||
{
|
||||
protected $name;
|
||||
protected $parent;
|
||||
protected $beforeTransformations;
|
||||
protected $afterTransformations;
|
||||
protected $nodeFactory;
|
||||
protected $normalizationClosures;
|
||||
protected $finalValidationClosures;
|
||||
protected $allowOverwrite;
|
||||
protected $required;
|
||||
protected $equivalentValues;
|
||||
|
||||
public function __construct($name, NodeInterface $parent = null, $beforeTransformations = array(), $afterTransformations = array())
|
||||
public function __construct($name, NodeInterface $parent = null)
|
||||
{
|
||||
if (false !== strpos($name, '.')) {
|
||||
throw new \InvalidArgumentException('The name must not contain ".".');
|
||||
@ -18,8 +28,41 @@ abstract class BaseNode implements NodeInterface
|
||||
|
||||
$this->name = $name;
|
||||
$this->parent = $parent;
|
||||
$this->beforeTransformations = $beforeTransformations;
|
||||
$this->afterTransformations = $afterTransformations;
|
||||
$this->normalizationClosures = array();
|
||||
$this->finalValidationClosures = array();
|
||||
$this->allowOverwrite = true;
|
||||
$this->required = false;
|
||||
$this->equivalentValues = array();
|
||||
}
|
||||
|
||||
public function addEquivalentValue($originalValue, $equivalentValue)
|
||||
{
|
||||
$this->equivalentValues[] = array($originalValue, $equivalentValue);
|
||||
}
|
||||
|
||||
public function setRequired($boolean)
|
||||
{
|
||||
$this->required = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
public function setAllowOverwrite($allow)
|
||||
{
|
||||
$this->allowOverwrite = (Boolean) $allow;
|
||||
}
|
||||
|
||||
public function setNormalizationClosures(array $closures)
|
||||
{
|
||||
$this->normalizationClosures = $closures;
|
||||
}
|
||||
|
||||
public function setFinalValidationClosures(array $closures)
|
||||
{
|
||||
$this->finalValidationClosures = $closures;
|
||||
}
|
||||
|
||||
public function isRequired()
|
||||
{
|
||||
return $this->required;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
@ -38,22 +81,64 @@ abstract class BaseNode implements NodeInterface
|
||||
return $path;
|
||||
}
|
||||
|
||||
public final function merge($leftSide, $rightSide)
|
||||
{
|
||||
if (!$this->allowOverwrite) {
|
||||
throw new ForbiddenOverwriteException(sprintf(
|
||||
'Configuration path "%s" cannot be overwritten. You have to '
|
||||
.'define all options for this path, and any of its sub-paths in '
|
||||
.'one configuration section.',
|
||||
$this->getPath()
|
||||
));
|
||||
}
|
||||
|
||||
$this->validateType($leftSide);
|
||||
$this->validateType($rightSide);
|
||||
|
||||
return $this->mergeValues($leftSide, $rightSide);
|
||||
}
|
||||
|
||||
public final function normalize($value)
|
||||
{
|
||||
// run before transformations
|
||||
foreach ($this->beforeTransformations as $transformation) {
|
||||
$value = $transformation($value);
|
||||
// run custom normalization closures
|
||||
foreach ($this->normalizationClosures as $closure) {
|
||||
$value = $closure($value);
|
||||
}
|
||||
|
||||
// replace value with their equivalent
|
||||
foreach ($this->equivalentValues as $data) {
|
||||
if ($data[0] === $value) {
|
||||
$value = $data[1];
|
||||
}
|
||||
}
|
||||
|
||||
// validate type
|
||||
$this->validateType($value);
|
||||
|
||||
// normalize value
|
||||
$value = $this->normalizeValue($value);
|
||||
return $this->normalizeValue($value);
|
||||
}
|
||||
|
||||
// run after transformations
|
||||
foreach ($this->afterTransformations as $transformation) {
|
||||
$value = $transformation($value);
|
||||
public final function finalize($value)
|
||||
{
|
||||
$this->validateType($value);
|
||||
|
||||
$value = $this->finalizeValue($value);
|
||||
|
||||
// Perform validation on the final value if a closure has been set.
|
||||
// The closure is also allowed to return another value.
|
||||
foreach ($this->finalValidationClosures as $closure) {
|
||||
try {
|
||||
$value = $closure($value);
|
||||
} catch (Exception $correctEx) {
|
||||
throw $correctEx;
|
||||
} catch (\Exception $invalid) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'Invalid configuration for path "%s": %s',
|
||||
$this->getPath(),
|
||||
$invalid->getMessage()
|
||||
), $invalid->getCode(), $invalid);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
@ -61,4 +146,6 @@ abstract class BaseNode implements NodeInterface
|
||||
|
||||
abstract protected function validateType($value);
|
||||
abstract protected function normalizeValue($value);
|
||||
abstract protected function mergeValues($leftSide, $rightSide);
|
||||
abstract protected function finalizeValue($value);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException;
|
||||
|
||||
class BooleanNode extends ScalarNode
|
||||
{
|
||||
protected function validateType($value)
|
||||
{
|
||||
parent::validateType($value);
|
||||
|
||||
if (!is_bool($value)) {
|
||||
throw new InvalidTypeException(sprintf(
|
||||
'Invalid type for path "%s". Expected boolean, but got %s.',
|
||||
$this->getPath(),
|
||||
json_encode($value)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,11 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Builder;
|
||||
|
||||
/**
|
||||
* This class builds an if expression.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ExprBuilder
|
||||
{
|
||||
public $parent;
|
||||
@ -13,8 +18,12 @@ class ExprBuilder
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
public function ifTrue(\Closure $closure)
|
||||
public function ifTrue(\Closure $closure = null)
|
||||
{
|
||||
if (null === $closure) {
|
||||
$closure = function($v) { return true === $v; };
|
||||
}
|
||||
|
||||
$this->ifPart = $closure;
|
||||
|
||||
return $this;
|
||||
@ -48,6 +57,31 @@ class ExprBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function thenReplaceKeyWithAttribute($attribute)
|
||||
{
|
||||
$this->thenPart = function($v) {
|
||||
$newValue = array();
|
||||
foreach ($v as $k => $oldValue) {
|
||||
if (is_array($oldValue) && isset($oldValue['id'])) {
|
||||
$k = $oldValue['id'];
|
||||
}
|
||||
|
||||
$newValue[$k] = $oldValue;
|
||||
}
|
||||
|
||||
return $newValue;
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function thenEmptyArray()
|
||||
{
|
||||
$this->thenPart = function($v) { return array(); };
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
if (null === $this->ifPart) {
|
||||
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Builder;
|
||||
|
||||
/**
|
||||
* This class builds merge conditions.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class MergeBuilder
|
||||
{
|
||||
public $parent;
|
||||
public $allowFalse;
|
||||
public $allowOverwrite;
|
||||
|
||||
public function __construct($parent)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->allowFalse = false;
|
||||
$this->allowOverwrite = true;
|
||||
}
|
||||
|
||||
public function allowUnset($allow = true)
|
||||
{
|
||||
$this->allowFalse = $allow;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function denyOverwrite($deny = true)
|
||||
{
|
||||
$this->allowOverwrite = !$deny;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
}
|
@ -2,6 +2,11 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Builder;
|
||||
|
||||
/**
|
||||
* This class provides a fluent interface for building a config tree.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class NodeBuilder
|
||||
{
|
||||
/************
|
||||
@ -13,9 +18,20 @@ class NodeBuilder
|
||||
public $parent;
|
||||
public $children;
|
||||
public $prototype;
|
||||
public $normalizeTransformations;
|
||||
public $beforeTransformations;
|
||||
public $afterTransformations;
|
||||
public $normalization;
|
||||
public $merge;
|
||||
public $finalization;
|
||||
public $defaultValue;
|
||||
public $default;
|
||||
public $addDefaults;
|
||||
public $required;
|
||||
public $atLeastOne;
|
||||
public $allowNewKeys;
|
||||
public $allowEmptyValue;
|
||||
public $nullEquivalent;
|
||||
public $trueEquivalent;
|
||||
public $falseEquivalent;
|
||||
public $performDeepMerging;
|
||||
|
||||
public function __construct($name, $type, $parent = null)
|
||||
{
|
||||
@ -23,10 +39,28 @@ class NodeBuilder
|
||||
$this->type = $type;
|
||||
$this->parent = $parent;
|
||||
|
||||
$this->children =
|
||||
$this->beforeTransformations =
|
||||
$this->afterTransformations =
|
||||
$this->normalizeTransformations = array();
|
||||
$this->default = false;
|
||||
$this->required = false;
|
||||
$this->addDefaults = false;
|
||||
$this->allowNewKeys = true;
|
||||
$this->atLeastOne = false;
|
||||
$this->allowEmptyValue = true;
|
||||
$this->children = array();
|
||||
$this->performDeepMerging = true;
|
||||
|
||||
if ('boolean' === $type) {
|
||||
$this->nullEquivalent = true;
|
||||
} else if ('array' === $type) {
|
||||
$this->nullEquivalent = array();
|
||||
}
|
||||
|
||||
if ('array' === $type) {
|
||||
$this->trueEquivalent = array();
|
||||
} else {
|
||||
$this->trueEquivalent = true;
|
||||
}
|
||||
|
||||
$this->falseEquivalent = false;
|
||||
}
|
||||
|
||||
/****************************
|
||||
@ -35,54 +69,178 @@ class NodeBuilder
|
||||
|
||||
public function node($name, $type)
|
||||
{
|
||||
$node = new NodeBuilder($name, $type, $this);
|
||||
$node = new static($name, $type, $this);
|
||||
|
||||
return $this->children[$name] = $node;
|
||||
}
|
||||
|
||||
public function normalize($key, $plural = null)
|
||||
public function arrayNode($name)
|
||||
{
|
||||
if (null === $plural) {
|
||||
$plural = $key.'s';
|
||||
}
|
||||
return $this->node($name, 'array');
|
||||
}
|
||||
|
||||
$this->normalizeTransformations[] = array($key, $plural);
|
||||
public function scalarNode($name)
|
||||
{
|
||||
return $this->node($name, 'scalar');
|
||||
}
|
||||
|
||||
public function booleanNode($name)
|
||||
{
|
||||
return $this->node($name, 'boolean');
|
||||
}
|
||||
|
||||
public function defaultValue($value)
|
||||
{
|
||||
$this->default = true;
|
||||
$this->defaultValue = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function key($name)
|
||||
public function isRequired()
|
||||
{
|
||||
$this->required = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function containsNameValuePairsWithKeyAttribute($attribute)
|
||||
{
|
||||
$this->beforeNormalization()
|
||||
->ifArray()
|
||||
->thenReplaceKeyWithAttribute($attribute)
|
||||
;
|
||||
|
||||
$this->useAttributeAsKey($attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function requiresAtLeastOneElement()
|
||||
{
|
||||
$this->atLeastOne = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function treatNullLike($value)
|
||||
{
|
||||
$this->nullEquivalent = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function treatTrueLike($value)
|
||||
{
|
||||
$this->trueEquivalent = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function treatFalseLike($value)
|
||||
{
|
||||
$this->falseEquivalent = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function defaultNull()
|
||||
{
|
||||
return $this->defaultValue(null);
|
||||
}
|
||||
|
||||
public function defaultTrue()
|
||||
{
|
||||
return $this->defaultValue(true);
|
||||
}
|
||||
|
||||
public function defaultFalse()
|
||||
{
|
||||
return $this->defaultValue(false);
|
||||
}
|
||||
|
||||
public function addDefaultsIfNotSet()
|
||||
{
|
||||
$this->addDefaults = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function disallowNewKeysInSubsequentConfigs()
|
||||
{
|
||||
$this->allowNewKeys = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function normalization()
|
||||
{
|
||||
if (null === $this->normalization) {
|
||||
$this->normalization = new NormalizationBuilder($this);
|
||||
}
|
||||
|
||||
return $this->normalization;
|
||||
}
|
||||
|
||||
public function beforeNormalization()
|
||||
{
|
||||
return $this->normalization()->before();
|
||||
}
|
||||
|
||||
public function fixXmlConfig($singular, $plural = null)
|
||||
{
|
||||
$this->normalization()->remap($singular, $plural);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function useAttributeAsKey($name)
|
||||
{
|
||||
$this->key = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function before(\Closure $closure = null)
|
||||
protected function merge()
|
||||
{
|
||||
if (null !== $closure) {
|
||||
$this->beforeTransformations[] = $closure;
|
||||
|
||||
return $this;
|
||||
if (null === $this->merge) {
|
||||
$this->merge = new MergeBuilder($this);
|
||||
}
|
||||
|
||||
return $this->beforeTransformations[] = new ExprBuilder($this);
|
||||
return $this->merge;
|
||||
}
|
||||
|
||||
public function cannotBeOverwritten($deny = true)
|
||||
{
|
||||
$this->merge()->denyOverwrite($deny);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function cannotBeEmpty()
|
||||
{
|
||||
$this->allowEmptyValue = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function canBeUnset($allow = true)
|
||||
{
|
||||
$this->merge()->allowUnset($allow);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function prototype($type)
|
||||
{
|
||||
return $this->prototype = new NodeBuilder(null, $type, $this);
|
||||
return $this->prototype = new static(null, $type, $this);
|
||||
}
|
||||
|
||||
public function after(\Closure $closure = null)
|
||||
public function performNoDeepMerging()
|
||||
{
|
||||
if (null !== $closure) {
|
||||
$this->afterTransformations[] = $closure;
|
||||
$this->performDeepMerging = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->afterTransformations[] = new ExprBuilder($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function end()
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Builder;
|
||||
|
||||
/**
|
||||
* This class builds normalization conditions.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class NormalizationBuilder
|
||||
{
|
||||
public $parent;
|
||||
public $before;
|
||||
public $remappings;
|
||||
|
||||
public function __construct($parent)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
$this->keys = false;
|
||||
|
||||
$this->remappings =
|
||||
$this->before =
|
||||
$this->after = array();
|
||||
}
|
||||
|
||||
public function remap($key, $plural = null)
|
||||
{
|
||||
if (null === $plural) {
|
||||
$plural = $key.'s';
|
||||
}
|
||||
|
||||
$this->remappings[] = array($key, $plural);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function before(\Closure $closure = null)
|
||||
{
|
||||
if (null !== $closure) {
|
||||
$this->before[] = $closure;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->before[] = new ExprBuilder($this->parent);
|
||||
}
|
||||
}
|
@ -2,9 +2,18 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Builder;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\BaseNode;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\BooleanNode;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\ArrayNode;
|
||||
use Symfony\Component\DependencyInjection\Configuration\ScalarNode;
|
||||
|
||||
/**
|
||||
* This is the entry class for building your own config tree.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class TreeBuilder
|
||||
{
|
||||
protected $root;
|
||||
@ -32,9 +41,6 @@ class TreeBuilder
|
||||
|
||||
protected function createConfigNode(NodeBuilder $node)
|
||||
{
|
||||
$node->beforeTransformations = $this->buildExpressions($node->beforeTransformations);
|
||||
$node->afterTransformations = $this->buildExpressions($node->afterTransformations);
|
||||
|
||||
$method = 'create'.$node->type.'ConfigNode';
|
||||
if (!method_exists($this, $method)) {
|
||||
throw new \RuntimeException(sprintf('Unknown node type: "%s"', $node->type));
|
||||
@ -43,14 +49,77 @@ class TreeBuilder
|
||||
return $this->$method($node);
|
||||
}
|
||||
|
||||
protected function createBooleanConfigNode(NodeBuilder $node)
|
||||
{
|
||||
$configNode = new BooleanNode($node->name, $node->parent);
|
||||
$this->configureScalarNode($configNode, $node);
|
||||
|
||||
return $configNode;
|
||||
}
|
||||
|
||||
protected function createScalarConfigNode(NodeBuilder $node)
|
||||
{
|
||||
return new ScalarNode($node->name, $node->parent, $node->beforeTransformations, $node->afterTransformations);
|
||||
$configNode = new ScalarNode($node->name, $node->parent);
|
||||
$this->configureScalarNode($configNode, $node);
|
||||
|
||||
return $configNode;
|
||||
}
|
||||
|
||||
protected function configureScalarNode(ScalarNode $configNode, NodeBuilder $node)
|
||||
{
|
||||
if (null !== $node->normalization) {
|
||||
$configNode->setNormalizationClosures(
|
||||
$this->buildExpressions($node->normalization->before)
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $node->merge) {
|
||||
$configNode->setAllowOverwrite($node->merge->allowOverwrite);
|
||||
}
|
||||
|
||||
if (true === $node->default) {
|
||||
$configNode->setDefaultValue($node->defaultValue);
|
||||
}
|
||||
|
||||
if (false === $node->allowEmptyValue) {
|
||||
$configNode->setAllowEmptyValue($node->allowEmptyValue);
|
||||
}
|
||||
|
||||
$configNode->addEquivalentValue(null, $node->nullEquivalent);
|
||||
$configNode->addEquivalentValue(true, $node->trueEquivalent);
|
||||
$configNode->addEquivalentValue(false, $node->falseEquivalent);
|
||||
}
|
||||
|
||||
protected function createArrayConfigNode(NodeBuilder $node)
|
||||
{
|
||||
$configNode = new ArrayNode($node->name, $node->parent, $node->beforeTransformations, $node->afterTransformations, $node->normalizeTransformations, $node->key);
|
||||
$configNode = new ArrayNode($node->name, $node->parent);
|
||||
$configNode->setAddIfNotSet($node->addDefaults);
|
||||
$configNode->setAllowNewKeys($node->allowNewKeys);
|
||||
$configNode->addEquivalentValue(null, $node->nullEquivalent);
|
||||
$configNode->addEquivalentValue(true, $node->trueEquivalent);
|
||||
$configNode->addEquivalentValue(false, $node->falseEquivalent);
|
||||
$configNode->setPerformDeepMerging($node->performDeepMerging);
|
||||
|
||||
if (null !== $node->key) {
|
||||
$configNode->setKeyAttribute($node->key);
|
||||
}
|
||||
|
||||
if (true === $node->atLeastOne) {
|
||||
$configNode->setMinNumberOfElements(1);
|
||||
}
|
||||
|
||||
if (null !== $node->normalization) {
|
||||
$configNode->setNormalizationClosures(
|
||||
$this->buildExpressions($node->normalization->before)
|
||||
);
|
||||
|
||||
$configNode->setXmlRemappings($node->normalization->remappings);
|
||||
}
|
||||
|
||||
if (null !== $node->merge) {
|
||||
$configNode->setAllowOverwrite($node->merge->allowOverwrite);
|
||||
$configNode->setAllowFalse($node->merge->allowFalse);
|
||||
}
|
||||
|
||||
foreach ($node->children as $child) {
|
||||
$child->parent = $configNode;
|
||||
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown whenever the key of an array is not unique. This can
|
||||
* only be the case if the configuration is coming from an XML file.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class DuplicateKeyException extends InvalidConfigurationException
|
||||
{
|
||||
}
|
@ -2,6 +2,11 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Exception;
|
||||
|
||||
/**
|
||||
* Base exception for all configuration exceptions
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class Exception extends \RuntimeException
|
||||
{
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown when a configuration path is overwritten from a
|
||||
* subsequent configuration file, but the entry node specifically forbids this.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ForbiddenOverwriteException extends InvalidConfigurationException
|
||||
{
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Exception;
|
||||
|
||||
/**
|
||||
* A very general exception which can be thrown whenever non of the more specific
|
||||
* exceptions is suitable.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class InvalidConfigurationException extends Exception
|
||||
{
|
||||
}
|
@ -5,8 +5,8 @@ namespace Symfony\Component\DependencyInjection\Configuration\Exception;
|
||||
/**
|
||||
* This exception is thrown if an invalid type is encountered.
|
||||
*
|
||||
* @author johannes
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class InvalidTypeException extends Exception
|
||||
class InvalidTypeException extends InvalidConfigurationException
|
||||
{
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration\Exception;
|
||||
|
||||
/**
|
||||
* This exception is usually not encountered by the end-user, but only used
|
||||
* internally to signal the parent scope to unset a key.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class UnsetKeyException extends Exception
|
||||
{
|
||||
}
|
@ -2,9 +2,21 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration;
|
||||
|
||||
/**
|
||||
* Common Interface among all nodes.
|
||||
*
|
||||
* In most cases, it is better to inherit from BaseNode instead of implementing
|
||||
* this interface yourself.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
interface NodeInterface
|
||||
{
|
||||
function getName();
|
||||
function getPath();
|
||||
function isRequired();
|
||||
function hasDefaultValue();
|
||||
function getDefaultValue();
|
||||
function normalize($value);
|
||||
function merge($leftSide, $rightSide);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
|
||||
/**
|
||||
* This class is the entry point for config normalization/merging/finalization.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class Processor
|
||||
{
|
||||
public function process(NodeInterface $configTree, array $configs)
|
||||
{
|
||||
$configs = Extension::normalizeKeys($configs);
|
||||
|
||||
$currentConfig = array();
|
||||
foreach ($configs as $config) {
|
||||
$config = $configTree->normalize($config);
|
||||
$currentConfig = $configTree->merge($currentConfig, $config);
|
||||
}
|
||||
|
||||
return $configTree->finalize($currentConfig);
|
||||
}
|
||||
}
|
@ -2,10 +2,41 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException;
|
||||
|
||||
/**
|
||||
* This node represents a scalar value in the config tree.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class ScalarNode extends BaseNode implements PrototypeNodeInterface
|
||||
{
|
||||
protected $defaultValueSet = false;
|
||||
protected $defaultValue;
|
||||
protected $allowEmptyValue = true;
|
||||
|
||||
public function setDefaultValue($value)
|
||||
{
|
||||
$this->defaultValueSet = true;
|
||||
$this->defaultValue = $value;
|
||||
}
|
||||
|
||||
public function hasDefaultValue()
|
||||
{
|
||||
return $this->defaultValueSet;
|
||||
}
|
||||
|
||||
public function getDefaultValue()
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
public function setAllowEmptyValue($boolean)
|
||||
{
|
||||
$this->allowEmptyValue = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
@ -14,7 +45,7 @@ class ScalarNode extends BaseNode implements PrototypeNodeInterface
|
||||
protected function validateType($value)
|
||||
{
|
||||
if (!is_scalar($value)) {
|
||||
throw new \InvalidTypeException(sprintf(
|
||||
throw new InvalidTypeException(sprintf(
|
||||
'Invalid type for path "%s". Expected scalar, but got %s.',
|
||||
$this->getPath(),
|
||||
json_encode($value)
|
||||
@ -22,8 +53,26 @@ class ScalarNode extends BaseNode implements PrototypeNodeInterface
|
||||
}
|
||||
}
|
||||
|
||||
protected function finalizeValue($value)
|
||||
{
|
||||
if (!$this->allowEmptyValue && empty($value)) {
|
||||
throw new InvalidConfigurationException(sprintf(
|
||||
'The path "%s" cannot contain an empty value, but got %s.',
|
||||
$this->getPath(),
|
||||
json_encode($value)
|
||||
));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function normalizeValue($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function mergeValues($leftSide, $rightSide)
|
||||
{
|
||||
return $rightSide;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\ArrayNode;
|
||||
|
||||
class ArrayNodeTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException
|
||||
*/
|
||||
public function testNormalizeThrowsExceptionWhenFalseIsNotAllowed()
|
||||
{
|
||||
$node = new ArrayNode('root');
|
||||
$node->normalize(false);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;
|
||||
use Symfony\Component\DependencyInjection\Configuration\Processor;
|
||||
use Symfony\Component\DependencyInjection\Configuration\NodeInterface;
|
||||
|
||||
class FinalizationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
||||
public function testUnsetKeyWithDeepHierarchy()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
$tree = $tb
|
||||
->root('config', 'array')
|
||||
->node('level1', 'array')
|
||||
->canBeUnset()
|
||||
->node('level2', 'array')
|
||||
->canBeUnset()
|
||||
->node('somevalue', 'scalar')->end()
|
||||
->node('anothervalue', 'scalar')->end()
|
||||
->end()
|
||||
->node('level1_scalar', 'scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->buildTree()
|
||||
;
|
||||
|
||||
$a = array(
|
||||
'level1' => array(
|
||||
'level2' => array(
|
||||
'somevalue' => 'foo',
|
||||
'anothervalue' => 'bar',
|
||||
),
|
||||
'level1_scalar' => 'foo',
|
||||
),
|
||||
);
|
||||
|
||||
$b = array(
|
||||
'level1' => array(
|
||||
'level2' => false,
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals(array(
|
||||
'level1' => array(
|
||||
'level1_scalar' => 'foo',
|
||||
),
|
||||
), $this->process($tree, array($a, $b)));
|
||||
}
|
||||
|
||||
protected function process(NodeInterface $tree, array $configs)
|
||||
{
|
||||
$processor = new Processor();
|
||||
|
||||
return $processor->process($tree, $configs);
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Tests\Component\DependencyInjection\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Configuration\Builder\TreeBuilder;
|
||||
|
||||
class MergeTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException Symfony\Component\DependencyInjection\Configuration\Exception\ForbiddenOverwriteException
|
||||
*/
|
||||
public function testForbiddenOverwrite()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
$tree = $tb
|
||||
->root('root', 'array')
|
||||
->node('foo', 'scalar')
|
||||
->merge()
|
||||
->denyOverwrite()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->buildTree()
|
||||
;
|
||||
|
||||
$a = array(
|
||||
'foo' => 'bar',
|
||||
);
|
||||
|
||||
$b = array(
|
||||
'foo' => 'moo',
|
||||
);
|
||||
|
||||
$tree->merge($a, $b);
|
||||
}
|
||||
|
||||
public function testUnsetKey()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
$tree = $tb
|
||||
->root('root', 'array')
|
||||
->node('foo', 'scalar')->end()
|
||||
->node('bar', 'scalar')->end()
|
||||
->node('unsettable', 'array')
|
||||
->merge()->allowUnset()->end()
|
||||
->node('foo', 'scalar')->end()
|
||||
->node('bar', 'scalar')->end()
|
||||
->end()
|
||||
->node('unsetted', 'array')
|
||||
->merge()->allowUnset()->end()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->buildTree()
|
||||
;
|
||||
|
||||
$a = array(
|
||||
'foo' => 'bar',
|
||||
'unsettable' => array(
|
||||
'foo' => 'a',
|
||||
'bar' => 'b',
|
||||
),
|
||||
'unsetted' => false,
|
||||
);
|
||||
|
||||
$b = array(
|
||||
'foo' => 'moo',
|
||||
'bar' => 'b',
|
||||
'unsettable' => false,
|
||||
'unsetted' => array('a', 'b'),
|
||||
);
|
||||
|
||||
$this->assertEquals(array(
|
||||
'foo' => 'moo',
|
||||
'bar' => 'b',
|
||||
'unsettable' => false,
|
||||
'unsetted' => array('a', 'b'),
|
||||
), $tree->merge($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Symfony\Component\DependencyInjection\Configuration\Exception\InvalidConfigurationException
|
||||
*/
|
||||
public function testDoesNotAllowNewKeysInSubsequentConfigs()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
$tree = $tb
|
||||
->root('config', 'array')
|
||||
->node('test', 'array')
|
||||
->disallowNewKeysInSubsequentConfigs()
|
||||
->useAttributeAsKey('key')
|
||||
->prototype('array')
|
||||
->node('value', 'scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->buildTree();
|
||||
|
||||
$a = array(
|
||||
'test' => array(
|
||||
'a' => array('value' => 'foo')
|
||||
)
|
||||
);
|
||||
|
||||
$b = array(
|
||||
'test' => array(
|
||||
'b' => array('value' => 'foo')
|
||||
)
|
||||
);
|
||||
|
||||
$tree->merge($a, $b);
|
||||
}
|
||||
|
||||
public function testPerformsNoDeepMerging()
|
||||
{
|
||||
$tb = new TreeBuilder();
|
||||
|
||||
$tree = $tb
|
||||
->root('config', 'array')
|
||||
->node('no_deep_merging', 'array')
|
||||
->performNoDeepMerging()
|
||||
->node('foo', 'scalar')->end()
|
||||
->node('bar', 'scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
->buildTree()
|
||||
;
|
||||
|
||||
$a = array(
|
||||
'no_deep_merging' => array(
|
||||
'foo' => 'a',
|
||||
'bar' => 'b',
|
||||
),
|
||||
);
|
||||
|
||||
$b = array(
|
||||
'no_deep_merging' => array(
|
||||
'c' => 'd',
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(array(
|
||||
'no_deep_merging' => array(
|
||||
'c' => 'd',
|
||||
)
|
||||
), $tree->merge($a, $b));
|
||||
}
|
||||
}
|
@ -15,11 +15,11 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase
|
||||
$tb = new TreeBuilder();
|
||||
$tree = $tb
|
||||
->root('root_name', 'array')
|
||||
->normalize('encoder')
|
||||
->fixXmlConfig('encoder')
|
||||
->node('encoders', 'array')
|
||||
->key('class')
|
||||
->useAttributeAsKey('class')
|
||||
->prototype('array')
|
||||
->before()->ifString()->then(function($v) { return array('algorithm' => $v); })->end()
|
||||
->beforeNormalization()->ifString()->then(function($v) { return array('algorithm' => $v); })->end()
|
||||
->node('algorithm', 'scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
@ -87,7 +87,7 @@ class NormalizerTest extends \PHPUnit_Framework_TestCase
|
||||
$tree = $tb
|
||||
->root('root', 'array')
|
||||
->node('logout', 'array')
|
||||
->normalize('handler')
|
||||
->fixXmlConfig('handler')
|
||||
->node('handlers', 'array')
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
|
Reference in New Issue
Block a user