diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3af6c2a83bc18..e51cf88761451 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ | Q | A | ------------- | --- -| Branch? | master / 2.7, 2.8, 3.3, or 3.4 +| Branch? | master for features / 2.7 up to 4.0 for bug fixes | Bug fix? | yes/no | New feature? | yes/no | BC breaks? | yes/no @@ -14,6 +14,5 @@ - Bug fixes must be submitted against the lowest branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against the master branch. -- Please fill in this template according to the PR you're about to submit. - Replace this comment by a description of what your PR is solving. --> diff --git a/CHANGELOG-3.3.md b/CHANGELOG-3.3.md index 310284444011e..eeb42846f5f5e 100644 --- a/CHANGELOG-3.3.md +++ b/CHANGELOG-3.3.md @@ -7,6 +7,41 @@ in 3.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.3.0...v3.3.1 +* 3.3.14 (2017-12-04) + + * bug #25304 [Bridge/PhpUnit] Prefer $_SERVER['argv'] over $argv (ricknox) + * bug #25308 [FrameworkBundle] Fix a bug where a color tag will be shown when passing an antislash (Simperfit) + * bug #25278 Fix for missing whitespace control modifier in form layout (kubawerlos) + * bug #25305 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25236 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25312 [DI] Fix deep-inlining of non-shared refs (nicolas-grekas) + * bug #25309 [Yaml] parse newlines in quoted multiline strings (xabbuh) + * bug #25241 [Yaml] do not eagerly filter comment lines (xabbuh) + * bug #25258 [link] Prevent warnings when running link with 2.7 (dunglas) + * bug #24750 [Validator] ExpressionValidator should use OBJECT_TO_STRING (Simperfit) + * bug #25232 [WebProfilerBundle] [TwigBundle] Fix Profiler breaking XHTML pages (tistre) + * bug #25209 [VarDumper] Dont use empty(), it chokes on eg GMP objects (nicolas-grekas) + * bug #25200 [HttpKernel] Arrays with scalar values passed to ESI fragment renderer throw deprecation notice (Simperfit) + * bug #25217 [Dotenv] Changed preg_match flags from null to 0 (deekthesqueak) + * bug #25203 [DI] Fix infinite loop in InlineServiceDefinitionsPass (nicolas-grekas) + * bug #25185 [Serializer] Do not cache attributes if `attributes` in context (sroze) + * bug #25182 [HttpFoundation] AutExpireFlashBag should not clear new flashes (Simperfit, sroze) + * bug #25179 [FrameworkBundle][Serializer] Remove YamlEncoder definition if Yaml component isn't installed (ogizanagi) + * bug #25163 [DI] Fix tracking of env vars in exceptions (nicolas-grekas) + * bug #25152 [Form] Don't rely on `Symfony\Component\HttpFoundation\File\File` if http-foundation isn't in FileType (issei-m) + * bug #24987 [Console] Fix global console flag when used in chain (Simperfit) + * bug #25146 [DI] Dont resolve envs in service ids (nicolas-grekas) + * bug #25113 [Routing] Fix "config-file-relative" annotation loader resources (nicolas-grekas, sroze) + * bug #25109 Make debug:container search command case-insensitive (jzawadzki) + * bug #25043 [Yaml] added ability for substitute aliases when mapping is on single line (Michał Strzelecki, xabbuh) + * bug #25102 [Form] Fixed ContextErrorException in FileType (chihiro-adachi) + * bug #25130 [DI] Fix handling of inlined definitions by ContainerBuilder (nicolas-grekas) + * bug #25094 [FrameworkBundle][DX] Display a nice error message if an enabled component is missing (derrabus) + * bug #25097 [Bridge\PhpUnit] Turn "preserveGlobalState" to false by default, revert "Blacklist" removal (nicolas-grekas) + * bug #25072 [Bridge/PhpUnit] Remove trailing "\n" from ClockMock::microtime(false) (joky) + * bug #25032 [Bridge\PhpUnit] Disable broken auto-require mechanism of phpunit (nicolas-grekas) + * bug #24956 Fix ambiguous pattern (weltling) + * 3.3.13 (2017-11-16) * security #24995 Validate redirect targets using the session cookie domain (nicolas-grekas) diff --git a/UPGRADE-3.3.md b/UPGRADE-3.3.md index 1b61c352238fe..2efc4df740d83 100644 --- a/UPGRADE-3.3.md +++ b/UPGRADE-3.3.md @@ -254,12 +254,6 @@ HttpFoundation HttpKernel ----------- - * Deprecated the `kernel.root_dir` parameter. Use the new `kernel.project_dir` - parameter instead. - - * Deprecated the `Kernel::getRootDir()` method. Use the new `Kernel::getProjectDir()` - method instead. - * The `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` methods have been deprecated and will be removed in 4.0. * The `Psr6CacheClearer::addPool()` method has been deprecated. Pass an array diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index b1bfc214837a4..5adaa8b794292 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -361,12 +361,6 @@ HttpFoundation HttpKernel ---------- - * Removed the `kernel.root_dir` parameter. Use the `kernel.project_dir` parameter - instead. - - * Removed the `Kernel::getRootDir()` method. Use the `Kernel::getProjectDir()` - method instead. - * The `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` methods have been removed. * Possibility to pass non-scalar values as URI attributes to the ESI and SSI diff --git a/composer.json b/composer.json index 1c8a7027bcaba..202e141f36c28 100644 --- a/composer.json +++ b/composer.json @@ -97,7 +97,7 @@ "ocramius/proxy-manager": "~0.4|~1.0|~2.0", "predis/predis": "~1.0", "egulias/email-validator": "~1.2,>=1.2.8|~2.0", - "symfony/phpunit-bridge": "~3.2", + "symfony/phpunit-bridge": "~3.4|~4.0", "symfony/security-acl": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0", "sensio/framework-extra-bundle": "^3.0.2" diff --git a/link b/link new file mode 100755 index 0000000000000..a5030d0683379 --- /dev/null +++ b/link @@ -0,0 +1,66 @@ +#!/usr/bin/env php + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +require __DIR__.'/src/Symfony/Component/Filesystem/Exception/ExceptionInterface.php'; +require __DIR__.'/src/Symfony/Component/Filesystem/Exception/IOExceptionInterface.php'; +require __DIR__.'/src/Symfony/Component/Filesystem/Exception/IOException.php'; +require __DIR__.'/src/Symfony/Component/Filesystem/Filesystem.php'; + +use Symfony\Component\Filesystem\Filesystem; + +/** + * Links dependencies to components to a local clone of the main symfony/symfony GitHub repository. + * + * @author Kévin Dunglas + */ + +if (2 !== $argc) { + echo 'Link dependencies to components to a local clone of the main symfony/symfony GitHub repository.'.PHP_EOL.PHP_EOL; + echo "Usage: $argv[0] /path/to/the/project".PHP_EOL; + exit(1); +} + +if (!is_dir("$argv[1]/vendor/symfony")) { + echo "The directory \"$argv[1]\" does not exist or the dependencies are not installed, did you forget to run \"composer install\" in your project?".PHP_EOL; + exit(1); +} + +$sfPackages = array('symfony/symfony' => __DIR__); + +$filesystem = new Filesystem(); +foreach (glob(__DIR__.'/src/Symfony/{Bundle,Bridge,Component,Component/Security}/*', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { + if ($filesystem->exists($composer = "$dir/composer.json")) { + $sfPackages[json_decode(file_get_contents($composer))->name] = $dir; + } +} + +foreach (glob("$argv[1]/vendor/symfony/*", GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { + $package = 'symfony/'.basename($dir); + if (is_link($dir)) { + echo "\"$package\" is already a symlink, skipping.".PHP_EOL; + continue; + } + + if (!isset($sfPackages[$package])) { + continue; + } + + $sfDir = '\\' === DIRECTORY_SEPARATOR ? $sfPackages[$package] : $filesystem->makePathRelative($sfPackages[$package], dirname(realpath($dir))); + + $filesystem->remove($dir); + $filesystem->symlink($sfDir, $dir); + echo "\"$package\" has been linked to \"$sfPackages[$package]\".".PHP_EOL; +} + +foreach (glob("$argv[1]/var/cache/*") as $cacheDir) { + $filesystem->remove($cacheDir); +} diff --git a/phpunit b/phpunit index 53e1a8dc31dbf..c0ffe8ddef9e9 100755 --- a/phpunit +++ b/phpunit @@ -1,7 +1,7 @@ #!/usr/bin/env php getName(); $this->testsWithWarnings = array(); + foreach ($suite->tests() as $test) { + if (!($test instanceof \PHPUnit_Framework_TestCase || $test instanceof TestCase)) { + continue; + } + if (null === $Test::getPreserveGlobalStateSettings(get_class($test), $test->getName(false))) { + $test->setPreserveGlobalState(false); + } + } + if (-1 === $this->state) { echo "Testing $suiteName\n"; $this->state = 0; diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index ac358dd4bed8e..b3f302cd340bf 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -11,7 +11,7 @@ */ // Please update when phpunit needs to be reinstalled with fresh deps: -// Cache-Id-Version: 2016-10-20 14:00 UTC +// Cache-Id-Version: 2017-11-22 09:30 UTC error_reporting(-1); @@ -51,7 +51,12 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? '(del /S /F /Q %s & rmdir %1$s) >nul': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION")); } if (extension_loaded('openssl') && ini_get('allow_url_fopen') && !isset($_SERVER['http_proxy']) && !isset($_SERVER['https_proxy'])) { - stream_copy_to_stream(fopen("https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip", 'rb'), fopen("$PHPUNIT_VERSION.zip", 'wb')); + $remoteZip = "https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"; + $remoteZipStream = @fopen($remoteZip, 'rb'); + if (!$remoteZipStream) { + throw new \RuntimeException("Could not find $remoteZip"); + } + stream_copy_to_stream($remoteZipStream, fopen("$PHPUNIT_VERSION.zip", 'wb')); } else { @unlink("$PHPUNIT_VERSION.zip"); passthru("wget https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"); @@ -68,7 +73,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ if (5.1 <= $PHPUNIT_VERSION && $PHPUNIT_VERSION < 5.4) { passthru("$COMPOSER require --no-update phpunit/phpunit-mock-objects \"~3.1.0\""); } - passthru("$COMPOSER require --no-update symfony/phpunit-bridge \"~3.3.11@dev|~3.4.0-beta2@dev|^4.0.0-beta2@dev\""); + passthru("$COMPOSER require --no-update symfony/phpunit-bridge \"~3.4-beta5@dev|^4.0-beta5@dev\""); $prevRoot = getenv('COMPOSER_ROOT_VERSION'); putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); $exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p, getcwd(), null, array('bypass_shell' => true))); @@ -103,6 +108,9 @@ EOPHP } +global $argv, $argc; +$argv = isset($_SERVER['argv']) ? $_SERVER['argv'] : array(); +$argc = isset($_SERVER['argc']) ? $_SERVER['argc'] : 0; $components = array(); $cmd = array_map('escapeshellarg', $argv); $exit = 0; diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 362879e43a89e..beed1c95b3a12 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Form\TwigRendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Component\Form\FormView; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\Extension\InitRuntimeInterface; @@ -105,6 +106,7 @@ public function getTests() { return array( new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'), + new TwigTest('rootform', 'Symfony\Bridge\Twig\Extension\twig_is_root_form'), ); } @@ -192,3 +194,11 @@ function twig_is_selected_choice(ChoiceView $choice, $selectedValue) return $choice->value === $selectedValue; } + +/** + * @internal + */ +function twig_is_root_form(FormView $formView) +{ + return null === $formView->parent; +} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index 4018762e5f0bb..709a3134220cc 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig @@ -276,12 +276,12 @@ {% block form_errors -%} {% if errors|length > 0 -%} - {% if form.parent %}{% else %}
{% endif %} + {% if form is not rootform %}{% else %}
{% endif %}
    {%- for error in errors -%}
  • {{ error.message }}
  • {%- endfor -%}
