From 199e6bf893d2fb7f138e4e4d0a44e78e583eca47 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Feb 2011 16:02:59 +0100 Subject: [PATCH 01/13] [BrowserKit] removed annotation in a unit test to fix coverage --- tests/Symfony/Tests/Component/BrowserKit/ClientTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php b/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php index 3d493f9d21ff2..42f45569dec2c 100644 --- a/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php +++ b/tests/Symfony/Tests/Component/BrowserKit/ClientTest.php @@ -102,9 +102,6 @@ public function testGetResponse() $this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request'); } - /** - * @covers Symfony\Component\BrowserKit\Client::getContent - */ public function testGetContent() { $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; From f9138d313b83f951cf82a2f7f902a2d6dd14fbb3 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Mon, 24 Jan 2011 14:50:31 -0500 Subject: [PATCH 02/13] [FrameworkBundle] Implemented single-pass config loading with intelligent option merging for FrameworkExtension Restructured config format to make processing more straightforward. Important changes that might break existing configs: * Added "enabled" option for translator (improves multi-format compat) * Removed hash variation of validation annotations option (only boolean) * Moved namespace option directly under validation (improves multi-format compat) The new merge process depends on an internal array of all supported options and their default values, which is used for both validating the config schema and inferring how to merge options (as an added benefit, it helps make the extension self-documenting). Exceptions will now be thrown for merge errors resulting from unrecognized options or invalid types. Since incoming configurations are all merged atop the defaults, many isset() checks were removed. As a rule of thumb, we probably only want to ignore null values when an option would be used to set a parameter. Also: * Added missing attributes to symfony-1.0.xsd * profiler: added only-exceptions attribute * session: fix types and add pdo attributes * Create FrameworkExtension tests with PHP/XML/YAML fixtures * Use "%" syntax instead of calling getParameter() within FrameworkExtension * Normalize config keys and arrays with helper methods for PHP/XML/YAML compatibility Earlier changes: * Remove nonexistent "DependencyInjection/Resources/" path from XmlFileLoaders * Remove hasDefinition() checks, as register methods should only execute once * Remove first-run logic from registerTranslatorConfiguration(), as it is only run once * Removed apparently obsolete clearTags() calls on definitions for non-enabled features --- .../FrameworkExtension.php | 771 +++++++++--------- .../Resources/config/schema/symfony-1.0.xsd | 63 +- .../DependencyInjection/Fixtures/php/full.php | 45 + .../Fixtures/php/session_pdo.php | 17 + .../Fixtures/php/validation_annotations.php | 17 + .../DependencyInjection/Fixtures/xml/full.xml | 24 + .../Fixtures/xml/session_pdo.xml | 16 + .../Fixtures/xml/validation_annotations.xml | 18 + .../DependencyInjection/Fixtures/yml/full.yml | 34 + .../Fixtures/yml/session_pdo.yml | 11 + .../Fixtures/yml/validation_annotations.yml | 10 + .../FrameworkExtensionTest.php | 181 +++- .../PhpFrameworkExtensionTest.php | 24 + .../XmlFrameworkExtensionTest.php | 24 + .../YamlFrameworkExtensionTest.php | 24 + 15 files changed, 865 insertions(+), 414 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ecbeae5bb72d7..9b7760ce072f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -11,150 +11,88 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\DependencyInjection\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Resource\FileResource; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\Form\FormContext; /** * FrameworkExtension. * * @author Fabien Potencier + * @author Jeremy Mikola */ class FrameworkExtension extends Extension { - public function configLoad(array $configs, ContainerBuilder $container) - { - foreach ($configs as $config) { - $this->doConfigLoad($config, $container); - } - } - /** - * Loads the web configuration. + * Responds to the app.config configuration parameter. * - * @param array $config An array of configuration settings - * @param ContainerBuilder $container A ContainerBuilder instance + * @param array $configs + * @param ContainerBuilder $container */ - protected function doConfigLoad(array $config, ContainerBuilder $container) + public function configLoad(array $configs, ContainerBuilder $container) { $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - if (!$container->hasDefinition('controller_resolver')) { - $loader->load('web.xml'); - } - - if (!$container->hasDefinition('form.factory')) { - $loader->load('form.xml'); - } - - if (isset($config['csrf-protection'])) { - $config['csrf_protection'] = $config['csrf-protection']; - } - - if (isset($config['csrf_protection'])) { - foreach (array('enabled', 'field_name', 'field-name', 'secret') as $key) { - if (isset($config['csrf_protection'][$key])) { - $container->setParameter('form.csrf_protection.'.strtr($key, '-', '_'), - $config['csrf_protection'][$key]); - } - } - } - - if (isset($config['ide'])) { - switch ($config['ide']) { - case 'textmate': - $pattern = 'txmt://open?url=file://%%f&line=%%l'; - break; - - case 'macvim': - $pattern = 'mvim://open?url=file://%%f&line=%%l'; - break; - - default: - // should be the link pattern then - $pattern = $config['ide']; - } - - $container->setParameter('debug.file_link_format', $pattern); - } + $loader->load('web.xml'); + $loader->load('form.xml'); + $loader->load('services.xml'); - foreach (array('document_root', 'document-root') as $key) { - if (isset($config[$key])) { - $container->setParameter('document_root', $config[$key]); - } + if ($container->getParameter('kernel.debug')) { + $loader->load('debug.xml'); + $container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher')); + $container->setAlias('debug.event_dispatcher', 'event_dispatcher'); } - if (!$container->hasDefinition('event_dispatcher')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('services.xml'); + $config = $this->mergeConfigs($configs); - if ($container->getParameter('kernel.debug')) { - $loader->load('debug.xml'); - $container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher')); - $container->setAlias('debug.event_dispatcher', 'event_dispatcher'); - } - } + $warmer = isset($config['cache_warmer']) ? $config['cache_warmer'] : !$container->getParameter('kernel.debug'); + $container->setParameter('kernel.cache_warmup', $warmer); if (isset($config['charset'])) { $container->setParameter('kernel.charset', $config['charset']); } - foreach (array('error_handler', 'error-handler') as $key) { - if (array_key_exists($key, $config)) { - if (false === $config[$key]) { - $container->getDefinition('error_handler')->setMethodCalls(array()); - } else { - $container->getDefinition('error_handler')->addMethodCall('register', array()); - $container->setParameter('error_handler.level', $config[$key]); - } - } + if (isset($config['document_root'])) { + $container->setParameter('document_root', $config['document_root']); } - if (isset($config['router'])) { - $this->registerRouterConfiguration($config, $container); - } - - if (isset($config['profiler'])) { - $this->registerProfilerConfiguration($config, $container); - } - - if (isset($config['validation']['enabled'])) { - $this->registerValidationConfiguration($config, $container); - } - - if (array_key_exists('templating', $config)) { - $this->registerTemplatingConfiguration($config, $container); - } - - if (array_key_exists('test', $config)) { - $this->registerTestConfiguration($config, $container); - } - - if (array_key_exists('session', $config)) { - $this->registerSessionConfiguration($config, $container); + if (isset($config['error_handler'])) { + if (false === $config['error_handler']) { + $container->getDefinition('error_handler')->setMethodCalls(array()); + } else { + $container->getDefinition('error_handler')->addMethodCall('register', array()); + $container->setParameter('error_handler.level', $config['error_handler']); + } } - // translator must always be registered (as support is included by default for forms for instance) - // if you disable it, an identity translator will be used and everything will still work as expected - $this->registerTranslatorConfiguration($config, $container); - - if (array_key_exists('esi', $config)) { - $this->registerEsiConfiguration($config, $container); + if (isset($config['ide'])) { + $patterns = array( + 'textmate' => 'txmt://open?url=file://%%f&line=%%l', + 'macvim' => 'mvim://open?url=file://%%f&line=%%l', + ); + $pattern = isset($patterns[$config['ide']]) ? $patterns[$config['ide']] : $config['ide']; + $container->setParameter('debug.file_link_format', $pattern); } - if (isset($config['cache-warmer'])) { - $config['cache_warmer'] = $config['cache-warmer']; + if ($config['test']) { + $loader->load('test.xml'); + $config['session']['storage_id'] = 'array'; } - $warmer = isset($config['cache_warmer']) ? $config['cache_warmer'] : !$container->getParameter('kernel.debug'); - $container->setParameter('kernel.cache_warmup', $warmer); + $this->registerCsrfProtectionConfiguration($config['csrf_protection'], $container); + $this->registerEsiConfiguration($config['esi'], $loader); + $this->registerProfilerConfiguration($config['profiler'], $container, $loader); + $this->registerRouterConfiguration($config['router'], $container, $loader); + $this->registerSessionConfiguration($config['session'], $container, $loader); + $this->registerTemplatingConfiguration($config['templating'], $container, $loader); + $this->registerTranslatorConfiguration($config['translator'], $container, $loader); + $this->registerValidationConfiguration($config['validation'], $container, $loader); $this->addClassesToCompile(array( 'Symfony\\Component\\HttpFoundation\\ParameterBag', @@ -178,237 +116,295 @@ protected function doConfigLoad(array $config, ContainerBuilder $container) 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface', 'Symfony\\Component\\EventDispatcher\\EventDispatcher', 'Symfony\\Bundle\\FrameworkBundle\\EventDispatcher', - - 'Symfony\\Component\\Form\\FormContext', - 'Symfony\\Component\\Form\\FormContextInterface', )); } /** - * Loads the templating configuration. + * Merges a set of configuration arrays and returns the result. * - * @param array $config An array of configuration settings - * @param ContainerBuilder $container A ContainerBuilder instance + * This method internally specifies the available options and their + * default values. Given an array of configuration arrays, this method + * intelligently merges those configuration values and returns the final, + * flattened product. + * + * @param array $configs An array of configuration arrays to merge + * @return array The merged configuration array */ - protected function registerTemplatingConfiguration(array $config, ContainerBuilder $container) + protected function mergeConfigs(array $configs) { - $config = isset($config['templating']) ? $config['templating'] : array(); + $defaultOptions = array( + 'cache_warmer' => null, + 'charset' => null, + 'csrf_protection' => array( + 'enabled' => null, + 'field_name' => null, + 'secret' => null, + ), + 'document_root' => null, + 'error_handler' => null, + // TODO: consolidate into a scalar unless future options are planned + 'esi' => array( + 'enabled' => null, + ), + 'ide' => null, + 'profiler' => array( + 'enabled' => true, + 'only_exceptions' => null, + 'matcher' => array( + 'ip' => null, + 'path' => null, + 'service' => null, + ), + ), + 'router' => array( + 'cache_warmer' => null, + 'resource' => null, + ), + 'session' => array( + 'auto_start' => null, + 'class' => null, + 'default_locale' => null, + 'storage_id' => 'native', + // NativeSessionStorage options + 'name' => null, + 'lifetime' => null, + 'path' => null, + 'domain' => null, + 'secure' => null, + 'httponly' => null, + // PdoSessionStorage options + 'pdo.db_table' => null, + 'pdo.db_id_col' => null, + 'pdo.db_data_col' => null, + 'pdo.db_time_col' => null, + ), + 'templating' => array( + 'assets_version' => null, + 'assets_base_urls' => null, + 'cache' => null, + 'cache_warmer' => null, + 'engines' => array(), + 'loaders' => array(), + ), + 'test' => null, + 'translator' => array( + 'enabled' => null, + 'fallback' => null, + ), + 'validation' => array( + 'enabled' => null, + 'annotations' => null, + 'namespaces' => array(), + ), + ); + + $mergedConfig = $defaultOptions; - if (!$container->hasDefinition('templating.locator')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('templating.xml'); - $loader->load('templating_php.xml'); + foreach ($configs as $config) { + $config = $this->normalizeKeys($config); - if ($container->getParameter('kernel.debug')) { - $loader->load('templating_debug.xml'); + if (isset($config['templating']) && is_array($config['templating'])) { + $config['templating']['engines'] = $this->normalizeConfig($config['templating'], 'engine'); + $config['templating']['loaders'] = $this->normalizeConfig($config['templating'], 'loader'); + unset($config['templating']['engine'], $config['templating']['loader']); } - } - - if (array_key_exists('assets-version', $config)) { - $container->setParameter('templating.assets.version', $config['assets-version']); - } - - if (array_key_exists('assets_version', $config)) { - $container->setParameter('templating.assets.version', $config['assets_version']); - } - if (array_key_exists('assets-base-urls', $config)) { - $container->setParameter('templating.assets.base_urls', $config['assets-base-urls']); - } - - if (array_key_exists('assets_base_urls', $config)) { - $container->setParameter('templating.assets.base_urls', $config['assets_base_urls']); - } - - // loaders - if (isset($config['loader'])) { - $loaders = array(); - $ids = is_array($config['loader']) ? $config['loader'] : array($config['loader']); - foreach ($ids as $id) { - $loaders[] = new Reference($id); + if (isset($config['validation']) && is_array($config['validation'])) { + $config['validation']['namespaces'] = $this->normalizeConfig($config['validation'], 'namespace'); + unset($config['validation']['namespace']); } - if (1 === count($loaders)) { - $container->setAlias('templating.loader', (string) $loaders[0]); - } else { - $container->getDefinition('templating.loader.chain')->addArgument($loaders); - $container->setAlias('templating.loader', 'templating.loader.chain'); - } - } - - // cache? - $container->setParameter('templating.loader.cache.path', null); - if (isset($config['cache'])) { - // wrap the loader with some cache - $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader')); - $container->setDefinition('templating.loader', $container->getDefinition('templating.loader.cache')); - $container->setParameter('templating.loader.cache.path', $config['cache']); - } - - if (isset($config['cache-warmer'])) { - $config['cache_warmer'] = $config['cache-warmer']; + $mergedConfig = $this->mergeOptions($mergedConfig, $config, $defaultOptions); } - if (isset($config['cache_warmer']) && $config['cache_warmer']) { - $container->getDefinition('templating.cache_warmer.template_paths')->addTag('kernel.cache_warmer'); - $container->setAlias('templating.locator', 'templating.locator.cached'); - } + return $mergedConfig; + } - // engines - if (!$engines = $this->normalizeConfig($config, 'engine')) { - throw new \LogicException('You must register at least one templating engine.'); + /** + * Merges a single level of configuration options. + * + * @param array $current The value of the options before merging + * @param array $new The new values to be merged + * @param array $default The corresponding default values for the option level + * @param string $basePath Base property path for the option level + * @return array The merged options + * @throws InvalidArgumentException When an unsupported is found + */ + protected function mergeOptions(array $current, array $new, array $default, $basePath = null) + { + if ($unsupportedOptions = array_diff_key($new, $default)) { + throw new \InvalidArgumentException('The following options are not supported: '.implode(', ', array_keys($unsupportedOptions))); } - $this->addClassesToCompile(array( - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface', - 'Symfony\\Component\\Templating\\EngineInterface', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\TemplateLocatorInterface', - $container->findDefinition('templating.locator')->getClass(), - )); - - foreach ($engines as $i => $engine) { - $id = is_array($engine) ? $engine['id'] : $engine; - $engines[$i] = new Reference('templating.engine.'.$id); - - if ('php' === $id) { - $this->addClassesToCompile(array( - 'Symfony\\Component\\Templating\\PhpEngine', - 'Symfony\\Component\\Templating\\TemplateNameParserInterface', - 'Symfony\\Component\\Templating\\TemplateNameParser', - 'Symfony\\Component\\Templating\\Loader\\LoaderInterface', - 'Symfony\\Component\\Templating\\Storage\\Storage', - 'Symfony\\Component\\Templating\\Storage\\FileStorage', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', - )); + foreach ($default as $key => $defaultValue) { + if (array_key_exists($key, $new)) { + $optionPath = $basePath ? $basePath.'.'.$key : $key; + $current[$key] = $this->mergeOptionValue($current[$key], $new[$key], $defaultValue, $optionPath); } } - if (1 === count($engines)) { - $container->setAlias('templating', (string) $engines[0]); - } else { - $def = $container->getDefinition('templating.engine.delegating'); - $def->setArgument(1, $engines); + return $current; + } - $container->setAlias('templating', 'templating.engine.delegating'); + /** + * Merges an option value. + * + * @param mixed $current The value of the option before merging + * @param mixed $new The new value to be merged + * @param mixed $default The corresponding default value for the option + * @param string $optionPath Property path for the option + * @return mixed The merged value + * @throws InvalidArgumentException When an invalid option is found + */ + protected function mergeOptionValue($current, $new, $defaultValue, $optionPath) + { + // Allow profiler.matcher array to be overridden with any value. This + // option requires no merge logic and would not benefit from the type + // validation below. + if ('profiler.matcher' === $optionPath) { + return $new; + } + + // Ensure that the new value's type is an array if expected + if (is_array($defaultValue) && !is_array($new)) { + throw new \InvalidArgumentException(sprintf('Expected array type for option "%s", %s given', $optionPath, gettype($new))); + } + + switch ($optionPath) { + // Engine options are arrays of strings, although XML configurations + // store the engine ID in an attribute. Dedupe after merging. + case 'templating.engines': + $new = array_map(function($engine) { return is_array($engine) ? $engine['id'] : $engine; }, $new); + return array_unique(array_merge($current, $new)); + + // Loader options are arrays of strings, so dedupe after merging + case 'templating.loaders': + return array_unique(array_merge($current, $new)); + + // The namespace options' keys are used for the annotation prefix + // and are significant, so do not dedupe array values. Be mindful + // of XML configurations, which store the prefix in an attribute. + case 'validation.namespaces': + foreach ($new as $prefix => $namespace) { + if (is_array($namespace)) { + $new[$namespace['prefix']] = $namespace['namespace']; + unset($new[$prefix]); + } + } + return array_merge($current, $new); } + + return is_array($defaultValue) ? $this->mergeOptions($current, $new, $defaultValue, $optionPath) : $new; } /** - * Loads the test configuration. + * Loads the CSRF protection configuration. * - * @param array $config A configuration array + * @param array $config A CSRF protection configuration array * @param ContainerBuilder $container A ContainerBuilder instance */ - protected function registerTestConfiguration(array $config, ContainerBuilder $container) + protected function registerCsrfProtectionConfiguration(array $config, ContainerBuilder $container) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('test.xml'); - - $container->setAlias('session.storage', 'session.storage.array'); + foreach (array('enabled', 'field_name', 'secret') as $key) { + if (isset($config[$key])) { + $container->setParameter('form.csrf_protection.'.$key, $config[$key]); + } + } } /** * Loads the ESI configuration. * - * @param array $config A configuration array - * @param ContainerBuilder $container A ContainerBuilder instance + * @param array $config An ESI configuration array + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerEsiConfiguration(array $config, ContainerBuilder $container) + protected function registerEsiConfiguration(array $config, XmlFileLoader $loader) { - if (isset($config['esi']['enabled']) && $config['esi']['enabled']) { - if (!$container->hasDefinition('esi')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('esi.xml'); - } + if ($config['enabled']) { + $loader->load('esi.xml'); } } /** - * Loads the translator configuration. + * Loads the profiler configuration. * - * @param array $config A configuration array + * @param array $config A profiler configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerTranslatorConfiguration(array $config, ContainerBuilder $container) + protected function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - $first = false; - if (!$container->hasDefinition('translator')) { - $first = true; - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('translation.xml'); - } + if ($config['enabled']) { + $loader->load('profiling.xml'); + $loader->load('collectors.xml'); - $config = array_key_exists('translator', $config) ? $config['translator'] : array(); - if (!is_array($config)) { - $config = array(); - } + if (isset($config['only_exceptions'])) { + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); + } - if (!isset($config['translator']['enabled']) || $config['translator']['enabled']) { - // use the "real" translator - $container->setDefinition('translator', $container->findDefinition('translator.real')); + if ($config['matcher']) { + if (isset($config['matcher']['service'])) { + $container->setAlias('profiler.request_matcher', $config['matcher']['service']); + } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) { + $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); + $definition->setPublic(false); - if ($first) { - // translation directories - $dirs = array(); - foreach ($container->getParameter('kernel.bundles') as $bundle) { - $reflection = new \ReflectionClass($bundle); - if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) { - $dirs[] = $dir; + if (isset($config['matcher']['ip'])) { + $definition->addMethodCall('matchIp', array($config['matcher']['ip'])); } - } - if (is_dir($dir = $container->getParameter('kernel.root_dir').'/translations')) { - $dirs[] = $dir; - } - // translation resources - $resources = array(); - if ($dirs) { - $finder = new Finder(); - $finder->files()->filter(function (\SplFileInfo $file) { return 2 === substr_count($file->getBasename(), '.'); })->in($dirs); - foreach ($finder as $file) { - // filename is domain.locale.format - list($domain, $locale, $format) = explode('.', $file->getBasename()); - - $resources[] = array($format, (string) $file, $locale, $domain); + if (isset($config['matcher']['path'])) { + $definition->addMethodCall('matchPath', array($config['matcher']['path'])); } } - $container->setParameter('translation.resources', $resources); } } - - if (array_key_exists('fallback', $config)) { - $container->setParameter('translator.fallback_locale', $config['fallback']); - } } /** - * Loads the session configuration. + * Loads the router configuration. * - * @param array $config A configuration array + * @param array $config A router configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerSessionConfiguration(array $config, ContainerBuilder $container) + protected function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (!$container->hasDefinition('session')) { - $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); - $loader->load('session.xml'); + $loader->load('routing.xml'); + + if (!isset($config['resource'])) { + throw new \InvalidArgumentException('Router configuration requires a resource option.'); } - $config = isset($config['session']) ? $config['session'] : array(); + $container->setParameter('routing.resource', $config['resource']); - foreach (array('default_locale', 'default-locale') as $key) { - if (isset($config[$key])) { - $container->setParameter('session.default_locale', $config[$key]); - } + if ($config['cache_warmer']) { + $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); + $container->setAlias('router', 'router.cached'); } - if (isset($config['auto-start'])) { - $config['auto_start'] = $config['auto-start']; - } + $this->addClassesToCompile(array( + 'Symfony\\Component\\Routing\\RouterInterface', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface', + 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + $container->findDefinition('router')->getClass() + )); + } + + /** + * Loads the session configuration. + * + * @param array $config A session configuration array + * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance + */ + protected function registerSessionConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + { + $loader->load('session.xml'); - if (isset($config['auto_start']) && $config['auto_start']) { + if ($config['auto_start']) { $container->getDefinition('session')->addMethodCall('start'); } @@ -416,29 +412,19 @@ protected function registerSessionConfiguration(array $config, ContainerBuilder $container->setParameter('session.class', $config['class']); } - if (isset($config['storage-id'])) { - $config['storage_id'] = $config['storage-id']; - } - - if (isset($config['storage_id'])) { - $container->setAlias('session.storage', 'session.storage.'.$config['storage_id']); - } else { - $config['storage_id'] = 'native'; + if (isset($config['default_locale'])) { + $container->setParameter('session.default_locale', $config['default_locale']); } - $options = $container->getParameter('session.storage.'.strtolower($config['storage_id']).'.options'); - foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly', 'cache_limiter', 'pdo.db_table', 'pdo.db_id_col', 'pdo.db_data_col', 'pdo.db_time_col') as $name) { - $key = str_replace('pdo.', '', $name); - if (isset($config[$name])) { - $options[$key] = $config[$name]; - } + $container->setAlias('session.storage', 'session.storage.'.$config['storage_id']); - $nName = str_replace('_', '-', $name); - if (isset($config[$nName])) { - $options[$key] = $config[$nName]; + $options = $container->getParameter('session.storage.'.$config['storage_id'].'.options'); + foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly', 'pdo.db_table', 'pdo.db_id_col', 'pdo.db_data_col', 'pdo.db_time_col') as $key) { + if (isset($config[$key])) { + $options[str_replace('pdo.', '', $key)] = $config[$key]; } } - $container->setParameter('session.storage.'.strtolower($config['storage_id']).'.options', $options); + $container->setParameter('session.storage.'.$config['storage_id'].'.options', $options); $this->addClassesToCompile(array( 'Symfony\\Component\\HttpFoundation\\Session', @@ -448,111 +434,160 @@ protected function registerSessionConfiguration(array $config, ContainerBuilder } /** - * Loads the router configuration. + * Loads the templating configuration. * - * @param array $config A configuration array + * @param array $config A templating configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerRouterConfiguration(array $config, ContainerBuilder $container) + protected function registerTemplatingConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (!$container->hasDefinition('router')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('routing.xml'); + $loader->load('templating.xml'); + $loader->load('templating_php.xml'); + + if ($container->getParameter('kernel.debug')) { + $loader->load('templating_debug.xml'); } - $container->setParameter('routing.resource', $config['router']['resource']); + if (isset($config['assets_version'])) { + $container->setParameter('templating.assets.version', $config['assets_version']); + } - if (isset($config['router']['cache-warmer'])) { - $config['router']['cache_warmer'] = $config['router']['cache-warmer']; + if (isset($config['assets_base_urls'])) { + $container->setParameter('templating.assets.base_urls', $config['assets_base_urls']); } - if (isset($config['router']['cache_warmer']) && $config['router']['cache_warmer']) { - $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); - $container->setAlias('router', 'router.cached'); + if ($config['loaders']) { + $loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']); + + // Use a deligation unless only a single loader was registered + if (1 === count($loaders)) { + $container->setAlias('templating.loader', (string) reset($loaders)); + } else { + $container->getDefinition('templating.loader.chain')->addArgument($loaders); + $container->setAlias('templating.loader', 'templating.loader.chain'); + } + } + + if ($config['cache']) { + // Wrap the existing loader with cache (must happen after loaders are registered) + $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader')); + $container->setDefinition('templating.loader', $container->getDefinition('templating.loader.cache')); + $container->setParameter('templating.loader.cache.path', $config['cache']); + } else { + $container->setParameter('templating.loader.cache.path', null); + } + + if ($config['cache_warmer']) { + $container->getDefinition('templating.cache_warmer.template_paths')->addTag('kernel.cache_warmer'); + $container->setAlias('templating.locator', 'templating.locator.cached'); + } + + if (!$config['engines']) { + throw new \LogicException('You must register at least one templating engine.'); } $this->addClassesToCompile(array( - 'Symfony\\Component\\Routing\\RouterInterface', - 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface', - 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface', - 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - $container->findDefinition('router')->getClass() + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface', + 'Symfony\\Component\\Templating\\EngineInterface', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\TemplateLocatorInterface', + $container->findDefinition('templating.locator')->getClass(), )); + + if (in_array('php', $config['engines'], true)) { + $this->addClassesToCompile(array( + 'Symfony\\Component\\Templating\\PhpEngine', + 'Symfony\\Component\\Templating\\TemplateNameParserInterface', + 'Symfony\\Component\\Templating\\TemplateNameParser', + 'Symfony\\Component\\Templating\\Loader\\LoaderInterface', + 'Symfony\\Component\\Templating\\Storage\\Storage', + 'Symfony\\Component\\Templating\\Storage\\FileStorage', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser', + 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', + )); + } + + $engines = array_map(function($engine) { return new Reference('templating.engine.'.$engine); }, $config['engines']); + + // Use a deligation unless only a single engine was registered + if (1 === count($engines)) { + $container->setAlias('templating', (string) reset($engines)); + } else { + $container->getDefinition('templating.engine.delegating')->setArgument(1, $engines); + $container->setAlias('templating', 'templating.engine.delegating'); + } + + } /** - * Loads the profiler configuration. + * Loads the translator configuration. * - * - * - * - * - * - * - * + * A translator must always be registered (as support is included by default + * in the forms component). If disabled, an identity translator will be + * used and everything will still work as expected. * - * @param array $config A configuration array + * @param array $config A translator configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerProfilerConfiguration(array $config, ContainerBuilder $container) + protected function registerTranslatorConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if ($config['profiler']) { - if (!$container->hasDefinition('profiler')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('profiling.xml'); - $loader->load('collectors.xml'); - } + $loader->load('translation.xml'); - if (isset($config['profiler']['only-exceptions'])) { - $container->setParameter('profiler_listener.only_exceptions', $config['profiler']['only-exceptions']); - } elseif (isset($config['profiler']['only_exceptions'])) { - $container->setParameter('profiler_listener.only_exceptions', $config['profiler']['only_exceptions']); - } + if ($config['enabled']) { + // Use the "real" translator instead of the identity default + $container->setDefinition('translator', $container->findDefinition('translator.real')); - if (isset($config['profiler']['matcher'])) { - if (isset($config['profiler']['matcher']['service'])) { - $container->setAlias('profiler.request_matcher', $config['profiler']['matcher']['service']); - } elseif (isset($config['profiler']['matcher']['_services'])) { - $container->setAlias('profiler.request_matcher', (string) $config['profiler']['matcher']['_services'][0]); - } else { - $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); - $definition->setPublic(false); + // Discover translation directories + $dirs = array(); + foreach ($container->getParameter('kernel.bundles') as $bundle) { + $reflection = new \ReflectionClass($bundle); + if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) { + $dirs[] = $dir; + } + } + if (is_dir($dir = $container->getParameter('kernel.root_dir').'/translations')) { + $dirs[] = $dir; + } - if (isset($config['profiler']['matcher']['ip'])) { - $definition->addMethodCall('matchIp', array($config['profiler']['matcher']['ip'])); - } + // Register translation resources + $resources = array(); + if ($dirs) { + $finder = new Finder(); + $finder->files()->filter(function (\SplFileInfo $file) { return 2 === substr_count($file->getBasename(), '.'); })->in($dirs); + foreach ($finder as $file) { + // filename is domain.locale.format + list($domain, $locale, $format) = explode('.', $file->getBasename()); - if (isset($config['profiler']['matcher']['path'])) { - $definition->addMethodCall('matchPath', array($config['profiler']['matcher']['path'])); - } + $resources[] = array($format, (string) $file, $locale, $domain); } - } else { - $container->removeAlias('profiler.request_matcher'); } - } elseif ($container->hasDefinition('profiler')) { - $container->getDefinition('profiling')->clearTags(); + $container->setParameter('translation.resources', $resources); + } + + if (isset($config['fallback'])) { + $container->setParameter('translator.fallback_locale', $config['fallback']); } } /** * Loads the validator configuration. * - * @param array $config A configuration array + * @param array $config A validation configuration array * @param ContainerBuilder $container A ContainerBuilder instance + * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerValidationConfiguration(array $config, ContainerBuilder $container) + protected function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if ($config['validation']['enabled']) { - if (!$container->hasDefinition('validator')) { - $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); - $loader->load('validator.xml'); - } + if ($config['enabled']) { + $loader->load('validator.xml'); $xmlMappingFiles = array(); $yamlMappingFiles = array(); - // default entries by the framework + // Include default entries from the framework $xmlMappingFiles[] = __DIR__.'/../../../Component/Form/Resources/config/validation.xml'; foreach ($container->getParameter('kernel.bundles') as $bundle) { @@ -565,16 +600,10 @@ protected function registerValidationConfiguration(array $config, ContainerBuild } } - $xmlFilesLoader = new Definition( - $container->getParameter('validator.mapping.loader.xml_files_loader.class'), - array($xmlMappingFiles) - ); + $xmlFilesLoader = new Definition('%validator.mapping.loader.xml_files_loader.class%', array($xmlMappingFiles)); $xmlFilesLoader->setPublic(false); - $yamlFilesLoader = new Definition( - $container->getParameter('validator.mapping.loader.yaml_files_loader.class'), - array($yamlMappingFiles) - ); + $yamlFilesLoader = new Definition('%validator.mapping.loader.yaml_files_loader.class%', array($yamlMappingFiles)); $yamlFilesLoader->setPublic(false); $container->setDefinition('validator.mapping.loader.xml_files_loader', $xmlFilesLoader); @@ -588,27 +617,27 @@ protected function registerValidationConfiguration(array $config, ContainerBuild $container->addResource(new FileResource($file)); } - if (isset($config['validation']['annotations'])) { - if (isset($config['validation']['annotations']['namespaces']) && is_array($config['validation']['annotations']['namespaces'])) { + if ($config['annotations']) { + // Register prefixes for constraint namespaces + if ($namespaces = $config['namespaces']) { $container->setParameter('validator.annotations.namespaces', array_merge( $container->getParameter('validator.annotations.namespaces'), - $config['validation']['annotations']['namespaces'] + $namespaces )); } - $annotationLoader = new Definition($container->getParameter('validator.mapping.loader.annotation_loader.class')); + // Register annotation loader + $annotationLoader = new Definition('%validator.mapping.loader.annotation_loader.class%'); $annotationLoader->setPublic(false); $annotationLoader->addArgument(new Parameter('validator.annotations.namespaces')); $container->setDefinition('validator.mapping.loader.annotation_loader', $annotationLoader); - $loader = $container->getDefinition('validator.mapping.loader.loader_chain'); - $arguments = $loader->getArguments(); + $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain'); + $arguments = $loaderChain->getArguments(); array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); - $loader->setArguments($arguments); + $loaderChain->setArguments($arguments); } - } elseif ($container->hasDefinition('validator')) { - $container->getDefinition('validator')->clearTags(); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index a70f035342b6d..a32d10a06650b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -9,34 +9,50 @@ - - + + + - - + - + - + - + + + + + + + + + + + + + + + + + @@ -51,11 +67,6 @@ - - - - - @@ -64,11 +75,14 @@ - - + + + + + @@ -83,21 +97,26 @@ - - + + - + - - + - + + + + + + - - + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php new file mode 100644 index 0000000000000..20a272c4c78a2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -0,0 +1,45 @@ +loadFromExtension('app', 'config', array( + 'csrf_protection' => array( + 'enabled' => true, + 'field_name' => '_csrf', + 'secret' => 's3cr3t', + ), + 'esi' => array( + 'enabled' => true, + ), + 'profiler' => array( + 'only_exceptions' => true, + ), + 'router' => array( + 'resource' => '%kernel.root_dir%/config/routing.xml', + 'cache_warmer' => true, + ), + 'session' => array( + 'auto_start' => true, + 'class' => 'Session', + 'default_locale' => 'fr', + 'storage_id' => 'native', + 'name' => '_SYMFONY', + 'lifetime' => 86400, + 'path' => '/', + 'domain' => 'example.com', + 'secure' => true, + 'httponly' => true, + ), + 'templating' => array( + 'assets_version' => 'SomeVersionScheme', + 'assets_base_urls' => 'http://cdn.example.com', + 'cache_warmer' => true, + 'engines' => array('php', 'twig'), + 'loader' => array('loader.foo', 'loader.bar'), + ), + 'translator' => array( + 'enabled' => true, + 'fallback' => 'fr', + ), + 'validation' => array( + 'enabled' => true, + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php new file mode 100644 index 0000000000000..edeca35e98dac --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php @@ -0,0 +1,17 @@ +loadFromExtension('app', 'config', array( + 'router' => array( + 'resource' => '%kernel.root_dir%/config/routing.xml', + ), + 'session' => array( + 'storage_id' => 'pdo', + 'pdo.db_table' => 'table', + 'pdo.db_id_col' => 'id', + 'pdo.db_data_col' => 'data', + 'pdo.db_time_col' => 'time', + ), + 'templating' => array( + 'engine' => 'php' + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php new file mode 100644 index 0000000000000..90de412f3220b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php @@ -0,0 +1,17 @@ +loadFromExtension('app', 'config', array( + 'router' => array( + 'resource' => '%kernel.root_dir%/config/routing.xml', + ), + 'validation' => array( + 'enabled' => true, + 'annotations' => true, + 'namespaces' => array( + 'app' => 'Application\\Validator\\Constraints\\', + ), + ), + 'templating' => array( + 'engine' => 'php' + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml new file mode 100644 index 0000000000000..ebc2f39fe6368 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + loader.foo + loader.bar + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml new file mode 100644 index 0000000000000..eeb918e8ccafe --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml new file mode 100644 index 0000000000000..13da91605a60c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml new file mode 100644 index 0000000000000..1aecd8ce48d67 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -0,0 +1,34 @@ +app.config: + csrf_protection: + enabled: true + field_name: _csrf + secret: s3cr3t + esi: + enabled: true + profiler: + only_exceptions: true + router: + resource: %kernel.root_dir%/config/routing.xml + cache_warmer: true + session: + auto_start: true + class: Session + default_locale: fr + storage_id: native + name: _SYMFONY + lifetime: 86400 + path: / + domain: example.com + secure: true + httponly: true + templating: + assets_version: SomeVersionScheme + assets_base_urls: http://cdn.example.com + cache_warmer: true + engines: [php, twig] + loader: [loader.foo, loader.bar] + translator: + enabled: true, + fallback: fr + validation: + enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml new file mode 100644 index 0000000000000..ae8f6ad961305 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml @@ -0,0 +1,11 @@ +app.config: + router: + resource: %kernel.root_dir%/config/routing.xml + session: + storage_id: pdo + pdo.db_table: table + pdo.db_id_col: id + pdo.db_data_col: data + pdo.db_time_col: time + templating: + engine: php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml new file mode 100644 index 0000000000000..3b029f6b6415b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml @@ -0,0 +1,10 @@ +app.config: + router: + resource: %kernel.root_dir%/config/routing.xml + validation: + enabled: true + annotations: true + namespaces: + app: Application\Validator\Constraints\ + templating: + engine: php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 0e83eb1ff8169..99da686fa3969 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -16,43 +16,182 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; -class FrameworkExtensionTest extends TestCase +abstract class FrameworkExtensionTest extends TestCase { - public function testConfigLoad() + abstract protected function loadFromFile(ContainerBuilder $container, $file); + + public function testCsrfProtection() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->getParameter('form.csrf_protection.enabled')); + $this->assertEquals('_csrf', $container->getParameter('form.csrf_protection.field_name')); + $this->assertEquals('s3cr3t', $container->getParameter('form.csrf_protection.secret')); + } + + public function testEsi() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('esi'), '->registerEsiConfiguration() loads esi.xml'); + } + + public function testProfiler() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('profiler'), '->registerProfilerConfiguration() loads profiling.xml'); + $this->assertTrue($container->hasDefinition('data_collector.config'), '->registerProfilerConfiguration() loads collectors.xml'); + $this->assertTrue($container->getParameter('profiler_listener.only_exceptions')); + } + + public function testRouter() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('router.real'), '->registerRouterConfiguration() loads routing.xml'); + $this->assertEquals($container->getParameter('kernel.root_dir').'/config/routing.xml', $container->getParameter('routing.resource'), '->registerRouterConfiguration() sets routing resource'); + $this->assertTrue($container->getDefinition('router.cache_warmer')->hasTag('kernel.cache_warmer'), '->registerRouterConfiguration() tags router cache warmer if cache warming is set'); + $this->assertEquals('router.cached', (string) $container->getAlias('router'), '->registerRouterConfiguration() changes router alias to cached if cache warming is set'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRouterRequiresResourceOption() { - $container = $this->getContainer(); + $container = $this->createContainer(); $loader = new FrameworkExtension(); + $loader->configLoad(array(array('router' => true)), $container); + } + + public function testSession() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('session'), '->registerSessionConfiguration() loads session.xml'); + $this->assertEquals('fr', $container->getParameter('session.default_locale')); + $this->assertTrue($container->getDefinition('session')->hasMethodCall('start')); + $this->assertEquals('Session', $container->getParameter('session.class')); + $this->assertEquals('session.storage.native', (string) $container->getAlias('session.storage')); + + $options = $container->getParameter('session.storage.native.options'); + $this->assertEquals('_SYMFONY', $options['name']); + $this->assertEquals(86400, $options['lifetime']); + $this->assertEquals('/', $options['path']); + $this->assertEquals('example.com', $options['domain']); + $this->assertTrue($options['secure']); + $this->assertTrue($options['httponly']); + } + + public function testSessionPdo() + { + $container = $this->createContainerFromFile('session_pdo'); + $options = $container->getParameter('session.storage.pdo.options'); + + $this->assertEquals('session.storage.pdo', (string) $container->getAlias('session.storage')); + $this->assertEquals('table', $options['db_table']); + $this->assertEquals('id', $options['db_id_col']); + $this->assertEquals('data', $options['db_data_col']); + $this->assertEquals('time', $options['db_time_col']); + } + + public function testTemplating() + { + $container = $this->createContainerFromFile('full'); - $loader->configLoad(array(array()), $container); - $this->assertEquals('Symfony\\Bundle\\FrameworkBundle\\RequestListener', $container->getParameter('request_listener.class'), '->webLoad() loads the web.xml file if not already loaded'); + $this->assertTrue($container->hasDefinition('templating.name_parser'), '->registerTemplatingConfiguration() loads templating.xml'); + $this->assertEquals('SomeVersionScheme', $container->getParameter('templating.assets.version')); + $this->assertEquals('http://cdn.example.com', $container->getParameter('templating.assets.base_urls')); - $container = $this->getContainer(); + $this->assertTrue($container->getDefinition('templating.cache_warmer.template_paths')->hasTag('kernel.cache_warmer'), '->registerTemplatingConfiguration() tags templating cache warmer if cache warming is set'); + $this->assertEquals('templating.locator.cached', (string) $container->getAlias('templating.locator'), '->registerTemplatingConfiguration() changes templating.locator alias to cached if cache warming is set'); + + $this->assertEquals('templating.engine.delegating', (string) $container->getAlias('templating'), '->registerTemplatingConfiguration() configures delegating loader if multiple engines are provided'); + + $this->assertEquals('templating.loader.chain', (string) $container->getAlias('templating.loader'), '->registerTemplatingConfiguration() configures loader chain if multiple loaders are provided'); + } + + public function testTranslator() + { + $container = $this->createContainerFromFile('full'); + + $this->assertTrue($container->hasDefinition('translator.real'), '->registerTranslatorConfiguration() loads translation.xml'); + $this->assertSame($container->getDefinition('translator.real'), $container->getDefinition('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator'); + + $this->assertContains( + realpath(__DIR__.'/../../Resources/translations/validators.fr.xliff'), + array_map(function($resource) { return $resource[1]; }, $container->getParameter('translation.resources')), + '->registerTranslatorConfiguration() finds FrameworkExtension translation resources' + ); + + $this->assertEquals('fr', $container->getParameter('translator.fallback_locale')); + } + + /** + * @expectedException LogicException + */ + public function testTemplatingRequiresAtLeastOneEngine() + { + $container = $this->createContainer(); $loader = new FrameworkExtension(); + $loader->configLoad(array(array('templating' => null)), $container); + } + + public function testValidation() + { + $container = $this->createContainerFromFile('full'); - // profiler - $loader->configLoad(array(array('profiler' => true)), $container); - $this->assertEquals('Symfony\Component\HttpKernel\Profiler\Profiler', $container->getParameter('profiler.class'), '->configLoad() loads the collectors.xml file if not already loaded'); + $this->assertTrue($container->hasDefinition('validator'), '->registerValidationConfiguration() loads validator.xml'); + $this->assertTrue($container->hasDefinition('validator.mapping.loader.xml_files_loader'), '->registerValidationConfiguration() defines the XML loader'); + $this->assertTrue($container->hasDefinition('validator.mapping.loader.yaml_files_loader'), '->registerValidationConfiguration() defines the YAML loader'); - // templating - $loader->configLoad(array(array('templating' => array('engines' => array('php')))), $container); - $this->assertEquals('Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', $container->getParameter('templating.engine.php.class'), '->templatingLoad() loads the templating.xml file if not already loaded'); + $xmlLoaderArgs = $container->getDefinition('validator.mapping.loader.xml_files_loader')->getArguments(); + $xmlFiles = $xmlLoaderArgs[0]; - // validation - $loader->configLoad(array(array('validation' => array('enabled' => true))), $container); - $this->assertEquals('Symfony\Component\Validator\Validator', $container->getParameter('validator.class'), '->validationLoad() loads the validation.xml file if not already loaded'); - $this->assertFalse($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->validationLoad() doesn\'t load the annotations service unless its needed'); + $this->assertContains( + realpath(__DIR__.'/../../../../Component/Form/Resources/config/validation.xml'), + array_map('realpath', $xmlFiles), + '->registerValidationConfiguration() adds Form validation.xml to XML loader' + ); - $loader->configLoad(array(array('validation' => array('enabled' => true, 'annotations' => true))), $container); - $this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->validationLoad() loads the annotations service'); + $this->assertFalse($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() does not define the annotation loader unless needed'); } - protected function getContainer() + public function testValidationAnnotations() + { + $container = $this->createContainerFromFile('validation_annotations'); + + $this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() defines the annotation loader'); + + $namespaces = $container->getParameter('validator.annotations.namespaces'); + $this->assertEquals('Symfony\\Component\\Validator\\Constraints\\', $namespaces['validation'], '->registerValidationConfiguration() loads the default "validation" namespace'); + $this->assertEquals('Application\\Validator\\Constraints\\', $namespaces['app'], '->registerValidationConfiguration() loads custom validation namespaces'); + } + + protected function createContainer() { return new ContainerBuilder(new ParameterBag(array( 'kernel.bundles' => array('FrameworkBundle' => 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle'), - 'kernel.root_dir' => __DIR__, - 'kernel.debug' => false, + 'kernel.cache_dir' => __DIR__, 'kernel.compiled_classes' => array(), + 'kernel.debug' => false, + 'kernel.environment' => 'test', + 'kernel.name' => 'kernel', + 'kernel.root_dir' => __DIR__, ))); } + + protected function createContainerFromFile($file) + { + $container = $this->createContainer(); + $container->registerExtension(new FrameworkExtension()); + $this->loadFromFile($container, $file); + + $container->getCompilerPassConfig()->setOptimizationPasses(array()); + $container->getCompilerPassConfig()->setRemovingPasses(array()); + $container->compile(); + + return $container; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php new file mode 100644 index 0000000000000..451d16364a329 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; + +class PhpFrameworkExtensionTest extends FrameworkExtensionTest +{ + protected function loadFromFile(ContainerBuilder $container, $file) + { + $loader = new PhpFileLoader($container, __DIR__.'/Fixtures/php'); + $loader->load($file.'.php'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php new file mode 100644 index 0000000000000..ea6fbe08a7335 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; + +class XmlFrameworkExtensionTest extends FrameworkExtensionTest +{ + protected function loadFromFile(ContainerBuilder $container, $file) + { + $loader = new XmlFileLoader($container, __DIR__.'/Fixtures/xml'); + $loader->load($file.'.xml'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php new file mode 100644 index 0000000000000..c1af39109e980 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/YamlFrameworkExtensionTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +class YamlFrameworkExtensionTest extends FrameworkExtensionTest +{ + protected function loadFromFile(ContainerBuilder $container, $file) + { + $loader = new YamlFileLoader($container, __DIR__.'/Fixtures/yml'); + $loader->load($file.'.yml'); + } +} From 575b75a9dfc1131411cf26ab2c7250d2e3fd72a9 Mon Sep 17 00:00:00 2001 From: ornicar Date: Sat, 5 Feb 2011 10:32:54 -0800 Subject: [PATCH 03/13] [DependencyInjection] Fix fixture class loaded twice during dumper tests --- .../Fixtures/containers/interfaces1.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php index c3efeb9f47207..27503a351c674 100755 --- a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/interfaces1.php @@ -12,12 +12,14 @@ return $container; -class FooClass -{ - public $bar; - - public function setBar($bar) +if (!class_exists('FooClass')) { + class FooClass { - $this->bar = $bar; + public $bar; + + public function setBar($bar) + { + $this->bar = $bar; + } } -} \ No newline at end of file +} From 743f25a287ebcd0cc6b58e984e19544108b6ba28 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 2 Feb 2011 12:22:27 -0500 Subject: [PATCH 04/13] [DependencyInjection] Create explicit factoryClass property for Definitions Previously, the Definition class was used both for type inference and factory construction (if factoryService was absent). This is fine for cases where classes create instances of themselves (e.g. getInstance() or create()), but leads to ambiguity when we have a separate factory class. --- .../DependencyInjection/DoctrineExtension.php | 1 + .../AbstractDoctrineExtensionTest.php | 6 ++ .../DoctrineMongoDBExtension.php | 1 + .../AbstractMongoDBExtensionTest.php | 6 ++ .../Compiler/CheckDefinitionValidityPass.php | 2 +- .../ResolveDefinitionTemplatesPass.php | 6 +- .../ResolveInterfaceInjectorsPass.php | 2 +- .../DependencyInjection/ContainerBuilder.php | 6 +- .../DependencyInjection/Definition.php | 34 +++++++++-- .../DefinitionDecorator.php | 13 +++- .../DependencyInjection/Dumper/PhpDumper.php | 22 ++++--- .../Loader/XmlFileLoader.php | 2 +- .../Loader/YamlFileLoader.php | 4 ++ .../schema/dic/services/services-1.0.xsd | 1 + .../CheckDefinitionValidityPassTest.php | 61 +++++++++++++++++++ .../ContainerBuilderTest.php | 2 +- .../DefinitionDecoratorTest.php | 3 +- .../DependencyInjection/DefinitionTest.php | 19 +++--- .../Fixtures/containers/container9.php | 2 + 19 files changed, 164 insertions(+), 29 deletions(-) create mode 100644 tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php diff --git a/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php b/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php index 813c6033727a7..55fc875889407 100755 --- a/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php +++ b/src/Symfony/Bundle/DoctrineBundle/DependencyInjection/DoctrineExtension.php @@ -408,6 +408,7 @@ protected function loadOrmEntityManager(array $entityManager, ContainerBuilder $ new Reference(sprintf('doctrine.orm.%s_configuration', $entityManager['name'])) ); $ormEmDef = new Definition('%doctrine.orm.entity_manager_class%', $ormEmArgs); + $ormEmDef->setFactoryClass('%doctrine.orm.entity_manager_class%'); $ormEmDef->setFactoryMethod('create'); $ormEmDef->addTag('doctrine.orm.entity_manager'); $container->setDefinition($entityManagerService, $ormEmDef); diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php index bd9fbefc9eaff..46a4e4046771b 100755 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php @@ -157,6 +157,7 @@ public function testDependencyInjectionConfigurationDefaults() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -198,6 +199,7 @@ public function testSingleEntityManagerConfiguration() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -239,6 +241,7 @@ public function testLoadSimpleSingleConnection() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -279,6 +282,7 @@ public function testLoadSingleConnection() $definition = $container->getDefinition('doctrine.orm.default_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -313,6 +317,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.orm.dm1_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); @@ -334,6 +339,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.orm.dm2_entity_manager'); $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.orm.entity_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.orm.entity_manager', $definition->getTags()); diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php index 880005e174879..0eb11749b3f48 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/DependencyInjection/DoctrineMongoDBExtension.php @@ -165,6 +165,7 @@ protected function loadDocumentManager(array $documentManager, ContainerBuilder new Reference($eventManagerId), ); $odmDmDef = new Definition('%doctrine.odm.mongodb.document_manager_class%', $odmDmArgs); + $odmDmDef->setFactoryClass('%doctrine.odm.mongodb.document_manager_class%'); $odmDmDef->setFactoryMethod('create'); $odmDmDef->addTag('doctrine.odm.mongodb.document_manager'); $container->setDefinition(sprintf('doctrine.odm.mongodb.%s_document_manager', $documentManager['name']), $odmDmDef); diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php index b500b08e06350..dee13dabc7669 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php @@ -65,6 +65,7 @@ public function testDependencyInjectionConfigurationDefaults() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -92,6 +93,7 @@ public function testSingleDocumentManagerConfiguration() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -126,6 +128,7 @@ public function testLoadSimpleSingleConnection() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -154,6 +157,7 @@ public function testLoadSingleConnection() $definition = $container->getDefinition('doctrine.odm.mongodb.default_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -184,6 +188,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.odm.mongodb.dm1_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); @@ -199,6 +204,7 @@ public function testLoadMultipleConnections() $definition = $container->getDefinition('doctrine.odm.mongodb.dm2_document_manager'); $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getClass()); + $this->assertEquals('%doctrine.odm.mongodb.document_manager_class%', $definition->getFactoryClass()); $this->assertEquals('create', $definition->getFactoryMethod()); $this->assertArrayHasKey('doctrine.odm.mongodb.document_manager', $definition->getTags()); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php index 4b93f9ac0f3c3..dc2654e77294c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php @@ -41,7 +41,7 @@ public function process(ContainerBuilder $container) // non-synthetic, non-abstract service has class if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) { - if ($definition->getFactoryService()) { + if ($definition->getFactoryClass() || $definition->getFactoryService()) { throw new \RuntimeException(sprintf( 'Please add the class to service "%s" even if it is constructed ' .'by a factory since we might need to add method calls based on ' diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index a0cc8213f541f..d3961ca57432b 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -49,8 +49,9 @@ protected function resolveDefinition($id, DefinitionDecorator $definition) $def->setClass($parentDef->getClass()); $def->setArguments($parentDef->getArguments()); $def->setMethodCalls($parentDef->getMethodCalls()); - $def->setFactoryService($parentDef->getFactoryService()); + $def->setFactoryClass($parentDef->getFactoryClass()); $def->setFactoryMethod($parentDef->getFactoryMethod()); + $def->setFactoryService($parentDef->getFactoryService()); $def->setConfigurator($parentDef->getConfigurator()); $def->setFile($parentDef->getFile()); $def->setPublic($parentDef->isPublic()); @@ -60,6 +61,9 @@ protected function resolveDefinition($id, DefinitionDecorator $definition) if (isset($changes['class'])) { $def->setClass($definition->getClass()); } + if (isset($changes['factory_class'])) { + $def->setFactoryClass($definition->getFactoryClass()); + } if (isset($changes['factory_method'])) { $def->setFactoryMethod($definition->getFactoryMethod()); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php index 1373665cd0391..2fecee1b6694e 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php @@ -32,7 +32,7 @@ public function process(ContainerBuilder $container) $loaded = false; foreach ($container->getInterfaceInjectors() as $injector) { - if (null !== $definition->getFactoryService()) { + if (null !== $definition->getFactoryClass() || null !== $definition->getFactoryService()) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 8bf948e67b9e4..008ef416c269e 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -695,10 +695,12 @@ protected function createService(Definition $definition, $id) $arguments = $this->resolveServices($this->getParameterBag()->resolveValue($definition->getArguments())); if (null !== $definition->getFactoryMethod()) { - if (null !== $definition->getFactoryService()) { + if (null !== $definition->getFactoryClass()) { + $factory = $this->getParameterBag()->resolveValue($definition->getFactoryClass()); + } elseif (null !== $definition->getFactoryService()) { $factory = $this->get($this->getParameterBag()->resolveValue($definition->getFactoryService())); } else { - $factory = $this->getParameterBag()->resolveValue($definition->getClass()); + throw new \RuntimeException('Cannot create service from factory method without a factory service or factory class.'); } $service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments); diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php index fa70eb6d14e55..e3d58cb6f2080 100644 --- a/src/Symfony/Component/DependencyInjection/Definition.php +++ b/src/Symfony/Component/DependencyInjection/Definition.php @@ -20,6 +20,7 @@ class Definition { protected $class; protected $file; + protected $factoryClass; protected $factoryMethod; protected $factoryService; protected $scope; @@ -49,16 +50,41 @@ public function __construct($class = null, array $arguments = array()) $this->abstract = false; } + /** + * Sets the name of the class that acts as a factory using the factory method, + * which will be invoked statically. + * + * @param string $factoryClass The factory class name + * + * @return Definition The current instance + */ + public function setFactoryClass($factoryClass) + { + $this->factoryClass = $factoryClass; + + return $this; + } + + /** + * Gets the factory class. + * + * @return string The factory class name + */ + public function getFactoryClass() + { + return $this->factoryClass; + } + /** * Sets the factory method able to create an instance of this class. * - * @param string $method The method name + * @param string $factoryMethod The factory method name * * @return Definition The current instance */ - public function setFactoryMethod($method) + public function setFactoryMethod($factoryMethod) { - $this->factoryMethod = $method; + $this->factoryMethod = $factoryMethod; return $this; } @@ -74,7 +100,7 @@ public function getFactoryMethod() } /** - * Sets the name of the service that acts as a factory using the constructor method. + * Sets the name of the service that acts as a factory using the factory method. * * @param string $factoryService The factory service id * diff --git a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php index cde424c50bf18..e74a1bf7e319c 100644 --- a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php +++ b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php @@ -37,11 +37,11 @@ public function setClass($class) return parent::setClass($class); } - public function setFactoryService($service) + public function setFactoryClass($class) { - $this->changes['factory_service'] = true; + $this->changes['factory_class'] = true; - return parent::setFactoryService($service); + return parent::setFactoryClass($class); } public function setFactoryMethod($method) @@ -51,6 +51,13 @@ public function setFactoryMethod($method) return parent::setFactoryMethod($method); } + public function setFactoryService($service) + { + $this->changes['factory_service'] = true; + + return parent::setFactoryService($service); + } + public function setConfigurator($callable) { $this->changes['configurator'] = true; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 57e984f408bb9..7829b10b1a5fa 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -230,10 +230,12 @@ protected function addServiceInlinedDefinitions($id, $definition) } if (null !== $sDefinition->getFactoryMethod()) { - if (null !== $sDefinition->getFactoryService()) { + if (null !== $sDefinition->getFactoryClass()) { + $code .= sprintf(" \$%s = call_user_func(array(%s, '%s')%s);\n", $name, $this->dumpValue($sDefinition->getFactoryClass()), $sDefinition->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $sDefinition->getFactoryService()) { $code .= sprintf(" \$%s = %s->%s(%s);\n", $name, $this->getServiceCall($sDefinition->getFactoryService()), $sDefinition->getFactoryMethod(), implode(', ', $arguments)); } else { - $code .= sprintf(" \$%s = call_user_func(array(%s, '%s')%s);\n", $name, $class, $sDefinition->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + throw new \RuntimeException('Factory service or factory class must be defined in service definition for '.$id); } } elseif (false !== strpos($class, '$')) { $code .= sprintf(" \$class = %s;\n \$%s = new \$class(%s);\n", $class, $name, implode(', ', $arguments)); @@ -294,10 +296,12 @@ protected function addServiceInstance($id, $definition) } if (null !== $definition->getFactoryMethod()) { - if (null !== $definition->getFactoryService()) { + if (null !== $definition->getFactoryClass()) { + $code = sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($definition->getFactoryClass()), $definition->getFactoryMethod(), $arguments ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $definition->getFactoryService()) { $code = sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->getServiceCall($definition->getFactoryService()), $definition->getFactoryMethod(), implode(', ', $arguments)); } else { - $code = sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $class, $definition->getFactoryMethod(), $arguments ? ', '.implode(', ', $arguments) : ''); + throw new \RuntimeException('Factory method requires a factory service or factory class in service definition for '.$id); } } elseif (false !== strpos($class, '$')) { $code = sprintf(" \$class = %s;\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments)); @@ -404,8 +408,10 @@ protected function addService($id, $definition) $return = ''; if ($definition->isSynthetic()) { $return = sprintf('@throws \RuntimeException always since this service is expected to be injected dynamically'); - } else if ($class = $definition->getClass()) { + } elseif ($class = $definition->getClass()) { $return = sprintf("@return %s A %s instance.", 0 === strpos($class, '%') ? 'Object' : $class, $class); + } elseif ($definition->getFactoryClass()) { + $return = sprintf('@return Object An instance returned by %s::%s().', $definition->getFactoryClass(), $definition->getFactoryMethod()); } elseif ($definition->getFactoryService()) { $return = sprintf('@return Object An instance returned by %s::%s().', $definition->getFactoryService(), $definition->getFactoryMethod()); } @@ -821,10 +827,12 @@ protected function dumpValue($value, $interpolate = true) } if (null !== $value->getFactoryMethod()) { - if (null !== $value->getFactoryService()) { + if (null !== $value->getFactoryClass()) { + return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($value->getFactoryClass()), $value->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $value->getFactoryService()) { return sprintf("%s->%s(%s)", $this->getServiceCall($value->getFactoryService()), $value->getFactoryMethod(), implode(', ', $arguments)); } else { - return sprintf("call_user_func(array(%s, '%s')%s)", $class, $value->getFactoryMethod(), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + throw new \RuntimeException('Cannot dump definitions which have factory method without factory service or factory class.'); } } diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index ec0a10625ee25..ec39573f83b72 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -143,7 +143,7 @@ protected function parseDefinition($id, $service, $file) $definition = new Definition(); } - foreach (array('class', 'scope', 'public', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) { + foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) { if (isset($service[$key])) { $method = 'set'.str_replace('-', '', $key); $definition->$method((string) $service->getAttributeAsPhp($key)); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 65def0455f834..926a274a00300 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -165,6 +165,10 @@ protected function parseDefinition($id, $service, $file) $definition->setAbstract($service['abstract']); } + if (isset($service['factory_class'])) { + $definition->setFactoryClass($service['factory_class']); + } + if (isset($service['factory_method'])) { $definition->setFactoryMethod($service['factory_method']); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index a6ccf726ee44f..35a8a684c9887 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -105,6 +105,7 @@ + diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php new file mode 100644 index 0000000000000..73d46bbc3853f --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Compiler/CheckDefinitionValidityPassTest.php @@ -0,0 +1,61 @@ +register('a')->setSynthetic(true)->setPublic(false); + + $this->process($container); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessDetectsSyntheticPrototypeDefinitions() + { + $container = new ContainerBuilder(); + $container->register('a')->setSynthetic(true)->setScope(ContainerInterface::SCOPE_PROTOTYPE); + + $this->process($container); + } + + /** + * @expectedException \RuntimeException + */ + public function testProcessDetectsNonSyntheticNonAbstractDefinitionWithoutClass() + { + $container = new ContainerBuilder(); + $container->register('a')->setSynthetic(false)->setAbstract(false); + + $this->process($container); + } + + public function testProcess() + { + $container = new ContainerBuilder(); + $container->register('a', 'class'); + $container->register('b', 'class')->setSynthetic(true)->setPublic(true); + $container->register('c', 'class')->setAbstract(true); + $container->register('d', 'class')->setSynthetic(true); + + $this->process($container); + } + + protected function process(ContainerBuilder $container) + { + $pass = new CheckDefinitionValidityPass(); + $pass->process($container); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php b/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php index b9d68f072e889..4a14e40664364 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/ContainerBuilderTest.php @@ -247,7 +247,7 @@ public function testCreateServiceFactoryMethod() { $builder = new ContainerBuilder(); $builder->register('bar', 'stdClass'); - $builder->register('foo1', 'FooClass')->setFactoryMethod('getInstance')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'))); + $builder->register('foo1', 'FooClass')->setFactoryClass('FooClass')->setFactoryMethod('getInstance')->addArgument(array('foo' => '%value%', '%value%' => 'foo', new Reference('bar'))); $builder->setParameter('value', 'bar'); $this->assertTrue($builder->get('foo1')->called, '->createService() calls the factory method to create the service instance'); $this->assertEquals(array('foo' => 'bar', 'bar' => 'foo', $builder->get('bar')), $builder->get('foo1')->arguments, '->createService() passes the arguments to the factory method'); diff --git a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php index ff80fba5d83b8..6c74adfe4a42d 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionDecoratorTest.php @@ -34,8 +34,9 @@ public function getPropertyTests() { return array( array('class', 'class'), - array('factoryService', 'factory_service'), + array('factoryClass', 'factory_class'), array('factoryMethod', 'factory_method'), + array('factoryService', 'factory_service'), array('configurator', 'configurator'), array('file', 'file'), ); diff --git a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php index 41ded4f06bc35..5a6097dcd3156 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php @@ -27,13 +27,18 @@ public function testConstructor() $this->assertEquals(array('foo'), $def->getArguments(), '__construct() takes an optional array of arguments as its second argument'); } - /** - * @covers Symfony\Component\DependencyInjection\Definition::setFactoryMethod - * @covers Symfony\Component\DependencyInjection\Definition::getFactoryMethod - */ - public function testSetGetConstructor() + public function testSetGetFactoryClass() + { + $def = new Definition('stdClass'); + $this->assertNull($def->getFactoryClass()); + $this->assertSame($def, $def->setFactoryClass('stdClass2'), "->setFactoryClass() implements a fluent interface."); + $this->assertEquals('stdClass2', $def->getFactoryClass(), "->getFactoryClass() returns current class to construct this service."); + } + + public function testSetGetFactoryMethod() { $def = new Definition('stdClass'); + $this->assertNull($def->getFactoryMethod()); $this->assertSame($def, $def->setFactoryMethod('foo'), '->setFactoryMethod() implements a fluent interface'); $this->assertEquals('foo', $def->getFactoryMethod(), '->getFactoryMethod() returns the factory method name'); } @@ -42,8 +47,8 @@ public function testSetGetFactoryService() { $def = new Definition('stdClass'); $this->assertNull($def->getFactoryService()); - $this->assertSame($def, $def->setFactoryService('stdClass2'), "->setFactoryService() implements a fluent interface."); - $this->assertEquals('stdClass2', $def->getFactoryService(), "->getFactoryService() returns current service to construct this service."); + $this->assertSame($def, $def->setFactoryService('foo.bar'), "->setFactoryService() implements a fluent interface."); + $this->assertEquals('foo.bar', $def->getFactoryService(), "->getFactoryService() returns current service to construct this service."); } /** diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php index 46c9dbb90932f..03e4c9db92829 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/containers/container9.php @@ -12,6 +12,7 @@ register('foo', 'FooClass')-> addTag('foo', array('foo' => 'foo'))-> addTag('foo', array('bar' => 'bar'))-> + setFactoryClass('FooClass')-> setFactoryMethod('getInstance')-> setArguments(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, new Reference('service_container')))-> setScope('prototype')-> @@ -27,6 +28,7 @@ ; $container-> register('foo.baz', '%baz_class%')-> + setFactoryClass('%baz_class%')-> setFactoryMethod('getInstance')-> setConfigurator(array('%baz_class%', 'configureStatic1')) ; From 80b03f92b300933e2c78eaee9ad1be77699f1db7 Mon Sep 17 00:00:00 2001 From: ornicar Date: Sat, 5 Feb 2011 11:26:14 -0800 Subject: [PATCH 05/13] [HttpKernel] Throw exception when SQLite statement execution failed This fixes SQLiteProfilerStorageTest, that was failing using PDO. --- .../Component/HttpKernel/Profiler/SQLiteProfilerStorage.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php index 25d4cc1f5f77c..f82ecec163176 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php @@ -157,7 +157,10 @@ protected function exec($db, $query, array $args = array()) foreach ($args as $arg => $val) { $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR); } - $stmt->execute(); + $success = $stmt->execute(); + if (!$success) { + throw new \RuntimeException(sprintf('Error executing SQLite query "%s"', $query)); + } } } From bfe701a2da2c0b7f6b1b40cdb1118bed52d30a82 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Feb 2011 21:36:20 +0100 Subject: [PATCH 06/13] fixed code coverage --- phpunit.xml.dist | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5151325450022..6cad90e26cdc2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -24,7 +24,8 @@ ./src/Symfony/Bundle/*/Resources ./src/Symfony/Component/*/Resources - ./src/Symfony/Component/HttpKernel/bootstrap* + ./src/Symfony/Component/HttpKernel/bootstrap.php + ./src/Symfony/Component/HttpKernel/bootstrap_cache.php From 523e652d9dcae71b143928df5f5eac02c7ce225a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Feb 2011 22:19:14 +0100 Subject: [PATCH 07/13] [FrameworkBundle] fixed the way profiler configuration works --- .../FrameworkExtension.php | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 9b7760ce072f2..28606232411b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -148,7 +148,7 @@ protected function mergeConfigs(array $configs) ), 'ide' => null, 'profiler' => array( - 'enabled' => true, + 'enabled' => false, 'only_exceptions' => null, 'matcher' => array( 'ip' => null, @@ -203,6 +203,10 @@ protected function mergeConfigs(array $configs) foreach ($configs as $config) { $config = $this->normalizeKeys($config); + if (isset($config['profiler'])) { + $config['profiler']['enabled'] = true; + } + if (isset($config['templating']) && is_array($config['templating'])) { $config['templating']['engines'] = $this->normalizeConfig($config['templating'], 'engine'); $config['templating']['loaders'] = $this->normalizeConfig($config['templating'], 'loader'); @@ -334,28 +338,30 @@ protected function registerEsiConfiguration(array $config, XmlFileLoader $loader */ protected function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if ($config['enabled']) { - $loader->load('profiling.xml'); - $loader->load('collectors.xml'); + if (!$config['enabled']) { + return; + } - if (isset($config['only_exceptions'])) { - $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); - } + $loader->load('profiling.xml'); + $loader->load('collectors.xml'); - if ($config['matcher']) { - if (isset($config['matcher']['service'])) { - $container->setAlias('profiler.request_matcher', $config['matcher']['service']); - } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) { - $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); - $definition->setPublic(false); + if (isset($config['only_exceptions'])) { + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); + } - if (isset($config['matcher']['ip'])) { - $definition->addMethodCall('matchIp', array($config['matcher']['ip'])); - } + if ($config['matcher']) { + if (isset($config['matcher']['service'])) { + $container->setAlias('profiler.request_matcher', $config['matcher']['service']); + } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) { + $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); + $definition->setPublic(false); - if (isset($config['matcher']['path'])) { - $definition->addMethodCall('matchPath', array($config['matcher']['path'])); - } + if (isset($config['matcher']['ip'])) { + $definition->addMethodCall('matchIp', array($config['matcher']['ip'])); + } + + if (isset($config['matcher']['path'])) { + $definition->addMethodCall('matchPath', array($config['matcher']['path'])); } } } From e5403490e7ea226d19f4fb2b57bf209858caeea9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Feb 2011 22:40:30 +0100 Subject: [PATCH 08/13] removed the need to define getNamespace() and getPath() in bundles --- .../CompatAssetsBundle/CompatAssetsBundle.php | 15 ------- .../Bundle/DoctrineBundle/DoctrineBundle.php | 16 ------- .../AnnotationsBundle/AnnotationsBundle.php | 17 +------ .../AnnotationsBundle/AnnotationsBundle.php | 17 +------ .../Fixtures/Bundles/XmlBundle/XmlBundle.php | 15 ------- .../Bundles/YamlBundle/YamlBundle.php | 15 ------- .../DoctrineMigrationsBundle.php | 15 ------- .../DoctrineMongoDBBundle.php | 16 ------- .../AnnotationsBundle/AnnotationsBundle.php | 15 ------- .../Fixtures/Bundles/XmlBundle/XmlBundle.php | 15 ------- .../Bundles/YamlBundle/YamlBundle.php | 15 ------- .../CacheWarmer/TemplatePathsCacheWarmer.php | 2 +- .../FrameworkBundle/FrameworkBundle.php | 16 ------- .../Resources/skeleton/bundle/Bundle.php | 15 ------- .../Fabpot/FooBundle/FabpotFooBundle.php | 16 ------- .../TestBundle/FooBundle/FooBundle.php | 15 ------- .../Cms/FooBundle/SensioCmsFooBundle.php | 15 ------- .../Sensio/FooBundle/SensioFooBundle.php | 15 ------- .../Bundle/SecurityBundle/SecurityBundle.php | 16 ------- .../SwiftmailerBundle/SwiftmailerBundle.php | 15 ------- src/Symfony/Bundle/TwigBundle/TwigBundle.php | 16 ------- .../WebProfilerBundle/WebProfilerBundle.php | 15 ------- src/Symfony/Bundle/ZendBundle/ZendBundle.php | 16 ------- .../Component/HttpKernel/Bundle/Bundle.php | 45 +++++++++++++------ .../HttpKernel/Bundle/BundleInterface.php | 10 +---- src/Symfony/Component/HttpKernel/Kernel.php | 2 +- .../Component/HttpKernel/bootstrap.php | 28 ++++++++---- .../Component/HttpKernel/bootstrap_cache.php | 2 +- .../HttpKernel/Bundle/BundleTest.php | 33 -------------- .../Tests/Component/HttpKernel/KernelTest.php | 4 +- 30 files changed, 58 insertions(+), 409 deletions(-) delete mode 100644 tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php diff --git a/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php b/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php index fbdb2663aa5fb..9e1158984b4f7 100644 --- a/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php +++ b/src/Symfony/Bundle/CompatAssetsBundle/CompatAssetsBundle.php @@ -20,19 +20,4 @@ */ class CompatAssetsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php b/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php index 5e4e6ab21507e..de1d170543fde 100644 --- a/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/DoctrineBundle.php @@ -31,20 +31,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new RegisterEventListenersAndSubscribersPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php index 3cd2803b1f05a..899d3e178eb0f 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php @@ -6,19 +6,4 @@ class AnnotationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } -} \ No newline at end of file +} diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php index 3e08c3a86e8b8..641edc122e105 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/Vendor/AnnotationsBundle/AnnotationsBundle.php @@ -6,19 +6,4 @@ class AnnotationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } -} \ No newline at end of file +} diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php index 9980c7bef12ea..3bdd9873eaa29 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php @@ -6,19 +6,4 @@ class XmlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php index 225ce0f90368f..c89784c34c4ac 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php +++ b/src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php @@ -6,19 +6,4 @@ class YamlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php b/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php index d987d30d79cda..6fadd42c11c35 100644 --- a/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php +++ b/src/Symfony/Bundle/DoctrineMigrationsBundle/DoctrineMigrationsBundle.php @@ -21,19 +21,4 @@ */ class DoctrineMigrationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php index d6d409c473b95..99e205e977a5f 100755 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/DoctrineMongoDBBundle.php @@ -35,20 +35,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new CreateProxyDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new CreateHydratorDirectoryPass(), PassConfig::TYPE_BEFORE_REMOVING); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php index 2c4eee1523a52..23b96a825d60f 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/AnnotationsBundle/AnnotationsBundle.php @@ -6,19 +6,4 @@ class AnnotationsBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php index bdb28af65133b..f02676fcb8d6d 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/XmlBundle/XmlBundle.php @@ -6,19 +6,4 @@ class XmlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php index 8c06610c5eb1e..3c63ccb389c15 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/Fixtures/Bundles/YamlBundle/YamlBundle.php @@ -6,19 +6,4 @@ class YamlBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php index 3477454468d35..032b927804292 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/TemplatePathsCacheWarmer.php @@ -64,7 +64,7 @@ protected function computeTemplatePaths() $prefix = '/Resources/views'; $templates = array(); foreach ($this->kernel->getBundles() as $name => $bundle) { - if (!is_dir($dir = $bundle->getNormalizedPath().$prefix)) { + if (!is_dir($dir = $bundle->getPath().$prefix)) { continue; } diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index fc15e0804ff83..c7a5577ef7a2e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -80,20 +80,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new TranslatorPass()); $container->addCompilerPass(new AddCacheWarmerPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php b/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php index 2c101de81610a..7721df5248599 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/skeleton/bundle/Bundle.php @@ -6,19 +6,4 @@ class {{ bundle }} extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return strtr(__DIR__, '\\', '/'); - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php index 1342b9b5eed32..22b95430711fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Fabpot/FooBundle/FabpotFooBundle.php @@ -27,20 +27,4 @@ public function getParent() { return 'SensioFooBundle'; } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php index 98dd4342273a2..af57d44bcdd2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/FooBundle/FooBundle.php @@ -20,19 +20,4 @@ */ class FooBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php index 811ed2f14115a..9e6918d34e955 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/Cms/FooBundle/SensioCmsFooBundle.php @@ -20,19 +20,4 @@ */ class SensioCmsFooBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php index 2b547f923ece1..e8930625f3b19 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/TestBundle/Sensio/FooBundle/SensioFooBundle.php @@ -20,19 +20,4 @@ */ class SensioFooBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 46436fb27d91c..b62d9b7557077 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -30,20 +30,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new AddSecurityVotersPass()); $container->addCompilerPass(new AddAuthenticationProvidersPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php b/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php index 7480863e96580..8b0a3a4a10148 100644 --- a/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php +++ b/src/Symfony/Bundle/SwiftmailerBundle/SwiftmailerBundle.php @@ -20,19 +20,4 @@ */ class SwiftmailerBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/TwigBundle/TwigBundle.php b/src/Symfony/Bundle/TwigBundle/TwigBundle.php index dff10138a6da3..d27d85bcd2d67 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigBundle.php +++ b/src/Symfony/Bundle/TwigBundle/TwigBundle.php @@ -28,20 +28,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new TwigEnvironmentPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php b/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php index 0a265a0004f7a..b3558f8ab3da0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php +++ b/src/Symfony/Bundle/WebProfilerBundle/WebProfilerBundle.php @@ -20,19 +20,4 @@ */ class WebProfilerBundle extends Bundle { - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Bundle/ZendBundle/ZendBundle.php b/src/Symfony/Bundle/ZendBundle/ZendBundle.php index e53ad48cb6476..c06f2636ab746 100644 --- a/src/Symfony/Bundle/ZendBundle/ZendBundle.php +++ b/src/Symfony/Bundle/ZendBundle/ZendBundle.php @@ -28,20 +28,4 @@ public function registerExtensions(ContainerBuilder $container) $container->addCompilerPass(new ZendLoggerWriterPass()); } - - /** - * {@inheritdoc} - */ - public function getNamespace() - { - return __NAMESPACE__; - } - - /** - * {@inheritdoc} - */ - public function getPath() - { - return __DIR__; - } } diff --git a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php index e214c00cd7238..17b5f75a73c71 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/Bundle.php +++ b/src/Symfony/Component/HttpKernel/Bundle/Bundle.php @@ -25,6 +25,7 @@ abstract class Bundle extends ContainerAware implements BundleInterface { protected $name; + protected $reflected; /** * Boots the Bundle. @@ -40,6 +41,34 @@ public function shutdown() { } + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + + return $this->reflected->getNamespaceName(); + } + + /** + * Gets the Bundle directory path. + * + * @return string The Bundle absolute path + */ + public function getPath() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + + return strtr(dirname($this->reflected->getFileName()), '\\', '/'); + } + /** * Returns the bundle parent name. * @@ -67,18 +96,6 @@ final public function getName() return $this->name = false === $pos ? $name : substr($name, $pos + 1); } - /** - * Gets the Bundle directory path. - * - * The path should always be returned as a Unix path (with /). - * - * @return string The Bundle absolute path - */ - final public function getNormalizedPath() - { - return strtr($this->getPath(), '\\', '/'); - } - /** * Finds and registers Dependency Injection Container extensions. * @@ -91,7 +108,7 @@ final public function getNormalizedPath() */ public function registerExtensions(ContainerBuilder $container) { - if (!$dir = realpath($this->getNormalizedPath().'/DependencyInjection')) { + if (!$dir = realpath($this->getPath().'/DependencyInjection')) { return; } @@ -118,7 +135,7 @@ public function registerExtensions(ContainerBuilder $container) */ public function registerCommands(Application $application) { - if (!$dir = realpath($this->getNormalizedPath().'/Command')) { + if (!$dir = realpath($this->getPath().'/Command')) { return; } diff --git a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php index fd55044e5ef5e..ba7f0ab6b647f 100644 --- a/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php +++ b/src/Symfony/Component/HttpKernel/Bundle/BundleInterface.php @@ -49,14 +49,6 @@ function getName(); */ function getNamespace(); - - /** - * Gets the Bundle directory path. - * - * @return string The Bundle absolute path - */ - function getPath(); - /** * Gets the Bundle directory path. * @@ -64,5 +56,5 @@ function getPath(); * * @return string The Bundle absolute path */ - function getNormalizedPath(); + function getPath(); } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c46d9bf1a150c..da64b790a76f0 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -237,7 +237,7 @@ public function locateResource($name, $dir = null, $first = true) } foreach ($this->getBundle($bundle, false) as $bundle) { - if (file_exists($file = $bundle->getNormalizedPath().'/'.$path)) { + if (file_exists($file = $bundle->getPath().'/'.$path)) { if ($first) { return $file; } diff --git a/src/Symfony/Component/HttpKernel/bootstrap.php b/src/Symfony/Component/HttpKernel/bootstrap.php index acdb86346579d..699e1f11a057d 100644 --- a/src/Symfony/Component/HttpKernel/bootstrap.php +++ b/src/Symfony/Component/HttpKernel/bootstrap.php @@ -113,7 +113,7 @@ public function getServiceIds() $ids = array(); $r = new \ReflectionClass($this); foreach ($r->getMethods() as $method) { - if (preg_match('/^get(.+)Service$/', $name = $method->getName(), $match)) { + if (preg_match('/^get(.+)Service$/', $method->getName(), $match)) { $ids[] = self::underscore($match[1]); } } @@ -229,7 +229,6 @@ function getParent(); function getName(); function getNamespace(); function getPath(); - function getNormalizedPath(); } } namespace Symfony\Component\HttpKernel\Bundle @@ -241,12 +240,27 @@ function getNormalizedPath(); abstract class Bundle extends ContainerAware implements BundleInterface { protected $name; + protected $reflected; public function boot() { } public function shutdown() { } + public function getNamespace() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + return $this->reflected->getNamespaceName(); + } + public function getPath() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + return strtr(dirname($this->reflected->getFileName()), '\\', '/'); + } public function getParent() { return null; @@ -260,13 +274,9 @@ final public function getName() $pos = strrpos($name, '\\'); return $this->name = false === $pos ? $name : substr($name, $pos + 1); } - final public function getNormalizedPath() - { - return strtr($this->getPath(), '\\', '/'); - } public function registerExtensions(ContainerBuilder $container) { - if (!$dir = realpath($this->getNormalizedPath().'/DependencyInjection')) { + if (!$dir = realpath($this->getPath().'/DependencyInjection')) { return; } $finder = new Finder(); @@ -279,7 +289,7 @@ public function registerExtensions(ContainerBuilder $container) } public function registerCommands(Application $application) { - if (!$dir = realpath($this->getNormalizedPath().'/Command')) { + if (!$dir = realpath($this->getPath().'/Command')) { return; } $finder = new Finder(); @@ -577,7 +587,7 @@ public function locateResource($name, $dir = null, $first = true) $files[] = $file; } foreach ($this->getBundle($bundle, false) as $bundle) { - if (file_exists($file = $bundle->getNormalizedPath().'/'.$path)) { + if (file_exists($file = $bundle->getPath().'/'.$path)) { if ($first) { return $file; } diff --git a/src/Symfony/Component/HttpKernel/bootstrap_cache.php b/src/Symfony/Component/HttpKernel/bootstrap_cache.php index 051f3bc25b43c..e1bbfc66aa100 100644 --- a/src/Symfony/Component/HttpKernel/bootstrap_cache.php +++ b/src/Symfony/Component/HttpKernel/bootstrap_cache.php @@ -151,7 +151,7 @@ public function locateResource($name, $dir = null, $first = true) $files[] = $file; } foreach ($this->getBundle($bundle, false) as $bundle) { - if (file_exists($file = $bundle->getNormalizedPath().'/'.$path)) { + if (file_exists($file = $bundle->getPath().'/'.$path)) { if ($first) { return $file; } diff --git a/tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php b/tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php deleted file mode 100644 index b3bf6fed30e6f..0000000000000 --- a/tests/Symfony/Tests/Component/HttpKernel/Bundle/BundleTest.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Tests\Component\HttpKernel\Bundle; - -class BundleTest extends \PHPUnit_Framework_TestCase -{ - public function testGetNormalizedPathReturnsANormalizedPath() - { - $bundle = $this - ->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') - ->setMethods(array('getPath')) - ->disableOriginalConstructor() - ->getMockForAbstractClass() - ; - - $bundle - ->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('path\\to\\foo\\bar')) - ; - - $this->assertEquals('path/to/foo/bar', $bundle->getNormalizedPath()); - } -} diff --git a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php index 5695f5a6fdbaf..a97eb74aeb926 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php @@ -255,7 +255,7 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu { $bundle = $this ->getMockBuilder('Symfony\Tests\Component\HttpKernel\BundleForTest') - ->setMethods(array('getNormalizedPath', 'getParent', 'getName')) + ->setMethods(array('getPath', 'getParent', 'getName')) ->disableOriginalConstructor() ; @@ -273,7 +273,7 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu $bundle ->expects($this->any()) - ->method('getNormalizedPath') + ->method('getPath') ->will($this->returnValue(strtr($dir, '\\', '/'))) ; From f56a6efbf5b86e9da8204ee53f07eb9f416b0916 Mon Sep 17 00:00:00 2001 From: pborreli Date: Sat, 5 Feb 2011 22:20:15 +0000 Subject: [PATCH 09/13] [HttpFoundation] File/File full coverage --- .../Component/HttpFoundation/File/File.php | 4 +- .../HttpFoundation/File/FileTest.php | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/File/File.php b/src/Symfony/Component/HttpFoundation/File/File.php index 8fd995d67351f..50464ea2a3759 100644 --- a/src/Symfony/Component/HttpFoundation/File/File.php +++ b/src/Symfony/Component/HttpFoundation/File/File.php @@ -605,7 +605,7 @@ public function getMimeType() */ public function size() { - if (false === ($size = filesize($this->getPath()))) { + if (false === ($size = @filesize($this->getPath()))) { throw new FileException(sprintf('Could not read file size of %s', $this->getPath())); } @@ -623,7 +623,7 @@ protected function doMove($directory, $filename) { $newPath = $directory . DIRECTORY_SEPARATOR . $filename; - if (!rename($this->getPath(), $newPath)) { + if (!@rename($this->getPath(), $newPath)) { throw new FileException(sprintf('Could not move file %s to %s', $this->getPath(), $newPath)); } diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php index 15048f7c3dd84..7f5e41a7be800 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/FileTest.php @@ -28,10 +28,16 @@ public function testGetPathReturnsAbsolutePath() $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', $this->file->getPath()); } + public function test__toString() + { + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'test.gif', (string) $this->file); + } + public function testGetWebPathReturnsPathRelativeToDocumentRoot() { File::setDocumentRoot(__DIR__); + $this->assertEquals(__DIR__, File::getDocumentRoot()); $this->assertEquals('/Fixtures/test.gif', $this->file->getWebPath()); } @@ -42,11 +48,24 @@ public function testGetWebPathReturnsEmptyPathIfOutsideDocumentRoot() $this->assertEquals('', $this->file->getWebPath()); } + public function testSetDocumentRootThrowsLogicExceptionWhenNotExists() + { + $this->setExpectedException('LogicException'); + + File::setDocumentRoot(__DIR__.'/Fixtures/not_here'); + } + public function testGetNameReturnsNameWithExtension() { $this->assertEquals('test.gif', $this->file->getName()); } + public function testGetExtensionReturnsEmptyString() + { + $file = new File(__DIR__.'/Fixtures/test'); + $this->assertEquals('', $file->getExtension()); + } + public function testGetExtensionReturnsExtensionWithDot() { $this->assertEquals('.gif', $this->file->getExtension()); @@ -66,6 +85,13 @@ public function testGetMimeTypeUsesMimeTypeGuessers() $this->assertEquals('image/gif', $this->file->getMimeType()); } + public function testGetDefaultExtensionWithoutGuesser() + { + $file = new File(__DIR__.'/Fixtures/directory/.empty'); + + $this->assertEquals('.empty', $file->getDefaultExtension()); + } + public function testGetDefaultExtensionIsBasedOnMimeType() { $file = new File(__DIR__.'/Fixtures/test'); @@ -76,11 +102,33 @@ public function testGetDefaultExtensionIsBasedOnMimeType() $this->assertEquals('.gif', $file->getDefaultExtension()); } + public function testConstructWhenFileNotExists() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new File(__DIR__.'/Fixtures/not_here'); + } + public function testSizeReturnsFileSize() { $this->assertEquals(filesize($this->file->getPath()), $this->file->size()); } + public function testSizeFailing() + { + $dir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'directory'; + $path = $dir.DIRECTORY_SEPARATOR.'test.copy.gif'; + @unlink($path); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + @unlink($path); + + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileException'); + $file->size($path); + + } + public function testMove() { $path = __DIR__.'/Fixtures/test.copy.gif'; @@ -121,6 +169,27 @@ public function testMoveWithNewName() @unlink($targetPath); } + public function testMoveFailing() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetPath = '/thisfolderwontexist'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileException'); + $file->move($targetPath); + + $this->assertFileExists($path); + $this->assertFileNotExists($path.$targetPath.'test.gif'); + $this->assertEquals($path, $file->getPath()); + + @unlink($path); + @unlink($targetPath); + } + public function testRename() { $path = __DIR__.'/Fixtures/test.copy.gif'; From 562ebd56082a93c70f0ba81a68f3cca020d3521a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Nahuel=20Cuesta=20Luengo?= Date: Sat, 5 Feb 2011 14:08:01 -0800 Subject: [PATCH 10/13] [CssSelector] Added PHPDoc blocks to first-level classes. --- src/Symfony/Component/CssSelector/Parser.php | 47 ++++++++++++ src/Symfony/Component/CssSelector/Token.php | 24 ++++++ .../Component/CssSelector/TokenStream.php | 28 +++++++ .../Component/CssSelector/Tokenizer.php | 32 ++++++++ .../Component/CssSelector/XPathExpr.php | 76 +++++++++++++++++++ .../Component/CssSelector/XPathExprOr.php | 11 +++ 6 files changed, 218 insertions(+) diff --git a/src/Symfony/Component/CssSelector/Parser.php b/src/Symfony/Component/CssSelector/Parser.php index 6861f80899b6a..4e80fc6098d4f 100644 --- a/src/Symfony/Component/CssSelector/Parser.php +++ b/src/Symfony/Component/CssSelector/Parser.php @@ -25,7 +25,16 @@ class Parser { /** + * Translate a CSS expression to its XPath equivalent. + * Optionally, a prefix can be added to the resulting XPath + * expression with the $prefix parameter. + * * @throws SyntaxError When got None for xpath expression + * + * @param mixed $cssExpr The CSS expression. + * @param string $prefix An optional prefix for the XPath expression. + * + * @return string */ static public function cssToXpath($cssExpr, $prefix = 'descendant-or-self::') { @@ -62,7 +71,14 @@ static public function cssToXpath($cssExpr, $prefix = 'descendant-or-self::') } /** + * Parse an expression and return the Node object that represents + * the parsed expression. + * * @throws \Exception When tokenizer throws it while parsing + * + * @param string $string The expression to parse + * + * @return Node\NodeInterface */ public function parse($string) { @@ -79,6 +95,14 @@ public function parse($string) } } + /** + * Parse a selector group contained in $stream and return + * the Node object that represents the expression. + * + * @param TokenStream $stream The stream to parse. + * + * @return Node\NodeInterface + */ protected function parseSelectorGroup($stream) { $result = array(); @@ -99,7 +123,14 @@ protected function parseSelectorGroup($stream) } /** + * Parse a selector contained in $stream and return the Node + * object that represents it. + * * @throws SyntaxError When expected selector but got something else + * + * @param TokenStrem $stream The stream containing the selector. + * + * @return Node\NodeInterface */ protected function parseSelector($stream) { @@ -128,7 +159,14 @@ protected function parseSelector($stream) } /** + * Parse a simple selector (the current token) from $stream and return + * the resulting Node object. + * * @throws SyntaxError When expected symbol but got something else + * + * @param TokenStream The stream containing the selector. + * + * @return Node\NodeInterface */ protected function parseSimpleSelector($stream) { @@ -228,7 +266,16 @@ protected function parseSimpleSelector($stream) } /** + * Parse an attribute from a selector contained in $stream and return + * the resulting AttribNode object. + * * @throws SyntaxError When encountered unexpected selector + * + * @param Node\NodeInterface $selector The selector object whose attribute + * is to be parsed. + * @param TokenStream $strem The container token stream. + * + * @return Node\AttribNode */ protected function parseAttrib($selector, $stream) { diff --git a/src/Symfony/Component/CssSelector/Token.php b/src/Symfony/Component/CssSelector/Token.php index 0dcb750167045..176617ac77850 100644 --- a/src/Symfony/Component/CssSelector/Token.php +++ b/src/Symfony/Component/CssSelector/Token.php @@ -25,6 +25,13 @@ class Token protected $value; protected $position; + /** + * Constructor. + * + * @param string $type The type of this token. + * @param mixed $value The value of this token. + * @param int $position The order of this token. + */ public function __construct($type, $value, $position) { $this->type = $type; @@ -32,16 +39,33 @@ public function __construct($type, $value, $position) $this->position = $position; } + /** + * Get a string representation of this token. + * + * @return string + */ public function __toString() { return (string) $this->value; } + /** + * Answer whether this token's type equals to $type. + * + * @param string $type The type to test against this token's one. + * + * @return bool + */ public function isType($type) { return $this->type == $type; } + /** + * Get the position of this token. + * + * @return int + */ public function getPosition() { return $this->position; diff --git a/src/Symfony/Component/CssSelector/TokenStream.php b/src/Symfony/Component/CssSelector/TokenStream.php index 5e262530aedcd..c6e62f312d6fb 100644 --- a/src/Symfony/Component/CssSelector/TokenStream.php +++ b/src/Symfony/Component/CssSelector/TokenStream.php @@ -27,6 +27,12 @@ class TokenStream protected $peeked; protected $peeking; + /** + * Constructor. + * + * @param array $tokens The tokens that make the stream. + * @param mixed $source The source of the stream. + */ public function __construct($tokens, $source = null) { $this->used = array(); @@ -36,11 +42,23 @@ public function __construct($tokens, $source = null) $this->peeking = false; } + /** + * Get the tokens that have already been visited in this stream. + * + * @return array + */ public function getUsed() { return $this->used; } + /** + * Get the next token in the stream or null if there is none. + * Note that if this stream was set to be peeking its behavior + * will be restored to not peeking after this operation. + * + * @return mixed + */ public function next() { if ($this->peeking) { @@ -60,6 +78,16 @@ public function next() return $next; } + /** + * Peek for the next token in this stream. This means that the next token + * will be returned but it won't be considered as used (visited) until the + * next() method is invoked. + * If there are no remaining tokens null will be returned. + * + * @see next() + * + * @return mixed + */ public function peek() { if (!$this->peeking) { diff --git a/src/Symfony/Component/CssSelector/Tokenizer.php b/src/Symfony/Component/CssSelector/Tokenizer.php index 339eed25e4821..5980f5a9f3eea 100644 --- a/src/Symfony/Component/CssSelector/Tokenizer.php +++ b/src/Symfony/Component/CssSelector/Tokenizer.php @@ -21,6 +21,14 @@ */ class Tokenizer { + /** + * Take a CSS selector and return an array holding the Tokens + * it contains. + * + * @param string $s The selector to lex. + * + * @return array Token[] + */ public function tokenize($s) { if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { @@ -95,7 +103,16 @@ public function tokenize($s) } /** + * Tokenize a quoted string (i.e. 'A string quoted with \' characters'), + * and return an array holding the unquoted string contained by $s and + * the new position from which tokenizing should take over. + * * @throws SyntaxError When expected closing is not found + * + * @param string $s The selector string containing the quoted string. + * @param int $pos The starting position for the quoted string. + * + * @return array */ protected function tokenizeEscapedString($s, $pos) { @@ -125,7 +142,13 @@ protected function tokenizeEscapedString($s, $pos) } /** + * Unescape a string literal and return the unescaped string. + * * @throws SyntaxError When invalid escape sequence is found + * + * @param string $literal The string literal to unescape. + * + * @return string */ protected function unescapeStringLiteral($literal) { @@ -143,7 +166,16 @@ protected function unescapeStringLiteral($literal) } /** + * Lex selector $s and return the an array holding the name of the symbol + * contained in it and the new position from which tokenizing should take + * over. + * * @throws SyntaxError When Unexpected symbol is found + * + * @param string $s The selector string. + * @param int $pos The position in $s at which the symbol starts. + * + * @return array */ protected function tokenizeSymbol($s, $pos) { diff --git a/src/Symfony/Component/CssSelector/XPathExpr.php b/src/Symfony/Component/CssSelector/XPathExpr.php index 52262e6efcf7c..cd32a8cc43b34 100644 --- a/src/Symfony/Component/CssSelector/XPathExpr.php +++ b/src/Symfony/Component/CssSelector/XPathExpr.php @@ -27,6 +27,15 @@ class XPathExpr protected $condition; protected $starPrefix; + /** + * Constructor. + * + * @param string $prefix Prefix for the XPath expression. + * @param string $path Actual path of the expression. + * @param string $element The element in the expression. + * @param string $condition A condition for the expression. + * @param bool $starPrefix Indicates whether to use a star prefix. + */ public function __construct($prefix = null, $path = null, $element = '*', $condition = null, $starPrefix = false) { $this->prefix = $prefix; @@ -36,31 +45,61 @@ public function __construct($prefix = null, $path = null, $element = '*', $condi $this->starPrefix = $starPrefix; } + /** + * Get the prefix of this XPath expression. + * + * @return string + */ public function getPrefix() { return $this->prefix; } + /** + * Get the path of this XPath expression. + * + * @return string + */ public function getPath() { return $this->path; } + /** + * Answer whether this XPath expression has a star prefix. + * + * @return bool + */ public function hasStarPrefix() { return $this->starPrefix; } + /** + * Get the element of this XPath expression. + * + * @return string + */ public function getElement() { return $this->element; } + /** + * Get the condition of this XPath expression. + * + * @return string + */ public function getCondition() { return $this->condition; } + /** + * Get a string representation for this XPath expression. + * + * @return string + */ public function __toString() { $path = ''; @@ -81,6 +120,12 @@ public function __toString() return $path; } + /** + * Add a condition to this XPath expression. + * Any pre-existant condition will be ANDed to it. + * + * @param string $condition The condition to add. + */ public function addCondition($condition) { if ($this->condition) { @@ -90,6 +135,12 @@ public function addCondition($condition) } } + /** + * Add a prefix to this XPath expression. + * It will be prepended to any pre-existant prefixes. + * + * @param string $prefix The prefix to add. + */ public function addPrefix($prefix) { if ($this->prefix) { @@ -99,6 +150,11 @@ public function addPrefix($prefix) } } + /** + * Add a condition to this XPath expression using the name of the element + * as the desired value. + * This method resets the element to '*'. + */ public function addNameTest() { if ($this->element == '*') { @@ -110,6 +166,11 @@ public function addNameTest() $this->element = '*'; } + /** + * Add a star prefix to this XPath expression. + * This method will prepend a '*' to the path and set the star prefix flag + * to true. + */ public function addStarPrefix() { /* @@ -125,6 +186,14 @@ public function addStarPrefix() $this->starPrefix = true; } + /** + * Join this XPath expression with $other (another XPath expression) using + * $combiner to join them. + * + * @param string $combiner The combiner string. + * @param XPathExpr $other The other XPath expression to combine with + * this one. + */ public function join($combiner, $other) { $prefix = (string) $this; @@ -143,6 +212,13 @@ public function join($combiner, $other) $this->condition = $other->GetCondition(); } + /** + * Get an XPath literal for $s. + * + * @param mixed $s Can either be a Node\ElementNode or a string. + * + * @return string + */ static public function xpathLiteral($s) { if ($s instanceof Node\ElementNode) { diff --git a/src/Symfony/Component/CssSelector/XPathExprOr.php b/src/Symfony/Component/CssSelector/XPathExprOr.php index c5347ba1306e6..21aa5a95494a8 100644 --- a/src/Symfony/Component/CssSelector/XPathExprOr.php +++ b/src/Symfony/Component/CssSelector/XPathExprOr.php @@ -23,12 +23,23 @@ */ class XPathExprOr extends XPathExpr { + /** + * Constructor. + * + * @param array $items The items in the expression. + * @param string $prefix Optional prefix for the expression. + */ public function __construct($items, $prefix = null) { $this->items = $items; $this->prefix = $prefix; } + /** + * Get a string representation of this |'d expression. + * + * @return string + */ public function __toString() { $prefix = $this->prefix; From bd97471954f18ac3843a618ccc968e2d297a3b80 Mon Sep 17 00:00:00 2001 From: ornicar Date: Sat, 5 Feb 2011 14:50:02 -0800 Subject: [PATCH 11/13] [HttpKernel] Add test coverage for cache warming --- .../CacheWarmer/CacheWarmerAggregateTest.php | 97 +++++++++++++++++++ .../CacheWarmer/CacheWarmerTest.php | 68 +++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php create mode 100644 tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php diff --git a/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php new file mode 100644 index 0000000000000..4b75a4717392f --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerAggregateTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpKernel\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerAggregateTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public function testInjectWarmersUsingConstructor() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingAdd() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->add($warmer); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingSetWarmers() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->setWarmers(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->enableOptionalWarmers(); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('isOptional') + ->will($this->returnValue(true)); + $warmer + ->expects($this->never()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + protected function getCacheWarmerMock() + { + $warmer = $this->getMockBuilder('Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface') + ->disableOriginalConstructor() + ->getMock(); + + return $warmer; + } +} diff --git a/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php new file mode 100644 index 0000000000000..063a114978da0 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpKernel/CacheWarmer/CacheWarmerTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpKernel\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheFile; + + public static function setUpBeforeClass() + { + self::$cacheFile = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheFile); + } + + public function testWriteCacheFileCreatesTheFile() + { + $warmer = new TestCacheWarmer(self::$cacheFile); + $warmer->warmUp(dirname(self::$cacheFile)); + + $this->assertTrue(file_exists(self::$cacheFile)); + } + + /** + * @expectedException \RuntimeException + */ + public function testWriteNonWritableCacheFileThrowsARuntimeException() + { + $nonWritableFile = '/this/file/is/very/probably/not/writable'; + $warmer = new TestCacheWarmer($nonWritableFile); + $warmer->warmUp(dirname($nonWritableFile)); + } +} + +class TestCacheWarmer extends CacheWarmer +{ + protected $file; + + public function __construct($file) + { + $this->file = $file; + } + + public function warmUp($cacheDir) + { + $this->writeCacheFile($this->file, 'content'); + } + + public function isOptional() + { + return false; + } +} From c24a50f839a6163042b1ce73f209bb9b1193ee13 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 6 Feb 2011 04:29:16 -0500 Subject: [PATCH 12/13] [FrameworkBundle] Integrate Configuration\Builder class for config merging and normalization This fixes some BC problems introduced in f9138d313b83f951cf82a2f7f902a2d6dd14fbb3. Some top-level can now be simply enabled by providing true/null in PHP/YAML. Additionally, the Configuration\Builder allows options to be unset by providing "false" (helpful for overriding activation in a previous config file). All options supporting these behaviors can be found in the Configuration.php file (look for canBeUnset() and treatNull/TrueLike()). Major changes: * Removed "enabled" option for profiler config. Profiler is now enabled if its config is true, null or a map. * Restore original config structure for validation namespaces. In PHP/YAML, namespaces are defined under annotations as an alternative to false (disabled) and true/null (enabled). For XML, annotation remains a boolean attribute for validation and a one or more optional namespace tags may appear within . During config normalization, namespace tags under validation will be moved to annotations to conform to the PHP/YAML structure (this occurs transparently to the user). * Restore behavior for router/templating config sections being optional (as shown in changes to session/validation test fixtures). If either top-level section is unset in the configuration, neither feature will be enabled and the user will no longer receive exceptions due to missing a resource option (router) or engines (templating). Resource/engines will still be properly required if the respective feature is enabled. * Remove unused router type option from XML config XSD. Type is only relevant for import statements, so this option is likely useless. Additional small changes: * Added isset()'s, since config options may be unset * Wrap registerXxxConfiguration() calls in isset() checks * Load translation.xml in configLoad(), since it's always required * Default cache_warmer value (!kernel.debug) is determined via Configuration class Things to be fixed: * Configuration\Builder doesn't seem to respect isRequired() and requiresAtLeastOneElement() (or I haven't set it properly); this should replace the need for FrameworkExtension to throw exceptions for bad router/templating configs * The config nodes for session options don't have the "pdo." prefix, as dots are not allowed in node names. To preserve BC for now, the "pdo." prefix is still allowed (and mandated by XSD) in configuration files. In the future, we may just want to do away with the "pdo." prefix. * Translator has an "enabled" option. If there's no use case for setting "fallback" independently (when "enabled" is false), perhaps "enabled" should be removed entirely and translator should function like profiler currently does. * Profiler matcher merging might need to be adjusted so multiple configs simply overwrite matcher instead of merging its array keys. --- .../DependencyInjection/Configuration.php | 220 ++++++++++ .../FrameworkExtension.php | 383 +++++------------- .../Resources/config/schema/symfony-1.0.xsd | 1 - .../Fixtures/php/session_pdo.php | 6 - .../Fixtures/php/validation_annotations.php | 13 +- .../Fixtures/xml/session_pdo.xml | 4 - .../Fixtures/xml/validation_annotations.xml | 4 - .../DependencyInjection/Fixtures/yml/full.yml | 2 +- .../Fixtures/yml/session_pdo.yml | 4 - .../Fixtures/yml/validation_annotations.yml | 10 +- 10 files changed, 339 insertions(+), 308 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..418a7544144cc --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -0,0 +1,220 @@ + + */ +class Configuration +{ + /** + * Generates the configuration tree. + * + * @param boolean $kernelDebug The kernel.debug DIC parameter + * @return \Symfony\Component\DependencyInjection\Configuration\NodeInterface + */ + public function getConfigTree($kernelDebug) + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('app:config', 'array'); + + $rootNode + ->scalarNode('cache_warmer')->defaultValue(!$kernelDebug)->end() + ->scalarNode('charset')->end() + ->scalarNode('document_root')->end() + ->scalarNode('error_handler')->end() + ->scalarNode('ide')->end() + ->booleanNode('test')->end() + ; + + $this->addCsrfProtectionSection($rootNode); + $this->addEsiSection($rootNode); + $this->addProfilerSection($rootNode); + $this->addRouterSection($rootNode); + $this->addSessionSection($rootNode); + $this->addTemplatingSection($rootNode); + $this->addTranslatorSection($rootNode); + $this->addValidationSection($rootNode); + + return $treeBuilder->buildTree(); + } + + private function addCsrfProtectionSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('csrf_protection') + ->canBeUnset() + ->treatNullLike(array('enabled' => true)) + ->treatTrueLike(array('enabled' => true)) + ->booleanNode('enabled')->end() + ->scalarNode('field_name')->end() + ->scalarNode('secret')->end() + ->end() + ; + } + + private function addEsiSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('esi') + ->canBeUnset() + ->treatNullLike(array('enabled' => true)) + ->treatTrueLike(array('enabled' => true)) + ->booleanNode('enabled')->end() + ->end() + ; + } + + private function addProfilerSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('profiler') + ->canBeUnset() + ->treatNullLike(array()) + ->treatTrueLike(array()) + ->booleanNode('only_exceptions')->end() + ->arrayNode('matcher') + ->canBeUnset() + ->scalarNode('ip')->end() + ->scalarNode('path')->end() + ->scalarNode('service')->end() + ->end() + ->end() + ; + } + + private function addRouterSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('router') + ->canBeUnset() + ->scalarNode('cache_warmer')->end() + ->scalarNode('resource')->isRequired()->end() + ->end() + ; + } + + private function addSessionSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('session') + ->canBeUnset() + ->treatNullLike(array()) + ->treatTrueLike(array()) + // Strip "pdo." prefix from option keys, since dots cannot appear in node names + ->beforeNormalization() + ->ifArray() + ->then(function($v){ + foreach ($v as $key => $value) { + if (0 === strncmp('pdo.', $key, 4)) { + $v[substr($key, 4)] = $value; + unset($v[$key]); + } + } + return $v; + }) + ->end() + ->booleanNode('auto_start')->end() + ->scalarNode('class')->end() + ->scalarNode('default_locale')->end() + ->scalarNode('storage_id')->defaultValue('native')->end() + // NativeSessionStorage options + ->scalarNode('name')->end() + ->scalarNode('lifetime')->end() + ->scalarNode('path')->end() + ->scalarNode('domain')->end() + ->booleanNode('secure')->end() + ->booleanNode('httponly')->end() + // PdoSessionStorage options + ->scalarNode('db_table')->end() + ->scalarNode('db_id_col')->end() + ->scalarNode('db_data_col')->end() + ->scalarNode('db_time_col')->end() + ->end() + ; + } + + private function addTemplatingSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('templating') + ->canBeUnset() + ->scalarNode('assets_version')->end() + ->scalarNode('assets_base_urls')->end() + ->scalarNode('cache')->end() + ->scalarNode('cache_warmer')->end() + ->fixXmlConfig('engine') + ->arrayNode('engines') + ->requiresAtLeastOneElement() + ->beforeNormalization() + ->ifTrue(function($v){ return !is_array($v); }) + ->then(function($v){ return array($v); }) + ->end() + ->prototype('scalar') + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && isset($v['id']); }) + ->then(function($v){ return $v['id']; }) + ->end() + ->end() + ->end() + ->fixXmlConfig('loader') + ->arrayNode('loaders') + ->beforeNormalization() + ->ifTrue(function($v){ return !is_array($v); }) + ->then(function($v){ return array($v); }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ; + } + + private function addTranslatorSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('translator') + ->canBeUnset() + ->booleanNode('enabled')->defaultTrue()->end() + ->scalarNode('fallback')->end() + ->end() + ; + } + + private function addValidationSection(NodeBuilder $rootNode) + { + $rootNode + ->arrayNode('validation') + ->canBeUnset() + // For XML, namespace is a child of validation, so it must be moved under annotations + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && !empty($v['annotations']) && !empty($v['namespace']); }) + ->then(function($v){ + $v['annotations'] = array('namespace' => $v['namespace']); + return $v; + }) + ->end() + ->booleanNode('enabled')->end() + ->arrayNode('annotations') + ->canBeUnset() + ->treatNullLike(array()) + ->treatTrueLike(array()) + ->fixXmlConfig('namespace') + ->arrayNode('namespaces') + ->containsNameValuePairsWithKeyAttribute('prefix') + ->prototype('scalar') + ->beforeNormalization() + ->ifTrue(function($v) { return is_array($v) && isset($v['namespace']); }) + ->then(function($v){ return $v['namespace']; }) + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 28606232411b0..411835221d3dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -15,10 +15,10 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Configuration\Processor; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Resource\FileResource; use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpFoundation\RequestMatcher; use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** @@ -43,16 +43,23 @@ public function configLoad(array $configs, ContainerBuilder $container) $loader->load('form.xml'); $loader->load('services.xml'); + // A translator must always be registered (as support is included by + // default in the Form component). If disabled, an identity translator + // will be used and everything will still work as expected. + $loader->load('translation.xml'); + if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); $container->setDefinition('event_dispatcher', $container->findDefinition('debug.event_dispatcher')); $container->setAlias('debug.event_dispatcher', 'event_dispatcher'); } - $config = $this->mergeConfigs($configs); + $processor = new Processor(); + $configuration = new Configuration(); + + $config = $processor->process($configuration->getConfigTree($container->getParameter('kernel.debug')), $configs); - $warmer = isset($config['cache_warmer']) ? $config['cache_warmer'] : !$container->getParameter('kernel.debug'); - $container->setParameter('kernel.cache_warmup', $warmer); + $container->setParameter('kernel.cache_warmup', $config['cache_warmer']); if (isset($config['charset'])) { $container->setParameter('kernel.charset', $config['charset']); @@ -80,19 +87,42 @@ public function configLoad(array $configs, ContainerBuilder $container) $container->setParameter('debug.file_link_format', $pattern); } - if ($config['test']) { + if (isset($config['test']) && $config['test']) { $loader->load('test.xml'); $config['session']['storage_id'] = 'array'; } - $this->registerCsrfProtectionConfiguration($config['csrf_protection'], $container); - $this->registerEsiConfiguration($config['esi'], $loader); - $this->registerProfilerConfiguration($config['profiler'], $container, $loader); - $this->registerRouterConfiguration($config['router'], $container, $loader); - $this->registerSessionConfiguration($config['session'], $container, $loader); - $this->registerTemplatingConfiguration($config['templating'], $container, $loader); - $this->registerTranslatorConfiguration($config['translator'], $container, $loader); - $this->registerValidationConfiguration($config['validation'], $container, $loader); + if (isset($config['csrf_protection'])) { + $this->registerCsrfProtectionConfiguration($config['csrf_protection'], $container); + } + + if (isset($config['esi'])) { + $this->registerEsiConfiguration($config['esi'], $loader); + } + + if (isset($config['profiler'])) { + $this->registerProfilerConfiguration($config['profiler'], $container, $loader); + } + + if (isset($config['router'])) { + $this->registerRouterConfiguration($config['router'], $container, $loader); + } + + if (isset($config['session'])) { + $this->registerSessionConfiguration($config['session'], $container, $loader); + } + + if (isset($config['templating'])) { + $this->registerTemplatingConfiguration($config['templating'], $container, $loader); + } + + if (isset($config['translator'])) { + $this->registerTranslatorConfiguration($config['translator'], $container); + } + + if (isset($config['validation'])) { + $this->registerValidationConfiguration($config['validation'], $container, $loader); + } $this->addClassesToCompile(array( 'Symfony\\Component\\HttpFoundation\\ParameterBag', @@ -119,195 +149,13 @@ public function configLoad(array $configs, ContainerBuilder $container) )); } - /** - * Merges a set of configuration arrays and returns the result. - * - * This method internally specifies the available options and their - * default values. Given an array of configuration arrays, this method - * intelligently merges those configuration values and returns the final, - * flattened product. - * - * @param array $configs An array of configuration arrays to merge - * @return array The merged configuration array - */ - protected function mergeConfigs(array $configs) - { - $defaultOptions = array( - 'cache_warmer' => null, - 'charset' => null, - 'csrf_protection' => array( - 'enabled' => null, - 'field_name' => null, - 'secret' => null, - ), - 'document_root' => null, - 'error_handler' => null, - // TODO: consolidate into a scalar unless future options are planned - 'esi' => array( - 'enabled' => null, - ), - 'ide' => null, - 'profiler' => array( - 'enabled' => false, - 'only_exceptions' => null, - 'matcher' => array( - 'ip' => null, - 'path' => null, - 'service' => null, - ), - ), - 'router' => array( - 'cache_warmer' => null, - 'resource' => null, - ), - 'session' => array( - 'auto_start' => null, - 'class' => null, - 'default_locale' => null, - 'storage_id' => 'native', - // NativeSessionStorage options - 'name' => null, - 'lifetime' => null, - 'path' => null, - 'domain' => null, - 'secure' => null, - 'httponly' => null, - // PdoSessionStorage options - 'pdo.db_table' => null, - 'pdo.db_id_col' => null, - 'pdo.db_data_col' => null, - 'pdo.db_time_col' => null, - ), - 'templating' => array( - 'assets_version' => null, - 'assets_base_urls' => null, - 'cache' => null, - 'cache_warmer' => null, - 'engines' => array(), - 'loaders' => array(), - ), - 'test' => null, - 'translator' => array( - 'enabled' => null, - 'fallback' => null, - ), - 'validation' => array( - 'enabled' => null, - 'annotations' => null, - 'namespaces' => array(), - ), - ); - - $mergedConfig = $defaultOptions; - - foreach ($configs as $config) { - $config = $this->normalizeKeys($config); - - if (isset($config['profiler'])) { - $config['profiler']['enabled'] = true; - } - - if (isset($config['templating']) && is_array($config['templating'])) { - $config['templating']['engines'] = $this->normalizeConfig($config['templating'], 'engine'); - $config['templating']['loaders'] = $this->normalizeConfig($config['templating'], 'loader'); - unset($config['templating']['engine'], $config['templating']['loader']); - } - - if (isset($config['validation']) && is_array($config['validation'])) { - $config['validation']['namespaces'] = $this->normalizeConfig($config['validation'], 'namespace'); - unset($config['validation']['namespace']); - } - - $mergedConfig = $this->mergeOptions($mergedConfig, $config, $defaultOptions); - } - - return $mergedConfig; - } - - /** - * Merges a single level of configuration options. - * - * @param array $current The value of the options before merging - * @param array $new The new values to be merged - * @param array $default The corresponding default values for the option level - * @param string $basePath Base property path for the option level - * @return array The merged options - * @throws InvalidArgumentException When an unsupported is found - */ - protected function mergeOptions(array $current, array $new, array $default, $basePath = null) - { - if ($unsupportedOptions = array_diff_key($new, $default)) { - throw new \InvalidArgumentException('The following options are not supported: '.implode(', ', array_keys($unsupportedOptions))); - } - - foreach ($default as $key => $defaultValue) { - if (array_key_exists($key, $new)) { - $optionPath = $basePath ? $basePath.'.'.$key : $key; - $current[$key] = $this->mergeOptionValue($current[$key], $new[$key], $defaultValue, $optionPath); - } - } - - return $current; - } - - /** - * Merges an option value. - * - * @param mixed $current The value of the option before merging - * @param mixed $new The new value to be merged - * @param mixed $default The corresponding default value for the option - * @param string $optionPath Property path for the option - * @return mixed The merged value - * @throws InvalidArgumentException When an invalid option is found - */ - protected function mergeOptionValue($current, $new, $defaultValue, $optionPath) - { - // Allow profiler.matcher array to be overridden with any value. This - // option requires no merge logic and would not benefit from the type - // validation below. - if ('profiler.matcher' === $optionPath) { - return $new; - } - - // Ensure that the new value's type is an array if expected - if (is_array($defaultValue) && !is_array($new)) { - throw new \InvalidArgumentException(sprintf('Expected array type for option "%s", %s given', $optionPath, gettype($new))); - } - - switch ($optionPath) { - // Engine options are arrays of strings, although XML configurations - // store the engine ID in an attribute. Dedupe after merging. - case 'templating.engines': - $new = array_map(function($engine) { return is_array($engine) ? $engine['id'] : $engine; }, $new); - return array_unique(array_merge($current, $new)); - - // Loader options are arrays of strings, so dedupe after merging - case 'templating.loaders': - return array_unique(array_merge($current, $new)); - - // The namespace options' keys are used for the annotation prefix - // and are significant, so do not dedupe array values. Be mindful - // of XML configurations, which store the prefix in an attribute. - case 'validation.namespaces': - foreach ($new as $prefix => $namespace) { - if (is_array($namespace)) { - $new[$namespace['prefix']] = $namespace['namespace']; - unset($new[$prefix]); - } - } - return array_merge($current, $new); - } - - return is_array($defaultValue) ? $this->mergeOptions($current, $new, $defaultValue, $optionPath) : $new; - } - /** * Loads the CSRF protection configuration. * * @param array $config A CSRF protection configuration array * @param ContainerBuilder $container A ContainerBuilder instance */ - protected function registerCsrfProtectionConfiguration(array $config, ContainerBuilder $container) + private function registerCsrfProtectionConfiguration(array $config, ContainerBuilder $container) { foreach (array('enabled', 'field_name', 'secret') as $key) { if (isset($config[$key])) { @@ -322,9 +170,9 @@ protected function registerCsrfProtectionConfiguration(array $config, ContainerB * @param array $config An ESI configuration array * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerEsiConfiguration(array $config, XmlFileLoader $loader) + private function registerEsiConfiguration(array $config, XmlFileLoader $loader) { - if ($config['enabled']) { + if (isset($config['enabled']) && $config['enabled']) { $loader->load('esi.xml'); } } @@ -336,12 +184,8 @@ protected function registerEsiConfiguration(array $config, XmlFileLoader $loader * @param ContainerBuilder $container A ContainerBuilder instance * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (!$config['enabled']) { - return; - } - $loader->load('profiling.xml'); $loader->load('collectors.xml'); @@ -349,7 +193,7 @@ protected function registerProfilerConfiguration(array $config, ContainerBuilder $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); } - if ($config['matcher']) { + if (isset($config['matcher'])) { if (isset($config['matcher']['service'])) { $container->setAlias('profiler.request_matcher', $config['matcher']['service']); } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path'])) { @@ -373,8 +217,9 @@ protected function registerProfilerConfiguration(array $config, ContainerBuilder * @param array $config A router configuration array * @param ContainerBuilder $container A ContainerBuilder instance * @param XmlFileLoader $loader An XmlFileLoader instance + * @throws \InvalidArgumentException if resource option is not set */ - protected function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { $loader->load('routing.xml'); @@ -384,7 +229,7 @@ protected function registerRouterConfiguration(array $config, ContainerBuilder $ $container->setParameter('routing.resource', $config['resource']); - if ($config['cache_warmer']) { + if (isset($config['cache_warmer']) && $config['cache_warmer']) { $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); $container->setAlias('router', 'router.cached'); } @@ -395,7 +240,7 @@ protected function registerRouterConfiguration(array $config, ContainerBuilder $ 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface', 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - $container->findDefinition('router')->getClass() + $container->findDefinition('router')->getClass(), )); } @@ -406,11 +251,11 @@ protected function registerRouterConfiguration(array $config, ContainerBuilder $ * @param ContainerBuilder $container A ContainerBuilder instance * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerSessionConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerSessionConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { $loader->load('session.xml'); - if ($config['auto_start']) { + if (isset($config['auto_start']) && $config['auto_start']) { $container->getDefinition('session')->addMethodCall('start'); } @@ -425,9 +270,9 @@ protected function registerSessionConfiguration(array $config, ContainerBuilder $container->setAlias('session.storage', 'session.storage.'.$config['storage_id']); $options = $container->getParameter('session.storage.'.$config['storage_id'].'.options'); - foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly', 'pdo.db_table', 'pdo.db_id_col', 'pdo.db_data_col', 'pdo.db_time_col') as $key) { + foreach (array('name', 'lifetime', 'path', 'domain', 'secure', 'httponly', 'db_table', 'db_id_col', 'db_data_col', 'db_time_col') as $key) { if (isset($config[$key])) { - $options[str_replace('pdo.', '', $key)] = $config[$key]; + $options[$key] = $config[$key]; } } $container->setParameter('session.storage.'.$config['storage_id'].'.options', $options); @@ -445,8 +290,9 @@ protected function registerSessionConfiguration(array $config, ContainerBuilder * @param array $config A templating configuration array * @param ContainerBuilder $container A ContainerBuilder instance * @param XmlFileLoader $loader An XmlFileLoader instance + * @throws \LogicException if no engines are defined */ - protected function registerTemplatingConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerTemplatingConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { $loader->load('templating.xml'); $loader->load('templating_php.xml'); @@ -463,7 +309,7 @@ protected function registerTemplatingConfiguration(array $config, ContainerBuild $container->setParameter('templating.assets.base_urls', $config['assets_base_urls']); } - if ($config['loaders']) { + if (isset($config['loaders'])) { $loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']); // Use a deligation unless only a single loader was registered @@ -475,7 +321,7 @@ protected function registerTemplatingConfiguration(array $config, ContainerBuild } } - if ($config['cache']) { + if (isset($config['cache'])) { // Wrap the existing loader with cache (must happen after loaders are registered) $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader')); $container->setDefinition('templating.loader', $container->getDefinition('templating.loader.cache')); @@ -484,12 +330,12 @@ protected function registerTemplatingConfiguration(array $config, ContainerBuild $container->setParameter('templating.loader.cache.path', null); } - if ($config['cache_warmer']) { + if (isset($config['cache_warmer'])) { $container->getDefinition('templating.cache_warmer.template_paths')->addTag('kernel.cache_warmer'); $container->setAlias('templating.locator', 'templating.locator.cached'); } - if (!$config['engines']) { + if (empty($config['engines'])) { throw new \LogicException('You must register at least one templating engine.'); } @@ -523,26 +369,17 @@ protected function registerTemplatingConfiguration(array $config, ContainerBuild $container->getDefinition('templating.engine.delegating')->setArgument(1, $engines); $container->setAlias('templating', 'templating.engine.delegating'); } - - } /** * Loads the translator configuration. * - * A translator must always be registered (as support is included by default - * in the forms component). If disabled, an identity translator will be - * used and everything will still work as expected. - * * @param array $config A translator configuration array * @param ContainerBuilder $container A ContainerBuilder instance - * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerTranslatorConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container) { - $loader->load('translation.xml'); - - if ($config['enabled']) { + if (isset($config['enabled']) && $config['enabled']) { // Use the "real" translator instead of the identity default $container->setDefinition('translator', $container->findDefinition('translator.real')); @@ -585,65 +422,67 @@ protected function registerTranslatorConfiguration(array $config, ContainerBuild * @param ContainerBuilder $container A ContainerBuilder instance * @param XmlFileLoader $loader An XmlFileLoader instance */ - protected function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if ($config['enabled']) { - $loader->load('validator.xml'); + if (empty($config['enabled'])) { + return; + } - $xmlMappingFiles = array(); - $yamlMappingFiles = array(); + $loader->load('validator.xml'); - // Include default entries from the framework - $xmlMappingFiles[] = __DIR__.'/../../../Component/Form/Resources/config/validation.xml'; + $xmlMappingFiles = array(); + $yamlMappingFiles = array(); - foreach ($container->getParameter('kernel.bundles') as $bundle) { - $reflection = new \ReflectionClass($bundle); - if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.xml')) { - $xmlMappingFiles[] = realpath($file); - } - if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.yml')) { - $yamlMappingFiles[] = realpath($file); - } + // Include default entries from the framework + $xmlMappingFiles[] = __DIR__.'/../../../Component/Form/Resources/config/validation.xml'; + + foreach ($container->getParameter('kernel.bundles') as $bundle) { + $reflection = new \ReflectionClass($bundle); + if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.xml')) { + $xmlMappingFiles[] = realpath($file); } + if (file_exists($file = dirname($reflection->getFilename()).'/Resources/config/validation.yml')) { + $yamlMappingFiles[] = realpath($file); + } + } - $xmlFilesLoader = new Definition('%validator.mapping.loader.xml_files_loader.class%', array($xmlMappingFiles)); - $xmlFilesLoader->setPublic(false); + $xmlFilesLoader = new Definition('%validator.mapping.loader.xml_files_loader.class%', array($xmlMappingFiles)); + $xmlFilesLoader->setPublic(false); - $yamlFilesLoader = new Definition('%validator.mapping.loader.yaml_files_loader.class%', array($yamlMappingFiles)); - $yamlFilesLoader->setPublic(false); + $yamlFilesLoader = new Definition('%validator.mapping.loader.yaml_files_loader.class%', array($yamlMappingFiles)); + $yamlFilesLoader->setPublic(false); - $container->setDefinition('validator.mapping.loader.xml_files_loader', $xmlFilesLoader); - $container->setDefinition('validator.mapping.loader.yaml_files_loader', $yamlFilesLoader); + $container->setDefinition('validator.mapping.loader.xml_files_loader', $xmlFilesLoader); + $container->setDefinition('validator.mapping.loader.yaml_files_loader', $yamlFilesLoader); - foreach ($xmlMappingFiles as $file) { - $container->addResource(new FileResource($file)); - } + foreach ($xmlMappingFiles as $file) { + $container->addResource(new FileResource($file)); + } - foreach ($yamlMappingFiles as $file) { - $container->addResource(new FileResource($file)); - } + foreach ($yamlMappingFiles as $file) { + $container->addResource(new FileResource($file)); + } - if ($config['annotations']) { - // Register prefixes for constraint namespaces - if ($namespaces = $config['namespaces']) { - $container->setParameter('validator.annotations.namespaces', array_merge( - $container->getParameter('validator.annotations.namespaces'), - $namespaces - )); - } + if (isset($config['annotations'])) { + // Register prefixes for constraint namespaces + if (!empty($config['annotations']['namespaces'])) { + $container->setParameter('validator.annotations.namespaces', array_merge( + $container->getParameter('validator.annotations.namespaces'), + $config['annotations']['namespaces'] + )); + } - // Register annotation loader - $annotationLoader = new Definition('%validator.mapping.loader.annotation_loader.class%'); - $annotationLoader->setPublic(false); - $annotationLoader->addArgument(new Parameter('validator.annotations.namespaces')); + // Register annotation loader + $annotationLoader = new Definition('%validator.mapping.loader.annotation_loader.class%'); + $annotationLoader->setPublic(false); + $annotationLoader->addArgument(new Parameter('validator.annotations.namespaces')); - $container->setDefinition('validator.mapping.loader.annotation_loader', $annotationLoader); + $container->setDefinition('validator.mapping.loader.annotation_loader', $annotationLoader); - $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain'); - $arguments = $loaderChain->getArguments(); - array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); - $loaderChain->setArguments($arguments); - } + $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain'); + $arguments = $loaderChain->getArguments(); + array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); + $loaderChain->setArguments($arguments); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index a32d10a06650b..689185a4610c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -63,7 +63,6 @@ - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php index edeca35e98dac..eb8deb9d1f01f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/session_pdo.php @@ -1,9 +1,6 @@ loadFromExtension('app', 'config', array( - 'router' => array( - 'resource' => '%kernel.root_dir%/config/routing.xml', - ), 'session' => array( 'storage_id' => 'pdo', 'pdo.db_table' => 'table', @@ -11,7 +8,4 @@ 'pdo.db_data_col' => 'data', 'pdo.db_time_col' => 'time', ), - 'templating' => array( - 'engine' => 'php' - ), )); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php index 90de412f3220b..bed6c991b44d2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_annotations.php @@ -1,17 +1,12 @@ loadFromExtension('app', 'config', array( - 'router' => array( - 'resource' => '%kernel.root_dir%/config/routing.xml', - ), 'validation' => array( 'enabled' => true, - 'annotations' => true, - 'namespaces' => array( - 'app' => 'Application\\Validator\\Constraints\\', + 'annotations' => array( + 'namespaces' => array( + 'app' => 'Application\\Validator\\Constraints\\', + ), ), ), - 'templating' => array( - 'engine' => 'php' - ), )); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml index eeb918e8ccafe..84455afff6d0c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/session_pdo.xml @@ -7,10 +7,6 @@ http://www.symfony-project.org/schema/dic/symfony http://www.symfony-project.org/schema/dic/symfony/symfony-1.0.xsd"> - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml index 13da91605a60c..1af18e148f7f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_annotations.xml @@ -7,12 +7,8 @@ http://www.symfony-project.org/schema/dic/symfony http://www.symfony-project.org/schema/dic/symfony/symfony-1.0.xsd"> - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml index 1aecd8ce48d67..bb8d3bc3bc90e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -28,7 +28,7 @@ app.config: engines: [php, twig] loader: [loader.foo, loader.bar] translator: - enabled: true, + enabled: true fallback: fr validation: enabled: true diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml index ae8f6ad961305..06323d53de9b4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/session_pdo.yml @@ -1,11 +1,7 @@ app.config: - router: - resource: %kernel.root_dir%/config/routing.xml session: storage_id: pdo pdo.db_table: table pdo.db_id_col: id pdo.db_data_col: data pdo.db_time_col: time - templating: - engine: php diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml index 3b029f6b6415b..1ff2545facb94 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_annotations.yml @@ -1,10 +1,6 @@ app.config: - router: - resource: %kernel.root_dir%/config/routing.xml validation: enabled: true - annotations: true - namespaces: - app: Application\Validator\Constraints\ - templating: - engine: php + annotations: + namespaces: + app: Application\Validator\Constraints\ From f3f1d94e24bf07cd5bb04de392b2af0c924d85fb Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Fri, 4 Feb 2011 14:37:01 +0100 Subject: [PATCH 13/13] [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. --- .../DependencyInjection/Configuration.php | 234 ++++++++++ .../Security/Factory/AbstractFactory.php | 44 +- .../Security/Factory/FormLoginFactory.php | 7 +- .../Security/Factory/HttpBasicFactory.php | 9 + .../Security/Factory/HttpDigestFactory.php | 9 + .../Security/Factory/RememberMeFactory.php | 54 ++- .../Factory/SecurityFactoryInterface.php | 3 + .../Security/Factory/X509Factory.php | 9 + .../DependencyInjection/SecurityExtension.php | 427 ++++++------------ .../Resources/config/security_listeners.xml | 12 +- .../Fixtures/php/merge.php | 15 + .../Fixtures/php/merge_import.php | 15 + .../Fixtures/xml/access.xml | 11 +- .../Fixtures/xml/hierarchy.xml | 8 +- .../Fixtures/xml/merge.xml | 20 + .../Fixtures/xml/merge_import.xml | 17 + .../Fixtures/yml/merge.yml | 11 + .../Fixtures/yml/merge_import.yml | 9 + .../Security/Factory/AbstractFactoryTest.php | 9 +- .../SecurityExtensionTest.php | 16 +- .../Configuration/ArrayNode.php | 271 ++++++++++- .../Configuration/BaseNode.php | 113 ++++- .../Configuration/BooleanNode.php | 21 + .../Configuration/Builder/ExprBuilder.php | 36 +- .../Configuration/Builder/MergeBuilder.php | 41 ++ .../Configuration/Builder/NodeBuilder.php | 212 +++++++-- .../Builder/NormalizationBuilder.php | 48 ++ .../Configuration/Builder/TreeBuilder.php | 79 +++- .../Exception/DuplicateKeyException.php | 13 + .../Configuration/Exception/Exception.php | 5 + .../Exception/ForbiddenOverwriteException.php | 13 + .../InvalidConfigurationException.php | 13 + .../Exception/InvalidTypeException.php | 4 +- .../Exception/UnsetKeyException.php | 13 + .../Configuration/NodeInterface.php | 12 + .../Configuration/Processor.php | 26 ++ .../Configuration/ScalarNode.php | 51 ++- .../Configuration/ArrayNodeTest.php | 17 + .../Configuration/FinalizationTest.php | 59 +++ .../Configuration/MergeTest.php | 148 ++++++ .../Configuration/NormalizationTest.php | 8 +- 41 files changed, 1713 insertions(+), 429 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php create mode 100644 src/Symfony/Component/DependencyInjection/Configuration/Processor.php create mode 100644 tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php create mode 100644 tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php create mode 100644 tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..5f48157cb313e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php @@ -0,0 +1,234 @@ + + */ +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() + ; + } +} \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index 860ba9e145b99..88b481e3b1f16 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -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 @@ public function create(ContainerBuilder $container, $id, $config, $userProviderI 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 @@ protected function createEntryPoint($container, $id, $config, $defaultEntryPoint */ 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 @@ protected function createListener($container, $id, $config, $userProvider) 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; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index 6149361fb8871..eef9aecdc4251 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -59,14 +59,11 @@ protected function createAuthProvider(ContainerBuilder $container, $id, $config, 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; diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php index 3686a1e06f1d2..9b2b7a870a864 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -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 @@ public function getKey() { return 'http-basic'; } + + public function addConfiguration(NodeBuilder $builder) + { + $builder + ->scalarNode('provider')->end() + ; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php index d837032e41096..49cf748cf40e9 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpDigestFactory.php @@ -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 @@ public function getKey() { return 'http-digest'; } + + public function addConfiguration(NodeBuilder $builder) + { + $builder + ->scalarNode('provider')->end() + ; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php index e8646e90d09a6..c4727429ab5e3 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php @@ -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 @@ 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 @@ public function create(ContainerBuilder $container, $id, $config, $userProvider, } // 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 @@ public function getKey() { 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); + } + } + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php index a2ec07df051ce..05dcc74f8a4c8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SecurityFactoryInterface.php @@ -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 @@ function create(ContainerBuilder $container, $id, $config, $userProvider, $defau function getPosition(); function getKey(); + + function addConfiguration(NodeBuilder $builder); } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php index dbf0e359148ac..d53f75d978ca8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/X509Factory.php @@ -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 @@ public function getKey() { return 'x509'; } + + public function addConfiguration(NodeBuilder $builder) + { + $builder + ->scalarNode('provider')->end() + ; + } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index edc27e69f21da..ac835082bd1a8 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -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,44 +36,38 @@ 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 configLoad(array $configs, ContainerBuilder $container) + public function __construct() { - foreach ($configs as $config) { - $this->doConfigLoad($this->normalizeKeys($config), $container); - } + $this->configuration = new Configuration(); } - public function aclLoad(array $configs, ContainerBuilder $container) + public function configLoad(array $configs, ContainerBuilder $container) { - foreach ($configs as $config) { - $this->doAclLoad($this->normalizeKeys($config), $container); - } - } + $processor = new Processor(); - /** - * Loads the web configuration. - * - * @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'); - } + // 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']); } - - // session fixation protection if (isset($config['session_fixation_protection'])) { $container->setParameter('security.authentication.session_strategy.strategy', $config['session_fixation_protection']); } @@ -79,97 +75,95 @@ protected function doConfigLoad($config, ContainerBuilder $container) $this->createFirewalls($config, $container); $this->createAuthorization($config, $container); $this->createRoleHierarchy($config, $container); - - return $container; } - protected function createRoleHierarchy($config, ContainerBuilder $container) + public function aclLoad(array $configs, ContainerBuilder $container) { - $roles = array(); - if (isset($config['role_hierarchy'])) { - $roles = $config['role_hierarchy']; + $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($roles['role']) && is_int(key($roles['role']))) { - $roles = $roles['role']; + if (isset($config['cache'])) { + $container->setAlias('security.acl.cache', sprintf('security.acl.cache.%s', $config['cache'])); } + } - $hierarchy = array(); - foreach ($roles as $id => $role) { - if (is_array($role) && isset($role['id'])) { - $id = $role['id']; - } + /** + * Returns the base path for the XSD files. + * + * @return string The XSD base path + */ + public function getXsdValidationBasePath() + { + return __DIR__.'/../Resources/config/schema'; + } - $value = $role; - if (is_array($role) && isset($role['value'])) { - $value = $role['value']; - } + public function getNamespace() + { + return 'http://www.symfony-project.org/schema/dic/security'; + } + + public function getAlias() + { + return 'security'; + } + + /** + * Loads the web configuration. + * + * @param array $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ - $hierarchy[$id] = is_array($value) ? $value : preg_split('/\s*,\s*/', $value); + protected function createRoleHierarchy($config, ContainerBuilder $container) + { + if (!isset($config['role_hierarchy'])) { + return; } - $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($rules['rule']) && is_array($rules['rule'])) { - $rules = $rules['rule']; + if (!isset($config['access_control'])) { + return; } - 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); + 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'] + ); - $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 @@ protected function createFirewalls($config, ContainerBuilder $container) // 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 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ } // Security disabled? - if (isset($firewall['security']) && !$firewall['security']) { + if (false === $firewall['security']) { return array($matcher, array(), null); } @@ -228,9 +214,6 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ 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 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ $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 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ } // 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 @@ protected function createFirewall(ContainerBuilder $container, $id, $firewall, $ $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 @@ protected function createAuthenticationListeners($container, $id, $firewall, $de 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 @@ protected function createAuthenticationListeners($container, $id, $firewall, $de } // Anonymous - if (array_key_exists('anonymous', $firewall)) { + if (isset($firewall['anonymous'])) { $listeners[] = new Reference('security.authentication.listener.anonymous'); $hasListeners = true; } @@ -371,66 +335,14 @@ protected function createAuthenticationListeners($container, $id, $firewall, $de 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 @@ protected function createEncoders($config, ContainerBuilder $container) 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 @@ protected function createEncoder(array $encoderMap, $accountClass, $config, Cont 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 @@ protected function createEncoder(array $encoderMap, $accountClass, $config, Cont // 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 @@ protected function createEncoder(array $encoderMap, $accountClass, $config, Cont return $encoderMap; } - // Parses a tag and returns the id for the related user provider service - protected function createUserDaoProvider($name, $provider, ContainerBuilder $container, $master = true) + // Parses user providers and returns an array of their ids + protected function createUserProviders($config, ContainerBuilder $container) { - if (isset($provider['name'])) { - $name = $provider['name']; + $providerIds = array(); + foreach ($config['providers'] as $name => $provider) { + $id = $this->createUserDaoProvider($name, $provider, $container); + $providerIds[] = $id; } - if (!$name) { - throw new \RuntimeException('You must define a name for each user provider.'); - } + return $providerIds; + } + // Parses a 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) + { $name = $this->getUserProviderId(strtolower($name)); // Existing DAO service provider @@ -533,7 +431,7 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con } // Chain provider - if (isset($provider['provider'])) { + if (count($provider['providers']) > 0) { // FIXME throw new \RuntimeException('Not implemented yet.'); } @@ -546,8 +444,9 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con ->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 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con ->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 @@ protected function createUserDaoProvider($name, $provider, ContainerBuilder $con // 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 @@ protected function createSwitchUserListener($container, $id, $config, $defaultPr $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 @@ protected function createRequestMatcher($container, $path = null, $host = null, 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'); - 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'); + // load user-created listener factories + foreach ($config['factories'] as $factory) { + $loader->load($parameterBag->resolveValue($factory)); } - } - /** - * Returns the base path for the XSD files. - * - * @return string The XSD base path - */ - public function getXsdValidationBasePath() - { - return __DIR__.'/../Resources/config/schema'; - } + $tags = $c->findTaggedServiceIds('security.listener.factory'); - public function getNamespace() - { - return 'http://www.symfony-project.org/schema/dic/security'; - } + $factories = array(); + foreach ($this->listenerPositions as $position) { + $factories[$position] = array(); + } - public function getAlias() - { - return 'security'; + foreach (array_keys($tags) as $tag) { + $factory = $c->get($tag); + $factories[$factory->getPosition()][] = $factory; + } + + return $this->factories = $factories; } } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 3245168813088..f36a6c461a7ea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -28,12 +28,8 @@ Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener Symfony\Component\Security\Http\Firewall\SwitchUserListener - ROLE_ALLOWED_TO_SWITCH - _switch_user Symfony\Component\Security\Http\Firewall\LogoutListener - /logout - / Symfony\Component\Security\Http\Logout\SessionLogoutHandler Symfony\Component\Security\Http\Logout\CookieClearingLogoutHandler @@ -83,14 +79,12 @@ - + - %security.logout.path% - %security.logout.target_path% @@ -162,13 +156,11 @@ - + - %security.authentication.switchuser.parameter% - %security.authentication.switchuser.role% diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php new file mode 100644 index 0000000000000..988640fb284dc --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge.php @@ -0,0 +1,15 @@ +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'), + ) +)); \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php new file mode 100644 index 0000000000000..2b9be399d77c7 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/merge_import.php @@ -0,0 +1,15 @@ +loadFromExtension('security', 'config', array( + 'firewalls' => array( + 'main' => array( + 'form_login' => array( + 'login_path' => '/login', + ) + ) + ), + 'role_hierarchy' => array( + 'FOO' => 'BAR', + 'ADMIN' => 'USER', + ), +)); \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml index c01acd61013eb..2ca9f5b9d0c7d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access.xml @@ -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"> - - - - - - - + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml index 795105230cd04..4c8985a79157a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/hierarchy.xml @@ -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"> - - ROLE_USER - ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH - ROLE_USER,ROLE_ADMIN - + ROLE_USER + ROLE_USER,ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH + ROLE_USER,ROLE_ADMIN diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml new file mode 100644 index 0000000000000..36f7b4de7208f --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml new file mode 100644 index 0000000000000..806719ab5fa07 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/merge_import.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml new file mode 100644 index 0000000000000..a42fc99fab00c --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge.yml @@ -0,0 +1,11 @@ +imports: + - { resource: merge_import.yml } + +security.config: + firewalls: + main: + form_login: false + http_basic: ~ + + role_hierarchy: + FOO: [MOO] \ No newline at end of file diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml new file mode 100644 index 0000000000000..497fb398c770c --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/merge_import.yml @@ -0,0 +1,9 @@ +security.config: + firewalls: + main: + form_login: + login_path: /login + + role_hierarchy: + FOO: BAR + ADMIN: USER diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php index 9d6d1390ee062..c412ce0e30d00 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/AbstractFactoryTest.php @@ -28,7 +28,7 @@ public function testCreate() 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 @@ public function testCreate() $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()); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index d76f505d93952..73df0096914ea 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -36,10 +36,10 @@ public function testUserProviders() $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 @@ public function testAccess() } } + 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(); diff --git a/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php b/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php index 9a9fc165f8716..1b9cbdfdc1291 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php @@ -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 + */ 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 @@ public function setName($name) $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 @@ public function addChild(NodeInterface $node) $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 @@ protected function validateType($value) 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 @@ protected function normalizeValue($value) 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 @@ protected function normalizeValue($value) 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; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php b/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php index 69f5847fbcd74..b2a78e9e910d5 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/BaseNode.php @@ -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 + */ 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 @@ public function __construct($name, NodeInterface $parent = null, $beforeTransfor $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 @@ public function getPath() 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); + } + + public final function finalize($value) + { + $this->validateType($value); + + $value = $this->finalizeValue($value); - // run after transformations - foreach ($this->afterTransformations as $transformation) { - $value = $transformation($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 @@ public final function normalize($value) abstract protected function validateType($value); abstract protected function normalizeValue($value); + abstract protected function mergeValues($leftSide, $rightSide); + abstract protected function finalizeValue($value); } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php b/src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php new file mode 100644 index 0000000000000..3960e0e756b5c --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/BooleanNode.php @@ -0,0 +1,21 @@ +getPath(), + json_encode($value) + )); + } + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php index c8f2e0cbdd848..1e29a6ed72778 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/ExprBuilder.php @@ -2,6 +2,11 @@ namespace Symfony\Component\DependencyInjection\Configuration\Builder; +/** + * This class builds an if expression. + * + * @author Johannes M. Schmitt + */ class ExprBuilder { public $parent; @@ -13,8 +18,12 @@ public function __construct($parent) $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 @@ public function then(\Closure $closure) 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) { diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php new file mode 100644 index 0000000000000..5ac10001dbd44 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/MergeBuilder.php @@ -0,0 +1,41 @@ + + */ +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; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php index 0c63068d71433..aa19de0e61139 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NodeBuilder.php @@ -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 + */ 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 @@ public function __construct($name, $type, $parent = null) $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 @@ public function __construct($name, $type, $parent = null) 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'); + } + + public function scalarNode($name) + { + return $this->node($name, 'scalar'); + } + + public function booleanNode($name) + { + return $this->node($name, 'boolean'); + } - $this->normalizeTransformations[] = array($key, $plural); + public function defaultValue($value) + { + $this->default = true; + $this->defaultValue = $value; return $this; } - public function key($name) + public function isRequired() { - $this->key = $name; + $this->required = true; + + return $this; + } + + public function containsNameValuePairsWithKeyAttribute($attribute) + { + $this->beforeNormalization() + ->ifArray() + ->thenReplaceKeyWithAttribute($attribute) + ; + + $this->useAttributeAsKey($attribute); return $this; } - public function before(\Closure $closure = null) + public function requiresAtLeastOneElement() { - if (null !== $closure) { - $this->beforeTransformations[] = $closure; + $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); + } - return $this; + 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->beforeTransformations[] = new ExprBuilder($this); + return $this->normalization; } - public function prototype($type) + public function beforeNormalization() { - return $this->prototype = new NodeBuilder(null, $type, $this); + return $this->normalization()->before(); } - public function after(\Closure $closure = null) + public function fixXmlConfig($singular, $plural = null) { - if (null !== $closure) { - $this->afterTransformations[] = $closure; + $this->normalization()->remap($singular, $plural); - return $this; + return $this; + } + + public function useAttributeAsKey($name) + { + $this->key = $name; + + return $this; + } + + protected function merge() + { + if (null === $this->merge) { + $this->merge = new MergeBuilder($this); } - return $this->afterTransformations[] = 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 static(null, $type, $this); + } + + public function performNoDeepMerging() + { + $this->performDeepMerging = false; + + return $this; } public function end() diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php new file mode 100644 index 0000000000000..96e76cb586d57 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/NormalizationBuilder.php @@ -0,0 +1,48 @@ + + */ +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); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php b/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php index 93f48bdf1936b..e24a23cb4dce5 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Builder/TreeBuilder.php @@ -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 + */ class TreeBuilder { protected $root; @@ -32,9 +41,6 @@ public function buildTree() 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 @@ protected function createConfigNode(NodeBuilder $node) 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; diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php new file mode 100644 index 0000000000000..7da500ba5693f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/DuplicateKeyException.php @@ -0,0 +1,13 @@ + + */ +class DuplicateKeyException extends InvalidConfigurationException +{ +} diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php index e5a464b27694d..c669089f472b4 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/Exception.php @@ -2,6 +2,11 @@ namespace Symfony\Component\DependencyInjection\Configuration\Exception; +/** + * Base exception for all configuration exceptions + * + * @author Johannes M. Schmitt + */ class Exception extends \RuntimeException { } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php new file mode 100644 index 0000000000000..0f7537747ec31 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/ForbiddenOverwriteException.php @@ -0,0 +1,13 @@ + + */ +class ForbiddenOverwriteException extends InvalidConfigurationException +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php new file mode 100644 index 0000000000000..71f3ffbd5282e --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidConfigurationException.php @@ -0,0 +1,13 @@ + + */ +class InvalidConfigurationException extends Exception +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php index 436c80fa80add..3cdbc52439c58 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/InvalidTypeException.php @@ -5,8 +5,8 @@ /** * This exception is thrown if an invalid type is encountered. * - * @author johannes + * @author Johannes M. Schmitt */ -class InvalidTypeException extends Exception +class InvalidTypeException extends InvalidConfigurationException { } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php b/src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php new file mode 100644 index 0000000000000..2388b134b42bb --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Exception/UnsetKeyException.php @@ -0,0 +1,13 @@ + + */ +class UnsetKeyException extends Exception +{ +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php b/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php index a5e8611c637d1..70271946b46b5 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/NodeInterface.php @@ -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 + */ interface NodeInterface { function getName(); function getPath(); + function isRequired(); + function hasDefaultValue(); + function getDefaultValue(); function normalize($value); + function merge($leftSide, $rightSide); } \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/Processor.php b/src/Symfony/Component/DependencyInjection/Configuration/Processor.php new file mode 100644 index 0000000000000..cdabd29a030fe --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Configuration/Processor.php @@ -0,0 +1,26 @@ + + */ +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); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php index cdcebe3dac6a7..fd871c3864590 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php @@ -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 + */ 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 @@ public function setName($name) 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 @@ protected function validateType($value) } } + 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; + } } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php new file mode 100644 index 0000000000000..da3155493e241 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ArrayNodeTest.php @@ -0,0 +1,17 @@ +normalize(false); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php new file mode 100644 index 0000000000000..ab3f5af12babe --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/FinalizationTest.php @@ -0,0 +1,59 @@ +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); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php new file mode 100644 index 0000000000000..0fab77cc11286 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php @@ -0,0 +1,148 @@ +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)); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php index 4afbdb9eeeb86..9254700eb63ff 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/NormalizationTest.php @@ -15,11 +15,11 @@ public function testNormalizeEncoders($denormalized) $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 @@ public function testAnonymousKeysArray($denormalized) $tree = $tb ->root('root', 'array') ->node('logout', 'array') - ->normalize('handler') + ->fixXmlConfig('handler') ->node('handlers', 'array') ->prototype('scalar')->end() ->end()