From 199e6bf893d2fb7f138e4e4d0a44e78e583eca47 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 5 Feb 2011 16:02:59 +0100 Subject: [PATCH 01/67] [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/67] [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/67] [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/67] [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/67] [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/67] 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/67] [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/67] 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/67] [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/67] [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/67] [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 4f6256b713a1fca105cce58d51ba96f2351a2a85 Mon Sep 17 00:00:00 2001 From: Kris Wallsmith Date: Sun, 6 Feb 2011 06:41:19 -0800 Subject: [PATCH 12/67] [FrameworkBundle] fixed invalid template filename --- .../Resources/views/Form/{url_field.php => url_field.html.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/{url_field.php => url_field.html.php} (100%) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.html.php similarity index 100% rename from src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.php rename to src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/url_field.html.php From c251a369351582324bf1afdd13b61ae01c3163a7 Mon Sep 17 00:00:00 2001 From: John Kary Date: Sat, 5 Feb 2011 22:42:19 -0600 Subject: [PATCH 13/67] [HttpFoundation] Add tests for Cookie --- .../Component/HttpFoundation/CookieTest.php | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php diff --git a/tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php b/tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php new file mode 100644 index 0000000000000..7ab76a4c1291c --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/CookieTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Cookie; + +/** + * CookieTest + * + * @author John Kary + */ +class CookieTest extends \PHPUnit_Framework_TestCase +{ + public function invalidNames() + { + return array( + array(''), + array(",MyName"), + array(";MyName"), + array(" MyName"), + array("\tMyName"), + array("\rMyName"), + array("\nMyName"), + array("\013MyName"), + array("\014MyName"), + ); + } + + public function invalidValues() + { + return array( + array(",MyValue"), + array(";MyValue"), + array(" MyValue"), + array("\tMyValue"), + array("\rMyValue"), + array("\nMyValue"), + array("\013MyValue"), + array("\014MyValue"), + ); + } + + /** + * @dataProvider invalidNames + * @expectedException InvalidArgumentException + * @covers Symfony\Component\HttpFoundation\Cookie::__construct + */ + public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) + { + new Cookie($name); + } + + /** + * @dataProvider invalidValues + * @expectedException InvalidArgumentException + * @covers Symfony\Component\HttpFoundation\Cookie::__construct + */ + public function testInstantiationThrowsExceptionIfCookieValueContainsInvalidCharacters($value) + { + new Cookie('MyCookie', $value); + } + + /** + * @covers Symfony\Component\HttpFoundation\Cookie::getValue + */ + public function testGetValue() + { + $value = 'MyValue'; + $cookie = new Cookie('MyCookie', $value); + + $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value'); + } +} From 9ba2943aff2941c283065e0f09c05a572462fa50 Mon Sep 17 00:00:00 2001 From: ornicar Date: Sat, 5 Feb 2011 17:09:52 -0800 Subject: [PATCH 14/67] [HttpKernel] Add unit tests for Kernel. Also slightly modify Kernel to make it more testable. --- src/Symfony/Component/HttpKernel/Kernel.php | 22 +- .../Tests/Component/HttpKernel/KernelTest.php | 197 +++++++++++++++++- 2 files changed, 208 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index da64b790a76f0..5eb169931b70c 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -97,7 +97,7 @@ public function boot() // init container $this->initializeContainer(); - foreach ($this->bundles as $bundle) { + foreach ($this->getBundles() as $bundle) { $bundle->setContainer($this->container); $bundle->boot(); } @@ -114,7 +114,7 @@ public function shutdown() { $this->booted = false; - foreach ($this->bundles as $bundle) { + foreach ($this->getBundles() as $bundle) { $bundle->shutdown(); $bundle->setContainer(null); } @@ -131,7 +131,17 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ $this->boot(); } - return $this->container->get('http_kernel')->handle($request, $type, $catch); + return $this->getHttpKernel()->handle($request, $type, $catch); + } + + /** + * Gets a http kernel from the container + * + * @return HttpKernel + */ + protected function getHttpKernel() + { + return $this->container->get('http_kernel'); } /** @@ -343,7 +353,7 @@ protected function initializeBundles() $this->bundles = array(); $topMostBundles = array(); $directChildren = array(); - + foreach ($this->registerBundles() as $bundle) { $name = $bundle->getName(); if (isset($this->bundles[$name])) { @@ -358,7 +368,7 @@ protected function initializeBundles() $directChildren[$parentName] = $name; } else { $topMostBundles[$name] = $bundle; - } + } } // look for orphans @@ -377,7 +387,7 @@ protected function initializeBundles() array_unshift($bundleMap, $this->bundles[$name]); $hierarchy[] = $name; } - + foreach ($hierarchy as $bundle) { $this->bundleMap[$bundle] = $bundleMap; array_pop($bundleMap); diff --git a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php index a97eb74aeb926..c6c11038d2d14 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php @@ -14,9 +14,191 @@ use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\DependencyInjection\Loader\LoaderInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; class KernelTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $this->assertEquals($env, $kernel->getEnvironment()); + $this->assertEquals($debug, $kernel->isDebug()); + $this->assertFalse($kernel->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $kernel->getStartTime()); + $this->assertNull($kernel->getContainer()); + } + + public function testClone() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $clone = clone $kernel; + + $this->assertEquals($env, $clone->getEnvironment()); + $this->assertEquals($debug, $clone->isDebug()); + $this->assertFalse($clone->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $clone->getStartTime()); + $this->assertNull($clone->getContainer()); + } + + public function testBootInitializesBundlesAndContainer() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('initializeBundles'); + $kernel->expects($this->once()) + ->method('initializeContainer'); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + } + + public function testBootSetsTheContainerToTheBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('setContainer'); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + } + + public function testBootSetsTheBootedFlagToTrue() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + + $this->assertTrue($kernel->isBooted()); + } + + public function testShutdownCallsShutdownOnAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('setContainer') + ->with(null); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->shutdown(); + } + + public function testHandleCallsHandleOnHttpKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->once()) + ->method('handle') + ->with($request, $type, $catch); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->handle($request, $type, $catch); + } + + public function testStripComments() + { + if (!function_exists('token_get_all')) { + $this->markTestSkipped(); + return; + } + $source = <<assertEquals($expected, Kernel::stripComments($source)); + } + /** * @expectedException \InvalidArgumentException */ @@ -241,14 +423,14 @@ public function testInitializeBundleThrowsExceptionWhenRegisteringTwoBundlesWith { $fooBundle = $this->getBundle(null, null, 'FooBundle', 'DuplicateName'); $barBundle = $this->getBundle(null, null, 'BarBundle', 'DuplicateName'); - + $kernel = $this->getKernel(); $kernel ->expects($this->once()) ->method('registerBundles') ->will($this->returnValue(array($fooBundle, $barBundle))) ; - $kernel->initializeBundles(); + $kernel->initializeBundles(); } protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) @@ -276,13 +458,13 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu ->method('getPath') ->will($this->returnValue(strtr($dir, '\\', '/'))) ; - + $bundle ->expects($this->any()) ->method('getParent') ->will($this->returnValue($parent)) ; - + return $bundle; } @@ -333,9 +515,14 @@ public function initializeBundles() { parent::initializeBundles(); } + + public function isBooted() + { + return $this->booted; + } } abstract class BundleForTest implements BundleInterface { // We can not extend Symfony\Component\HttpKernel\Bundle\Bundle as we want to mock getName() which is final -} \ No newline at end of file +} From f08c9480ee28dffca6929105442b642a6523409d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Nahuel=20Cuesta=20Luengo?= Date: Sat, 5 Feb 2011 14:52:16 -0800 Subject: [PATCH 15/67] [CssSelector] Fixed PHPDoc blocks. --- src/Symfony/Component/CssSelector/Parser.php | 12 ++++++------ src/Symfony/Component/CssSelector/Token.php | 2 +- src/Symfony/Component/CssSelector/TokenStream.php | 2 +- src/Symfony/Component/CssSelector/Tokenizer.php | 10 +++++----- src/Symfony/Component/CssSelector/XPathExpr.php | 12 ++++++------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Component/CssSelector/Parser.php b/src/Symfony/Component/CssSelector/Parser.php index 4e80fc6098d4f..612e556d2607b 100644 --- a/src/Symfony/Component/CssSelector/Parser.php +++ b/src/Symfony/Component/CssSelector/Parser.php @@ -25,7 +25,7 @@ class Parser { /** - * Translate a CSS expression to its XPath equivalent. + * Translates a CSS expression to its XPath equivalent. * Optionally, a prefix can be added to the resulting XPath * expression with the $prefix parameter. * @@ -71,7 +71,7 @@ static public function cssToXpath($cssExpr, $prefix = 'descendant-or-self::') } /** - * Parse an expression and return the Node object that represents + * Parses an expression and returns the Node object that represents * the parsed expression. * * @throws \Exception When tokenizer throws it while parsing @@ -96,7 +96,7 @@ public function parse($string) } /** - * Parse a selector group contained in $stream and return + * Parses a selector group contained in $stream and returns * the Node object that represents the expression. * * @param TokenStream $stream The stream to parse. @@ -123,7 +123,7 @@ protected function parseSelectorGroup($stream) } /** - * Parse a selector contained in $stream and return the Node + * Parses a selector contained in $stream and returns the Node * object that represents it. * * @throws SyntaxError When expected selector but got something else @@ -159,7 +159,7 @@ protected function parseSelector($stream) } /** - * Parse a simple selector (the current token) from $stream and return + * Parses a simple selector (the current token) from $stream and returns * the resulting Node object. * * @throws SyntaxError When expected symbol but got something else @@ -266,7 +266,7 @@ protected function parseSimpleSelector($stream) } /** - * Parse an attribute from a selector contained in $stream and return + * Parses an attribute from a selector contained in $stream and returns * the resulting AttribNode object. * * @throws SyntaxError When encountered unexpected selector diff --git a/src/Symfony/Component/CssSelector/Token.php b/src/Symfony/Component/CssSelector/Token.php index 176617ac77850..7f1fdfb4f291d 100644 --- a/src/Symfony/Component/CssSelector/Token.php +++ b/src/Symfony/Component/CssSelector/Token.php @@ -50,7 +50,7 @@ public function __toString() } /** - * Answer whether this token's type equals to $type. + * Answers whether this token's type equals to $type. * * @param string $type The type to test against this token's one. * diff --git a/src/Symfony/Component/CssSelector/TokenStream.php b/src/Symfony/Component/CssSelector/TokenStream.php index c6e62f312d6fb..bbfbd88213a8f 100644 --- a/src/Symfony/Component/CssSelector/TokenStream.php +++ b/src/Symfony/Component/CssSelector/TokenStream.php @@ -79,7 +79,7 @@ public function next() } /** - * Peek for the next token in this stream. This means that the next token + * Peeks 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. diff --git a/src/Symfony/Component/CssSelector/Tokenizer.php b/src/Symfony/Component/CssSelector/Tokenizer.php index 5980f5a9f3eea..d6f934f5f5f96 100644 --- a/src/Symfony/Component/CssSelector/Tokenizer.php +++ b/src/Symfony/Component/CssSelector/Tokenizer.php @@ -22,7 +22,7 @@ class Tokenizer { /** - * Take a CSS selector and return an array holding the Tokens + * Takes a CSS selector and returns an array holding the Tokens * it contains. * * @param string $s The selector to lex. @@ -103,8 +103,8 @@ 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 + * Tokenizes a quoted string (i.e. 'A string quoted with \' characters'), + * and returns 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 @@ -142,7 +142,7 @@ protected function tokenizeEscapedString($s, $pos) } /** - * Unescape a string literal and return the unescaped string. + * Unescapes a string literal and returns the unescaped string. * * @throws SyntaxError When invalid escape sequence is found * @@ -166,7 +166,7 @@ protected function unescapeStringLiteral($literal) } /** - * Lex selector $s and return the an array holding the name of the symbol + * Lexes selector $s and returns an array holding the name of the symbol * contained in it and the new position from which tokenizing should take * over. * diff --git a/src/Symfony/Component/CssSelector/XPathExpr.php b/src/Symfony/Component/CssSelector/XPathExpr.php index cd32a8cc43b34..d5a54caf06307 100644 --- a/src/Symfony/Component/CssSelector/XPathExpr.php +++ b/src/Symfony/Component/CssSelector/XPathExpr.php @@ -66,7 +66,7 @@ public function getPath() } /** - * Answer whether this XPath expression has a star prefix. + * Answers whether this XPath expression has a star prefix. * * @return bool */ @@ -121,7 +121,7 @@ public function __toString() } /** - * Add a condition to this XPath expression. + * Adds a condition to this XPath expression. * Any pre-existant condition will be ANDed to it. * * @param string $condition The condition to add. @@ -136,7 +136,7 @@ public function addCondition($condition) } /** - * Add a prefix to this XPath expression. + * Adds a prefix to this XPath expression. * It will be prepended to any pre-existant prefixes. * * @param string $prefix The prefix to add. @@ -151,7 +151,7 @@ public function addPrefix($prefix) } /** - * Add a condition to this XPath expression using the name of the element + * Adds a condition to this XPath expression using the name of the element * as the desired value. * This method resets the element to '*'. */ @@ -167,7 +167,7 @@ public function addNameTest() } /** - * Add a star prefix to this XPath expression. + * Adds a star prefix to this XPath expression. * This method will prepend a '*' to the path and set the star prefix flag * to true. */ @@ -187,7 +187,7 @@ public function addStarPrefix() } /** - * Join this XPath expression with $other (another XPath expression) using + * Joins this XPath expression with $other (another XPath expression) using * $combiner to join them. * * @param string $combiner The combiner string. From 39ed62de46718af993e06a99d5e5f1ddfd7a8cbf Mon Sep 17 00:00:00 2001 From: hidenorigoto Date: Mon, 7 Feb 2011 01:04:43 +0900 Subject: [PATCH 16/67] translated validators resources into Japanese --- .../translations/validators.ja_JP.xliff | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff new file mode 100644 index 0000000000000..cabba0c0d2f69 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.ja_JP.xliff @@ -0,0 +1,131 @@ + + + + + + This value should be false + 値はfalseでなければなりません + + + This value should be true + 値はtrueでなければなりません + + + This value should be of type {{ type }} + 値の型は{{ type }}でなければなりません + + + This value should be blank + 値は空でなければなりません + + + This value should be one of the given choices + 値は指定された選択肢のうちの1つでなければなりません + + + You should select at least {{ limit }} choices + {{ limit }}個以上選択してください + + + You should select at most {{ limit }} choices + {{ limit }}個以内で選択してください + + + The fields {{ fields }} were not expected + フィールド{{ fields }}は無効です + + + The fields {{ fields }} are missing + フィールド{{ fields }}は必須です + + + This value is not a valid date + 値が有効な日付ではありません + + + This value is not a valid datetime + 値が有効な日時ではありません + + + This value is not a valid email address + 値が有効なメールアドレスではありません + + + The file could not be found + ファイルが見つかりません + + + The file is not readable + ファイルを読み込めません + + + The file is too large ({{ size }}). Allowed maximum size is {{ limit }} + ファイルのサイズが大きすぎます({{ size }})。有効な最大サイズは{{ limit }}です + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }} + ファイルのMIMEタイプが無効です({{ type }})。有効なMIMEタイプは{{ types }}です + + + This value should be {{ limit }} or less + 値は{{ limit }}以下でなければなりません + + + This value is too long. It should have {{ limit }} characters or less + 値が長すぎます。{{ limit }}文字以内でなければなりません + + + This value should be {{ limit }} or more + 値は{{ limit }}以上でなければなりません + + + This value is too short. It should have {{ limit }} characters or more + 値が短すぎます。{{ limit }}文字以上でなければなりません + + + This value should not be blank + 値が空であってはなりません + + + This value should not be null + 値がnullであってはなりません + + + This value should be null + 値はnullでなければなりません + + + This value is not valid + 値が無効です + + + This value is not a valid time + 値が有効な時刻ではありません + + + This value is not a valid URL + 値が有効なURLではありません + + + This value should be instance of class {{ class }} + 値は{{ class }}のインスタンスでなければなりません + + + This field group should not contain extra fields + フィールドグループに追加のフィールドを含んではなりません + + + The uploaded file was too large. Please try to upload a smaller file + アップロードされたファイルが大きすぎます。小さなファイルで再度アップロードしてください + + + The CSRF token is invalid + CSRFトークンが無効です + + + The two values should be equal + 2つの値が同じでなければなりません + + + + From 42a3e404b28def5e85a1912412a6eb2311cbaf48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Egu=C3=ADluz?= Date: Sun, 6 Feb 2011 17:49:45 +0100 Subject: [PATCH 17/67] translated validators resources into Spanish --- .../translations/validators.es.xliff | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.es.xliff diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.es.xliff b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.es.xliff new file mode 100644 index 0000000000000..96e2fc6063498 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.es.xliff @@ -0,0 +1,131 @@ + + + + + + This value should be false + Este valor debería ser falso + + + This value should be true + Este valor debería ser verdadero + + + This value should be of type {{ type }} + Este valor debería ser de tipo {{ type }} + + + This value should be blank + Este valor debería estar vacío + + + This value should be one of the given choices + Seleccione este valor entre las opciones mostradas + + + You should select at least {{ limit }} choices + Debería seleccionar al menos {{ limit }} opciones + + + You should select at most {{ limit }} choices + Debería seleccionar como máximo {{ limit }} opciones + + + The fields {{ fields }} were not expected + No se esperaban los campos {{ fields }} + + + The fields {{ fields }} are missing + Faltan los campos {{ fields }} + + + This value is not a valid date + Este valor no es una fecha válida + + + This value is not a valid datetime + Este valor no es una fecha y hora válidas + + + This value is not a valid email address + Este valor no es una dirección de email válida + + + The file could not be found + No se pudo encontrar el archivo + + + The file is not readable + No se puede leer el archivo + + + The file is too large ({{ size }}). Allowed maximum size is {{ limit }} + El archivo es demasiado grande ({{ size }}). El tamaño máximo permitido es {{ limit }} + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }} + El tipo mime del archivo no es válido ({{ type }}). Los tipos mime válidos son {{ types }} + + + This value should be {{ limit }} or less + Este valor debería ser {{ limit }} o menos + + + This value is too long. It should have {{ limit }} characters or less + Este valor es demasiado largo. Debería tener {{ limit }} caracteres o menos + + + This value should be {{ limit }} or more + Este valor debería ser {{ limit }} o más + + + This value is too short. It should have {{ limit }} characters or more + Este valor es demasiado corto. Debería tener {{ limit }} caracteres o más + + + This value should not be blank + Este valor no debería estar vacío + + + This value should not be null + Este valor no debería ser null + + + This value should be null + Este valor debería ser null + + + This value is not valid + Este valor no es válido + + + This value is not a valid time + Este valor no es una hora válida + + + This value is not a valid URL + Este valor no es una URL válida + + + This value should be instance of class {{ class }} + Este valor debería ser una instancia de la clase {{ class }} + + + This field group should not contain extra fields + Este grupo de campos no debería contener campos adicionales + + + The uploaded file was too large. Please try to upload a smaller file + El archivo subido es demasiado grande. Por favor, suba un archivo más pequeño + + + The CSRF token is invalid + El token CSRF no es válido + + + The two values should be equal + Los dos valores deberían ser iguales + + + + From 6ff41207843154c1f5546de54755e56bc9d9efd5 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Sun, 6 Feb 2011 18:04:20 +0100 Subject: [PATCH 18/67] [Form] Added Form option "by_reference" so that objects received from parent forms are modified by reference when this option is true (the default) The implication is that set() in the object of the parent form will not be called (and thus not has to be implemented/public). If you want to suppress this behaviour, manually set "by_reference" to false. --- src/Symfony/Component/Form/DateField.php | 13 ++ src/Symfony/Component/Form/DateTimeField.php | 13 ++ src/Symfony/Component/Form/Form.php | 16 +++ src/Symfony/Component/Form/TimeField.php | 13 ++ .../Symfony/Tests/Component/Form/FormTest.php | 118 ++++++++++++++++++ 5 files changed, 173 insertions(+) diff --git a/src/Symfony/Component/Form/DateField.php b/src/Symfony/Component/Form/DateField.php index 3330bad81f765..f08c4733e976f 100644 --- a/src/Symfony/Component/Form/DateField.php +++ b/src/Symfony/Component/Form/DateField.php @@ -89,6 +89,19 @@ class DateField extends HybridField */ protected $formatter; + /** + * {@inheritDoc} + */ + public function __construct($key, array $options = array()) + { + // Override parent option + // \DateTime objects are never edited by reference, because + // we treat them like value objects + $this->addOption('by_reference', false); + + parent::__construct($key, $options); + } + protected function configure() { $this->addOption('widget', self::CHOICE, self::$widgets); diff --git a/src/Symfony/Component/Form/DateTimeField.php b/src/Symfony/Component/Form/DateTimeField.php index 3002b7f2bfb7c..5f9d96ed8d13e 100644 --- a/src/Symfony/Component/Form/DateTimeField.php +++ b/src/Symfony/Component/Form/DateTimeField.php @@ -75,6 +75,19 @@ class DateTimeField extends Form TimeField::INPUT, ); + /** + * {@inheritDoc} + */ + public function __construct($key, array $options = array()) + { + // Override parent option + // \DateTime objects are never edited by reference, because + // we treat them like value objects + $this->addOption('by_reference', false); + + parent::__construct($key, $options); + } + protected function configure() { $this->addOption('date_widget', DateField::CHOICE, self::$dateWidgets); diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 820772bd1bbd3..6b0886bf826dd 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -93,6 +93,7 @@ public function __construct($name = null, array $options = array()) $this->addOption('virtual', false); $this->addOption('validator'); $this->addOption('context'); + $this->addOption('by_reference', true); if (isset($options['validation_groups'])) { $options['validation_groups'] = (array)$options['validation_groups']; @@ -880,6 +881,21 @@ public function validateData(ExecutionContext $context) } } + /** + * {@inheritDoc} + */ + public function writeProperty(&$objectOrArray) + { + $data = $this->getData(); + + // Don't update parent if data is a composite type (object or array) + // and "by_reference" option is true, because then we expect that + // we are working with a reference to the parent's data + if (!(is_object($data) || is_array($data)) || !$this->getOption('by_reference')) { + parent::writeProperty($objectOrArray); + } + } + /** * Merges two arrays without reindexing numeric keys. * diff --git a/src/Symfony/Component/Form/TimeField.php b/src/Symfony/Component/Form/TimeField.php index 3f221db2b4bf3..a8961fe7aa830 100644 --- a/src/Symfony/Component/Form/TimeField.php +++ b/src/Symfony/Component/Form/TimeField.php @@ -59,6 +59,19 @@ class TimeField extends Form self::RAW, ); + /** + * {@inheritDoc} + */ + public function __construct($key, array $options = array()) + { + // Override parent option + // \DateTime objects are never edited by reference, because + // we treat them like value objects + $this->addOption('by_reference', false); + + parent::__construct($key, $options); + } + /** * {@inheritDoc} */ diff --git a/tests/Symfony/Tests/Component/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php index 128fd50f6437d..e23cc3212dff7 100644 --- a/tests/Symfony/Tests/Component/Form/FormTest.php +++ b/tests/Symfony/Tests/Component/Form/FormTest.php @@ -41,6 +41,59 @@ protected function configure() } } +// behaves like a form with a value transformer that transforms into +// a specific format +class FormTest_FormThatReturns extends Form +{ + protected $returnValue; + + public function setReturnValue($returnValue) + { + $this->returnValue = $returnValue; + } + + public function setData($data) + { + } + + public function getData() + { + return $this->returnValue; + } +} + +class FormTest_AuthorWithoutRefSetter +{ + protected $reference; + + protected $referenceCopy; + + public function __construct($reference) + { + $this->reference = $reference; + $this->referenceCopy = $reference; + } + + // The returned object should be modified by reference without having + // to provide a setReference() method + public function getReference() + { + return $this->reference; + } + + // The returned object is a copy, so setReferenceCopy() must be used + // to update it + public function getReferenceCopy() + { + return is_object($this->referenceCopy) ? clone $this->referenceCopy : $this->referenceCopy; + } + + public function setReferenceCopy($reference) + { + $this->referenceCopy = $reference; + } +} + class TestSetDataBeforeConfigureForm extends Form { protected $testCase; @@ -1130,6 +1183,71 @@ public function testValidateDataDoesNotWalkScalars() $form->validateData($context); } + public function testSubformDoesntCallSetters() + { + $author = new FormTest_AuthorWithoutRefSetter(new Author()); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new Form('reference'); + $refForm->add(new TestField('firstName')); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + // reference has a getter, but not setter + 'reference' => array( + 'firstName' => 'Foo', + ) + ) + ))); + + $this->assertEquals('Foo', $author->getReference()->firstName); + } + + public function testSubformCallsSettersIfByReferenceIsFalse() + { + $author = new FormTest_AuthorWithoutRefSetter(new Author()); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new Form('referenceCopy', array('by_reference' => false)); + $refForm->add(new TestField('firstName')); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + // referenceCopy has a getter that returns a copy + 'referenceCopy' => array( + 'firstName' => 'Foo', + ) + ) + ))); + + // firstName can only be updated if setReferenceCopy() was called + $this->assertEquals('Foo', $author->getReferenceCopy()->firstName); + } + + public function testSubformCallsSettersIfReferenceIsScalar() + { + $author = new FormTest_AuthorWithoutRefSetter('scalar'); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new FormTest_FormThatReturns('referenceCopy'); + $refForm->setReturnValue('foobar'); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + 'referenceCopy' => array(), // doesn't matter actually + ) + ))); + + // firstName can only be updated if setReferenceCopy() was called + $this->assertEquals('foobar', $author->getReferenceCopy()); + } + /** * Create a group containing two fields, "visibleField" and "hiddenField" * From bd3e6c6b4953ed05e41662766b14568f96df986c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Sun, 6 Feb 2011 18:10:50 +0100 Subject: [PATCH 19/67] [Form] Fixed: Form::bind() throws an exception if form is anonymous --- src/Symfony/Component/Form/Form.php | 4 ++++ tests/Symfony/Tests/Component/Form/FormTest.php | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 6b0886bf826dd..07e7f1a40d584 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -713,6 +713,10 @@ public function getCsrfProvider() */ public function bind(Request $request, $data = null) { + if (!$this->getName()) { + throw new FormException('You cannot bind anonymous forms. Please give this form a name'); + } + // Store object from which to read the default values and where to // write the submitted values if (null !== $data) { diff --git a/tests/Symfony/Tests/Component/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php index e23cc3212dff7..b56f7f8e521aa 100644 --- a/tests/Symfony/Tests/Component/Form/FormTest.php +++ b/tests/Symfony/Tests/Component/Form/FormTest.php @@ -276,6 +276,16 @@ public function testValidationGroupsAreNotInheritedFromParentIfSet() $this->assertEquals(array('group2'), $childForm->getValidationGroups()); } + /** + * @expectedException Symfony\Component\Form\Exception\FormException + */ + public function testBindThrowsExceptionIfAnonymous() + { + $form = new Form(null, array('validator' => $this->createMockValidator())); + + $form->bind($this->createPostRequest()); + } + public function testBindValidatesData() { $form = new Form('author', array( From 74d0ac82f7d4ef2539a7fa0114025cdfd8229c54 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Sun, 6 Feb 2011 19:24:45 +0100 Subject: [PATCH 20/67] [Form] Cleaned up ValueTransformerInterface This commit removes CollectionToStringTransformer. Transformers should never change the state of the outside world, otherwise hard-to-track bugs might creap in. This functionality needs to be implemented as a custom FieldType (see EntityChoiceField). --- .../BooleanToStringTransformer.php | 2 +- .../CollectionToStringTransformer.php | 157 ------------ .../DateTimeToArrayTransformer.php | 2 +- .../DateTimeToLocalizedStringTransformer.php | 2 +- .../DateTimeToStringTransformer.php | 2 +- .../DateTimeToTimestampTransformer.php | 2 +- .../MoneyToLocalizedStringTransformer.php | 4 +- .../NumberToLocalizedStringTransformer.php | 2 +- .../PercentToLocalizedStringTransformer.php | 2 +- .../ValueTransformer/ReversedTransformer.php | 2 +- .../ValueTransformerChain.php | 4 +- .../ValueTransformerInterface.php | 4 +- .../CollectionToStringTransformerTest.php | 223 ------------------ 13 files changed, 13 insertions(+), 395 deletions(-) delete mode 100644 src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php delete mode 100644 tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php diff --git a/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php index 1a793f5fce149..890b0bc326bfa 100644 --- a/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/BooleanToStringTransformer.php @@ -47,7 +47,7 @@ public function transform($value) * @param string $value String value. * @return Boolean Boolean value. */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (!is_string($value)) { throw new UnexpectedTypeException($value, 'string'); diff --git a/src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php deleted file mode 100644 index b939f69236dda..0000000000000 --- a/src/Symfony/Component/Form/ValueTransformer/CollectionToStringTransformer.php +++ /dev/null @@ -1,157 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\ValueTransformer; - -use Symfony\Component\Form\Configurable; -use Doctrine\Common\Collections\Collection; - -/** - * Transforms an instance of Doctrine\Common\Collections\Collection into a string of unique names. - * - * Use-Cases for this transformer include: List of Tag-Names, List Of Group/User-Names or the like. - * - * This transformer only makes sense if you know the list of related collections to be small and - * that they have a unique identifier field that is of meaning to the user (Tag Names) and is - * enforced to be unique in the storage. - * - * This transformer can cause the following SQL operations to happen in the case of an ORM collection: - * 1. Initialize the whole collection using one SELECT query - * 2. For each removed element issue an UPDATE or DELETE stmt (depending on one-to-many or many-to-many) - * 3. For each inserted element issue an INSERT or UPDATE stmt (depending on one-to-many or many-to-many) - * 4. Extra updates if necessary by the ORM. - * - * @todo Refactor to make 'fieldName' optional (identifier). - * - * @author Benjamin Eberlei - * @author Bernhard Schussek - */ -class CollectionToStringTransformer extends Configurable implements ValueTransformerInterface -{ - protected function configure() - { - $this->addOption('trim', true); - $this->addOption('separator', ','); - $this->addOption('explode_callback', 'explode'); - $this->addOption('implode_callback', 'implode'); - $this->addOption('create_instance_callback', null); - $this->addRequiredOption('em'); - $this->addRequiredOption('class_name'); - $this->addRequiredOption('field_name'); - - parent::configure(); - } - - /** - * @param string $value - * @param Collection $collection - */ - public function reverseTransform($value, $collection) - { - if (strlen(trim($value)) == 0) { - // don't check for collection count, a straight clear doesnt initialize the collection - $collection->clear(); - return $collection; - } - - $callback = $this->getOption('explode_callback'); - $values = call_user_func($callback, $this->getOption('separator'), $value); - - if ($this->getOption('trim') === true) { - $values = array_map('trim', $values); - } - - /* @var $em Doctrine\ORM\EntityManager */ - $em = $this->getOption('em'); - $className = $this->getOption('class_name'); - $reflField = $em->getClassMetadata($className) - ->getReflectionProperty($this->getOption('field_name')); - - // 1. removing elements that are not yet present anymore - foreach ($collection as $object) { - $uniqueIdent = $reflField->getValue($object); - $key = array_search($uniqueIdent, $values); - if (false === $key) { - $collection->removeElement($object); - } else { - // found in the collection, no need to do anything with it so remove it - unset($values[$key]); - } - } - - // 2. add elements that are known to the EntityManager but newly connected, query them from the repository - if (count($values)) { - $dql = sprintf('SELECT o FROM %s o WHERE o.%s IN (', $className, $this->getOption('field_name')); - $query = $em->createQuery(); - $needles = array(); - $i = 0; - foreach ($values as $val) { - $query->setParameter(++$i, $val); - $needles[] = '?'.$i; - } - $dql .= implode(',', $needles).')'; - $query->setDql($dql); - $newElements = $query->getResult(); - - foreach ($newElements as $object) { - $collection->add($object); - - $uniqueIdent = $reflField->getValue($object); - $key = array_search($uniqueIdent, $values); - unset($values[$key]); - } - } - - // 3. new elements that are not in the repository have to be created and persisted then attached: - if (count($values)) { - $callback = $this->getOption('create_instance_callback'); - if (!$callback || !is_callable($callback)) { - throw new TransformationFailedException('Cannot transform list of identifiers, because a new element was detected and it is unknown how to create an instance of this element.'); - } - - foreach ($values as $newValue) { - $newInstance = call_user_func($callback, $newValue); - if (!($newInstance instanceof $className)) { - throw new TransformationFailedException(sprintf('Error while trying to create a new instance for the identifier "%s". No new instance was created.', $newValue)); - } - $collection->add($newInstance); - $em->persist($newInstance); - } - } - - return $collection; - } - - /** - * Transform a Doctrine Collection into a string of identifies with a separator. - * - * @param Collection $value - * @return string - */ - public function transform($value) - { - if (null === $value) { - return ''; - } - - $values = array(); - $em = $this->getOption('em'); - $reflField = $em->getClassMetadata($this->getOption('class_name')) - ->getReflectionProperty($this->getOption('field_name')); - - foreach ($value as $object) { - $values[] = $reflField->getValue($object); - } - $callback = $this->getOption('implode_callback'); - - return call_user_func($callback, $this->getOption('separator'), $values); - } -} diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php index 1684a10b22a69..e66085c8ee6de 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToArrayTransformer.php @@ -95,7 +95,7 @@ public function transform($dateTime) * @param array $value Localized date string/array * @return DateTime Normalized date */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (null === $value) { return null; diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php index fd2bb2756e69d..e5cc1ec2268e1 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToLocalizedStringTransformer.php @@ -86,7 +86,7 @@ public function transform($dateTime) * @param string|array $value Localized date string/array * @return DateTime Normalized date */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { $inputTimezone = $this->getOption('input_timezone'); diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php index 014b39c4b31f5..17b86d7be468a 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToStringTransformer.php @@ -62,7 +62,7 @@ public function transform($value) * @param string $value A value as produced by PHP's date() function * @return DateTime A DateTime object */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (empty($value)) { return null; diff --git a/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php b/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php index 3be5fc6c115e3..c05575f0da0b7 100644 --- a/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/DateTimeToTimestampTransformer.php @@ -60,7 +60,7 @@ public function transform($value) * @param string $value A value as produced by PHP's date() function * @return DateTime A DateTime object */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (null === $value) { return null; diff --git a/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php index 15acd97207cb3..8db5b4cad9b31 100644 --- a/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/MoneyToLocalizedStringTransformer.php @@ -58,9 +58,9 @@ public function transform($value) * @param string $value Localized money string * @return number Normalized number */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { - $value = parent::reverseTransform($value, $originalValue); + $value = parent::reverseTransform($value); if (null !== $value) { $value *= $this->getOption('divisor'); diff --git a/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php index a960dbdf62c8f..19d9e3012d5f6 100644 --- a/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/NumberToLocalizedStringTransformer.php @@ -74,7 +74,7 @@ public function transform($value) * * @param string $value */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (!is_string($value)) { throw new UnexpectedTypeException($value, 'string'); diff --git a/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php b/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php index a3762ee788ffb..ece2e4da90476 100644 --- a/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/PercentToLocalizedStringTransformer.php @@ -82,7 +82,7 @@ public function transform($value) * @param number $value Percentage value. * @return number Normalized value. */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { if (!is_string($value)) { throw new UnexpectedTypeException($value, 'string'); diff --git a/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php b/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php index 93c068b81839f..99af8c16da5b6 100644 --- a/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php +++ b/src/Symfony/Component/Form/ValueTransformer/ReversedTransformer.php @@ -48,7 +48,7 @@ public function transform($value) /** * {@inheritDoc} */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { return $this->reversedTransformer->transform($value); } diff --git a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php index a8bbda4dbfba3..129df2dc34c7d 100644 --- a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php +++ b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerChain.php @@ -66,10 +66,10 @@ public function transform($value) * @param mixed $value The transformed value * @return mixed The reverse-transformed value */ - public function reverseTransform($value, $originalValue) + public function reverseTransform($value) { for ($i = count($this->transformers) - 1; $i >= 0; --$i) { - $value = $this->transformers[$i]->reverseTransform($value, $originalValue); + $value = $this->transformers[$i]->reverseTransform($value); } return $value; diff --git a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php index 663011d5b8ecd..b170b7412a2ea 100644 --- a/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php +++ b/src/Symfony/Component/Form/ValueTransformer/ValueTransformerInterface.php @@ -65,11 +65,9 @@ function transform($value); * is passed. * * @param mixed $value The value in the transformed representation - * @param mixed $originalValue The original value from the datasource that is about to be overwritten by the new value. - * @return mixed The value in the original representation * @throws UnexpectedTypeException when the argument is not of the * expected type * @throws ValueTransformerException when the transformation fails */ - function reverseTransform($value, $originalValue); + function reverseTransform($value); } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php b/tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php deleted file mode 100644 index 97565c4070cec..0000000000000 --- a/tests/Symfony/Tests/Component/Form/ValueTransformer/CollectionToStringTransformerTest.php +++ /dev/null @@ -1,223 +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\Form\ValueTransformer; - -require_once __DIR__.'/../DoctrineOrmTestCase.php'; - -use Symfony\Tests\Component\Form\DoctrineOrmTestCase; -use Symfony\Component\Form\ValueTransformer\CollectionToStringTransformer; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Tools\SchemaTool; - -class CollectionToStringTransformerTest extends DoctrineOrmTestCase -{ - /** - * @var EntityManager - */ - private $em; - - protected function setUp() - { - parent::setUp(); - $this->em = $this->createTestEntityManager(); - - $schemaTool = new SchemaTool($this->em); - $classes = array($this->em->getClassMetadata(__NAMESPACE__.'\Tag')); - try { - $schemaTool->dropSchema($classes); - } catch(\Exception $e) { - - } - try { - $schemaTool->createSchema($classes); - } catch(\Exception $e) { - echo $e->getMessage(); - } - } - - public function testNoEntityManagerThrowsException() - { - $this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException'); - $transformer = new CollectionToStringTransformer(array( - 'class_name' => __NAMESPACE__.'\Tag', - 'field_name' => 'name', - )); - } - - public function testNoClassNameThrowsException() - { - $this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException'); - $transformer = new CollectionToStringTransformer(array( - 'field_name' => 'name', - 'em' => $this->em, - )); - } - - public function testNoFieldNameThrowsException() - { - $this->setExpectedException('Symfony\Component\Form\Exception\MissingOptionsException'); - $transformer = new CollectionToStringTransformer(array( - 'class_name' => __NAMESPACE__.'\Tag', - 'em' => $this->em, - )); - } - - public function createTransformer() - { - $transformer = new CollectionToStringTransformer(array( - 'class_name' => __NAMESPACE__.'\Tag', - 'field_name' => 'name', - 'em' => $this->em, - 'create_instance_callback' => function($tagName) { - return new Tag($tagName); - } - )); - return $transformer; - } - - public function testTransformEmptyCollection() - { - $transformer = $this->createTransformer(); - $ret = $transformer->transform(new ArrayCollection()); - - $this->assertEquals("", $ret); - } - - /** - * @depends testTransformEmptyCollection - */ - public function testTransformCollection() - { - $transformer = $this->createTransformer(); - - $tags = new ArrayCollection(); - $tags->add(new Tag("foo")); - $tags->add(new Tag("bar")); - - $this->assertEquals("foo,bar", $transformer->transform($tags)); - } - - public function createTagCollection() - { - $tags = new ArrayCollection(); - $tags->add(new Tag("foo")); - $tags->add(new Tag("bar")); - - return $tags; - } - - /** - * @depends testTransformEmptyCollection - */ - public function testReverseTransformEmptyString() - { - $transformer = $this->createTransformer(); - - $col = new ArrayCollection(); - - $newCol = $transformer->reverseTransform("", $col); - $this->assertSame($col, $newCol, "A collection is an expenive object that is re-used by the transformer!"); - $this->assertEquals(0, count($newCol)); - } - - /** - * @depends testReverseTransformEmptyString - */ - public function testReverseTransformEmptyStringEmptiesCollection() - { - $transformer = $this->createTransformer(); - - $col = $this->createTagCollection(); - - $newCol = $transformer->reverseTransform("", $col); - $this->assertSame($col, $newCol, "A collection is an expenive object that is re-used by the transformer!"); - $this->assertEquals(0, count($newCol)); - } - - /** - * @depends testTransformEmptyCollection - */ - public function testReverseTransformUnchanged() - { - $transformer = $this->createTransformer(); - - $tags = $this->createTagCollection(); - - $tags = $transformer->reverseTransform("foo,bar", $tags); - - $this->assertEquals(2, count($tags)); - } - - /** - * @depends testTransformEmptyCollection - */ - public function testReverseTransformNewKnownEntity() - { - $transformer = $this->createTransformer(); - - $newTag = new Tag("baz"); - $this->em->persist($newTag); - $this->em->flush(); - - $tags = $this->createTagCollection(); - $tags = $transformer->reverseTransform("foo, bar, baz", $tags); - - $this->assertEquals(3, count($tags)); - $this->assertTrue($tags->contains($newTag)); - } - - /** - * @depends testReverseTransformNewKnownEntity - */ - public function testReverseTransformNewUnknownEntity() - { - $transformer = $this->createTransformer(); - - $tags = $this->createTagCollection(); - $tags = $transformer->reverseTransform("foo, bar, baz", $tags); - - $this->assertEquals(3, count($tags)); - $this->em->flush(); - - $this->assertSame($this->em, $transformer->getOption('em')); - - $this->assertEquals(1, count($this->em->getRepository(__NAMESPACE__.'\Tag')->findAll())); - } - - /** - * @depends testReverseTransformNewUnknownEntity - */ - public function testReverseTransformRemoveEntity() - { - $transformer = $this->createTransformer(); - - $tags = $this->createTagCollection(); - $tags = $transformer->reverseTransform("foo", $tags); - - $this->assertEquals(1, count($tags)); - } - -} - -/** @Entity */ -class Tag -{ - /** @Id @GeneratedValue @Column(type="integer") */ - public $id; - - /** @Column(type="string") */ - public $name; - - public function __construct($name) { - $this->name = $name; - } -} \ No newline at end of file From c5fb96b86b65ad0aa09f5c3ad5abada4f4f9a24e Mon Sep 17 00:00:00 2001 From: ornicar Date: Sun, 6 Feb 2011 10:37:19 -0800 Subject: [PATCH 21/67] [HttpKernel] Add more unit tests for Kernel --- src/Symfony/Component/HttpKernel/Kernel.php | 2 +- .../Tests/Component/HttpKernel/KernelTest.php | 115 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 5eb169931b70c..a63add55048c0 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -163,7 +163,7 @@ public function getBundles() */ public function isClassInActiveBundle($class) { - foreach ($this->bundles as $bundle) { + foreach ($this->getBundles() as $bundle) { $bundleClass = get_class($bundle); if (0 === strpos($class, substr($bundleClass, 0, strrpos($bundleClass, '\\')))) { return true; diff --git a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php index c6c11038d2d14..18542f50f7a30 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\Loader\LoaderInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpFoundation\Request; @@ -98,6 +99,20 @@ public function testBootSetsTheBootedFlagToTrue() $this->assertTrue($kernel->isBooted()); } + public function testBootKernelSeveralTimesOnlyInitializesBundlesOnce() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + $kernel->boot(); + } + public function testShutdownCallsShutdownOnAllBundles() { $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') @@ -163,6 +178,35 @@ public function testHandleCallsHandleOnHttpKernel() $kernel->handle($request, $type, $catch); } + public function testHandleBootsTheKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel', 'boot')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->expects($this->once()) + ->method('boot'); + + // required as this value is initialized + // in the kernel constructor, which we don't call + $kernel->setIsBooted(false); + + $kernel->handle($request, $type, $catch); + } + public function testStripComments() { if (!function_exists('token_get_all')) { @@ -199,6 +243,66 @@ public function doStuff() $this->assertEquals($expected, Kernel::stripComments($source)); } + public function testIsClassInActiveBundleFalse() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertFalse($kernel->isClassInActiveBundle('Not\In\Active\Bundle')); + } + + public function testIsClassInActiveBundleFalseNoNamespace() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertFalse($kernel->isClassInActiveBundle('NotNamespacedClass')); + } + + public function testIsClassInActiveBundleTrue() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertTrue($kernel->isClassInActiveBundle(__NAMESPACE__.'\FooBarBundle\SomeClass')); + } + + protected function getKernelMockForIsClassInActiveBundleTest() + { + $bundle = new FooBarBundle(); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + return $kernel; + } + + public function testGetRootDir() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals(__DIR__, $kernel->getRootDir()); + } + + public function testGetName() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals('HttpKernel', $kernel->getName()); + } + + public function testSerialize() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $expected = serialize(array($env, $debug)); + $this->assertEquals($expected, $kernel->serialize()); + } + /** * @expectedException \InvalidArgumentException */ @@ -497,6 +601,7 @@ public function getBundleMap() public function registerRootDir() { + return __DIR__; } public function registerBundles() @@ -520,9 +625,19 @@ public function isBooted() { return $this->booted; } + + public function setIsBooted($value) + { + $this->booted = (bool) $value; + } } abstract class BundleForTest implements BundleInterface { // We can not extend Symfony\Component\HttpKernel\Bundle\Bundle as we want to mock getName() which is final } + +class FooBarBundle extends Bundle +{ + // We need a full namespaced bundle instance to test isClassInActiveBundle +} From 0b8fef234724cb4cb3a4ab37466efe193a6c7708 Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Fri, 4 Feb 2011 14:37:01 +0100 Subject: [PATCH 22/67] [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() From c7ef8d98d67a92ec2e2e72be73769a4c513626f2 Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sun, 6 Feb 2011 11:47:06 +0100 Subject: [PATCH 23/67] [DependencyInjection] append new elements for prototype nodes without a key attribute --- .../Configuration/ArrayNode.php | 6 ++++ .../Configuration/MergeTest.php | 32 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php b/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php index 1b9cbdfdc1291..e5b75a5a0a96a 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ArrayNode.php @@ -318,6 +318,12 @@ protected function mergeValues($leftSide, $rightSide) } foreach ($rightSide as $k => $v) { + // prototype, and key is irrelevant, so simply append the element + if (null !== $this->prototype && null === $this->keyAttribute) { + $leftSide[] = $v; + continue; + } + // no conflict if (!array_key_exists($k, $leftSide)) { if (!$this->allowNewKeys) { diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php index 0fab77cc11286..64fc6da4eda72 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/MergeTest.php @@ -15,9 +15,7 @@ public function testForbiddenOverwrite() $tree = $tb ->root('root', 'array') ->node('foo', 'scalar') - ->merge() - ->denyOverwrite() - ->end() + ->cannotBeOverwritten() ->end() ->end() ->buildTree() @@ -42,12 +40,12 @@ public function testUnsetKey() ->node('foo', 'scalar')->end() ->node('bar', 'scalar')->end() ->node('unsettable', 'array') - ->merge()->allowUnset()->end() + ->canBeUnset() ->node('foo', 'scalar')->end() ->node('bar', 'scalar')->end() ->end() ->node('unsetted', 'array') - ->merge()->allowUnset()->end() + ->canBeUnset() ->prototype('scalar')->end() ->end() ->end() @@ -145,4 +143,28 @@ public function testPerformsNoDeepMerging() ) ), $tree->merge($a, $b)); } + + public function testPrototypeWithoutAKeyAttribute() + { + $tb = new TreeBuilder(); + + $tree = $tb + ->root('config', 'array') + ->node('append_elements', 'array') + ->prototype('scalar')->end() + ->end() + ->end() + ->buildTree() + ; + + $a = array( + 'append_elements' => array('a', 'b'), + ); + + $b = array( + 'append_elements' => array('c', 'd'), + ); + + $this->assertEquals(array('append_elements' => array('a', 'b', 'c', 'd')), $tree->merge($a, $b)); + } } \ No newline at end of file From 2316c90ec29f1a3c5f227d27e5a3fe2f9cfabc54 Mon Sep 17 00:00:00 2001 From: Johannes Schmitt Date: Sun, 6 Feb 2011 13:33:54 +0100 Subject: [PATCH 24/67] [Security] fixes a regression --- .../Bundle/SecurityBundle/DependencyInjection/Configuration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php index 5f48157cb313e..2bb11266c2901 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Configuration.php @@ -69,6 +69,7 @@ protected function addRoleHierarchySection($rootNode) ->arrayNode('role_hierarchy') ->containsNameValuePairsWithKeyAttribute('id') ->prototype('array') + ->performNoDeepMerging() ->beforeNormalization()->ifString()->then(function($v) { return array('value' => $v); })->end() ->beforeNormalization() ->ifTrue(function($v) { return is_array($v) && isset($v['value']); }) From cdff8b2bf8a0df7a5329e03d92a9eb2d96ce7741 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Feb 2011 20:22:57 +0100 Subject: [PATCH 25/67] [FrameworkBundle] fixed error message for template as an array --- .../Bundle/FrameworkBundle/Templating/DelegatingEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php b/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php index 438d32ee18d1a..8885834462922 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/DelegatingEngine.php @@ -69,7 +69,7 @@ protected function getEngine($name) } } - throw new \RuntimeException(sprintf('No engine is able to work with the "%s" template.', $name)); + throw new \RuntimeException(sprintf('No engine is able to work with the %s template.', json_encode($name))); } /** From 2b256a0804bb96ecd436388151dbc00ef3f7d67c Mon Sep 17 00:00:00 2001 From: "Johannes M. Schmitt" Date: Sun, 6 Feb 2011 20:40:21 +0100 Subject: [PATCH 26/67] [DependencyInjection] allow null for scalar nodes --- .../Configuration/ScalarNode.php | 9 +++- .../Configuration/ScalarNodeTest.php | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/Symfony/Tests/Component/DependencyInjection/Configuration/ScalarNodeTest.php diff --git a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php index fd871c3864590..3f270bf14cf24 100644 --- a/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php +++ b/src/Symfony/Component/DependencyInjection/Configuration/ScalarNode.php @@ -8,6 +8,13 @@ /** * This node represents a scalar value in the config tree. * + * The following values are considered scalars: + * * booleans + * * strings + * * null + * * integers + * * floats + * * @author Johannes M. Schmitt */ class ScalarNode extends BaseNode implements PrototypeNodeInterface @@ -44,7 +51,7 @@ public function setName($name) protected function validateType($value) { - if (!is_scalar($value)) { + if (!is_scalar($value) && null !== $value) { throw new InvalidTypeException(sprintf( 'Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ScalarNodeTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ScalarNodeTest.php new file mode 100644 index 0000000000000..8ff43ca073b46 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Configuration/ScalarNodeTest.php @@ -0,0 +1,49 @@ +assertSame($value, $node->normalize($value)); + } + + public function getValidValues() + { + return array( + array(false), + array(true), + array(null), + array(''), + array('foo'), + array(0), + array(1), + array(0.0), + array(0.1), + ); + } + + /** + * @dataProvider getInvalidValues + * @expectedException Symfony\Component\DependencyInjection\Configuration\Exception\InvalidTypeException + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new ScalarNode('test'); + $node->normalize($value); + } + + public function getInvalidValues() + { + return array( + array(array()), + array(array('foo' => 'bar')), + array(new \stdClass()), + ); + } +} \ No newline at end of file From 099b9dee1f4ad0ef411bb16fb38ab3a1ac91d1bb Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 6 Feb 2011 04:29:16 -0500 Subject: [PATCH 27/67] [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 7cb42d2a686194655f814b22a194bb6b0cc4f146 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Feb 2011 21:05:37 +0100 Subject: [PATCH 28/67] Revert "[TwigBundle] fixed error messages when an error occurs during template compilation" This reverts commit c68b3266651cc15475963ae11fc41f6b9bf94b3c. --- src/Symfony/Bundle/TwigBundle/TwigEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/TwigBundle/TwigEngine.php b/src/Symfony/Bundle/TwigBundle/TwigEngine.php index 20d7082785c3e..7fdf2d77c2a38 100644 --- a/src/Symfony/Bundle/TwigBundle/TwigEngine.php +++ b/src/Symfony/Bundle/TwigBundle/TwigEngine.php @@ -131,6 +131,6 @@ protected function load($name) return $name; } - return $this->environment->loadTemplate($this->parser->parse($name), is_array($name) ? json_encode($name) : $name); + return $this->environment->loadTemplate($this->parser->parse($name)); } } From 86631a9feef5965f67b5e9089dc4e15f575ae961 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Feb 2011 21:39:30 +0100 Subject: [PATCH 29/67] [FrameworkBundle] fixed template loaders in configuration --- .../DependencyInjection/FrameworkExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 411835221d3dc..40bf4df8c2a88 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -309,10 +309,10 @@ private function registerTemplatingConfiguration(array $config, ContainerBuilder $container->setParameter('templating.assets.base_urls', $config['assets_base_urls']); } - if (isset($config['loaders'])) { + if (isset($config['loaders']) && $config['loaders']) { $loaders = array_map(function($loader) { return new Reference($loader); }, $config['loaders']); - // Use a deligation unless only a single loader was registered + // Use a delegation unless only a single loader was registered if (1 === count($loaders)) { $container->setAlias('templating.loader', (string) reset($loaders)); } else { From afad1132976af953619449d8bb98894a9047080d Mon Sep 17 00:00:00 2001 From: ornicar Date: Sun, 6 Feb 2011 12:39:06 -0800 Subject: [PATCH 30/67] [HttpKernel] Fix notice in Kernel --- src/Symfony/Component/HttpKernel/Kernel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index a63add55048c0..86b7ebbe9dc24 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -372,7 +372,7 @@ protected function initializeBundles() } // look for orphans - if (count($diff = array_diff(array_keys($directChildren), array_keys($this->bundles)))) { + if (count($diff = array_values(array_diff(array_keys($directChildren), array_keys($this->bundles))))) { throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); } From 44e6a5d45f11c6efc1ed4ece11133d2e6c110766 Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Sun, 6 Feb 2011 21:29:13 +0100 Subject: [PATCH 31/67] added exception_controller config option --- .../FrameworkBundle/DependencyInjection/Configuration.php | 1 + .../DependencyInjection/FrameworkExtension.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 418a7544144cc..a5c351aa28311 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -28,6 +28,7 @@ public function getConfigTree($kernelDebug) ->scalarNode('charset')->end() ->scalarNode('document_root')->end() ->scalarNode('error_handler')->end() + ->scalarNode('exception_controller')->end() ->scalarNode('ide')->end() ->booleanNode('test')->end() ; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 40bf4df8c2a88..9ceab8ed3e676 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -78,6 +78,10 @@ public function configLoad(array $configs, ContainerBuilder $container) } } + if (isset($config['exception_controller'])) { + $container->setParameter('exception_listener.controller', $config['exception_controller']); + } + if (isset($config['ide'])) { $patterns = array( 'textmate' => 'txmt://open?url=file://%%f&line=%%l', From 33c8d12b9a2a21bff75b51525d065aaa50bb9d83 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 6 Feb 2011 21:44:04 +0100 Subject: [PATCH 32/67] [FrameworkBundle] fixed XSD for the new exception-controller option --- .../FrameworkBundle/Resources/config/schema/symfony-1.0.xsd | 1 + 1 file changed, 1 insertion(+) 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 689185a4610c0..07c0dc20c58d8 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 @@ -22,6 +22,7 @@ + From 7ad4f99153eb496bed17bfe6f17edb18276e91b1 Mon Sep 17 00:00:00 2001 From: pborreli Date: Sun, 6 Feb 2011 04:20:21 +0000 Subject: [PATCH 33/67] [HttpFoundation] File/UploadedFile, MimeTest, Exception full coverage --- .../File/MimeType/FileinfoMimeTypeGuesser.php | 11 +-- .../File/Fixtures/.unknownextension | 1 + .../File/MimeType/MimeTypeTest.php | 77 +++++++++++++++++++ .../HttpFoundation/File/UploadedFileTest.php | 52 +++++++++++++ 4 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 tests/Symfony/Tests/Component/HttpFoundation/File/Fixtures/.unknownextension create mode 100644 tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php diff --git a/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php b/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php index 7ea2d59cdfa7d..0aee4cd978cee 100644 --- a/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php +++ b/src/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -50,17 +50,10 @@ public function guess($path) return null; } - if (!$finfo = new \finfo(FILEINFO_MIME)) { + if (!$finfo = new \finfo(FILEINFO_MIME_TYPE)) { return null; } - $type = $finfo->file($path); - - // remove charset (added as of PHP 5.3) - if (false !== $pos = strpos($type, ';')) { - $type = substr($type, 0, $pos); - } - - return $type; + return $finfo->file($path); } } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/Fixtures/.unknownextension b/tests/Symfony/Tests/Component/HttpFoundation/File/Fixtures/.unknownextension new file mode 100644 index 0000000000000..4d1ae35ba2c8e --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/Fixtures/.unknownextension @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php new file mode 100644 index 0000000000000..a56bc9ac3c4fc --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\ContentTypeMimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +class MimeTypeTest extends \PHPUnit_Framework_TestCase +{ + protected $path; + + public function testGuessImageWithoutExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithContentTypeMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new ContentTypeMimeTypeGuesser()); + $this->assertEquals('image/gif', $guesser->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithFileBinaryMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new FileBinaryMimeTypeGuesser()); + $this->assertEquals('image/gif', $guesser->guess(__DIR__.'/../Fixtures/test')); + } + + public function testGuessImageWithKnownExtension() + { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); + } + + public function testGuessFileWithUnknownExtension() + { + $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); + } + + public function testGuessWithIncorrectPath() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here'); + } + + public function testGuessWithNonReadablePath() + { + $path = __DIR__.'/../Fixtures/to_delete'; + touch($path); + chmod($path, 0333); + + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); + MimeTypeGuesser::getInstance()->guess($path); + + + } + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + chmod($path, 0666); + @unlink($path); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php index 5e5cb5d69677a..fe2fa432a4302 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/UploadedFileTest.php @@ -15,6 +15,7 @@ class UploadedFileTest extends \PHPUnit_Framework_TestCase { + public function testFileUploadsMustBeEnabled() { // we can't change this setting without modifying php.ini :( @@ -31,6 +32,42 @@ public function testFileUploadsMustBeEnabled() } } + public function testFileUploadsWithNoMimeType() + { + // we can't change this setting without modifying php.ini :( + if (ini_get('file_uploads')) { + + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertAttributeEquals('application/octet-stream', 'mimeType', $file); + $this->assertEquals('image/gif', $file->getMimeType()); + } + } + + public function testFileUploadsWithUnknownMimeType() + { + // we can't change this setting without modifying php.ini :( + if (ini_get('file_uploads')) { + + $file = new UploadedFile( + __DIR__.'/Fixtures/.unknownextension', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/.unknownextension'), + UPLOAD_ERR_OK + ); + + $this->assertAttributeEquals('application/octet-stream', 'mimeType', $file); + $this->assertEquals('application/octet-stream', $file->getMimeType()); + } + } + public function testErrorIsOkByDefault() { // we can't change this setting without modifying php.ini :( @@ -46,4 +83,19 @@ public function testErrorIsOkByDefault() $this->assertEquals(UPLOAD_ERR_OK, $file->getError()); } } + public function testGetOriginalName() + { + // we can't change this setting without modifying php.ini :( + if (ini_get('file_uploads')) { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getOriginalName()); + } + } } \ No newline at end of file From b9ed739d75fa59f5749be6c4b5ac15784fef2d91 Mon Sep 17 00:00:00 2001 From: Marcin Sikon Date: Sun, 6 Feb 2011 21:51:02 +0100 Subject: [PATCH 34/67] fixed RedirectController - removed parmanent attribute before generate new url, added tests --- .../Controller/RedirectController.php | 2 +- .../Controller/RedirectControllerTest.php | 122 ++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php index cb094a3d6d37c..0f95d187a1a31 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/RedirectController.php @@ -47,7 +47,7 @@ public function redirectAction($route, $permanent = false) $code = $permanent ? 301 : 302; $attributes = $this->container->get('request')->attributes->all(); - unset($attributes['_route'], $attributes['route']); + unset($attributes['_route'], $attributes['route'], $attributes['permanent'] ); $response = $this->container->get('response'); $response->setRedirect($this->container->get('router')->generate($route, $attributes), $code); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php new file mode 100644 index 0000000000000..5f0d3a06a8d13 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; +use Symfony\Bundle\FrameworkBundle\Tests\Logger; +use Symfony\Bundle\FrameworkBundle\Tests\Kernel; + + + +/** + * + * @author Marcin Sikon + */ +class RedirectControllerTest extends TestCase +{ + public function testEmptyRoute() + { + $response = new Response(); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->once()) + ->method('get') + ->with($this->equalTo('response')) + ->will($this->returnValue($response)) + ; + + $controller = new RedirectController(); + $controller->setContainer($container); + + $returnResponse = $controller->redirectAction(''); + + $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); + + $this->assertEquals(410, $returnResponse->getStatusCode()); + } + + + + /** + * @dataProvider provider + */ + public function testRoute($permanent, $expectedCode) + { + $response = new Response(); + $request = new Request(); + + $route = 'new-route'; + $url = '/redirect-url'; + $params = array('additional-parameter' => 'value'); + + + $request->attributes = new ParameterBag(array('route' => $route, '_route' => 'current-route', 'permanent' => $permanent) + $params); + + $router = $this->getMock('Symfony\Component\Routing\RouterInterface'); + $router + ->expects($this->once()) + ->method('generate') + ->with($this->equalTo($route),$this->equalTo($params)) + ->will($this->returnValue($url)); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + + + $container + ->expects($this->at(0)) + ->method('get') + ->with($this->equalTo('request')) + ->will($this->returnValue($request)); + + $container + ->expects($this->at(1)) + ->method('get') + ->with($this->equalTo('response')) + ->will($this->returnValue($response)); + + $container + ->expects($this->at(2)) + ->method('get') + ->with($this->equalTo('router')) + ->will($this->returnValue($router)); + + + $controller = new RedirectController(); + $controller->setContainer($container); + + $returnResponse = $controller->redirectAction($route, $permanent); + + + $this->assertInstanceOf('\Symfony\Component\HttpFoundation\Response', $returnResponse); + + $this->assertTrue($returnResponse->isRedirect()); + $this->assertTrue($returnResponse->isRedirected($url)); + $this->assertEquals($expectedCode, $returnResponse->getStatusCode()); + } + + + + public function provider() + { + return array( + array(true, 301), + array(false, 302), + ); + } + +} From 69672bad597d4b02a0dc17df214efb879953655c Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Sun, 6 Feb 2011 13:10:40 -0800 Subject: [PATCH 35/67] Added phpdoc to BrowserKit --- src/Symfony/Component/BrowserKit/Client.php | 35 +++++++++++++++++++ src/Symfony/Component/BrowserKit/Response.php | 5 +++ 2 files changed, 40 insertions(+) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 02c11dc47968f..9373337f710ee 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -266,16 +266,39 @@ protected function getScript($request) // @codeCoverageIgnoreEnd } + /** + * Filters the request + * + * @param Request The request to filter + * + * @return Request + */ protected function filterRequest(Request $request) { return $request; } + /** + * Filters the Response + * + * @param Response The Response to filter + * + * @return Response + */ protected function filterResponse($response) { return $response; } + /** + * Creates a crawler + * + * @param string A uri + * @param string Content for the crawler to use + * @param string Content type + * + * @return Crawler + */ protected function createCrawlerFromContent($uri, $content, $type) { $crawler = new Crawler(null, $uri); @@ -286,6 +309,8 @@ protected function createCrawlerFromContent($uri, $content, $type) /** * Goes back in the browser history. + * + * @return Crawler */ public function back() { @@ -294,6 +319,8 @@ public function back() /** * Goes forward in the browser history. + * + * @return Crawler */ public function forward() { @@ -302,6 +329,8 @@ public function forward() /** * Reloads the current browser. + * + * @return Crawler */ public function reload() { @@ -335,6 +364,12 @@ public function restart() $this->history->clear(); } + /** + * Takes a URI and converts it to absolute if it is not already absolute. + * + * @param string A uri + * @return string An absolute uri + */ protected function getAbsoluteUri($uri) { // already absolute? diff --git a/src/Symfony/Component/BrowserKit/Response.php b/src/Symfony/Component/BrowserKit/Response.php index 0ed9a4c064b66..26bd45f2ef452 100644 --- a/src/Symfony/Component/BrowserKit/Response.php +++ b/src/Symfony/Component/BrowserKit/Response.php @@ -39,6 +39,11 @@ public function __construct($content = '', $status = 200, array $headers = array $this->headers = $headers; } + /** + * Converts the response object to string containing all headers and the response content + * + * @return string The response with headers and content + */ public function __toString() { $headers = ''; From 108efc6ec96d88092daa67d139e88ab883792a87 Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Sun, 6 Feb 2011 13:31:19 -0800 Subject: [PATCH 36/67] Classloader PHPDoc --- .../Component/ClassLoader/ClassCollectionLoader.php | 13 +++++++++++++ .../Component/ClassLoader/MapFileClassLoader.php | 6 ++++++ .../Component/ClassLoader/UniversalClassLoader.php | 4 ++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index 62e9f4f5dffc3..0773eb747af06 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -119,6 +119,10 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = /** * Adds brackets around each namespace if it's not already the case. + * + * @param string Namespace string + * + * @return string Namespaces with brackets */ static public function fixNamespaceDeclarations($source) { @@ -162,6 +166,15 @@ static public function fixNamespaceDeclarations($source) return $output; } + /** + * Writes a cache file + * + * @param string Filename + * @param string Temporary file content + * + * @throws \RuntimeException when a cache file cannot be written + */ + static protected function writeCacheFile($file, $content) { $tmpFile = tempnam(dirname($file), basename($file)); diff --git a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php index 8a370af645a85..1cce5e22a624e 100644 --- a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php +++ b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\ClassLoader; /** + * A class loader that uses a mapping file to look up paths. * * @author Fabien Potencier */ @@ -19,6 +20,11 @@ class MapFileClassLoader { protected $map = array(); + /** + * Constructor + * + * @param string Path to class mapping file + */ public function __construct($file) { $this->map = require $file; diff --git a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php b/src/Symfony/Component/ClassLoader/UniversalClassLoader.php index 55d3527d57f79..0394fa496e294 100644 --- a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php +++ b/src/Symfony/Component/ClassLoader/UniversalClassLoader.php @@ -102,7 +102,7 @@ public function getPrefixFallback() /** * Registers the directory to use as a fallback for namespaces. * - * @return string|array $dirs A directory path or an array of directories + * @param string|array $dirs A directory path or an array of directories */ public function registerNamespaceFallback($dirs) { @@ -112,7 +112,7 @@ public function registerNamespaceFallback($dirs) /** * Registers the directory to use as a fallback for class prefixes. * - * @return string|array $dirs A directory path or an array of directories + * @param string|array $dirs A directory path or an array of directories */ public function registerPrefixFallback($dirs) { From 0d6a6b79f04eca9f19f6a1ce9e30d97101525955 Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Sun, 6 Feb 2011 14:27:05 -0800 Subject: [PATCH 37/67] PHPDoc fixes --- src/Symfony/Component/BrowserKit/Client.php | 12 ++++++------ .../Component/ClassLoader/ClassCollectionLoader.php | 6 +++--- .../Component/ClassLoader/MapFileClassLoader.php | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 9373337f710ee..72a98d165c0e2 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -269,7 +269,7 @@ protected function getScript($request) /** * Filters the request * - * @param Request The request to filter + * @param Request $request The request to filter * * @return Request */ @@ -281,7 +281,7 @@ protected function filterRequest(Request $request) /** * Filters the Response * - * @param Response The Response to filter + * @param Response $response The Response to filter * * @return Response */ @@ -293,9 +293,9 @@ protected function filterResponse($response) /** * Creates a crawler * - * @param string A uri - * @param string Content for the crawler to use - * @param string Content type + * @param string $uri A uri + * @param string $content Content for the crawler to use + * @param string $type Content type * * @return Crawler */ @@ -367,7 +367,7 @@ public function restart() /** * Takes a URI and converts it to absolute if it is not already absolute. * - * @param string A uri + * @param string $uri A uri * @return string An absolute uri */ protected function getAbsoluteUri($uri) diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index 0773eb747af06..eeb81cc90d74e 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -120,7 +120,7 @@ static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = /** * Adds brackets around each namespace if it's not already the case. * - * @param string Namespace string + * @param string $source Namespace string * * @return string Namespaces with brackets */ @@ -169,8 +169,8 @@ static public function fixNamespaceDeclarations($source) /** * Writes a cache file * - * @param string Filename - * @param string Temporary file content + * @param string $file Filename + * @param string $content Temporary file content * * @throws \RuntimeException when a cache file cannot be written */ diff --git a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php index 1cce5e22a624e..a379970578063 100644 --- a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php +++ b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php @@ -23,7 +23,7 @@ class MapFileClassLoader /** * Constructor * - * @param string Path to class mapping file + * @param string $file Path to class mapping file */ public function __construct($file) { From 803cc91b8b7dc25b9a3ad0b24e2ce331fc11f6fd Mon Sep 17 00:00:00 2001 From: ornicar Date: Sun, 6 Feb 2011 15:36:01 -0800 Subject: [PATCH 38/67] [FrameworkBundle] Fix TemplateNameParser template name exception message --- .../Bundle/FrameworkBundle/Templating/TemplateNameParser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php index ff762c31070d0..e6ecf2a3cafe6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/TemplateNameParser.php @@ -53,12 +53,12 @@ public function parse($name) $parts = explode(':', $name); if (3 !== count($parts)) { - throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.engine.format").', $name)); + throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.format.engine").', $name)); } $elements = explode('.', $parts[2]); if (3 !== count($elements)) { - throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.engine.format").', $name)); + throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid (format is "bundle:section:template.format.engine").', $name)); } $parameters = array( From 93fd935b702bbb5b94b705331f67696b6ed155fe Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Sun, 6 Feb 2011 15:58:54 -0800 Subject: [PATCH 39/67] PHPDoc style fix --- src/Symfony/Component/BrowserKit/Client.php | 6 +++--- src/Symfony/Component/BrowserKit/Response.php | 2 +- src/Symfony/Component/ClassLoader/ClassCollectionLoader.php | 2 +- src/Symfony/Component/ClassLoader/MapFileClassLoader.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index 72a98d165c0e2..dffb1e9327935 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -267,7 +267,7 @@ protected function getScript($request) } /** - * Filters the request + * Filters the request. * * @param Request $request The request to filter * @@ -279,7 +279,7 @@ protected function filterRequest(Request $request) } /** - * Filters the Response + * Filters the Response. * * @param Response $response The Response to filter * @@ -291,7 +291,7 @@ protected function filterResponse($response) } /** - * Creates a crawler + * Creates a crawler. * * @param string $uri A uri * @param string $content Content for the crawler to use diff --git a/src/Symfony/Component/BrowserKit/Response.php b/src/Symfony/Component/BrowserKit/Response.php index 26bd45f2ef452..a80129a802ae2 100644 --- a/src/Symfony/Component/BrowserKit/Response.php +++ b/src/Symfony/Component/BrowserKit/Response.php @@ -40,7 +40,7 @@ public function __construct($content = '', $status = 200, array $headers = array } /** - * Converts the response object to string containing all headers and the response content + * Converts the response object to string containing all headers and the response content. * * @return string The response with headers and content */ diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index eeb81cc90d74e..d5419a06ce769 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -167,7 +167,7 @@ static public function fixNamespaceDeclarations($source) } /** - * Writes a cache file + * Writes a cache file. * * @param string $file Filename * @param string $content Temporary file content diff --git a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php index a379970578063..0a4d5c15e64b7 100644 --- a/src/Symfony/Component/ClassLoader/MapFileClassLoader.php +++ b/src/Symfony/Component/ClassLoader/MapFileClassLoader.php @@ -21,7 +21,7 @@ class MapFileClassLoader protected $map = array(); /** - * Constructor + * Constructor. * * @param string $file Path to class mapping file */ From 2201382fa1e778e801b1db41f1588e0bd5710010 Mon Sep 17 00:00:00 2001 From: ornicar Date: Sun, 6 Feb 2011 16:18:51 -0800 Subject: [PATCH 40/67] [HttpKernel] Fix issue in SQLite profiler storage when PDO fails to prepare a statement --- .../Component/HttpKernel/Profiler/SQLiteProfilerStorage.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php index f82ecec163176..fc090d390475b 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php @@ -146,6 +146,11 @@ protected function initDb() protected function exec($db, $query, array $args = array()) { $stmt = $db->prepare($query); + + if (false === $stmt) { + throw new \RuntimeException('The database cannot successfully prepare the statement'); + } + if ($db instanceof \SQLite3) { foreach ($args as $arg => $val) { $stmt->bindValue($arg, $val, is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT); From bebdcb242df685d822b7f0c4b712f67784401879 Mon Sep 17 00:00:00 2001 From: Marc Weistroff Date: Sun, 6 Feb 2011 16:06:54 -0800 Subject: [PATCH 41/67] [HttpKernel] Added response cache-control modification if page is composed of ESIs. Rules are : - If one of the ESI has validation cache strategy, the whole page will be forced to validate. - In none of the ESI has validation, the response will feature a Cache-Control directive with s-maxage value equals to the smallest TTL of ESIs. --- .../HttpKernel/HttpCache/HttpCache.php | 36 +++++++++ .../HttpKernel/HttpCache/HttpCacheTest.php | 63 +++++++++++++++ .../HttpCache/HttpCacheTestCase.php | 18 ++++- .../HttpCache/TestMultipleHttpKernel.php | 78 +++++++++++++++++++ 4 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index f4359f0285ea9..0b97d8dfa5d15 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -31,6 +31,7 @@ class HttpCache implements HttpKernelInterface protected $store; protected $request; protected $esi; + protected $esiTtls; /** * Constructor. @@ -136,6 +137,7 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ if (HttpKernelInterface::MASTER_REQUEST === $type) { $this->traces = array(); $this->request = $request; + $this->esiTtls = array(); } $path = $request->getPathInfo(); @@ -160,9 +162,43 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ $response->headers->set('X-Symfony-Cache', $this->getLog()); } + if (null !== $this->esi) { + $this->addEsiTtl($response); + + if ($request === $this->request) { + $this->updateResponseCacheControl($response); + } + } + return $response; } + /** + * Stores the response's TTL locally. + * + * @param Response $response + */ + protected function addEsiTtl(Response $response) + { + $this->esiTtls[] = $response->isValidateable() ? 0 : $response->getTtl(); + } + + /** + * Changes the master response TTL to the smallest TTL received or force validation if + * one of the ESI has validation cache strategy. + * + * @param Response $response + */ + protected function updateResponseCacheControl(Response $response) + { + $ttl = min($this->esiTtls); + if (0 === $ttl) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + } else { + $response->setSharedMaxAge($ttl); + } + } + /** * Forwards the Request to the backend without storing the Response in the cache. * diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php index 4c0d7cfde9e42..d6a5ba7f2afaf 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php @@ -893,4 +893,67 @@ public function testShouldNotCatchExceptions() $this->assertExceptionsAreNotCaught(); } + + public function testEsiCacheSendsTheLowestTtl() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('Cache-Control' => 's-maxage=300'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals("Hello World! My name is Bobby.", $this->response->getContent()); + $this->assertEquals(100, $this->response->getTtl()); + } + + public function testEsiCacheForceValidation() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('ETag' => 'foobar'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + $this->assertEquals(null, $this->response->getTtl()); + $this->assertTrue($this->response->mustRevalidate()); + $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); + $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); + } } diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php index 1af5811cc1808..a7654a5e162f5 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTestCase.php @@ -12,8 +12,10 @@ namespace Symfony\Tests\Component\HttpKernel\HttpCache; require_once __DIR__.'/TestHttpKernel.php'; +require_once __DIR__.'/TestMultipleHttpKernel.php'; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\Esi; use Symfony\Component\HttpKernel\HttpCache\HttpCache; use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -28,12 +30,14 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase protected $response; protected $responses; protected $catch; + protected $esi; protected function setUp() { $this->kernel = null; $this->cache = null; + $this->esi = null; $this->caches = array(); $this->cacheConfig = array(); @@ -41,6 +45,7 @@ protected function setUp() $this->response = null; $this->responses = array(); + $this->catch = false; $this->clearDirectory(sys_get_temp_dir().'/http_cache'); @@ -101,7 +106,7 @@ public function assertExceptionsAreNotCaught() $this->assertFalse($this->kernel->isCatchingExceptions()); } - public function request($method, $uri = '/', $server = array(), $cookies = array()) + public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false) { if (null === $this->kernel) { throw new \LogicException('You must call setNextResponse() before calling request().'); @@ -112,7 +117,9 @@ public function request($method, $uri = '/', $server = array(), $cookies = array $this->store = new Store(sys_get_temp_dir().'/http_cache'); $this->cacheConfig['debug'] = true; - $this->cache = new HttpCache($this->kernel, $this->store, null, $this->cacheConfig); + + $this->esi = $esi ? new Esi() : null; + $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); $this->request = Request::create($uri, $method, array(), $cookies, array(), $server); $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); @@ -133,11 +140,14 @@ public function getMetaStorageValues() // A basic response with 200 status code and a tiny body. public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null) { - $called = false; - $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer); } + public function setNextResponses($responses) + { + $this->kernel = new TestMultipleHttpKernel($responses); + } + public function catchExceptions($catch = true) { $this->catch = $catch; diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php new file mode 100644 index 0000000000000..899e8c2b57dd7 --- /dev/null +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/TestMultipleHttpKernel.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; + +class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface +{ + protected $bodies; + protected $statuses; + protected $headers; + protected $catch; + protected $call; + + public function __construct($responses) + { + $this->bodies = array(); + $this->statuses = array(); + $this->headers = array(); + $this->call = false; + + foreach ($responses as $response) { + $this->bodies[] = $response['body']; + $this->statuses[] = $response['status']; + $this->headers[] = $response['headers']; + } + + parent::__construct(new EventDispatcher(), $this); + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + return parent::handle($request, $type, $catch); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers)); + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->call = false; + } +} From ea4ab77b6d0f6983b67aa7bd856d3873258c6913 Mon Sep 17 00:00:00 2001 From: Marc Weistroff Date: Sun, 6 Feb 2011 16:42:50 -0800 Subject: [PATCH 42/67] [HttpKernel] HttpCache now sends maxage=0 cache-control directive in case of Esi presence. --- src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 0b97d8dfa5d15..cadbfa9f83263 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -180,7 +180,7 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ */ protected function addEsiTtl(Response $response) { - $this->esiTtls[] = $response->isValidateable() ? 0 : $response->getTtl(); + $this->esiTtls[] = $response->isValidateable() ? -1 : $response->getTtl(); } /** @@ -192,10 +192,11 @@ protected function addEsiTtl(Response $response) protected function updateResponseCacheControl(Response $response) { $ttl = min($this->esiTtls); - if (0 === $ttl) { + if (-1 === $ttl) { $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); } else { $response->setSharedMaxAge($ttl); + $response->setMaxAge(0); } } From 4252680ccf2e07bbe4c1929c927b393083fba53e Mon Sep 17 00:00:00 2001 From: ornicar Date: Sun, 6 Feb 2011 16:30:43 -0800 Subject: [PATCH 43/67] [HttpKernel] Catch exception when SQLite profiler storage fails to cleanup --- .../Component/HttpKernel/Profiler/SQLiteProfilerStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php index fc090d390475b..0840f8649d22d 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/SQLiteProfilerStorage.php @@ -94,11 +94,11 @@ public function write($token, $data, $ip, $url, $time) ); try { $this->exec($db, 'INSERT INTO data (token, data, ip, url, time, created_at) VALUES (:token, :data, :ip, :url, :time, :created_at)', $args); + $this->cleanup(); $status = true; } catch (\Exception $e) { $status = false; } - $this->cleanup(); $this->close($db); return $status; From f3b6e1a30c9fa4c3a320206fdc0d480baee49e0e Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Sun, 6 Feb 2011 16:34:24 -0800 Subject: [PATCH 44/67] PHPDoc for Console --- src/Symfony/Component/Console/Application.php | 27 +++++++++++++++++-- .../Component/Console/Command/HelpCommand.php | 9 +++++-- .../Component/Console/Command/ListCommand.php | 4 +-- .../Console/Helper/FormatterHelper.php | 9 +++++++ src/Symfony/Component/Console/Input/Input.php | 12 +++++++++ .../Console/Input/InputDefinition.php | 7 +++++ .../Console/Input/InputInterface.php | 7 +++++ .../Component/Console/Input/StringInput.php | 3 +++ .../Component/Console/Output/Output.php | 13 +++++++++ 9 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 89c31bade7fa0..d7509de85b245 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -201,7 +201,7 @@ public function setHelperSet(HelperSet $helperSet) } /** - * Get the helper set associated with the command + * Get the helper set associated with the command. * * @return HelperSet The HelperSet instance associated with this command */ @@ -396,7 +396,7 @@ public function get($name) } /** - * Returns true if the command exists, false otherwise + * Returns true if the command exists, false otherwise. * * @param string $name The command name or alias * @@ -429,6 +429,8 @@ public function getNamespaces() /** * Finds a registered namespace by a name or an abbreviation. * + * @param string $namespace A namespace or abbreviation to search for + * * @return string A registered namespace * * @throws \InvalidArgumentException When namespace is incorrect or ambiguous @@ -715,11 +717,25 @@ public function renderException($e, $output) } } + /** + * Gets the name of the command based on input. + * + * @param InputInterface $input The input interface + * + * @return string The command name + */ protected function getCommandName(InputInterface $input) { return $input->getFirstArgument('command'); } + /** + * Sorts commands in alphabetical order. + * + * @param array $commands An associative array of commands to sort + * + * @return array A sorted array of commands + */ protected function sortCommands($commands) { $namespacedCommands = array(); @@ -741,6 +757,13 @@ protected function sortCommands($commands) return $namespacedCommands; } + /** + * Returns abbreviated suggestions in string format. + * + * @param array $abbrevs Abbreviated suggestions to convert + * + * @return string A formatted string of abbreviated suggestions + */ protected function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); diff --git a/src/Symfony/Component/Console/Command/HelpCommand.php b/src/Symfony/Component/Console/Command/HelpCommand.php index 49e91782dcde0..61ff032c0a383 100644 --- a/src/Symfony/Component/Console/Command/HelpCommand.php +++ b/src/Symfony/Component/Console/Command/HelpCommand.php @@ -28,7 +28,7 @@ class HelpCommand extends Command protected $command; /** - * @see Command + * {@inheritdoc} */ protected function configure() { @@ -54,13 +54,18 @@ protected function configure() ); } + /** + * Sets the command + * + * @param Command $command The command to set + */ public function setCommand(Command $command) { $this->command = $command; } /** - * @see Command + * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { diff --git a/src/Symfony/Component/Console/Command/ListCommand.php b/src/Symfony/Component/Console/Command/ListCommand.php index 3d2ee7bb159f8..76fb7cbcefdba 100644 --- a/src/Symfony/Component/Console/Command/ListCommand.php +++ b/src/Symfony/Component/Console/Command/ListCommand.php @@ -26,7 +26,7 @@ class ListCommand extends Command { /** - * @see Command + * {@inheritdoc} */ protected function configure() { @@ -54,7 +54,7 @@ protected function configure() } /** - * @see Command + * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { diff --git a/src/Symfony/Component/Console/Helper/FormatterHelper.php b/src/Symfony/Component/Console/Helper/FormatterHelper.php index 7da190631fad9..5a05aa049a994 100644 --- a/src/Symfony/Component/Console/Helper/FormatterHelper.php +++ b/src/Symfony/Component/Console/Helper/FormatterHelper.php @@ -67,6 +67,13 @@ public function formatBlock($messages, $style, $large = false) return implode("\n", $messages); } + /** + * Returns the length of a string, uses mb_strlen if it is available. + * + * @param string $string The string to check its length + * + * @return integer The length of the string + */ protected function strlen($string) { return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string); @@ -74,6 +81,8 @@ protected function strlen($string) /** * Returns the helper's canonical name + * + * @return string The canonical name of the helper */ public function getName() { diff --git a/src/Symfony/Component/Console/Input/Input.php b/src/Symfony/Component/Console/Input/Input.php index 8fcf9899251f6..aba45273a7bc9 100644 --- a/src/Symfony/Component/Console/Input/Input.php +++ b/src/Symfony/Component/Console/Input/Input.php @@ -64,6 +64,8 @@ public function bind(InputDefinition $definition) abstract protected function parse(); /** + * Validates the input. + * * @throws \RuntimeException When not enough arguments are given */ public function validate() @@ -73,11 +75,21 @@ public function validate() } } + /** + * Checks if the input is interactive. + * + * @return Boolean Returns true if the input is interactive + */ public function isInteractive() { return $this->interactive; } + /** + * Sets the input interactivity. + * + * @param Boolean $interactive If the input should be interactive + */ public function setInteractive($interactive) { $this->interactive = (Boolean) $interactive; diff --git a/src/Symfony/Component/Console/Input/InputDefinition.php b/src/Symfony/Component/Console/Input/InputDefinition.php index a4ed4ae3a2a9b..9a7baff2a60e5 100644 --- a/src/Symfony/Component/Console/Input/InputDefinition.php +++ b/src/Symfony/Component/Console/Input/InputDefinition.php @@ -42,6 +42,11 @@ public function __construct(array $definition = array()) $this->setDefinition($definition); } + /** + * Sets the definition of the input. + * + * @param array $definition The definition array + */ public function setDefinition(array $definition) { $arguments = array(); @@ -297,6 +302,8 @@ public function hasShortcut($name) /** * Gets an InputOption by shortcut. * + * @param string $shortcut the Shortcut name + * * @return InputOption An InputOption object */ public function getOptionForShortcut($shortcut) diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index bf943716a4e1a..1027507ff7a3a 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php @@ -63,6 +63,7 @@ function getArguments(); /** * Get argument by name. * + * @param string $name The name of the argument * @return mixed */ function getArgument($name); @@ -72,6 +73,12 @@ function getArgument($name); */ function getOptions(); + /** + * Get an option by name. + * + * @param string $name The name of the option + * @return mixed + */ function getOption($name); /** diff --git a/src/Symfony/Component/Console/Input/StringInput.php b/src/Symfony/Component/Console/Input/StringInput.php index 64d9772af2d99..7a7575996b9c8 100644 --- a/src/Symfony/Component/Console/Input/StringInput.php +++ b/src/Symfony/Component/Console/Input/StringInput.php @@ -39,6 +39,9 @@ public function __construct($input, InputDefinition $definition = null) } /** + * Tokenizes a string. + * + * @param string $input The input to tokenise * @throws \InvalidArgumentException When unable to parse input (should never happen) */ protected function tokenize($input) diff --git a/src/Symfony/Component/Console/Output/Output.php b/src/Symfony/Component/Console/Output/Output.php index 388848614af0c..acc1a2f03c9b2 100644 --- a/src/Symfony/Component/Console/Output/Output.php +++ b/src/Symfony/Component/Console/Output/Output.php @@ -179,6 +179,12 @@ protected function format($message) } /** + * Replaces the starting style of the output. + * + * @param array $match + * + * @return string The replaced style + * * @throws \InvalidArgumentException When style is unknown */ protected function replaceStartStyle($match) @@ -220,6 +226,13 @@ protected function replaceStartStyle($match) return "\033[".implode(';', $codes).'m'; } + /** + * Replaces the end style. + * + * @param string $match The text to match + * + * @return string The end style + */ protected function replaceEndStyle($match) { if (!$this->decorated) { From 41bf849a637d9434f859eeafd816fec4a0ab51be Mon Sep 17 00:00:00 2001 From: pborreli Date: Mon, 7 Feb 2011 01:40:20 +0000 Subject: [PATCH 45/67] [HttpFoundation] Request coverage --- .../Component/HttpFoundation/RequestTest.php | 192 +++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) diff --git a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php index 59be4968a83d1..467e7c8735ea7 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/RequestTest.php @@ -54,28 +54,37 @@ public function testCreate() $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('bar=baz', $request->getQueryString()); $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); $request = Request::create('https://test.com/foo?bar=baz'); $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri()); $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('bar=baz', $request->getQueryString()); $this->assertEquals(443, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertTrue($request->isSecure()); $request = Request::create('test.com:90/foo'); $this->assertEquals('http://test.com:90/foo', $request->getUri()); $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); $this->assertEquals(90, $request->getPort()); + $this->assertFalse($request->isSecure()); $request = Request::create('https://test.com:90/foo'); $this->assertEquals('https://test.com:90/foo', $request->getUri()); $this->assertEquals('/foo', $request->getPathInfo()); $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json); $this->assertEquals($json, $request->getContent()); + $this->assertFalse($request->isSecure()); } /** @@ -101,6 +110,7 @@ public function testDuplicate() /** * @covers Symfony\Component\HttpFoundation\Request::getFormat + * @covers Symfony\Component\HttpFoundation\Request::setFormat * @dataProvider getFormatToMimeTypeMapProvider */ public function testGetFormatFromMimeType($format, $mimeTypes) @@ -109,7 +119,10 @@ public function testGetFormatFromMimeType($format, $mimeTypes) foreach ($mimeTypes as $mime) { $this->assertEquals($format, $request->getFormat($mime)); } - + $request->setFormat($format, $mimeTypes); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + } } /** @@ -322,6 +335,7 @@ public function testGetUriForPath() $request->initialize(array(), array(), array(), array(), array(), $server); $this->assertEquals('http://hostname/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER'); + $this->assertEquals('hostname', $request->getHttpHost()); } /** @@ -420,6 +434,23 @@ public function testGetSetMethod() $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); } + public function testGetClientIp() + { + $request = new Request; + $this->assertEquals('', $request->getClientIp()); + $this->assertEquals('', $request->getClientIp(true)); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '88.88.88.88')); + $this->assertEquals('88.88.88.88', $request->getClientIp()); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_CLIENT_IP' => '88.88.88.88')); + $this->assertEquals('127.0.0.1', $request->getClientIp()); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_CLIENT_IP' => '88.88.88.88')); + $this->assertEquals('88.88.88.88', $request->getClientIp(true)); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_FOR' => '88.88.88.88')); + $this->assertEquals('127.0.0.1', $request->getClientIp()); + $request->initialize(array(), array(), array(), array(), array(), array('REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_FORWARDED_FOR' => '88.88.88.88')); + $this->assertEquals('88.88.88.88', $request->getClientIp(true)); + } + public function testGetContentWorksTwiceInDefaultMode() { $req = new Request; @@ -473,4 +504,163 @@ public function testCreateFromGlobals() unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); } + + public function testOverrideGlobals() + { + $time = $_SERVER['REQUEST_TIME']; // fix for phpunit timer + + $request = new Request(); + $request->initialize(array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + + $request->initialize(array(), array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_POST); + + $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('X_FORWARDED_PROTO', 'https'); + + $this->assertTrue($request->isSecure()); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $_SERVER['REQUEST_TIME'] = $time; // fix for phpunit timer + } + + public function testGetScriptName() + { + $request = new Request(); + $this->assertEquals('', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + + $server = array(); + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/frontend.php', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + } + + public function testGetBasePath() + { + $request = new Request(); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['PHP_SELF'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + } + + public function testGetPreferredLanguage() + { + $request = new Request(); + $this->assertEquals('', $request->getPreferredLanguage()); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr'))); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en'))); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr'))); + $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + } + + public function testIsXmlHttpRequest() + { + $request = new Request(); + $this->assertFalse($request->isXmlHttpRequest()); + + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + $this->assertTrue($request->isXmlHttpRequest()); + + $request->headers->remove('X-Requested-With'); + $this->assertFalse($request->isXmlHttpRequest()); + } + + public function testGetCharsets() + { + $request = new Request(); + $this->assertEquals(array(), $request->getCharsets()); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array(), $request->getCharsets()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets()); + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'); + $this->assertEquals(array('ISO-8859-1', '*', 'utf-8'), $request->getCharsets()); + } + + public function testGetAcceptableContentTypes() + { + $request = new Request(); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching + + $request = new Request(); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array('multipart/mixed', '*/*', 'text/html', 'application/xhtml+xml', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/vnd.wap.wmlscriptc'), $request->getAcceptableContentTypes()); + } + + public function testGetLanguages() + { + $request = new Request(); + $this->assertNull($request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6'); + $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages()); + } } From 9f30e42c16ad24f5fd4dc512d2a85ffb785e5c26 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 7 Feb 2011 03:03:02 +0100 Subject: [PATCH 46/67] added --debug/-d and --env/-d to console --- .../FrameworkBundle/Console/Application.php | 4 ++- .../Component/Console/Input/ArgvInput.php | 32 +++++++++++++++++++ .../Component/Console/Input/ArrayInput.php | 27 ++++++++++++++++ .../Console/Input/InputInterface.php | 12 +++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php index 118bbfaeb6696..806bd1835ef37 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Application.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Application.php @@ -34,9 +34,11 @@ public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; - parent::__construct('Symfony', Kernel::VERSION.' - '.$kernel->getName()); + parent::__construct('Symfony', Kernel::VERSION.' - '.$kernel->getName().'/'.$kernel->getEnvironment().($kernel->isDebug() ? '/debug' : '')); $this->definition->addOption(new InputOption('--shell', '-s', InputOption::VALUE_NONE, 'Launch the shell.')); + $this->definition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev')); + $this->definition->addOption(new InputOption('--debug', '-d', InputOption::VALUE_NONE, 'Whether to run in debug mode.')); $this->kernel->boot(); diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 5662e5f69bdcb..ddaeb2b99e4da 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -252,4 +252,36 @@ public function hasParameterOption($values) return false; } + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before it has been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * + * @return mixed The option value + */ + public function getParameterOption(array $values, $default = false) + { + if (!is_array($values)) { + $values = array($values); + } + + $tokens = $this->tokens; + while ($token = array_shift($tokens)) { + foreach ($values as $value) { + if (0 === strpos($token, $value)) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } else { + return array_shift($this->tokens); + } + } + } + } + + return $default; + } } diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index e9572d71ed297..407f3f243270a 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -82,6 +82,33 @@ public function hasParameterOption($values) return false; } + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before it has been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * + * @return mixed The option value + */ + public function getParameterOption(array $values, $default = false) + { + if (!is_array($values)) { + $values = array($values); + } + + foreach ($this->parameters as $k => $v) { + if (is_int($k) && in_array($v, $values)) { + return true; + } elseif (in_array($k, $values)) { + return $v; + } + } + + return $default; + } + /** * Processes command line arguments. */ diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index 1027507ff7a3a..3fd6800c2a87b 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php @@ -37,6 +37,18 @@ function getFirstArgument(); */ function hasParameterOption($value); + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before it has been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * + * @return mixed The option value + */ + function getParameterOption(array $values, $default = false); + /** * Binds the current Input instance with the given arguments and options. * From 4c651619537a896c53ab54e29084b92951f408eb Mon Sep 17 00:00:00 2001 From: Xavier Perez Date: Mon, 7 Feb 2011 13:57:27 +0100 Subject: [PATCH 47/67] [Yaml] Added PHPDoc on getTimestampRegex() --- src/Symfony/Component/Yaml/Inline.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index bb7f3057fc7e5..9cc7b5932b21a 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -379,6 +379,11 @@ static protected function evaluateScalar($scalar) } } + /** + * Get a regex that match a unix timestamp + * + * @return string The regular expression + */ static protected function getTimestampRegex() { return << Date: Mon, 7 Feb 2011 14:11:46 +0100 Subject: [PATCH 48/67] [Templating] Added some missing PHPDoc --- .../Component/Templating/DelegatingEngine.php | 9 +++++++++ .../Component/Templating/PhpEngine.php | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Templating/DelegatingEngine.php b/src/Symfony/Component/Templating/DelegatingEngine.php index 9c558961dea7f..8717ef0f3d620 100644 --- a/src/Symfony/Component/Templating/DelegatingEngine.php +++ b/src/Symfony/Component/Templating/DelegatingEngine.php @@ -103,6 +103,15 @@ public function supports($name) return false; } + /** + * Get an engine able to render the given template. + * + * @param string $name A template name + * + * @return EngineInterface The engine + * + * @throws \RuntimeException if no engine able to work with the template is found + */ protected function getEngine($name) { foreach ($this->engines as $engine) { diff --git a/src/Symfony/Component/Templating/PhpEngine.php b/src/Symfony/Component/Templating/PhpEngine.php index c38898383d671..4fbaf13bb06c4 100644 --- a/src/Symfony/Component/Templating/PhpEngine.php +++ b/src/Symfony/Component/Templating/PhpEngine.php @@ -221,6 +221,11 @@ public function addHelpers(array $helpers = array()) } } + /** + * Sets the helpers. + * + * @params Helper[] $helpers An array of helper + */ public function setHelpers(array $helpers) { $this->helpers = array(); @@ -286,7 +291,8 @@ public function extend($template) /** * Escapes a string by using the current charset. * - * @param mixed $value A variable to escape + * @param mixed $value A variable to escape + * @param string $context The context name * * @return string The escaped value */ @@ -440,6 +446,17 @@ function ($value) use ($that) ); } + /** + * Convert a string from one encoding to another. + * + * @param string $string The string to convert + * @param string $to The input encoding + * @param string $from The output encoding + * + * @return string The string with the new encoding + * + * @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring) + */ public function convertEncoding($string, $to, $from) { if (function_exists('iconv')) { From 734be8107cb9e7b292794f74d81686d9434f2090 Mon Sep 17 00:00:00 2001 From: Bulat Shakirzyanov Date: Mon, 7 Feb 2011 19:49:07 -0500 Subject: [PATCH 49/67] [DoctrineMongoDBBundle] fixed UniqueValidatorTest to mark skipped if Doctrine\ODM classes not found --- .../Tests/Validator/Constraints/UniqueValidatorTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php index 4af23affe6d8c..f3e0d6fae7e1c 100755 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/Validator/Constraints/UniqueValidatorTest.php @@ -5,10 +5,11 @@ use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\DocumentRepository; use Symfony\Bundle\DoctrineMongoDBBundle\Tests\Fixtures\Validator\Document; +use Symfony\Bundle\DoctrineMongoDBBundle\Tests\TestCase; use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\Unique; use Symfony\Bundle\DoctrineMongoDBBundle\Validator\Constraints\UniqueValidator; -class UniqueValidatorTest extends \PHPUnit_Framework_TestCase +class UniqueValidatorTest extends TestCase { private $dm; private $repository; @@ -18,6 +19,7 @@ class UniqueValidatorTest extends \PHPUnit_Framework_TestCase public function setUp() { + parent::setUp(); $this->classMetadata = $this->getClassMetadata(); $this->repository = $this->getDocumentRepository(); $this->dm = $this->getDocumentManager($this->classMetadata, $this->repository); From d447d22809e3751c5d6c13868c77a1657a8f04bd Mon Sep 17 00:00:00 2001 From: Bulat Shakirzyanov Date: Mon, 7 Feb 2011 19:43:45 -0500 Subject: [PATCH 50/67] [DoctrineBundle, DoctrineAbstractBundle, DoctrineMongoDBBundle] added container aware fixture loader Updated load data fixtures command in DoctrineMongoDBBundle to be identical to the one in DoctrineBundle Created custom loader, that passes $container to all ContainerAware DataFixtures --- .../Common/DataFixtures/Loader.php | 27 +++++++++++++++++++ .../LoadDataFixturesDoctrineCommand.php | 2 +- .../LoadDataFixturesDoctrineODMCommand.php | 8 +++--- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php new file mode 100644 index 0000000000000..80d12cc313814 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php @@ -0,0 +1,27 @@ +container = $container; + } + + public function addFixture(FixtureInterface $fixture) + { + if ($fixture instanceof ContainerAwareInterface) { + $fixture->setContainer($container); + } + + parent::addFixture($fixture); + } +} diff --git a/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php b/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php index f0557b34eff04..9f32ff281c87d 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php +++ b/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php @@ -72,7 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $loader = new \Doctrine\Common\DataFixtures\Loader(); + $loader = $loader = new \Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader($this->container); foreach ($paths as $path) { if (is_dir($path)) { $loader->loadFromDirectory($path); diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php index 51609ab4d3771..a910d1c041c54 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php @@ -72,11 +72,11 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $paths = array_filter($paths, 'is_dir'); - - $loader = new \Doctrine\Common\DataFixtures\Loader(); + $loader = new \Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader($this->container); foreach ($paths as $path) { - $loader->loadFromDirectory($path); + if (is_dir($path)) { + $loader->loadFromDirectory($path); + } } $fixtures = $loader->getFixtures(); $purger = new \Doctrine\Common\DataFixtures\Purger\MongoDBPurger($dm); From 97679e5bdab5f2494d6f444d995ae79dd4ccb3ca Mon Sep 17 00:00:00 2001 From: Bulat Shakirzyanov Date: Mon, 7 Feb 2011 20:03:00 -0500 Subject: [PATCH 51/67] [DoctrineAbstractBundle] added test for new Loader --- .../Common/DataFixtures/Loader.php | 2 +- .../Tests/Common/ContainerAwareFixture.php | 21 +++++++++++++++++++ .../Tests/Common/DataFixtures/LoaderTest.php | 21 +++++++++++++++++++ .../DoctrineAbstractBundle/Tests/TestCase.php | 13 ++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php create mode 100644 src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php create mode 100644 src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php index 80d12cc313814..1e7c2f754b2f5 100644 --- a/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Common/DataFixtures/Loader.php @@ -19,7 +19,7 @@ public function __construct(ContainerInterface $container) public function addFixture(FixtureInterface $fixture) { if ($fixture instanceof ContainerAwareInterface) { - $fixture->setContainer($container); + $fixture->setContainer($this->container); } parent::addFixture($fixture); diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php new file mode 100644 index 0000000000000..51a873cd56dac --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/ContainerAwareFixture.php @@ -0,0 +1,21 @@ +container = $container; + } + + public function load($manager) + { + } +} diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php new file mode 100644 index 0000000000000..8bef32a6e230c --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/Common/DataFixtures/LoaderTest.php @@ -0,0 +1,21 @@ +getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $loader = new Loader($container); + $fixture = new ContainerAwareFixture(); + + $loader->addFixture($fixture); + + $this->assertSame($container, $fixture->container); + } +} diff --git a/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php new file mode 100644 index 0000000000000..17326b6d5a876 --- /dev/null +++ b/src/Symfony/Bundle/DoctrineAbstractBundle/Tests/TestCase.php @@ -0,0 +1,13 @@ +markTestSkipped('Doctrine Data Fixtures is not available.'); + } + } +} From ef43b225465d966ff9f6c438f524f3b59e2d6383 Mon Sep 17 00:00:00 2001 From: Kris Wallsmith Date: Mon, 7 Feb 2011 13:50:20 -0800 Subject: [PATCH 52/67] [HttpFoundation] fixed test cleanup --- .../HttpFoundation/File/MimeType/MimeTypeTest.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php b/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php index a56bc9ac3c4fc..957f2c0375e59 100644 --- a/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php +++ b/tests/Symfony/Tests/Component/HttpFoundation/File/MimeType/MimeTypeTest.php @@ -65,13 +65,14 @@ public function testGuessWithNonReadablePath() $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); MimeTypeGuesser::getInstance()->guess($path); - - } + public static function tearDownAfterClass() { $path = __DIR__.'/../Fixtures/to_delete'; - chmod($path, 0666); - @unlink($path); + if (file_exists($path)) { + chmod($path, 0666); + @unlink($path); + } } -} \ No newline at end of file +} From 59bf50bf418a28d2d0a42ad13318e363dbf74b60 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 7 Feb 2011 17:44:16 +0100 Subject: [PATCH 53/67] [Form] Disabled by reference handling for arrays --- src/Symfony/Component/Form/Form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 07e7f1a40d584..ead59ddaa2512 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -895,7 +895,7 @@ public function writeProperty(&$objectOrArray) // Don't update parent if data is a composite type (object or array) // and "by_reference" option is true, because then we expect that // we are working with a reference to the parent's data - if (!(is_object($data) || is_array($data)) || !$this->getOption('by_reference')) { + if (!is_object($data) || !$this->getOption('by_reference')) { parent::writeProperty($objectOrArray); } } From 09a50c3c55257edb95c102cb435e353f0b118208 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 7 Feb 2011 18:36:29 +0100 Subject: [PATCH 54/67] [Form] Fixed: "by_reference" option is ignored if reading from/writing to an array --- src/Symfony/Component/Form/Form.php | 2 +- .../Symfony/Tests/Component/Form/FormTest.php | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index ead59ddaa2512..fd1f1d11a3b6d 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -895,7 +895,7 @@ public function writeProperty(&$objectOrArray) // Don't update parent if data is a composite type (object or array) // and "by_reference" option is true, because then we expect that // we are working with a reference to the parent's data - if (!is_object($data) || !$this->getOption('by_reference')) { + if (!is_object($data) || !is_object($objectOrArray) || !$this->getOption('by_reference')) { parent::writeProperty($objectOrArray); } } diff --git a/tests/Symfony/Tests/Component/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php index b56f7f8e521aa..711274a14de3c 100644 --- a/tests/Symfony/Tests/Component/Form/FormTest.php +++ b/tests/Symfony/Tests/Component/Form/FormTest.php @@ -1258,6 +1258,29 @@ public function testSubformCallsSettersIfReferenceIsScalar() $this->assertEquals('foobar', $author->getReferenceCopy()); } + public function testSubformAlwaysInsertsIntoArrays() + { + $ref1 = new Author(); + $ref2 = new Author(); + $author = array('referenceCopy' => $ref1); + + $form = new Form('author', array('validator' => $this->createMockValidator())); + $form->setData($author); + $refForm = new FormTest_FormThatReturns('referenceCopy'); + $refForm->setReturnValue($ref2); + $form->add($refForm); + + $form->bind($this->createPostRequest(array( + 'author' => array( + 'referenceCopy' => array(), // doesn't matter actually + ) + ))); + + // the new reference was inserted into the array + $author = $form->getData(); + $this->assertSame($ref2, $author['referenceCopy']); + } + /** * Create a group containing two fields, "visibleField" and "hiddenField" * From 5b95805340822da53aacc59229b747d590b26037 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 7 Feb 2011 18:52:37 +0100 Subject: [PATCH 55/67] [Form] Added option "data_constructor" to Form. When a Form is initialized with NULL, an object is automatically created using this constructor. If no constructor is given but the option "data_class" is set, an object of that class is created with the default constructor instead. --- src/Symfony/Component/Form/Form.php | 20 +++++++++++ .../Symfony/Tests/Component/Form/FormTest.php | 35 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index fd1f1d11a3b6d..ebd69f550ddb5 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -58,6 +58,12 @@ class Form extends Field implements \IteratorAggregate, FormInterface */ protected $dataClass; + /** + * Stores the constructor closure for creating new domain object instances + * @var \Closure + */ + protected $dataConstructor; + /** * The context used when creating the form * @var FormContext @@ -86,6 +92,7 @@ public static function create(FormContextInterface $context, $name = null, array public function __construct($name = null, array $options = array()) { $this->addOption('data_class'); + $this->addOption('data_constructor'); $this->addOption('csrf_field_name', '_token'); $this->addOption('csrf_provider'); $this->addOption('field_factory'); @@ -103,6 +110,14 @@ public function __construct($name = null, array $options = array()) $this->dataClass = $options['data_class']; } + if (isset($options['data_constructor'])) { + $this->dataConstructor = $options['data_constructor']; + } else { + $this->dataConstructor = function ($class) { + return new $class(); + }; + } + parent::__construct($name, $options); // Enable CSRF protection @@ -341,6 +356,11 @@ protected function getFieldsByVisibility($hidden, $recursive) */ public function setData($data) { + if (empty($data) && $this->dataClass) { + $constructor = $this->dataConstructor; + $data = $constructor($this->dataClass); + } + parent::setData($data); // get transformed data and pass its values to child fields diff --git a/tests/Symfony/Tests/Component/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php index 711274a14de3c..e708824d8e9e8 100644 --- a/tests/Symfony/Tests/Component/Form/FormTest.php +++ b/tests/Symfony/Tests/Component/Form/FormTest.php @@ -991,6 +991,41 @@ public function testSetDataMatchesAgainstDataClass_succeeds() $form->setData(new Author()); } + public function testSetDataToNull() + { + $form = new Form('author'); + $form->setData(null); + + $this->assertNull($form->getData()); + } + + public function testSetDataToNullCreatesObjectIfClassAvailable() + { + $form = new Form('author', array( + 'data_class' => 'Symfony\Tests\Component\Form\Fixtures\Author', + )); + $form->setData(null); + + $this->assertEquals(new Author(), $form->getData()); + } + + public function testSetDataToNullUsesDataConstructorOption() + { + $test = $this; + $author = new Author(); + $form = new Form('author', array( + 'data_class' => 'Symfony\Tests\Component\Form\Fixtures\Author', + 'data_constructor' => function ($class) use ($test, $author) { + $test->assertEquals('Symfony\Tests\Component\Form\Fixtures\Author', $class); + + return $author; + } + )); + $form->setData(null); + + $this->assertSame($author, $form->getData()); + } + public function testSubmitUpdatesTransformedDataFromAllFields() { $originalAuthor = new Author(); From f51dafca3f181249ef58acb18d242a037a45371f Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 7 Feb 2011 19:01:11 +0100 Subject: [PATCH 56/67] [Form] Fixed: "data_constructor" option is used even if "data_class" option is not set --- src/Symfony/Component/Form/Form.php | 15 ++++++++------- tests/Symfony/Tests/Component/Form/FormTest.php | 6 +----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index ebd69f550ddb5..1cd9f7ff262fb 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -112,10 +112,6 @@ public function __construct($name = null, array $options = array()) if (isset($options['data_constructor'])) { $this->dataConstructor = $options['data_constructor']; - } else { - $this->dataConstructor = function ($class) { - return new $class(); - }; } parent::__construct($name, $options); @@ -356,9 +352,14 @@ protected function getFieldsByVisibility($hidden, $recursive) */ public function setData($data) { - if (empty($data) && $this->dataClass) { - $constructor = $this->dataConstructor; - $data = $constructor($this->dataClass); + if (empty($data)) { + if ($this->dataConstructor) { + $constructor = $this->dataConstructor; + $data = $constructor(); + } else if ($this->dataClass) { + $class = $this->dataClass; + $data = new $class(); + } } parent::setData($data); diff --git a/tests/Symfony/Tests/Component/Form/FormTest.php b/tests/Symfony/Tests/Component/Form/FormTest.php index e708824d8e9e8..da609f48b27ba 100644 --- a/tests/Symfony/Tests/Component/Form/FormTest.php +++ b/tests/Symfony/Tests/Component/Form/FormTest.php @@ -1011,13 +1011,9 @@ public function testSetDataToNullCreatesObjectIfClassAvailable() public function testSetDataToNullUsesDataConstructorOption() { - $test = $this; $author = new Author(); $form = new Form('author', array( - 'data_class' => 'Symfony\Tests\Component\Form\Fixtures\Author', - 'data_constructor' => function ($class) use ($test, $author) { - $test->assertEquals('Symfony\Tests\Component\Form\Fixtures\Author', $class); - + 'data_constructor' => function () use ($author) { return $author; } )); From 9f52333bf681d92d285920f68ab0beaa0a46ede1 Mon Sep 17 00:00:00 2001 From: Marc Weistroff Date: Tue, 8 Feb 2011 10:09:50 -0800 Subject: [PATCH 57/67] [HttpKernel] Fixed DataCollector Request attributes when attributes have objects --- .../HttpKernel/DataCollector/RequestDataCollector.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index 0af971fbe8b0a..dfe47a59cbd89 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -39,6 +39,11 @@ public function collect(Request $request, Response $response, \Exception $except $responseHeaders['Set-Cookie'] = $cookies; } + $attributes = array(); + foreach ($request->attributes->all() as $key => $value) { + $attributes[$key] = is_object($value) ? sprintf('Object(%s)', get_class($value)) : $value; + } + $this->data = array( 'format' => $request->getRequestFormat(), 'content_type' => $response->headers->get('Content-Type') ? $response->headers->get('Content-Type') : 'text/html', @@ -48,7 +53,7 @@ public function collect(Request $request, Response $response, \Exception $except 'request_headers' => $request->headers->all(), 'request_server' => $request->server->all(), 'request_cookies' => $request->cookies->all(), - 'request_attributes' => $request->attributes->all(), + 'request_attributes' => $attributes, 'response_headers' => $responseHeaders, 'session_attributes' => $request->hasSession() ? $request->getSession()->getAttributes() : array(), ); From f35f1e1f3ca14267279fbd7923ff658eafe779e0 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 8 Feb 2011 22:36:36 +0100 Subject: [PATCH 58/67] [FrameworkBundle] fixed init:bundle command --- .../Bundle/FrameworkBundle/Command/InitBundleCommand.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php index deb6a1639befb..500eb99eda21c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/InitBundleCommand.php @@ -71,16 +71,18 @@ protected function execute(InputInterface $input, OutputInterface $output) // user specified bundle name? $bundle = $input->getArgument('bundleName'); - if ('' === $bundle) { + if (!$bundle) { $bundle = strtr($namespace, array('\\' => '')); - } elseif (!preg_match('/Bundle$/', $bundle)) { + } + + if (!preg_match('/Bundle$/', $bundle)) { throw new \InvalidArgumentException('The bundle name must end with Bundle.'); } $dir = $input->getArgument('dir'); // add trailing / if necessary - $dir = '/' === substr($dir, -1, 1) ? $dir : $dir . '/'; + $dir = '/' === substr($dir, -1, 1) ? $dir : $dir.'/'; $targetDir = $dir.strtr($namespace, '\\', '/'); From 7e0ed2759b246406e96c23b7496ba54d13b3b8a7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 8 Feb 2011 22:36:58 +0100 Subject: [PATCH 59/67] [HttpKernel] fixed phpdoc --- .../HttpKernel/Controller/ControllerResolverInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php index fc7b1de3238e0..0f44beb246abf 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php @@ -49,6 +49,8 @@ function getController(Request $request); * @param Request $request A Request instance * @param mixed $controller A PHP callable * + * @return array An array of arguments to pass to the controller + * * @throws \RuntimeException When value for argument given is not provided */ function getArguments(Request $request, $controller); From fae8a557f9dc45e6bcb9faa9acf0cf2ce3c7d04a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 8 Feb 2011 22:37:21 +0100 Subject: [PATCH 60/67] [Console] fixed typo --- src/Symfony/Component/Console/Input/ArgvInput.php | 2 +- src/Symfony/Component/Console/Input/ArrayInput.php | 2 +- src/Symfony/Component/Console/Input/InputInterface.php | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index ddaeb2b99e4da..2e4ed7af184f6 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -263,7 +263,7 @@ public function hasParameterOption($values) * * @return mixed The option value */ - public function getParameterOption(array $values, $default = false) + public function getParameterOption($values, $default = false) { if (!is_array($values)) { $values = array($values); diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index 407f3f243270a..b64c0e578d23b 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -92,7 +92,7 @@ public function hasParameterOption($values) * * @return mixed The option value */ - public function getParameterOption(array $values, $default = false) + public function getParameterOption($values, $default = false) { if (!is_array($values)) { $values = array($values); diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index 3fd6800c2a87b..12ed27c2c20b7 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php @@ -31,11 +31,11 @@ function getFirstArgument(); * This method is to be used to introspect the input parameters * before it has been validated. It must be used carefully. * - * @param string $value The value to look for in the raw parameters + * @param string|array $value The values to look for in the raw parameters (can be an array) * * @return Boolean true if the value is contained in the raw parameters */ - function hasParameterOption($value); + function hasParameterOption($values); /** * Returns the value of a raw option (not parsed). @@ -47,7 +47,7 @@ function hasParameterOption($value); * * @return mixed The option value */ - function getParameterOption(array $values, $default = false); + function getParameterOption($values, $default = false); /** * Binds the current Input instance with the given arguments and options. From 9f77cabd2fef2bbefa83a73312f94e297d74d8e0 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 8 Feb 2011 16:41:15 -0500 Subject: [PATCH 61/67] [TwigBundle] Cast non-array resources argument to array in form_field() twig function Methods within FormExtension later type-hint this parameter as an array, but it's convenient to allow a single string to be passed from Twig if we ensure it's wrapped in an array. --- .../Bundle/TwigBundle/Extension/FormExtension.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php index aab5c28c556ee..4f96e842111bf 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/FormExtension.php @@ -123,12 +123,17 @@ public function renderRow(FieldInterface $field) * * {{ form_field(field, {}, {'separator': '+++++'}) }} * - * @param FieldInterface $field The field to render - * @param array $params Additional variables passed to the template - * @param string $resources + * @param FieldInterface $field The field to render + * @param array $attributes HTML attributes passed to the template + * @param array $parameters Additional variables passed to the template + * @param array|string $resources A resource or array of resources */ public function renderField(FieldInterface $field, array $attributes = array(), array $parameters = array(), $resources = null) { + if (null !== $resources && !is_array($resources)) { + $resources = array($resources); + } + return $this->render($field, 'field', array( 'field' => $field, 'attr' => $attributes, @@ -190,7 +195,7 @@ public function renderData(FieldInterface $field) return $field->getData(); } - protected function render(FieldInterface $field, $name, array $arguments, $resources = null) + protected function render(FieldInterface $field, $name, array $arguments, array $resources = null) { if ('field' === $name) { list($name, $template) = $this->getWidget($field, $resources); From 76e9b6ec977e29cf18264e5b9e6ca4852278a1c3 Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Tue, 8 Feb 2011 16:18:37 -0800 Subject: [PATCH 62/67] [Console] PHPDoc fixes --- src/Symfony/Component/Console/Input/ArgvInput.php | 2 +- src/Symfony/Component/Console/Input/ArrayInput.php | 1 + src/Symfony/Component/Console/Input/InputInterface.php | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 2e4ed7af184f6..6ad48121442aa 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -260,7 +260,7 @@ public function hasParameterOption($values) * before it has been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) - * + * @param mixed $default The default value to return if no result is found * @return mixed The option value */ public function getParameterOption($values, $default = false) diff --git a/src/Symfony/Component/Console/Input/ArrayInput.php b/src/Symfony/Component/Console/Input/ArrayInput.php index b64c0e578d23b..56c5fad9ab518 100644 --- a/src/Symfony/Component/Console/Input/ArrayInput.php +++ b/src/Symfony/Component/Console/Input/ArrayInput.php @@ -89,6 +89,7 @@ public function hasParameterOption($values) * before it has been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ diff --git a/src/Symfony/Component/Console/Input/InputInterface.php b/src/Symfony/Component/Console/Input/InputInterface.php index 12ed27c2c20b7..53147732987ab 100644 --- a/src/Symfony/Component/Console/Input/InputInterface.php +++ b/src/Symfony/Component/Console/Input/InputInterface.php @@ -44,6 +44,7 @@ function hasParameterOption($values); * before it has been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ From 06e2c01e76505c7826253d66e80e054f1b3e27f8 Mon Sep 17 00:00:00 2001 From: Tim Nagel Date: Tue, 8 Feb 2011 16:19:47 -0800 Subject: [PATCH 63/67] [CssSelector] PHPDoc additions --- .../Component/CssSelector/Node/AttribNode.php | 24 ++++++- .../Component/CssSelector/Node/ClassNode.php | 12 ++++ .../CssSelector/Node/CombinedSelectorNode.php | 35 +++++++++++ .../CssSelector/Node/ElementNode.php | 17 +++++ .../CssSelector/Node/FunctionNode.php | 63 ++++++++++++++++++- .../Component/CssSelector/Node/HashNode.php | 12 ++++ .../CssSelector/Node/NodeInterface.php | 10 +++ .../Component/CssSelector/Node/OrNode.php | 11 ++++ .../Component/CssSelector/Node/PseudoNode.php | 54 ++++++++++++++++ 9 files changed, 236 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/CssSelector/Node/AttribNode.php b/src/Symfony/Component/CssSelector/Node/AttribNode.php index 30128826cdb28..3471986d2d166 100644 --- a/src/Symfony/Component/CssSelector/Node/AttribNode.php +++ b/src/Symfony/Component/CssSelector/Node/AttribNode.php @@ -30,6 +30,15 @@ class AttribNode implements NodeInterface protected $operator; protected $value; + /** + * Constructor. + * + * @param NodeInterface $selector The XPath selector + * @param string $namespace The namespace + * @param string $attrib The attribute + * @param string $operator The operator + * @param string $value The value + */ public function __construct($selector, $namespace, $attrib, $operator, $value) { $this->selector = $selector; @@ -39,6 +48,9 @@ public function __construct($selector, $namespace, $attrib, $operator, $value) $this->value = $value; } + /** + * {@inheritDoc} + */ public function __toString() { if ($this->operator == 'exists') { @@ -49,7 +61,7 @@ public function __toString() } /** - * @throws SyntaxError When unknown operator is found + * {@inheritDoc} */ public function toXpath() { @@ -88,6 +100,11 @@ public function toXpath() return $path; } + /** + * Returns the XPath Attribute + * + * @return string The XPath attribute + */ protected function xpathAttrib() { // FIXME: if attrib is *? @@ -98,6 +115,11 @@ protected function xpathAttrib() return sprintf('@%s:%s', $this->namespace, $this->attrib); } + /** + * Returns a formatted attribute + * + * @return string The formatted attributep + */ protected function formatAttrib() { if ($this->namespace == '*') { diff --git a/src/Symfony/Component/CssSelector/Node/ClassNode.php b/src/Symfony/Component/CssSelector/Node/ClassNode.php index de7e931c4fe57..850b44ec892ea 100644 --- a/src/Symfony/Component/CssSelector/Node/ClassNode.php +++ b/src/Symfony/Component/CssSelector/Node/ClassNode.php @@ -26,17 +26,29 @@ class ClassNode implements NodeInterface protected $selector; protected $className; + /** + * The constructor. + * + * @param NodeInterface $selector The XPath Selector + * @param string $className The class name + */ public function __construct($selector, $className) { $this->selector = $selector; $this->className = $className; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s.%s]', __CLASS__, $this->selector, $this->className); } + /** + * {@inheritDoc} + */ public function toXpath() { $selXpath = $this->selector->toXpath(); diff --git a/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php b/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php index 1662d59f58433..80e88e130a51f 100644 --- a/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php +++ b/src/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php @@ -34,6 +34,13 @@ class CombinedSelectorNode implements NodeInterface protected $combinator; protected $subselector; + /** + * The constructor. + * + * @param NodeInterface $selector The XPath selector + * @param string $combinator The combinator + * @param NodeInterface $subselector The sub XPath selector + */ public function __construct($selector, $combinator, $subselector) { $this->selector = $selector; @@ -41,6 +48,9 @@ public function __construct($selector, $combinator, $subselector) $this->subselector = $subselector; } + /** + * {@inheritDoc} + */ public function __toString() { $comb = $this->combinator == ' ' ? '' : $this->combinator; @@ -49,6 +59,7 @@ public function __toString() } /** + * {@inheritDoc} * @throws SyntaxError When unknown combinator is found */ public function toXpath() @@ -63,6 +74,12 @@ public function toXpath() return $this->$method($path, $this->subselector); } + /** + * Joins a NodeInterface into the XPath of this object. + * + * @param XPathExpr $xpath The XPath expression for this object + * @param NodeInterface $sub The NodeInterface object to add + */ protected function _xpath_descendant($xpath, $sub) { // when sub is a descendant in any way of xpath @@ -71,6 +88,12 @@ protected function _xpath_descendant($xpath, $sub) return $xpath; } + /** + * Joins a NodeInterface as a child of this object. + * + * @param XPathExpr $xpath The parent XPath expression + * @param NodeInterface $sub The NodeInterface object to add + */ protected function _xpath_child($xpath, $sub) { // when sub is an immediate child of xpath @@ -79,6 +102,12 @@ protected function _xpath_child($xpath, $sub) return $xpath; } + /** + * Joins an XPath expression as an adjacent of another. + * + * @param XPathExpr $xpath The parent XPath expression + * @param NodeInterface $sub The adjacent XPath expression + */ protected function _xpath_direct_adjacent($xpath, $sub) { // when sub immediately follows xpath @@ -89,6 +118,12 @@ protected function _xpath_direct_adjacent($xpath, $sub) return $xpath; } + /** + * Joins an XPath expression as an indirect adjacent of another. + * + * @param XPathExpr $xpath The parent XPath expression + * @param NodeInterface $sub The indirect adjacent NodeInterface object + */ protected function _xpath_indirect_adjacent($xpath, $sub) { // when sub comes somewhere after xpath as a sibling diff --git a/src/Symfony/Component/CssSelector/Node/ElementNode.php b/src/Symfony/Component/CssSelector/Node/ElementNode.php index 86ffd57721091..683d36562496b 100644 --- a/src/Symfony/Component/CssSelector/Node/ElementNode.php +++ b/src/Symfony/Component/CssSelector/Node/ElementNode.php @@ -26,17 +26,31 @@ class ElementNode implements NodeInterface protected $namespace; protected $element; + /** + * Constructor. + * + * @param string $namespace Namespace + * @param string $element Element + */ public function __construct($namespace, $element) { $this->namespace = $namespace; $this->element = $element; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s]', __CLASS__, $this->formatElement()); } + /** + * Formats the element into a string. + * + * @return string Element as an XPath string + */ public function formatElement() { if ($this->namespace == '*') { @@ -46,6 +60,9 @@ public function formatElement() return sprintf('%s|%s', $this->namespace, $this->element); } + /** + * {@inheritDoc} + */ public function toXpath() { if ($this->namespace == '*') { diff --git a/src/Symfony/Component/CssSelector/Node/FunctionNode.php b/src/Symfony/Component/CssSelector/Node/FunctionNode.php index 9f7467b37d23c..8de049672d80e 100644 --- a/src/Symfony/Component/CssSelector/Node/FunctionNode.php +++ b/src/Symfony/Component/CssSelector/Node/FunctionNode.php @@ -31,6 +31,14 @@ class FunctionNode implements NodeInterface protected $name; protected $expr; + /** + * Constructor. + * + * @param NodeInterface $selector The XPath expression + * @param string $type + * @param string $name + * @param XPathExpr $expr + */ public function __construct($selector, $type, $name, $expr) { $this->selector = $selector; @@ -39,12 +47,16 @@ public function __construct($selector, $type, $name, $expr) $this->expr = $expr; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s%s%s(%s)]', __CLASS__, $this->selector, $this->type, $this->name, $this->expr); } /** + * {@inheritDoc} * @throws SyntaxError When unsupported or unknown pseudo-class is found */ public function toXpath() @@ -61,6 +73,15 @@ public function toXpath() return $this->$method($sel_path, $this->expr); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param mixed $expr + * @param string $last + * @param string $addNameTest + * @return XPathExpr + */ protected function _xpath_nth_child($xpath, $expr, $last = false, $addNameTest = true) { list($a, $b) = $this->parseSeries($expr); @@ -124,11 +145,25 @@ protected function _xpath_nth_child($xpath, $expr, $last = false, $addNameTest = -1n+6 means elements 6 and previous */ } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_nth_last_child($xpath, $expr) { return $this->_xpath_nth_child($xpath, $expr, true); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_nth_of_type($xpath, $expr) { if ($xpath->getElement() == '*') { @@ -138,11 +173,25 @@ protected function _xpath_nth_of_type($xpath, $expr) return $this->_xpath_nth_child($xpath, $expr, false, false); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_nth_last_of_type($xpath, $expr) { return $this->_xpath_nth_child($xpath, $expr, true, false); } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_contains($xpath, $expr) { // text content, minus tags, must contain expr @@ -159,6 +208,13 @@ protected function _xpath_contains($xpath, $expr) return $xpath; } + /** + * undocumented function + * + * @param XPathExpr $xpath + * @param XPathExpr $expr + * @return XPathExpr + */ protected function _xpath_not($xpath, $expr) { // everything for which not expr applies @@ -170,7 +226,12 @@ protected function _xpath_not($xpath, $expr) return $xpath; } - // Parses things like '1n+2', or 'an+b' generally, returning (a, b) + /** + * Parses things like '1n+2', or 'an+b' generally, returning (a, b) + * + * @param mixed $s + * @return array + */ protected function parseSeries($s) { if ($s instanceof ElementNode) { diff --git a/src/Symfony/Component/CssSelector/Node/HashNode.php b/src/Symfony/Component/CssSelector/Node/HashNode.php index 0e72ce0d6ccf6..8c557b547cc58 100644 --- a/src/Symfony/Component/CssSelector/Node/HashNode.php +++ b/src/Symfony/Component/CssSelector/Node/HashNode.php @@ -26,17 +26,29 @@ class HashNode implements NodeInterface protected $selector; protected $id; + /** + * Constructor. + * + * @param NodeInterface $selector The NodeInterface object + * @param string $id The ID + */ public function __construct($selector, $id) { $this->selector = $selector; $this->id = $id; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s#%s]', __CLASS__, $this->selector, $this->id); } + /** + * {@inheritDoc} + */ public function toXpath() { $path = $this->selector->toXpath(); diff --git a/src/Symfony/Component/CssSelector/Node/NodeInterface.php b/src/Symfony/Component/CssSelector/Node/NodeInterface.php index f1e8392d045f5..2249617688b12 100644 --- a/src/Symfony/Component/CssSelector/Node/NodeInterface.php +++ b/src/Symfony/Component/CssSelector/Node/NodeInterface.php @@ -21,7 +21,17 @@ */ interface NodeInterface { + /** + * Returns a string representation of the object. + * + * @return string The string representation + */ function __toString(); + /** + * @return XPathExpr The XPath expression + * + * @throws SyntaxError When unknown operator is found + */ function toXpath(); } diff --git a/src/Symfony/Component/CssSelector/Node/OrNode.php b/src/Symfony/Component/CssSelector/Node/OrNode.php index c0ea77569502a..af13a6caff50c 100644 --- a/src/Symfony/Component/CssSelector/Node/OrNode.php +++ b/src/Symfony/Component/CssSelector/Node/OrNode.php @@ -25,16 +25,27 @@ class OrNode implements NodeInterface { protected $items; + /** + * Constructor. + * + * @param array $items An array of NodeInterface objects + */ public function __construct($items) { $this->items = $items; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s(%s)', __CLASS__, $this->items); } + /** + * {@inheritDoc} + */ public function toXpath() { $paths = array(); diff --git a/src/Symfony/Component/CssSelector/Node/PseudoNode.php b/src/Symfony/Component/CssSelector/Node/PseudoNode.php index d55009d68d34c..d0d9166ca4283 100644 --- a/src/Symfony/Component/CssSelector/Node/PseudoNode.php +++ b/src/Symfony/Component/CssSelector/Node/PseudoNode.php @@ -34,6 +34,11 @@ class PseudoNode implements NodeInterface protected $ident; /** + * Constructor. + * + * @param NodeInterface $element The NodeInterface element + * @param string $type Node type + * @param string $ident The ident * @throws SyntaxError When incorrect PseudoNode type is given */ public function __construct($element, $type, $ident) @@ -48,12 +53,16 @@ public function __construct($element, $type, $ident) $this->ident = $ident; } + /** + * {@inheritDoc} + */ public function __toString() { return sprintf('%s[%s%s%s]', __CLASS__, $this->element, $this->type, $this->ident); } /** + * {@inheritDoc} * @throws SyntaxError When unsupported or unknown pseudo-class is found */ public function toXpath() @@ -71,6 +80,11 @@ public function toXpath() return $this->$method($el_xpath); } + /** + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified XPath expression + */ protected function xpath_checked($xpath) { // FIXME: is this really all the elements? @@ -80,6 +94,8 @@ protected function xpath_checked($xpath) } /** + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified XPath expression * @throws SyntaxError If this element is the root element */ protected function xpath_root($xpath) @@ -88,6 +104,12 @@ protected function xpath_root($xpath) throw new SyntaxError(); } + /** + * Marks this XPath expression as the first child. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_first_child($xpath) { $xpath->addStarPrefix(); @@ -97,6 +119,12 @@ protected function xpath_first_child($xpath) return $xpath; } + /** + * Sets the XPath to be the last child. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_last_child($xpath) { $xpath->addStarPrefix(); @@ -106,6 +134,12 @@ protected function xpath_last_child($xpath) return $xpath; } + /** + * Sets the XPath expression to be the first of type. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_first_of_type($xpath) { if ($xpath->getElement() == '*') { @@ -118,6 +152,10 @@ protected function xpath_first_of_type($xpath) } /** + * Sets the XPath expression to be the last of type. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression * @throws SyntaxError Because *:last-of-type is not implemented */ protected function xpath_last_of_type($xpath) @@ -131,6 +169,12 @@ protected function xpath_last_of_type($xpath) return $xpath; } + /** + * Sets the XPath expression to be the only child. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_only_child($xpath) { $xpath->addNameTest(); @@ -141,6 +185,10 @@ protected function xpath_only_child($xpath) } /** + * Sets the XPath expression to be only of type. + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression * @throws SyntaxError Because *:only-of-type is not implemented */ protected function xpath_only_of_type($xpath) @@ -153,6 +201,12 @@ protected function xpath_only_of_type($xpath) return $xpath; } + /** + * undocumented function + * + * @param XPathExpr $xpath The XPath expression + * @return XPathExpr The modified expression + */ protected function xpath_empty($xpath) { $xpath->addCondition('not(*) and not(normalize-space())'); From 5d87d83a10ebf001c1609f626d27300479aba308 Mon Sep 17 00:00:00 2001 From: Sven Paulus Date: Tue, 8 Feb 2011 11:14:38 +0100 Subject: [PATCH 64/67] optimize duplication of Request objects --- .../Component/HttpFoundation/Request.php | 36 ++++++++++++++----- .../Component/HttpKernel/bootstrap.php | 36 ++++++++++++++----- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 18a3a1c11583f..57883272c457b 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -223,14 +223,34 @@ static public function create($uri, $method = 'GET', $parameters = array(), $coo public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { $dup = clone $this; - $dup->initialize( - null !== $query ? $query : $this->query->all(), - null !== $request ? $request : $this->request->all(), - null !== $attributes ? $attributes : $this->attributes->all(), - null !== $cookies ? $cookies : $this->cookies->all(), - null !== $files ? $files : $this->files->all(), - null !== $server ? $server : $this->server->all() - ); + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $this->languages = null; + $this->charsets = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; return $dup; } diff --git a/src/Symfony/Component/HttpKernel/bootstrap.php b/src/Symfony/Component/HttpKernel/bootstrap.php index 699e1f11a057d..75e70a9a93926 100644 --- a/src/Symfony/Component/HttpKernel/bootstrap.php +++ b/src/Symfony/Component/HttpKernel/bootstrap.php @@ -1243,14 +1243,34 @@ static public function create($uri, $method = 'GET', $parameters = array(), $coo public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) { $dup = clone $this; - $dup->initialize( - null !== $query ? $query : $this->query->all(), - null !== $request ? $request : $this->request->all(), - null !== $attributes ? $attributes : $this->attributes->all(), - null !== $cookies ? $cookies : $this->cookies->all(), - null !== $files ? $files : $this->files->all(), - null !== $server ? $server : $this->server->all() - ); + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $this->languages = null; + $this->charsets = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; return $dup; } public function __clone() From 56dc1281d0306191d44517e93e3c50bad65b1660 Mon Sep 17 00:00:00 2001 From: hhamon Date: Tue, 8 Feb 2011 12:31:50 +0100 Subject: [PATCH 65/67] [DoctrineBundle] use namespace and fix double variable declaration --- .../Command/LoadDataFixturesDoctrineCommand.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php b/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php index 9f32ff281c87d..3a369bbd61ef7 100644 --- a/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php +++ b/src/Symfony/Bundle/DoctrineBundle/Command/LoadDataFixturesDoctrineCommand.php @@ -18,8 +18,11 @@ use Symfony\Component\Console\Output\Output; use Symfony\Component\Finder\Finder; use Symfony\Bundle\FrameworkBundle\Util\Filesystem; +use Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader as DataFixturesLoader; use Doctrine\Common\Cli\Configuration; use Doctrine\Common\Cli\CliController as DoctrineCliController; +use Doctrine\Common\DataFixtures\Executor\ORMExecutor; +use Doctrine\Common\DataFixtures\Purger\ORMPurger; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Internal\CommitOrderCalculator; use Doctrine\ORM\Mapping\ClassMetadata; @@ -72,15 +75,15 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $loader = $loader = new \Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader($this->container); + $loader = new DataFixturesLoader($this->container); foreach ($paths as $path) { if (is_dir($path)) { $loader->loadFromDirectory($path); } } $fixtures = $loader->getFixtures(); - $purger = new \Doctrine\Common\DataFixtures\Purger\ORMPurger($em); - $executor = new \Doctrine\Common\DataFixtures\Executor\ORMExecutor($em, $purger); + $purger = new ORMPurger($em); + $executor = new ORMExecutor($em, $purger); $executor->setLogger(function($message) use ($output) { $output->writeln(sprintf(' > %s', $message)); }); From d076ee4f3aa5967305395610da056347c3656f36 Mon Sep 17 00:00:00 2001 From: hhamon Date: Tue, 8 Feb 2011 16:06:21 +0100 Subject: [PATCH 66/67] [DoctrineMongoDBBundle] use namespace in MongoDB data fixtures command --- .../Command/LoadDataFixturesDoctrineODMCommand.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php b/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php index a910d1c041c54..bcaf9e0d85a16 100644 --- a/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php +++ b/src/Symfony/Bundle/DoctrineMongoDBBundle/Command/LoadDataFixturesDoctrineODMCommand.php @@ -18,8 +18,11 @@ use Symfony\Component\Console\Output\Output; use Symfony\Component\Finder\Finder; use Symfony\Bundle\FrameworkBundle\Util\Filesystem; +use Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader as DataFixturesLoader; use Doctrine\Common\Cli\Configuration; use Doctrine\Common\Cli\CliController as DoctrineCliController; +use Doctrine\Common\DataFixtures\Executor\MongoDBExecutor; +use Doctrine\Common\DataFixtures\Purger\MongoDBPurger; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Internal\CommitOrderCalculator; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; @@ -72,15 +75,15 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $loader = new \Symfony\Bundle\DoctrineAbstractBundle\Common\DataFixtures\Loader($this->container); + $loader = new DataFixturesLoader($this->container); foreach ($paths as $path) { if (is_dir($path)) { $loader->loadFromDirectory($path); } } $fixtures = $loader->getFixtures(); - $purger = new \Doctrine\Common\DataFixtures\Purger\MongoDBPurger($dm); - $executor = new \Doctrine\Common\DataFixtures\Executor\MongoDBExecutor($dm, $purger); + $purger = new MongoDBPurger($dm); + $executor = new MongoDBExecutor($dm, $purger); $executor->setLogger(function($message) use ($output) { $output->writeln(sprintf(' > %s', $message)); }); From 643647fb95f356b1867ebdf7f4409ac536872d70 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Thu, 10 Feb 2011 10:26:13 -0500 Subject: [PATCH 67/67] [FrameworkBundle/Routing] Add "type" option for main Router resource (and expose this in FrameworkExtension config) In routing files, import statements allow an optional "type" option to hint the resources' type (e.g. for ambiguous file extensions). This adds the same type option to the FrameworkExtension config, which defines the main routing resource. --- .../FrameworkBundle/DependencyInjection/Configuration.php | 1 + .../DependencyInjection/FrameworkExtension.php | 4 ++++ .../Bundle/FrameworkBundle/Resources/config/routing.xml | 2 ++ .../Resources/config/schema/symfony-1.0.xsd | 3 ++- .../Tests/DependencyInjection/Fixtures/php/full.php | 3 ++- .../Tests/DependencyInjection/Fixtures/xml/full.xml | 2 +- .../Tests/DependencyInjection/Fixtures/yml/full.yml | 3 ++- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 1 + src/Symfony/Component/Routing/Router.php | 8 +++++--- 9 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index a5c351aa28311..066d1f3c829a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -96,6 +96,7 @@ private function addRouterSection(NodeBuilder $rootNode) ->canBeUnset() ->scalarNode('cache_warmer')->end() ->scalarNode('resource')->isRequired()->end() + ->scalarNode('type')->end() ->end() ; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 9ceab8ed3e676..d6121b0fc00dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -233,6 +233,10 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co $container->setParameter('routing.resource', $config['resource']); + if (isset($config['type'])) { + $container->setParameter('router.options.resource_type', $config['type']); + } + if (isset($config['cache_warmer']) && $config['cache_warmer']) { $container->getDefinition('router.cache_warmer')->addTag('kernel.cache_warmer'); $container->setAlias('router', 'router.cached'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index 65ff4e29b029c..c75ca9aa9df1a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -22,6 +22,7 @@ Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer %kernel.name%%kernel.environment%UrlMatcher %kernel.name%%kernel.environment%UrlGenerator + @@ -71,6 +72,7 @@ %router.options.matcher_base_class% %router.options.matcher_dumper_class% %router.options.matcher.cache_class% + %router.options.resource_type% 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 07c0dc20c58d8..4a635a758e83a 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,8 +63,9 @@ - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php index 20a272c4c78a2..7439b4dc9f972 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php @@ -13,8 +13,9 @@ 'only_exceptions' => true, ), 'router' => array( - 'resource' => '%kernel.root_dir%/config/routing.xml', 'cache_warmer' => true, + 'resource' => '%kernel.root_dir%/config/routing.xml', + 'type' => 'xml', ), 'session' => array( 'auto_start' => true, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml index ebc2f39fe6368..8c508d98e2c38 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml @@ -10,7 +10,7 @@ - + loader.foo 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 bb8d3bc3bc90e..74571b6e6d479 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/full.yml @@ -8,8 +8,9 @@ app.config: profiler: only_exceptions: true router: - resource: %kernel.root_dir%/config/routing.xml cache_warmer: true + resource: %kernel.root_dir%/config/routing.xml + type: xml session: auto_start: true class: Session diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 99da686fa3969..95d7d21decfc8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -51,6 +51,7 @@ public function testRouter() $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->assertEquals('xml', $container->getParameter('router.options.resource_type'), '->registerRouterConfiguration() sets routing resource type'); $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'); } diff --git a/src/Symfony/Component/Routing/Router.php b/src/Symfony/Component/Routing/Router.php index cc90ee8dc1ffc..1a39019abd799 100644 --- a/src/Symfony/Component/Routing/Router.php +++ b/src/Symfony/Component/Routing/Router.php @@ -35,8 +35,9 @@ class Router implements RouterInterface * * Available options: * - * * cache_dir: The cache directory (or null to disable caching) - * * debug: Whether to enable debugging or not (false by default) + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * resource_type: Type hint for the main resource (optional) * * @param LoaderInterface $loader A LoaderInterface instance * @param mixed $resource The main resource to load @@ -63,6 +64,7 @@ public function __construct(LoaderInterface $loader, $resource, array $options = 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, ); // check option names @@ -81,7 +83,7 @@ public function __construct(LoaderInterface $loader, $resource, array $options = public function getRouteCollection() { if (null === $this->collection) { - $this->collection = $this->loader->load($this->resource); + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); } return $this->collection;