- {% if form.parent %}{% else %}
{% endif %} + {% if form is not rootform %}
{% else %}
{% endif %} {%- endif %} {%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 3d8afe2cba602..22a778a19dc5f 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -15,7 +15,7 @@ {%- block form_widget_compound -%}
- {%- if form.parent is empty -%} + {%- if form is rootform -%} {{ form_errors(form) }} {%- endif -%} {{- block('form_rows') -}} @@ -337,9 +337,9 @@ {% if not child.rendered %} {{- form_row(child) -}} {% endif %} - {%- endfor %} + {%- endfor -%} - {% if not form.methodRendered and form.parent is null %} + {% if not form.methodRendered and form is rootform %} {%- do form.setMethodRendered() -%} {% set method = method|upper %} {%- if method in ["GET", "POST"] -%} @@ -351,7 +351,7 @@ {%- if form_method != method -%} {%- endif -%} - {% endif %} + {% endif -%} {% endblock form_rest %} {# Support #} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig index c7b3a4365b51b..39274c6c8d058 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig @@ -31,7 +31,7 @@ {%- block form_widget_compound -%} - {%- if form.parent is empty and errors|length > 0 -%} + {%- if form is rootform and errors|length > 0 -%}
{{- form_errors(form) -}} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig index dc7bec9fb6ccd..af9e658923923 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/foundation_5_layout.html.twig @@ -318,11 +318,11 @@ {% block form_errors -%} {% if errors|length > 0 -%} - {% if form.parent %}{% else %}
{% endif %} + {% if form is not rootform %}{% else %}
{% endif %} {%- for error in errors -%} {{ error.message }} {% if not loop.last %}, {% endif %} {%- endfor -%} - {% if form.parent %}{% else %}
{% endif %} + {% if form is not rootform %}
{% else %}
{% endif %} {%- endif %} {%- endblock form_errors %} diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php index 27a8ee3c5b4e9..2d1d03c5e6d26 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/FormExtensionDivLayoutTest.php @@ -146,6 +146,22 @@ public function testStartTagHasActionAttributeWhenActionIsZero() $this->assertSame('
', $html); } + public function isRootFormProvider() + { + return array( + array(true, new FormView()), + array(false, new FormView(new FormView())), + ); + } + + /** + * @dataProvider isRootFormProvider + */ + public function testIsRootForm($expected, FormView $formView) + { + $this->assertSame($expected, \Symfony\Bridge\Twig\Extension\twig_is_root_form($formView)); + } + protected function renderForm(FormView $view, array $vars = array()) { return (string) $this->renderer->renderBlock($view, 'form', $vars); diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php index 21d1e6d3c9976..a9c5663eecca4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/ContainerDebugCommand.php @@ -217,9 +217,8 @@ private function findServiceIdsContaining(ContainerBuilder $builder, $name) { $serviceIds = $builder->getServiceIds(); $foundServiceIds = array(); - $name = strtolower($name); foreach ($serviceIds as $serviceId) { - if (false === strpos($serviceId, $name)) { + if (false === stripos($serviceId, $name)) { continue; } $foundServiceIds[] = $serviceId; diff --git a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php index 6977d4df9afa5..70012e3dc7c1b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; +use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Alias; @@ -226,7 +227,8 @@ protected function describeContainerServices(ContainerBuilder $builder, array $o $rawOutput = isset($options['raw_text']) && $options['raw_text']; foreach ($this->sortServiceIds($serviceIds) as $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); - $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', $serviceId); + + $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', OutputFormatter::escape($serviceId)); if ($definition instanceof Definition) { if ($showTag) { foreach ($definition->getTag($showTag) as $key => $tag) { diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2617163523a4c..8b21f6c644a5a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -56,6 +56,7 @@ use Symfony\Component\Validator\ObjectInitializerInterface; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; +use Symfony\Component\Yaml\Yaml; /** * FrameworkExtension. @@ -171,6 +172,10 @@ public function load(array $configs, ContainerBuilder $container) } if ($this->isConfigEnabled($container, $config['form'])) { + if (!class_exists('Symfony\Component\Form\Form')) { + throw new LogicException('Form support cannot be enabled as the Form component is not installed.'); + } + $this->formConfigEnabled = true; $this->registerFormConfiguration($config, $container, $loader); $config['validation']['enabled'] = true; @@ -216,6 +221,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); if ($this->isConfigEnabled($container, $config['serializer'])) { + if (!class_exists('Symfony\Component\Serializer\Serializer')) { + throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed.'); + } + $this->registerSerializerConfiguration($config['serializer'], $container, $loader); } @@ -1164,6 +1173,10 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.normalizer.object'); } + if (!class_exists(Yaml::class)) { + $container->removeDefinition('serializer.encoder.yaml'); + } + $serializerLoaders = array(); if (isset($config['enable_annotations']) && $config['enable_annotations']) { if (!$this->annotationsConfigEnabled) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php index 14b6e4428e550..c4d6d837c6a7a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/RedirectControllerTest.php @@ -290,7 +290,7 @@ private function createRedirectController($httpPort = null, $httpsPort = null) return $controller; } - public function assertRedirectUrl(Response $returnResponse, $expectedUrl) + private function assertRedirectUrl(Response $returnResponse, $expectedUrl) { $this->assertTrue($returnResponse->isRedirect($expectedUrl), "Expected: $expectedUrl\nGot: ".$returnResponse->headers->get('Location')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php index 5f28d0648f388..a1ecee6d0b375 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Article.php @@ -2,9 +2,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation; -if (!function_exists('__phpunit_run_isolated_test')) { - class Article implements NotExistingInterface - { - public $category; - } +class Article implements NotExistingInterface +{ + public $category; } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index d1b7980faa8a6..39c73dc0c3d95 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; /** * AbstractFactory is the base class for all classes inheriting from @@ -29,7 +30,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface protected $options = array( 'check_path' => '/login_check', 'use_forward' => false, - 'require_previous_session' => true, + 'require_previous_session' => null, ); protected $defaultSuccessHandlerOptions = array( @@ -80,6 +81,10 @@ public function addConfiguration(NodeDefinition $node) ->scalarNode('failure_handler')->end() ; + if (array_key_exists('require_previous_session', $this->options) && null === $this->options['require_previous_session']) { + $this->options['require_previous_session'] = !class_exists(AbstractSessionHandler::class); + } + foreach (array_merge($this->options, $this->defaultSuccessHandlerOptions, $this->defaultFailureHandlerOptions) as $name => $default) { if (is_bool($default)) { $builder->booleanNode($name)->defaultValue($default); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php index dfb9c702b2d8b..5238eb3f842c3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Compiler/AddSessionDomainConstraintPassTest.php @@ -126,7 +126,8 @@ private function createContainer($sessionStorageOptions) $ext = new SecurityExtension(); $ext->load($config, $container); - (new AddSessionDomainConstraintPass())->process($container); + $pass = new AddSessionDomainConstraintPass(); + $pass->process($container); return $container; } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig index ccabdc968fb2e..0ac832ee0610d 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig +++ b/src/Symfony/Bundle/TwigBundle/Resources/views/base_js.html.twig @@ -1,6 +1,6 @@ {# This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. #} -/*/*baseDir.DIRECTORY_SEPARATOR.$file; - if (preg_match("'(^|[/\\\\])\.\.?([/\\\\]|$)'", $file) || !is_readable($filename)) { + if (preg_match("'(^|[/\\\\])\.'", $file) || !is_readable($filename)) { throw new NotFoundHttpException(sprintf('The file "%s" cannot be opened.', $file)); } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index f930580c53996..78c3f511738c8 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -1,6 +1,6 @@ {# This file is partially duplicated in TwigBundle/Resources/views/base_js.html.twig. If you make any change in this file, verify the same change is needed in the other file. #} -/*/* {{ include('@WebProfiler/Profiler/toolbar.css.twig', { 'position': position, 'floatable': true }) }} -/*/*getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock(); + $twig = $this->getMockBuilder('Twig\Environment')->disableOriginalConstructor()->getMock(); + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock(); + + $controller = new ProfilerController($urlGenerator, $profiler, $twig, array(), 'bottom', null, __DIR__.'/../..'); + + try { + $response = $controller->openAction(Request::create('/_wdt/open', Request::METHOD_GET, array('file' => $path))); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertTrue($isAllowed); + } catch (NotFoundHttpException $e) { + $this->assertFalse($isAllowed); + } + } + + public function getOpenFileCases() + { + return array( + array('README.md', true), + array('composer.json', true), + array('Controller/ProfilerController.php', true), + array('.gitignore', false), + array('../TwigBundle/README.md', false), + array('Controller/../README.md', false), + array('Controller/./ProfilerController.php', false), + ); + } + /** * @dataProvider provideCspVariants */ diff --git a/src/Symfony/Component/Config/Tests/Fixtures/BadParent.php b/src/Symfony/Component/Config/Tests/Fixtures/BadParent.php index 3f9720bb8a962..68d7296ed8696 100644 --- a/src/Symfony/Component/Config/Tests/Fixtures/BadParent.php +++ b/src/Symfony/Component/Config/Tests/Fixtures/BadParent.php @@ -2,8 +2,6 @@ namespace Symfony\Component\Config\Tests\Fixtures; -if (!function_exists('__phpunit_run_isolated_test')) { - class BadParent extends MissingParent - { - } +class BadParent extends MissingParent +{ } diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index cd75219b33d8f..f532eef157ffa 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -121,11 +121,10 @@ public function run(InputInterface $input = null, OutputInterface $output = null $e = null; $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { - } catch (\Throwable $e) { } if (null !== $e) { - if (!$this->catchExceptions || !$e instanceof \Exception) { + if (!$this->catchExceptions) { throw $e; } @@ -998,8 +997,8 @@ public function extractNamespace($name, $limit = null) * Finds alternative of $name among $collection, * if nothing is found in $collection, try in $abbrevs. * - * @param string $name The string - * @param array|\Traversable $collection The collection + * @param string $name The string + * @param iterable $collection The collection * * @return string[] A sorted array of similar string */ diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 1a430f1ff9641..eef360aad3e26 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -285,6 +285,14 @@ public function hasParameterOption($values, $onlyParams = false) if ($token === $value || 0 === strpos($token, $value.'=')) { return true; } + + if (0 === strpos($token, '-') && 0 !== strpos($token, '--')) { + $searchableToken = str_replace('-', '', $token); + $searchableValue = str_replace('-', '', $value); + if ('' !== $searchableToken && '' !== $searchableValue && false !== strpos($searchableToken, $searchableValue)) { + return true; + } + } } } diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php index f750183799414..63afbc4c0e4d0 100644 --- a/src/Symfony/Component/Console/Question/Question.php +++ b/src/Symfony/Component/Console/Question/Question.php @@ -117,7 +117,7 @@ public function setHiddenFallback($fallback) /** * Gets values for the autocompleter. * - * @return null|array|\Traversable + * @return null|iterable */ public function getAutocompleterValues() { @@ -127,7 +127,7 @@ public function getAutocompleterValues() /** * Sets values for the autocompleter. * - * @param null|array|\Traversable $values + * @param null|iterable $values * * @return $this * diff --git a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php index 8287bce521d37..b9b42b9af4c3f 100644 --- a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php +++ b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php @@ -314,6 +314,9 @@ public function testHasParameterOption() $input = new ArgvInput(array('cli.php', '-f', 'foo')); $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '-fh')); + $this->assertTrue($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '--foo', 'foo')); $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given short option is in the raw input'); diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php b/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php index d338ca9d5752b..21e0aba17d358 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/Throwing.php @@ -1,5 +1,3 @@ resolveEnvPlaceholders($id, null, $usedEnvs); - if (null !== $usedEnvs) { - throw new EnvParameterException(array($resolvedId), null, 'A service name ("%s") cannot contain dynamic values.'); + if ($definition->isPublic()) { + $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); + if (null !== $usedEnvs) { + throw new EnvParameterException(array($resolvedId), null, 'A service name ("%s") cannot contain dynamic values.'); + } } } foreach ($container->getAliases() as $id => $alias) { - $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); - if (null !== $usedEnvs) { - throw new EnvParameterException(array($resolvedId), null, 'An alias name ("%s") cannot contain dynamic values.'); + if ($alias->isPublic()) { + $resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs); + if (null !== $usedEnvs) { + throw new EnvParameterException(array($resolvedId), null, 'An alias name ("%s") cannot contain dynamic values.'); + } } } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index f2ef363c837cc..8eb6e94d24696 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -13,6 +13,7 @@ use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; /** @@ -23,6 +24,7 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface { private $repeatedPass; + private $cloningIds = array(); private $inlinedServiceIds = array(); /** @@ -54,21 +56,44 @@ protected function processValue($value, $isRoot = false) // Reference found in ArgumentInterface::getValues() are not inlineable return $value; } - if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) { - $definition = $this->container->getDefinition($id); - if ($this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { - $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); - $this->inlinedServiceIds[$id][] = $this->currentId; - - if ($definition->isShared()) { - return $definition; - } - $value = clone $definition; + if ($value instanceof Definition && $this->cloningIds) { + if ($value->isShared()) { + return $value; } + $value = clone $value; + } + + if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) { + return parent::processValue($value, $isRoot); + } + + $definition = $this->container->getDefinition($id); + + if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { + return $value; } - return parent::processValue($value, $isRoot); + $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); + $this->inlinedServiceIds[$id][] = $this->currentId; + + if ($definition->isShared()) { + return $definition; + } + + if (isset($this->cloningIds[$id])) { + $ids = array_keys($this->cloningIds); + $ids[] = $id; + + throw new ServiceCircularReferenceException($id, array_slice($ids, array_search($id, $ids))); + } + + $this->cloningIds[$id] = true; + try { + return $this->processValue($definition); + } finally { + unset($this->cloningIds[$id]); + } } /** diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php index c3b4d78dd622d..491e2c48589d2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php @@ -52,18 +52,26 @@ public function process(ContainerBuilder $container) } $config = $resolvingBag->resolveValue($config); - $tmpContainer = new ContainerBuilder($resolvingBag); - $tmpContainer->setResourceTracking($container->isTrackingResources()); - $tmpContainer->addObjectResource($extension); - if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) { - $tmpContainer->addObjectResource($configuration); - } + try { + $tmpContainer = new ContainerBuilder($resolvingBag); + $tmpContainer->setResourceTracking($container->isTrackingResources()); + $tmpContainer->addObjectResource($extension); + if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) { + $tmpContainer->addObjectResource($configuration); + } - foreach ($exprLangProviders as $provider) { - $tmpContainer->addExpressionLanguageProvider($provider); - } + foreach ($exprLangProviders as $provider) { + $tmpContainer->addExpressionLanguageProvider($provider); + } - $extension->load($config, $tmpContainer); + $extension->load($config, $tmpContainer); + } catch (\Exception $e) { + if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { + $container->getParameterBag()->mergeEnvPlaceholders($resolvingBag); + } + + throw $e; + } if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { // don't keep track of env vars that are *overridden* when configs are merged diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveEnvPlaceholdersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveEnvPlaceholdersPass.php index 8e44008317c27..f42107c6f7fd2 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveEnvPlaceholdersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveEnvPlaceholdersPass.php @@ -35,7 +35,7 @@ protected function processValue($value, $isRoot = false) $value = parent::processValue($value, $isRoot); - if ($value && is_array($value)) { + if ($value && is_array($value) && !$isRoot) { $value = array_combine($this->container->resolveEnvPlaceholders(array_keys($value), true), $value); } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index be88e4ca845c9..c3aee4e6fd35a 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -573,7 +573,7 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV $this->loading[$id] = true; try { - $service = $this->createService($definition, $id); + $service = $this->createService($definition, new \SplObjectStorage(), $id); } finally { unset($this->loading[$id]); } @@ -1013,8 +1013,12 @@ public function findDefinition($id) * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, $id, $tryProxy = true) + private function createService(Definition $definition, \SplObjectStorage $inlinedDefinitions, $id = null, $tryProxy = true) { + if (null === $id && isset($inlinedDefinitions[$definition])) { + return $inlinedDefinitions[$definition]; + } + if ($definition instanceof ChildDefinition) { throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id)); } @@ -1033,11 +1037,11 @@ private function createService(Definition $definition, $id, $tryProxy = true) ->instantiateProxy( $this, $definition, - $id, function () use ($definition, $id) { - return $this->createService($definition, $id, false); + $id, function () use ($definition, $inlinedDefinitions, $id) { + return $this->createService($definition, $inlinedDefinitions, $id, false); } ); - $this->shareService($definition, $proxy, $id); + $this->shareService($definition, $proxy, $id, $inlinedDefinitions); return $proxy; } @@ -1048,11 +1052,11 @@ private function createService(Definition $definition, $id, $tryProxy = true) require_once $parameterBag->resolveValue($definition->getFile()); } - $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments()))); + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlinedDefinitions); if (null !== $factory = $definition->getFactory()) { if (is_array($factory)) { - $factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]); + $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlinedDefinitions), $factory[1]); } elseif (!is_string($factory)) { throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); } @@ -1081,16 +1085,16 @@ private function createService(Definition $definition, $id, $tryProxy = true) if ($tryProxy || !$definition->isLazy()) { // share only if proxying failed, or if not a proxy - $this->shareService($definition, $service, $id); + $this->shareService($definition, $service, $id, $inlinedDefinitions); } - $properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties()))); + $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlinedDefinitions); foreach ($properties as $name => $value) { $service->$name = $value; } foreach ($definition->getMethodCalls() as $call) { - $this->callMethod($service, $call); + $this->callMethod($service, $call, $inlinedDefinitions); } if ($callable = $definition->getConfigurator()) { @@ -1100,7 +1104,7 @@ private function createService(Definition $definition, $id, $tryProxy = true) if ($callable[0] instanceof Reference) { $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior()); } elseif ($callable[0] instanceof Definition) { - $callable[0] = $this->createService($callable[0], null); + $callable[0] = $this->createService($callable[0], $inlinedDefinitions); } } @@ -1123,10 +1127,15 @@ private function createService(Definition $definition, $id, $tryProxy = true) * the real service instances and all expressions evaluated */ public function resolveServices($value) + { + return $this->doResolveServices($value, new \SplObjectStorage()); + } + + private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions) { if (is_array($value)) { foreach ($value as $k => $v) { - $value[$k] = $this->resolveServices($v); + $value[$k] = $this->doResolveServices($v, $inlinedDefinitions); } } elseif ($value instanceof ServiceClosureArgument) { $reference = $value->getValues()[0]; @@ -1161,7 +1170,7 @@ public function resolveServices($value) } elseif ($value instanceof Reference) { $value = $this->get((string) $value, $value->getInvalidBehavior()); } elseif ($value instanceof Definition) { - $value = $this->createService($value, null); + $value = $this->createService($value, $inlinedDefinitions); } elseif ($value instanceof Expression) { $value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this)); } @@ -1277,7 +1286,7 @@ public function getAutoconfiguredInstanceof() * true to resolve to the actual values of the referenced env vars * @param array &$usedEnvs Env vars found while resolving are added to this array * - * @return string The string with env parameters resolved + * @return mixed The value with env parameters resolved if a string or an array is passed */ public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null) { @@ -1430,7 +1439,7 @@ private function getProxyInstantiator() return $this->proxyInstantiator; } - private function callMethod($service, $call) + private function callMethod($service, $call, \SplObjectStorage $inlinedDefinitions) { $services = self::getServiceConditionals($call[1]); @@ -1440,7 +1449,7 @@ private function callMethod($service, $call) } } - call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])))); + call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlinedDefinitions)); } /** @@ -1450,9 +1459,14 @@ private function callMethod($service, $call) * @param object $service * @param string|null $id */ - private function shareService(Definition $definition, $service, $id) + private function shareService(Definition $definition, $service, $id, \SplObjectStorage $inlinedDefinitions) { - if (null !== $id && $definition->isShared()) { + if (!$definition->isShared()) { + return; + } + if (null === $id) { + $inlinedDefinitions[$definition] = $service; + } else { $this->services[$this->normalizeId($id)] = $service; } } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index d60072b7551e9..6c0cf4f1b6a69 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -680,6 +680,7 @@ private function addServices() private function addNewInstance(Definition $definition, $return, $instantiation, $id) { $class = $this->dumpValue($definition->getClass()); + $return = ' '.$return.$instantiation; $arguments = array(); foreach ($definition->getArguments() as $value) { @@ -695,7 +696,7 @@ private function addNewInstance(Definition $definition, $return, $instantiation, if ($callable[0] instanceof Reference || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { - return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); } $class = $this->dumpValue($callable[0]); @@ -705,24 +706,24 @@ private function addNewInstance(Definition $definition, $return, $instantiation, throw new RuntimeException(sprintf('Cannot dump definition: The "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id)); } - return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); } if (0 === strpos($class, 'new ')) { - return sprintf(" $return{$instantiation}(%s)->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("(%s)->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); + return $return.sprintf("call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); } if (false !== strpos($class, '$')) { - return sprintf(" \$class = %s;\n\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments)); + return sprintf(" \$class = %s;\n\n%snew \$class(%s);\n", $class, $return, implode(', ', $arguments)); } - return sprintf(" $return{$instantiation}new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); + return $return.sprintf("new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); } /** @@ -890,7 +891,7 @@ private function addNormalizedIds() ksort($normalizedIds); foreach ($normalizedIds as $id => $normalizedId) { if ($this->container->has($normalizedId)) { - $code .= ' '.$this->export($id).' => '.$this->export($normalizedId).",\n"; + $code .= ' '.$this->doExport($id).' => '.$this->doExport($normalizedId).",\n"; } } @@ -912,7 +913,7 @@ private function addMethodMap() $code = " \$this->methodMap = array(\n"; ksort($definitions); foreach ($definitions as $id => $definition) { - $code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n"; + $code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n"; } return $code." );\n"; @@ -933,7 +934,7 @@ private function addPrivateServices() ksort($definitions); foreach ($definitions as $id => $definition) { if (!$definition->isPublic()) { - $code .= ' '.$this->export($id)." => true,\n"; + $code .= ' '.$this->doExport($id)." => true,\n"; } } @@ -966,7 +967,7 @@ private function addAliases() while (isset($aliases[$id])) { $id = (string) $aliases[$id]; } - $code .= ' '.$this->export($alias).' => '.$this->export($id).",\n"; + $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n"; } return $code." );\n"; @@ -1688,9 +1689,9 @@ private function exportTargetDirs() private function export($value) { if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) { - $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1])).'.' : ''; + $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : ''; $suffix = $matches[0][1] + strlen($matches[0][0]); - $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix)) : ''; + $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; $dirname = '__DIR__'; if (0 < $offset = 1 + $this->targetDirMaxMatches - count($matches)) { @@ -1704,10 +1705,10 @@ private function export($value) return $dirname; } - return $this->doExport($value); + return $this->doExport($value, true); } - private function doExport($value) + private function doExport($value, $resolveEnv = false) { if (is_string($value) && false !== strpos($value, "\n")) { $cleanParts = explode("\n", $value); @@ -1717,7 +1718,7 @@ private function doExport($value) $export = var_export($value, true); } - if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('%s').'")) { + if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('%s').'")) { $export = $resolvedExport; if ("'" === $export[1]) { $export = substr($export, 3); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckDefinitionValidityPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckDefinitionValidityPassTest.php index 473d5667d448e..d47303e85138c 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckDefinitionValidityPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/CheckDefinitionValidityPassTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CheckDefinitionValidityPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -79,11 +80,11 @@ public function testInvalidTags() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\EnvParameterException */ - public function testDynamicServiceName() + public function testDynamicPublicServiceName() { $container = new ContainerBuilder(); $env = $container->getParameterBag()->get('env(BAR)'); - $container->register("foo.$env", 'class'); + $container->register("foo.$env", 'class')->setPublic(true); $this->process($container); } @@ -91,15 +92,27 @@ public function testDynamicServiceName() /** * @expectedException \Symfony\Component\DependencyInjection\Exception\EnvParameterException */ - public function testDynamicAliasName() + public function testDynamicPublicAliasName() { $container = new ContainerBuilder(); $env = $container->getParameterBag()->get('env(BAR)'); - $container->setAlias("foo.$env", 'class'); + $container->setAlias("foo.$env", new Alias('class', true)); $this->process($container); } + public function testDynamicPrivateName() + { + $container = new ContainerBuilder(); + $env = $container->getParameterBag()->get('env(BAR)'); + $container->register("foo.$env", 'class')->setPublic(false); + $container->setAlias("bar.$env", new Alias('class', false)); + + $this->process($container); + + $this->addToAssertionCount(1); + } + protected function process(ContainerBuilder $container) { $pass = new CheckDefinitionValidityPass(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php index 26b24fa713242..365c16c2d2c95 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php @@ -92,6 +92,79 @@ public function testProcessDoesInlineNonSharedService() $this->assertNotSame($container->getDefinition('bar'), $arguments[2]); } + public function testProcessInlinesMixedServicesLoop() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->addArgument(new Reference('bar')) + ->setShared(false) + ; + $container + ->register('bar') + ->setPublic(false) + ->addMethodCall('setFoo', array(new Reference('foo'))) + ; + + $this->process($container); + + $this->assertEquals($container->getDefinition('foo')->getArgument(0), $container->getDefinition('bar')); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException + * @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> foo -> bar". + */ + public function testProcessThrowsOnNonSharedLoops() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->addArgument(new Reference('bar')) + ->setShared(false) + ; + $container + ->register('bar') + ->setShared(false) + ->addMethodCall('setFoo', array(new Reference('foo'))) + ; + + $this->process($container); + } + + public function testProcessNestedNonSharedServices() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->addArgument(new Reference('bar1')) + ->addArgument(new Reference('bar2')) + ; + $container + ->register('bar1') + ->setShared(false) + ->addArgument(new Reference('baz')) + ; + $container + ->register('bar2') + ->setShared(false) + ->addArgument(new Reference('baz')) + ; + $container + ->register('baz') + ->setShared(false) + ; + + $this->process($container); + + $baz1 = $container->getDefinition('foo')->getArgument(0)->getArgument(0); + $baz2 = $container->getDefinition('foo')->getArgument(1)->getArgument(0); + + $this->assertEquals($container->getDefinition('baz'), $baz1); + $this->assertEquals($container->getDefinition('baz'), $baz2); + $this->assertNotSame($baz1, $baz2); + } + public function testProcessInlinesIfMultipleReferencesButAllFromTheSameDefinition() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php index 44d1933a80fa8..7f4583a16c95d 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php @@ -84,6 +84,22 @@ public function testOverriddenEnvsAreMerged() $this->assertSame(array('BAZ', 'FOO'), array_keys($container->getParameterBag()->getEnvPlaceholders())); $this->assertSame(array('BAZ' => 1, 'FOO' => 0), $container->getEnvCounters()); } + + public function testThrowingExtensionsGetMergedBag() + { + $container = new ContainerBuilder(); + $container->registerExtension(new ThrowingExtension()); + $container->prependExtensionConfig('throwing', array('bar' => '%env(FOO)%')); + + try { + $pass = new MergeExtensionConfigurationPass(); + $pass->process($container); + $this->fail('An exception should have been thrown.'); + } catch (\Exception $e) { + } + + $this->assertSame(array('FOO'), array_keys($container->getParameterBag()->getEnvPlaceholders())); + } } class FooConfiguration implements ConfigurationInterface @@ -125,3 +141,21 @@ public function load(array $configs, ContainerBuilder $container) } } } + +class ThrowingExtension extends Extension +{ + public function getAlias() + { + return 'throwing'; + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new FooConfiguration(); + } + + public function load(array $configs, ContainerBuilder $container) + { + throw new \Exception(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php index 33372695903e9..7e9238f22301b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/OptionalServiceClass.php @@ -13,8 +13,6 @@ use Symfony\Bug\NotExistClass; -if (!function_exists('__phpunit_run_isolated_test')) { - class OptionalServiceClass extends NotExistClass - { - } +class OptionalServiceClass extends NotExistClass +{ } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index efe9840e42c40..8b905746b2e43 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -665,6 +665,22 @@ public function testCompileWithResolveMissingEnv() $container->compile(true); } + public function testEnvInId() + { + $container = include __DIR__.'/Fixtures/containers/container_env_in_id.php'; + $container->compile(true); + + $expected = array( + 'service_container', + 'foo', + 'bar', + 'bar_%env(BAR)%', + ); + $this->assertSame($expected, array_keys($container->getDefinitions())); + + $this->assertSame(array('baz_bar'), array_keys($container->getDefinition('foo')->getArgument(1))); + } + /** * @expectedException \LogicException */ @@ -1006,6 +1022,28 @@ public function testLazyLoadedService() $this->assertTrue($classInList); } + public function testInlinedDefinitions() + { + $container = new ContainerBuilder(); + + $definition = new Definition('BarClass'); + + $container->register('bar_user', 'BarUserClass') + ->addArgument($definition) + ->setProperty('foo', $definition); + + $container->register('bar', 'BarClass') + ->setProperty('foo', $definition) + ->addMethodCall('setBaz', array($definition)); + + $barUser = $container->get('bar_user'); + $bar = $container->get('bar'); + + $this->assertSame($barUser->foo, $barUser->bar); + $this->assertSame($bar->foo, $bar->getBaz()); + $this->assertNotSame($bar->foo, $barUser->foo); + } + public function testInitializePropertiesBeforeMethodCalls() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 0d734cb4c222a..44e172fed2c79 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -327,6 +327,15 @@ public function testDumpAutowireData() $this->assertStringEqualsFile(self::$fixturesPath.'/php/services24.php', $dumper->dump()); } + public function testEnvInId() + { + $container = include self::$fixturesPath.'/containers/container_env_in_id.php'; + $container->compile(); + $dumper = new PhpDumper($container); + + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_env_in_id.php', $dumper->dump()); + } + public function testEnvParameter() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_env_in_id.php new file mode 100644 index 0000000000000..4699f41011a41 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_env_in_id.php @@ -0,0 +1,22 @@ +setParameter('env(BAR)', 'bar'); + +$container->register('foo', 'stdClass')->setPublic(true) + ->addArgument(new Reference('bar_%env(BAR)%')) + ->addArgument(array('baz_%env(BAR)%' => new Reference('baz_%env(BAR)%'))); + +$container->register('bar', 'stdClass')->setPublic(true) + ->addArgument(new Reference('bar_%env(BAR)%')); + +$container->register('bar_%env(BAR)%', 'stdClass')->setPublic(false); +$container->register('baz_%env(BAR)%', 'stdClass')->setPublic(false); + +return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php index 717dcdc52e579..aa5ea7f3e5501 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php @@ -9,7 +9,7 @@ function sc_configure($instance) $instance->configure(); } -class BarClass +class BarClass extends BazClass { protected $baz; public $foo = 'foo'; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php new file mode 100644 index 0000000000000..d7f2ea626aa87 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_env_in_id.php @@ -0,0 +1,177 @@ +parameters = $this->getDefaultParameters(); + + $this->services = array(); + $this->normalizedIds = array( + 'bar_%env(bar)%' => 'bar_%env(BAR)%', + ); + $this->methodMap = array( + 'bar' => 'getBarService', + 'bar_%env(BAR)%' => 'getBarenvBARService', + 'foo' => 'getFooService', + ); + $this->privates = array( + 'bar_%env(BAR)%' => true, + ); + + $this->aliases = array(); + } + + /** + * {@inheritdoc} + */ + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + /** + * {@inheritdoc} + */ + public function isCompiled() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isFrozen() + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); + + return true; + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected function getBarService() + { + return $this->services['bar'] = new \stdClass(${($_ = isset($this->services['bar_%env(BAR)%']) ? $this->services['bar_%env(BAR)%'] : $this->getBarenvBARService()) && false ?: '_'}); + } + + /** + * Gets the public 'foo' shared service. + * + * @return \stdClass + */ + protected function getFooService() + { + return $this->services['foo'] = new \stdClass(${($_ = isset($this->services['bar_%env(BAR)%']) ? $this->services['bar_%env(BAR)%'] : $this->getBarenvBARService()) && false ?: '_'}, array('baz_'.$this->getEnv('BAR') => new \stdClass())); + } + + /** + * Gets the private 'bar_%env(BAR)%' shared service. + * + * @return \stdClass + */ + protected function getBarenvBARService() + { + return $this->services['bar_%env(BAR)%'] = new \stdClass(); + } + + /** + * {@inheritdoc} + */ + public function getParameter($name) + { + $name = strtolower($name); + + if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]))) { + throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + } + if (isset($this->loadedDynamicParameters[$name])) { + return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + + return $this->parameters[$name]; + } + + /** + * {@inheritdoc} + */ + public function hasParameter($name) + { + $name = strtolower($name); + + return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]); + } + + /** + * {@inheritdoc} + */ + public function setParameter($name, $value) + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + /** + * {@inheritdoc} + */ + public function getParameterBag() + { + if (null === $this->parameterBag) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + $this->parameterBag = new FrozenParameterBag($parameters); + } + + return $this->parameterBag; + } + + private $loadedDynamicParameters = array(); + private $dynamicParameters = array(); + + /** + * Computes a dynamic parameter. + * + * @param string The name of the dynamic parameter to load + * + * @return mixed The value of the dynamic parameter + * + * @throws InvalidArgumentException When the dynamic parameter does not exist + */ + private function getDynamicParameter($name) + { + throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name)); + } + + /** + * Gets the default parameters. + * + * @return array An array of the default parameters + */ + protected function getDefaultParameters() + { + return array( + 'env(bar)' => 'bar', + ); + } +} diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index 910a1622f303b..dde92f46af5b6 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -173,7 +173,7 @@ private function lexVarname() private function lexValue() { - if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, null, $this->cursor)) { + if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) { $this->moveCursor($matches[0]); $this->skipEmptyLines(); @@ -295,7 +295,7 @@ private function lexNestedExpression() private function skipEmptyLines() { - if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, null, $this->cursor)) { + if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) { $this->moveCursor($match[0]); } } diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 72d429228de55..735d11cf66570 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -83,8 +83,8 @@ public function copy($originFile, $targetFile, $overwriteNewerFiles = false) /** * Creates a directory recursively. * - * @param string|array|\Traversable $dirs The directory path - * @param int $mode The directory mode + * @param string|iterable $dirs The directory path + * @param int $mode The directory mode * * @throws IOException On any directory creation failure */ @@ -111,7 +111,7 @@ public function mkdir($dirs, $mode = 0777) /** * Checks the existence of files or directories. * - * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check * * @return bool true if the file exists, false otherwise */ @@ -135,9 +135,9 @@ public function exists($files) /** * Sets access and modification time of file. * - * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create - * @param int $time The touch time as a Unix timestamp - * @param int $atime The access time as a Unix timestamp + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create + * @param int $time The touch time as a Unix timestamp + * @param int $atime The access time as a Unix timestamp * * @throws IOException When touch fails */ @@ -154,7 +154,7 @@ public function touch($files, $time = null, $atime = null) /** * Removes files or directories. * - * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove * * @throws IOException When removal fails */ @@ -190,10 +190,10 @@ public function remove($files) /** * Change mode for an array of files or directories. * - * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change mode - * @param int $mode The new mode (octal) - * @param int $umask The mode mask (octal) - * @param bool $recursive Whether change the mod recursively or not + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode + * @param int $mode The new mode (octal) + * @param int $umask The mode mask (octal) + * @param bool $recursive Whether change the mod recursively or not * * @throws IOException When the change fail */ @@ -212,9 +212,9 @@ public function chmod($files, $mode, $umask = 0000, $recursive = false) /** * Change the owner of an array of files or directories. * - * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change owner - * @param string $user The new owner user name - * @param bool $recursive Whether change the owner recursively or not + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner + * @param string $user The new owner user name + * @param bool $recursive Whether change the owner recursively or not * * @throws IOException When the change fail */ @@ -239,9 +239,9 @@ public function chown($files, $user, $recursive = false) /** * Change the group of an array of files or directories. * - * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change group - * @param string $group The group name - * @param bool $recursive Whether change the group recursively or not + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group + * @param string $group The group name + * @param bool $recursive Whether change the group recursively or not * * @throws IOException When the change fail */ diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index 8971f62db0207..0335dbfd1cdb3 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -56,11 +56,11 @@ class ArrayChoiceList implements ChoiceListInterface * * The given choice array must have the same array keys as the value array. * - * @param array|\Traversable $choices The selectable choices - * @param callable|null $value The callable for creating the value - * for a choice. If `null` is passed, - * incrementing integers are used as - * values + * @param iterable $choices The selectable choices + * @param callable|null $value The callable for creating the value + * for a choice. If `null` is passed, + * incrementing integers are used as + * values */ public function __construct($choices, callable $value = null) { diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 1c6f24d6986f8..5abbeac491601 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -31,9 +31,9 @@ interface ChoiceListFactoryInterface * The callable receives the choice as first and the array key as the second * argument. * - * @param array|\Traversable $choices The choices - * @param null|callable $value The callable generating the choice - * values + * @param iterable $choices The choices + * @param null|callable $value The callable generating the choice + * values * * @return ChoiceListInterface The choice list */ diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 3e6f1c177b258..8bed7ca8349a1 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -63,7 +63,7 @@ public function getDecoratedFactory() /** * {@inheritdoc} * - * @param array|\Traversable $choices The choices + * @param iterable $choices The choices * @param null|callable|string|PropertyPath $value The callable or path for * generating the choice values * diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index b801101cbae7f..387325782933c 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -34,8 +34,13 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ($options['multiple']) { $data = array(); + $files = $event->getData(); - foreach ($event->getData() as $file) { + if (!is_array($files)) { + $files = array(); + } + + foreach ($files as $file) { if ($requestHandler->isFileUpload($file)) { $data[] = $file; } @@ -87,9 +92,12 @@ public function finishView(FormView $view, FormInterface $form, array $options) */ public function configureOptions(OptionsResolver $resolver) { - $dataClass = function (Options $options) { - return $options['multiple'] ? null : 'Symfony\Component\HttpFoundation\File\File'; - }; + $dataClass = null; + if (class_exists('Symfony\Component\HttpFoundation\File\File')) { + $dataClass = function (Options $options) { + return $options['multiple'] ? null : 'Symfony\Component\HttpFoundation\File\File'; + }; + } $emptyData = function (Options $options) { return $options['multiple'] ? array() : null; diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 368a5a80a1481..571db0eb41dfc 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -108,6 +108,9 @@ public function handleRequest(FormInterface $form, $request = null) $form->submit($data, 'PATCH' !== $method); } + /** + * {@inheritdoc} + */ public function isFileUpload($data) { return $data instanceof File; diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index 801393b6cf9ca..f6cdff5310494 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -32,7 +32,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface * * @var NativeRequestHandler */ - private static $nativeRequestProcessor; + private static $nativeRequestHandler; /** * The accepted request methods. @@ -496,10 +496,10 @@ public function getMethod() public function getRequestHandler() { if (null === $this->requestHandler) { - if (null === self::$nativeRequestProcessor) { - self::$nativeRequestProcessor = new NativeRequestHandler(); + if (null === self::$nativeRequestHandler) { + self::$nativeRequestHandler = new NativeRequestHandler(); } - $this->requestHandler = self::$nativeRequestProcessor; + $this->requestHandler = self::$nativeRequestHandler; } return $this->requestHandler; diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index f68efe25cfe54..37e1c99a74505 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -118,6 +118,9 @@ public function handleRequest(FormInterface $form, $request = null) $form->submit($data, 'PATCH' !== $method); } + /** + * {@inheritdoc} + */ public function isFileUpload($data) { // POST data will always be strings or arrays of strings. Thus, we can be sure diff --git a/src/Symfony/Component/Form/RequestHandlerInterface.php b/src/Symfony/Component/Form/RequestHandlerInterface.php index e6360e449889f..3d7b45d5064af 100644 --- a/src/Symfony/Component/Form/RequestHandlerInterface.php +++ b/src/Symfony/Component/Form/RequestHandlerInterface.php @@ -27,7 +27,9 @@ interface RequestHandlerInterface public function handleRequest(FormInterface $form, $request = null); /** - * @param mixed $data + * Returns true if the given data is a file upload. + * + * @param mixed $data The form field data * * @return bool */ diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php index 8ac62c8dfb1da..5ba1dc5a5101d 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php @@ -159,6 +159,24 @@ public function testMultipleSubmittedFilePathsAreDropped(RequestHandlerInterface $this->assertCount(1, $form->getData()); } + /** + * @dataProvider requestHandlerProvider + */ + public function testSubmitNonArrayValueWhenMultiple(RequestHandlerInterface $requestHandler) + { + $form = $this->factory + ->createBuilder(static::TESTED_TYPE, null, array( + 'multiple' => true, + )) + ->setRequestHandler($requestHandler) + ->getForm(); + $form->submit(null); + + $this->assertSame(array(), $form->getData()); + $this->assertSame(array(), $form->getNormData()); + $this->assertSame(array(), $form->getViewData()); + } + public function requestHandlerProvider() { return array( diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php index 59ba309005b38..0ed6600974371 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php @@ -106,7 +106,7 @@ public function get($type, array $default = array()) public function all() { $return = $this->flashes['display']; - $this->flashes = array('new' => array(), 'display' => array()); + $this->flashes['display'] = array(); return $return; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php index 18b71a5021fbb..0e0724a3249a4 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php @@ -150,4 +150,12 @@ public function testClear() { $this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear()); } + + public function testDoNotRemoveTheNewFlashesWhenDisplayingTheExistingOnes() + { + $this->bag->add('success', 'Something'); + $this->bag->all(); + + $this->assertEquals(array('new' => array('success' => array('Something')), 'display' => array()), $this->array); + } } diff --git a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php index e955cd0d43c6f..9526908a37858 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -98,8 +98,8 @@ private function generateSignedFragmentUri($uri, Request $request) private function containsNonScalars(array $values) { foreach ($values as $value) { - if (is_array($value) && $this->containsNonScalars($value)) { - return true; + if (is_array($value)) { + return $this->containsNonScalars($value); } elseif (!is_scalar($value) && null !== $value) { return true; } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index c5aa20930fc3f..d904460cf4f42 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -61,11 +61,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface private $projectDir; - const VERSION = '3.3.13'; - const VERSION_ID = 30313; + const VERSION = '3.3.14'; + const VERSION_ID = 30314; const MAJOR_VERSION = 3; const MINOR_VERSION = 3; - const RELEASE_VERSION = 13; + const RELEASE_VERSION = 14; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2018'; diff --git a/src/Symfony/Component/HttpKernel/Tests/Event/FilterControllerArgumentsEventTest.php b/src/Symfony/Component/HttpKernel/Tests/Event/FilterControllerArgumentsEventTest.php new file mode 100644 index 0000000000000..9165d31f24a15 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/Event/FilterControllerArgumentsEventTest.php @@ -0,0 +1,17 @@ +assertEquals($filterController->getArguments(), array('test')); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php index bfe922e22c76b..7099b4d7e8bf6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php @@ -34,7 +34,15 @@ public function testRenderFallbackWithObjectAttributesIsDeprecated() { $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo')); $request = Request::create('/'); - $reference = new ControllerReference('main_controller', array('foo' => array('a' => array(), 'b' => new \stdClass())), array()); + $reference = new ControllerReference('main_controller', array('foo' => new \stdClass()), array()); + $strategy->render($reference, $request); + } + + public function testRenderFallbackWithScalarIsNotDeprecated() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo')); + $request = Request::create('/'); + $reference = new ControllerReference('main_controller', array('foo' => array(true)), array()); $strategy->render($reference, $request); } diff --git a/src/Symfony/Component/Intl/Data/Bundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/Data/Bundle/Writer/TextBundleWriter.php index 3a444fd350edb..20fad5347aed2 100644 --- a/src/Symfony/Component/Intl/Data/Bundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/Data/Bundle/Writer/TextBundleWriter.php @@ -195,11 +195,11 @@ private function writeArray($file, array $value, $indentation) /** * Writes a "table" node. * - * @param resource $file The file handle to write to - * @param array|\Traversable $value The value of the node - * @param int $indentation The number of levels to indent - * @param bool $fallback Whether the table should be merged - * with the fallback locale + * @param resource $file The file handle to write to + * @param iterable $value The value of the node + * @param int $indentation The number of levels to indent + * @param bool $fallback Whether the table should be merged + * with the fallback locale * * @throws UnexpectedTypeException when $value is not an array and not a * \Traversable instance diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 2309cc63b4ed7..c23229dd86ca2 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -645,11 +645,11 @@ private function writeProperty($zval, $property, $value) /** * Adjusts a collection-valued property by calling add*() and remove*() methods. * - * @param array $zval The array containing the object to write to - * @param string $property The property to write - * @param array|\Traversable $collection The collection to write - * @param string $addMethod The add*() method - * @param string $removeMethod The remove*() method + * @param array $zval The array containing the object to write to + * @param string $property The property to write + * @param iterable $collection The collection to write + * @param string $addMethod The add*() method + * @param string $removeMethod The remove*() method */ private function writeCollection($zval, $property, $collection, $addMethod, $removeMethod) { diff --git a/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php index 616d01ef4cda4..4574a0201c0c3 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php @@ -34,7 +34,9 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader */ public function load($path, $type = null) { - $dir = $this->locator->locate($path); + if (!is_dir($dir = $this->locator->locate($path))) { + return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection(); + } $collection = new RouteCollection(); $collection->addResource(new DirectoryResource($dir, '/\.php$/')); @@ -74,16 +76,18 @@ function (\SplFileInfo $current) { */ public function supports($resource, $type = null) { - if (!is_string($resource)) { + if ('annotation' === $type) { + return true; + } + + if ($type || !is_string($resource)) { return false; } try { - $path = $this->locator->locate($resource); + return is_dir($this->locator->locate($resource)); } catch (\Exception $e) { return false; } - - return is_dir($path) && (!$type || 'annotation' === $type); } } diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.php b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.php index f59a234d6589a..482c80b29e919 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/validresource.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/validresource.php @@ -1,8 +1,5 @@ import('validpattern.php'); diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php index 78cec4be2a159..1e8ee394015e1 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php @@ -69,6 +69,24 @@ public function testSupports() $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified'); } + public function testItSupportsAnyAnnotation() + { + $this->assertTrue($this->loader->supports(__DIR__.'/../Fixtures/even-with-not-existing-folder', 'annotation')); + } + + public function testLoadFileIfLocatedResourceIsFile() + { + $this->reader->expects($this->exactly(1))->method('getClassAnnotation'); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php'); + } + private function expectAnnotationsToBeReadFrom(array $classes) { $this->reader->expects($this->exactly(count($classes))) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 68e86da1f21e2..29eab6f1ca8b4 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -126,6 +126,10 @@ protected function getAttributes($object, $format = null, array $context) return $allowedAttributes; } + if (isset($context['attributes'])) { + return $this->extractAttributes($object, $format, $context); + } + if (isset($this->attributesCache[$class])) { return $this->attributesCache[$class]; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index b2d7b872f4d94..9407fdf52d7b0 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -713,6 +713,37 @@ public function testAttributesContextDenormalizeConstructor() 'inner' => array('foo' => 'foo', 'bar' => 'bar'), ), DummyWithConstructorObjectAndDefaultValue::class, null, $context)); } + + public function testNormalizeSameObjectWithDifferentAttributes() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $this->normalizer = new ObjectNormalizer($classMetadataFactory); + $serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($serializer); + + $dummy = new ObjectOuter(); + $dummy->foo = new ObjectInner(); + $dummy->foo->foo = 'foo.foo'; + $dummy->foo->bar = 'foo.bar'; + + $dummy->bar = new ObjectInner(); + $dummy->bar->foo = 'bar.foo'; + $dummy->bar->bar = 'bar.bar'; + + $this->assertEquals(array( + 'foo' => array( + 'bar' => 'foo.bar', + ), + 'bar' => array( + 'foo' => 'bar.foo', + ), + ), $this->normalizer->normalize($dummy, 'json', array( + 'attributes' => array( + 'foo' => array('bar'), + 'bar' => array('foo'), + ), + ))); + } } class ObjectDummy diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php index be05f8f8a0af0..ce77d9ba843a4 100644 --- a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php +++ b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php @@ -45,7 +45,7 @@ public function validate($value, Constraint $constraint) if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) { $this->context->buildViolation($constraint->message) - ->setParameter('{{ value }}', $this->formatValue($value)) + ->setParameter('{{ value }}', $this->formatValue($value, self::OBJECT_TO_STRING)) ->setCode(Expression::EXPRESSION_FAILED_ERROR) ->addViolation(); } diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index 62ec4ddaeee24..7c6c15f4b342a 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -24,7 +24,7 @@ class UrlValidator extends ConstraintValidator (%s):// # protocol (([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth ( - ([\pL\pN\pS-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name + ([\pL\pN\pS\-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name | # or \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address | # or diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php index e4316243e5580..07f17d648aa39 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ExpressionValidatorTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Validator\Constraints\ExpressionValidator; use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; use Symfony\Component\Validator\Tests\Fixtures\Entity; +use Symfony\Component\Validator\Tests\Fixtures\ToString; class ExpressionValidatorTest extends ConstraintValidatorTestCase { @@ -87,6 +88,40 @@ public function testFailingExpressionAtObjectLevel() ->assertRaised(); } + public function testSucceedingExpressionAtObjectLevelWithToString() + { + $constraint = new Expression('this.data == 1'); + + $object = new ToString(); + $object->data = '1'; + + $this->setObject($object); + + $this->validator->validate($object, $constraint); + + $this->assertNoViolation(); + } + + public function testFailingExpressionAtObjectLevelWithToString() + { + $constraint = new Expression(array( + 'expression' => 'this.data == 1', + 'message' => 'myMessage', + )); + + $object = new ToString(); + $object->data = '2'; + + $this->setObject($object); + + $this->validator->validate($object, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', 'toString') + ->setCode(Expression::EXPRESSION_FAILED_ERROR) + ->assertRaised(); + } + public function testSucceedingExpressionAtPropertyLevel() { $constraint = new Expression('value == this.data'); diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php b/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php new file mode 100644 index 0000000000000..714fdb9e98f5f --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class ToString +{ + public $data; + + public function __toString() + { + return 'toString'; + } +} diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index 5ac343bfbc9af..ca3e57a75266b 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -374,7 +374,7 @@ private function validateObject($object, $propertyPath, array $groups, $traversa * objects are iterated as well. Nested arrays are always iterated, * regardless of the value of $recursive. * - * @param array|\Traversable $collection The collection + * @param iterable $collection The collection * @param string $propertyPath The current property path * @param string[] $groups The validated groups * @param ExecutionContextInterface $context The current execution context diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 29f002b8cc1a6..5fdae442dcda8 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -118,8 +118,8 @@ public static function filter(array $a, $filter, array $listedProperties = array if (null === $v) { $type |= self::EXCLUDE_NULL & $filter; - } - if (empty($v)) { + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || array() === $v) { $type |= self::EXCLUDE_EMPTY & $filter; } if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) { diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php index 5399c66010a89..1f588db82cae5 100644 --- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php @@ -94,13 +94,16 @@ protected function doClone($var) // Create $stub when the original value $v can not be used directly // If $v is a nested structure, put that structure in array $a switch (true) { - case empty($v): - case true === $v: + case null === $v: + case \is_bool($v): case \is_int($v): case \is_float($v): continue 2; case \is_string($v): + if ('' === $v) { + continue 2; + } if (!\preg_match('//u', $v)) { $stub = new Stub(); $stub->type = Stub::TYPE_STRING; @@ -124,6 +127,9 @@ protected function doClone($var) break; case \is_array($v): + if (!$v) { + continue 2; + } $stub = $arrayStub; $stub->class = Stub::ARRAY_INDEXED; diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php index d747960fddacc..b0997500339b1 100644 --- a/src/Symfony/Component/Yaml/Inline.php +++ b/src/Symfony/Component/Yaml/Inline.php @@ -462,6 +462,7 @@ private static function parseMapping($mapping, $flags, &$i = 0, $references = ar $output = array(); $len = strlen($mapping); ++$i; + $allowOverwrite = false; // {foo: bar, bar:foo, ...} while ($i < $len) { @@ -502,6 +503,10 @@ private static function parseMapping($mapping, $flags, &$i = 0, $references = ar @trigger_error(sprintf('Using a colon after an unquoted mapping key that is not followed by an indication character (i.e. " ", ",", "[", "]", "{", "}") is deprecated since version 3.2 and will throw a ParseException in 4.0 on line %d.', self::$parsedLineNumber + 1), E_USER_DEPRECATED); } + if ('<<' === $key) { + $allowOverwrite = true; + } + while ($i < $len) { if (':' === $mapping[$i] || ' ' === $mapping[$i]) { ++$i; @@ -510,7 +515,6 @@ private static function parseMapping($mapping, $flags, &$i = 0, $references = ar } $tag = self::parseTag($mapping, $i, $flags); - $duplicate = false; switch ($mapping[$i]) { case '[': // nested sequence @@ -518,9 +522,19 @@ private static function parseMapping($mapping, $flags, &$i = 0, $references = ar // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (isset($output[$key])) { + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { @trigger_error(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0 on line %d.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED); - $duplicate = true; } break; case '{': @@ -529,9 +543,17 @@ private static function parseMapping($mapping, $flags, &$i = 0, $references = ar // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (isset($output[$key])) { + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { @trigger_error(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0 on line %d.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED); - $duplicate = true; } break; default: @@ -539,20 +561,20 @@ private static function parseMapping($mapping, $flags, &$i = 0, $references = ar // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (isset($output[$key])) { + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { @trigger_error(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since version 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0 on line %d.', $key, self::$parsedLineNumber + 1), E_USER_DEPRECATED); - $duplicate = true; } --$i; } - - if (!$duplicate) { - if (null !== $tag) { - $output[$key] = new TaggedValue($tag, $value); - } else { - $output[$key] = $value; - } - } ++$i; continue 2; diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 5093d7cd1297f..a6e39007dfe0f 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -601,21 +601,10 @@ private function getNextEmbedBlock($indentation = null, $inSequence = false) continue; } - // we ignore "comment" lines only when we are not inside a scalar block - if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { - // remember ignored comment lines (they are used later in nested - // parser calls to determine real line numbers) - // - // CAUTION: beware to not populate the global property here as it - // will otherwise influence the getRealCurrentLineNb() call here - // for consecutive comment lines and subsequent embedded blocks - $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb(); - - continue; - } - if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; } elseif (0 == $indent) { $this->moveToPreviousLine(); @@ -711,6 +700,8 @@ private function parseValue($value, $flags, $context) return Inline::parse($value, $flags, $this->refs); } + $lines = array(); + while ($this->moveToNextLine()) { // unquoted strings end before the first unindented line if (null === $quotation && 0 === $this->getCurrentLineIndentation()) { @@ -719,7 +710,7 @@ private function parseValue($value, $flags, $context) break; } - $value .= ' '.trim($this->currentLine); + $lines[] = trim($this->currentLine); // quoted string values end with a line that is terminated with the quotation character if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) { @@ -727,6 +718,19 @@ private function parseValue($value, $flags, $context) } } + for ($i = 0, $linesCount = count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); $parsedValue = Inline::parse($value, $flags, $this->refs); diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml index 18e5abf939f7c..83264cfab8190 100644 --- a/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml +++ b/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml @@ -21,6 +21,7 @@ yaml: | c: foo: bar bar: foo + bar_inline: {a: before, d: other, <<: *foo, b: new, x: Oren, c: { foo: bar, bar: foo}} foo2: &foo2 a: Ballmer ding: &dong [ fi, fei, fo, fam] @@ -42,14 +43,19 @@ yaml: | p: 12345 z: <<: *nestedref + head_inline: &head_inline { <<: [ *foo , *dong , *foo2 ] } + recursive_inline: { <<: *head_inline, c: { <<: *foo2 } } php: | array( 'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull'), 'bar' => array('a' => 'before', 'd' => 'other', 'e' => null, 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'), + 'bar_inline' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'e' => 'notnull', 'x' => 'Oren'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'), 'taz' => array('a' => 'Steve', 'w' => array('p' => 1234)), - 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)) + 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)), + 'head_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'), + 'recursive_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => array('a' => 'Ballmer'), 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'), ) diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 6cae75ae6ebbc..1d35ecc1bd819 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -1554,6 +1554,38 @@ public function testMultiLineQuotedStringWithTrailingBackslash() $this->assertSame(array('foobar' => 'foobar'), $this->parser->parse($yaml)); } + public function testCommentCharactersInMultiLineQuotedStrings() + { + $yaml = << array( + 'foobar' => 'foo #bar', + 'bar' => 'baz', + ), + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + + public function testBlankLinesInQuotedMultiLineString() + { + $yaml = << "foo\nbar", + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + public function testParseMultiLineUnquotedString() { $yaml = <<assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP)); } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage Reference "foo" does not exist + */ + public function testEvalRefException() + { + $yaml = <<parser->parse($yaml); + } } class B