diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000000000..8fb89ca158094 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,76 @@ +build: false +clone_depth: 2 +clone_folder: c:\projects\symfony + +cache: + - composer.phar + - .phpunit -> phpunit + +init: + - SET PATH=c:\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET SYMFONY_DEPRECATIONS_HELPER=strict + - SET "SYMFONY_REQUIRE=>=2.8" + - SET ANSICON=121x90 (121x90) + - SET SYMFONY_PHPUNIT_VERSION=4.8 + - REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v DelayedExpansion /t REG_DWORD /d 1 /f + +install: + - mkdir c:\php && cd c:\php + - appveyor DownloadFile https://raw.githubusercontent.com/symfony/binary-utils/master/cacert.pem + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-5.3.9-nts-Win32-VC9-x86.zip + - appveyor DownloadFile https://raw.githubusercontent.com/symfony/binary-utils/master/ICU-51.2-dlls.zip + - 7z x ICU-51.2-dlls.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php-7.1.3-Win32-VC14-x86.zip + - 7z x php-7.1.3-Win32-VC14-x86.zip -y >nul + - cd ext + - appveyor DownloadFile https://raw.githubusercontent.com/symfony/binary-utils/master/php_intl-3.0.0-5.3-nts-vc9-x86.zip + - 7z x php_intl-3.0.0-5.3-nts-vc9-x86.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_apcu-4.0.10-5.3-nts-vc9-x86.zip + - 7z x php_apcu-4.0.10-5.3-nts-vc9-x86.zip -y >nul + - appveyor DownloadFile https://github.com/symfony/binary-utils/releases/download/v0.1/php_memcache-3.0.8-5.3-nts-vc9-x86.zip + - 7z x php_memcache-3.0.8-5.3-nts-vc9-x86.zip -y >nul + - cd .. + - copy /Y php.ini-development php.ini-min + - echo memory_limit=-1 >> php.ini-min + - echo serialize_precision=14 >> php.ini-min + - echo max_execution_time=1200 >> php.ini-min + - echo date.timezone="America/Los_Angeles" >> php.ini-min + - echo extension_dir=ext >> php.ini-min + - copy /Y php.ini-min php.ini-max + - echo extension=php_openssl.dll >> php.ini-max + - echo extension=php_apcu.dll >> php.ini-max + - echo apc.enable_cli=1 >> php.ini-max + - echo extension=php_memcache.dll >> php.ini-max + - echo extension=php_intl.dll >> php.ini-max + - echo extension=php_mbstring.dll >> php.ini-max + - echo extension=php_fileinfo.dll >> php.ini-max + - echo extension=php_pdo_sqlite.dll >> php.ini-max + - echo extension=php_ldap.dll >> php.ini-max + - echo extension=php_curl.dll >> php.ini-max + - echo curl.cainfo=c:\php\cacert.pem >> php.ini-max + - copy /Y php.ini-min php.ini + - echo extension=php_openssl.dll >> php.ini + - cd c:\projects\symfony + - IF NOT EXIST composer.phar (appveyor DownloadFile https://github.com/composer/composer/releases/download/1.7.1/composer.phar) + - php composer.phar self-update + - copy /Y .composer\* %APPDATA%\Composer\ + - php composer.phar global require --no-progress --no-scripts --no-plugins symfony/flex dev-master + - php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit + - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) + - php composer.phar config platform.php 5.3.9 + - php composer.phar update --no-progress --no-suggest --ansi + - php phpunit install + +test_script: + - SET X=0 + - cd c:\php && copy /Y php.ini-min php.ini + - cd c:\projects\symfony + - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - cd c:\php && 7z x php-5.3.9-nts-Win32-VC9-x86.zip -y >nul && copy /Y php.ini-min php.ini + - cd c:\projects\symfony + - SET SYMFONY_PHPUNIT_SKIPPED_TESTS=phpunit.skipped + - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - copy /Y c:\php\php.ini-max c:\php\php.ini + - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - exit %X% diff --git a/.composer/config.json b/.composer/config.json new file mode 100644 index 0000000000000..941bc3b56e8cd --- /dev/null +++ b/.composer/config.json @@ -0,0 +1,7 @@ +{ + "config": { + "preferred-install": { + "*": "dist" + } + } +} diff --git a/.editorconfig b/.editorconfig index 153cf3ef5bd04..d769b46a4b6ff 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,8 +3,17 @@ root = true ; Unix-style newlines [*] +charset = utf-8 end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true -[*.php] +[*.{php,html,twig}] indent_style = space indent_size = 4 + +[*.md] +max_line_length = 80 + +[COMMIT_EDITMSG] +max_line_length = 0 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..0bef18db33536 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,29 @@ +# Console +/src/Symfony/Component/Console/Logger/ConsoleLogger.php @dunglas +# DependencyInjection +/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @dunglas +# HttpKernel +/src/Symfony/Component/HttpKernel/Log/Logger.php @dunglas +# LDAP +/src/Symfony/Component/Ldap/* @csarrazi +# Lock +/src/Symfony/Component/Lock/* @jderusse +# Messenger +/src/Symfony/Bridge/Doctrine/Messenger/* @sroze +/src/Symfony/Component/Messenger/* @sroze +# PropertyInfo +/src/Symfony/Component/PropertyInfo/* @dunglas +/src/Symfony/Bridge/Doctrine/PropertyInfo/* @dunglas +# Serializer +/src/Symfony/Component/Serializer/* @dunglas +# WebLink +/src/Symfony/Component/WebLink/* @dunglas +# Workflow +/src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php @lyrixx +/src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php @lyrixx +/src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php @lyrixx +/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php @lyrixx +/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php @lyrixx +/src/Symfony/Component/Workflow/* @lyrixx +# Yaml +/src/Symfony/Component/Yaml/* @xabbuh diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..16e2603b76a1d --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,8 @@ +# Code of Conduct + +This project follows a [Code of Conduct][code_of_conduct] in order to ensure an open and welcoming environment. +Please read the full text for understanding the accepted and unaccepted behavior. +Please read also the [reporting guidelines][guidelines], in case you encountered or witnessed any misbehavior. + +[code_of_conduct]: https://symfony.com/doc/current/contributing/code_of_conduct/index.html +[guidelines]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000000..6eaec7c81da9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,13 @@ +| Q | A +| ---------------- | ----- +| Bug report? | yes/no +| Feature request? | yes/no +| BC Break report? | yes/no +| RFC? | yes/no +| Symfony version | x.y.z + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000000..b6f39741d9dbc --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +| Q | A +| ------------- | --- +| Branch? | master for features / 2.8 up to 4.1 for bug fixes +| Bug fix? | yes/no +| New feature? | yes/no +| BC breaks? | no +| Deprecations? | yes/no +| Tests pass? | yes +| Fixed tickets | #... +| License | MIT +| Doc PR | symfony/symfony-docs#... + + diff --git a/.github/build-packages.php b/.github/build-packages.php new file mode 100644 index 0000000000000..b09cea2fe230a --- /dev/null +++ b/.github/build-packages.php @@ -0,0 +1,84 @@ + $_SERVER['argc']) { + echo "Usage: branch dir1 dir2 ... dirN\n"; + exit(1); +} +chdir(dirname(__DIR__)); + +$json = ltrim(file_get_contents('composer.json')); +if ($json !== $package = preg_replace('/\n "repositories": \[\n.*?\n \],/s', '', $json)) { + file_put_contents('composer.json', $package); +} + +$dirs = $_SERVER['argv']; +array_shift($dirs); +$mergeBase = trim(shell_exec(sprintf('git merge-base "%s" HEAD', array_shift($dirs)))); + +$packages = array(); +$flags = \PHP_VERSION_ID >= 50400 ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0; + +foreach ($dirs as $k => $dir) { + if (!system("git diff --name-only $mergeBase -- $dir", $exitStatus)) { + if ($exitStatus) { + exit($exitStatus); + } + unset($dirs[$k]); + continue; + } + echo "$dir\n"; + + $json = ltrim(file_get_contents($dir.'/composer.json')); + if (null === $package = json_decode($json)) { + passthru("composer validate $dir/composer.json"); + exit(1); + } + + $package->repositories = array(array( + 'type' => 'composer', + 'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/', + )); + if (false === strpos($json, "\n \"repositories\": [\n")) { + $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); + file_put_contents($dir.'/composer.json', $json); + } + passthru("cd $dir && tar -cf package.tar --exclude='package.tar' *"); + + if (!isset($package->extra->{'branch-alias'}->{'dev-master'})) { + echo "Missing \"dev-master\" branch-alias in composer.json extra.\n"; + exit(1); + } + $package->version = str_replace('-dev', '.x-dev', $package->extra->{'branch-alias'}->{'dev-master'}); + $package->dist['type'] = 'tar'; + $package->dist['url'] = 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__))."/$dir/package.tar"; + + $packages[$package->name][$package->version] = $package; + + $versions = @file_get_contents('https://repo.packagist.org/p/'.$package->name.'.json') ?: sprintf('{"packages":{"%s":{"dev-master":%s}}}', $package->name, file_get_contents($dir.'/composer.json')); + $versions = json_decode($versions)->packages->{$package->name}; + + if ($package->version === str_replace('-dev', '.x-dev', $versions->{'dev-master'}->extra->{'branch-alias'}->{'dev-master'})) { + unset($versions->{'dev-master'}); + } + + foreach ($versions as $v => $package) { + $packages[$package->name] += array($v => $package); + } +} + +file_put_contents('packages.json', json_encode(compact('packages'), $flags)); + +if ($dirs) { + $json = ltrim(file_get_contents('composer.json')); + if (null === $package = json_decode($json)) { + passthru("composer validate $dir/composer.json"); + exit(1); + } + + $package->repositories = array(array( + 'type' => 'composer', + 'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/', + )); + $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); + file_put_contents('composer.json', $json); +} diff --git a/.github/rm-invalid-lowest-lock-files.php b/.github/rm-invalid-lowest-lock-files.php new file mode 100644 index 0000000000000..c036fd356f045 --- /dev/null +++ b/.github/rm-invalid-lowest-lock-files.php @@ -0,0 +1,158 @@ + array(), 'packages-dev' => array()); + $composerJsons[$composerJson['name']] = array($dir, $composerLock['packages'] + $composerLock['packages-dev'], getRelevantContent($composerJson)); +} + +$referencedCommits = array(); + +foreach ($composerJsons as list($dir, $lockedPackages)) { + foreach ($lockedPackages as $lockedJson) { + if (0 !== strpos($version = $lockedJson['version'], 'dev-') && '-dev' !== substr($version, -4)) { + continue; + } + + if (!isset($composerJsons[$name = $lockedJson['name']])) { + echo "$dir/composer.lock references missing $name.\n"; + @unlink($dir.'/composer.lock'); + continue 2; + } + + if (isset($composerJsons[$name][2]['repositories']) && !isset($lockedJson['repositories'])) { + // the locked package has been patched locally but the lock references a commit, + // which means the referencing package itself is not modified + continue; + } + + foreach (array('minimum-stability', 'prefer-stable') as $key) { + if (array_key_exists($key, $composerJsons[$name][2])) { + $lockedJson[$key] = $composerJsons[$name][2][$key]; + } + } + + // use weak comparison to ignore ordering + if (getRelevantContent($lockedJson) != $composerJsons[$name][2]) { + echo "$dir/composer.lock is not in sync with $name.\n"; + @unlink($dir.'/composer.lock'); + continue 2; + } + + if ($lockedJson['dist']['reference']) { + $referencedCommits[$name][$lockedJson['dist']['reference']][] = $dir; + } + } +} + +if (!$referencedCommits) { + return; +} + +@mkdir($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org', 0777, true); + +$ch = null; +$mh = curl_multi_init(); +$sh = curl_share_init(); +curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); +curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); +curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); +$chs = array(); + +foreach ($referencedCommits as $name => $dirsByCommit) { + $chs[] = $ch = array(curl_init(), fopen($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org/provider-'.strtr($name, '/', '$').'.json', 'wb')); + curl_setopt($ch[0], CURLOPT_URL, 'https://repo.packagist.org/p/'.$name.'.json'); + curl_setopt($ch[0], CURLOPT_FILE, $ch[1]); + curl_setopt($ch[0], CURLOPT_SHARE, $sh); + curl_multi_add_handle($mh, $ch[0]); +} + +do { + curl_multi_exec($mh, $active); + curl_multi_select($mh); +} while ($active); + +foreach ($chs as list($ch, $fd)) { + curl_multi_remove_handle($mh, $ch); + curl_close($ch); + fclose($fd); +} + +foreach ($referencedCommits as $name => $dirsByCommit) { + $repo = file_get_contents($_SERVER['HOME'].'/.cache/composer/repo/https---repo.packagist.org/provider-'.strtr($name, '/', '$').'.json'); + $repo = json_decode($repo, true); + + foreach ($repo['packages'][$name] as $version) { + unset($referencedCommits[$name][$version['source']['reference']]); + } +} + +foreach ($referencedCommits as $name => $dirsByCommit) { + foreach ($dirsByCommit as $dirs) { + foreach ($dirs as $dir) { + if (file_exists($dir.'/composer.lock')) { + echo "$dir/composer.lock references old commit for $name.\n"; + @unlink($dir.'/composer.lock'); + } + } + } +} diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 11d42d1287e47..0000000000000 --- a/.php_cs +++ /dev/null @@ -1,28 +0,0 @@ -setUsingLinter(false) - ->setUsingCache(true) - ->finder( - Symfony\CS\Finder\DefaultFinder::create() - ->in(__DIR__) - ->exclude(array( - // directories containing files with content that is autogenerated by `var_export`, which breaks CS in output code - 'src/Symfony/Component/DependencyInjection/Tests/Fixtures', - 'src/Symfony/Component/Routing/Tests/Fixtures/dumper', - // fixture templates - 'src/Symfony/Component/Templating/Tests/Fixtures/templates', - // resource templates - 'src/Symfony/Bundle/FrameworkBundle/Resources/views/Form', - )) - // file content autogenerated by `var_export` - ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php') - // autogenerated xmls - ->notPath('src/Symfony/Component/Console/Tests/Fixtures/application_1.xml') - ->notPath('src/Symfony/Component/Console/Tests/Fixtures/application_2.xml') - // yml - ->notPath('src/Symfony/Component/Yaml/Tests/Fixtures/sfTests.yml') - // test template - ->notPath('src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php') - ) -; diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000000000..8398c437127e8 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,55 @@ +setRules(array( + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit48Migration:risky' => true, + 'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice + 'array_syntax' => array('syntax' => 'long'), + 'fopen_flags' => false, + 'ordered_imports' => true, + 'protected_to_private' => false, + // rule disabled due to https://bugs.php.net/bug.php?id=60573 bug; + // to be re-enabled (by dropping next line, rule is part of @Symfony already) on branch that requires PHP 5.4+ + 'self_accessor' => false, + // Part of @Symfony:risky in PHP-CS-Fixer 2.13.0. To be removed from the config file once upgrading + 'native_function_invocation' => array('include' => array('@compiler_optimized'), 'scope' => 'namespaced'), + // Part of future @Symfony ruleset in PHP-CS-Fixer To be removed from the config file once upgrading + 'phpdoc_types_order' => array('null_adjustment' => 'always_last', 'sort_algorithm' => 'none'), + )) + ->setRiskyAllowed(true) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->append(array(__FILE__)) + ->exclude(array( + // directories containing files with content that is autogenerated by `var_export`, which breaks CS in output code + 'Symfony/Component/DependencyInjection/Tests/Fixtures', + 'Symfony/Component/Routing/Tests/Fixtures/dumper', + // fixture templates + 'Symfony/Component/Templating/Tests/Fixtures/templates', + 'Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom', + // generated fixtures + 'Symfony/Component/VarDumper/Tests/Fixtures', + // resource templates + 'Symfony/Bundle/FrameworkBundle/Resources/views/Form', + // explicit trigger_error tests + 'Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/', + )) + // Support for older PHPunit version + ->notPath('Symfony/Bridge/PhpUnit/SymfonyTestsListener.php') + // file content autogenerated by `var_export` + ->notPath('Symfony/Component/Translation/Tests/fixtures/resources.php') + // test template + ->notPath('Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php') + // explicit heredoc test + ->notPath('Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php') + // explicit trigger_error tests + ->notPath('Symfony/Component/Debug/Tests/DebugClassLoaderTest.php') + ) +; diff --git a/.travis.php b/.travis.php deleted file mode 100644 index 1334f14ad4ace..0000000000000 --- a/.travis.php +++ /dev/null @@ -1,47 +0,0 @@ - $_SERVER['argc']) { - echo "Usage: commit-range branch dir1 dir2 ... dirN\n"; - exit(1); -} - -$dirs = $_SERVER['argv']; -array_shift($dirs); -$range = array_shift($dirs); -$branch = array_shift($dirs); - -$packages = array(); -$flags = PHP_VERSION_ID >= 50400 ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0; - -foreach ($dirs as $dir) { - if (!`git diff --name-only $range -- $dir`) { - continue; - } - echo "$dir\n"; - - $json = file_get_contents($dir.'/composer.json'); - $package = json_decode($json); - - $package->repositories = array(array( - 'type' => 'composer', - 'url' => 'file://'.__DIR__.'/', - )); - $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); - file_put_contents($dir.'/composer.json', $json); - passthru("cd $dir && tar -cf package.tar --exclude='package.tar' *"); - - $package->version = $branch.'.x-dev'; - $package->dist['type'] = 'tar'; - $package->dist['url'] = 'file://'.__DIR__."/$dir/package.tar"; - - $packages[$package->name][$package->version] = $package; - - $versions = file_get_contents('https://packagist.org/packages/'.$package->name.'.json'); - $versions = json_decode($versions); - - foreach ($versions->package->versions as $version => $package) { - $packages[$package->name] += array($version => $package); - } -} - -file_put_contents('packages.json', json_encode(compact('packages'), $flags)); diff --git a/.travis.yml b/.travis.yml index 6e37c138c494a..34ef0b343e0d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,60 +1,249 @@ language: php -sudo: false +dist: trusty + +git: + depth: 2 addons: apt_packages: - parallel - language-pack-fr-base -cache: - directories: - - .phpunit +env: + global: + - MIN_PHP=5.4.9 + - SYMFONY_PROCESS_PHP_TEST_BINARY=~/.phpenv/versions/5.6/bin/php matrix: include: - - php: hhvm - - php: 5.3 + - php: hhvm-3.18 + sudo: required + group: edge - php: 5.4 - - php: 5.5 - - php: 5.6 + env: php_extra="5.5 5.6 7.0" + - php: 7.1 env: deps=high - - php: 7 + - php: 7.2 env: deps=low fast_finish: true -services: mongodb +cache: + directories: + - .phpunit + - php-$MIN_PHP + - ~/php-ext -env: - global: - - deps=no - - SYMFONY_DEPRECATIONS_HELPER=weak +services: mongodb before_install: - - if [[ "$deps" = "no" ]] && [[ "$TRAVIS_PHP_VERSION" =~ 5.[45] ]] && [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then export deps=skip; fi; - - if [ "$deps" != "skip" ]; then composer self-update; fi; - - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "memory_limit = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then phpenv config-rm xdebug.ini; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then echo "extension = memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.7 && echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) || echo "Let's continue without apcu extension"; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then pecl install -f memcached-2.1.0 || echo "Let's continue without memcached extension"; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]] && [ "$deps" = "no" ]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo "extension = $(pwd)/modules/symfony_debug.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini); fi; - - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then echo "extension = ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi; - - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; fi; - - if [ "$deps" != "skip" ]; then ./phpunit install; fi; - - export PHPUNIT="$(readlink -f ./phpunit)" + - | + # General configuration + set -e + stty cols 120 + [ -d ~/.composer ] || mkdir ~/.composer + cp .composer/* ~/.composer/ + export PHPUNIT=$(readlink -f ./phpunit) + export PHPUNIT_X="$PHPUNIT --exclude-group tty,benchmark,intl-data" + export COMPOSER_UP='composer update --no-progress --no-suggest --ansi' + export COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') + find ~/.phpenv -name xdebug.ini -delete + + if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + composer () { + $HOME/.phpenv/versions/7.1/bin/php $HOME/.phpenv/versions/7.1/bin/composer config platform.php $(echo ' /dev/null 2>&1; then + cmd="gdate" + elif [[ "$os" = Darwin ]]; then + format="+%s000000000" + fi + $cmd -u $format + } + export -f nanoseconds + + # tfold is a helper to create folded reports + tfold () { + local title="🐘 $PHP $1" + local fold=$(echo $title | sed -r 's/[^-_A-Za-z0-9]+/./g') + shift + local id=$(printf %08x $(( RANDOM * RANDOM ))) + local start=$(nanoseconds) + echo -e "travis_fold:start:$fold" + echo -e "travis_time:start:$id" + echo -e "\\e[1;34m$title\\e[0m" + + bash -xc "$*" 2>&1 + local ok=$? + local end=$(nanoseconds) + echo -e "\\ntravis_time:end:$id:start=$start,finish=$end,duration=$(($end-$start))" + (exit $ok) && + echo -e "\\e[32mOK\\e[0m $title\\n\\ntravis_fold:end:$fold" || + echo -e "\\e[41mKO\\e[0m $title\\n" + (exit $ok) + } + export -f tfold + + # tpecl is a helper to compile and cache php extensions + tpecl () { + local ext_name=$1 + local ext_so=$2 + local INI=$3 + local ext_dir=$(php -r "echo ini_get('extension_dir');") + local ext_cache=~/php-ext/$(basename $ext_dir)/$ext_name + + if [[ -e $ext_cache/$ext_so ]]; then + echo extension = $ext_cache/$ext_so >> $INI + else + rm ~/.pearrc /tmp/pear 2>/dev/null || true + mkdir -p $ext_cache + echo yes | pecl install -f $ext_name && + cp $ext_dir/$ext_so $ext_cache + fi + } + export -f tpecl + + - | + # Install sigchild-enabled PHP to test the Process component on the lowest PHP matrix line + if [[ ! $deps && $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} && ! -d php-$MIN_PHP/sapi ]]; then + wget http://museum.php.net/php5/php-$MIN_PHP.tar.bz2 -O - | tar -xj && + (cd php-$MIN_PHP && ./configure --enable-sigchild --enable-pcntl && make -j2) + fi + + - | + # php.ini configuration + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + if [[ $PHP = hhvm* ]]; then + INI=/etc/hhvm/php.ini + else + phpenv global $PHP 2>/dev/null || (cd / && wget https://s3.amazonaws.com/travis-php-archives/binaries/ubuntu/14.04/x86_64/php-$PHP.tar.bz2 -O - | tar -xj) + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + fi + echo date.timezone = Europe/Paris >> $INI + echo memory_limit = -1 >> $INI + echo session.gc_probability = 0 >> $INI + echo opcache.enable_cli = 1 >> $INI + echo hhvm.jit = 0 >> $INI + echo apc.enable_cli = 1 >> $INI + [[ $PHP = 5.* ]] && echo extension = memcache.so >> $INI + if [[ $PHP = 5.* ]]; then + echo extension = mongo.so >> $INI + fi + done + + - | + # Install extra PHP extensions + for PHP in $TRAVIS_PHP_VERSION $php_extra; do + if [[ $PHP = hhvm* ]]; then + continue + fi + export PHP=$PHP + phpenv global $PHP + INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini + if [[ $PHP = 5.* ]]; then + tfold ext.memcached tpecl memcached-2.1.0 memcached.so $INI + tfold ext.apcu tpecl apcu-4.0.11 apcu.so $INI + [[ $deps ]] && continue + ext_cache=~/php-ext/$(php -r "echo basename(ini_get('extension_dir'));")/symfony_debug.so + [[ -e $ext_cache ]] || (tfold ext.symfony_debug "cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && mv modules/symfony_debug.so $ext_cache && phpize --clean") + echo extension = $ext_cache >> $INI + elif [[ $PHP = 7.* ]]; then + tfold ext.apcu tpecl apcu-5.1.6 apcu.so $INI + tfold ext.mongodb tpecl mongodb-1.6.0alpha1 mongodb.so $INI + fi + done install: - - if [ "$TRAVIS_BRANCH" = "master" ]; then export COMPOSER_ROOT_VERSION=dev-master; else export COMPOSER_ROOT_VERSION="$TRAVIS_BRANCH".x-dev; fi; - - if [ "$deps" = "no" ]; then export SYMFONY_DEPRECATIONS_HELPER=strict; fi; - - if [ "$deps" = "no" ]; then composer --prefer-source install; fi; - - if [ "$deps" != "skip" ]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; - - if [ "$deps" != "skip" ] && [ "$deps" != "no" ]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi; + - | + # Create local composer packages for each patched components and reference them in composer.json files when cross-testing components + if [[ ! $deps ]]; then + php .github/build-packages.php HEAD^ src/Symfony/Bridge/PhpUnit + else + export SYMFONY_DEPRECATIONS_HELPER=weak && + cp composer.json composer.json.orig && + echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json && + php .github/build-packages.php HEAD^ $COMPONENTS && + mv composer.json composer.json.phpunit && + mv composer.json.orig composer.json + fi + + - | + # For the master branch, when deps=high, the version before master is checked out and tested with the locally patched components + if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then + SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//) && + git fetch origin $SYMFONY_VERSION && + git checkout -m FETCH_HEAD && + COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n') + else + SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*') + fi + + - | + # Install symfony/flex + if [[ $deps = low ]]; then + export SYMFONY_REQUIRE='>=2.3' + else + export SYMFONY_REQUIRE=">=$SYMFONY_VERSION" + fi + composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master + + - | + # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one + [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]] && LEGACY=,legacy + + export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev + if [[ $deps ]]; then mv composer.json.phpunit composer.json; fi + + - | + # phpinfo + if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi + + - | + run_tests () { + set -e + export PHP=$1 + if [[ $PHP != $TRAVIS_PHP_VERSION && $TRAVIS_PULL_REQUEST != false ]]; then + echo -e "\\n\\e[1;34mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m" + break + fi + phpenv global ${PHP/hhvm*/hhvm} + if [[ $PHP = 7.* ]]; then + ([[ $deps ]] && cd src/Symfony/Component/HttpFoundation; composer config platform.ext-mongodb 1.6.0; composer require --dev --no-update mongodb/mongodb) + fi + tfold 'composer update' $COMPOSER_UP + if [[ $TRAVIS_PHP_VERSION = 5.* || $TRAVIS_PHP_VERSION = hhvm* ]]; then + tfold 'phpunit install' 'composer global remove symfony/flex && ./phpunit install && composer global require --no-progress --no-scripts --no-plugins symfony/flex dev-master' + else + tfold 'phpunit install' ./phpunit install + fi + if [[ $deps = high ]]; then + echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && $COMPOSER_UP && $PHPUNIT_X$LEGACY'" + elif [[ $deps = low ]]; then + [[ -e ~/php-ext/composer-lowest.lock.tar ]] && tar -xf ~/php-ext/composer-lowest.lock.tar + tar -cf ~/php-ext/composer-lowest.lock.tar --files-from /dev/null + php .github/rm-invalid-lowest-lock-files.php $COMPONENTS + echo "$COMPONENTS" | parallel --gnu -j10% "tfold {} 'cd {} && ([ -e composer.lock ] && ${COMPOSER_UP/update/install} || $COMPOSER_UP --prefer-lowest --prefer-stable) && $PHPUNIT_X'" + echo "$COMPONENTS" | xargs -n1 -I{} tar --append -f ~/php-ext/composer-lowest.lock.tar {}/composer.lock + elif [[ $PHP = hhvm* ]]; then + $PHPUNIT --exclude-group no-hhvm,benchmark,intl-data + else + echo "$COMPONENTS" | parallel --gnu "tfold {} $PHPUNIT_X {}" + tfold src/Symfony/Component/Console.tty $PHPUNIT src/Symfony/Component/Console --group tty + if [[ $PHP = ${MIN_PHP%.*} ]]; then + export PHP=$MIN_PHP + echo -e "1\\n0" | xargs -I{} bash -c "tfold src/Symfony/Component/Process.sigchild{} SYMFONY_DEPRECATIONS_HELPER=weak ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/" + fi + fi + } script: - - if [ "$deps" = "no" ]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi; - - if [ "$deps" = "no" ]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi; - - if [ "$deps" = "high" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data,legacy'; fi; - - if [ "$deps" = "low" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source --prefer-lowest --prefer-stable update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; - - if [ "$deps" = "skip" ]; then echo 'This matrix line is skipped for pull requests.'; fi; + - for PHP in $TRAVIS_PHP_VERSION $php_extra; do (run_tests $PHP); done diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md index 9c9d593aa08c7..2758f011f3cfd 100644 --- a/CHANGELOG-2.3.md +++ b/CHANGELOG-2.3.md @@ -7,6 +7,189 @@ in 2.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/v2.3.0...v2.3.1 +* 2.3.42 (2016-05-30) + + * bug #18908 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #18893 [DependencyInjection] Skip deep reference check for 'service_container' (RobertMe) + * bug #18812 Catch \Throwable (fprochazka) + * bug #18821 [Form] Removed UTC specification with timestamp (francisbesset) + * bug #18861 Fix for #18843 (inso) + * bug #18907 [Routing] Fix the annotation loader taking a class constant as a beginning of a class name (jakzal, nicolas-grekas) + * bug #18864 [Console][DX] Fixed ambiguous error message when using a duplicate option shortcut (peterrehm) + * bug #18844 [Yaml] fix exception contexts (xabbuh) + * bug #18840 [Yaml] properly handle unindented collections (xabbuh) + * bug #18839 People - person singularization (Keeo) + * bug #18828 [Yaml] chomp newlines only at the end of YAML documents (xabbuh) + * bug #18635 [Console] Prevent fatal error when calling Command::getHelper without helperSet (chalasr) + * bug #18761 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + +* 2.3.41 (2016-05-09) + + * security #18733 limited the maximum length of a submitted username (fabpot) + * bug #18709 [DependencyInjection] top-level anonymous services must be public (xabbuh) + +* 2.3.40 (2016-04-29) + + * bug #18246 [DependencyInjection] fix ambiguous services schema (backbone87) + * bug #18603 [PropertyAccess] ->getValue() should be read-only (nicolas-grekas) + * bug #18280 [Routing] add query param if value is different from default (Tobion) + * bug #18515 [Filesystem] Better error handling in remove() (nicolas-grekas) + * bug #18449 [PropertyAccess] Fix regression (nicolas-grekas) + * bug #18467 [DependencyInjection] Resolve aliases before removing abstract services + add tests (nicolas-grekas) + * bug #18460 [DomCrawler] Fix select option with empty value (Matt Wells) + * bug #18425 [Security] Fixed SwitchUserListener when exiting an impersonation with AnonymousToken (lyrixx) + * bug #18317 [Form] fix "prototype" not required when parent form is not required (HeahDude) + * bug #18439 [Logging] Add support for Firefox (43+) in ChromePhpHandler (arjenm) + * bug #18385 Detect CLI color support for Windows 10 build 10586 (mlocati) + * bug #18426 [EventDispatcher] Try first if the event is Stopped (lyrixx) + * bug #18265 Optimize ReplaceAliasByActualDefinitionPass (ajb-in) + * bug #18358 [Form] NumberToLocalizedStringTransformer should return floats when possible (nicolas-grekas) + * bug #17926 [DependencyInjection] Enable alias for service_container (hason) + * bug #18336 [Debug] Fix handling of php7 throwables (nicolas-grekas) + * bug #18312 [ClassLoader] Fix storing not-found classes in APC cache (nicolas-grekas) + * bug #18255 [HttpFoundation] Fix support of custom mime types with parameters (Ener-Getick) + * bug #18259 [PropertyAccess] Backport fixes from 2.7 (nicolas-grekas) + * bug #18224 [PropertyAccess] Remove most ref mismatches to improve perf (nicolas-grekas) + * bug #18210 [PropertyAccess] Throw an UnexpectedTypeException when the type do not match (dunglas, nicolas-grekas) + * bug #18216 [Intl] Fix invalid numeric literal on PHP 7 (nicolas-grekas) + * bug #18147 [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols (natechicago) + * bug #18175 [Translation] Add support for fuzzy tags in PoFileLoader (nud) + * bug #18179 [Form] Fix NumberToLocalizedStringTransformer::reverseTransform with big integers (ovrflo, nicolas-grekas) + * bug #18164 [HttpKernel] set s-maxage only if all responses are cacheable (xabbuh) + +* 2.3.39 (2016-03-13) + + * bug #18080 [HttpFoundation] Set the Content-Range header if the requested Range is unsatisfied (jakzal) + * bug #18084 [HttpFoundation] Avoid warnings when checking malicious IPs (jakzal) + * bug #18048 [HttpKernel] Fix mem usage when stripping the prod container (nicolas-grekas) + * bug #18065 [Finder] Partially revert #17134 to fix a regression (jakzal) + * bug #18018 [HttpFoundation] exception when registering bags for started sessions (xabbuh) + * bug #18054 [Filesystem] Fix false positive in ->remove() (nicolas-grekas) + * bug #18049 [Validator] Fix the locale validator so it treats a locale alias as a valid locale (jakzal) + * bug #18019 [Intl] Update ICU to version 55 (jakzal) + * bug #16656 [HttpFoundation] automatically generate safe fallback filename (xabbuh) + * bug #15794 [Console] default to stderr in the console helpers (alcohol) + * bug #17984 Allow to normalize \Traversable when serializing xml (Ener-Getick) + * bug #17434 Improved the error message when a template is not found (rvanginneken, javiereguiluz) + * bug #17894 [FrameworkBundle] Fix a regression in handling absolute template paths (jakzal) + * bug #17595 [HttpKernel] Remove _path from query parameters when fragment is a subrequest (cmenning) + * bug #17986 [DomCrawler] Dont use LIBXML_PARSEHUGE by default (nicolas-grekas) + * bug #17668 add 'guid' to list of exception to filter out (garak) + * bug #17615 Ensure backend slashes for symlinks on Windows systems (cpsitgmbh) + * bug #17626 Try to delete broken symlinks (IchHabRecht) + * bug #17978 [Yaml] ensure dump indentation to be greather than zero (xabbuh) + * bug #17976 [WebProfilerBundle] fix debug toolbar rendering by removing inadvertently added links (craue) + * bug #17971 Variadic controller params (NiR-, fabpot) + * bug #17925 [Bridge] The WebProcessor now forwards the client IP (magnetik) + +* 2.3.38 (2016-02-28) + + * bug #17947 Fix - #17676 (backport #17919 to 2.3) (Ocramius) + * bug #17942 Fix bug when using an private aliased factory service (WouterJ) + * bug #17542 ChoiceFormField of type "select" could be "disabled" (bouland) + * bug #17602 [HttpFoundation] Fix BinaryFileResponse incorrect behavior with if-range header (bburnichon) + * bug #17914 [Console] Fix escaping of trailing backslashes (nicolas-grekas) + * bug #17074 Fix constraint validator alias being required (Triiistan) + * bug #17867 [DependencyInjection] replace alias in factory services (xabbuh) + * bug #17569 [FrameworkBundle] read commands from bundles when accessing list (havvg) + * bug #16987 [FileSystem] Windows fix (flip111) + * bug #17835 [Yaml] fix default timezone to be UTC (xabbuh) + * bug #17823 [DependencyInjection] fix dumped YAML string (xabbuh) + * bug #17814 [DependencyInjection] fix dumped YAML snytax (xabbuh) + * bug #17099 [Form] Fixed violation mapping if multiple forms are using the same (or part of the same) property path (alekitto) + * bug #17719 [DependencyInjection] fixed exceptions thrown by get method of ContainerBuilder (lukaszmakuch) + * bug #17742 [DependencyInjection] Fix #16461 Container::set() replace aliases (mnapoli) + * bug #17745 Added more exceptions to singularify method (javiereguiluz) + * bug #17766 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17757 [HttpFoundation] BinaryFileResponse sendContent return as parent. (2.3) (SpacePossum) + * bug #17702 [TwigBridge] forward compatibility with Yaml 3.1 (xabbuh) + * bug #17672 [DependencyInjection][Routing] add files used in FileResource objects (xabbuh) + * bug #17596 [Translation] Add resources from fallback locale to parent catalogue (c960657) + * bug #16956 [DependencyInjection] XmlFileLoader: enforce tags to have a name (xabbuh) + * bug #16265 [BrowserKit] Corrected HTTP_HOST logic (Naktibalda) + * bug #17555 [DependencyInjection] resolve aliases in factory services (xabbuh) + * bug #15272 [FrameworkBundle] Fix template location for PHP templates (jakzal) + * bug #11232 [Routing] Fixes fatal errors with object resources in AnnotationDirectoryLoader::supports (Tischoi) + * bug #17526 Escape the delimiter in Glob::toRegex (javiereguiluz) + * bug #17527 fixed undefined variable (fabpot) + * bug #15706 [framework-bundle] Added support for the `0.0.0.0/0` trusted proxy (zerkms) + * bug #16274 [HttpKernel] Lookup the response even if the lock was released after two second wait (jakzal) + * bug #17355 [DoctrineBridge][Validator] >= 2.3 Pass association instead of ID as argument (xavismeh) + * bug #16736 [Request] Ignore invalid IP addresses sent by proxies (GromNaN) + * bug #16873 Able to load big xml files with DomCrawler (zorn-v) + * bug #16897 [Form] Fix constraints could be null if not set (DZunke) + * bug #17505 sort bundles in config:dump-reference command (xabbuh) + * bug #17478 [HttpFoundation] Do not overwrite the Authorization header if it is already set (jakzal) + * bug #17461 [Yaml] tag for dumped PHP objects must be a local one (xabbuh) + * bug #17423 [Process] Use stream based storage to avoid memory issues (romainneutron) + * bug #17373 [SecurityBundle] fix SecureRandom service constructor args (Tobion) + * bug #17377 Fix performance (PHP5) and memory (PHP7) issues when using token_get_all (nicolas-grekas, peteward) + * bug #17389 [Routing] Fixed correct class name in thrown exception (fixes #17388) (robinvdvleuten) + * bug #17358 [ClassLoader] Use symfony/polyfill-apcu (nicolas-grekas) + * bug #17370 [HttpFoundation][Cookie] Cookie DateTimeInterface fix (wildewouter) + +* 2.3.37 (2016-01-14) + + * security #17359 do not ship with a custom rng implementation (xabbuh, fabpot) + * bug #17326 [Console] Display console application name even when no version set (polc) + * bug #17140 [Serializer] Remove normalizer cache in Serializer class (jvasseur) + * bug #17307 [FrameworkBundle] Fix paths with % in it (like urlencoded) (scaytrase) + * bug #17078 [Bridge] [Doctrine] [Validator] Added support \IteratorAggregate for UniqueEntityValidator (Disparity) + * bug #17287 [HttpKernel] Forcing string comparison on query parameters sort in UriSigner (Tim van Densen) + * bug #17278 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17276 [Process] Fix potential race condition (nicolas-grekas) + * bug #17183 [FrameworkBundle] Set the kernel.name properly after a cache warmup (jakzal) + * bug #17159 [Yaml] recognize when a block scalar is left (xabbuh) + * bug #17195 bug #14246 [Filesystem] dumpFile() non atomic (Hidde Boomsma) + * bug #17177 [Process] Fix potential race condition leading to transient tests (nicolas-grekas) + +* 2.3.36 (2015-12-26) + + * bug #16864 [Yaml] fix indented line handling in folded blocks (xabbuh) + * bug #16826 Embedded identifier support (mihai-stancu) + * bug #17129 [Config] Fix array sort on normalization in edge case (romainneutron) + * bug #17094 [Process] More robustness and deterministic tests (nicolas-grekas) + * bug #17112 [PropertyAccess] Reorder elements array after PropertyPathBuilder::replace (alekitto) + * bug #16797 [Filesystem] Recursively widen non-executable directories (Slamdunk) + * bug #17040 [Console] Avoid extra blank lines when rendering exceptions (ogizanagi) + * bug #17055 [Security] Verify if a password encoded with bcrypt is no longer than 72 characters (jakzal) + * bug #16959 [Form] fix #15544 when a collection type attribute "required" is false, "prototype" should too (HeahDude) + * bug #16860 [Yaml] do not remove "comments" in scalar blocks (xabbuh) + * bug #16971 [HttpFoundation] Added the ability of using BinaryFileResponse with stream wrappers (jakzal, Sander-Toonen) + * bug #17048 Fix the logout path when not using the router (stof) + * bug #17057 [FrameworkBundle][HttpKernel] the finder is required to discover bundle commands (xabbuh) + * bug #16915 [Process] Enhance compatiblity with --enable-sigchild (nicolas-grekas) + * bug #16829 [FrameworkBundle] prevent cache:clear creating too long paths (Tobion) + * bug #16870 [FrameworkBundle] Disable the server:run command when Process component is missing (gnugat, xabbuh) + * bug #16799 Improve error message for undefined DIC aliases (mpdude) + * bug #16772 Refactoring EntityUserProvider::__construct() to not do work, cause cache warm error (weaverryan) + * bug #16753 [Process] Fix signaling/stopping logic on Windows (nicolas-grekas) + * bug #16733 [Console] do not encode backslashes in console default description (Tobion) + * bug #16312 [HttpKernel] clearstatcache() so the Cache sees when a .lck file has been released (mpdude) + * bug #16695 [SecurityBundle] disable the init:acl command if ACL is not used (Tobion) + * bug #16676 [HttpFoundation] Workaround HHVM rewriting HTTP response line (nicolas-grekas) + * bug #16668 [ClassLoader] Fix parsing namespace when token_get_all() is missing (nicolas-grekas) + * bug #16386 Bug #16343 [Router] Too many Routes ? (jelte) + +* 2.3.35 (2015-11-23) + + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + * bug #16292 fix race condition at mkdir (#16258) (ewgRa) + * bug #16462 [PropertyAccess] Fix dynamic property accessing. (dunglas) + * bug #16294 [PropertyAccess] Major performance improvement (dunglas) + * bug #16331 fixed Twig deprecation notices (fabpot) + * bug #16306 [DoctrineBridge] Fix issue which prevent the profiler to explain a query (Baachi) + * bug #16359 Use mb_detect_encoding with $strict = true (nicolas-grekas) + * bug #16144 [Security] don't allow to install the split Security packages (xabbuh) + * 2.3.34 (2015-10-27) * bug #16288 [Process] Inherit env vars by default in PhpProcess (nicolas-grekas) diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index 05bf4d856d835..a209a917388a5 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,914 @@ in 2.7 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/v2.7.0...v2.7.1 +* 2.7.49 (2018-08-01) + + * security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas) + * security #cve-2018-14773 [HttpFoundation] Remove support for legacy and risky HTTP headers (nicolas-grekas) + +* 2.7.48 (2018-05-25) + + * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) + * security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured + * security #cve-2018-11406 clear CSRF tokens when the user is logged out + * security #cve-2018-11385 Adding session strategy to ALL listeners to avoid *any* possible fixation + * security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode + +* 2.7.47 (2018-05-21) + + * bug #26781 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions on transform() (syastrebov) + * bug #27286 [Translation] Add Occitan plural rule (kylekatarnls) + * bug #27246 Disallow invalid characters in session.name (ostrolucky) + * bug #24805 [Security] Fix logout (MatTheCat) + * bug #27141 [Process] Suppress warnings when open_basedir is non-empty (cbj4074) + * bug #27250 [Session] limiting :key for GET_LOCK to 64 chars (oleg-andreyev) + * bug #27237 [Debug] Fix populating error_get_last() for handled silent errors (nicolas-grekas) + * bug #27236 [Filesystem] Fix usages of error_get_last() (nicolas-grekas) + * bug #27152 [HttpFoundation] use brace-style regex delimiters (xabbuh) + * feature #24896 Add CODE_OF_CONDUCT.md (egircys) + * bug #27067 [HttpFoundation] Fix setting session-related ini settings (e-moe) + +* 2.7.46 (2018-04-27) + + * bug #26831 [Bridge/Doctrine] count(): Parameter must be an array or an object that implements Countable (gpenverne) + * bug #27044 [Security] Skip user checks if not implementing UserInterface (chalasr) + * bug #26910 Use new PHP7.2 functions in hasColorSupport (johnstevenson) + * bug #26999 [VarDumper] Fix dumping of SplObjectStorage (corphi) + * bug #26886 Don't assume that file binary exists on *nix OS (teohhanhui) + * bug #26643 Fix that ESI/SSI processing can turn a "private" response "public" (mpdude) + * bug #26932 [Form] Fixed trimming choice values (HeahDude) + * bug #26875 [Console] Don't go past exact matches when autocompleting (nicolas-grekas) + * bug #26823 [Validator] Fix LazyLoadingMetadataFactory with PSR6Cache for non classname if tested values isn't existing class (Pascal Montoya, pmontoya) + * bug #26834 [Yaml] Throw parse error on unfinished inline map (nicolas-grekas) + +* 2.7.45 (2018-04-06) + + * bug #26763 [Finder] Remove duplicate slashes in filenames (helhum) + * bug #26749 Add PHPDbg support to HTTP components (hkdobrev) + * bug #26609 [Console] Fix check of color support on Windows (mlocati) + +* 2.7.44 (2018-04-02) + + * bug #26727 [HttpCache] Unlink tmp file on error (Chansig) + * bug #26675 [HttpKernel] DumpDataCollector: do not flush when a dumper is provided (ogizanagi) + * bug #26663 [TwigBridge] Fix rendering of currency by MoneyType (ro0NL) + * bug #26677 Support phpdbg SAPI in Debug::enable() (hkdobrev) + * bug #26621 [Form] no type errors with invalid submitted data types (xabbuh) + * bug #26337 [Finder] Fixed leading/trailing / in filename (lyrixx) + * bug #26584 [TwigBridge] allow html5 compatible rendering of forms with null names (systemist) + * bug #24401 [Form] Change datetime to datetime-local for HTML5 datetime input (pierredup) + * bug #26370 [Security] added userChecker to SimpleAuthenticationProvider (i3or1s) + * bug #26569 [BrowserKit] Fix cookie path handling when $domain is null (dunglas) + * bug #26598 Fixes #26563 (open_basedir restriction in effect) (temperatur) + * bug #26568 [Debug] Reset previous exception handler earlier to prevent infinite loop (nicolas-grekas) + * bug #26567 [DoctrineBridge] Don't rely on ClassMetadataInfo->hasField in DoctrineOrmTypeGuesser anymore (fancyweb) + * bug #26356 [FrameworkBundle] HttpCache is not longer abstract (lyrixx) + * bug #26548 [DomCrawler] Change bad wording in ChoiceFormField::untick (dunglas) + * bug #26433 [DomCrawler] extract(): fix a bug when the attribute list is empty (dunglas) + * bug #26452 [Intl] Load locale aliases to support alias fallbacks (jakzal) + * bug #26450 [CssSelector] Fix CSS identifiers parsing - they can start with dash (jakubkulhan) + +* 2.7.43 (2018-03-05) + + * bug #26368 [WebProfilerBundle] Fix Debug toolbar breaks app (xkobal) + +* 2.7.42 (2018-02-28) + + * bug #26338 [Debug] Keep previous errors of Error instances (Philipp91) + * bug #26312 [Routing] Don't throw 405 when scheme requirement doesn't match (nicolas-grekas) + * bug #26298 Fix ArrayInput::toString() for InputArgument::IS_ARRAY args (maximium) + * bug #25557 [WebProfilerBundle] add a way to limit ajax request (Simperfit) + * bug #26228 [HttpFoundation] Fix missing "throw" in JsonResponse (nicolas-grekas) + * bug #26211 [Console] Suppress warning from sapi_windows_vt100_support (adawolfa) + * bug #26156 Fixes #26136: Avoid emitting warning in hasParameterOption() (greg-1-anderson) + * bug #26183 [DI] Add null check for removeChild (changmin.keum) + * bug #26159 created validator.tl.xlf for Form/Translations (ergiegonzaga) + * bug #26100 [Routing] Throw 405 instead of 404 when redirect is not possible (nicolas-grekas) + * bug #26040 [Process] Check PHP_BINDIR before $PATH in PhpExecutableFinder (nicolas-grekas) + * bug #26012 Exit as late as possible (greg0ire) + * bug #25893 [Console] Fix hasParameterOption / getParameterOption when used with multiple flags (greg-1-anderson) + * bug #25940 [Form] keep the context when validating forms (xabbuh) + * bug #25373 Use the PCRE_DOLLAR_ENDONLY modifier in route regexes (mpdude) + * bug #26010 [CssSelector] For AND operator, the left operand should have parentheses, not only right operand (Arnaud CHASSEUX) + * bug #25971 [Debug] Fix bad registration of exception handler, leading to mem leak (nicolas-grekas) + * bug #25962 [Routing] Fix trailing slash redirection for non-safe verbs (nicolas-grekas) + * bug #25948 [Form] Fixed empty data on expanded ChoiceType and FileType (HeahDude) + * bug #25972 support sapi_windows_vt100_support for php 7.2+ (jhdxr) + * bug #25744 [TwigBridge] Allow label translation to be safe (MatTheCat) + +* 2.7.41 (2018-01-29) + + * bug #25922 [HttpFoundation] Use the correct syntax for session gc based on Pdo driver (tanasecosminromeo) + * bug #25933 Disable CSP header on exception pages only in debug (ostrolucky) + * bug #25926 [Form] Fixed Button::setParent() when already submitted (HeahDude) + * bug #25927 [Form] Fixed submitting disabled buttons (HeahDude) + * bug #25891 [DependencyInjection] allow null values for root nodes in YAML configs (xabbuh) + * bug #25848 [Validator] add missing parent isset and add test (Simperfit) + * bug #25861 do not conflict with egulias/email-validator 2.0+ (xabbuh) + * bug #25851 [Validator] Conflict with egulias/email-validator 2.0 (emodric) + * bug #25837 [SecurityBundle] Don't register in memory users as services (chalasr) + * bug #25835 [HttpKernel] DebugHandlersListener should always replace the existing exception handler (nicolas-grekas) + * bug #25829 [Debug] Always decorate existing exception handlers to deal with fatal errors (nicolas-grekas) + * bug #25824 Fixing a bug where the dump() function depended on bundle ordering (weaverryan) + * bug #25789 Enableable ArrayNodeDefinition is disabled for empty configuration (kejwmen) + * bug #25816 Problem in phar see mergerequest #25579 (betzholz) + * bug #25781 [Form] Disallow transform dates beyond the year 9999 (curry684) + * bug #25812 Copied NO language files to the new NB locale (derrabus) + * bug #25801 [Router] Skip anonymous classes when loading annotated routes (pierredup) + * bug #25657 [Security] Fix fatal error on non string username (chalasr) + * bug #25799 Fixed Request::__toString ignoring cookies (Toflar) + * bug #25755 [Debug] prevent infinite loop with faulty exception handlers (nicolas-grekas) + * bug #25771 [Validator] 19 digits VISA card numbers are valid (xabbuh) + * bug #25751 [FrameworkBundle] Add the missing `enabled` session attribute (sroze) + * bug #25750 [HttpKernel] Turn bad hosts into 400 instead of 500 (nicolas-grekas) + * bug #25490 [Serializer] Fixed throwing exception with option JSON_PARTIAL_OUTPUT_ON_ERROR (diversantvlz) + * feature #25669 [Security] Fail gracefully if the security token cannot be unserialized from the session (thewilkybarkid) + +* 2.7.40 (2018-01-05) + + * bug #25532 [HttpKernel] Disable CSP header on exception pages (ostrolucky) + * bug #25491 [Routing] Use the default host even if context is empty (sroze) + * bug #25662 Dumper shouldn't use html format for phpdbg / cli-server (jhoff) + * bug #25529 [Validator] Fix access to root object when using composite constraint (ostrolucky) + * bug #25430 Fixes for Oracle in PdoSessionHandler (elislenio) + * bug #25599 Add application/ld+json format associated to json (vincentchalamon) + * bug #25407 [Console] Commands with an alias should not be recognized as ambiguous (Simperfit) + * bug #25521 [Console] fix a bug when you are passing a default value and passing -n would output the index (Simperfit) + * bug #25489 [FrameworkBundle] remove esi/ssi renderers if inactive (dmaicher) + * bug #25427 Preserve percent-encoding in URLs when performing redirects in the UrlMatcher (mpdude) + * bug #25480 [FrameworkBundle] add missing validation options to XSD file (xabbuh) + * bug #25487 [Console] Fix a bug when passing a letter that could be an alias (Simperfit) + * bug #25233 [TwigBridge][Form] Fix hidden currency element with Bootstrap 3 theme (julienfalque) + * bug #25408 [Debug] Fix catching fatal errors in case of nested error handlers (nicolas-grekas) + * bug #25330 [HttpFoundation] Support 0 bit netmask in IPv6 (`::/0`) (stephank) + * bug #25410 [HttpKernel] Fix logging of post-terminate errors/exceptions (nicolas-grekas) + * bug #25323 [ExpressionLanguage] throw an SyntaxError instead of an undefined index notice (Simperfit) + +* 2.7.39 (2017-12-04) + + * bug #25278 Fix for missing whitespace control modifier in form layout (kubawerlos) + * bug #25236 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25258 [link] Prevent warnings when running link with 2.7 (dunglas) + * bug #24750 [Validator] ExpressionValidator should use OBJECT_TO_STRING (Simperfit) + * bug #25182 [HttpFoundation] AutExpireFlashBag should not clear new flashes (Simperfit, sroze) + * 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 #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 #24956 Fix ambiguous pattern (weltling) + +* 2.7.38 (2017-11-16) + + * security #24995 Validate redirect targets using the session cookie domain (nicolas-grekas) + * security #24994 Prevent bundle readers from breaking out of paths (xabbuh) + * security #24993 Ensure that submitted data are uploaded files (xabbuh) + * security #24992 Namespace generated CSRF tokens depending of the current scheme (dunglas) + +* 2.7.37 (2017-11-13) + + * bug #24952 [HttpFoundation] Fix session-related BC break (nicolas-grekas, sroze) + * bug #24929 [Console] Fix traversable autocomplete values (ro0NL) + +* 2.7.36 (2017-11-10) + + * bug #24888 [FrameworkBundle] Specifically inject the debug dispatcher in the collector (ogizanagi) + * bug #24909 [Intl] Update ICU data to 60.1 (jakzal) + * bug #24906 [Bridge/ProxyManager] Remove direct reference to value holder property (nicolas-grekas) + * bug #24900 [Validator] Fix Costa Rica IBAN format (Bozhidar Hristov) + * bug #24904 [Validator] Add Belarus IBAN format (Bozhidar Hristov) + * bug #24531 [HttpFoundation] Fix forward-compat of NativeSessionStorage with PHP 7.2 (sroze) + * bug #24814 [Intl] Make intl-data tests pass and save language aliases again (jakzal) + * bug #24764 [HttpFoundation] add Early Hints to Reponse to fix test (Simperfit) + * bug #24605 [FrameworkBundle] Do not load property_access.xml if the component isn't installed (ogizanagi) + * bug #24606 [HttpFoundation] Fix FileBag issue with associative arrays (enumag) + * bug #24660 Escape trailing \ in QuestionHelper autocompletion (kamazee) + * bug #24644 [Security] Fixed auth provider authenticate() cannot return void (glye) + * bug #24626 streamed response should return $this (DQNEO) + * bug #24589 Username and password in basic auth are allowed to contain '.' (Richard Quadling) + * bug #24566 Fixed unsetting from loosely equal keys OrderedHashMap (maryo) + * bug #24570 [Debug] Fix same vendor detection in class loader (Jean-Beru) + * bug #24563 [Serializer] ObjectNormalizer: throw if PropertyAccess isn't installed (dunglas) + * bug #24579 pdo session fix (mxp100) + * bug #24536 [Security] Reject remember-me token if UserCheckerInterface::checkPostAuth() fails (kbond) + * bug #24519 [Validator] [Twig] added magic method __isset() to File Constraint class (loru88) + * bug #24532 [DI] Fix possible incorrect php-code when dumped strings contains newlines (Strate) + * bug #24502 [HttpFoundation] never match invalid IP addresses (xabbuh) + * bug #24460 [Form] fix parsing invalid floating point numbers (xabbuh) + * bug #24490 [HttpFoundation] Combine Cache-Control headers (c960657) + * bug #23711 Fix support for PHP 7.2 (Simperfit, nicolas-grekas) + * bug #24494 [HttpFoundation] Add missing session.lazy_write config option (nicolas-grekas) + * bug #24434 [Form] Use for=ID on radio/checkbox label. (Nyholm) + * bug #24455 [Console] Escape command usage (sroze) + +* 2.7.35 (2017-10-05) + + * bug #24448 [Session] fix MongoDb session handler to gc all expired sessions (Tobion) + * bug #24417 [Yaml] parse references on merge keys (xabbuh) + * bug #24421 [Config] Fix dumped files invalidation by OPCache (nicolas-grekas) + * bug #23980 Tests and fix for issue in array model data in EntityType field with multiple=true (stoccc) + * bug #22586 [Form] Fixed PercentToLocalizedStringTransformer to accept both comma and dot as decimal separator, if possible (aaa2000) + * bug #24157 [Intl] Fixed support of Locale::getFallback (lyrixx) + * bug #24198 [HttpFoundation] Fix file upload multiple with no files (enumag) + * bug #24036 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions and multiplications (Rubinum) + * bug #24367 PdoSessionHandler: fix advisory lock for pgsql (Tobion) + * bug #24243 HttpCache does not consider ESI resources in HEAD requests (mpdude) + * bug #24304 [FrameworkBundle] Fix Routing\DelegatingLoader (nicolas-grekas) + * bug #24219 [Console] Preserving line breaks between sentences according to the exception message (yceruto) + * bug #23722 [Form] Fixed GroupSequence with "constraints" option (HeahDude) + * bug #22321 [Filesystem] Fixed makePathRelative (ausi) + * bug #23473 [Filesystem] mirror - fix copying content with same name as source/target. (gitlost) + * bug #24162 [WebProfilerBundle] fixed TemplateManager when using Twig 2 without compat interfaces (fabpot) + * bug #24141 [DomCrawler] Fix conversion to int on GetPhpFiles (MaraBlaga) + * bug #23853 Filtering empty uuids in ORMQueryBuilderLoader. (mlazovla) + * bug #24101 [Security] Fix exception when use_referer option is true and referer is not set or empty (linniksa) + * bug #24105 [Filesystem] check permissions if dump target dir is missing (xabbuh) + * bug #24115 [FrameworkBundle] Get KERNEL_DIR through $_ENV too for KernelTestCase (yceruto) + * bug #24041 [ExpressionLanguage] throws an exception on calling uncallable method (fmata) + * bug #24096 Fix ArrayInput::toString() for VALUE_IS_ARRAY options/args (chalasr) + * bug #23730 Fixed the escaping of back slashes and << in console output (javiereguiluz) + +* 2.7.34 (2017-08-28) + + * bug #23989 [Debug] Remove false-positive check in DebugClassLoader (nicolas-grekas) + * bug #23982 [VarDumper] Strengthen dumped JS (nicolas-grekas) + * bug #23925 [Validator] Fix use of GroupSequenceProvider in child classes (linniksa) + * bug #23945 [Validator] Fix Greek translation (azhurb) + * bug #23909 [Console] Initialize lazily to render exceptions properly (nicolas-grekas) + * bug #23856 [DI] Fix dumping abstract with YamlDumper (nicolas-grekas) + * bug #23752 Ignore memcached missing key error on session destroy (jderusse) + * bug #23658 [HttpFoundation] Generate safe fallback filename for wrongly encoded filename (xelaris) + * bug #23783 Avoid infinite loops when profiler data is malformed (javiereguiluz) + * bug #23729 [Bridge\ProxyManager] Dont call __destruct() on non-instantiated services (nicolas-grekas) + +* 2.7.33 (2017-08-01) + + * bug #22244 [Console] Fix passing options with defaultCommand (Jakub Sacha) + * bug #23684 [Debug] Missing escape in debug output (c960657) + * bug #23662 [VarDumper] Adapt to php 7.2 changes (nicolas-grekas) + * bug #23649 [Form][TwigBridge] Don't render _method in form_rest() for a child form (fmarchalemisys) + * bug #23619 [Validator] Fix IbanValidator for ukrainian IBANs (paroe) + * bug #23238 [Security] ensure the 'route' index is set before attempting to use it (gsdevme) + * bug #23580 Fix login redirect when referer contains a query string (fabpot) + * bug #23574 [VarDumper] Move locale sniffing to dump() time (nicolas-grekas) + +* 2.7.32 (2017-07-17) + + * security #23507 [Security] validate empty passwords again (xabbuh) + * bug #23526 [HttpFoundation] Set meta refresh time to 0 in RedirectResponse content (jnvsor) + * bug #23468 [DI] Handle root namespace in service definitions (ro0NL) + * bug #23256 [Security] Fix authentication.failure event not dispatched on AccountStatusException (chalasr) + * bug #23461 Use rawurlencode() to transform the Cookie into a string (javiereguiluz) + * bug #23459 [TwigBundle] allow to configure custom formats in XML configs (xabbuh) + * bug #23261 Fixed absolute url generation for query strings and hash urls (alexander-schranz) + * bug #23398 [Filesystem] Dont copy perms when origin is remote (nicolas-grekas) + +* 2.7.31 (2017-07-05) + + * bug #23378 [FrameworkBundle] Do not remove files from assets dir (1ed) + +* 2.7.30 (2017-07-03) + + * bug #23341 [DoctrineBridge][Security][Validator] do not validate empty values (xabbuh) + * bug #23274 Display a better error design when the toolbar cannot be displayed (yceruto) + * bug #23333 [PropertyAccess] Fix TypeError discard (dunglas) + * bug #23345 [Console] fix description of INF default values (xabbuh) + * bug #23279 Don't call count on non countable object (pierredup) + * bug #23283 [TwigBundle] add back exception check (xabbuh) + * bug #23268 Show exception is checked twice in ExceptionController of twig (gmponos) + * bug #23266 Display a better error message when the toolbar cannot be displayed (javiereguiluz) + * bug #23271 [FrameworkBundle] allow SSI fragments configuration in XML files (xabbuh) + * bug #23254 [Form][TwigBridge] render hidden _method field in form_rest() (xabbuh) + * bug #23250 [Translation] return fallback locales whenever possible (xabbuh) + * bug #22732 [Security] fix switch user _exit without having current token (dmaicher) + * bug #22730 [FrameworkBundle] Sessions: configurable "use_strict_mode" option for NativeSessionStorage (MacDada) + * bug #23195 [FrameworkBundle] [Command] Clean bundle directory, fixes #23177 (NicolasPion) + * bug #23052 [TwigBundle] Add Content-Type header for exception response (rchoquet) + * bug #23199 Reset redirectCount when throwing exception (hvanoch) + * bug #23186 [TwigBundle] Move template.xml loading to a compiler pass (ogizanagi) + * bug #23130 Keep s-maxage when expiry and validation are used in combination (mpdude) + * bug #23129 Fix two edge cases in ResponseCacheStrategy (mpdude) + * feature #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #23057 [Translation][FrameworkBundle] Fix resource loading order inconsistency reported in #23034 (mpdude) + * bug #23092 [Filesystem] added workaround in Filesystem::rename for PHP bug (VolCh) + * bug #23128 [HttpFoundation] fix for Support for new 7.1 session options (vincentaubert) + * bug #23176 [VarDumper] fixes (nicolas-grekas) + * bug #23086 [FrameworkBundle] Fix perf issue in CacheClearCommand::warmup() (nicolas-grekas) + * bug #23098 Cache ipCheck (2.7) (gonzalovilaseca) + +* 2.7.29 (2017-06-07) + + * bug #23069 [SecurityBundle] Show unique Inherited roles in profile panel (yceruto) + * bug #23073 [TwigBridge] Fix namespaced classes (ogizanagi) + * bug #22936 [Form] Mix attr option between guessed options and user options (yceruto) + * bug #23024 [EventDispatcher] Fix ContainerAwareEventDispatcher::hasListeners(null) (nicolas-grekas) + * bug #22996 [Form] Fix \IntlDateFormatter timezone parameter usage to bypass PHP bug #66323 (romainneutron) + * bug #22994 Harden the debugging of Twig filters and functions (stof) + +* 2.7.28 (2017-05-29) + + * bug #22847 [Console] ChoiceQuestion must have choices (ro0NL) + * bug #22900 [FrameworkBundle][Console] Fix the override of a command registered by the kernel (aaa2000) + * bug #22910 [Filesystem] improve error handling in lock() (xabbuh) + * bug #22718 [Console] Fixed different behaviour of key and value user inputs in multiple choice question (borNfreee) + * bug #22901 Fix missing abstract key in XmlDumper (weaverryan) + * bug #22817 [PhpUnitBridge] optional error handler arguments (xabbuh) + * bug #22647 [VarDumper] Fix dumping of non-nested stubs (nicolas-grekas) + * bug #22584 [Security] Avoid unnecessary route lookup for empty logout path (ro0NL) + * bug #22690 [Console] Fix errors not rethrown even if not handled by console.error listeners (chalasr) + * bug #22669 [FrameworkBundle] AbstractConfigCommand: do not try registering bundles twice (ogizanagi) + * bug #22676 [FrameworkBundle] Adding the extension XML (flug) + +* 2.7.27 (2017-05-01) + + * bug #22528 [Asset] Starting slash should indicate no basePath wanted (weaverryan) + * bug #22526 [Asset] Preventing the base path or absolute URL from being prefixed incorrectly (weaverryan) + * bug #22435 [Console] Fix dispatching throwables from ConsoleEvents::COMMAND (nicolas-grekas) + * bug #22478 [Serializer] XmlEncoder: fix negative int and large numbers handling (dunglas) + * bug #22424 [Debug] Set exit status to 255 on error (nicolas-grekas) + * bug #22396 Prevent double registrations related to tag priorities (nicolas-grekas) + * bug #22352 [HttpFoundation] Add `use_strict_mode` in validOptions for session (sstok) + * bug #22351 [Yaml] don't keep internal state between parser runs (xabbuh) + * bug #22307 [Debug] Fix php notice (enumag) + * bug #22109 [Validator] check for empty host when calling checkdnsrr() (apetitpa) + * bug #22280 [DI] Fix the xml schema (GuilhemN) + * bug #22255 [Translation] avoid creating cache files for fallback locales. (aitboudad) + * bug #22292 Fixes #22264 - add support for Chrome headless (redthor) + +* 2.7.26 (2017-04-04) + + * bug #22229 [ExpressionLanguage] Provide the expression in syntax errors (k0pernikus, stof) + * bug #22240 [DI] Fix fatal error at ContainerBuilder::compile() if config is not installed (chalasr) + * bug #22140 [Form] Improve the exceptions when trying to get the data in a PRE_SET_DATA listener and the data has not already been set (fancyweb) + * bug #22217 [Console] Fix table cell styling (ro0NL) + * bug #22194 [Console] CommandTester: disable color support detection (julienfalque) + * bug #22188 [Console] Revised exception rendering (ro0NL) + * bug #22154 [WebProfilerBundle] Normalize whitespace in exceptions passed in headers (curry684) + * bug #22142 [Console] Escape exception messages in renderException (chalasr) + * bug #22172 Fix port usage in server:status command (alcaeus) + * bug #22164 [Bridge\Doctrine] Fix change breaking doctrine-bundle test suite (nicolas-grekas) + * bug #22133 [Filesystem] normalize paths before making them relative (xabbuh) + * bug #22138 [HttpFoundation][bugfix] $bags should always be initialized (MacDada) + * bug #21810 #21809 [SecurityBundle] bugfix: if security provider's name contains upper cases then container didn't compile (Antanas Arvasevicius) + * bug #19778 [Security] Fixed roles serialization on token from user object (eko) + * bug #22022 [Validator] fix URL validator to detect non supported chars according to RFC 3986 (e-moe) + * bug #21968 Fixed pathinfo calculation for requests starting with a question mark. (syzygymsu) + * bug #21846 [HttpFoundation] Fix Request::getHost() when having several hosts in X_FORWARDED_HOST (nicolas-grekas) + * bug #21208 [Validator] Add object handling of invalid constraints in Composite (SenseException) + * bug #22044 [Serializer] [XML] Ignore Process Instruction (jordscream) + * bug #22079 [HttpKernel] Fixed bug with purging of HTTPS URLs (ausi) + * bug #21523 #20411 fix Yaml parsing for very long quoted strings (RichardBradley) + * bug #22001 [Doctrine Bridge] fix priority for doctrine event listeners (dmaicher) + * bug #21981 [Console] Use proper line endings in BufferedOutput (julienfalque) + * bug #21957 [Form] Choice type int values (BC Fix) (mcfedr) + * bug #21923 [travis] Test with hhvm 3.18 (nicolas-grekas) + * bug #21823 dumpFile(), preserve existing file permissions (chs2) + * bug #21865 [Security] context listener: hardening user provider handling (xabbuh) + * bug #21883 [HttpKernel] fix Kernel name when stored in a directory starting with a number (fabpot) + +* 2.7.25 (2017-03-06) + + * bug #21671 [Serializer] Xml encoder throws exception for valid data (gr1ev0us) + * bug #21805 Provide less state in getRequestFormat (dawehner) + * bug #21832 [Routing] Ignore hidden directories when loading routes from annotations (jakzal) + * bug #21769 [Form] Improve rounding precision (foaly-nr1) + * bug #21267 [Form] Fix ChoiceType to ensure submitted data is not nested unnecessarily (issei-m) + * bug #21731 Fix emacs link (rubenrua) + * bug #21800 Fix issues reported by static analyze (romainneutron) + * bug #21798 Revert "bug #21791 [SecurityBundle] only pass relevant user provider (xabbuh)" (xabbuh) + * bug #21791 [SecurityBundle] only pass relevant user provider (xabbuh) + * bug #21756 [Yaml] Stop replacing NULLs when merging (gadelat) + * bug #21722 [ExpressionLanguage] Registering functions after calling evaluate(), compile() or parse() is not supported (maidmaid) + * bug #21679 [SecurityBundle] fix priority ordering of security voters (xabbuh) + * bug #21115 [Validator] do not guess getter method names (xabbuh) + * bug #21661 Fix Composer constraints (fabpot) + * bug #21582 [HttpCache] purge both http and https from http cache (dbu) + * bug #21637 [FrameworkBundle] remove translation data collector when not usable (xabbuh) + * bug #21634 [VarDumper] Added missing persistent stream cast (lyrixx) + * bug #21436 [DependencyInjection] check for circular refs caused by method calls (xabbuh) + * bug #21400 [Serializer] fix upper camel case conversion (see #21399) (markusu49) + * bug #21599 [Console][Table] fixed render when using multiple rowspans. (aitboudad) + * bug #21613 [Process] Permit empty suffix on Windows (Bilge) + * bug #21057 [DI] Auto register extension configuration classes as a resource (ro0NL) + * bug #21592 [Validator] property constraints can be added in child classes (angelk, xabbuh) + * bug #21458 [Config] Early return for DirectoryResource (robfrawley) + * bug #21562 [DoctrineBridge] make sure that null can be the invalid value (xabbuh) + +* 2.7.24 (2017-02-06) + + * bug #21063 [Form] Fixed DateType format option for single text widget (HeahDude) + * bug #21430 Casting TableCell value to string. (jaydiablo) + * bug #21359 [FrameworkBundle] fixed custom domain for translations in php templates (robinlehrmann) + * bug #21485 [Process] Non ASCII characters disappearing during the escapeshellarg (GuillaumeVerdon) + * bug #21462 [BrowserKit] ignore invalid cookies expires date format (xabbuh) + * bug #21438 [Console] Fix TableCell issues with decoration (ogizanagi) + * bug #21431 [DoctrineBridge] always check for all fields to be mapped (xabbuh) + * bug #21360 [PropertyAccess] Handle interfaces in the invalid argument exception (fancyweb) + * bug #21401 [Debug] Workaround "null" $context (nicolas-grekas) + * bug #21333 [HttpKernel] Fix ArgumentValueResolver for arguments default null (chalasr) + * bug #20871 [HttpKernel] Give higher priority to adding request formats (akeeman) + * bug #21285 [TwigBundle] do not lose already set method calls (xabbuh) + * bug #21279 #20411 fix Yaml parsing for very long quoted strings (RichardBradley) + +* 2.7.23 (2017-01-12) + + * bug #21218 [Form] DateTimeToLocalizedStringTransformer does not use timezone when using date only (magnetik) + * bug #21104 [FrameworkBundle] fix IPv6 address handling in server commands (xabbuh) + * bug #20793 [Validator] Fix caching of constraints derived from non-serializable parents (uwej711) + * bug #19586 [TwigBundle] Fix bug where namespaced paths don't take parent bundles in account (wesleylancel) + * bug #21237 [FrameworkBundle] Fix relative paths used as cache keys (nicolas-grekas) + * bug #21183 [Validator] respect groups when merging constraints (xabbuh) + * bug #21179 [TwigBundle] Fixing regression in TwigEngine exception handling (Bertalan Attila) + * bug #21220 [DI] Fix missing new line after private alias (ogizanagi) + * bug #21211 Classloader tmpname (lyrixx) + * bug #21205 [TwigBundle] fixed usage when Templating is not installed (fabpot) + * bug #21155 [Validator] Check cascasdedGroups for being countable (scaytrase) + * bug #21200 [Filesystem] Check that directory is writable after created it in dumpFile() (chalasr) + * bug #21113 [FrameworkBundle][HttpKernel] Fix resources loading for bundles with custom structure (chalasr) + * bug #21084 [Yaml] handle empty lines inside unindented collection (xabbuh) + * bug #20925 [HttpFoundation] Validate/cast cookie expire time (ro0NL) + * bug #21032 [SecurityBundle] Made collection of user provider unique when injecting them to the RemberMeService (lyrixx) + * bug #21078 [Console] Escape default value when dumping help (lyrixx) + * bug #21076 [Console] OS X Can't call cli_set_process_title php without superuser (ogizanagi) + * bug #20900 [Console] Descriptors should use Helper::strlen (ogizanagi) + * bug #21064 [Debug] Wrap call to ->log in a try catch block (lyrixx) + * bug #21010 [Debug] UndefinedMethodFatalErrorHandler - Handle anonymous classes (SpacePossum) + * bug #20859 Avoid warning in PHP 7.2 because of non-countable data (wouterj) + * bug #21053 [Validator] override property constraints in child class (xabbuh) + * bug #20970 [Console] Fix question formatting using SymfonyStyle::ask() (chalasr, ogizanagi) + * bug #20975 [Form] fix group sequence based validation (xabbuh) + * bug #20599 [WebProfilerBundle] Display multiple HTTP headers in WDT (ro0NL) + * bug #20799 [TwigBundle] do not try to register incomplete definitions (xabbuh) + * bug #20961 [Validator] phpize default option values (xabbuh) + * bug #20934 [FrameworkBundle] Fix PHP form templates on translatable attributes (ro0NL) + * bug #20957 [FrameworkBundle] test for the Validator component to be present (xabbuh) + * bug #20936 [DependencyInjection] Fix on-invalid attribute type in xsd (ogizanagi) + * bug #20931 [VarDumper] Fix dumping by-ref variadics (nicolas-grekas) + * bug #20734 [Security] AbstractVoter->supportsAttribute gives false positive if attribute is zero (0) (martynas-foodpanda) + * bug #14082 [config] Fix issue when key removed and left value only (zerustech) + +* 2.7.22 (2016-12-13) + + * bug #20714 [FrameworkBundle] Fix unresolved parameters from default configs in debug:config (chalasr) + * bug #20442 [FrameworkBundle] Bundle commands are not available via find() (julienfalque) + * bug #20840 [WebProfilerBundle] add dependency on Twig (xabbuh) + * bug #20828 [Validator] Fix init of YamlFileLoader::$classes for empty files (nicolas-grekas) + * bug #20539 Cast result to int before adding to it (alcaeus) + * bug #20831 [Twig] Fix deprecations with Twig 1.29 (nicolas-grekas) + * bug #20767 [Cache] Fix dumping SplDoublyLinkedList iter mode (nicolas-grekas) + * bug #20736 [Console] fixed PHP7 Errors when not using Dispatcher (keradus) + * bug #20755 [HttpKernel] Regression test for missing controller arguments (iltar) + * bug #20418 [Form][DX] FileType "multiple" fixes (yceruto) + * bug #19902 [DependencyInjection] PhpDumper.php: hasReference() shouldn't search references in lazy service. (antanas-arvasevicius) + * bug #20704 [Console] Fix wrong handling of multiline arg/opt descriptions (ogizanagi) + * bug #20712 [TwigBundle] Fix twig loader registered twice (ogizanagi) + * bug #20671 [Config] ConfigCache::isFresh() should return false when unserialize() fails (nicolas-grekas) + * bug #20676 [ClassLoader] Use only forward slashes in generated class map (nicolas-grekas) + * bug #20664 [Validator] ensure the proper context for nested validations (xabbuh) + * bug #20661 bug #20653 [WebProfilerBundle] Profiler includes ghost panels (jzawadzki) + * bug #20374 [FrameworkBundle] Improve performance of ControllerNameParser (enumag) + * bug #20474 [Routing] Fail properly when a route parameter name cannot be used as a PCRE subpattern name (fancyweb) + * bug #20566 [DI] Initialize properties before method calls (ro0NL) + * bug #20609 [DI] Fixed custom services definition BC break introduced in ec7e70fb… (kiler129) + * bug #20598 [DI] Aliases should preserve the aliased invalid behavior (nicolas-grekas) + * bug #20602 [HttpKernel] Revert BC breaking change of Request::isMethodSafe() (nicolas-grekas) + * bug #20499 [Doctrine][Form] support large integers (xabbuh) + * bug #20576 [Process] Do feat test before enabling TTY mode (nicolas-grekas) + +* 2.7.21 (2016-11-21) + + * bug #20543 [DI] Fix error when trying to resolve a DefinitionDecorator (nicolas-grekas) + * bug #20484 bumped min version of Twig to 1.28 (fabpot) + * bug #20519 [Debug] Remove GLOBALS from exception context to avoid endless recursion (Seldaek) + * bug #20455 [ClassLoader] Fix ClassCollectionLoader inlining with __halt_compiler (giosh94mhz) + * bug #20307 [Form] Fix Date\TimeType marked as invalid on request with single_text and zero seconds (LuisDeimos) + * bug #20466 [Translation] fixed nested fallback catalogue using multiple locales. (aitboudad) + * bug #20465 [#18637][TranslationDebug] workaround for getFallbackLocales. (aitboudad) + * bug #20440 [TwigBridge][TwigBundle][HttpKernel] prefer getSourceContext() over getSource() (xabbuh) + * bug #20422 [Translation][fallback] add missing resources in parent catalogues. (aitboudad) + * bug #20378 [Form] Fixed show float values as choice value in ChoiceType (yceruto) + * bug #20375 [HttpFoundation][Session] Fix memcache session handler (klandaika) + * bug #20377 [Console] Fix infinite loop on missing input (chalasr) + * bug #20342 [Form] Fix UrlType transforms valid protocols (ogizanagi) + * bug #20292 Enhance GAE compat by removing some realpath() (nicolas-grekas) + * bug #20321 Compatibility with Twig 1.27 (xkobal) + +* 2.7.20 (2016-10-27) + + * bug #20289 Fix edge case with StreamedResponse where headers are sent twice (Nicofuma) + * bug #20278 [DependencyInjection] merge tags instead of completely replacing them (xabbuh) + * bug #20271 Changes related to Twig 1.27 (fabpot) + * bug #20252 Trim constant values in XmlFileLoader (lstrojny) + * bug #20253 [TwigBridge] Use non-deprecated Twig_Node::getTemplateLine() (fabpot) + * bug #20235 [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions (klausi, nicolas-grekas) + * bug #20224 [Twig] removed deprecations added in Twig 1.27 (fabpot) + * bug #19478 fixed Filesystem:makePathRelative and added 2 more testcases (muhammedeminakbulut) + * bug #20218 [HttpFoundation] no 304 response if method is not cacheable (xabbuh) + * bug #20207 [DependencyInjection] move tags from decorated to decorating service (xabbuh) + * bug #20205 [HttpCache] fix: do not cache OPTIONS request (dmaicher) + * bug #20146 [Validator] Prevent infinite loop in PropertyMetadata (wesleylancel) + * bug #20184 [FrameworkBundle] Convert null prefix to an empty string in translation:update (chalasr) + * bug #19725 [Security] $attributes can be anything, but RoleVoter assumes strings (Jonatan Männchen) + * bug #20127 [HttpFoundation] JSONP callback validation (ro0NL) + * bug #20163 add missing use statement (xabbuh) + * bug #19961 [Console] Escape question text and default value in SymfonyStyle::ask() (chalasr) + * bug #20141 [Console] Fix validation of empty values using SymfonyQuestionHelper::ask() (chalasr) + * bug #20147 [FrameworkBundle] Alter container class instead of kernel name in cache:clear command (nicolas-grekas) + +* 2.7.19 (2016-10-03) + + * bug #20102 [Validator] Url validator not validating hosts ending in a number (gwkunze) + * bug #20132 Use "more entropy" option for uniqid() (javiereguiluz) + * bug #20122 [Validator] Reset constraint options (ro0NL) + * bug #20116 fixed AddConstraintValidatorsPass config (fabpot) + * bug #20078 Fix #19943 Make sure to process each interface metadata only once (lemoinem) + * bug #20080 [Form] compound forms without children should be considered rendered implicitly (backbone87) + * bug #20086 [VarDumper] Fix PHP 7.1 compat (nicolas-grekas) + * bug #20077 [Process] silent file operation to avoid open basedir issues (xabbuh) + * bug #20079 fixed Twig support for 1.26 and 2.0 (fabpot) + * bug #19951 [Finder] Trim trailing directory slash in ExcludeDirectoryFilterIterator (ro0NL) + * bug #20010 [DX] Fixed regression when exception message swallowed when logging it. (Koc) + * bug #19983 [TwigBridge] removed Twig null nodes (deprecated as of Twig 1.25) (fabpot) + * bug #19946 [Console] Fix parsing optionnal options with empty value in argv (chalasr) + * bug #19636 [Finder] no PHP warning on empty directory iteration (ggottwald) + * bug #19923 [bugfix] [Console] Set `Input::$interactive` to `false` when command is executed with `--quiet` as verbosity level (phansys) + * bug #19811 Fixed the nullable support for php 7.1 and below (2.7, 2.8, 3.0) (iltar) + * bug #19904 [Form] Fixed collapsed ChoiceType options attributes (HeahDude) + * bug #19908 [Config] Handle open_basedir restrictions in FileLocator (Nicofuma) + * bug #19922 [Yaml][TwigBridge] Use JSON_UNESCAPED_SLASHES for lint commands output (chalasr) + * bug #19928 [Validator] Update IpValidatorTest data set with a valid reserved IP (jakzal) + * bug #19813 [Console] fixed PHP7 Errors are now handled and converted to Exceptions (fonsecas72) + * bug #19879 [Form] Incorrect timezone with DateTimeLocalizedStringTransformer (mbeccati) + +* 2.7.18 (2016-09-07) + + * bug #19859 [ClassLoader] Fix ClassCollectionLoader inlining with declare(strict_types=1) (nicolas-grekas) + * bug #19780 [FrameworkBundle] Incorrect line break in exception message (500 debug page) (pedroresende) + * bug #19595 [form] lazy trans `post_max_size_message`. (aitboudad) + * bug #19870 [DI] Fix setting synthetic services on ContainerBuilder (nicolas-grekas) + * bug #19848 Revert "minor #19689 [DI] Cleanup array_key_exists (ro0NL)" (nicolas-grekas) + * bug #19842 [FrameworkBundle] Check for class existence before is_subclass_of (chalasr) + * bug #19827 [BrowserKit] Fix cookie expiration on 32 bit systems (jameshalsall) + +* 2.7.17 (2016-09-02) + + * bug #19794 [VarDumper] Various minor fixes & cleanups (nicolas-grekas) + * bug #19751 Fixes the calendar in constructor to handle null (wakqasahmed) + * bug #19388 [Validator][GroupSequence] fixed GroupSequence validation ignores PropetyMetadata of parent classes (Sandro Hopf) + * bug #19601 [FrameworkBundle] Added friendly exception when constraint validator class does not exist (yceruto) + * bug #19580 [Validator] fixed duplicate constraints with parent class interfaces (dmaicher) + * bug #19647 [Debug] Swap dumper services at bootstrap (lyrixx) + * bug #19685 [DI] Include dynamic services in alternatives (ro0NL) + * bug #19702 [Debug][HttpKernel][VarDumper] Prepare for committed 7.2 changes (aka "small-bc-breaks") (nicolas-grekas) + * bug #19704 [DependencyInjection] PhpDumper::isFrozen inconsistency (allflame) + * bug #19666 Verify explicitly that the request IP is a valid IPv4 address (nesk) + * bug #19660 Disable CLI color for Windows 10 greater than 10.0.10586 (mlocati) + * bug #19663 Exception details break the layout (Dionysis Arvanitis) + * bug #19651 [HttpKernel] Fix HttpCache validation HTTP method (tgalopin) + * bug #19623 [VarDumper] Fix dumping continuations (nicolas-grekas) + * bug #19549 [HttpFoundation] fixed Request::getContent() reusage bug (1ma) + * bug #19373 [Form] Skip CSRF validation on form when POST max size is exceeded (jameshalsall) + * bug #19541 Fix #19531 [Form] DateType fails parsing when midnight is not a valid time (mbeccati) + * bug #19579 [Process] Strengthen Windows pipe files opening (again...) (nicolas-grekas) + * bug #19564 Added class existence check if is_subclass_of() fails in compiler passes (SCIF) + * bug #19522 [SwiftMailerBridge] Fix flawed deprecation message (chalasr) + * bug #19510 [Process] Fix double-fread() when reading unix pipes (nicolas-grekas) + * bug #19508 [Process] Fix AbstractPipes::write() for a situation seen on HHVM (at least) (nicolas-grekas) + +* 2.7.16 (2016-07-30) + + * bug #19470 undefined offset fix (#19406) (ReenExe) + * bug #19300 [HttpKernel] Use flock() for HttpCache's lock files (mpdude) + * bug #19428 [Process] Fix write access check for pipes on Windows (nicolas-grekas) + * bug #19397 [HttpFoundation] HttpCache refresh stale responses containing an ETag (maennchen) + * bug #19426 [Form] Fix the money form type render with Bootstrap3 (Th3Mouk) + * bug #19425 [BrowserKit] Uppercase the "GET" method in redirects (jakzal) + * bug #19384 Fix PHP 7.1 related failures (nicolas-grekas) + * bug #19379 [VarDumper] Fix for PHP 7.1 (nicolas-grekas) + * bug #19369 Fix the DBAL session handler version check for Postgresql (stof) + * bug #19368 [VarDumper] Fix dumping jsons casted as arrays (nicolas-grekas) + * bug #19334 [Security] Fix the retrieval of the last username when using forwarding (stof) + * bug #19321 [HttpFoundation] Add OPTIONS and TRACE to the list of safe methods (dunglas) + * bug #19317 [BrowserKit] Update Client::getAbsoluteUri() for query string only URIs (georaldc) + * bug #19298 [ClassLoader] Fix declared classes being computed when not needed (nicolas-grekas) + * bug #19316 [Validator] Added additional MasterCard range to the CardSchemeValidator (Dennis Væversted) + * bug #19290 [HttpKernel] fixed internal subrequests having an if-modified-since-header (MalteWunsch) + * bug #19306 [Form] fixed bug - name in ButtonBuilder (cheprasov) + * bug #19267 [Validator] UuidValidator must accept a Uuid constraint. (hhamon) + * bug #19186 Fix for #19183 to add support for new PHP MongoDB extension in sessions. (omanizer) + +* 2.7.15 (2016-06-30) + + * bug #19217 [HttpKernel] Inline ValidateRequestListener logic into HttpKernel (nicolas-grekas) + * bug #18688 [HttpFoundation] Warning when request has both Forwarded and X-Forwarded-For (magnusnordlander) + * bug #19173 [Console] Decouple SymfonyStyle from TableCell (ro0NL) + * bug #17822 [WIP] [Form] fix `empty_data` option in expanded `ChoiceType` (HeahDude) + * bug #19134 Distinguish between first and subsequent progress bar displays (rquadling) + * bug #19061 [FORM] fix post_max_size_message translation (alt. 2) (David Badura) + * bug #19100 [Console] Fixed SymfonyQuestionHelper multi-choice with defaults (sstok) + * bug #18924 [DoctrineBridge] Don't use object IDs in DoctrineChoiceLoader when passing a value closure (webmozart) + * bug #19138 [DomCrawler] No more exception on field name with strange format (guiled, fabpot) + * bug #18935 [Form] Consider a violation even if the form is not submitted (egeloen) + * bug #19127 [Form] Add exception to FormRenderer about non-unique block names (enumag) + * bug #19118 [Process] Fix pipes cleaning on Windows (nicolas-grekas) + * bug #19128 Avoid phpunit 5.4 warnings on getMock (2.7+) (iltar) + * bug #19114 [HttpKernel] Dont close the reponse stream in debug (nicolas-grekas) + * bug #19101 [Session] fix PDO transaction aborted under PostgreSQL (Tobion) + * bug #18501 [HttpFoundation] changed MERGE queries (hjkl) + * bug #19062 [HttpFoundation] Fix UPSERT for PgSql >= 9.5 (nicolas-grekas) + * bug #18548 [Form] minor fixes in DateTime transformers (HeahDude) + * bug #18732 [PropertyAccess][DX] Enhance exception that say that some methods are missing if they don't (nykopol) + * bug #19048 [HttpFoundation] Use UPSERT for sessions stored in PgSql >= 9.5 (nicolas-grekas) + * bug #19038 Fix feature detection for IE (Alsciende) + * bug #18915 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #19020 [Form] Fixed collapsed choice attributes (HeahDude) + * bug #19028 [Yaml] properly count skipped comment lines (xabbuh) + * bug #17733 [Yaml] Fix wrong line number when comments are inserted in the middle of a block. (paradajozsef) + * bug #18911 Fixed singular of committee (peterrehm) + * bug #18971 Do not inject web debug toolbar on attachments (peterrehm) + +* 2.7.14 (2016-06-06) + + * bug #18908 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #18893 [DependencyInjection] Skip deep reference check for 'service_container' (RobertMe) + * bug #18812 Catch \Throwable (fprochazka) + * bug #18821 [Form] Removed UTC specification with timestamp (francisbesset) + * bug #18861 Fix for #18843 (inso) + * bug #18907 [Routing] Fix the annotation loader taking a class constant as a beginning of a class name (jakzal, nicolas-grekas) + * bug #18879 [Console] SymfonyStyle: Align multi-line/very-long-line blocks (chalasr) + * bug #18864 [Console][DX] Fixed ambiguous error message when using a duplicate option shortcut (peterrehm) + * bug #18883 Fix js comment in profiler (linnaea) + * bug #18844 [Yaml] fix exception contexts (xabbuh) + * bug #18840 [Yaml] properly handle unindented collections (xabbuh) + * bug #18813 Catch \Throwable (fprochazka) + * bug #18839 People - person singularization (Keeo) + * bug #18828 [Yaml] chomp newlines only at the end of YAML documents (xabbuh) + * bug #18814 Fixed server status command when port has been omitted (peterrehm) + * bug #18799 Use levenshtein level for better Bundle matching (j0k3r) + * bug #18413 [WebProfilerBundle] Fix CORS ajax security issues (romainneutron) + * bug #18507 [BUG] Delete class 'control-group' in bootstrap 3 (Philippe Degeeter) + * bug #18747 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + * bug #18635 [Console] Prevent fatal error when calling Command::getHelper without helperSet (chalasr) + * bug #18686 [console][table] adjust width of colspanned cell. (aitboudad) + * bug #18761 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + * bug #18737 [Debug] Fix fatal error handlers on PHP 7 (nicolas-grekas) + +* 2.7.13 (2016-05-09) + + * security #18733 limited the maximum length of a submitted username (fabpot) + * bug #18730 [FrameworkBundle] prevent calling get() for service_container service (xabbuh) + * bug #18709 [DependencyInjection] top-level anonymous services must be public (xabbuh) + * bug #18692 add @Event annotation for KernelEvents (Haehnchen) + * bug #18246 [DependencyInjection] fix ambiguous services schema (backbone87) + +* 2.7.12 (2016-04-29) + + * bug #18180 [Form] fixed BC break with pre selection of choices with `ChoiceType` and its children (HeahDude) + * bug #18562 [WebProfilerBunde] Give an absolute url in case the request occured from another domain (romainneutron) + * bug #18603 [PropertyAccess] ->getValue() should be read-only (nicolas-grekas) + * bug #18593 [VarDumper] Fix dumping type hints for non-existing parent classes (nicolas-grekas) + * bug #18581 [Console] [TableHelper] make it work with SymfonyStyle. (aitboudad) + * bug #18280 [Routing] add query param if value is different from default (Tobion) + * bug #18496 [Console] use ANSI escape sequences in ProgressBar overwrite method (alekitto) + * bug #18491 [DependencyInjection] anonymous services are always private (xabbuh) + * bug #18515 [Filesystem] Better error handling in remove() (nicolas-grekas) + * bug #18449 [PropertyAccess] Fix regression (nicolas-grekas) + * bug #18429 [Console] Correct time formatting. (camporter) + * bug #18467 [DependencyInjection] Resolve aliases before removing abstract services + add tests (nicolas-grekas) + * bug #18460 [DomCrawler] Fix select option with empty value (Matt Wells) + * bug #18425 [Security] Fixed SwitchUserListener when exiting an impersonation with AnonymousToken (lyrixx) + * bug #18317 [Form] fix "prototype" not required when parent form is not required (HeahDude) + * bug #18439 [Logging] Add support for Firefox (43+) in ChromePhpHandler (arjenm) + * bug #18385 Detect CLI color support for Windows 10 build 10586 (mlocati) + * bug #18426 [EventDispatcher] Try first if the event is Stopped (lyrixx) + * bug #18394 [FrameworkBundle] Return the invokable service if its name is the class name (dunglas) + * bug #18265 Optimize ReplaceAliasByActualDefinitionPass (ajb-in) + * bug #18349 [Process] Fix stream_select priority when writing to stdin (nicolas-grekas) + * bug #18358 [Form] NumberToLocalizedStringTransformer should return floats when possible (nicolas-grekas) + * bug #17926 [DependencyInjection] Enable alias for service_container (hason) + * bug #18352 [Debug] Fix case sensitivity checks (nicolas-grekas) + * bug #18336 [Debug] Fix handling of php7 throwables (nicolas-grekas) + * bug #18354 [FrameworkBundle][TwigBridge] fix high deps tests (xabbuh) + * bug #18312 [ClassLoader] Fix storing not-found classes in APC cache (nicolas-grekas) + * bug #18298 [Validator] do not treat payload as callback (xabbuh) + +* 2.7.11 (2016-03-25) + + * bug #18255 [HttpFoundation] Fix support of custom mime types with parameters (Ener-Getick) + * bug #18272 [Bridge\PhpUnit] Workaround old phpunit bug, no colors in weak mode, add tests (nicolas-grekas) + * bug #18259 [PropertyAccess] Backport fixes from 2.7 (nicolas-grekas) + * bug #18261 [PropertyAccess] Fix isPropertyWritable not using the reflection cache (nicolas-grekas) + * bug #18224 [PropertyAccess] Remove most ref mismatches to improve perf (nicolas-grekas) + * bug #18210 [PropertyAccess] Throw an UnexpectedTypeException when the type do not match (dunglas, nicolas-grekas) + * bug #18216 [Intl] Fix invalid numeric literal on PHP 7 (nicolas-grekas) + * bug #18147 [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols (natechicago) + * bug #18023 [Process] getIncrementalOutput should work without calling getOutput (romainneutron) + * bug #18175 [Translation] Add support for fuzzy tags in PoFileLoader (nud) + * bug #18179 [Form] Fix NumberToLocalizedStringTransformer::reverseTransform with big integers (ovrflo, nicolas-grekas) + * bug #18164 [HttpKernel] set s-maxage only if all responses are cacheable (xabbuh) + * bug #18150 [Process] Wait a bit less on Windows (nicolas-grekas) + * bug #18130 [Debug] Replaced logic for detecting filesystem case sensitivity (Dan Blows) + * bug #18080 [HttpFoundation] Set the Content-Range header if the requested Range is unsatisfied (jakzal) + * bug #18084 [HttpFoundation] Avoid warnings when checking malicious IPs (jakzal) + * bug #18066 [Process] Fix pipes handling (nicolas-grekas) + * bug #18078 [Console] Fix an autocompletion question helper issue with non-sequentially indexed choices (jakzal) + * bug #18048 [HttpKernel] Fix mem usage when stripping the prod container (nicolas-grekas) + * bug #18065 [Finder] Partially revert #17134 to fix a regression (jakzal) + * bug #18018 [HttpFoundation] exception when registering bags for started sessions (xabbuh) + * bug #18054 [Filesystem] Fix false positive in ->remove() (nicolas-grekas) + * bug #18049 [Validator] Fix the locale validator so it treats a locale alias as a valid locale (jakzal) + * bug #18019 [Intl] Update ICU to version 55 (jakzal) + * bug #18015 [Process] Fix memory issue when using large input streams (romainneutron) + * bug #16656 [HttpFoundation] automatically generate safe fallback filename (xabbuh) + * bug #15794 [Console] default to stderr in the console helpers (alcohol) + * bug #17984 Allow to normalize \Traversable when serializing xml (Ener-Getick) + * bug #17434 Improved the error message when a template is not found (rvanginneken, javiereguiluz) + * bug #17687 Improved the error message when using "@" in a decorated service (javiereguiluz) + * bug #17744 Improve error reporting in router panel of web profiler (javiereguiluz) + * bug #17894 [FrameworkBundle] Fix a regression in handling absolute template paths (jakzal) + * bug #17990 [DoctrineBridge][Form] Fix performance regression in EntityType (kimlai) + * bug #17595 [HttpKernel] Remove _path from query parameters when fragment is a subrequest (cmenning) + * bug #17986 [DomCrawler] Dont use LIBXML_PARSEHUGE by default (nicolas-grekas) + * bug #17668 add 'guid' to list of exception to filter out (garak) + * bug #17615 Ensure backend slashes for symlinks on Windows systems (cpsitgmbh) + * bug #17626 Try to delete broken symlinks (IchHabRecht) + * bug #17978 [Yaml] ensure dump indentation to be greather than zero (xabbuh) + * bug #16886 [Form] [ChoiceType] Prefer placeholder to empty_value (boite) + * bug #17976 [WebProfilerBundle] fix debug toolbar rendering by removing inadvertently added links (craue) + * bug #17971 Variadic controller params (NiR-, fabpot) + * bug #17568 Improved Bootstrap form theme for hidden fields (javiereguiluz) + * bug #17925 [Bridge] The WebProcessor now forwards the client IP (magnetik) + +* 2.7.10 (2016-02-28) + + * bug #17947 Fix - #17676 (backport #17919 to 2.3) (Ocramius) + * bug #17942 Fix bug when using an private aliased factory service (WouterJ) + * bug #17798 [Form] Fix BC break by allowing 'choice_label' option to be 'false' in ChoiceType (HeahDude) + * bug #17542 ChoiceFormField of type "select" could be "disabled" (bouland) + * bug #17602 [HttpFoundation] Fix BinaryFileResponse incorrect behavior with if-range header (bburnichon) + * bug #17760 [Form] fix choice value "false" in ChoiceType (HeahDude) + * bug #17914 [Console] Fix escaping of trailing backslashes (nicolas-grekas) + * bug #17074 Fix constraint validator alias being required (Triiistan) + * bug #17866 [DependencyInjection] replace alias in factories (xabbuh) + * bug #17867 [DependencyInjection] replace alias in factory services (xabbuh) + * bug #17569 [FrameworkBundle] read commands from bundles when accessing list (havvg) + * bug #16987 [FileSystem] Windows fix (flip111) + * bug #17787 [Form] Fix choice placeholder edge cases (Tobion) + * bug #17835 [Yaml] fix default timezone to be UTC (xabbuh) + * bug #17823 [DependencyInjection] fix dumped YAML string (xabbuh) + * bug #17818 [Console] InvalidArgumentException is thrown under wrong condition (robinkanters) + * bug #17819 [HttpKernel] Prevent a fatal error when DebugHandlersListener is used with a kernel with no terminateWithException() method (jakzal) + * bug #17814 [DependencyInjection] fix dumped YAML snytax (xabbuh) + * bug #17099 [Form] Fixed violation mapping if multiple forms are using the same (or part of the same) property path (alekitto) + * bug #17694 [DoctrineBridge] [Form] fix choice_value in EntityType (HeahDude) + * bug #17719 [DependencyInjection] fixed exceptions thrown by get method of ContainerBuilder (lukaszmakuch) + * bug #17742 [DependencyInjection] Fix #16461 Container::set() replace aliases (mnapoli) + * bug #17745 Added more exceptions to singularify method (javiereguiluz) + * bug #17691 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17766 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17757 [HttpFoundation] BinaryFileResponse sendContent return as parent. (2.3) (SpacePossum) + * bug #17702 [TwigBridge] forward compatibility with Yaml 3.1 (xabbuh) + * bug #17672 [DependencyInjection][Routing] add files used in FileResource objects (xabbuh) + * bug #17600 Fixed the Bootstrap form theme for inlined checkbox/radio (javiereguiluz) + * bug #17596 [Translation] Add resources from fallback locale to parent catalogue (c960657) + * bug #17605 [FrameworkBundle] remove default null value for asset version (xabbuh) + * bug #17606 [DependencyInjection] pass triggerDeprecationError arg to parent class (xabbuh) + * bug #16956 [DependencyInjection] XmlFileLoader: enforce tags to have a name (xabbuh) + * bug #16265 [BrowserKit] Corrected HTTP_HOST logic (Naktibalda) + * bug #17554 [DependencyInjection] resolve aliases in factories (xabbuh) + * bug #17555 [DependencyInjection] resolve aliases in factory services (xabbuh) + * bug #17511 [Form] ArrayChoiceList can now deal with a null in choices (issei-m) + * bug #17430 [Serializer] Ensure that groups are strings (dunglas) + * bug #15272 [FrameworkBundle] Fix template location for PHP templates (jakzal) + * bug #11232 [Routing] Fixes fatal errors with object resources in AnnotationDirectoryLoader::supports (Tischoi) + * bug #17526 Escape the delimiter in Glob::toRegex (javiereguiluz) + * bug #17527 fixed undefined variable (fabpot) + * bug #15706 [framework-bundle] Added support for the `0.0.0.0/0` trusted proxy (zerkms) + * bug #16274 [HttpKernel] Lookup the response even if the lock was released after two second wait (jakzal) + * bug #17355 [DoctrineBridge][Validator] >= 2.3 Pass association instead of ID as argument (xavismeh) + * bug #17454 Allow absolute URLs to be displayed in the debug toolbar (javiereguiluz) + * bug #16736 [Request] Ignore invalid IP addresses sent by proxies (GromNaN) + * bug #17486 [FrameworkBundle] Throw for missing container extensions (kix) + * bug #16873 Able to load big xml files with DomCrawler (zorn-v) + * bug #16897 [Form] Fix constraints could be null if not set (DZunke) + * bug #16912 [Translation][Writer] avoid calling setBackup if the dumper is not FileDumper (aitboudad) + * bug #17505 sort bundles in config:dump-reference command (xabbuh) + * bug #17514 [Asset] Add defaultNull to version configuration (ewgRa) + * bug #16511 [Asset] Ability to set empty version strategy in packages (ewgRa) + * bug #17503 [Asset] CLI: use request context to generate absolute URLs (xabbuh) + * bug #17478 [HttpFoundation] Do not overwrite the Authorization header if it is already set (jakzal) + * bug #17461 [Yaml] tag for dumped PHP objects must be a local one (xabbuh) + * bug #17456 [DX] Remove default match from AbstractConfigCommand::findExtension (kix) + * bug #17424 [Process] Update in 2.7 for stream-based output storage (romainneutron) + * bug #17423 [Process] Use stream based storage to avoid memory issues (romainneutron) + * bug #17406 [Form] ChoiceType: Fix a notice when 'choices' normalizer is replaced (paradajozsef) + * bug #17433 [FrameworkBundle] Don't log twice with the error handler (nicolas-grekas) + * bug #17418 Fixed Bootstrap form theme form "reset" buttons (javiereguiluz) + * bug #17404 fix merge 2.3 into 2.7 for SecureRandom dependency (Tobion) + * bug #17373 [SecurityBundle] fix SecureRandom service constructor args (Tobion) + * bug #17380 [TwigBridge] Use label_format option for checkbox and radio labels (enumag) + * bug #17377 Fix performance (PHP5) and memory (PHP7) issues when using token_get_all (nicolas-grekas, peteward) + * bug #17389 [Routing] Fixed correct class name in thrown exception (fixes #17388) (robinvdvleuten) + * bug #17358 [ClassLoader] Use symfony/polyfill-apcu (nicolas-grekas) + * bug #17370 [HttpFoundation][Cookie] Cookie DateTimeInterface fix (wildewouter) + +* 2.7.9 (2016-01-14) + + * security #17359 do not ship with a custom rng implementation (xabbuh, fabpot) + * bug #17314 Fix max width for multibyte keys in choice question (mheki) + * bug #17326 [Console] Display console application name even when no version set (polc) + * bug #17328 [Serializer] Allow to use proxies in object_to_populate (dunglas) + * bug #17347 Workaround https://bugs.php.net/63206 (nicolas-grekas) + * bug #17140 [Serializer] Remove normalizer cache in Serializer class (jvasseur) + * bug #17307 [FrameworkBundle] Fix paths with % in it (like urlencoded) (scaytrase) + * bug #17078 [Bridge] [Doctrine] [Validator] Added support \IteratorAggregate for UniqueEntityValidator (Disparity) + * bug #17298 [FrameworkBundle] Use proper class to fetch $versionStrategy property (dosten) + * bug #17287 [HttpKernel] Forcing string comparison on query parameters sort in UriSigner (Tim van Densen) + * bug #17279 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17278 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17275 [PhpUnitBridge] Re-enable the garbage collector (nicolas-grekas) + * bug #17276 [Process] Fix potential race condition (nicolas-grekas) + * bug #17183 [FrameworkBundle] Set the kernel.name properly after a cache warmup (jakzal) + * bug #17159 [Yaml] recognize when a block scalar is left (xabbuh) + * bug #17195 bug #14246 [Filesystem] dumpFile() non atomic (Hidde Boomsma) + * feature #16747 [Form] Improved performance of ChoiceType and its subtypes (webmozart) + * bug #17177 [Process] Fix potential race condition leading to transient tests (nicolas-grekas) + * bug #17163 [Form] fix Catchable Fatal Error if choices is not an array (Gladhon, nicolas-grekas) + * bug #17119 [Form] improve deprecation message for "empty_value" and "choice_list" options. (hhamon) + +* 2.7.8 (2015-12-26) + + * bug #16864 [Yaml] fix indented line handling in folded blocks (xabbuh) + * bug #17052 Fixed flatten exception recursion with errors (GrahamCampbell) + * bug #16826 Embedded identifier support (mihai-stancu) + * bug #17079 Also transform inline mappings to objects (WouterJ) + * bug #17129 [Config] Fix array sort on normalization in edge case (romainneutron) + * bug #17094 [Process] More robustness and deterministic tests (nicolas-grekas) + * bug #17112 [PropertyAccess] Reorder elements array after PropertyPathBuilder::replace (alekitto) + * bug #16797 [Filesystem] Recursively widen non-executable directories (Slamdunk) + * bug #17040 [Console] Avoid extra blank lines when rendering exceptions (ogizanagi) + * bug #17055 [Security] Verify if a password encoded with bcrypt is no longer than 72 characters (jakzal) + * bug #16959 [Form] fix #15544 when a collection type attribute "required" is false, "prototype" should too (HeahDude) + * bug #16860 [Yaml] do not remove "comments" in scalar blocks (xabbuh) + * bug #17002 [Console][Table] fixed render row that contains multiple cells. (aitboudad) + * bug #16971 [HttpFoundation] Added the ability of using BinaryFileResponse with stream wrappers (jakzal, Sander-Toonen) + * bug #17048 Fix the logout path when not using the router (stof) + * bug #17049 Fix the logout path when not using the router (stof) + * bug #17057 [FrameworkBundle][HttpKernel] the finder is required to discover bundle commands (xabbuh) + * bug #17006 [Form] Fix casting regression in DoctrineChoiceLoader (bendavies) + * bug #16915 [Process] Enhance compatiblity with --enable-sigchild (nicolas-grekas) + * bug #16829 [FrameworkBundle] prevent cache:clear creating too long paths (Tobion) + * bug #16921 Fix short array syntax for php 5.3 (ewgRa) + * bug #16450 [Serializer] Fixed `array_unique` on array of objects in `getAllowedAttributes`. (CornyPhoenix) + * bug #16757 [FrameworkBundle] [Translation] Fixed translations not written when no translations directory in update command (jeremyFreeAgent) + * bug #16871 [FrameworkBundle] Disable built-in server commands when Process component is missing (gnugat, xabbuh) + * bug #16870 [FrameworkBundle] Disable the server:run command when Process component is missing (gnugat, xabbuh) + * bug #16742 [Console][ProgressBar] redrawFrequency should never be 0 (dritter) + * bug #16799 Improve error message for undefined DIC aliases (mpdude) + * bug #16825 [VarDumper] fix .sf-dump z-index (debug bar conflict) (Antoine LA) + * bug #16772 Refactoring EntityUserProvider::__construct() to not do work, cause cache warm error (weaverryan) + * bug #16753 [Process] Fix signaling/stopping logic on Windows (nicolas-grekas) + * bug #16733 [Console] do not encode backslashes in console default description (Tobion) + * bug #16312 [HttpKernel] clearstatcache() so the Cache sees when a .lck file has been released (mpdude) + * bug #16351 [WIP] [Form] [TwigBridge] Bootstrap horizontal theme missing tests (pieter2627) + * bug #16685 [Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false (webmozart) + * bug #16705 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * bug #16695 [SecurityBundle] disable the init:acl command if ACL is not used (Tobion) + * bug #16679 [Form] Disabled view data validation if "data_class" is set to null (webmozart) + * bug #16676 [HttpFoundation] Workaround HHVM rewriting HTTP response line (nicolas-grekas) + * bug #16668 [ClassLoader] Fix parsing namespace when token_get_all() is missing (nicolas-grekas) + * bug #16386 Bug #16343 [Router] Too many Routes ? (jelte) + * bug #16651 [Debug] Ensure class declarations are loaded only once (nicolas-grekas) + +* 2.7.7 (2015-11-23) + + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16578 [Console] Fix bug in windows detection (kbond) + * bug #16546 [Serializer] ObjectNormalizer: don't serialize static methods and props (dunglas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + * bug #16292 fix race condition at mkdir (#16258) (ewgRa) + * bug #15945 [Form] trigger deprecation warning when using empty_value (xabbuh) + * bug #16384 [FrameworkBundle] JsonDescriptor - encode container params only once (xabbuh) + * bug #16480 [VarDumper] Fix PHP7 type-hints compat (nicolas-grekas) + * bug #16463 [PropertyAccess] Port of the performance optimization from 2.3 (dunglas) + * bug #16462 [PropertyAccess] Fix dynamic property accessing. (dunglas) + * bug #16454 [Serializer] GetSetNormalizer shouldn't set/get static methods (boekkooi) + * bug #16453 [Serializer] PropertyNormalizer shouldn't set static properties (boekkooi) + * bug #16471 [VarDumper] Fix casting for ReflectionParameter (nicolas-grekas) + * bug #16294 [PropertyAccess] Major performance improvement (dunglas) + * bug #16331 fixed Twig deprecation notices (fabpot) + * bug #16306 [DoctrineBridge] Fix issue which prevent the profiler to explain a query (Baachi) + * bug #16359 Use mb_detect_encoding with $strict = true (nicolas-grekas) + * bug #16144 [Security] don't allow to install the split Security packages (xabbuh) + * 2.7.6 (2015-10-27) * bug #16338 [VarDumper] Fix anonymous class dumping (nicolas-grekas) diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md new file mode 100644 index 0000000000000..aa13e1f9f1b12 --- /dev/null +++ b/CHANGELOG-2.8.md @@ -0,0 +1,1361 @@ +CHANGELOG for 2.8.x +=================== + +This changelog references the relevant changes (bug and security fixes) done +in 2.8 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/v2.8.0...v2.8.1 + +* 2.8.52 (2019-11-13) + + * security #cve-2019-18888 [HttpFoundation] fix guessing mime-types of files with leading dash (nicolas-grekas) + * security #cve-2019-18887 [HttpKernel] Use constant time comparison in UriSigner (stof) + +* 2.8.51 (2019-04-17) + + * no changes + +* 2.8.50 (2019-04-17) + + * security #cve-2019-10910 [DI] Check service IDs are valid (nicolas-grekas) + * security #cve-2019-10909 [FrameworkBundle][Form] Fix XSS issues in the form theme of the PHP templating engine (stof) + * security #cve-2019-10912 [PHPUnit Bridge] Prevent destructors with side-effects from being unserialized (nicolas-grekas) + * security #cve-2019-10911 [Security] Add a separator in the remember me cookie hash (pborreli) + * security #cve-2019-10913 [HttpFoundation] reject invalid method override (nicolas-grekas) + +* 2.8.49 (2018-12-06) + + * security #cve-2018-19790 [Security\Http] detect bad redirect targets using backslashes (xabbuh) + * security #cve-2018-19789 [Form] Filter file uploads out of regular form types (nicolas-grekas) + +* 2.8.48 (2018-11-26) + + * bug #28917 [DoctrineBridge] catch errors while converting to db values in data collector (alekitto) + * bug #27314 [DoctrineBridge] fix case sensitivity issue in RememberMe\DoctrineTokenProvider (PF4Public) + * bug #29308 [Translation] Use XLIFF source rather than resname when there's no target (thewilkybarkid) + * bug #26244 [BrowserKit] fixed BC Break for HTTP_HOST header (brizzz) + * bug #28147 [DomCrawler] exclude fields inside "template" tags (Gorjunov) + * bug #29271 [HttpFoundation] Fix trailing space for mime-type with parameters (Sascha Dens) + * bug #29223 [Validator] Added the missing constraints instance checks (thomasbisignani) + * bug #29182 [Form] Fixed empty data for compound date types (HeahDude) + * bug #29185 [Form] Fixed keeping hash of equal \DateTimeInterface on submit (HeahDude) + * bug #28731 [Form] invalidate forms on transformation failures (xabbuh) + * bug #29152 [Config] Unset key during normalization (ro0NL) + * bug #29057 [HttpFoundation] replace any preexisting Content-Type headers (nicolas-grekas) + +* 2.8.47 (2018-11-03) + + * bug #29020 Fix ini_get() for boolean values (deguif) + * bug #28861 [DependencyInjection] Skip empty proxy code (olvlvl) + * bug #28801 Convert InsufficientAuthenticationException to HttpException with 401 status code (vincentchalamon) + * bug #28840 add missing double-quotes to extra_fields output message (danielkay) + * bug #28712 [Form] reverse transform RFC 3339 formatted dates (xabbuh) + * bug #28813 Fix for race condition in console output stream write (rudolfratusinski) + * bug #27772 [Console] Fixes multiselect choice question defaults in non-interactive mode (veewee) + * bug #28689 [Process] fix locking of pipe files on Windows (nicolas-grekas) + * bug #28704 [Form] fix multi-digit seconds fraction handling (xabbuh) + * bug #28648 [PHPUnitBridge] Fix ClockMock microtime() format (acasademont) + +* 2.8.46 (2018-09-30) + + * bug #28376 [TwigBundle] Fixed caching of templates in src/Resources//views on cache warmup (yceruto) + * bug #28565 [HttpFoundation][Security] forward locale and format to subrequests (nicolas-grekas) + * bug #28545 [Console] Send the right exit code to console.terminate listeners (mpdude) + * bug #28466 [Form] fail reverse transforming invalid RFC 3339 dates (xabbuh) + * bug #28540 [Intl] parse numbers terminated with decimal separator (xabbuh) + * bug #28548 [Console] Fixed boxed table style with colspan (ro0NL) + * bug #28433 [HttpFoundation] Allow reuse of Session between requests if ID did not change (tgalopin) + * bug #28508 [Form] forward false label option to nested types (xabbuh) + * bug #28464 [Form] forward the invalid_message option in date types (xabbuh) + * bug #28499 [Ldap] Use shut up operator on connection errors at ldap_start_tls (Andras Debreczeni) + * bug #28372 [Form] Fix DateTimeType html5 input format (franzwilding, mcfedr) + * bug #28396 [Intl] Blacklist Eurozone and United Nations in Region Data Generator (gregurco) + * bug #28393 [Console] fixed corrupt error output for unknown multibyte short option (downace) + * bug #28401 [Console] Fix SymfonyQuestionHelper::askQuestion() with choice value as default (chalasr) + * bug #28377 fix fopen flags (SpacePossum) + * bug #27970 [FileValidator] Format file size in validation message according to binaryFormat option (jfredon) + * bug #28029 [TwigBundle] remove cache warmers when Twig cache is disabled (xabbuh) + * bug #28344 [HttpKernel][FrameworkBundle] Fix escaping of serialized payloads passed to test clients (nicolas-grekas) + +* 2.8.45 (2018-08-27) + + * bug #28278 [HttpFoundation] Fix unprepared BinaryFileResponse sends empty file (wackymole) + * bug #28241 [HttpKernel] fix forwarding trusted headers as server parameters (nicolas-grekas) + * bug #28220 [PropertyAccess] fix type error handling when writing values (xabbuh) + * bug #28100 [Security] Call AccessListener after LogoutListener (chalasr) + * bug #28144 [HttpFoundation] fix false-positive ConflictingHeadersException (nicolas-grekas) + * bug #28055 [PropertyInfo] Allow nested collections (jderusse) + * bug #28083 Remove the Expires header when calling Response::expire() (javiereguiluz) + +* 2.8.44 (2018-08-01) + + * security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas) + * security #cve-2018-14773 [HttpFoundation] Remove support for legacy and risky HTTP headers (nicolas-grekas) + * bug #28003 [HttpKernel] Fixes invalid REMOTE_ADDR in inline subrequest when configuring trusted proxy with subnet (netiul) + * bug #28045 [HttpFoundation] Fix Cookie::isCleared (ro0NL) + * bug #28080 [HttpFoundation] fixed using _method parameter with invalid type (Phobetor) + +* 2.8.43 (2018-07-23) + + * bug #28005 [HttpKernel] Fixed templateExists on parse error of the template name (yceruto) + * bug #27997 Serbo-Croatian has Serbian plural rule (kylekatarnls) + * bug #27941 [WebProfilerBundle] Fixed icon alignment issue using Bootstrap 4.1.2 (jmsche) + * bug #27937 [HttpFoundation] reset callback on StreamedResponse when setNotModified() is called (rubencm) + * bug #27927 [HttpFoundation] Suppress side effects in 'get' and 'has' methods of NamespacedAttributeBag (webnet-fr) + * bug #27904 [Filesystem] fix lock file permissions (fritzmg) + * bug #27758 [WebProfilerBundle] Prevent toolbar links color override by css (alcalyn) + * bug #27831 Check for Hyper terminal on all operating systems. (azjezz) + * bug #27794 Add color support for Hyper terminal . (azjezz) + * bug #27809 [HttpFoundation] Fix tests: new message for status 425 (dunglas) + * bug #27716 [DI] fix dumping deprecated service in yaml (nicolas-grekas) + +* 2.8.42 (2018-06-25) + + * bug #27669 [Filesystem] fix file lock on SunOS (fritzmg) + * bug #27309 Fix surrogate not using original request (Toflar) + * bug #27630 [Validator][Form] Remove BOM in some xlf files (gautierderuette) + * bug #27591 [VarDumper] Fix dumping ArrayObject and ArrayIterator instances (nicolas-grekas) + * bug #27581 Fix bad method call with guard authentication + session migration (weaverryan) + * bug #27452 Avoid migration on stateless firewalls (weaverryan) + * bug #27514 [Debug] Pass previous exception to FatalErrorException (pmontoya) + * bug #26973 [HttpKernel] Set first trusted proxy as REMOTE_ADDR in InlineFragmentRenderer. (kmadejski) + * bug #27303 [Process] Consider "executable" suffixes first on Windows (sanmai) + * bug #27297 Triggering RememberMe's loginFail() when token cannot be created (weaverryan) + * bug #27366 [DI] never inline lazy services (nicolas-grekas) + +* 2.8.41 (2018-05-25) + + * bug #27359 [HttpFoundation] Fix perf issue during MimeTypeGuesser intialization (nicolas-grekas) + * security #cve-2018-11408 [SecurityBundle] Fail if security.http_utils cannot be configured + * security #cve-2018-11406 clear CSRF tokens when the user is logged out + * security #cve-2018-11385 Adding session authentication strategy to Guard to avoid session fixation + * security #cve-2018-11385 Adding session strategy to ALL listeners to avoid *any* possible fixation + * security #cve-2018-11386 [HttpFoundation] Break infinite loop in PdoSessionHandler when MySQL is in loose mode + +* 2.8.40 (2018-05-21) + + * bug #26781 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions on transform() (syastrebov) + * bug #27286 [Translation] Add Occitan plural rule (kylekatarnls) + * bug #27246 Disallow invalid characters in session.name (ostrolucky) + * bug #24805 [Security] Fix logout (MatTheCat) + * bug #27141 [Process] Suppress warnings when open_basedir is non-empty (cbj4074) + * bug #27250 [Session] limiting :key for GET_LOCK to 64 chars (oleg-andreyev) + * bug #27237 [Debug] Fix populating error_get_last() for handled silent errors (nicolas-grekas) + * bug #27236 [Filesystem] Fix usages of error_get_last() (nicolas-grekas) + * bug #27152 [HttpFoundation] use brace-style regex delimiters (xabbuh) + * feature #24896 Add CODE_OF_CONDUCT.md (egircys) + +* 2.8.39 (2018-04-30) + + * bug #27067 [HttpFoundation] Fix setting session-related ini settings (e-moe) + * bug #27016 [Security][Guard] GuardAuthenticationProvider::authenticate cannot return null (biomedia-thomas) + * bug #26831 [Bridge/Doctrine] count(): Parameter must be an array or an object that implements Countable (gpenverne) + * bug #27044 [Security] Skip user checks if not implementing UserInterface (chalasr) + * bug #26014 [Security] Fixed being logged out on failed attempt in guard (iltar) + * bug #26910 Use new PHP7.2 functions in hasColorSupport (johnstevenson) + * bug #26999 [VarDumper] Fix dumping of SplObjectStorage (corphi) + * bug #25841 [DoctrineBridge] Fix bug when indexBy is meta key in PropertyInfo\DoctrineExtractor (insekticid) + * bug #26886 Don't assume that file binary exists on *nix OS (teohhanhui) + * bug #26643 Fix that ESI/SSI processing can turn a "private" response "public" (mpdude) + * bug #26932 [Form] Fixed trimming choice values (HeahDude) + * bug #26875 [Console] Don't go past exact matches when autocompleting (nicolas-grekas) + * bug #26823 [Validator] Fix LazyLoadingMetadataFactory with PSR6Cache for non classname if tested values isn't existing class (Pascal Montoya, pmontoya) + * bug #26834 [Yaml] Throw parse error on unfinished inline map (nicolas-grekas) + +* 2.8.38 (2018-04-06) + + * bug #26788 [Security] Load the user before pre/post auth checks when needed (chalasr) + * bug #26774 [SecurityBundle] Add missing argument to security.authentication.provider.simple (i3or1s, chalasr) + * bug #26763 [Finder] Remove duplicate slashes in filenames (helhum) + * bug #26749 Add PHPDbg support to HTTP components (hkdobrev) + * bug #26609 [Console] Fix check of color support on Windows (mlocati) + +* 2.8.37 (2018-04-02) + + * bug #26727 [HttpCache] Unlink tmp file on error (Chansig) + * bug #26675 [HttpKernel] DumpDataCollector: do not flush when a dumper is provided (ogizanagi) + * bug #26663 [TwigBridge] Fix rendering of currency by MoneyType (ro0NL) + * bug #26677 Support phpdbg SAPI in Debug::enable() (hkdobrev) + * bug #26589 [Ldap] cast to string when checking empty passwords (ismail1432) + * bug #26621 [Form] no type errors with invalid submitted data types (xabbuh) + * bug #26337 [Finder] Fixed leading/trailing / in filename (lyrixx) + * bug #26584 [TwigBridge] allow html5 compatible rendering of forms with null names (systemist) + * bug #24401 [Form] Change datetime to datetime-local for HTML5 datetime input (pierredup) + * bug #26370 [Security] added userChecker to SimpleAuthenticationProvider (i3or1s) + * bug #26569 [BrowserKit] Fix cookie path handling when $domain is null (dunglas) + * bug #26598 Fixes #26563 (open_basedir restriction in effect) (temperatur) + * bug #26568 [Debug] Reset previous exception handler earlier to prevent infinite loop (nicolas-grekas) + * bug #26567 [DoctrineBridge] Don't rely on ClassMetadataInfo->hasField in DoctrineOrmTypeGuesser anymore (fancyweb) + * bug #26356 [FrameworkBundle] HttpCache is not longer abstract (lyrixx) + * bug #26548 [DomCrawler] Change bad wording in ChoiceFormField::untick (dunglas) + * bug #26433 [DomCrawler] extract(): fix a bug when the attribute list is empty (dunglas) + * bug #26452 [Intl] Load locale aliases to support alias fallbacks (jakzal) + * bug #26450 [CssSelector] Fix CSS identifiers parsing - they can start with dash (jakubkulhan) + +* 2.8.36 (2018-03-05) + + * bug #26368 [WebProfilerBundle] Fix Debug toolbar breaks app (xkobal) + +* 2.8.35 (2018-03-01) + + * bug #26338 [Debug] Keep previous errors of Error instances (Philipp91) + * bug #26312 [Routing] Don't throw 405 when scheme requirement doesn't match (nicolas-grekas) + * bug #26298 Fix ArrayInput::toString() for InputArgument::IS_ARRAY args (maximium) + * bug #26236 [PropertyInfo] ReflectionExtractor: give a chance to other extractors if no properties (dunglas) + * bug #25557 [WebProfilerBundle] add a way to limit ajax request (Simperfit) + * bug #26228 [HttpFoundation] Fix missing "throw" in JsonResponse (nicolas-grekas) + * bug #26211 [Console] Suppress warning from sapi_windows_vt100_support (adawolfa) + * bug #26156 Fixes #26136: Avoid emitting warning in hasParameterOption() (greg-1-anderson) + * bug #26183 [DI] Add null check for removeChild (changmin.keum) + * bug #26173 [Security] fix accessing request values (xabbuh) + * bug #26159 created validator.tl.xlf for Form/Translations (ergiegonzaga) + * bug #26100 [Routing] Throw 405 instead of 404 when redirect is not possible (nicolas-grekas) + * bug #26040 [Process] Check PHP_BINDIR before $PATH in PhpExecutableFinder (nicolas-grekas) + * bug #26012 Exit as late as possible (greg0ire) + * bug #26111 [Security] fix merge of 2.7 into 2.8 + add test case (dmaicher) + * bug #25893 [Console] Fix hasParameterOption / getParameterOption when used with multiple flags (greg-1-anderson) + * bug #25940 [Form] keep the context when validating forms (xabbuh) + * bug #25373 Use the PCRE_DOLLAR_ENDONLY modifier in route regexes (mpdude) + * bug #26010 [CssSelector] For AND operator, the left operand should have parentheses, not only right operand (Arnaud CHASSEUX) + * bug #25971 [Debug] Fix bad registration of exception handler, leading to mem leak (nicolas-grekas) + * bug #25962 [Routing] Fix trailing slash redirection for non-safe verbs (nicolas-grekas) + * bug #25948 [Form] Fixed empty data on expanded ChoiceType and FileType (HeahDude) + * bug #25972 support sapi_windows_vt100_support for php 7.2+ (jhdxr) + * bug #25744 [TwigBridge] Allow label translation to be safe (MatTheCat) + +* 2.8.34 (2018-01-29) + + * bug #25922 [HttpFoundation] Use the correct syntax for session gc based on Pdo driver (tanasecosminromeo) + * bug #25933 Disable CSP header on exception pages only in debug (ostrolucky) + * bug #25926 [Form] Fixed Button::setParent() when already submitted (HeahDude) + * bug #25927 [Form] Fixed submitting disabled buttons (HeahDude) + * bug #25891 [DependencyInjection] allow null values for root nodes in YAML configs (xabbuh) + * bug #25848 [Validator] add missing parent isset and add test (Simperfit) + * bug #25861 do not conflict with egulias/email-validator 2.0+ (xabbuh) + * bug #25851 [Validator] Conflict with egulias/email-validator 2.0 (emodric) + * bug #25837 [SecurityBundle] Don't register in memory users as services (chalasr) + * bug #25835 [HttpKernel] DebugHandlersListener should always replace the existing exception handler (nicolas-grekas) + * bug #25829 [Debug] Always decorate existing exception handlers to deal with fatal errors (nicolas-grekas) + * bug #25824 Fixing a bug where the dump() function depended on bundle ordering (weaverryan) + * bug #25789 Enableable ArrayNodeDefinition is disabled for empty configuration (kejwmen) + * bug #25816 Problem in phar see mergerequest #25579 (betzholz) + * bug #25781 [Form] Disallow transform dates beyond the year 9999 (curry684) + * bug #25812 Copied NO language files to the new NB locale (derrabus) + * bug #25801 [Router] Skip anonymous classes when loading annotated routes (pierredup) + * bug #25657 [Security] Fix fatal error on non string username (chalasr) + * bug #25799 Fixed Request::__toString ignoring cookies (Toflar) + * bug #25755 [Debug] prevent infinite loop with faulty exception handlers (nicolas-grekas) + * bug #25771 [Validator] 19 digits VISA card numbers are valid (xabbuh) + * bug #25751 [FrameworkBundle] Add the missing `enabled` session attribute (sroze) + * bug #25750 [HttpKernel] Turn bad hosts into 400 instead of 500 (nicolas-grekas) + * bug #25490 [Serializer] Fixed throwing exception with option JSON_PARTIAL_OUTPUT_ON_ERROR (diversantvlz) + * bug #25709 Tweaked some styles in the profiler tables (javiereguiluz) + * feature #25669 [Security] Fail gracefully if the security token cannot be unserialized from the session (thewilkybarkid) + +* 2.8.33 (2018-01-05) + + * bug #25532 [HttpKernel] Disable CSP header on exception pages (ostrolucky) + * bug #25491 [Routing] Use the default host even if context is empty (sroze) + * bug #25662 Dumper shouldn't use html format for phpdbg / cli-server (jhoff) + * bug #25529 [Validator] Fix access to root object when using composite constraint (ostrolucky) + * bug #25430 Fixes for Oracle in PdoSessionHandler (elislenio) + * bug #25599 Add application/ld+json format associated to json (vincentchalamon) + * bug #25407 [Console] Commands with an alias should not be recognized as ambiguous (Simperfit) + * bug #25521 [Console] fix a bug when you are passing a default value and passing -n would output the index (Simperfit) + * bug #25489 [FrameworkBundle] remove esi/ssi renderers if inactive (dmaicher) + * bug #25427 Preserve percent-encoding in URLs when performing redirects in the UrlMatcher (mpdude) + * bug #25480 [FrameworkBundle] add missing validation options to XSD file (xabbuh) + * bug #25487 [Console] Fix a bug when passing a letter that could be an alias (Simperfit) + * bug #25233 [TwigBridge][Form] Fix hidden currency element with Bootstrap 3 theme (julienfalque) + * bug #25408 [Debug] Fix catching fatal errors in case of nested error handlers (nicolas-grekas) + * bug #25330 [HttpFoundation] Support 0 bit netmask in IPv6 (`::/0`) (stephank) + * bug #25410 [HttpKernel] Fix logging of post-terminate errors/exceptions (nicolas-grekas) + * bug #25323 [ExpressionLanguage] throw an SyntaxError instead of an undefined index notice (Simperfit) + +* 2.8.32 (2017-12-04) + + * bug #25278 Fix for missing whitespace control modifier in form layout (kubawerlos) + * bug #25236 [Form][TwigBridge] Fix collision between view properties and form fields (yceruto) + * bug #25258 [link] Prevent warnings when running link with 2.7 (dunglas) + * bug #24750 [Validator] ExpressionValidator should use OBJECT_TO_STRING (Simperfit) + * bug #25182 [HttpFoundation] AutExpireFlashBag should not clear new flashes (Simperfit, sroze) + * 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 #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 #25072 [Bridge/PhpUnit] Remove trailing "\n" from ClockMock::microtime(false) (joky) + * bug #24956 Fix ambiguous pattern (weltling) + +* 2.8.31 (2017-11-16) + + * security #24995 Validate redirect targets using the session cookie domain (nicolas-grekas) + * security #24994 Prevent bundle readers from breaking out of paths (xabbuh) + * security #24993 Ensure that submitted data are uploaded files (xabbuh) + * security #24992 Namespace generated CSRF tokens depending of the current scheme (dunglas) + +* 2.8.30 (2017-11-13) + + * bug #24952 [HttpFoundation] Fix session-related BC break (nicolas-grekas, sroze) + * bug #24929 [Console] Fix traversable autocomplete values (ro0NL) + +* 2.8.29 (2017-11-10) + + * bug #24888 [FrameworkBundle] Specifically inject the debug dispatcher in the collector (ogizanagi) + * bug #24909 [Intl] Update ICU data to 60.1 (jakzal) + * bug #24906 [Bridge/ProxyManager] Remove direct reference to value holder property (nicolas-grekas) + * bug #24900 [Validator] Fix Costa Rica IBAN format (Bozhidar Hristov) + * bug #24904 [Validator] Add Belarus IBAN format (Bozhidar Hristov) + * bug #24531 [HttpFoundation] Fix forward-compat of NativeSessionStorage with PHP 7.2 (sroze) + * bug #24665 Fix dump panel hidden when closing a dump (julienfalque) + * bug #24814 [Intl] Make intl-data tests pass and save language aliases again (jakzal) + * bug #24764 [HttpFoundation] add Early Hints to Reponse to fix test (Simperfit) + * bug #24605 [FrameworkBundle] Do not load property_access.xml if the component isn't installed (ogizanagi) + * bug #24606 [HttpFoundation] Fix FileBag issue with associative arrays (enumag) + * bug #24660 Escape trailing \ in QuestionHelper autocompletion (kamazee) + * bug #24644 [Security] Fixed auth provider authenticate() cannot return void (glye) + * bug #24642 [Routing] Fix resource miss (dunglas) + * bug #24608 Adding the Form default theme files to be warmed up in Twig's cache (weaverryan) + * bug #24626 streamed response should return $this (DQNEO) + * bug #24589 Username and password in basic auth are allowed to contain '.' (Richard Quadling) + * bug #24566 Fixed unsetting from loosely equal keys OrderedHashMap (maryo) + * bug #24570 [Debug] Fix same vendor detection in class loader (Jean-Beru) + * bug #24563 [Serializer] ObjectNormalizer: throw if PropertyAccess isn't installed (dunglas) + * bug #24571 [PropertyInfo] Add support for the iterable type (dunglas) + * bug #24579 pdo session fix (mxp100) + * bug #24536 [Security] Reject remember-me token if UserCheckerInterface::checkPostAuth() fails (kbond) + * bug #24519 [Validator] [Twig] added magic method __isset() to File Constraint class (loru88) + * bug #24532 [DI] Fix possible incorrect php-code when dumped strings contains newlines (Strate) + * bug #24502 [HttpFoundation] never match invalid IP addresses (xabbuh) + * bug #24460 [Form] fix parsing invalid floating point numbers (xabbuh) + * bug #24490 [HttpFoundation] Combine Cache-Control headers (c960657) + * bug #23711 Fix support for PHP 7.2 (Simperfit, nicolas-grekas) + * bug #24494 [HttpFoundation] Add missing session.lazy_write config option (nicolas-grekas) + * bug #24434 [Form] Use for=ID on radio/checkbox label. (Nyholm) + * bug #24455 [Console] Escape command usage (sroze) + +* 2.8.28 (2017-10-05) + + * bug #24448 [Session] fix MongoDb session handler to gc all expired sessions (Tobion) + * bug #24417 [Yaml] parse references on merge keys (xabbuh) + * bug #24421 [Config] Fix dumped files invalidation by OPCache (nicolas-grekas) + * bug #23980 Tests and fix for issue in array model data in EntityType field with multiple=true (stoccc) + * bug #22586 [Form] Fixed PercentToLocalizedStringTransformer to accept both comma and dot as decimal separator, if possible (aaa2000) + * bug #24157 [Intl] Fixed support of Locale::getFallback (lyrixx) + * bug #24198 [HttpFoundation] Fix file upload multiple with no files (enumag) + * bug #24036 [Form] Fix precision of MoneyToLocalizedStringTransformer's divisions and multiplications (Rubinum) + * bug #24367 PdoSessionHandler: fix advisory lock for pgsql (Tobion) + * bug #24243 HttpCache does not consider ESI resources in HEAD requests (mpdude) + * bug #24304 [FrameworkBundle] Fix Routing\DelegatingLoader (nicolas-grekas) + * bug #24219 [Console] Preserving line breaks between sentences according to the exception message (yceruto) + * bug #23722 [Form] Fixed GroupSequence with "constraints" option (HeahDude) + * bug #22321 [Filesystem] Fixed makePathRelative (ausi) + * bug #23473 [Filesystem] mirror - fix copying content with same name as source/target. (gitlost) + * bug #24162 [WebProfilerBundle] fixed TemplateManager when using Twig 2 without compat interfaces (fabpot) + * bug #24141 [DomCrawler] Fix conversion to int on GetPhpFiles (MaraBlaga) + * bug #23853 Filtering empty uuids in ORMQueryBuilderLoader. (mlazovla) + * bug #24101 [Security] Fix exception when use_referer option is true and referer is not set or empty (linniksa) + * bug #24105 [Filesystem] check permissions if dump target dir is missing (xabbuh) + * bug #24115 [FrameworkBundle] Get KERNEL_DIR through $_ENV too for KernelTestCase (yceruto) + * bug #24041 [ExpressionLanguage] throws an exception on calling uncallable method (fmata) + * bug #24096 Fix ArrayInput::toString() for VALUE_IS_ARRAY options/args (chalasr) + * bug #23730 Fixed the escaping of back slashes and << in console output (javiereguiluz) + +* 2.8.27 (2017-08-28) + + * bug #23989 [Debug] Remove false-positive check in DebugClassLoader (nicolas-grekas) + * bug #23982 [VarDumper] Strengthen dumped JS (nicolas-grekas) + * bug #23925 [Validator] Fix use of GroupSequenceProvider in child classes (linniksa) + * bug #23945 [Validator] Fix Greek translation (azhurb) + * bug #23909 [Console] Initialize lazily to render exceptions properly (nicolas-grekas) + * bug #23856 [DI] Fix dumping abstract with YamlDumper (nicolas-grekas) + * bug #23752 Ignore memcached missing key error on session destroy (jderusse) + * bug #23658 [HttpFoundation] Generate safe fallback filename for wrongly encoded filename (xelaris) + * bug #23783 Avoid infinite loops when profiler data is malformed (javiereguiluz) + * bug #23729 [Bridge\ProxyManager] Dont call __destruct() on non-instantiated services (nicolas-grekas) + +* 2.8.26 (2017-08-01) + + * bug #22244 [Console] Fix passing options with defaultCommand (Jakub Sacha) + * bug #23684 [Debug] Missing escape in debug output (c960657) + * bug #23662 [VarDumper] Adapt to php 7.2 changes (nicolas-grekas) + * bug #23649 [Form][TwigBridge] Don't render _method in form_rest() for a child form (fmarchalemisys) + * bug #23023 [DoctrineBridge][PropertyInfo] Added support for Doctrine Embeddables (vudaltsov) + * bug #23619 [Validator] Fix IbanValidator for ukrainian IBANs (paroe) + * bug #23238 [Security] ensure the 'route' index is set before attempting to use it (gsdevme) + * bug #23330 [WebProfilerBundle] Fix full sized dump hovering in toolbar (ogizanagi) + * bug #23580 Fix login redirect when referer contains a query string (fabpot) + * bug #23574 [VarDumper] Move locale sniffing to dump() time (nicolas-grekas) + +* 2.8.25 (2017-07-17) + + * security #23507 [Security] validate empty passwords again (xabbuh) + * bug #23526 [HttpFoundation] Set meta refresh time to 0 in RedirectResponse content (jnvsor) + * bug #23540 Disable inlining deprecated services (alekitto) + * bug #23468 [DI] Handle root namespace in service definitions (ro0NL) + * bug #23256 [Security] Fix authentication.failure event not dispatched on AccountStatusException (chalasr) + * bug #23461 Use rawurlencode() to transform the Cookie into a string (javiereguiluz) + * bug #23459 [TwigBundle] allow to configure custom formats in XML configs (xabbuh) + * bug #23460 Don't display the Symfony debug toolbar when printing the page (javiereguiluz) + * bug #23261 Fixed absolute url generation for query strings and hash urls (alexander-schranz) + * bug #23398 [Filesystem] Dont copy perms when origin is remote (nicolas-grekas) + +* 2.8.24 (2017-07-05) + + * bug #23378 [FrameworkBundle] Do not remove files from assets dir (1ed) + +* 2.8.23 (2017-07-04) + + * bug #23341 [DoctrineBridge][Security][Validator] do not validate empty values (xabbuh) + * bug #23274 Display a better error design when the toolbar cannot be displayed (yceruto) + * bug #23333 [PropertyAccess] Fix TypeError discard (dunglas) + * bug #23345 [Console] fix description of INF default values (xabbuh) + * bug #23279 Don't call count on non countable object (pierredup) + * bug #23283 [TwigBundle] add back exception check (xabbuh) + * bug #23268 Show exception is checked twice in ExceptionController of twig (gmponos) + * bug #23266 Display a better error message when the toolbar cannot be displayed (javiereguiluz) + * bug #23271 [FrameworkBundle] allow SSI fragments configuration in XML files (xabbuh) + * bug #23254 [Form][TwigBridge] render hidden _method field in form_rest() (xabbuh) + * bug #23250 [Translation] return fallback locales whenever possible (xabbuh) + * bug #23240 [Console] Fix catching exception type in QuestionHelper (voronkovich) + * bug #23229 [WebProfilerBundle] Eliminate line wrap on count column (routing) (e-moe) + * bug #22732 [Security] fix switch user _exit without having current token (dmaicher) + * bug #22730 [FrameworkBundle] Sessions: configurable "use_strict_mode" option for NativeSessionStorage (MacDada) + * bug #23195 [FrameworkBundle] [Command] Clean bundle directory, fixes #23177 (NicolasPion) + * bug #23052 [TwigBundle] Add Content-Type header for exception response (rchoquet) + * bug #23199 Reset redirectCount when throwing exception (hvanoch) + * bug #23186 [TwigBundle] Move template.xml loading to a compiler pass (ogizanagi) + * bug #23130 Keep s-maxage when expiry and validation are used in combination (mpdude) + * bug #23129 Fix two edge cases in ResponseCacheStrategy (mpdude) + * feature #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #22636 [Routing] Expose request in route conditions, if needed and possible (ro0NL) + * bug #23057 [Translation][FrameworkBundle] Fix resource loading order inconsistency reported in #23034 (mpdude) + * bug #23092 [Filesystem] added workaround in Filesystem::rename for PHP bug (VolCh) + * bug #23128 [HttpFoundation] fix for Support for new 7.1 session options (vincentaubert) + * bug #23176 [VarDumper] fixes (nicolas-grekas) + * bug #22953 #22839 - changed debug toolbar dump section to relative and use full window width (mkurzeja) + * bug #23086 [FrameworkBundle] Fix perf issue in CacheClearCommand::warmup() (nicolas-grekas) + * bug #23098 Cache ipCheck (2.7) (gonzalovilaseca) + * bug #23069 [SecurityBundle] Show unique Inherited roles in profile panel (yceruto) + +* 2.8.22 (2017-06-07) + + * bug #23073 [TwigBridge] Fix namespaced classes (ogizanagi) + * bug #22936 [Form] Mix attr option between guessed options and user options (yceruto) + * bug #22988 [PropertyInfo][DoctrineBridge] The bigint Doctrine's type must be converted to string (dunglas) + * bug #23014 Fix optional cache warmers are always instantiated whereas they should be lazy-loaded (romainneutron) + * bug #23024 [EventDispatcher] Fix ContainerAwareEventDispatcher::hasListeners(null) (nicolas-grekas) + * bug #22996 [Form] Fix \IntlDateFormatter timezone parameter usage to bypass PHP bug #66323 (romainneutron) + * bug #22994 Harden the debugging of Twig filters and functions (stof) + +* 2.8.21 (2017-05-29) + + * bug #22847 [Console] ChoiceQuestion must have choices (ro0NL) + * bug #22900 [FrameworkBundle][Console] Fix the override of a command registered by the kernel (aaa2000) + * bug #22910 [Filesystem] improve error handling in lock() (xabbuh) + * bug #22718 [Console] Fixed different behaviour of key and value user inputs in multiple choice question (borNfreee) + * bug #22901 Fix missing abstract key in XmlDumper (weaverryan) + * bug #22817 [PhpUnitBridge] optional error handler arguments (xabbuh) + * bug #22752 Improved how profiler errors are displayed on small screens (javiereguiluz) + * bug #22647 [VarDumper] Fix dumping of non-nested stubs (nicolas-grekas) + * bug #22584 [Security] Avoid unnecessary route lookup for empty logout path (ro0NL) + * bug #22690 [Console] Fix errors not rethrown even if not handled by console.error listeners (chalasr) + * bug #22669 [FrameworkBundle] AbstractConfigCommand: do not try registering bundles twice (ogizanagi) + * bug #22676 [FrameworkBundle] Adding the extension XML (flug) + +* 2.8.20 (2017-05-01) + + * bug #22550 Allow Upper Case property names in ObjectNormalizer (insekticid) + * bug #22528 [Asset] Starting slash should indicate no basePath wanted (weaverryan) + * bug #22541 [EventDispatcher] fix: unwrap listeners for correct info (dmaicher) + * bug #22526 [Asset] Preventing the base path or absolute URL from being prefixed incorrectly (weaverryan) + * bug #22523 [WebProfilerBundle] Fixed the flickering when loading complex profiler panels (javiereguiluz) + * bug #22435 [Console] Fix dispatching throwables from ConsoleEvents::COMMAND (nicolas-grekas) + * bug #22478 [Serializer] XmlEncoder: fix negative int and large numbers handling (dunglas) + * bug #22424 [Debug] Set exit status to 255 on error (nicolas-grekas) + * bug #22426 [PropertyInfo] Prevent returning int values in some cases (dunglas) + * bug #22399 Prevent double registrations related to tag priorities (nicolas-grekas) + * bug #22396 Prevent double registrations related to tag priorities (nicolas-grekas) + * bug #22352 [HttpFoundation] Add `use_strict_mode` in validOptions for session (sstok) + * bug #22351 [Yaml] don't keep internal state between parser runs (xabbuh) + * bug #22307 [Debug] Fix php notice (enumag) + * bug #22311 [DI] Fix second auto-registration (nicolas-grekas) + * bug #22109 [Validator] check for empty host when calling checkdnsrr() (apetitpa) + * bug #22280 [DI] Fix the xml schema (GuilhemN) + * bug #22282 [DI] Prevent AutowirePass from triggering irrelevant deprecations (chalasr) + * bug #22255 [Translation] avoid creating cache files for fallback locales. (aitboudad) + * bug #22292 Fixes #22264 - add support for Chrome headless (redthor) + +* 2.8.19 (2017-04-05) + + * bug #22265 Allow Upper Case property names (insekticid) + * bug #22258 [DI] Autowiring and factories are incompatible with each others (nicolas-grekas) + * bug #22254 [DI] Don't use auto-registered services to populate type-candidates (nicolas-grekas) + * bug #22229 [ExpressionLanguage] Provide the expression in syntax errors (k0pernikus, stof) + * bug #22251 [PropertyInfo] Support nullable array or collection (4rthem) + * bug #22240 [DI] Fix fatal error at ContainerBuilder::compile() if config is not installed (chalasr) + * bug #22140 [Form] Improve the exceptions when trying to get the data in a PRE_SET_DATA listener and the data has not already been set (fancyweb) + * bug #22217 [Console] Fix table cell styling (ro0NL) + * bug #22194 [Console] CommandTester: disable color support detection (julienfalque) + * bug #22188 [Console] Revised exception rendering (ro0NL) + * bug #22154 [WebProfilerBundle] Normalize whitespace in exceptions passed in headers (curry684) + * bug #22142 [Console] Escape exception messages in renderException (chalasr) + * bug #22172 Fix port usage in server:status command (alcaeus) + * bug #22164 [Bridge\Doctrine] Fix change breaking doctrine-bundle test suite (nicolas-grekas) + * bug #22133 [Filesystem] normalize paths before making them relative (xabbuh) + * bug #22138 [HttpFoundation][bugfix] $bags should always be initialized (MacDada) + * bug #21810 #21809 [SecurityBundle] bugfix: if security provider's name contains upper cases then container didn't compile (Antanas Arvasevicius) + * bug #22123 [WebProfilerBundle] Fix for CSS attribute at Profiler Translation Page (e-moe) + * bug #19778 [Security] Fixed roles serialization on token from user object (eko) + * bug #22036 Set Date header in Response constructor already (mpdude) + * bug #22022 [Validator] fix URL validator to detect non supported chars according to RFC 3986 (e-moe) + * bug #21849 [HttpFoundation] Fix missing handling of for/host/proto info from "Forwarded" header (nicolas-grekas) + * bug #21968 Fixed pathinfo calculation for requests starting with a question mark. (syzygymsu) + * bug #22027 Revert "bug #21841 [Console] Do not squash input changes made from console.command event (chalasr)" (chalasr) + * bug #21846 [HttpFoundation] Fix Request::getHost() when having several hosts in X_FORWARDED_HOST (nicolas-grekas) + * bug #21208 [Validator] Add object handling of invalid constraints in Composite (SenseException) + * bug #22044 [Serializer] [XML] Ignore Process Instruction (jordscream) + * bug #22079 [HttpKernel] Fixed bug with purging of HTTPS URLs (ausi) + * bug #21523 #20411 fix Yaml parsing for very long quoted strings (RichardBradley) + * bug #22001 [Doctrine Bridge] fix priority for doctrine event listeners (dmaicher) + * bug #21981 [Console] Use proper line endings in BufferedOutput (julienfalque) + * bug #21976 [VarDumper] Add missing isset() checks in some casters (nicolas-grekas) + * bug #21957 [Form] Choice type int values (BC Fix) (mcfedr) + * bug #21923 [travis] Test with hhvm 3.18 (nicolas-grekas) + * bug #21823 dumpFile(), preserve existing file permissions (chs2) + * bug #21865 [Security] context listener: hardening user provider handling (xabbuh) + * bug #21883 [HttpKernel] fix Kernel name when stored in a directory starting with a number (fabpot) + +* 2.8.18 (2017-03-06) + + * bug #21841 [Console] Do not squash input changes made from console.command event (chalasr) + * bug #21671 [Serializer] Xml encoder throws exception for valid data (gr1ev0us) + * bug #21805 Provide less state in getRequestFormat (dawehner) + * bug #21832 [Routing] Ignore hidden directories when loading routes from annotations (jakzal) + * bug #21769 [Form] Improve rounding precision (foaly-nr1) + * bug #21825 [PhpUnitBridge] disable global test listener when not registered (xabbuh) + * bug #21267 [Form] Fix ChoiceType to ensure submitted data is not nested unnecessarily (issei-m) + * bug #21731 Fix emacs link (rubenrua) + * bug #21800 Fix issues reported by static analyze (romainneutron) + * bug #21798 Revert "bug #21791 [SecurityBundle] only pass relevant user provider (xabbuh)" (xabbuh) + * bug #21791 [SecurityBundle] only pass relevant user provider (xabbuh) + * bug #21787 [PhpUnitBridge] do not register the test listener twice (xabbuh) + * bug #21756 [Yaml] Stop replacing NULLs when merging (gadelat) + * bug #21689 [WebServerBundle] fixed html attribute escape (Seb33300) + * bug #21722 [ExpressionLanguage] Registering functions after calling evaluate(), compile() or parse() is not supported (maidmaid) + * bug #21679 [SecurityBundle] fix priority ordering of security voters (xabbuh) + * bug #21115 [Validator] do not guess getter method names (xabbuh) + * bug #21670 [DependencyInjection] Fix autowiring types when there are more than 2 services colliding (GuilhemN) + * bug #21665 [DependencyInjection] Fix autowiring collisions detection (nicolas-grekas, GuilhemN) + * bug #21661 Fix Composer constraints (fabpot) + * bug #21582 [HttpCache] purge both http and https from http cache (dbu) + * bug #21637 [FrameworkBundle] remove translation data collector when not usable (xabbuh) + * bug #21634 [VarDumper] Added missing persistent stream cast (lyrixx) + * bug #21436 [DependencyInjection] check for circular refs caused by method calls (xabbuh) + * bug #21400 [Serializer] fix upper camel case conversion (see #21399) (markusu49) + * bug #21599 [Console][Table] fixed render when using multiple rowspans. (aitboudad) + * bug #21613 [Process] Permit empty suffix on Windows (Bilge) + * bug #21057 [DI] Auto register extension configuration classes as a resource (ro0NL) + * bug #21592 [Validator] property constraints can be added in child classes (angelk, xabbuh) + * bug #21458 [Config] Early return for DirectoryResource (robfrawley) + * bug #21562 [DoctrineBridge] make sure that null can be the invalid value (xabbuh) + * bug #21584 [WebProfilerBundle] Readd Symfony version status in the toolbar (wouterj) + * bug #21557 [VarDumper] Improve dump of AMQP* Object (lyrixx) + * bug #21542 [VarDumper] Fixed dumping of terminated generator (lyrixx) + +* 2.8.17 (2017-02-06) + + * bug #20844 [Config] Fix checking cache for non existing meta file (hason) + * bug #21063 [Form] Fixed DateType format option for single text widget (HeahDude) + * bug #21430 Casting TableCell value to string. (jaydiablo) + * bug #21359 [FrameworkBundle] fixed custom domain for translations in php templates (robinlehrmann) + * bug #21485 [Process] Non ASCII characters disappearing during the escapeshellarg (GuillaumeVerdon) + * bug #21370 [FrameworkBundle] Execute the PhpDocExtractor earlier (GuilhemN) + * bug #21462 [BrowserKit] ignore invalid cookies expires date format (xabbuh) + * bug #21438 [Console] Fix TableCell issues with decoration (ogizanagi) + * bug #21431 [DoctrineBridge] always check for all fields to be mapped (xabbuh) + * bug #21360 [PropertyAccess] Handle interfaces in the invalid argument exception (fancyweb) + * bug #21403 [DI] Fix defaults overriding empty strings in AutowirePass (nicolas-grekas) + * bug #21401 [Debug] Workaround "null" $context (nicolas-grekas) + * bug #21333 [HttpKernel] Fix ArgumentValueResolver for arguments default null (chalasr) + * bug #20871 [HttpKernel] Give higher priority to adding request formats (akeeman) + * bug #21332 [PropertyInfo] Don't try to access a property thru a static method (dunglas) + * bug #21331 [PropertyInfo] Exclude static methods form properties guessing (dunglas) + * bug #21285 [TwigBundle] do not lose already set method calls (xabbuh) + * bug #21279 #20411 fix Yaml parsing for very long quoted strings (RichardBradley) + +* 2.8.16 (2017-01-12) + + * bug #21218 [Form] DateTimeToLocalizedStringTransformer does not use timezone when using date only (magnetik) + * bug #21104 [FrameworkBundle] fix IPv6 address handling in server commands (xabbuh) + * bug #20793 [Validator] Fix caching of constraints derived from non-serializable parents (uwej711) + * bug #19586 [TwigBundle] Fix bug where namespaced paths don't take parent bundles in account (wesleylancel) + * bug #21237 [FrameworkBundle] Fix relative paths used as cache keys (nicolas-grekas) + * bug #21183 [Validator] respect groups when merging constraints (xabbuh) + * bug #21179 [TwigBundle] Fixing regression in TwigEngine exception handling (Bertalan Attila) + * bug #21220 [DI] Fix missing new line after private alias (ogizanagi) + * bug #21211 Classloader tmpname (lyrixx) + * bug #21205 [TwigBundle] fixed usage when Templating is not installed (fabpot) + * bug #21155 [Validator] Check cascasdedGroups for being countable (scaytrase) + * bug #21200 [Filesystem] Check that directory is writable after created it in dumpFile() (chalasr) + * bug #21113 [FrameworkBundle][HttpKernel] Fix resources loading for bundles with custom structure (chalasr) + * bug #21084 [Yaml] handle empty lines inside unindented collection (xabbuh) + * bug #20925 [HttpFoundation] Validate/cast cookie expire time (ro0NL) + * bug #21032 [SecurityBundle] Made collection of user provider unique when injecting them to the RemberMeService (lyrixx) + * bug #21078 [Console] Escape default value when dumping help (lyrixx) + * bug #21076 [Console] OS X Can't call cli_set_process_title php without superuser (ogizanagi) + * bug #20900 [Console] Descriptors should use Helper::strlen (ogizanagi) + * bug #21064 [Debug] Wrap call to ->log in a try catch block (lyrixx) + * bug #21010 [Debug] UndefinedMethodFatalErrorHandler - Handle anonymous classes (SpacePossum) + * bug #20859 Avoid warning in PHP 7.2 because of non-countable data (wouterj) + * bug #21053 [Validator] override property constraints in child class (xabbuh) + * bug #21034 [FrameworkBundle] Make TemplateController working without the Templating component (dunglas) + * bug #20970 [Console] Fix question formatting using SymfonyStyle::ask() (chalasr, ogizanagi) + * bug #20975 [Form] fix group sequence based validation (xabbuh) + * bug #20599 [WebProfilerBundle] Display multiple HTTP headers in WDT (ro0NL) + * bug #20799 [TwigBundle] do not try to register incomplete definitions (xabbuh) + * bug #20961 [Validator] phpize default option values (xabbuh) + * bug #20934 [FrameworkBundle] Fix PHP form templates on translatable attributes (ro0NL) + * bug #20957 [FrameworkBundle] test for the Validator component to be present (xabbuh) + * bug #20936 [DependencyInjection] Fix on-invalid attribute type in xsd (ogizanagi) + * bug #20931 [VarDumper] Fix dumping by-ref variadics (nicolas-grekas) + * bug #20734 [Security] AbstractVoter->supportsAttribute gives false positive if attribute is zero (0) (martynas-foodpanda) + * bug #14082 [config] Fix issue when key removed and left value only (zerustech) + * bug #20847 [Console] fixed BC issue with static closures (araines) + +* 2.8.15 (2016-12-13) + + * bug #20714 [FrameworkBundle] Fix unresolved parameters from default configs in debug:config (chalasr) + * bug #20442 [FrameworkBundle] Bundle commands are not available via find() (julienfalque) + * bug #20840 [WebProfilerBundle] add dependency on Twig (xabbuh) + * bug #20828 [Validator] Fix init of YamlFileLoader::$classes for empty files (nicolas-grekas) + * bug #20539 Cast result to int before adding to it (alcaeus) + * bug #20831 [Twig] Fix deprecations with Twig 1.29 (nicolas-grekas) + * bug #20767 [Cache] Fix dumping SplDoublyLinkedList iter mode (nicolas-grekas) + * bug #20736 [Console] fixed PHP7 Errors when not using Dispatcher (keradus) + * bug #20755 [HttpKernel] Regression test for missing controller arguments (iltar) + * bug #20418 [Form][DX] FileType "multiple" fixes (yceruto) + * bug #19902 [DependencyInjection] PhpDumper.php: hasReference() shouldn't search references in lazy service. (antanas-arvasevicius) + * bug #20704 [Console] Fix wrong handling of multiline arg/opt descriptions (ogizanagi) + * bug #20712 [TwigBundle] Fix twig loader registered twice (ogizanagi) + * bug #20716 [WebProfilerBundle] Fix dump block is unfairly restrained (ogizanagi) + * bug #20671 [Config] ConfigCache::isFresh() should return false when unserialize() fails (nicolas-grekas) + * bug #20676 [ClassLoader] Use only forward slashes in generated class map (nicolas-grekas) + * bug #20664 [Validator] ensure the proper context for nested validations (xabbuh) + * bug #20661 bug #20653 [WebProfilerBundle] Profiler includes ghost panels (jzawadzki) + * bug #20374 [FrameworkBundle] Improve performance of ControllerNameParser (enumag) + * bug #20474 [Routing] Fail properly when a route parameter name cannot be used as a PCRE subpattern name (fancyweb) + * bug #20566 [DI] Initialize properties before method calls (ro0NL) + * bug #20609 [DI] Fixed custom services definition BC break introduced in ec7e70fb… (kiler129) + * bug #20598 [DI] Aliases should preserve the aliased invalid behavior (nicolas-grekas) + * bug #20602 [HttpKernel] Revert BC breaking change of Request::isMethodSafe() (nicolas-grekas) + * bug #20499 [Doctrine][Form] support large integers (xabbuh) + * bug #20576 [Process] Do feat test before enabling TTY mode (nicolas-grekas) + +* 2.8.14 (2016-11-21) + + * bug #20543 [DI] Fix error when trying to resolve a DefinitionDecorator (nicolas-grekas) + * bug #20544 [PhpUnitBridge] Fix time-sensitive tests that use data providers (julienfalque) + * bug #20484 bumped min version of Twig to 1.28 (fabpot) + * bug #20519 [Debug] Remove GLOBALS from exception context to avoid endless recursion (Seldaek) + * bug #20455 [ClassLoader] Fix ClassCollectionLoader inlining with __halt_compiler (giosh94mhz) + * bug #20307 [Form] Fix Date\TimeType marked as invalid on request with single_text and zero seconds (LuisDeimos) + * bug #20466 [Translation] fixed nested fallback catalogue using multiple locales. (aitboudad) + * bug #20465 [#18637][TranslationDebug] workaround for getFallbackLocales. (aitboudad) + * bug #20440 [TwigBridge][TwigBundle][HttpKernel] prefer getSourceContext() over getSource() (xabbuh) + * bug #20422 [Translation][fallback] add missing resources in parent catalogues. (aitboudad) + * bug #20378 [Form] Fixed show float values as choice value in ChoiceType (yceruto) + * bug #20294 Improved the design of the metrics in the profiler (javiereguiluz) + * bug #20375 [HttpFoundation][Session] Fix memcache session handler (klandaika) + * bug #20377 [Console] Fix infinite loop on missing input (chalasr) + * bug #20372 [Console] simplified code (fabpot) + * bug #20342 [Form] Fix UrlType transforms valid protocols (ogizanagi) + * bug #20292 Enhance GAE compat by removing some realpath() (nicolas-grekas) + * bug #20326 [VarDumper] Fix dumping Twig source in stack traces (nicolas-grekas) + * bug #20321 Compatibility with Twig 1.27 (xkobal) + +* 2.8.13 (2016-10-27) + + * bug #20289 Fix edge case with StreamedResponse where headers are sent twice (Nicofuma) + * bug #20267 [DependencyInjection] A decorated service should not keep the autowiring types (chalasr) + * bug #20278 [DependencyInjection] merge tags instead of completely replacing them (xabbuh) + * bug #20271 Changes related to Twig 1.27 (fabpot) + * bug #20252 Trim constant values in XmlFileLoader (lstrojny) + * bug #20253 [TwigBridge] Use non-deprecated Twig_Node::getTemplateLine() (fabpot) + * bug #20243 [WebProfilerBundle][btn-link] add `cursor: pointer` (aitboudad) + * bug #20175 [VarDumper] Fix source links with latests Twig versions (nicolas-grekas) + * bug #20235 [DomCrawler] Allow pipe (|) character in link tags when using Xpath expressions (klausi, nicolas-grekas) + * bug #20224 [Twig] removed deprecations added in Twig 1.27 (fabpot) + * bug #19478 fixed Filesystem:makePathRelative and added 2 more testcases (muhammedeminakbulut) + * bug #20218 [HttpFoundation] no 304 response if method is not cacheable (xabbuh) + * bug #20207 [DependencyInjection] move tags from decorated to decorating service (xabbuh) + * bug #20205 [HttpCache] fix: do not cache OPTIONS request (dmaicher) + * bug #20146 [Validator] Prevent infinite loop in PropertyMetadata (wesleylancel) + * bug #20184 [FrameworkBundle] Convert null prefix to an empty string in translation:update (chalasr) + * bug #20154 [PropertyInfo] Fix edge cases in ReflectionExtractor (nicolas-grekas) + * bug #19725 [Security] $attributes can be anything, but RoleVoter assumes strings (Jonatan Männchen) + * bug #20127 [HttpFoundation] JSONP callback validation (ro0NL) + * bug #20163 add missing use statement (xabbuh) + * bug #19961 [Console] Escape question text and default value in SymfonyStyle::ask() (chalasr) + * bug #20141 [Console] Fix validation of empty values using SymfonyQuestionHelper::ask() (chalasr) + * bug #20147 [FrameworkBundle] Alter container class instead of kernel name in cache:clear command (nicolas-grekas) + +* 2.8.12 (2016-10-03) + + * bug #20102 [Validator] Url validator not validating hosts ending in a number (gwkunze) + * bug #20132 Use "more entropy" option for uniqid() (javiereguiluz) + * bug #20122 [Validator] Reset constraint options (ro0NL) + * bug #20116 fixed AddConstraintValidatorsPass config (fabpot) + * bug #20078 Fix #19943 Make sure to process each interface metadata only once (lemoinem) + * bug #20080 [Form] compound forms without children should be considered rendered implicitly (backbone87) + * bug #20087 [VarDumper] Fix PHP 7.1 compat (nicolas-grekas) + * bug #20086 [VarDumper] Fix PHP 7.1 compat (nicolas-grekas) + * bug #20077 [Process] silent file operation to avoid open basedir issues (xabbuh) + * bug #20079 fixed Twig support for 1.26 and 2.0 (fabpot) + * bug #20051 Fix indexBy type extraction (lemoinem) + * bug #19951 [Finder] Trim trailing directory slash in ExcludeDirectoryFilterIterator (ro0NL) + * bug #20018 [VarDumper] Fix test (nicolas-grekas) + * bug #20011 Use UUID for error codes for Form validator. (Koc) + * bug #20010 [DX] Fixed regression when exception message swallowed when logging it. (Koc) + * bug #19983 [TwigBridge] removed Twig null nodes (deprecated as of Twig 1.25) (fabpot) + * bug #19946 [Console] Fix parsing optionnal options with empty value in argv (chalasr) + * bug #19636 [Finder] no PHP warning on empty directory iteration (ggottwald) + * bug #19923 [bugfix] [Console] Set `Input::$interactive` to `false` when command is executed with `--quiet` as verbosity level (phansys) + * bug #19811 Fixed the nullable support for php 7.1 and below (2.7, 2.8, 3.0) (iltar) + * bug #19853 [PropertyInfo] Make ReflectionExtractor compatible with ReflectionType changes in PHP 7.1 (teohhanhui) + * bug #19904 [Form] Fixed collapsed ChoiceType options attributes (HeahDude) + * bug #19908 [Config] Handle open_basedir restrictions in FileLocator (Nicofuma) + * bug #19924 [DoctrineBridge][PropertyInfo] Treat Doctrine decimal type as string (teohhanhui) + * bug #19932 Fixed bad merge (GrahamCampbell) + * bug #19922 [Yaml][TwigBridge] Use JSON_UNESCAPED_SLASHES for lint commands output (chalasr) + * bug #19928 [Validator] Update IpValidatorTest data set with a valid reserved IP (jakzal) + * bug #19813 [Console] fixed PHP7 Errors are now handled and converted to Exceptions (fonsecas72) + * bug #19879 [Form] Incorrect timezone with DateTimeLocalizedStringTransformer (mbeccati) + * bug #19878 Fix translation:update command count (tgalopin) + +* 2.8.11 (2016-09-07) + + * bug #19859 [ClassLoader] Fix ClassCollectionLoader inlining with declare(strict_types=1) (nicolas-grekas) + * bug #19780 [FrameworkBundle] Incorrect line break in exception message (500 debug page) (pedroresende) + * bug #19595 [form] lazy trans `post_max_size_message`. (aitboudad) + * bug #19870 [DI] Fix setting synthetic services on ContainerBuilder (nicolas-grekas) + * bug #19848 Revert "minor #19689 [DI] Cleanup array_key_exists (ro0NL)" (nicolas-grekas) + * bug #19842 [FrameworkBundle] Check for class existence before is_subclass_of (chalasr) + * bug #19827 [BrowserKit] Fix cookie expiration on 32 bit systems (jameshalsall) + +* 2.8.10 (2016-09-02) + + * bug #19786 Update profiler's layout to use flexbox (javiereguiluz) + * bug #19794 [VarDumper] Various minor fixes & cleanups (nicolas-grekas) + * bug #19751 Fixes the calendar in constructor to handle null (wakqasahmed) + * bug #19388 [Validator][GroupSequence] fixed GroupSequence validation ignores PropetyMetadata of parent classes (Sandro Hopf) + * bug #19601 [FrameworkBundle] Added friendly exception when constraint validator class does not exist (yceruto) + * bug #19580 [Validator] fixed duplicate constraints with parent class interfaces (dmaicher) + * bug #19647 [Debug] Swap dumper services at bootstrap (lyrixx) + * bug #19685 [DI] Include dynamic services in alternatives (ro0NL) + * bug #19702 [Debug][HttpKernel][VarDumper] Prepare for committed 7.2 changes (aka "small-bc-breaks") (nicolas-grekas) + * bug #19704 [DependencyInjection] PhpDumper::isFrozen inconsistency (allflame) + * bug #19643 [DependencyInjection] Fix service autowiring inheritance (chalasr) + * bug #19667 [SecurityBundle] Add missing deprecation notice for form_login.intention (ro0NL) + * bug #19666 Verify explicitly that the request IP is a valid IPv4 address (nesk) + * bug #19660 Disable CLI color for Windows 10 greater than 10.0.10586 (mlocati) + * bug #19663 Exception details break the layout (Dionysis Arvanitis) + * bug #19651 [HttpKernel] Fix HttpCache validation HTTP method (tgalopin) + * bug #19623 [VarDumper] Fix dumping continuations (nicolas-grekas) + * bug #19549 [HttpFoundation] fixed Request::getContent() reusage bug (1ma) + * bug #19373 [Form] Skip CSRF validation on form when POST max size is exceeded (jameshalsall) + * bug #19541 Fix #19531 [Form] DateType fails parsing when midnight is not a valid time (mbeccati) + * bug #19579 [Process] Strengthen Windows pipe files opening (again...) (nicolas-grekas) + * bug #19564 Added class existence check if is_subclass_of() fails in compiler passes (SCIF) + * bug #19522 [SwiftMailerBridge] Fix flawed deprecation message (chalasr) + * bug #19510 [Process] Fix double-fread() when reading unix pipes (nicolas-grekas) + * bug #19508 [Process] Fix AbstractPipes::write() for a situation seen on HHVM (at least) (nicolas-grekas) + +* 2.8.9 (2016-07-30) + + * bug #19470 undefined offset fix (#19406) (ReenExe) + * bug #19300 [HttpKernel] Use flock() for HttpCache's lock files (mpdude) + * bug #19428 [Process] Fix write access check for pipes on Windows (nicolas-grekas) + * bug #19439 [DependencyInjection] Fixed deprecated default message template with XML (jeremyFreeAgent) + * bug #19397 [HttpFoundation] HttpCache refresh stale responses containing an ETag (maennchen) + * bug #19426 [Form] Fix the money form type render with Bootstrap3 (Th3Mouk) + * bug #19422 [DomCrawler] Inherit the namespace cache in subcrawlers (stof) + * bug #19425 [BrowserKit] Uppercase the "GET" method in redirects (jakzal) + * bug #19384 Fix PHP 7.1 related failures (nicolas-grekas) + * bug #19379 [VarDumper] Fix for PHP 7.1 (nicolas-grekas) + * bug #19342 Added class existence check if is_subclass_of() fails in compiler passes (SCIF) + * bug #19369 Fix the DBAL session handler version check for Postgresql (stof) + * bug #19368 [VarDumper] Fix dumping jsons casted as arrays (nicolas-grekas) + * bug #19334 [Security] Fix the retrieval of the last username when using forwarding (stof) + * bug #19321 [HttpFoundation] Add OPTIONS and TRACE to the list of safe methods (dunglas) + * bug #19317 [BrowserKit] Update Client::getAbsoluteUri() for query string only URIs (georaldc) + * bug #19298 [ClassLoader] Fix declared classes being computed when not needed (nicolas-grekas) + * bug #19316 [Validator] Added additional MasterCard range to the CardSchemeValidator (Dennis Væversted) + * bug #19290 [HttpKernel] fixed internal subrequests having an if-modified-since-header (MalteWunsch) + * bug #19307 [Security] Fix deprecated usage of DigestAuthenticationEntryPoint::getKey() in DigestAuthenticationListener (Maxime STEINHAUSSER) + * bug #19309 [DoctrineBridge] added missing error code for constraint. (Koc) + * bug #19306 [Form] fixed bug - name in ButtonBuilder (cheprasov) + * bug #19292 [varDumper] Fix missing usage of ExceptionCaster::$traceArgs (nicolas-grekas) + * bug #19288 [VarDumper] Fix indentation trimming in ExceptionCaster (nicolas-grekas) + * bug #19267 [Validator] UuidValidator must accept a Uuid constraint. (hhamon) + * bug #19186 Fix for #19183 to add support for new PHP MongoDB extension in sessions. (omanizer) + * bug #19253 [Console] Fix block() padding formatting after #19189 (chalasr) + * bug #19218 [Security][Guard] check if session exist before using it (pasdeloup) + +* 2.8.8 (2016-06-30) + + * bug #19217 [HttpKernel] Inline ValidateRequestListener logic into HttpKernel (nicolas-grekas) + * bug #18688 [HttpFoundation] Warning when request has both Forwarded and X-Forwarded-For (magnusnordlander) + * bug #19173 [Console] Decouple SymfonyStyle from TableCell (ro0NL) + * bug #19189 [Console] Fix formatting of SymfonyStyle::comment() (chalasr) + * bug #19211 [Form] fix post max size translation type extension for >= 2.8 (Tobion) + * bug #17822 [WIP] [Form] fix `empty_data` option in expanded `ChoiceType` (HeahDude) + * bug #19134 Distinguish between first and subsequent progress bar displays (rquadling) + * bug #19061 [FORM] fix post_max_size_message translation (alt. 2) (David Badura) + * bug #19100 [Console] Fixed SymfonyQuestionHelper multi-choice with defaults (sstok) + * bug #18924 [DoctrineBridge] Don't use object IDs in DoctrineChoiceLoader when passing a value closure (webmozart) + * bug #19138 [DomCrawler] No more exception on field name with strange format (guiled, fabpot) + * bug #18935 [Form] Consider a violation even if the form is not submitted (egeloen) + * bug #19127 [Form] Add exception to FormRenderer about non-unique block names (enumag) + * bug #19118 [Process] Fix pipes cleaning on Windows (nicolas-grekas) + * bug #19128 Avoid phpunit 5.4 warnings on getMock (2.7+) (iltar) + * bug #19114 [HttpKernel] Dont close the reponse stream in debug (nicolas-grekas) + * bug #19101 [Session] fix PDO transaction aborted under PostgreSQL (Tobion) + * bug #18501 [HttpFoundation] changed MERGE queries (hjkl) + * bug #19062 [HttpFoundation] Fix UPSERT for PgSql >= 9.5 (nicolas-grekas) + * bug #18548 [Form] minor fixes in DateTime transformers (HeahDude) + * bug #18732 [PropertyAccess][DX] Enhance exception that say that some methods are missing if they don't (nykopol) + * bug #19048 [HttpFoundation] Use UPSERT for sessions stored in PgSql >= 9.5 (nicolas-grekas) + * bug #19038 Fix feature detection for IE (Alsciende) + * bug #18915 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #19020 [Form] Fixed collapsed choice attributes (HeahDude) + * bug #19028 [Yaml] properly count skipped comment lines (xabbuh) + * bug #19009 [WebProfilerBundle] Fix invalid CSS style (romainneutron) + * bug #17733 [Yaml] Fix wrong line number when comments are inserted in the middle of a block. (paradajozsef) + * bug #18911 Fixed singular of committee (peterrehm) + * bug #18971 Do not inject web debug toolbar on attachments (peterrehm) + +* 2.8.7 (2016-06-06) + + * bug #18908 [DependencyInjection] force enabling the external XML entity loaders (xabbuh) + * bug #18893 [DependencyInjection] Skip deep reference check for 'service_container' (RobertMe) + * bug #18812 Catch \Throwable (fprochazka) + * bug #18821 [Form] Removed UTC specification with timestamp (francisbesset) + * bug #18861 Fix for #18843 (inso) + * bug #18889 [Console] SymfonyStyle: Fix alignment/prefixing of multi-line comments (chalasr) + * bug #18907 [Routing] Fix the annotation loader taking a class constant as a beginning of a class name (jakzal, nicolas-grekas) + * bug #18879 [Console] SymfonyStyle: Align multi-line/very-long-line blocks (chalasr) + * bug #18864 [Console][DX] Fixed ambiguous error message when using a duplicate option shortcut (peterrehm) + * bug #18883 Fix js comment in profiler (linnaea) + * bug #18844 [Yaml] fix exception contexts (xabbuh) + * bug #18840 [Yaml] properly handle unindented collections (xabbuh) + * bug #18765 Catch \Throwable (fprochazka) + * bug #18813 Catch \Throwable (fprochazka) + * bug #18839 People - person singularization (Keeo) + * bug #18828 [Yaml] chomp newlines only at the end of YAML documents (xabbuh) + * bug #18814 Fixed server status command when port has been omitted (peterrehm) + * bug #18799 Use levenshtein level for better Bundle matching (j0k3r) + * bug #18413 [WebProfilerBundle] Fix CORS ajax security issues (romainneutron) + * bug #18774 [console][table] adjust width of colspanned cell. (aitboudad) + * bug #18507 [BUG] Delete class 'control-group' in bootstrap 3 (Philippe Degeeter) + * bug #18747 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + * bug #18635 [Console] Prevent fatal error when calling Command::getHelper without helperSet (chalasr) + * bug #18686 [console][table] adjust width of colspanned cell. (aitboudad) + * bug #18761 [Form] Modified iterator_to_array's 2nd parameter to false in ViolationMapper (issei-m) + * bug #18737 [Debug] Fix fatal error handlers on PHP 7 (nicolas-grekas) + +* 2.8.6 (2016-05-09) + + * security #18736 Fixed issue with blank password with Ldap (csarrazi) + * security #18733 limited the maximum length of a submitted username (fabpot) + * bug #18730 [FrameworkBundle] prevent calling get() for service_container service (xabbuh) + * bug #18705 added a conflict between Monolog bridge 2.8 and HTTP Kernel 3.0+ (fabpot) + * bug #18709 [DependencyInjection] top-level anonymous services must be public (xabbuh) + * bug #18388 [EventDispatcher] check for method to exist (xabbuh) + * bug #18699 [DependencyInjection] Use the priority of service decoration on service with parent (hason) + * bug #18692 add @Event annotation for KernelEvents (Haehnchen) + * bug #18246 [DependencyInjection] fix ambiguous services schema (backbone87) + +* 2.8.5 (2016-04-29) + + * bug #18180 [Form] fixed BC break with pre selection of choices with `ChoiceType` and its children (HeahDude) + * bug #18645 [Console] Fix wrong exceptions being thrown (JhonnyL) + * bug #18562 [WebProfilerBunde] Give an absolute url in case the request occured from another domain (romainneutron) + * bug #18600 [DI] Fix AutowirePass fatal error with classes that have non-existing parents (hason, nicolas-grekas) + * bug #18603 [PropertyAccess] ->getValue() should be read-only (nicolas-grekas) + * bug #18593 [VarDumper] Fix dumping type hints for non-existing parent classes (nicolas-grekas) + * bug #18596 [DI] Fix internal caching in AutowirePass (nicolas-grekas) + * bug #18581 [Console] [TableHelper] make it work with SymfonyStyle. (aitboudad) + * bug #18280 [Routing] add query param if value is different from default (Tobion) + * bug #18540 Replace iconv_*() uses by mb_*(), add mbstring polyfill when required (nicolas-grekas) + * bug #18496 [Console] use ANSI escape sequences in ProgressBar overwrite method (alekitto) + * bug #18490 [LDAP] Free the search result after a search to free memory (hiddewie) + * bug #18491 [DependencyInjection] anonymous services are always private (xabbuh) + * bug #18515 [Filesystem] Better error handling in remove() (nicolas-grekas) + * bug #18360 [PropertyInfo] Extract nullable and collection key type for Doctrine associations (teohhanhui) + * bug #18449 [PropertyAccess] Fix regression (nicolas-grekas) + * bug #18429 [Console] Correct time formatting. (camporter) + * bug #18457 [WebProfilerBundle] Fixed error from unset twig variable (simonsargeant) + * bug #18467 [DependencyInjection] Resolve aliases before removing abstract services + add tests (nicolas-grekas) + * bug #18469 Force profiler toolbar svg display (pyrech) + * bug #18460 [DomCrawler] Fix select option with empty value (Matt Wells) + * bug #18425 [Security] Fixed SwitchUserListener when exiting an impersonation with AnonymousToken (lyrixx) + * bug #18317 [Form] fix "prototype" not required when parent form is not required (HeahDude) + * bug #18439 [Logging] Add support for Firefox (43+) in ChromePhpHandler (arjenm) + * bug #18385 Detect CLI color support for Windows 10 build 10586 (mlocati) + * bug #18426 [EventDispatcher] Try first if the event is Stopped (lyrixx) + * bug #18407 Fixed the "hover" state of the profiler sidebar menu (javiereguiluz) + * bug #18394 [FrameworkBundle] Return the invokable service if its name is the class name (dunglas) + * bug #18347 Fixed the styles of the Symfony icon in the web debug toolbar (javiereguiluz) + * bug #18265 Optimize ReplaceAliasByActualDefinitionPass (ajb-in) + * bug #18349 [Process] Fix stream_select priority when writing to stdin (nicolas-grekas) + * bug #18358 [Form] NumberToLocalizedStringTransformer should return floats when possible (nicolas-grekas) + * bug #17926 [DependencyInjection] Enable alias for service_container (hason) + * bug #18352 [Debug] Fix case sensitivity checks (nicolas-grekas) + * bug #18336 [Debug] Fix handling of php7 throwables (nicolas-grekas) + * bug #18354 [FrameworkBundle][TwigBridge] fix high deps tests (xabbuh) + * bug #18312 [ClassLoader] Fix storing not-found classes in APC cache (nicolas-grekas) + +* 2.8.4 (2016-03-27) + + * bug #18298 [Validator] do not treat payload as callback (xabbuh) + * bug #18275 [Form] Fix BC break introduced in #14403 (HeahDude) + * bug #18271 [FileSystem] Google app engine filesystem (swordbeta) + * bug #18255 [HttpFoundation] Fix support of custom mime types with parameters (Ener-Getick) + * bug #18272 [Bridge\PhpUnit] Workaround old phpunit bug, no colors in weak mode, add tests (nicolas-grekas) + * bug #18259 [PropertyAccess] Backport fixes from 2.7 (nicolas-grekas) + * bug #18261 [PropertyAccess] Fix isPropertyWritable not using the reflection cache (nicolas-grekas) + * bug #18224 [PropertyAccess] Remove most ref mismatches to improve perf (nicolas-grekas) + * bug #18237 [WebProfilerBundle] Added table-layout property to AJAX toolbar css (kevintweber) + * bug #18209 [PropertyInfo] Support Doctrine custom mapping type in DoctrineExtractor (teohhanhui) + * bug #18210 [PropertyAccess] Throw an UnexpectedTypeException when the type do not match (dunglas, nicolas-grekas) + * bug #18216 [Intl] Fix invalid numeric literal on PHP 7 (nicolas-grekas) + * bug #18147 [Validator] EmailValidator cannot extract hostname if email contains multiple @ symbols (natechicago) + * bug #18023 [Process] getIncrementalOutput should work without calling getOutput (romainneutron) + * bug #18175 [Translation] Add support for fuzzy tags in PoFileLoader (nud) + * bug #18179 [Form] Fix NumberToLocalizedStringTransformer::reverseTransform with big integers (ovrflo, nicolas-grekas) + * bug #18164 [HttpKernel] set s-maxage only if all responses are cacheable (xabbuh) + * bug #18150 [Process] Wait a bit less on Windows (nicolas-grekas) + * bug #18130 [Debug] Replaced logic for detecting filesystem case sensitivity (Dan Blows) + * bug #18137 Autowiring the concrete class too - consistent with behavior of other services (weaverryan) + * bug #18087 [WebProfiler] Sidebar button padding (rvanlaak) + * bug #18080 [HttpFoundation] Set the Content-Range header if the requested Range is unsatisfied (jakzal) + * bug #18084 [HttpFoundation] Avoid warnings when checking malicious IPs (jakzal) + * bug #18066 [Process] Fix pipes handling (nicolas-grekas) + * bug #18078 [Console] Fix an autocompletion question helper issue with non-sequentially indexed choices (jakzal) + * bug #18048 [HttpKernel] Fix mem usage when stripping the prod container (nicolas-grekas) + * bug #18065 [Finder] Partially revert #17134 to fix a regression (jakzal) + * bug #18018 [HttpFoundation] exception when registering bags for started sessions (xabbuh) + * bug #18054 [Filesystem] Fix false positive in ->remove() (nicolas-grekas) + * bug #18049 [Validator] Fix the locale validator so it treats a locale alias as a valid locale (jakzal) + * bug #18019 [Intl] Update ICU to version 55 (jakzal) + * bug #18015 [Process] Fix memory issue when using large input streams (romainneutron) + * bug #16656 [HttpFoundation] automatically generate safe fallback filename (xabbuh) + * bug #15794 [Console] default to stderr in the console helpers (alcohol) + * bug #17984 Allow to normalize \Traversable when serializing xml (Ener-Getick) + * bug #17434 Improved the error message when a template is not found (rvanginneken, javiereguiluz) + * bug #17687 Improved the error message when using "@" in a decorated service (javiereguiluz) + * bug #17744 Improve error reporting in router panel of web profiler (javiereguiluz) + * bug #17894 [FrameworkBundle] Fix a regression in handling absolute template paths (jakzal) + * bug #17990 [DoctrineBridge][Form] Fix performance regression in EntityType (kimlai) + * bug #17595 [HttpKernel] Remove _path from query parameters when fragment is a subrequest (cmenning) + * bug #17986 [DomCrawler] Dont use LIBXML_PARSEHUGE by default (nicolas-grekas) + * bug #17668 add 'guid' to list of exception to filter out (garak) + * bug #17615 Ensure backend slashes for symlinks on Windows systems (cpsitgmbh) + * bug #17626 Try to delete broken symlinks (IchHabRecht) + * bug #17978 [Yaml] ensure dump indentation to be greather than zero (xabbuh) + * bug #16886 [Form] [ChoiceType] Prefer placeholder to empty_value (boite) + * bug #17976 [WebProfilerBundle] fix debug toolbar rendering by removing inadvertently added links (craue) + * bug #17971 Variadic controller params (NiR-, fabpot) + * bug #17876 [DependencyInjection] Fixing autowiring bug when some args are set (weaverryan) + * bug #17568 Improved Bootstrap form theme for hidden fields (javiereguiluz) + * bug #17561 [WebProfilerBundle] Fix design issue in profiler when having errors in forms (Pierstoval) + * bug #17925 [Bridge] The WebProcessor now forwards the client IP (magnetik) + +* 2.8.3 (2016-02-28) + + * bug #17947 Fix - #17676 (backport #17919 to 2.3) (Ocramius) + * bug #17942 Fix bug when using an private aliased factory service (WouterJ) + * bug #17798 [Form] Fix BC break by allowing 'choice_label' option to be 'false' in ChoiceType (HeahDude) + * bug #17542 ChoiceFormField of type "select" could be "disabled" (bouland) + * bug #17602 [HttpFoundation] Fix BinaryFileResponse incorrect behavior with if-range header (bburnichon) + * bug #17760 [Form] fix choice value "false" in ChoiceType (HeahDude) + * bug #17914 [Console] Fix escaping of trailing backslashes (nicolas-grekas) + * bug #17074 Fix constraint validator alias being required (Triiistan) + * bug #17866 [DependencyInjection] replace alias in factories (xabbuh) + * bug #17867 [DependencyInjection] replace alias in factory services (xabbuh) + * bug #17860 Fixed the antialiasing of the toolbar text (javiereguiluz) + * bug #17569 [FrameworkBundle] read commands from bundles when accessing list (havvg) + * bug #16987 [FileSystem] Windows fix (flip111) + * bug #17787 [Form] Fix choice placeholder edge cases (Tobion) + * bug #17835 [Yaml] fix default timezone to be UTC (xabbuh) + * bug #17823 [DependencyInjection] fix dumped YAML string (xabbuh) + * bug #17818 [Console] InvalidArgumentException is thrown under wrong condition (robinkanters) + * bug #17819 [HttpKernel] Prevent a fatal error when DebugHandlersListener is used with a kernel with no terminateWithException() method (jakzal) + * bug #17814 [DependencyInjection] fix dumped YAML snytax (xabbuh) + * bug #17099 [Form] Fixed violation mapping if multiple forms are using the same (or part of the same) property path (alekitto) + * bug #17694 [DoctrineBridge] [Form] fix choice_value in EntityType (HeahDude) + * bug #17790 [Config] Fix EnumNodeDefinition to allow building enum nodes with one element (ogizanagi) + * bug #17729 [Yaml] properly parse lists in object maps (xabbuh) + * bug #17719 [DependencyInjection] fixed exceptions thrown by get method of ContainerBuilder (lukaszmakuch) + * bug #17742 [DependencyInjection] Fix #16461 Container::set() replace aliases (mnapoli) + * bug #17745 Added more exceptions to singularify method (javiereguiluz) + * bug #17691 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17766 Fixed (string) catchable fatal error for PHP Incomplete Class instances (yceruto) + * bug #17757 [HttpFoundation] BinaryFileResponse sendContent return as parent. (2.3) (SpacePossum) + * bug #17748 [DomCrawler] Remove the overridden getHash() method to prevent problems when cloning the crawler (jakzal) + * bug #17725 [WebProfilerBundle] Add width attribute on SVG - Fix toolbar profiler on microsoft edge (AlexandrePavy) + * bug #17703 [FrameworkBundle] Support autowiring for TranslationInterface (dunglas) + * bug #17613 [WebProfiler] Fixed logo and menu profiler for Microsoft Edge (WhiteEagle88) + * bug #17702 [TwigBridge] forward compatibility with Yaml 3.1 (xabbuh) + * bug #17673 [Routing] add files used in FileResource objects (xabbuh) + * bug #17672 [DependencyInjection][Routing] add files used in FileResource objects (xabbuh) + * bug #17669 [Console] remove readline support (xabbuh) + * bug #17600 Fixed the Bootstrap form theme for inlined checkbox/radio (javiereguiluz) + * bug #17596 [Translation] Add resources from fallback locale to parent catalogue (c960657) + * bug #17605 [FrameworkBundle] remove default null value for asset version (xabbuh) + * bug #17606 [DependencyInjection] pass triggerDeprecationError arg to parent class (xabbuh) + * bug #16956 [DependencyInjection] XmlFileLoader: enforce tags to have a name (xabbuh) + * bug #16265 [BrowserKit] Corrected HTTP_HOST logic (Naktibalda) + * bug #17559 [SecurityBundle] Fix HTTP Digest auth not being passed user checker (SamFleming) + * bug #17554 [DependencyInjection] resolve aliases in factories (xabbuh) + * bug #17555 [DependencyInjection] resolve aliases in factory services (xabbuh) + * bug #17511 [Form] ArrayChoiceList can now deal with a null in choices (issei-m) + * bug #17430 [Serializer] Ensure that groups are strings (dunglas) + * bug #15272 [FrameworkBundle] Fix template location for PHP templates (jakzal) + * bug #11232 [Routing] Fixes fatal errors with object resources in AnnotationDirectoryLoader::supports (Tischoi) + * bug #17526 Escape the delimiter in Glob::toRegex (javiereguiluz) + * bug #17527 fixed undefined variable (fabpot) + * bug #15706 [framework-bundle] Added support for the `0.0.0.0/0` trusted proxy (zerkms) + * bug #16274 [HttpKernel] Lookup the response even if the lock was released after two second wait (jakzal) + * bug #16954 [TranslationUpdateCommand] fixed undefined resultMessage var. (aitboudad) + * bug #17355 [DoctrineBridge][Validator] >= 2.3 Pass association instead of ID as argument (xavismeh) + * bug #17330 Limit the max height/width of icons in the profiler menu (javiereguiluz) + * bug #17454 Allow absolute URLs to be displayed in the debug toolbar (javiereguiluz) + * bug #16736 [Request] Ignore invalid IP addresses sent by proxies (GromNaN) + * bug #17459 [EventDispatcher] TraceableEventDispatcher resets event listener priorities (c960657) + * bug #17486 [FrameworkBundle] Throw for missing container extensions (kix) + * bug #16961 Overriding profiler position in CSS breaks JS positioning (aschempp) + * bug #16873 Able to load big xml files with DomCrawler (zorn-v) + * bug #16897 [Form] Fix constraints could be null if not set (DZunke) + * bug #16912 [Translation][Writer] avoid calling setBackup if the dumper is not FileDumper (aitboudad) + * bug #17505 sort bundles in config:dump-reference command (xabbuh) + * bug #17514 [Asset] Add defaultNull to version configuration (ewgRa) + * bug #16511 [Asset] Ability to set empty version strategy in packages (ewgRa) + * bug #17457 Display Ajax requests from newest to oldest in the toolbar (javiereguiluz) + * bug #17503 [Asset] CLI: use request context to generate absolute URLs (xabbuh) + * bug #17478 [HttpFoundation] Do not overwrite the Authorization header if it is already set (jakzal) + * bug #17461 [Yaml] tag for dumped PHP objects must be a local one (xabbuh) + * bug #16822 [FrameworkBundle][Validator] Fix apc cache service deprecation (ogizanagi) + * bug #17463 [Form] make tests compatible with Symfony 2.8 and 3.0 (xabbuh) + * bug #17456 [DX] Remove default match from AbstractConfigCommand::findExtension (kix) + * bug #17424 [Process] Update in 2.7 for stream-based output storage (romainneutron) + * bug #17417 Fixed the form profiler when using long form types (javiereguiluz) + * bug #17423 [Process] Use stream based storage to avoid memory issues (romainneutron) + * bug #17406 [Form] ChoiceType: Fix a notice when 'choices' normalizer is replaced (paradajozsef) + * bug #17433 [FrameworkBundle] Don't log twice with the error handler (nicolas-grekas) + * bug #17418 Fixed Bootstrap form theme form "reset" buttons (javiereguiluz) + * bug #17416 [PropertyInfo] PhpDocExtractor: Fix a notice when the property doesn'… (dunglas) + * bug #17404 fix merge 2.3 into 2.7 for SecureRandom dependency (Tobion) + * bug #17373 [SecurityBundle] fix SecureRandom service constructor args (Tobion) + * bug #17382 [TwigBridge] Use label_format option for checkbox and radio labels (enumag) + * bug #17380 [TwigBridge] Use label_format option for checkbox and radio labels (enumag) + * bug #17377 Fix performance (PHP5) and memory (PHP7) issues when using token_get_all (nicolas-grekas, peteward) + * bug #17389 [Routing] Fixed correct class name in thrown exception (fixes #17388) (robinvdvleuten) + * bug #17358 [ClassLoader] Use symfony/polyfill-apcu (nicolas-grekas) + * bug #17370 [HttpFoundation][Cookie] Cookie DateTimeInterface fix (wildewouter) + +* 2.8.2 (2016-01-14) + + * security #17359 do not ship with a custom rng implementation (xabbuh, fabpot) + * bug #17253 [Console] HHVM read input stream bug (mbutkereit) + * bug #17314 Fix max width for multibyte keys in choice question (mheki) + * bug #17326 [Console] Display console application name even when no version set (polc) + * bug #17328 [Serializer] Allow to use proxies in object_to_populate (dunglas) + * bug #17202 [FrameworkBundle] Don't log twice with the error handler (nicolas-grekas) + * bug #17347 Workaround https://bugs.php.net/63206 (nicolas-grekas) + * bug #17199 [Serializer] Allow context to contain not serializable data (dunglas, nicolas-grekas) + * bug #17334 [WebProfiler] Fixed sf-minitoolbar height (yceruto) + * bug #17140 [Serializer] Remove normalizer cache in Serializer class (jvasseur) + * bug #17320 [Debug] Fixed erroneous deprecation notice for extended Interfaces (peterrehm) + * bug #17307 [FrameworkBundle] Fix paths with % in it (like urlencoded) (scaytrase) + * bug #17078 [Bridge] [Doctrine] [Validator] Added support \IteratorAggregate for UniqueEntityValidator (Disparity) + * bug #17298 [FrameworkBundle] Use proper class to fetch $versionStrategy property (dosten) + * bug #17287 [HttpKernel] Forcing string comparison on query parameters sort in UriSigner (Tim van Densen) + * bug #17279 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17278 [FrameworkBundle] Add case in Kernel directory guess for PHPUnit (tgalopin) + * bug #17283 [WebProfilerBundle] Remove loading status from AJAX toolbar after error (kucharovic) + * bug #17275 [PhpUnitBridge] Re-enable the garbage collector (nicolas-grekas) + * bug #17276 [Process] Fix potential race condition (nicolas-grekas) + * bug #17261 [FrameworkBundle] Allow to autowire service_container (dunglas) + * bug #17183 [FrameworkBundle] Set the kernel.name properly after a cache warmup (jakzal) + * bug #17197 [Yaml] cast arrays to objects after parsing has finished (xabbuh) + * bug #17247 Fix toolbar display when nvd3 is loaded on page (Seldaek) + * bug #17159 [Yaml] recognize when a block scalar is left (xabbuh) + * bug #17195 bug #14246 [Filesystem] dumpFile() non atomic (Hidde Boomsma) + * feature #16747 [Form] Improved performance of ChoiceType and its subtypes (webmozart) + * bug #17179 [WebProfiler] Removed an object as route generator argument (iltar) + * bug #17177 [Process] Fix potential race condition leading to transient tests (nicolas-grekas) + * bug #17163 [Form] fix Catchable Fatal Error if choices is not an array (Gladhon, nicolas-grekas) + * bug #17152 [DoctrineBridge] [PropertyInfo] Catch Doctrine\ORM\Mapping\MappingException (dunglas) + * bug #17119 [Form] improve deprecation message for "empty_value" and "choice_list" options. (hhamon) + * bug #17156 [HttpFoundation] add missing symfony/polyfill-php55 dependency (xabbuh) + * bug #17162 [Form] Fix regression on Collection type (hason) + +* 2.8.1 (2015-12-26) + + * bug #16864 [Yaml] fix indented line handling in folded blocks (xabbuh) + * bug #17052 Fixed flatten exception recursion with errors (GrahamCampbell) + * bug #16826 Embedded identifier support (mihai-stancu) + * bug #17079 Also transform inline mappings to objects (WouterJ) + * bug #17129 [Config] Fix array sort on normalization in edge case (romainneutron) + * feature #17035 [DomCrawler] Revert previous restriction, allow selection of every DOMNode object (EdgarPE) + * bug #17094 [Process] More robustness and deterministic tests (nicolas-grekas) + * bug #17112 [PropertyAccess] Reorder elements array after PropertyPathBuilder::replace (alekitto) + * bug #17109 Improved the design of the web debug toolbar (javiereguiluz) + * bug #16797 [Filesystem] Recursivly widen non-executable directories (Slamdunk) + * bug #16926 [DependencyInjection] fixed definition loosing property shared when decorated by a parent definition (wahler) + * bug #17040 [Console] Avoid extra blank lines when rendering exceptions (ogizanagi) + * bug #17044 [Form] fix BC break introduced with prototype_data option (memphys) + * bug #17055 [Security] Verify if a password encoded with bcrypt is no longer than 72 characters (jakzal) + * bug #16959 [Form] fix #15544 when a collection type attribute "required" is false, "prototype" should too (HeahDude) + * bug #16806 [Validator] BicValidator - fixed raising violations to a maximum of one (mvhirsch) + * bug #16842 [Ldap] Escape carriage returns in LDAP DNs. (ChadSikorra) + * bug #16860 [Yaml] do not remove "comments" in scalar blocks (xabbuh) + * bug #17002 [Console][Table] fixed render row that contains multiple cells. (aitboudad) + * bug #16964 CSS min-height and min-width should not be "auto" (aschempp) + * bug #16971 [HttpFoundation] Added the ability of using BinaryFileResponse with stream wrappers (jakzal, Sander-Toonen) + * bug #17048 Fix the logout path when not using the router (stof) + * bug #17049 Fix the logout path when not using the router (stof) + * bug #17057 [FrameworkBundle][HttpKernel] the finder is required to discover bundle commands (xabbuh) + * bug #17059 [HttpFoundation] fix error level for deprecation (xabbuh) + * bug #17006 [Form] Fix casting regression in DoctrineChoiceLoader (bendavies) + * bug #16911 [PropertyInfo] Update List Information from ReflectionExtractor (zanderbaldwin) + * bug #16955 [FrameworkBundle] ContainerDebugCommand: pass the right object to the descriptors (xabbuh) + * feature #16760 Show silenced errors in separate tab (peterrehm) + * feature #16937 [PhpUnitBridge] Replace "weak-verbose" by "deprecations upper bound" mode (nicolas-grekas) + * bug #16915 [Process] Enhance compatiblity with --enable-sigchild (nicolas-grekas) + * bug #16829 [FrameworkBundle] prevent cache:clear creating too long paths (Tobion) + * bug #16922 [FrameworkBundle] [Bug] Fixes new InputStyle bug #16920 (AlmogBaku) + * bug #16921 Fix short array syntax for php 5.3 (ewgRa) + * bug #16450 [Serializer] Fixed `array_unique` on array of objects in `getAllowedAttributes`. (CornyPhoenix) + * bug #16757 [FrameworkBundle] [Translation] Fixed translations not written when no translations directory in update command (jeremyFreeAgent) + * bug #16902 [Security] Fix a Polyfill import statement in StringUtils (magnetik) + * bug #16871 [FrameworkBundle] Disable built-in server commands when Process component is missing (gnugat, xabbuh) + * bug #16870 [FrameworkBundle] Disable the server:run command when Process component is missing (gnugat, xabbuh) + * feature #16789 [PhpUnitBridge] Add weak-verbose mode and match against message instead of test name (nicolas-grekas) + * bug #16796 [Form] Fix choices defined as Traversable (nicolas-grekas) + * bug #16742 [Console][ProgressBar] redrawFrequency should never be 0 (dritter) + * bug #16846 [MonologBridge] Monolog Bridge 2.8 is incompatible with HttpKernel 3.0 (derrabus) + * bug #16799 Improve error message for undefined DIC aliases (mpdude) + * bug #16825 [VarDumper] fix .sf-dump z-index (debug bar conflict) (Antoine LA) + * bug #16772 Refactoring EntityUserProvider::__construct() to not do work, cause cache warm error (weaverryan) + +* 2.8.0 (2015-11-30) + + * bug #16758 Fix BC for the default root form name (stof) + * bug #16753 [Process] Fix signaling/stopping logic on Windows (nicolas-grekas) + * feature #16755 [Security] add subject variable to expression context (xabbuh) + * bug #16642 [DI][autowiring] throw exception when many services use the same class. (aitboudad) + * bug #16745 [Yaml] look for colon in parsed inline string (xabbuh) + * bug #16733 [Console] do not encode backslashes in console default description (Tobion) + * feature #16735 [WIP] [Ldap] Marked the Ldap component as internal (csarrazi) + * bug #16734 Make sure security.role_hierarchy.roles always exists (WouterJ) + * feature #16722 [Security][SecurityBundle] Use csrf_token_id instead of deprecated intention (jakzal) + * bug #16312 [HttpKernel] clearstatcache() so the Cache sees when a .lck file has been released (mpdude) + * bug #16351 [WIP] [Form] [TwigBridge] Bootstrap horizontal theme missing tests (pieter2627) + * bug #16685 [Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false (webmozart) + * feature #16709 [Bridge\PhpUnit] Display the stack trace of a deprecation on-demand (nicolas-grekas) + * bug #16704 [Form+SecurityBundle] Trigger deprecation for csrf_provider+intention options (nicolas-grekas) + * feature #16706 [HttpFoundation] Deprecate $deep parameter on ParameterBag (nicolas-grekas) + * bug #16705 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * feature #16690 [Form] Deprecated ArrayKeyChoiceList (webmozart) + * feature #16687 [Form] Deprecated TimezoneType::getTimezones() (webmozart) + * bug #16681 [Form] Deprecated setting "choices_as_values" to "false" (webmozart) + * bug #16695 [SecurityBundle] disable the init:acl command if ACL is not used (Tobion) + * bug #16677 [Form] Fixed wrong usages of the "text" type (webmozart) + * bug #16679 [Form] Disabled view data validation if "data_class" is set to null (webmozart) + * bug #16621 [Console] Fix bug with $output overloading (WouterJ) + * feature #16601 [Security] Deprecate "AbstractVoter" in favor of "Voter" (nicolas-grekas, lyrixx) + * bug #16676 [HttpFoundation] Workaround HHVM rewriting HTTP response line (nicolas-grekas) + * bug #16668 [ClassLoader] Fix parsing namespace when token_get_all() is missing (nicolas-grekas) + * bug #16386 Bug #16343 [Router] Too many Routes ? (jelte) + * bug #16498 fix unused variable warning (eventhorizonpl) + * feature #16031 [Translation][Form] Do not translate form labels and placeholders when 'translation_domain' is false (Restless-ET) + * bug #16651 [Debug] Ensure class declarations are loaded only once (nicolas-grekas) + * security #16631 CVE-2015-8124: Session Fixation in the "Remember Me" Login Feature (xabbuh) + * security #16630 CVE-2015-8125: Potential Remote Timing Attack Vulnerability in Security Remember-Me Service (xabbuh) + * bug #16633 [Filesystem] Fixed failing test due to tempdir symlink (toretto460) + * bug #16609 [HttpKernel] Don't reset on shutdown but in FrameworkBundle/Test/KernelTestCase (nicolas-grekas) + * bug #16477 [Routing] Changing RouteCollectionBuilder::import() behavior to add to the builder (weaverryan) + * bug #16588 Sent out a status text for unknown HTTP headers. (dawehner) + * bug #16295 [DependencyInjection] Unescape parameters for all types of injection (Nicofuma) + * bug #16377 [WebProfilerBundle] Fix minitoolbar height (rvanlaak) + * bug #16585 Add support for HTTP status code 418 back (dawehner) + * bug #16574 [Process] Fix PhpProcess with phpdbg runtime (nicolas-grekas) + * bug #16581 Fix call to undefined function json_last_error_message (dawehner) + * bug #16573 [FrameworkBundle] Fix PropertyInfo extractor namespace in framework bundle (jvasseur) + * bug #16578 [Console] Fix bug in windows detection (kbond) + * bug #16546 [Serializer] ObjectNormalizer: don't serialize static methods and props (dunglas) + * bug #16352 Fix the server variables in the router_*.php files (leofeyer) + * bug #16537 [Validator] Allow an empty path with a non empty fragment or a query (jakzal) + * bug #16528 [Translation] Add support for Armenian pluralization. (marcosdsanchez) + * bug #16510 [Process] fix Proccess run with pts enabled (ewgRa) + +* 2.8.0-BETA1 (2015-11-16) + + * feature #16156 [Filesystem] Changed dumpFile to allow dumping to streams (markchalloner, pierredup) + * feature #16502 [Bridge\PhpUnit] Add extra clock-mocked namespaces in phpunit.xml.dist (nicolas-grekas) + * feature #16464 [DependencyInjection] Fix some edge cases with autowiring (dunglas) + * feature #16433 [Yaml] deprecate unquoted indicator characters (xabbuh) + * feature #16419 [FrameworkBundle][Form] Better exception message for private form tagged services (ogizanagi) + * feature #15990 added a micro kernel (fabpot) + * feature #16459 [Security\Core] Deprecate passing $salt to BCryptPasswordEncoder::encodePassword() (nicolas-grekas) + * feature #16409 [Console] Add progress indicator helper (kbond) + * feature #16423 [VarDumper] Deprecate VarDumperTestCase in favor of the trait (nicolas-grekas) + * feature #16424 [DI] Deprecate ContainerAware in favor of ContainerAwareTrait (nicolas-grekas) + * feature #16430 [HttpKernel] PostResponseEvent should extend the KernelEvent (jakzal) + * feature #16325 [VarDumper] Casters for Generator, ReflectionGenerator and ReflectionType (nicolas-grekas) + * feature #16395 checkCredentials() force it to be an affirmative yes! (weaverryan) + * feature #16344 [WebProfilerBundle] Filter links in search results (Rvanlaak) + * feature #16285 [Yaml] deprecated usage of @ and ` at the beginning of an unquoted string (fabpot) + * feature #16317 Rely on iconv and symfony/polyfill-* (nicolas-grekas) + * feature #15966 [FrameworkBundle] PropertyInfo support (dunglas) + * feature #16161 [Validator] Add expressionLanguage to ExpressionValidator constructor (enumag) + * feature #16263 [FrameworkBundle] Add a new ClassCache cache warmer (tucksaun) + * feature #16271 [TwigBundle] added a Twig templates warmer when templating is disabled (fabpot) + * feature #16276 Unify URL generator reference type + make linking in php templates consistent with twig (Tobion) + * feature #15947 Added UserLoaderInterface for loading users through Doctrine. (mtrojanowski) + * feature #16194 [PhpUnit] Mock clock on @group time-sensitive annotations (nicolas-grekas) + * feature #16201 [Yaml] deprecated non-escaped \ in double-quoted strings when parsing (fabpot) + * feature #16198 [EventDispatcher] added EventDispatcher::getListenerPriority() (fabpot) + * feature #15025 [2.8] [Form] Rename CollectionType options for entries (WouterJ) + * feature #16189 [PhpUnitBridge] Add SkippedTestsListener to collect and replay skipped tests (nicolas-grekas) + * feature #15879 Deprecate the SecureRandom class (pierredup) + * feature #16001 [DI] Warn when a definition relies on a deprecated class in ContainerBuilder::createService() (nicolas-grekas) + * feature #14044 [Console] [Helper] [Table] Columns styles (MAXakaWIZARD) + * feature #14908 Include working directory in ProcessFailedException (Rvanlaak) + * feature #16102 Simplify AbstractVoter (Koc) + * feature #15613 [DependencyInjection] Add autowiring capabilities (dunglas) + * feature #14721 [Security] Configuring a user checker per firewall (iltar) + * feature #16069 [WebProfilerBundle] Move AjaxCollector to HttpKernel for use with Silex (glaubinix, fabpot) + * feature #16063 [VarDumper] Add $this->getDump($var) when using VarDumperTestTrait (nicolas-grekas) + * feature #16058 Prevent adding non-DOMElement elements in DomCrawler (stof) + * feature #16057 Deprecate loading multiple documents in the same crawler (stof) + * feature #15742 Using a service as a router resource (weaverryan) + * feature #15778 Fluid interface for building routes in PHP (weaverryan) + * feature #16029 [FrameworkBundle][TwigBridge] do not render empty form action attributes (xabbuh) + * feature #15938 [Console] Bind input before executing the COMMAND event (WouterJ) + * feature #15503 UI & CSS improvement to new toolbar (WouterJ) + * feature #15838 [VarDumper] Dump PHP+Twig code excerpts in backtraces (nicolas-grekas) + * feature #16011 [FrameworkBundle] Tag deprecated services (nicolas-grekas) + * feature #15944 Remove profiler storages (javiereguiluz) + * feature #16007 [HttpFoundation] deprecate finding deep items in request parameters (xabbuh) + * feature #15978 Updated the styles of the cache commands (javiereguiluz) + * feature #15972 [Console] Updated the styles of the server commands (javiereguiluz) + * feature #15964 Symfony Console Style tweaks (javiereguiluz) + * feature #15919 [Form] Guess currency field based on validator constraint (enumag) + * feature #15934 Add a non-static API for the CssSelector component (stof) + * feature #14235 [FrameworkBundle] Refactored assets:install command and apply Symfony styles (1ed) + * feature #15963 added logging of unused tags (Marmelatze, fabpot) + * feature #15970 [TwigBundle] removed usage of Templating classes (fabpot) + * feature #14132 Applied the new styles to the router: commands (javiereguiluz) + * feature #15356 [WebProfilerBundle] Profiler View Latest should preserve all the current query parameters (jbafford) + * feature #15953 [TwigBridge] is_granted no longer raise an exception if the token storage is empty (lyrixx) + * feature #14602 [2.8] [Ldap] Added support for LDAP (New Component + integration in the Security Component). (csarrazi, lyrixx) + * feature #15939 Removed the "Delete profiles" action from the web profiler sidebar (javiereguiluz) + * feature #15962 [Finder] simplified code (fabpot) + * feature #15882 Easier Custom Authentication errors (weaverryan) + * feature #15907 [DomCrawler] Deprecate methods inherited from SplObjectStorage (stof) + * feature #15301 [Form][Type Date/Time] added choice_translation_domain option. (aitboudad) + * feature #15697 [BrowserKit] Added isFollowingRedirects and getMaxRedirects methods (Naktibalda) + * feature #15719 Deprecate ResourceInterface::getResource() (mpdude) + * feature #15818 [WebProfilerBundle] Add collapsed sidebar on small screens (hason) + * feature #15858 [PropertyInfo] Import the component (dunglas) + * feature #15892 deprecated the Shell Console class (fabpot) + * feature #15519 [Validator] added BIC (SWIFT-BIC) validation constraint (mvhirsch) + * feature #12587 [TwigBridge] Foundation form layout integration (totophe) + * feature #15151 [Security] Deprecated supportsAttribute and supportsClass methods (WouterJ) + * feature #15491 Add support for deprecated definitions (Taluu) + * feature #14894 [Console] Add domain exceptions to replace generic exceptions (GromNaN) + * feature #15738 Implement service-based Resource (cache) validation (mpdude) + * feature #14673 New Guard Authentication System (e.g. putting the joy back into security) (weaverryan) + * feature #15870 Updating AbstractVoter so that the method receives the TokenInterface (weaverryan) + * feature #15786 [Translation][File dumper] allow get file content without writing in file. (aitboudad) + * feature #15805 [Finder] Deprecate adapters and related classes (nicolas-grekas) + * feature #15837 [VarDumper] Add EnumStub for dumping virtual collections with casters (nicolas-grekas) + * feature #15699 [Translator][FileDumper] deprecated format method in favor of formatCatalogue. (aitboudad) + * feature #15717 [Translator][Loader] added XLIFF 2.0 support. (xphere, aitboudad) + * feature #15743 Validate the extended type for lazy-loaded type extensions (stof) + * feature #13761 Automatically process extensions when they implement CompilerPassInterface (WouterJ) + * feature #15787 [VarDumper] Add caster for OuterIterator objects (nicolas-grekas) + * feature #13616 [HttpKernel] Add entry point to more easily create/configure the DI extension (egeloen) + * feature #14378 [DX] Added a logout link in the security panel of the web debug toolbar (javiereguiluz) + * feature #15620 [WIP] #15502 Make template shortcuts be usable without Templating component (Koc) + * feature #15523 Redesigned the Symfony Profiler (javiereguiluz) + * feature #15773 Make the exception output visible even in quiet mode, fixes #15680 (Seldaek) + * feature #15772 Convert Output::write's type to an options arg where verbosity can be passed in as well (Seldaek) + * feature #15756 [Translation] added option json_options to JsonFileDumper. (gepo) + * feature #15724 [HttpKernel] Move required RequestStack args as first arguments (nicolas-grekas) + * feature #15521 [Debug] Add BufferingLogger for errors that happen before a proper logger is configured (nicolas-grekas) + * feature #15709 [WebProfilerBundle] deprecated import/export commands (fabpot) + * feature #15710 added ExceptionHandler::getHtml() to expose the full HTML of an exception (fabpot) + * feature #15562 [translation] Deprecated DiffOperation (zerustech) + * feature #15635 [Config] Prototypes info (ogizanagi) + * feature #15551 [Translation] added element metadata to XliffFileDumper (aitboudad) + * feature #15555 [VarDumper] Add caster for pgsql resources (nicolas-grekas) + * feature #15452 [Translator] [Xliff] Add support for target attributes. (marcosdsanchez) + * feature #15416 [DependencyInjection] Added a way to define the priority of service decoration (dosten) + * feature #15433 Allow to define Enum nodes with 1 single element (javiereguiluz) + * feature #13990 [Form] Add flexibility for EntityType (raziel057) + * feature #15382 [Console] Use readline for user input when available #DX (michaelperrin) + * feature #15013 [Security] Removed security-acl from the core (iltar) + * feature #15079 [Form] Deprecated FormTypeInterface::getName() and passing of type instances (webmozart) + * feature #15418 [Debug] Deprecate ExceptionHandler::createResponse (nicolas-grekas) + * feature #15123 [2.8][FrameworkBundle] Allow parameter use_cookies in session configuration (derrabus) + * feature #14987 [FrameworkBundle] Configurable Serializer name converter (dunglas) + * feature #15285 [Config] deprecate cannotBeEmpty() for boolean and numeric nodes (xabbuh) + * feature #15372 [FrameworkBundle] Change the default value of cookie_httponly (jderusse) + * feature #15160 Redesigned the web debug toolbar (javiereguiluz) + * feature #15185 Implement resettable containers (stof) + * feature #15131 [Security] Moved Simple{Form,Pre}AuthenticatorInterfaces to Security\Http (WouterJ) + * feature #15290 [DependencyInjection] Forbid container cloning (jakzal) + * feature #14264 [WebProfilerBundle] Add link to show profile of latest request (xelaris) + * feature #15139 [Translation] Add parameters to DataCollectorTranslator (damienalexandre) + * feature #15175 [VarDumper] Ingore PHPUnit and Prophecy object when they are nested (lyrixx) + * feature #15141 [DX] [Security] Renamed Token#getKey() to getSecret() (WouterJ) + * feature #15154 [Validator] Added missing error codes and turned codes into UUIDs (webmozart) + * feature #15096 [DependencyInjection] Allow anonymous DefinitionDecorator resolving (nicolas-grekas) + * feature #14764 [TwigBundle] Warmup twig templates in non-standard paths (kbond) + * feature #15134 [FrameworkBundle] add option to force web server startup (xabbuh) + * feature #14238 [config] added remove option to ignoreExtraKeys (twifty) + * feature #15076 [Debug] Allow throwing from __toString() with `return trigger_error($e, E_USER_ERROR);` (nicolas-grekas) + * feature #14984 [DependencyInjection] Deprecate scope concept (dosten) + * feature #14429 [FrameworkBundle] Add a doctrine cache service definition for validator mapping (jakzal) + * feature #14991 [Console][Table] allow multiple render() calls. (jaytaph) + * feature #14660 [Form] moved data trimming logic of TrimListener into StringUtil (issei-m) + * feature #15019 [Form] Deprecated "cascade_validation" (webmozart) + * feature #12314 [Form] Add "prototype_data" option to collection type (kgilden) + * feature #12067 [Form] Added the 'range' FormType (jaytaph) + * feature #14993 [Profiler][Translation] added filter. (aitboudad) + * feature #14912 [HttpFoundation] Postpone setting the date header on a Response (jakzal) + * feature #14903 [profiler][request toolbar] Removed route name from the toolbar (MJBGO) + * feature #14904 [toolbar] Merged colored icons in toolbar (MJBGO) + * feature #14781 [TwigBundle] Reconfigure twig paths when they are updated (chbruyand) + * feature #14700 [DependencyInjection] [Routing] [Config] Recursive directory loading (lavoiesl, nicolas-grekas) + * feature #14733 [Security] Add setVoters() on AccessDecisionManager (nicolas-grekas) + * feature #14756 [Serializer] Support for array denormalization (derrabus) + * feature #14630 [Translator] Dump translation constants as tree instead of simple list (gepo) + * feature #14403 [Form] deprecate read_only option (Tobion) + * feature #13324 [WebProfilerBundle] Improved page for logs (hason) + * feature #14561 [FrameworkBundle][DX] Add option to specify additional translation loading paths (Seldaek) + * feature #14563 [FrameworkBundle][EventDispatcher] Add priorities to the debug:event-dispatcher command (Seldaek) + * feature #14546 [Translator] deprecate getMessages in favor of getCatalogue. (aitboudad) + * feature #14320 [Translation] added an --all option to the debug:translation command #14237 (sgehrig) + * feature #14473 [DX] Minor improvement for the translation:debug output (nicolasdewez) + * feature #14443 [VarDumper] Allow preserving a subset of cut arrays (nicolas-grekas) + * feature #14431 [Console] Bind the closure (code) to the Command if possible (lyrixx) + * feature #14424 [VarDumper] Added support for SplFileInfo (lyrixx) + * feature #14359 [Translation] added FileLoader. (aitboudad) + * feature #14383 [FrameworkBundle][Server Command] add address port number option. (aitboudad) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..d211dd419d064 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +Code of Conduct +=============== + +Our Pledge +---------- + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnic origin, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +religion, or sexual identity and orientation. + +Our Standards +------------- + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Our Responsibilities +-------------------- + +[CoC Active Response Ensurers, or CARE][1], are responsible for clarifying the +standards of acceptable behavior and are expected to take appropriate and fair +corrective action in response to any instances of unacceptable behavior. + +CARE team members have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +Scope +----- + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by CARE team members. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior +[may be reported][2] by contacting the [CARE team members][1]. +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. The CARE team is +obligated to maintain confidentiality with regard to the reporter of an +incident. Further details of specific enforcement policies may be posted +separately. + +CARE team members who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by the +[core team][3]. + +Attribution +----------- + +This Code of Conduct is adapted from the [Contributor Covenant version 1.4][4]. + +[1]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html +[2]: https://symfony.com/doc/current/contributing/code_of_conduct/reporting_guidelines.html +[3]: https://symfony.com/doc/current/contributing/code/core_team.html +[4]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 123d5aa755911..7902d9aff3a77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,29 +5,22 @@ Symfony is an open source, community-driven project. If you'd like to contribute, please read the following documents: -* [Contributing Code][1]: The document index related to contributions; - -* [Submitting a Patch][2]: Guidelines for submitting a pull request; - -* [Pull Request Template][3]: Template header to use in your pull request - description; - -```markdown -| Q | A -| ------------- | --- -| Bug fix? | yes/no -| New feature? | yes/no -| BC breaks? | no -| Deprecations? | no -| Tests pass? | yes -| Fixed tickets | #1234 -| License | MIT -| Doc PR | symfony/symfony-docs#1234 -``` - -* [Backwards Compatibility][4]: Backward compatibility rules. - -[1]: https://symfony.com/doc/current/contributing/code/index.html -[2]: https://symfony.com/doc/current/contributing/code/patches.html#check-list -[3]: https://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request -[4]: https://symfony.com/doc/current/contributing/code/bc.html#working-on-symfony-code +* [Reviewing issues/pull requests][0] +* [Reporting a Bug][1] +* [Submitting a Patch][2] +* [Symfony Core Team][3] +* [Security Issues][4] +* [Running Symfony Tests][5] +* [Our Backwards Compatibility Promise][6] +* [Coding Standards][7] +* [Conventions][8] + +[0]: https://symfony.com/doc/current/contributing/community/reviews.html +[1]: https://symfony.com/doc/current/contributing/code/bugs.html +[2]: https://symfony.com/doc/current/contributing/code/patches.html +[3]: https://symfony.com/doc/current/contributing/code/core_team.html +[4]: https://symfony.com/doc/current/contributing/code/security.html +[5]: https://symfony.com/doc/current/contributing/code/tests.html +[6]: https://symfony.com/doc/current/contributing/code/bc.html +[7]: https://symfony.com/doc/current/contributing/code/standards.html +[8]: https://symfony.com/doc/current/contributing/code/conventions.html diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b78fd1ff5ee94..1c329f2146c3e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,247 +5,360 @@ Symfony is the result of the work of many people who made the code better (see https://symfony.com/contributors for more information): - Fabien Potencier (fabpot) - - Bernhard Schussek (bschussek) - Nicolas Grekas (nicolas-grekas) + - Bernhard Schussek (bschussek) + - Christian Flothmann (xabbuh) - Tobias Schultze (tobion) - - Victor Berchet (victor) - - Jordi Boggiano (seldaek) - Christophe Coevoet (stof) + - Jordi Boggiano (seldaek) + - Victor Berchet (victor) + - Robin Chalas (chalas_r) + - Kévin Dunglas (dunglas) + - Maxime Steinhausser (ogizanagi) + - Jakub Zalas (jakubzalas) - Johannes S (johannes) - Kris Wallsmith (kriswallsmith) - - Jakub Zalas (jakubzalas) - - Christian Flothmann (xabbuh) - Ryan Weaver (weaverryan) - - Pascal Borreli (pborreli) + - Javier Eguiluz (javier.eguiluz) + - Grégoire Pineau (lyrixx) - Hugo Hamon (hhamon) + - Roland Franssen (ro0) + - Abdellatif Ait boudad (aitboudad) + - Romain Neutron (romain) + - Pascal Borreli (pborreli) + - Wouter De Jong (wouterj) - Joseph Bielawski (stloyd) - Karma Dordrak (drak) + - Samuel ROZE (sroze) - Lukas Kahwe Smith (lsmith) - - Romain Neutron (romain) - - Abdellatif Ait boudad (aitboudad) + - Martin Hasoň (hason) - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - - Martin Hasoň (hason) - - Wouter De Jong (wouterj) + - Jules Pietri (heah) + - Yonel Ceruto (yonelceruto) - Eriksen Costa (eriksencosta) - - Grégoire Pineau (lyrixx) - - Javier Eguiluz (javier.eguiluz) - - Kévin Dunglas (dunglas) + - Guilhem Niot (energetick) + - Sarah Khalil (saro0h) - Jonathan Wage (jwage) + - Hamza Amrouche (simperfit) + - Tobias Nyholm (tobias) + - Diego Saint Esteben (dosten) + - Iltar van der Berg (kjarli) - Alexandre Salomé (alexandresalome) - William Durand (couac) - ornicar + - Francis Besset (francisbesset) + - Dany Maillard (maidmaid) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - Bulat Shakirzyanov (avalanche123) - - Francis Besset (francisbesset) + - Matthias Pigulla (mpdude) + - Peter Rehm (rpet) - Saša Stamenković (umpirsky) + - Pierre du Plessis (pierredup) + - Kevin Bond (kbond) - Henrik Bjørnskov (henrikbjorn) - Miha Vrhovnik + - Jérémy DERUSSÉ (jderusse) - Diego Saint Esteben (dii3g0) - - Sarah Khalil (saro0h) + - Alexander M. Turek (derrabus) - Konstantin Kudryashov (everzet) - Bilal Amarni (bamarni) - Florin Patan (florinpatan) - - Eric Clemmons (ericclemmons) + - Gábor Egyed (1ed) + - Mathieu Piot (mpiot) + - Titouan Galopin (tgalopin) + - Vladimir Reznichenko (kalessil) + - Michel Weimerskirch (mweimerskirch) - Andrej Hudec (pulzarraider) + - Konstantin Myakshin (koc) + - Eric Clemmons (ericclemmons) + - Jáchym Toušek (enumag) + - Charles Sarrazin (csarrazi) + - David Maicher (dmaicher) + - Christian Raue + - Issei Murasawa (issei_m) + - Arnout Boks (aboks) - Deni - - Maxime Steinhausser (ogizanagi) - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Gábor Egyed (1ed) - - Christian Raue - - Arnout Boks (aboks) - - Michel Weimerskirch (mweimerskirch) - - Kevin Bond (kbond) + - Douglas Greenshields (shieldo) + - Dariusz Ruminski + - Grégoire Paris (greg0ire) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) - - Douglas Greenshields (shieldo) + - Graham Campbell (graham) - Daniel Holmes (dholmes) + - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) + - Jérôme Tamarelle (gromnan) - John Wards (johnwards) - Fran Moreno (franmomu) + - Valentin Udaltsov (vudaltsov) - Antoine Hérault (herzult) - - Toni Uebernickel (havvg) - - Matthias Pigulla (mpdude) + - Paráda József (paradajozsef) - Arnaud Le Blanc (arnaud-lb) + - Maxime STEINHAUSSER + - Michal Piotrowski (eventhorizon) + - gadelat (gadelat) - Tim Nagel (merk) - Brice BERNARD (brikou) - - Jérôme Tamarelle (gromnan) + - Baptiste Clavié (talus) - marc.weistroff + - David Buchmann (dbu) - lenar - - Graham Campbell (graham) + - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) + - Tomáš Votruba (tomas_votruba) + - Peter Kokot (maastermedia) + - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Colin Frei - Adrien Brault (adrienbrault) + - Joshua Thijssen - excelwebzone - - Jacob Dreesen (jdreesen) - - Fabien Pennequin (fabienpennequin) - - Peter Rehm (rpet) - - Peter Kokot (maastermedia) - - Alexander Schwenn (xelaris) - Gordon Franke (gimler) + - Fabien Pennequin (fabienpennequin) + - Eric GELOEN (gelo) + - Sebastiaan Stok (sstok) + - Jérôme Vasseur (jvasseur) + - Lars Strojny (lstrojny) + - Daniel Wehner (dawehner) + - Tugdual Saunier (tucksaun) + - Javier Spagnoletti (phansys) + - Théo FIDRY (theofidry) - Robert Schönthal (digitalkaoz) - - Jérémy DERUSSÉ (jderusse) - - Dariusz Ruminski - - Michal Piotrowski (eventhorizon) + - Florian Lonqueu-Brochard (florianlb) + - Chris Wilkinson (thewilkybarkid) - Stefano Sala (stefano.sala) - - David Buchmann (dbu) - - Issei Murasawa (issei_m) - - Iltar van der Berg (kjarli) + - Evgeniy (ewgraf) + - Alex Pott + - Vincent AUBERT (vincent) - Juti Noppornpitak (shiroyuki) - - Eric GELOEN (gelo) + - Tigran Azatyan (tigranazatyan) - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) + - Gabriel Caruso - Hidenori Goto (hidenorigoto) - - Joshua Thijssen + - Arnaud Kleinpeter (nanocom) + - Jannik Zschiesche (apfelbox) - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - - Vladimir Reznichenko (kalessil) - Jérémie Augustin (jaugustin) - - Sebastiaan Stok (sstok) + - Andréia Bohner (andreia) + - Philipp Wahala (hifi) + - Julien Falque (julienfalque) - Rafael Dohms (rdohms) - - Arnaud Kleinpeter (nanocom) - - Tigran Azatyan (tigranazatyan) + - jwdeitch + - Teoh Han Hui (teohhanhui) + - Mikael Pajunen + - Joel Wurtz (brouznouf) + - Oleg Voronkovich + - Vyacheslav Pavlov + - Richard van Laak (rvanlaak) - Richard Shank (iampersistent) + - Thomas Rabaix (rande) + - Rouven Weßling (realityking) - Clemens Tolboom - Helmer Aaviksoo - - Baptiste Clavié (talus) - Hiromi Hishida (77web) + - Niels Keurentjes (curry684) - Matthieu Ouellette-Vachon (maoueh) - Michał Pipa (michal.pipa) + - Dawid Nowak + - Gabriel Ostrolucký - Amal Raghav (kertz) - Jonathan Ingram (jonathaningram) - Artur Kotyrba - - Rouven Weßling (realityking) - - Charles Sarrazin (csarrazi) - - Pierre du Plessis (pierredup) - - Tugdual Saunier (tucksaun) - - Andréia Bohner (andreia) + - GDIBass + - SpacePossum + - jeremyFreeAgent (Jérémy Romey) (jeremyfreeagent) + - James Halsall (jaitsu) + - Matthieu Napoli (mnapoli) + - Florent Mata (fmata) + - Warnar Boekkooi (boekkooi) + - Alessandro Chitolina (alekitto) - Dmitrii Chekaliuk (lazyhammer) - Clément JOBEILI (dator) + - Daniel Espendiller + - Possum - Dorian Villet (gnutix) - - Javier Spagnoletti (phansys) + - Sergey Linnik (linniksa) - Richard Miller (mr_r_miller) - - hacfi (hifi) + - Albert Casademont (acasademont) - Mario A. Alvarez Garcia (nomack84) - Dennis Benkert (denderello) - - Alexander M. Turek (derrabus) - - Konstantin Myakshin (koc) + - DQNEO - Benjamin Dulau (dbenjamin) + - Mathieu Lemoine (lemoinem) + - Thomas Calvet (fancyweb) + - Christian Schmidt - Andreas Hucks (meandmymonkey) - - Mikael Pajunen - Noel Guilbert (noel) - - Joel Wurtz (brouznouf) + - Yanick Witschi (toflar) + - Marek Štípek (maryo) + - Stepan Anchugov (kix) - bronze1man - sun (sun) - Larry Garfield (crell) + - Michaël Perrin (michael.perrin) + - Nikolay Labinskiy (e-moe) - Martin Schuhfuß (usefulthink) - - Thomas Rabaix (rande) + - apetitpa - Matthieu Bontemps (mbontemps) + - apetitpa - Pierre Minnieur (pminnieur) - fivestar - Dominique Bongiraud + - Jeremy Livingston (jeremylivingston) + - Michael Lee (zerustech) + - Matthieu Auger (matthieuauger) - Leszek Prabucki (l3l0) - François Zaninotto (fzaninotto) - Dustin Whittle (dustinwhittle) - jeff - John Kary (johnkary) - Justin Hileman (bobthecow) + - Blanchon Vincent (blanchonvincent) - Michele Orselli (orso) + - Tom Van Looy (tvlooy) - Sven Paulus (subsven) - - Warnar Boekkooi (boekkooi) - - Lars Strojny (lstrojny) - Rui Marinho (ruimarinho) + - Eugene Wissner + - Pascal Montoya - Julien Brochet (mewt) - - Sergey Linnik (linniksa) - - Jáchym Toušek + - Leo Feyer + - Tristan Darricau (nicofuma) - Marcel Beerta (mazen) - - Vincent AUBERT (vincent) + - Pavel Batanov (scaytrase) + - Loïc Faugeron + - Hidde Wieringa (hiddewie) + - Marco Pivetta (ocramius) + - Rob Frawley 2nd (robfrawley) - julien pauli (jpauli) + - Lorenz Schori + - Oskar Stark (oskarstark) + - Sébastien Lavoie (lavoiesl) + - Gregor Harlan (gharlan) + - Dariusz - Francois Zaninotto - Alexander Kotynia (olden) - Daniel Tschinder + - Christian Schmidt + - Marcos Sánchez - Elnur Abdurrakhimov (elnur) - Manuel Reinhard (sprain) - Danny Berger (dpb587) + - Ruben Gonzalez (rubenrua) + - Adam Prager (padam87) + - Benoît Burnichon (bburnichon) - Roman Marintšenko (inori) - Xavier Montaña Carreras (xmontana) - - Chris Wilkinson (thewilkybarkid) + - François-Xavier de Guillebon (de-gui_f) + - Mickaël Andrieu (mickaelandrieu) + - Maxime Veber (nek-) - Xavier Perez - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA + - Patrick McDougle (patrick-mcdougle) - Alif Rachmawadi + - Kristen Gilden (kgilden) - Pierre-Yves LEBECQ (pylebecq) + - Jordan Samouh (jordansamouh) + - Baptiste Lafontaine (magnetik) + - Jakub Kucharovic (jkucharovic) + - Edi Modrić (emodric) + - Uwe Jäger (uwej711) - Eugene Leonovich (rybakit) + - Filippo Tessarotto - Joseph Rouff (rouffj) - Félix Labrecque (woodspire) - GordonsLondon - Jan Sorgalla (jsor) - Ray + - Tyson Andre - Chekote - Thomas Adam - - Albert Casademont (acasademont) + - Viktor Bocharskyi (bocharsky_bw) + - Jhonny Lidfors (jhonne) + - Diego Agulló (aeoris) + - Andreas Schempp (aschempp) - jdhoek - - Jeremy Livingston (jeremylivingston) + - Massimiliano Arione (garak) + - Bob den Otter (bopp) - Nikita Konstantinov - Wodor Wodorski - - Matthieu Auger (matthieuauger) - - Michael Lee (zerustech) - - Sébastien Lavoie (lavoiesl) + - Thomas Lallement (raziel057) + - mcfedr (mcfedr) + - Colin O'Dell (colinodell) + - Fabien Bourigault (fbourigault) + - Giorgio Premi + - Jan Schädlich (jschaedl) - Beau Simensen (simensen) + - Michael Hirschler (mvhirsch) - Robert Kiss (kepten) - - Ruben Gonzalez (rubenrua) + - Roumen Damianoff (roumen) + - Antonio J. García Lagar (ajgarlag) - Kim Hemsø Rasmussen (kimhemsoe) - - Diego Saint Esteben (dosten) - - Florian Lonqueu-Brochard (florianlb) - - Tom Van Looy (tvlooy) - Wouter Van Hecke + - Jérôme Parmentier (lctrs) + - Michael Babker (mbabker) - Peter Kruithof (pkruithof) - Michael Holm (hollo) + - Remon van de Kamp (rpkamp) - Marc Weistroff (futurecat) - - Dawid Nowak - - Kristen Gilden (kgilden) + - Christian Schmidt + - MatTheCat + - Chad Sikorra (chadsikorra) - Chris Smith (cs278) - Florian Klein (docteurklein) + - Gary PEGEOT (gary-p) - Manuel Kiessling (manuelkiessling) - - Daniel Wehner - Atsuhiro KUBO (iteman) + - rudy onfroy (ronfroy) - Andrew Moore (finewolf) - Bertrand Zuchuat (garfield-fr) + - Sullivan SENECHAL (soullivaneuh) - Gabor Toth (tgabi333) - - Grégoire Paris (greg0ire) - - Alex Pott - realmfoo - Thomas Tourlourat (armetiz) - Andrey Esaulov (andremaha) - Grégoire Passault (gregwar) + - Jerzy Zawadzki (jzawadzki) + - Wouter J - Ismael Ambrosi (iambrosi) - - Uwe Jäger (uwej711) + - François Pluchino (francoispluchino) - Aurelijus Valeiša (aurelijus) - Jan Decavele (jandc) - Gustavo Piltcher - Stepan Tanasiychuk (stfalcon) - Tiago Ribeiro (fixe) - - Bob den Otter (bopp) + - Hidde Boomsma (hboomsma) + - John Bafford (jbafford) - Adrian Rudnik (kreischweide) - Francesc Rosàs (frosas) + - Romain Pierre (romain-pierre) - Julien Galenski (ruian) - Bongiraud Dominique - janschoenherr - Thomas Schulz (king2500) - - Marco Pivetta (ocramius) + - Dariusz Rumiński + - Frank de Jonge (frenkynet) + - Berny Cantos (xphere81) + - Thierry Thuon (lepiaf) - Ricard Clau (ricardclau) - - Lorenz Schori - - Giorgio Premi + - Mark Challoner (markchalloner) + - Gennady Telegin (gtelegin) + - Ben Davies (bendavies) - Erin Millard + - Artur Melo (restless) - Matthew Lewinski (lewinski) - - Marcos Sánchez + - Magnus Nordlander (magnusnordlander) + - Thomas Royer (cydonia7) - alquerci - Francesco Levorato - Vitaliy Zakharov (zakharovvi) @@ -254,72 +367,103 @@ Symfony is the result of the work of many people who made the code better - Inal DJAFAR (inalgnu) - Christian Gärtner (dagardner) - Tomasz Kowalczyk (thunderer) - - François-Xavier de Guillebon (de-gui_f) - - Daniel Wehner + - Artur Eshenbrener + - Damien Alexandre (damienalexandre) + - Thomas Perez (scullwm) - Felix Labrecque - Yaroslav Kiliba - - Stepan Anchugov (kix) - Terje Bråten - - Evgeniy (ewgraf) + - Mathieu Lechat - Robbert Klarenbeek (robbertkl) - - Blanchon Vincent (blanchonvincent) + - JhonnyL + - David Badura (davidbadura) - hossein zolfi (ocean) - Clément Gautier (clementgautier) + - Sanpi - Eduardo Gulias (egulias) - giulio de donato (liuggio) + - ShinDarth - Stéphane PY (steph_py) - Philipp Kräutli (pkraeutli) + - Grzegorz (Greg) Zdanowski (kiler129) - Kirill chEbba Chebunin (chebba) - Greg Thornton (xdissent) - Costin Bereveanu (schniper) - Loïc Chardonnet (gnusat) - Marek Kalnik (marekkalnik) - - Tobias Nyholm (tobias) - Vyacheslav Salakhutdinov (megazoll) - Hassan Amouhzi - Tamas Szijarto - - Michaël Perrin (michael.perrin) + - Michele Locati - Pavel Volokitin (pvolok) + - Smaine Milianni (ismail1432) + - Arthur de Moulins (4rthem) + - Nicolas Dewez (nicolas_dewez) - Endre Fejes - Tobias Naumann (tna) + - George Mponos (gmponos) + - Daniel Beyer - Shein Alexey + - Alex Rock Ancelet (pierstoval) + - Romain Gautier (mykiwi) - Joe Lencioni - Daniel Tschinder + - vladimir.reznichenko - Kai - Lee Rowlands + - Krzysztof Piasecki (krzysztek) - Maximilian Reichel (phramz) + - Loick Piera (pyrech) - Karoly Negyesi (chx) + - Ivan Kurnosov - Xavier HAUSHERR + - David Prévot - Albert Jessurum (ajessu) - Laszlo Korte - Miha Vrhovnik - Alessandro Desantis - hubert lecorche (hlecorche) - Marc Morales Valldepérez (kuert) + - Jean-Baptiste GOMOND (mjbgo) - Vadim Kharitonov (virtuozzz) - Oscar Cubo Medina (ocubom) - Karel Souffriau - Christophe L. (christophelau) - - Massimiliano Arione (garak) - Anthon Pang (robocoder) - - Jannik Zschiesche (apfelbox) - Emanuele Gaspari (inmarelibero) - - Dariusz Rumiński + - Sébastien Santoro (dereckson) - Brian King - Michel Salib (michelsalib) - geoffrey + - Steffen Roßkamp + - Alexandru Furculita (afurculita) + - Valentin Jonovs (valentins-jonovs) - Jeanmonod David (jeanmonod) - - Berny Cantos (xphere81) - - Thomas Lallement (raziel057) + - Christopher Davis (chrisguitarguy) - Jan Schumann - Niklas Fiekas + - Markus Bachmann (baachi) - lancergr - - Antonio J. García Lagar (ajgarlag) + - Zan Baldwin + - Mihai Stancu + - Ivan Nikolaev (destillat) - Olivier Dolbeau (odolbeau) - - Roumen Damianoff (roumen) + - Jan Rosier (rosier) + - Alessandro Lai (jean85) + - Pascal Luna (skalpa) + - Arturs Vonda + - Josip Kruslin + - Asmir Mustafic (goetas) - vagrant + - Aurimas Niekis (gcds) + - EdgarPE + - Florian Pfitzer (marmelatze) - Asier Illarramendi (doup) + - Andreas Braun + - Boris Vujicic (boris.vujicic) - Chris Sedlmayr (catchamonkey) + - Mateusz Sip (mateusz_sip) + - Kamil Kokot (pamil) - Seb Koelen - Christoph Mewes (xrstf) - Vitaliy Tverdokhlib (vitaliytv) @@ -327,374 +471,580 @@ Symfony is the result of the work of many people who made the code better - Dirk Pahl (dirkaholic) - cedric lombardot (cedriclombardot) - Jonas Flodén (flojon) - - Christian Schmidt + - Gonzalo Vilaseca (gonzalovilaseca) - Marcin Sikoń (marphi) - Dominik Zogg (dominik.zogg) - - Mathieu Lemoine + - Marek Pietrzak + - Luc Vieillescazes (iamluc) - franek (franek) - - Damien Alexandre (damienalexandre) + - Christian Wahler + - Gintautas Miselis + - Rob Bast + - Zander Baldwin - Adam Harvey + - Anton Bakai + - Rhodri Pugh (rodnaph) - Alex Bakhturin + - insekticid + - Alexander Obuhovich (aik099) - boombatower - Fabrice Bernhard (fabriceb) - Jérôme Macias (jeromemacias) + - Andrey Astakhov (aast) + - ReenExe - Fabian Lange (codingfabian) + - Frank Neff (fneff) + - Roman Lapin (memphys) - Yoshio HANAWA + - Gladhon + - Haralan Dobrev (hkdobrev) - Sebastian Bergmann + - Miroslav Sustek - Pablo Díez (pablodip) + - Martin Hujer (martinhujer) - Kevin McBride + - Sergio Santoro + - Robin van der Vleuten (robinvdvleuten) - Philipp Rieber (bicpi) - Manuel de Ruiter (manuel) - - Jérémy Romey (jeremyfreeagent) - Eduardo Oliveira (entering) + - Ilya Antipenko (aivus) - Iker Ibarguren (ikerib) - Ricardo Oliveira (ricardolotr) + - Roy Van Ginneken (rvanginneken) - ondrowan - Barry vd. Heuvel (barryvdh) - - Jerzy Zawadzki (jzawadzki) + - Craig Duncan (duncan3dc) + - Sébastien Alfaiate (seb33300) - Evan S Kaufman (evanskaufman) - mcben - Jérôme Vieilledent (lolautruche) - Maks Slesarenko + - Filip Procházka (fprochazka) - mmoreram - Markus Lanthaler (lanthaler) + - Remi Collet - Vicent Soria Durá (vicentgodella) + - Michael Moravec - Anthony Ferrara - Ioan Negulescu - Jakub Škvára (jskvara) - - Daniel Beyer - Andrew Udvare (audvare) - alexpods - - Richard van Laak (rvanlaak) + - Arjen van der Meijden + - Adam Szaraniec (mimol) + - Dariusz Ruminski - Erik Trapman (eriktrapman) - De Cock Xavier (xdecock) + - Almog Baku (almogbaku) - Scott Arciszewski + - Xavier HAUSHERR - Norbert Orzechowicz (norzechowicz) + - Denis Charrier (brucewouaigne) - Matthijs van den Bos (matthijs) - - Loick Piera (pyrech) - Lenard Palko - Nils Adermann (naderman) - Gábor Fási + - DUPUCH (bdupuch) - Benjamin Leveque (benji07) - - Ivan Kurnosov + - Nate (frickenate) + - Timothée Barray (tyx) + - jhonnyL + - Grenier Kévin (mcsky_biig) - sasezaki - Dawid Pakuła (zulusx) - Florian Rey (nervo) - Rodrigo Borrego Bernabé (rodrigobb) - - MatTheCat - - John Bafford (jbafford) - Denis Gorbachev (starfall) - - Titouan Galopin (tgalopin) + - Peter van Dommelen + - Tim van Densen + - Martin Morávek (keeo) - Steven Surowiec - Kevin Saliou (kbsali) + - Shawn Iwinski + - NothingWeAre - Ryan - Alexander Deruwe (aderuwe) - Alain Hippolyte (aloneh) - Dave Hulbert (dave1010) - - François Pluchino (francoispluchino) - Ivan Rey (ivanrey) - Marcin Chyłek (songoq) + - Ben Scott - Ned Schwartz - Ziumin + - Jeremy Benoist + - fritzmg - Lenar Lõhmus + - Sander Toonen (xatoo) - Benjamin Laugueux (yzalis) - Zach Badgett (zachbadgett) - Aurélien Fredouelle - Pavel Campr (pcampr) - Johnny Robeson (johnny) - Disquedur + - Michiel Boeckaert (milio) - Geoffrey Tran (geoff) - Jan Behrens - Mantas Var (mvar) - Sebastian Krebs - - Christopher Davis (chrisguitarguy) + - Laurent VOULLEMIER (lvo) + - Jean-Christophe Cuvelier [Artack] + - Simon DELICATA - alcaeus + - Fred Cox - vitaliytv - - Markus Bachmann (baachi) + - Dalibor Karlović (dkarlovi) - Sebastian Blum - aubx + - Marvin Butkereit - Ricky Su (ricky) - Gildas Quéméner (gquemener) + - Charles-Henri Bruyand - Max Rath (drak3) - Stéphane Escandell (sescandell) + - Konstantin S. M. Möllers (ksmmoellers) + - James Johnston - Sinan Eldem - - Gennady Telegin (gtelegin) - Alexandre Dupuy (satchette) + - Malte Blättermann + - Andre Rømcke (andrerom) - Nahuel Cuesta (ncuesta) - Chris Boden (cboden) - - Asmir Mustafic (goetas) - - Josip Kruslin + - Christophe Villeger (seragan) + - Julien Fredon + - Bob van de Vijver (bobvandevijver) + - Stefan Gehrig (sgehrig) - Hany el-Kerdany - Wang Jingyu + - Webnet team (webnet) - Åsmund Garfors + - Gunnstein Lye (glye) - Maxime Douailin - - Gregor Harlan + - Jean Pasdeloup (pasdeloup) + - Benjamin Cremer (bcremer) - Javier López (loalf) - Reinier Kip + - Geoffrey Brier (geoffrey-brier) + - Vlad Gregurco (vgregurco) + - Vladimir Tsykun - Dustin Dobervich (dustin10) + - dantleech + - Anne-Sophie Bachelard (annesophie) - Sebastian Marek (proofek) - Erkhembayar Gantulga (erheme318) + - Michal Trojanowski - David Fuhr - - Kamil Kokot (pamil) + - Max Grigorian (maxakawizard) + - DerManoMann - Rostyslav Kinash - Maciej Malarz (malarzm) - Daisuke Ohata - Vincent Simonin + - Alex Bogomazov (alebo) + - maxime.steinhausser + - adev - Stefan Warman + - Arkadius Stefanski (arkadius) - Tristan Maindron (tmaindron) + - Wesley Lancel - Ke WANG (yktd26) + - Ivo Bathke (ivoba) - Strate - - Jakub Kucharovic + - Anton A. Sumin + - Israel J. Carberry + - Tim Goudriaan (codedmonkey) - Miquel Rodríguez Telep (mrtorrent) - Sergey Kolodyazhnyy (skolodyazhnyy) - umpirski + - Denis Brumann (dbrumann) + - Quentin de Longraye (quentinus95) - Chris Heng (gigablah) + - Shaun Simmons (simshaun) + - Richard Bradley - Ulumuddin Yunus (joenoez) - - Florian Pfitzer (marmelatze) - - Luc Vieillescazes (iamluc) - Johann Saunier (prophet777) + - Sergey (upyx) + - Michael Devery (mickadoo) - Antoine Corcy - - Artur Eshenbrener - - Arturs Vonda + - Dmitrii Poddubnyi (karser) - Sascha Grossenbacher - Szijarto Tamas + - Robin Lehrmann (robinlehrmann) + - Catalin Dan + - Jaroslav Kuba + - Stephan Vock - Benjamin Zikarsky (bzikarsky) - - Ben Davies (bendavies) - - Mickaël Andrieu (mickaelandrieu) + - Roberto Espinoza (respinoza) - Simon Schick (simonsimcity) - redstar504 + - Tristan Roussel + - Cameron Porter - Hossein Bukhamsin + - Oliver Hoff + - Christian Sciberras (uuf6429) + - Martin Auswöger + - Disparity - origaminal + - Matteo Beccati (matteobeccati) + - Kevin (oxfouzer) - Paweł Wacławczyk (pwc) - Oleg Zinchenko (cystbear) + - Baptiste Meyer (meyerbaptiste) - Johannes Klauss (cloppy) - Evan Villemez - fzerorubigd - Thomas Ploch - Benjamin Grandfond (benjamin) - Tiago Brito (blackmx) + - - Richard van den Brand (ricbra) - develop + - flip111 + - Greg Anderson + - VJ + - Delf Tonder (leberknecht) - Mark Sonnabaum - - Alexander Obuhovich (aik099) + - Massimiliano Braglia (massimilianobraglia) + - Richard Quadling - jochenvdv - - Filippo Tessarotto - Arturas Smorgun (asarturas) - Alexander Volochnev (exelenz) - Michael Piecko - yclian - - Sergio Santoro + - Aleksey Prilipko + - twifty + - Indra Gunawan (guind) + - Peter Ward + - Davide Borsatto (davide.borsatto) + - Julien DIDIER (juliendidier) + - Dominik Ritter (dritter) - Sebastian Grodzicki (sgrodzicki) + - Jeroen van den Enden (stoefke) - Pascal Helfenstein + - Anthony GRASSIOT (antograssiot) - Baldur Rensch (brensch) + - Pierre Rineau - Vladyslav Petrovych - Alex Xandra Albert Sim + - Alexander Schranz (alexander-schranz) + - Carson Full + - Sergey Yastrebov + - Trent Steel (trsteel88) - Yuen-Chi Lian - Besnik Br + - Jose Gonzalez + - Oleksii Zhurbytskyi + - Dariusz Ruminski - Joshua Nye + - Claudio Zizza - Dave Marshall (davedevelopment) + - Jakub Kulhan (jakubkulhan) - avorobiev - Venu - Lars Vierbergen - - Mark Challoner + - Jonatan Männchen - Dennis Hotson - Andrew Tchircoff (andrewtch) - michaelwilliams - 1emming - Leevi Graham (leevigraham) + - Nykopol (nykopol) + - Jordan Deitch - Casper Valdemar Poulsen - Josiah (josiah) - - Marek Štípek (maryo) + - Joschi Kuphal - John Bohn (jbohn) - Marc Morera (mmoreram) + - Saif Eddin Gmati (azjezz) - Andrew Hilobok (hilobok) + - Noah Heck (myesain) - Christian Soronellas (theunic) - - Romain Gautier (mykiwi) + - Johann Pardanaud - Yosmany Garcia (yosmanyga) + - Wouter de Wild + - Antoine M (amakdessi) - Degory Valentine + - izzyp - Benoit Lévêque (benoit_leveque) - Jeroen Fiege (fieg) - Krzysiek Łabuś - - Nicolas Dewez (nicolas_dewez) - Xavier Lacot (xavier) + - possum + - Denis Zunke (donalberto) + - Ahmadou Waly Ndiaye (waly) + - Philipp Cordes + - Ahmed TAILOULOUTE (ahmedtai) - Olivier Maisonneuve (olineuve) + - Masterklavi - Francis Turmel (fturmel) + - Nikita Nefedov (nikita2206) - cgonzalez - Ben + - Vincent Composieux (eko) - Jayson Xu (superjavason) + - Christopher Hertel (chertel) + - Hubert Lenoir (hubert_lenoir) - Jaik Dean (jaikdean) - fago - Harm van Tilborg - Jan Prieser - - Artur Melo (restless) + - GDIBass + - Adrien Lucas (adrienlucas) + - Zhuravlev Alexander (scif) - James Michael DuPont - Tom Klingenberg - Christopher Hall (mythmakr) + - Patrick Dawkins (pjcdawkins) - Paul Kamer (pkamer) - Rafał Wrzeszcz (rafalwrzeszcz) + - Vincent CHALAMON (vincentchalamon) - Reen Lokum - Martin Parsiegla (spea) - - Possum - - Denis Charrier (brucewouaigne) + - Nguyen Xuan Quynh (xuanquynh) - Quentin Schuler - Pierre Vanliefland (pvanliefland) + - Sofiane HADDAG (sofhad) - frost-nzcr4 - - Oskar Stark (oskarstark) + - Bozhidar Hristov + - Laurent Bassin (lbassin) + - andrey1s - Abhoryo - Fabian Vogler (fabian) - Korvin Szanto + - Stéphan Kochen + - Arjan Keeman - Alaattin Kahramanlar (alaattin) + - Sergey Zolotov (enleur) - Maksim Kotlyar (makasim) - Neil Ferreira + - Nathanael Noblet (gnat) + - Indra Gunawan (indragunawan) + - Julie Hourcade (juliehde) - Dmitry Parnas (parnas) - - DQNEO + - Paul LE CORRE - Emanuele Iannone - Tony Malzhacker - - DUPUCH (bdupuch) + - Mathieu MARCHOIS - Cyril Quintin (cyqui) - Gerard van Helden (drm) - Johnny Peck (johnnypeck) + - Ivan Menshykov - David Romaní - Patrick Allaert - Gustavo Falco (gfalco) - Matt Robinson (inanimatt) - - Tristan Darricau (nicofuma) + - Ruud Kamphuis (ruudk) - Aleksey Podskrebyshev - - Steffen Roßkamp + - Calin Mihai Pristavu - David Marín Carreño (davefx) + - Fabien LUCAS (flucas2) - Jörn Lang (j.lang) - - Leo Feyer + - Omar Yepez (oyepez003) + - Gawain Lynch (gawain) + - Samuel NELA (snela) - mwsaz + - Jelle Kapitein - Benoît Bourgeois + - mantulo - corphi - grizlik - Derek ROTH + - Ben Johnson + - mweimerskirch + - Dmytro Boiko (eagle) - Shin Ohno (ganchiku) - Geert De Deckere (geertdd) - Jan Kramer (jankramer) - - Jean-Baptiste GOMOND (mjbgo) - abdul malik ikhsan (samsonasik) - Henry Snoek (snoek09) - - Timothée Barray (tyx) + - Jérémy M (th3mouk) + - Simone Di Maulo (toretto460) - Christian Morgan - Alexander Miehe (engerim) - Morgan Auchede (mauchede) - Don Pinkster - Maksim Muruev - Emil Einarsson + - Thomas Landauer + - 243083df - Thibault Duplessis - Marc Abramowitz - Martijn Evers + - Tony Tran - Jacques Moati - Balazs Csaba (balazscsaba2006) + - Douglas Reith (douglas_reith) + - Forfarle (forfarle) - Harry Walter (haswalt) - Johnson Page (jwpage) - - Tomáš Votruba (tomas_votruba) + - Ruben Gonzalez (rubenruateltek) - Michael Roterman (wtfzdotnet) - Arno Geurts - Adán Lobato (adanlobato) + - Ian Jenkins (jenkoian) - Matthew Davis (mdavis1982) + - Sam Fleming (sam_fleming) - Maks + - Antoine LA + - den + - pawel-lewtak + - omerida - Gábor Tóth - Daniel Cestari + - David Lima + - Brian Freytag (brianfreytag) - Brunet Laurent (lbrunet) - - Magnus Nordlander (magnusnordlander) - - Michiel Boeckaert (milio) + - Florent Viel (luxifer) - Mikhail Yurasov (mym) - LOUARDI Abdeltif (ouardisoft) - Robert Gruendler (pulse00) - Simon Terrien (sterrien) + - Tarmo Leppänen (tarlepp) - Benoît Merlet (trompette) - Koen Kuipers - datibbaw + - Erik Saunier (snickers) + - Rootie + - Kyle + - Daniel Alejandro Castro Arellano (lexcast) - Raul Fraile (raulfraile) - sensio + - Baptiste Leduc (bleduc) + - Sebastien Morel (plopix) - Patrick Kaufmann + - Piotr Stankowski + - Anton Dyshkant - Reece Fowell (reecefowell) + - Mátyás Somfai (smatyas) - stefan.r - - Matthieu Napoli (mnapoli) - - Alexandru Furculita (afurculita) + - Valérian Galliat + - d-ph + - Rikijs Murgs - Ben Ramsey (ramsey) + - Amaury Leroux de Lens (amo__) - Christian Jul Jensen + - Alexandre GESLIN (alexandregeslin) - The Whole Life to Learn + - ergiegonzaga - Farhad Safarov - Liverbool (liverbool) - Sam Malone - Phan Thanh Ha (haphan) - Chris Jones (leek) - - Colin O'Dell (colinodell) - xaav - - Jean-Christophe Cuvelier [Artack] - Mahmoud Mostafa (mahmoud) + - Pieter - Michael Tibben + - Billie Thompson + - Ganesh Chandrasekaran - Sander Marechal + - Franz Wilding (killerpoke) + - ProgMiner + - Oleg Golovakhin (doc_tr) + - Icode4Food (icode4food) - Radosław Benkel + - kevin.nadin + - jean pasqualini (darkilliant) + - Ross Motley (rossmotley) - ttomor - Mei Gwilym (meigwilym) - Michael H. Arieli (excelwebzone) + - Tom Panier (neemzy) + - Fred Cox - Luciano Mammino (loige) - - Michael Hirschler (mvhirsch) - fabios - - Jérôme Vasseur - Sander Coolen (scoolen) - Nicolas Le Goff (nlegoff) + - Ben Oman + - Guilhem N (guilhemn) + - Chris de Kok + - Andreas Kleemann - Manuele Menozzi - Anton Babenko (antonbabenko) - Irmantas Šiupšinskas (irmantas) - - Charles-Henri Bruyand - Danilo Silva + - Arnaud PETITPAS (apetitpa) - Zachary Tong (polyfractal) + - Ashura - Hryhorii Hrebiniuk + - johnstevenson + - Dennis Fridrich (dfridrich) + - hamza - dantleech + - Bastien DURAND (deamon) - Xavier Leune - - Christian Schmidt + - Rudy Onfroy - Tero Alén (tero) - DerManoMann - Guillaume Royer - Artem (digi) - - dantleech + - boite + - MGDSoft - Vadim Tyukov (vatson) + - David Wolter (davewww) - Sortex - chispita - Wojciech Sznapka + - Gavin Staniforth + - Ariel J. Birnbaum + - Mathieu Santostefano + - Arjan Keeman - Máximo Cuadros (mcuadros) - - Stefan Gehrig (sgehrig) - - Alex Bogomazov + - Lukas Mencl - tamirvs - julien.galenski - Christian Neff + - Oliver Hoff + - Ole Rößner (basster) - Per Sandström (per) - Goran Juric - Laurent Ghirardotti (laurentg) - Nicolas Macherey - - Jan Rosier (rosier) + - Guido Donnari + - AKeeman (akeeman) - Lin Clark - Jeremy David (jeremy.david) + - Gocha Ossinkine (ossinkine) - Troy McCabe - Ville Mattila - - Boris Vujicic (boris.vujicic) + - ilyes kooli + - gr1ev0us + - mlazovla + - Behnoush norouzali (behnoush) - Max Beutel - - Michal Trojanowski - - Catalin Dan + - Antanas Arvasevicius + - Pierre Dudoret + - Thomas + - Maximilian Berghoff (electricmaxxx) - nacho - Piotr Antosik (antek88) - Artem Lopata - - Samuel ROZE (sroze) - Sergey Novikov (s12v) - Marcos Quesada (marcos_quesada) - Matthew Vickery (mattvick) + - Viktor Novikov (panzer_commander) + - Paul Mitchum (paul-m) + - Angel Koilov (po_taka) - Dan Finnie - Ken Marfilla (marfillaster) - - Max Grigorian (maxakawizard) - benatespina (benatespina) - Denis Kop + - Jean-Guilhem Rouel (jean-gui) - jfcixmedia + - Dominic Tubach + - Nikita Konstantinov - Martijn Evers - Benjamin Paap (benjaminpaap) - Christian + - Denis Golubovskiy (bukashk0zzz) - Sergii Smertin (nfx) + - Michał Strzelecki + - hugofonseca (fonsecas72) + - Martynas Narbutas + - Toon Verwerft (veewee) - Bailey Parker - Eddie Jaoude + - Antanas Arvasevicius - Haritz Iturbe (hizai) - Nerijus Arlauskas (nercury) - SPolischook @@ -706,24 +1056,42 @@ Symfony is the result of the work of many people who made the code better - Matteo Giachino (matteosister) - Alex Demchenko (pilot) - Tadas Gliaubicas (tadcka) + - Thanos Polymeneas (thanos) - Benoit Garret - - Thomas Royer (cydonia7) - - DerManoMann + - Jakub Sacha + - Olaf Klischat + - orlovv + - Jonathan Hedstrom + - Peter Smeets (darkspartan) + - Jhonny Lidfors (jhonny) - Julien Bianchi (jubianchi) + - Robert Meijers + - James Sansbury - Marcin Chwedziak - - Roland Franssen (ro0) + - hjkl - Tony Cosentino (tony-co) + - Dan Wilga + - Andrew Tch + - Alexander Cheprasov - Rodrigo Díez Villamuera (rodrigodiez) - e-ivanov + - Einenlum - Jochen Bayer (jocl) + - Patrick Carlo-Hickman + - Bruno MATEU + - Alex Bowers - Jeremy Bush - wizhippo + - Mathias STRASSER (roukmoute) + - Thomason, James - Viacheslav Sychov + - Helmut Hummel (helhum) + - Matt Brunt + - Carlos Ortega Huetos - rpg600 - Péter Buri (burci) - - Davide Borsatto (davide.borsatto) - - Teoh Han Hui (teohhanhui) - kaiwa + - RJ Garcia - Charles Sanquer (csanquer) - Albert Ganiev (helios-ag) - Neil Katin @@ -731,21 +1099,33 @@ Symfony is the result of the work of many people who made the code better - Will Donohoe - peter - Jérémy Jourdin (jjk801) + - BRAMILLE Sébastien (oktapodia) - Artem Kolesnikov (tyomo4ka) - Gustavo Adrian - Yannick - spdionis + - rchoquet + - gitlost + - Taras Girnyk - Eduardo García Sanz (coma) - James Gilliland - - Roy Van Ginneken - - Stephan Vock + - fduch (fduch) - David de Boer (ddeboer) + - Ryan Rogers + - Klaus Purer + - arnaud (arnooo999) - Gilles Doge (gido) - abulford + - Philipp Kretzschmar + - antograssiot + - Ilya Vertakov - Brooks Boyd - Roger Webb - Dmitriy Simushev - - Martin Hujer (martinhujer) + - Pawel Smolinski + - Oxan van Leeuwen + - pkowalczyk + - Soner Sayakci - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) @@ -757,35 +1137,51 @@ Symfony is the result of the work of many people who made the code better - Peter Thompson (petert82) - Felicitus - Krzysztof Przybyszewski + - alexpozzi + - Frederic Godfrin - Paul Matthews + - Jakub Kisielewski + - Vacheslav Silyutin - Juan Traverso + - Alain Flaus (halundra) + - Tarjei Huse (tarjei) + - tsufeki - Philipp Strube - - Christian Sciberras - - Anton Bakai - Clement Herreman (clemherreman) + - Dan Ionut Dumitriu (danionut90) + - Vladislav Rastrusny (fractalizer) + - Alexander Kurilo (kamazee) - Nyro (nyro) - - Trent Steel (trsteel88) - Marco - Marc Torres - Alberto Aldegheri + - Dmitri Petmanson - heccjj - Alexandre Melard + - Jay Klehr - Sergey Yuferev - Tobias Stöckler - Mario Young - - Jakub Kulhan + - Ilia (aliance) + - Chris McCafferty (cilefen) + - Grégoire Penverne (gpenverne) - Mo Di (modi) - - Jeroen van den Enden (stoefke) + - Pablo Schläpfer + - Gert de Pagter + - Jelte Steijaert (jelte) - Quique Porta (quiqueporta) + - stoccc - Tomasz Szymczyk (karion) + - Xavier Coureau - ConneXNL - Aharon Perkel + - matze + - Rubén Calvo (rubencm) - Abdul.Mohsen B. A. A - - Gintautas Miselis - Benoît Burnichon - - Remi Collet - pthompson - Malaney J. Hill + - Alexandre Pavy - Christian Flach (cmfcmf) - Cédric Girard (enk_) - Lars Ambrosius Wallenborn (larsborn) @@ -794,134 +1190,200 @@ Symfony is the result of the work of many people who made the code better - Tatsuya Tsuruoka - Ross Tuck - Kévin Gomez (kevin) + - Andrei Igna - azine - Dawid Sajdak - Ludek Stepan - - Geoffrey Brier - Aaron Stephens (astephens) + - Craig Menning (cmenning) - Balázs Benyó (duplabe) - Erika Heidi Reinaldo (erikaheidi) - Pierre Tachoire (krichprollsch) - Marc J. Schmidt (marcjs) + - Sebastian Schwarz - Marco Jantke - Saem Ghani + - Clément LEFEBVRE + - Conrad Kleinespel + - Zacharias Luiten - Sebastian Utz - Adrien Gallou (agallou) + - Maks Rafalko (bornfree) - Karol Sójko (karolsojko) - sl_toto (sl_toto) - Walter Dal Mut (wdalmut) + - abluchet + - Matthieu + - Albin Kerouaton - Sébastien HOUZÉ - Jingyu Wang - - Daniel Espendiller - steveYeah - Samy Dindane (dinduks) - Keri Henare (kerihenare) - Cédric Lahouste (rapotor) - Samuel Vogel (samuelvogel) + - Alexey Kopytko (sanmai) - Berat Doğan - - twifty + - Guillaume LECERF + - Juanmi Rodriguez Cerón + - Andy Raines - Anthony Ferrara + - Geoffrey Pécro (gpekz) - Klaas Cuvelier (kcuvelier) + - Mathieu TUDISCO (mathieutu) + - markusu49 + - Steve Frécinaux + - Constantine Shtompel + - Jules Lamur + - Renato Mendes Figueiredo - ShiraNai7 + - Antal Áron (antalaron) + - Markus Fasselt (digilist) + - Vašek Purchart (vasek-purchart) - Janusz Jabłoński (yanoosh) + - Sandro Hopf + - Łukasz Makuch - George Giannoulopoulos + - Luis Ramirez (luisdeimos) - Daniel Richter (richtermeister) - ChrisC - Ilya Biryukov + - Kim Laï Trinh - Jason Desrosiers - m.chwedziak + - Andreas Frömer - Philip Frank - Lance McNearney - - Frank Neff (fneff) - Giorgio Premi + - Andrew Berry + - ncou + - Ian Carroll - caponica + - Daniel Kay (danielkay-cp) - Matt Daum (daum) - Alberto Pirovano (geezmo) + - Nicolas LEFEVRE (nicoweb) - Pete Mitchell (peterjmit) - Tom Corrigan (tomcorrigan) + - Luis Galeas - Martin Pärtel - - Miroslav Sustek - Patrick Daley (padrig) - Xavier Briand (xavierbriand) - Max Summe - WedgeSama - Felds Liscia - - James Halsall (jaitsu) - - Maxime Veber (nek-) - - Sullivan SENECHAL + - Chihiro Adachi (chihiro-adachi) + - Emanuele Panzeri (thepanz) - Tadcka - Beth Binkovitz + - Gonzalo Míguez - Romain Geissler + - Adrien Moiruad - Tomaz Ahlin - - Benjamin Cremer (bcremer) + - Philip Ardery - Marcus Stöhr (dafish) - Emmanuel Vella (emmanuel.vella) + - Jonathan Johnson (jrjohnson) - Carsten Nielsen (phreaknerd) - Mathieu Rochette - Jay Severson - René Kerner - Nathaniel Catchpole - - Jose Gonzalez - Adrien Samson (adriensamson) - Samuel Gordalina (gordalina) - Max Romanovsky (maxromanovsky) + - Nicolas Eeckeloo (neeckeloo) - Mathieu Morlon - Daniel Tschinder + - Arnaud CHASSEUX - Rafał Muszyński (rafmus90) + - Sébastien Decrême (sebdec) - Timothy Anido (xanido) + - Mara Blaga - Rick Prent + - skalpa - Martin Eckhardt - Pieter Jordaan - Damien Tournoud - Jon Gotlin (jongotlin) - Michael Dowling (mtdowling) + - Karlos Presumido (oneko) + - Sylvain Fabre (sylfabre) + - Thomas Counsell - BilgeXA - r1pp3rj4ck - Robert Queck + - Peter Bouwdewijn - mlively + - Amine Matmati - Fabian Steiner (fabstei) - Klaus Silveira (klaussilveira) - Thomas Chmielowiec (chmielot) - Jānis Lukss - rkerner - Alex Silcock - - Rob Bast + - Qingshan Luo + - Ergie Gonzaga - Matthew J Mucklo + - AnrDaemon - fdgdfg (psampaz) - Stéphane Seng - Maxwell Vandervelde - kaywalker - Mike Meier + - Tim Jabs - Sebastian Ionescu - Thomas Ploch - Simon Neidhold - Valentin VALCIU + - Jeremiah VALERIE + - Julien Menth - Kevin Dew - James Cowgill + - 1ma (jautenim) - Nicolas Schwartz (nicoschwartz) - Patrik Gmitter (patie) - Jonathan Gough - Benjamin Bender + - Jared Farrish + - karl.rixon + - raplider - Konrad Mohrfeldt - Lance Chen + - Ciaran McNulty (ciaranmcnulty) + - Andrew (drew) - kor3k kor3k (kor3k) - Stelian Mocanita (stelian) + - Thomas Bisignani (toma) + - Justin (wackymole) - Flavian (2much) + - Gautier Deuette - mike + - Kirk Madera - Keith Maika - Mephistofeles - Hoffmann András - Olivier + - Cyril PASCAL - pscheit + - Wybren Koelmans + - Zdeněk Drahoš - Dan Harper - moldcraft + - Antoine Bellion (abellion) - Ramon Kleiss (akathos) - César Suárez (csuarez) + - Bjorn Twachtmann (dotbjorn) - Nicolas Badey (nico-b) - Shane Preece (shane) + - Johannes Goslar + - Geoff + - georaldc + - Maarten de Boer + - Malte Wunsch - wusuopu - povilas - - Diego Agulló + - Gavin Staniforth - Alessandro Tagliapietra (alex88) - Biji (biji) - Gunnar Lium (gunnarlium) @@ -929,19 +1391,25 @@ Symfony is the result of the work of many people who made the code better - Artiom - Jakub Simon - Bouke Haarsma + - Evert Harmeling + - mschop + - Alan Poulain - Martin Eckhardt - - Denis Zunke + - natechicago - Jonathan Poston - Adrian Olek (adrianolek) + - Jody Mickey (jwmickey) - Przemysław Piechota (kibao) - Leonid Terentyev (li0n) - - Adam Prager (padam87) + - Martynas Sudintas (martiis) - ryunosuke + - zenmate - victoria - Francisco Facioni (fran6co) - Iwan van Staveren (istaveren) - Povilas S. (povilas) - pborreli + - Boris Betzholz - Eric Caron - 2manypeople - Wing @@ -950,31 +1418,59 @@ Symfony is the result of the work of many people who made the code better - catch - Alexandre Segura - Josef Cech + - Harold Iedema - Arnau González (arnaugm) - - Nate (frickenate) + - Simon Bouland (bouland) - Matthew Foster (mfoster) - Paul Seiffert (seiffert) - Vasily Khayrulin (sirian) - Stefan Koopmanschap (skoop) - Stefan Hüsges (tronsha) + - Jake Bishop (yakobeyak) + - Dan Blows + - Matt Wells + - Sander van der Vlugt + - Nicolas Appriou - stloyd + - Andreas - Chris Tickner + - BoShurik - Andrew Coulton + - Ulugbek Miniyarov + - Jeremy Benoist - Michal Gebauer - Gleb Sidora - David Stone + - Jovan Perovic (jperovic) - Pablo Maria Martelletti (pmartelletti) - Yassine Guedidi (yguedidi) + - Waqas Ahmed + - Bert Hekman - Luis Muñoz + - Matthew Donadio + - Houziaux mike + - Phobetor - Andreas + - Markus + - Daniel Gorgan - Thomas Chmielowiec + - shdev - Andrey Ryaguzov + - Stefan + - Peter Bex - Manatsawin Hanmongkolchai - Gunther Konig + - Mickael GOETZ - Maciej Schmidt + - Greg ORIOL + - Dennis Væversted - nuncanada - flack - František Bereň + - Kamil Madejski + - Jeremiah VALERIE + - Mike Francis + - Gerd Christian Kunze (derdu) - Christoph Nissle (derstoffel) - Ionel Scutelnicu (ionelscutelnicu) - Nicolas Tallefourtané (nicolab) @@ -984,32 +1480,53 @@ Symfony is the result of the work of many people who made the code better - David Stone - jjanvier - Julius Beckmann + - loru88 - Romain Dorgueil + - Christopher Parotat + - me_shaon + - 蝦米 - Grayson Koonce (breerly) - Karim Cassam Chenaï (ka) + - Maksym Slesarenko (maksym_slesarenko) + - Michal Kurzeja (mkurzeja) - Nicolas Bastien (nicolas_bastien) + - Nikola Svitlica (thecelavi) - Denis (yethee) - Andrew Zhilin (zhil) + - Sjors Ottjes - Andy Stanberry + - Felix Marezki + - Normunds - Luiz “Felds” Liscia - Thomas Rothe + - nietonfir - alefranz + - David Barratt + - Pavel.Batanov - avi123 + - Pavel Prischepa - alsar + - downace - Aarón Nieves Fernández - Mike Meier - Kirill Saksin + - Julien Pauli + - Koalabaerchen - michalmarcinkowski - Warwick - Chris + - Florent Olivaud - JakeFr + - Simon Sargeant - efeen + - Nicolas Pion + - Muhammed Akbulut - Michał Dąbrowski (defrag) - - Nathanael Noblet (gnat) - Simone Fumagalli (hpatoio) - Brian Graham (incognito) - Kevin Vergauwen (innocenzo) - Alessio Baglio (ioalessio) + - Johannes Müller (johmue) - Jordi Llonch (jordillonch) - Cédric Dugat (ph3nol) - Philip Dahlstrøm (phidah) @@ -1018,34 +1535,64 @@ Symfony is the result of the work of many people who made the code better - Robin Duval (robin-duval) - Grinbergs Reinis (shima5) - Artem Lopata (bumz) + - alex + - Nicole Cordes + - Roman Orlov + - VolCh - Alexey Popkov + - Gijs Kunze - Artyom Protaskin - Nathanael d. Noblet - helmer + - ged15 - Daan van Renterghem + - Nicole Cordes + - Martin Kirilov + - amcastror - Bram Van der Sype (brammm) + - Guile (guile) - Julien Moulin (lizjulien) - - Nikita Nefedov (nikita2206) - Mauro Foti (skler) - Yannick Warnier (ywarnier) - Kevin Decherf - Jason Woods + - Oleg Andreyev + - klemens - dened - Dmitry Korotovsky + - mcorteel + - Michael van Tricht + - ReScO + - Tim Strehle - Sam Ward - Walther Lalk - Adam + - Sören Bernstein - devel + - taiiiraaa - Trevor Suarez - gedrox + - Alan Bondarchuk - dropfen - Andrey Chernykh - Edvinas Klovas - Drew Butler + - Peter Breuls + - Chansig + - Tischoi - J Bruni + - Fritz Michael Gschwantner - Alexey Prilipko + - Dmitriy Fedorenko + - vlakoff - bertillon - - Victor Bocharsky (bocharsky_bw) + - Rudolf Ratusiński + - Bertalan Attila + - AmsTaFF (amstaff) + - Simon Müller (boscho) + - Yannick Bensacq (cibou) + - Frédéric G. Marand (fgm) + - Freek Van der Herten (freekmurze) - Luca Genuzio (genuzio) - Hans Nilsson (hansnilsson) - Andrew Marcinkevičius (ifdattic) @@ -1053,125 +1600,174 @@ Symfony is the result of the work of many people who made the code better - Jan Marek (janmarek) - Mark de Haan (markdehaan) - Dan Patrick (mdpatrick) + - Pedro Magalhães (pmmaga) - Rares Vlaseanu (raresvla) - - Sofiane HADDAG (sofhad) - tante kinast (tante) - Vincent LEFORT (vlefort) + - Darryl Hein (xmmedia) - Sadicov Vladimir (xtech) - - Peter van Dommelen + - Kevin EMO (zarcox) - Alexander Zogheb - Rémi Blaise - Joel Marcey - David Christmann - root + - Vincent Chalnot - James Hudson - Tom Maguire + - Richard Quadling - David Zuelke + - Adrian + - Oleg Andreyev + - neFAST - Pierre Rineau + - Maxim Lovchikov - adenkejawen + - Florent SEVESTRE (aniki-taicho) - Ari Pringle (apringle) - Dan Ordille (dordille) - Jan Eichhorn (exeu) - Grégory Pelletier (ip512) - John Nickell (jrnickell) - - Julien DIDIER (juliendidier) - Martin Mayer (martin) - Grzegorz Łukaszewicz (newicz) - - Omar Yepez (oyepez003) + - Jonny Schmid (schmidjon) + - Götz Gottwald - Veres Lajos - grifx - Robert Campbell - Matt Lehner - - Hidde Wieringa + - Helmut Januschka - Hein Zaw Htet™ - Ruben Kruiswijk + - Cosmin-Romeo TANASE + - Julien Maulny - Michael J - Joseph Maarek - Alexander Menk - Alex Pods - hadriengem - timaschew + - Jochen Mandl + - Marin Nicolae + - Alessandro Loffredo - Ian Phillips + - Marco Lipparini - Haritz - Matthieu Prat + - Ion Bazan - Grummfy - - Thomas Landauer + - Paul Le Corre - Filipe Guerra + - Jean Ragouin - Gerben Wijnja - Rowan Manning - Per Modin - David Windell - Gabriel Birke - skafandri + - Derek Bonner - Alan Chen - Maerlyn - Even André Fiskvik - - Diego Agulló + - Arjan Keeman + - Erik van Wingerden + - Valouleloup - Dane Powell + - Alexis MARQUIS - Gerrit Drost + - Linnaea Von Lavia + - Simon Mönch + - Javan Eskander - Lenar Lõhmus - Cristian Gonzalez - AlberT + - hainey - Juan M Martínez - Gilles Gauthier - ddebree + - Kuba Werłos + - Tomas Liubinas - Alex + - Jan Hort - Klaas Naaijkens - Daniel González Cerviño - - possum - Rafał + - Achilles Kaloeridis (achilles) - Adria Lopez (adlpz) + - Aaron Scherer (aequasi) - Rosio (ben-rosio) - Simon Paarlberg (blamh) - Jeroen Thora (bolle) + - Brieuc THOMAS (brieucthomas) - Masao Maeda (brtriver) - Darius Leskauskas (darles) - David Joos (djoos) - Denis Klementjev (dklementjev) - Tomáš Polívka (draczris) - - Vincent Composieux (eko) + - Dennis Smink (dsmink) - Franz Liedke (franzliedke) + - Gaylord Poillon (gaylord_p) - Christophe BECKER (goabonga) - gondo (gondo) - Gusakov Nikita (hell0w0rd) - Osman Üngür (import) - Javier Núñez Berrocoso (javiernuber) - Jelle Bekker (jbekker) - - Ian Jenkins (jenkoian) + - Giovanni Albero (johntree) - Jorge Martin (jorgemartind) + - Joeri Verdeyen (jverdeyen) + - Kevin Verschaeve (keversc) - Kevin Herrera (kherge) - Luis Ramón López López (lrlopez) + - Bart Reunes (metalarend) - Muriel (metalmumu) - Michael Pohlers (mick_the_big) + - mlpo (mlpo) + - Marek Šimeček (mssimi) - Cayetano Soriano Gallego (neoshadybeat) + - Olivier Laviale (olvlvl) + - Ondrej Machulda (ondram) - Pablo Monterde Perez (plebs) - Jimmy Leger (redpanda) - - Pavel Batanov (scaytrase) + - Marcin Szepczynski (szepczynski) - Cyrille Jouineau (tuxosaurus) + - Vladimir Chernyshev (volch) - Yorkie Chadwick (yorkie76) - - Yanick Witschi + - GuillaumeVerdon + - Philipp Keck - Ondrej Mirtes - akimsko - Youpie - srsbiz - Taylan Kasap + - Michael Orlitzky - Nicolas A. Bérard-Nault - - Gladhon - Saem Ghani - Stefan Oderbolz - Curtis + - Gabriel Moreira - Alexey Popkov + - ChS + - Alexis MARQUIS - Joseph Deray - Damian Sromek - Ben + - Evgeniy Tetenchuk + - dasmfm + - Mathias Geat - Arnaud Buathier (arnapou) - chesteroni (chesteroni) - Mauricio Lopez (diaspar) + - HADJEDJ Vincent (hadjedjvincent) - Daniele Cesarini (ijanki) - Ismail Asci (ismailasci) - Simon CONSTANS (kosssi) - Kristof Van Cauwenbergh (kristofvc) + - Dennis Langen (nijusan) + - Paulius Jarmalavičius (pjarmalavicius) - Ramon Henrique Ornelas (ramonornela) + - Ricardo de Vries (ricknox) - Markus S. (staabm) - Till Klampaeckel (till) - Tobias Weinert (tweini) @@ -1179,82 +1775,125 @@ Symfony is the result of the work of many people who made the code better - Wotre - goohib - Xavier HAUSHERR + - Ron Gähler + - Edwin Hageman - Mantas Urnieža + - temperatur - Cas - Dusan Kasan + - Karolis - Myke79 - Brian Debuire - Piers Warmers + - Guilliam Xavier - Sylvain Lorinet - klyk50 - Andreas Lutro - jc - BenjaminBeck - Aurelijus Rožėnas + - Jordan Hoff - znerol - Christian Eikermann + - Kai Eichinger - Antonio Angelino + - Matt Fields + - Niklas Keller + - Andras Debreczeni - Vladimir Sazhin + - Tomas Kmieliauskas + - Billie Thompson - lol768 - jamogon - Vyacheslav Slinko + - Jakub Chábek - Johannes - Jörg Rühl - wesleyh - sergey + - Daniel Bannert + - Karim Miladi - Michael Genereux - patrick-mcdougle - Dariusz Czech + - Jack Wright - Anonymous User + - Paweł Tomulik - Eric J. Duran + - Alexandru Bucur - cmfcmf - Drew Butler - Steve Müller - Andras Ratz - andreabreu98 - Michael Schneider + - Cédric Bertolini - n-aleha + - Anatol Belski - Şəhriyar İmanov + - Alexis BOYER - Kaipi Yann - Sam Williams + - Guillaume Aveline - Adrian Philipp - James Michael DuPont - Kasperki - Tammy D + - Daniel STANCU - Ondrej Slinták - vlechemin - Brian Corrigan - Ladislav Tánczos - - Brian Freytag - Skorney + - fmarchalemisys - mieszko4 - Steve Preston + - Kevin Frantz - Neophy7e - bokonet - Arrilot - Markus Staab - Pierre-Louis LAUNAY - djama + - Michael Gwynne - Eduardo Conceição + - changmin.keum - Jon Cave - Sébastien HOUZE - Abdulkadir N. A. + - Adam Klvač - Yevgen Kovalienia + - Lebnik + - nsbx + - Shude + - Ondřej Führer - Sema + - Michael Käfer - Elan Ruusamäe - Thorsten Hallwas - Michael Squires + - Egor Gorbachev + - Derek Stephen McLean - Norman Soetbeer + - zorn + - Yuriy Potemkin + - Emilie Lorenzo + - Edvin Hultberg - Benjamin Long - Matt Janssen + - Ben Miller - Peter Gribanov - kwiateusz + - jspee - David Soria Parra - Sergiy Sokolenko + - Ahmed Abdulrahman - dinitrol - Penny Leach + - Yurii K - Richard Trebichavský - g123456789l + - Jonathan Vollebregt - oscartv - DanSync - Peter Zwosta @@ -1262,40 +1901,56 @@ Symfony is the result of the work of many people who made the code better - Diego Campoy - TeLiXj - Oncle Tom + - Sam Anthony - Christian Stocker + - Oussama Elgoumri - Dawid Nowak - - Richard Quadling + - Lesnykh Ilia + - darnel - Karolis Daužickas + - Nicolas + - Sergio Santoro - tirnanog06 - phc - Дмитрий Пацура - ilyes kooli + - Marko Kaznovac - Matthias Althaus - Michaël VEROUX - Julia + - Lin Lu - arduanov - sualko + - Bilge + - ADmad - Nicolas Roudaire - Alfonso (afgar) - Andreas Forsblom (aforsblo) - Alex Olmos (alexolmos) - Antonio Mansilla (amansilla) + - Robin Kanters (anddarerobin) - Juan Ases García (ases) - Siragusa (asiragusa) - Daniel Basten (axhm3a) - Bill Hance (billhance) - Bernd Matzner (bmatzner) + - Bram Tweedegolf (bram_tweedegolf) + - Brandon Kelly (brandonkelly) - Choong Wei Tjeng (choonge) - Kousuke Ebihara (co3k) - Loïc Vernet (coil) + - Christian Gripp (core23) - Christoph Schaefer (cvschaefer) - Damon Jones (damon__jones) - - David Badura (davidbadura) + - Łukasz Giza (destroyer) - Daniel Londero (dlondero) + - Samuele Lilli (doncallisto) + - Sebastian Landwehr (dword123) - Adel ELHAIBA (eadel) - Damián Nohales (eagleoneraptor) - Elliot Anderson (elliot) - Fabien D. (fabd) + - Carsten Eilers (fnc) - Sorin Gitlan (forapathy) - Yohan Giarelli (frequence-web) - Gerry Vandermaesen (gerryvdm) @@ -1303,56 +1958,74 @@ Symfony is the result of the work of many people who made the code better - Arash Tabriziyan (ghost098) - ibasaw (ibasaw) - Vladislav Krupenkin (ideea) + - Ilija Tovilo (ilijatovilo) + - Peter Orosz (ill_logical) + - Imangazaliev Muhammad (imangazaliev) + - j0k (j0k) - joris de wit (jdewit) - Jérémy CROMBEZ (jeremy) - Jose Manuel Gonzalez (jgonzalez) + - Joachim Krempel (jkrempel) - Jorge Maiden (jorgemaiden) - Justin Rainbow (jrainbow) + - Juan Luis (juanlugb) - JuntaTom (juntatom) - Ismail Faizi (kanafghan) - Sébastien Armand (khepin) + - Pierre-Chanel Gauthier (kmecnin) - Krzysztof Menżyk (krymen) - samuel laulhau (lalop) - Laurent Bachelier (laurentb) - - Jérôme Parmentier (lctrs) - - Florent Viel (luxifer) + - Luís Cobucci (lcobucci) + - Matthieu Mota (matthieumota) - Matthieu Moquet (mattketmo) - Moritz Borgmann (mborgmann) + - Michal Čihař (mcihar) - Matt Drollette (mdrollette) - Adam Monsen (meonkeys) - Ala Eddine Khefifi (nayzo) - emilienbouard (neime) - Nicholas Byfleet (nickbyfleet) + - Tomas Norkūnas (norkunas) + - Marco Petersen (ocrampete16) - ollie harridge (ollietb) - Paul Andrieux (paulandrieux) - Paweł Szczepanek (pauluz) + - Philippe Degeeter (pdegeeter) + - Pedro Miguel Maymone de Resende (pedroresende) - Christian López Espínola (penyaskito) - Petr Jaroš (petajaros) - Philipp Hoffmann (philipphoffmann) - Alex Carol (picard89) - Daniel Perez Pinazo (pitiflautico) + - Phil Taylor (prazgod) + - Maxim Pustynnikov (pustynnikov) - Brayden Williams (redstar504) - Rich Sage (richsage) - - Ruud Kamphuis (ruudk) + - Rokas Mikalkėnas (rokasm) - Bart Ruysseveldt (ruyss) - Sascha Dens (saschadens) - scourgen hung (scourgen) - Sebastian Busch (sebu) + - Sepehr Lajevardi (sepehr) - André Filipe Gonçalves Neves (seven) - Bruno Ziegler (sfcoder) - Andrea Giuliano (shark) - Schuyler Jager (sjager) - Volker (skydiablo) + - Serkan Yildiz (srknyldz) - Julien Sanchez (sumbobyboys) - Guillermo Gisinger (t3chn0r) - Markus Tacker (tacker) + - Andrew Clark (tqt_andrew_clark) - Tyler Stroud (tystr) - Moritz Kraft (userfriendly) - Víctor Mateo (victormateo) - Vincent (vincent1870) + - David Herrmann (vworldat) - Eugene Babushkin (warl) + - Wouter Sioen (wouter_sioen) - Xavier Amado (xamado) - - Yonel Ceruto González (yonelceruto) - Jesper Søndergaard Pedersen (zerrvox) - Florent Cailhol - szymek @@ -1362,24 +2035,42 @@ Symfony is the result of the work of many people who made the code better - simpson - drublic - Andreas Streichardt + - Pascal Hofmann + - Stefan Kruppa - smokeybear87 - Gustavo Adrian + - Kevin Weber + - Ben Scott + - Dionysis Arvanitis + - Sergey Fedotov + - Konstantin Scheumann - Michael - fh-github@fholzhauer.de + - AbdElKader Bouadjadja + - DSeemiller + - Jan Emrich - Mark Topper - Xavier REN - Zander Baldwin - Philipp Scheit - max + - Ahmad Mayahi (ahmadmayahi) - Mohamed Karnichi (amiral) + - Andrew Carter (andrewcarteruk) + - Adam Elsodaney (archfizz) + - Gregório Bonfante Borba (bonfante) - Daniel Kolvik (dkvk) + - Marc Lemay (flug) + - Henne Van Och (hennevo) - Jeroen De Dauw (jeroendedauw) + - Jonathan Scheiber (jmsche) - Maxime COLIN (maximecolin) - Muharrem Demirci (mdemirci) - Evgeny Z (meze) - Nicolas de Marqué (nicola) - - Kevin (oxfouzer) - Pierre Geyer (ptheg) - - Erik Saunier (snickers) + - Thomas BERTRAND (sevrahk) - Matej Žilák (teo_sk) - Vladislav Vlastovskiy (vlastv) + - RENAUDIN Xavier (xorrox) + - Yannick Vanhaeren (yvh) diff --git a/LICENSE b/LICENSE index 43028bc600f26..21d7fb9e2f29b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index dca4c3cbdeec6..b9fc51b3cf964 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,77 @@ -README -====== +

+ +

-What is Symfony? ------------------ - -Symfony is a PHP 5.3 full-stack web framework. It is written with speed and -flexibility in mind. It allows developers to build better and easy to maintain -websites with PHP. - -Symfony can be used to develop all kind of websites, from your personal blog -to high traffic ones like Dailymotion or Yahoo! Answers. - -Requirements ------------- - -Symfony is only supported on PHP 5.3.9 and up. - -Be warned that PHP 5.3.16 has a major bug in the Reflection subsystem and is -not suitable to run Symfony (https://bugs.php.net/bug.php?id=62715) +[Symfony][1] is a **PHP framework** for web applications and a set of reusable +**PHP components**. Symfony is used by thousands of web applications (including +BlaBlaCar.com and Spotify.com) and most of the [popular PHP projects][2] (including +Drupal and Magento). Installation ------------ -The best way to install Symfony is to use the [official Symfony Installer][7]. -It allows you to start a new project based on the version you want. +* [Install Symfony][4] with Composer or with our own installer (see + [requirements details][3]). +* Symfony follows the [semantic versioning][5] strictly, publishes "Long Term + Support" (LTS) versions and has a [release process][6] that is predictable and + business-friendly. Documentation ------------- -The "[Quick Tour][1]" tutorial gives you a first feeling of the framework. If, -like us, you think that Symfony can help speed up your development and take -the quality of your work to the next level, read the official -[Symfony documentation][2]. +* Read the [Getting Started guide][7] if you are new to Symfony. +* Try the [Symfony Demo application][23] to learn Symfony in practice. +* Master Symfony with the [Guides and Tutorials][8], the [Components docs][9] + and the [Best Practices][10] reference. + +Community +--------- + +* [Join the Symfony Community][11] and meet other members at the [Symfony events][12]. +* [Get Symfony support][13] on Stack Overflow, Slack, IRC, etc. +* Follow us on [GitHub][14], [Twitter][15] and [Facebook][16]. +* Read our [Code of Conduct][24] and meet the [CARE Team][25] Contributing ------------ -Symfony is an open source, community-driven project. If you'd like to contribute, -please read the [Contributing Code][3] part of the documentation. If you're submitting -a pull request, please follow the guidelines in the [Submitting a Patch][4] section -and use [Pull Request Template][5]. +Symfony is an Open Source, community-driven project with thousands of +[contributors][19]. Join them [contributing code][17] or [contributing documentation][18]. + +Security Issues +--------------- + +If you discover a security vulnerability within Symfony, please follow our +[disclosure procedure][20]. -Running Symfony Tests ----------------------- +About Us +-------- -Information on how to run the Symfony test suite can be found in the -[Running Symfony Tests][6] section. +Symfony development is sponsored by [SensioLabs][21], led by the +[Symfony Core Team][22] and supported by [Symfony contributors][19]. -[1]: https://symfony.com/get_started -[2]: https://symfony.com/doc/current/ -[3]: https://symfony.com/doc/current/contributing/code/index.html -[4]: https://symfony.com/doc/current/contributing/code/patches.html#check-list -[5]: https://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request -[6]: https://symfony.com/doc/master/contributing/code/tests.html -[7]: https://symfony.com/doc/current/book/installation.html#installing-the-symfony-installer +[1]: https://symfony.com +[2]: https://symfony.com/projects +[3]: https://symfony.com/doc/current/reference/requirements.html +[4]: https://symfony.com/doc/current/setup.html +[5]: http://semver.org +[6]: https://symfony.com/doc/current/contributing/community/releases.html +[7]: https://symfony.com/doc/current/page_creation.html +[8]: https://symfony.com/doc/current/index.html +[9]: https://symfony.com/doc/current/components/index.html +[10]: https://symfony.com/doc/current/best_practices/index.html +[11]: https://symfony.com/community +[12]: https://symfony.com/events/ +[13]: https://symfony.com/support +[14]: https://github.com/symfony +[15]: https://twitter.com/symfony +[16]: https://www.facebook.com/SymfonyFramework/ +[17]: https://symfony.com/doc/current/contributing/code/index.html +[18]: https://symfony.com/doc/current/contributing/documentation/index.html +[19]: https://symfony.com/contributors +[20]: https://symfony.com/security +[21]: https://sensiolabs.com +[22]: https://symfony.com/doc/current/contributing/code/core_team.html +[23]: https://github.com/symfony/symfony-demo +[24]: https://symfony.com/coc +[25]: https://symfony.com/doc/current/contributing/code_of_conduct/care_team.html diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index ff3dc0a1860e0..b548fa4d79f40 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -26,7 +26,6 @@ * The `standalone` option is deprecated and will be replaced with the `strategy` option in 2.3. * The values `true`, `false`, `js` for the `standalone` option were deprecated and replaced respectively with the `esi`, `inline`, `hinclude` in 2.3. - Before: ```jinja @@ -43,7 +42,6 @@ {{ render(controller('BlogBundle:Post:list', { 'limit': 2 }), { 'strategy': 'hinclude'}) }} ``` - ### HttpFoundation * The MongoDbSessionHandler default field names and timestamp type have changed. diff --git a/UPGRADE-2.5.md b/UPGRADE-2.5.md index fc5ad61fadf7c..bbfd15cde9959 100644 --- a/UPGRADE-2.5.md +++ b/UPGRADE-2.5.md @@ -245,7 +245,6 @@ Validator ->getValidator(); ``` - Yaml Component -------------- diff --git a/UPGRADE-2.7.md b/UPGRADE-2.7.md index f52220fadc2ed..8e6a99c62e33c 100644 --- a/UPGRADE-2.7.md +++ b/UPGRADE-2.7.md @@ -32,15 +32,17 @@ Router * The `getMatcherDumperInstance()` and `getGeneratorDumperInstance()` methods in the `Symfony\Component\Routing\Router` have been changed from `protected` to `public`. - If you override these methods in a subclass, you will need to change your + If you override these methods in a subclass, you will need to change your methods to `public` as well. Note however that this is a temporary change needed for PHP 5.3 compatibility only. It will be reverted in Symfony 3.0. - + Form ---- + * the `isFileUpload()` method was added to the `RequestHandlerInterface` + * In form types and extension overriding the "setDefaultOptions" of the - AbstractType or AbstractExtensionType has been deprecated in favor of + AbstractType or AbstractTypeExtension has been deprecated in favor of overriding the new "configureOptions" method. The method "setDefaultOptions(OptionsResolverInterface $resolver)" will @@ -528,9 +530,9 @@ PropertyAccess Config ------ - * The `__toString()` method of the `\Symfony\Component\Config\ConfigCache` is marked as + * The `__toString()` method of the `\Symfony\Component\Config\ConfigCache` is marked as deprecated in favor of the new `getPath()` method. - + Validator --------- @@ -596,11 +598,11 @@ TwigBundle FrameworkBundle --------------- - * The `templating.helper.assets` was refactored and returns now an object of the type + * The `templating.helper.assets` service was refactored and now returns an object of type `Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper` instead of `Symfony\Component\Templating\Helper\CoreAssetsHelper`. You can update your class definition - or use the `assets.packages` service instead. Using the `assets.packages` service is the recommended - way. The `templating.helper.assets` service will be removed in Symfony 3.0. + or use the `assets.packages` service instead. Using the `assets.packages` service is the recommended + way. Before: @@ -644,6 +646,34 @@ FrameworkBundle } ``` + * The assets settings under `framework.templating` were deprecated and will be removed in Symfony 3.0. Use `framework.assets` instead. + + Before: + + ```yml + framework: + templating: + assets_version: 'v123' + assets_version_format: '%%s?version=%%s' + assets_base_urls: + http: ['http://cdn.example.com'] + ssl: ['https://secure.example.com'] + packages: + # ... + ``` + + After: + + ```yml + framework: + assets: + version: 'v123' + version_format: '%%s?version=%%s' + base_urls: ['http://cdn.example.com', 'https://secure.example.com'] + packages: + # ... + ``` + Security --------------- @@ -674,48 +704,48 @@ Form * In order to fix a few regressions in the new `ChoiceList` implementation, a few details had to be changed compared to 2.7. - - The legacy `Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface` + + The legacy `Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface` now does not extend the new `Symfony\Component\Form\ChoiceList\ChoiceListInterface` anymore. If you pass an implementation of the old interface in a context where the new interface is required, wrap the list into a `LegacyChoiceListAdapter`: - + Before: - + ```php use Symfony\Component\Form\ChoiceList\ChoiceListInterface; - + function doSomething(ChoiceListInterface $choiceList) { // ... } - + doSomething($legacyList); ``` - + After: - + ```php use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter; - + function doSomething(ChoiceListInterface $choiceList) { // ... } - + doSomething(new LegacyChoiceListAdapter($legacyList)); ``` - + The new `ChoiceListInterface` now has two additional methods `getStructuredValues()` and `getOriginalKeys()`. You should add these methods if you implement this interface. See their doc blocks and the implementation of the core choice lists for inspiration. - + The method `ArrayKeyChoiceList::toArrayKey()` was marked as internal. This method was never supposed to be used outside the class. - + The method `ChoiceListFactoryInterface::createView()` does not accept arrays and `Traversable` instances anymore for the `$groupBy` parameter. Pass a callable instead. diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md index ae8bb7fa4ac98..64a31dde75600 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.8.md @@ -1,9 +1,29 @@ UPGRADE FROM 2.7 to 2.8 ======================= +All components +-------------- + +* Symfony now requires the iconv extension to be present, which is the case by + default in most environments. However, if you're not able to ensure this + extension to be installed in your target environment, you can add Symfony's + iconv polyfill to your project's composer.json file. + + ```json + "require": { + "symfony/polyfill-iconv": "~1.0" + } + ``` + Form ---- + * The `intention` option was deprecated and will be removed in 3.0 in favor + of the new `csrf_token_id` option. + + * The `csrf_provider` option was deprecated and will be removed in 3.0 in favor + of the new `csrf_token_manager` option. + * The "cascade_validation" option was deprecated. Use the "constraints" option together with the `Valid` constraint instead. Contrary to "cascade_validation", "constraints" must be set on the respective child forms, @@ -186,6 +206,41 @@ Form } ``` + * In Symfony 2.7 a small BC break was introduced with the new choices_as_values + option. In order to have the choice values populated to the html value attribute + you had to define the choice_value option. This is now not any more needed. + + Before: + + ```php + $form->add('status', 'choice', array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ), + 'choices_as_values' => true, + // important if you rely on your option value attribute (e.g. for JavaScript) + // this will keep the same functionality as before + 'choice_value' => function ($choice) { + return $choice; + }, + )); + ``` + + After (Symfony 2.8+): + + ```php + $form->add('status', ChoiceType::class, array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ), + 'choices_as_values' => true + )); + ``` + * Returning type instances from `FormTypeInterface::getParent()` is deprecated and will not be supported anymore in Symfony 3.0. Return the fully-qualified class name of the parent type class instead. @@ -214,6 +269,14 @@ Form } ``` + * The option "options" of the CollectionType has been renamed to "entry_options". + The usage of the option "options" is deprecated and will be removed in Symfony 3.0. + + * The option "type" of the CollectionType has been renamed to "entry_type". + The usage of the option "type" is deprecated and will be removed in Symfony 3.0. + As a value for the option you should provide the fully-qualified class name (FQCN) + now as well. + * Passing type instances to `Form::add()`, `FormBuilder::add()` and the `FormFactory::create*()` methods is deprecated and will not be supported anymore in Symfony 3.0. Pass the fully-qualified class name of the type @@ -252,6 +315,12 @@ Form ``` + * The `TimezoneType::getTimezones()` method was deprecated and will be removed + in Symfony 3.0. You should not use this method. + + * The class `ArrayKeyChoiceList` was deprecated and will be removed in Symfony + 3.0. Use `ArrayChoiceList` instead. + Translator ---------- @@ -346,6 +415,10 @@ DependencyInjection ``` + * `Symfony\Component\DependencyInjection\ContainerAware` has been deprecated, use + `Symfony\Component\DependencyInjection\ContainerAwareTrait` or implement + `Symfony\Component\DependencyInjection\ContainerAwareInterface` manually + WebProfiler ----------- @@ -439,42 +512,145 @@ FrameworkBundle cookie_httponly: false ``` + * The `validator.mapping.cache.apc` service is deprecated, and will be removed in 3.0. + Use `validator.mapping.cache.doctrine.apc` instead. + + * The ability to pass `apc` as the `framework.validation.cache` configuration key value is deprecated, + and will be removed in 3.0. Use `validator.mapping.cache.doctrine.apc` instead: + + Before: + + ```yaml + framework: + validation: + cache: apc + ``` + + After: + + ```yaml + framework: + validation: + cache: validator.mapping.cache.doctrine.apc + ``` + Security -------- - * The AbstractToken::isGranted() method was deprecated. Instead, - override the voteOnAttribute() method. This method has one small - difference: it's passed the TokenInterface instead of the user: + * The `object` variable passed to expressions evaluated by the `ExpressionVoter` + is deprecated. Instead use the new `subject` variable. + + * The `AbstractVoter` class was deprecated. Instead, extend the `Voter` class and + move your voting logic in the `supports($attribute, $subject)` and + `voteOnAttribute($attribute, $object, TokenInterface $token)` methods. + + * The `VoterInterface::supportsClass` and `supportsAttribute` methods were + deprecated and will be removed from the interface in 3.0. + + * The `key` setting of `anonymous`, `remember_me` and `http_digest` is + deprecated, and will be removed in 3.0. Use `secret` instead. Before: + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { key: "%secret%" } + remember_me: + key: "%secret%" + http_digest: + key: "%secret%" + ``` + + ```xml + + + + + + + + + + + + + ``` + ```php - class MyCustomVoter extends AbstractVoter - { + // ... + $container->loadFromExtension('security', array( // ... - - protected function isGranted($attribute, $object, $user = null) - { + 'firewalls' => array( // ... - } - } + 'anonymous' => array('key' => '%secret%'), + 'remember_me' => array('key' => '%secret%'), + 'http_digest' => array('key' => '%secret%'), + ), + )); ``` After: + ```yaml + security: + # ... + firewalls: + default: + # ... + anonymous: { secret: "%secret%" } + remember_me: + secret: "%secret%" + http_digest: + secret: "%secret%" + ``` + + ```xml + + + + + + + + + + + + + ``` + ```php - class MyCustomVoter extends AbstractVoter - { + // ... + $container->loadFromExtension('security', array( // ... - - protected function voteOnAttribute($attribute, $object, TokenInterface $token) - { - $user = $token->getUser(); + 'firewalls' => array( // ... - } - } + 'anonymous' => array('secret' => '%secret%'), + 'remember_me' => array('secret' => '%secret%'), + 'http_digest' => array('secret' => '%secret%'), + ), + )); ``` + * The `intention` option is deprecated for all the authentication listeners, + and will be removed in 3.0. Use the `csrf_token_id` option instead. + + * The `csrf_provider` option is deprecated for all the authentication listeners, + and will be removed in 3.0. Use the `csrf_token_generator` option instead. + +SecurityBundle +-------------- + + * The `intention` firewall listener setting is deprecated, and will be removed in 3.0. + Use the `csrf_token_id` option instead. + + * The `csrf_provider` firewall listener setting is deprecated, and will be removed in 3.0. + Use the `csrf_token_generator` option instead. + Config ------ @@ -505,3 +681,74 @@ Config Additionally, if you have implemented cache validation strategies *using* `isFresh()` yourself, you should have a look at the new cache validation system based on `ResourceChecker`s. + +Yaml +---- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of `@`, `` ` ``, `|`, and `>` at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +HttpFoundation +-------------- + + * Deprecated finding deep items in `ParameterBag::get()`. This may affect you + when getting parameters from the `Request` class: + + Before: + + ```php + $request->query->get('foo[bar]', null, true); + ``` + + After: + + ```php + $request->query->get('foo')['bar']; + ``` + +Routing +------- + + * Deprecated the hardcoded value for the `$referenceType` argument of the `UrlGeneratorInterface::generate` method. + Use the constants defined in the `UrlGeneratorInterface` instead. + + Before: + + ```php + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), true); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + +DoctrineBridge +-------------- + * Deprecated using the entity provider with a Doctrine repository implementing `UserProviderInterface`. + Make it implement `UserLoaderInterface` instead. diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 55746215c1b7b..1d97d78a82f45 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1,6 +1,35 @@ UPGRADE FROM 2.x to 3.0 ======================= +# Table of Contents + +- [ClassLoader](#classloader) +- [Config](#config) +- [Console](#console) +- [DependencyInjection](#dependencyinjection) +- [DoctrineBridge](#doctrinebridge) +- [DomCrawler](#domcrawler) +- [EventDispatcher](#eventdispatcher) +- [Form](#form) +- [FrameworkBundle](#frameworkbundle) +- [HttpFoundation](#httpfoundation) +- [HttpKernel](#httpkernel) +- [Locale](#locale) +- [Monolog Bridge](#monolog-bridge) +- [Process](#process) +- [PropertyAccess](#propertyaccess) +- [Routing](#routing) +- [Security](#security) +- [SecurityBundle](#securitybundle) +- [Serializer](#serializer) +- [Swiftmailer Bridge](#swiftmailer-bridge) +- [Translator](#translator) +- [Twig Bridge](#twig-bridge) +- [TwigBundle](#twigbundle) +- [Validator](#validator) +- [WebProfiler](#webprofiler) +- [Yaml](#yaml) + ### ClassLoader * The `UniversalClassLoader` class has been removed in favor of @@ -10,8 +39,8 @@ UPGRADE FROM 2.x to 3.0 | -------- | --- | `registerNamespaces()` | `addPrefixes()` | `registerPrefixes()` | `addPrefixes()` - | `registerNamespaces()` | `addPrefix()` - | `registerPrefixes()` | `addPrefix()` + | `registerNamespace()` | `addPrefix()` + | `registerPrefix()` | `addPrefix()` | `getNamespaces()` | `getPrefixes()` | `getNamespaceFallbacks()` | `getFallbackDirs()` | `getPrefixFallbacks()` | `getFallbackDirs()` @@ -20,6 +49,16 @@ UPGRADE FROM 2.x to 3.0 `DebugClassLoader`. The difference is that the constructor now takes a loader to wrap. +### Config + + * `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` has been removed. Also, + cache validation through this method (which was still supported in 2.8 for BC) does no longer + work because the `\Symfony\Component\Config\Resource\BCResourceInterfaceChecker` helper class + has been removed as well. + + * The `__toString()` method of the `\Symfony\Component\Config\ConfigCache` class + was removed in favor of the new `getPath()` method. + ### Console * The `dialog` helper has been removed in favor of the `question` helper. @@ -88,8 +127,87 @@ UPGRADE FROM 2.x to 3.0 $table->render(); ``` +* Parameters of `renderException()` method of the + `Symfony\Component\Console\Application` are type hinted. + You must add the type hint to your implementations. + ### DependencyInjection + * The method `remove` was added to `Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface`. + + * The concept of scopes was removed, the removed methods are: + + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopes()` + - `Symfony\Component\DependencyInjection\ContainerBuilder::getScopeChildren()` + - `Symfony\Component\DependencyInjection\ContainerInterface::enterScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::leaveScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::addScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::hasScope()` + - `Symfony\Component\DependencyInjection\ContainerInterface::isScopeActive()` + - `Symfony\Component\DependencyInjection\Definition::setScope()` + - `Symfony\Component\DependencyInjection\Definition::getScope()` + - `Symfony\Component\DependencyInjection\Reference::isStrict()` + + Also, the `$scope` and `$strict` parameters of `Symfony\Component\DependencyInjection\ContainerInterface::set()` + and `Symfony\Component\DependencyInjection\Reference` respectively were removed. + + * A new `shared` flag has been added to the service definition + in replacement of the `prototype` scope. + + Before: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setScope(ContainerBuilder::SCOPE_PROTOTYPE) + ; + ``` + + ```yml + services: + foo: + class: stdClass + scope: prototype + ``` + + ```xml + + + + ``` + + After: + + ```php + use Symfony\Component\DependencyInjection\ContainerBuilder; + + $container = new ContainerBuilder(); + $container + ->register('foo', 'stdClass') + ->setShared(false) + ; + ``` + + ```yml + services: + foo: + class: stdClass + shared: false + ``` + + ```xml + + + + ``` + + * `Symfony\Component\DependencyInjection\ContainerAware` was removed, use + `Symfony\Component\DependencyInjection\ContainerAwareTrait` or implement + `Symfony\Component\DependencyInjection\ContainerAwareInterface` manually + * The methods `Definition::setFactoryClass()`, `Definition::setFactoryMethod()`, and `Definition::setFactoryService()` have been removed in favor of `Definition::setFactory()`. Services defined using @@ -99,19 +217,287 @@ UPGRADE FROM 2.x to 3.0 removed: `ContainerBuilder::synchronize()`, `Definition::isSynchronized()`, and `Definition::setSynchronized()`. +### DomCrawler + + * The interface of the `Symfony\Component\DomCrawler\Crawler` changed. It does no longer implement `\Iterator` but `\IteratorAggregate`. If you rely on methods of the `\Iterator` interface, call the `getIterator` method of the `\IteratorAggregate` interface before. No changes are required in a `\Traversable`-aware control structure, such as `foreach`. + + Before: + + ```php + $crawler->current(); + ``` + + After: + + ```php + $crawler->getIterator()->current(); + ``` + +### DoctrineBridge + + * The `property` option of `DoctrineType` was removed in favor of the `choice_label` option. + + * The `loader` option of `DoctrineType` was removed. You now have to override the `getLoader()` + method in your custom type. + + * The `Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList` was removed in favor + of `Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader`. + + * Passing a query builder closure to `ORMQueryBuilderLoader` is not supported anymore. + You should pass resolved query builders only. + + Consequently, the arguments `$manager` and `$class` of `ORMQueryBuilderLoader` + have been removed as well. + + Note that the `query_builder` option of `DoctrineType` *does* support + closures, but the closure is now resolved in the type instead of in the + loader. + + * Using the entity provider with a Doctrine repository implementing `UserProviderInterface` is not supported anymore. + You should make the repository implement `UserLoaderInterface` instead. + ### EventDispatcher + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. * The interface `Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface` extends `Symfony\Component\EventDispatcher\EventDispatcherInterface`. ### Form + * The `getBlockPrefix()` method was added to the `FormTypeInterface` in replacement of + the `getName()` method which has been removed. + + * The `configureOptions()` method was added to the `FormTypeInterface` in replacement + of the `setDefaultOptions()` method which has been removed. + + * The `getBlockPrefix()` method was added to the `ResolvedFormTypeInterface` in + replacement of the `getName()` method which has been removed. + + * The option `options` of the `CollectionType` has been removed in favor + of the `entry_options` option. + + * The `cascade_validation` option was removed. Use the `constraints` option + together with the `Valid` constraint instead. + + * Type names were removed. Instead of referencing types by name, you must + reference them by their fully-qualified class name (FQCN) instead: + + Before: + + ```php + $form = $this->createFormBuilder() + ->add('name', 'text') + ->add('age', 'integer') + ->getForm(); + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\IntegerType; + use Symfony\Component\Form\Extension\Core\Type\TextType; + + $form = $this->createFormBuilder() + ->add('name', TextType::class) + ->add('age', IntegerType::class) + ->getForm(); + ``` + + If you want to customize the block prefix of a type in Twig, you must now + implement `FormTypeInterface::getBlockPrefix()`: + + Before: + + ```php + class UserProfileType extends AbstractType + { + public function getName() + { + return 'profile'; + } + } + ``` + + After: + + ```php + class UserProfileType extends AbstractType + { + public function getBlockPrefix() + { + return 'profile'; + } + } + ``` + + If you don't customize `getBlockPrefix()`, it defaults to the class name + without "Type" suffix in underscore notation (here: "user_profile"). + + Type extension must return the fully-qualified class name of the extended + type from `FormTypeExtensionInterface::getExtendedType()` now. + + Before: + + ```php + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return 'form'; + } + } + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + class MyTypeExtension extends AbstractTypeExtension + { + public function getExtendedType() + { + return FormType::class; + } + } + ``` + + * The `FormTypeInterface::getName()` method was removed. + + * Returning type instances from `FormTypeInterface::getParent()` is not + supported anymore. Return the fully-qualified class name of the parent + type class instead. + + Before: + + ```php + class MyType + { + public function getParent() + { + return new ParentType(); + } + } + ``` + + After: + + ```php + class MyType + { + public function getParent() + { + return ParentType::class; + } + } + ``` + + * The option `type` of the `CollectionType` has been removed in favor of + the `entry_type` option. The value for the `entry_type` option must be + the fully-qualified class name (FQCN). + + * Passing type instances to `Form::add()`, `FormBuilder::add()` and the + `FormFactory::create*()` methods is not supported anymore. Pass the + fully-qualified class name of the type instead. + + Before: + + ```php + $form = $this->createForm(new MyType()); + ``` + + After: + + ```php + $form = $this->createForm(MyType::class); + ``` + + * Passing custom data to forms now needs to be done + through the options resolver. + + In the controller: + + Before: + ```php + $form = $this->createForm(new MyType($variable), $entity, array( + 'action' => $this->generateUrl('action_route'), + 'method' => 'PUT', + )); + ``` + After: + ```php + $form = $this->createForm(MyType::class, $entity, array( + 'action' => $this->generateUrl('action_route'), + 'method' => 'PUT', + 'custom_value' => $variable, + )); + ``` + In the form type: + + Before: + ```php + class MyType extends AbstractType + { + private $value; + + public function __construct($variableValue) + { + $this->value = $value; + } + // ... + } + ``` + + After: + ```php + public function buildForm(FormBuilderInterface $builder, array $options) + { + $value = $options['custom_value']; + // ... + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'custom_value' => null, + )); + } + ``` + + * The alias option of the `form.type_extension` tag was removed in favor of + the `extended_type`/`extended-type` option. + + Before: + ```xml + + + + ``` + + After: + ```xml + + + + ``` + + * The `max_length` option was removed. Use the `attr` option instead by setting it to + an `array` with a `maxlength` key. + + * The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`, + `FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed. + + * The `choice_list` option of `ChoiceType` was removed. + * The option "precision" was renamed to "scale". Before: ```php - $builder->add('length', 'number', array( + use Symfony\Component\Form\Extension\Core\Type\NumberType; + + $builder->add('length', NumberType::class, array( 'precision' => 3, )); ``` @@ -119,11 +505,35 @@ UPGRADE FROM 2.x to 3.0 After: ```php - $builder->add('length', 'number', array( + use Symfony\Component\Form\Extension\Core\Type\NumberType; + + $builder->add('length', NumberType::class, array( 'scale' => 3, )); ``` + * The option "`virtual`" was renamed to "`inherit_data`". + + Before: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + $builder->add('address', FormType::class, array( + 'virtual' => true, + )); + ``` + + After: + + ```php + use Symfony\Component\Form\Extension\Core\Type\FormType; + + $builder->add('address', FormType::class, array( + 'inherit_data' => true, + )); + ``` + * The method `AbstractType::setDefaultOptions(OptionsResolverInterface $resolver)` and `AbstractTypeExtension::setDefaultOptions(OptionsResolverInterface $resolver)` have been renamed. You should use `AbstractType::configureOptions(OptionsResolver $resolver)` and @@ -185,42 +595,42 @@ UPGRADE FROM 2.x to 3.0 } } ``` - - * The events `PRE_BIND`, `BIND` and `POST_BIND` were renamed to `PRE_SUBMIT`, `SUBMIT` - and `POST_SUBMIT`. + + If the form is submitted with a different request method than `POST`, you need to configure this in the form: Before: ```php - $builder->addEventListener(FormEvents::PRE_BIND, function (FormEvent $event) { - // ... - }); + $form = $this->createForm(FormType::class, $entity); + $form->submit($request); ``` After: ```php - $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { - // ... - }); + $form = $this->createForm(FormType::class, $entity, [ + 'method' => 'PUT', + ]); + $form->handleRequest($request); ``` - * The option "`virtual`" was renamed to "`inherit_data`". + * The events `PRE_BIND`, `BIND` and `POST_BIND` were renamed to `PRE_SUBMIT`, `SUBMIT` + and `POST_SUBMIT`. Before: ```php - $builder->add('address', 'form', array( - 'virtual' => true, - )); + $builder->addEventListener(FormEvents::PRE_BIND, function (FormEvent $event) { + // ... + }); ``` After: ```php - $builder->add('address', 'form', array( - 'inherit_data' => true, - )); + $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) { + // ... + }); ``` * The class `VirtualFormAwareIterator` was renamed to `InheritDataAwareIterator`. @@ -265,17 +675,23 @@ UPGRADE FROM 2.x to 3.0 } ``` - * The `FormIntegrationTestCase` and `FormPerformanceTestCase` classes were moved form the `Symfony\Component\Form\Tests` namespace to the `Symfony\Component\Form\Test` namespace. + * The option "options" of the CollectionType has been renamed to "entry_options". + + * The option "type" of the CollectionType has been renamed to "entry_type". + As a value for the option you must provide the fully-qualified class name (FQCN) + now as well. + + * The `FormIntegrationTestCase` and `FormPerformanceTestCase` classes were moved from the `Symfony\Component\Form\Tests` namespace to the `Symfony\Component\Form\Test` namespace. * The constants `ROUND_HALFEVEN`, `ROUND_HALFUP` and `ROUND_HALFDOWN` in class `NumberToLocalizedStringTransformer` were renamed to `ROUND_HALF_EVEN`, `ROUND_HALF_UP` and `ROUND_HALF_DOWN`. - * The methods `ChoiceListInterface::getIndicesForChoices()` and - `ChoiceListInterface::getIndicesForValues()` were removed. No direct - replacement exists, although in most cases - `ChoiceListInterface::getChoicesForValues()` and - `ChoiceListInterface::getValuesForChoices()` should be sufficient. + * The `Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface` was + removed in favor of `Symfony\Component\Form\ChoiceList\ChoiceListInterface`. + + * `Symfony\Component\Form\Extension\Core\View\ChoiceView` was removed in favor of + `Symfony\Component\Form\ChoiceList\View\ChoiceView`. * The interface `Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface` and all of its implementations were removed. Use the new interface @@ -312,6 +728,12 @@ UPGRADE FROM 2.x to 3.0 * The `Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList` class has been removed in favor of `Symfony\Component\Form\ChoiceList\ArrayChoiceList`. + * The `TimezoneType::getTimezones()` method was removed. You should not use + this method. + + * The `Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList` class has been removed in + favor of `Symfony\Component\Form\ChoiceList\ArrayChoiceList`. + ### FrameworkBundle * The `config:debug`, `container:debug`, `router:debug`, `translation:debug` @@ -319,6 +741,11 @@ UPGRADE FROM 2.x to 3.0 be removed in Symfony 3.0. Use the `debug:config`, `debug:container`, `debug:router`, `debug:translation` and `lint:yaml` commands instead. + * The base `Controller`class is now abstract. + + * The visibility of all methods of the base `Controller` class has been changed from + `public` to `protected`. + * The `getRequest` method of the base `Controller` class has been deprecated since Symfony 2.4 and must be therefore removed in 3.0. The only reliable way to get the `Request` object is to inject it in the action method. @@ -354,54 +781,45 @@ UPGRADE FROM 2.x to 3.0 } ``` - * The `request` service was removed. You must inject the `request_stack` - service instead. - - * The `templating.helper.assets` was removed in Symfony 3.0. You should - use the `assets.package` service instead. + * In Symfony 2.7 a small BC break was introduced with the new choices_as_values + option. In order to have the choice values populated to the html value attribute + you had to define the choice_value option. This is now not any more needed. Before: ```php - use Symfony\Component\Templating\Helper\CoreAssetsHelper; - - class DemoService - { - private $assetsHelper; - - public function __construct(CoreAssetsHelper $assetsHelper) - { - $this->assetsHelper = $assetsHelper; - } - - public function testMethod() - { - return $this->assetsHelper->getUrl('thumbnail.png', null, $this->assetsHelper->getVersion()); - } - } + $form->add('status', 'choice', array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ), + // choices_as_values defaults to true in Symfony 3.0 + // and setting it to anything else is deprecated as of 3.0 + 'choices_as_values' => true, + // important if you rely on your option value attribute (e.g. for JavaScript) + // this will keep the same functionality as before + 'choice_value' => function ($choice) { + return $choice; + }, + )); ``` After: ```php - use Symfony\Component\Asset\Packages; - - class DemoService - { - private $assetPackages; - - public function __construct(Packages $assetPackages) - { - $this->assetPackages = $assetPackages; - } - - public function testMethod() - { - return $this->assetPackages->getUrl('thumbnail.png').$this->assetPackages->getVersion(); - } - } + $form->add('status', ChoiceType::class, array( + 'choices' => array( + 'Enabled' => Status::ENABLED, + 'Disabled' => Status::DISABLED, + 'Ignored' => Status::IGNORED, + ) + )); ``` + * The `request` service was removed. You must inject the `request_stack` + service instead. + * The `enctype` method of the `form` helper was removed. You should use the new method `start` instead. @@ -454,6 +872,8 @@ UPGRADE FROM 2.x to 3.0 * The `RouterApacheDumperCommand` was removed. + * The `createEsi` method of `Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache` was removed. Use `createSurrogate` instead. + * The `templating.helper.router` service was moved to `templating_php.xml`. You have to ensure that the PHP templating engine is enabled to be able to use it: @@ -463,12 +883,61 @@ UPGRADE FROM 2.x to 3.0 engines: ['php'] ``` + * The assets settings under `framework.templating` were moved to `framework.assets`. + + Before: + + ```yml + framework: + templating: + assets_version: 'v123' + assets_version_format: '%%s?version=%%s' + assets_base_urls: + http: ['http://cdn.example.com'] + ssl: ['https://secure.example.com'] + packages: + # ... + ``` + + After: + + ```yml + framework: + assets: + version: 'v123' + version_format: '%%s?version=%%s' + base_urls: ['http://cdn.example.com', 'https://secure.example.com'] + packages: + # ... + ``` + * The `form.csrf_provider` service is removed as it implements an adapter for the new token manager to the deprecated `Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface` interface. The `security.csrf.token_manager` should be used instead. + * The `validator.mapping.cache.apc` service has been removed in favor of the `validator.mapping.cache.doctrine.apc` one. + + * The ability to pass `apc` as the `framework.validation.cache` configuration key value has been removed. + Use `validator.mapping.cache.doctrine.apc` instead: + + Before: + + ```yaml + framework: + validation: + cache: apc + ``` + + After: + + ```yaml + framework: + validation: + cache: validator.mapping.cache.doctrine.apc + ``` + ### HttpKernel * The `Symfony\Component\HttpKernel\Log\LoggerInterface` has been removed in @@ -544,7 +1013,7 @@ UPGRADE FROM 2.x to 3.0 * Some route settings have been renamed: - * The `pattern` setting for a route has been deprecated in favor of `path` + * The `pattern` setting has been removed in favor of `path` * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings Before: @@ -597,11 +1066,45 @@ UPGRADE FROM 2.x to 3.0 the performance gains were minimal and it's hard to replicate the behaviour of PHP implementation. + * The `getMatcherDumperInstance()` and `getGeneratorDumperInstance()` methods in the + `Symfony\Component\Routing\Router` have been changed from `public` to `protected`. + + * Use the constants defined in the UrlGeneratorInterface for the $referenceType argument of the UrlGeneratorInterface::generate method. + + Before: + + ```php + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), true); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), true); + ``` + + After: + + ```php + use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + // url generated in controller + $this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + + // url generated in @router service + $router->generate('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL); + ``` + ### Security + * The `vote()` method from the `VoterInterface` was changed to now accept arbitrary + types and not only objects. You can rely on the new abstract `Voter` class introduced + in 2.8 to ease integrating your own voters. + + * The `AbstractVoter` class was removed in favor of the new `Voter` class. + * The `Resources/` directory was moved to `Core/Resources/` - * The `key` settings of `anonymous` and `remember_me` are renamed to `secret`. + * The `key` settings of `anonymous`, `remember_me` and `http_digest` are + renamed to `secret`. Before: @@ -614,6 +1117,8 @@ UPGRADE FROM 2.x to 3.0 anonymous: { key: "%secret%" } remember_me: key: "%secret%" + http_digest: + key: "%secret%" ``` ```xml @@ -626,6 +1131,7 @@ UPGRADE FROM 2.x to 3.0 + ``` @@ -638,6 +1144,7 @@ UPGRADE FROM 2.x to 3.0 // ... 'anonymous' => array('key' => '%secret%'), 'remember_me' => array('key' => '%secret%'), + 'http_digest' => array('key' => '%secret%'), ), )); ``` @@ -653,6 +1160,8 @@ UPGRADE FROM 2.x to 3.0 anonymous: { secret: "%secret%" } remember_me: secret: "%secret%" + http_digest: + secret: "%secret%" ``` ```xml @@ -665,6 +1174,7 @@ UPGRADE FROM 2.x to 3.0 + ``` @@ -677,12 +1187,20 @@ UPGRADE FROM 2.x to 3.0 // ... 'anonymous' => array('secret' => '%secret%'), 'remember_me' => array('secret' => '%secret%'), + 'http_digest' => array('secret' => '%secret%'), ), )); - ``` + ``` + + * The `AbstractVoter` class was removed. Instead, extend the new `Voter` class, + introduced in 2.8, and move your voting logic to the to the `supports($attribute, $subject)` + and `voteOnAttribute($attribute, $object, TokenInterface $token)` methods. + + * The `vote()` method from the `VoterInterface` was changed to now accept arbitrary + types, and not only objects. - * The `AbstractVoter::getSupportedAttributes()` and `AbstractVoter::getSupportedClasses()` - methods have been removed in favor of `AbstractVoter::supports()`. + * The `supportsClass` and `supportsAttribute` methods were + removed from the `VoterInterface` interface. Before: @@ -706,22 +1224,146 @@ UPGRADE FROM 2.x to 3.0 After: ```php - class MyVoter extends AbstractVoter + use Symfony\Component\Security\Core\Authorization\Voter\Voter; + + class MyVoter extends Voter { protected function supports($attribute, $object) { return $object instanceof Post && in_array($attribute, array('CREATE', 'EDIT')); } + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + // Return true or false + } + } + ``` + + * The `AbstractVoter::isGranted()` method has been replaced by `Voter::voteOnAttribute()`. + + Before: + + ```php + class MyVoter extends AbstractVoter + { + protected function isGranted($attribute, $object, $user = null) + { + return 'EDIT' === $attribute && $user === $object->getAuthor(); + } + // ... } ``` + After: + + ```php + class MyVoter extends Voter + { + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + return 'EDIT' === $attribute && $token->getUser() === $object->getAuthor(); + } + + // ... + } + ``` + + * The `supportsAttribute()` and `supportsClass()` methods of the `AuthenticatedVoter`, `ExpressionVoter`, + and `RoleVoter` classes have been removed. + + * The `intention` option was renamed to `csrf_token_id` for all the authentication listeners. + + * The `csrf_provider` option was renamed to `csrf_token_generator` for all the authentication listeners. + +### SecurityBundle + + * The `intention` firewall listener setting was renamed to `csrf_token_id`. + + * The `csrf_provider` firewall listener setting was renamed to `csrf_token_generator`. + +### Serializer + + * The `setCamelizedAttributes()` method of the + `Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer` and + `Symfony\Component\Serializer\Normalizer\PropertyNormalizer` classes + was removed. + + * The `Symfony\Component\Serializer\Exception\Exception` interface was removed + in favor of the new `Symfony\Component\Serializer\Exception\ExceptionInterface`. + ### Translator * The `Translator::setFallbackLocale()` method has been removed in favor of `Translator::setFallbackLocales()`. + * The visibility of the `locale` property has been changed from protected to private. Rely on `getLocale` and `setLocale` + instead. + + Before: + + ```php + class CustomTranslator extends Translator + { + public function fooMethod() + { + // get locale + $locale = $this->locale; + + // update locale + $this->locale = $locale; + } + } + ``` + + After: + + ```php + class CustomTranslator extends Translator + { + public function fooMethod() + { + // get locale + $locale = $this->getLocale(); + + // update locale + $this->setLocale($locale); + } + } + ``` + + * The method `FileDumper::format()` was removed. You should use + `FileDumper::formatCatalogue()` instead. + + Before: + + ```php + class CustomDumper extends FileDumper + { + protected function format(MessageCatalogue $messages, $domain) + { + ... + } + } + ``` + + After: + + ```php + class CustomDumper extends FileDumper + { + public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array()) + { + ... + } + } + ``` + + * The `getMessages()` method of the `Symfony\Component\Translation\Translator` + class was removed. You should use the `getCatalogue()` method of the + `Symfony\Component\Translation\TranslatorBagInterface`. + ### Twig Bridge * The `twig:lint` command has been deprecated since Symfony 2.7 and will be @@ -781,11 +1423,18 @@ UPGRADE FROM 2.x to 3.0 ### TwigBundle + * The `Symfony\Bundle\TwigBundle\TwigDefaultEscapingStrategy` was removed + in favor of `Twig_FileExtensionEscapingStrategy`. + * The `twig:debug` command has been deprecated since Symfony 2.7 and will be removed in Symfony 3.0. Use the `debug:twig` command instead. ### Validator + * The PHP7-incompatible constraints (`Null`, `True`, `False`) and their related + validators (`NullValidator`, `TrueValidator`, `FalseValidator`) have been + removed in favor of their `Is`-prefixed equivalent. + * The class `Symfony\Component\Validator\Mapping\Cache\ApcCache` has been removed in favor of `Symfony\Component\Validator\Mapping\Cache\DoctrineCache`. @@ -1219,6 +1868,24 @@ UPGRADE FROM 2.x to 3.0 ### Yaml + * Using a colon in an unquoted mapping value leads to a `ParseException`. + * Starting an unquoted string with `@`, `` ` ``, `|`, or `>` leads to a `ParseException`. + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) leads to a `ParseException`. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + + * The ability to pass file names to `Yaml::parse()` has been removed. Before: @@ -1233,15 +1900,62 @@ UPGRADE FROM 2.x to 3.0 Yaml::parse(file_get_contents($fileName)); ``` +### WebProfiler + + * The `profiler:import` and `profiler:export` commands have been removed. + + * All the profiler storages different than `FileProfilerStorage` have been + removed. The removed classes are: + + - `Symfony\Component\HttpKernel\Profiler\BaseMemcacheProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MemcachedProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MemcacheProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\MysqlProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\PdoProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\RedisProfilerStorage` + - `Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage` + ### Process * `Process::setStdin()` and `Process::getStdin()` have been removed. Use `Process::setInput()` and `Process::getInput()` that works the same way. * `Process::setInput()` and `ProcessBuilder::setInput()` do not accept non-scalar types. -### Config +### Monolog Bridge - * `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` has been removed. Also, - cache validation through this method (which was still supported in 2.8 for BC) does no longer - work because the `\Symfony\Component\Config\Resource\BCResourceInterfaceChecker` helper class - has been removed as well. + * `Symfony\Bridge\Monolog\Logger::emerg()` was removed. Use `emergency()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::crit()` was removed. Use `critical()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::err()` was removed. Use `error()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::warn()` was removed. Use `warning()` which is PSR-3 compatible. + +### Swiftmailer Bridge + + * `Symfony\Bridge\Swiftmailer\DataCollector\MessageDataCollector` was removed. Use the `Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector` class instead. + +### HttpFoundation + + * The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY" + + * `Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface` no longer implements the `IteratorAggregate` interface. Use the `all()` method instead of iterating over the flash bag. + + * Removed the feature that allowed finding deep items in `ParameterBag::get()`. + This may affect you when getting parameters from the `Request` class: + + Before: + + ```php + $request->query->get('foo[bar]', null, true); + ``` + + After: + + ```php + $request->query->get('foo')['bar']; + ``` +### Monolog Bridge + + * `Symfony\Bridge\Monolog\Logger::emerg()` was removed. Use `emergency()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::crit()` was removed. Use `critical()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::err()` was removed. Use `error()` which is PSR-3 compatible. + * `Symfony\Bridge\Monolog\Logger::warn()` was removed. Use `warning()` which is PSR-3 compatible. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 97f9b6663a109..0000000000000 --- a/appveyor.yml +++ /dev/null @@ -1,64 +0,0 @@ -build: false -shallow_clone: true -platform: x86 -clone_folder: c:\projects\symfony - -cache: - - c:\php -> appveyor.yml - - .phpunit -> phpunit - -init: - - SET PATH=c:\php;%PATH% - - SET COMPOSER_NO_INTERACTION=1 - - SET SYMFONY_DEPRECATIONS_HELPER=strict - - SET PHP=1 - - SET ANSICON=121x90 (121x90) - - SET SYMFONY_PHPUNIT_SKIPPED_TESTS=phpunit.skipped - -install: - - IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php) - - cd c:\php - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-5.3.11-nts-Win32-VC9-x86.zip - - IF %PHP%==1 7z x php-5.3.11-nts-Win32-VC9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://nebm.ist.utl.pt/~glopes/misc/intl_win/ICU-51.2-dlls.zip - - IF %PHP%==1 7z x ICU-51.2-dlls.zip -y >nul - - IF %PHP%==1 del /Q *.zip - - IF %PHP%==1 cd ext - - IF %PHP%==1 appveyor DownloadFile http://nebm.ist.utl.pt/~glopes/misc/intl_win/php_intl-3.0.0-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_intl-3.0.0-5.3-nts-vc9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.7/php_apcu-4.0.7-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_apcu-4.0.7-5.3-nts-vc9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/memcache/3.0.8/php_memcache-3.0.8-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_memcache-3.0.8-5.3-nts-vc9-x86.zip -y >nul - - IF %PHP%==1 del /Q *.zip - - IF %PHP%==1 cd .. - - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat - - IF %PHP%==1 copy /Y php.ini-development php.ini-min - - IF %PHP%==1 echo max_execution_time=1200 >> php.ini-min - - IF %PHP%==1 echo date.timezone="UTC" >> php.ini-min - - IF %PHP%==1 echo extension_dir=ext >> php.ini-min - - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini-min - - IF %PHP%==1 copy /Y php.ini-min php.ini-max - - IF %PHP%==1 echo extension=php_apcu.dll >> php.ini-max - - IF %PHP%==1 echo apc.enable_cli=1 >> php.ini-max - - IF %PHP%==1 echo extension=php_memcache.dll >> php.ini-max - - IF %PHP%==1 echo extension=php_intl.dll >> php.ini-max - - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini-max - - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini-max - - IF %PHP%==1 echo extension=php_pdo_sqlite.dll >> php.ini-max - - IF %PHP%==1 echo extension=php_ldap.dll >> php.ini-max - - appveyor DownloadFile https://getcomposer.org/composer.phar - - copy /Y php.ini-max php.ini - - cd c:\projects\symfony - - php phpunit install - - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - - composer update --prefer-source --no-progress --ansi - -test_script: - - cd c:\projects\symfony - - SET X=0 - - copy /Y c:\php\php.ini-min c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=1 - - copy /Y c:\php\php.ini-max c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=1 - - exit %X% diff --git a/composer.json b/composer.json index d7a70e81c0975..f09d3dfedbf32 100644 --- a/composer.json +++ b/composer.json @@ -17,10 +17,13 @@ ], "require": { "php": ">=5.3.9", + "ext-xml": "*", "doctrine/common": "~2.4", - "twig/twig": "~1.20|~2.0", + "twig/twig": "~1.34|~2.4", "psr/log": "~1.0", - "symfony/security-acl": "~2.7", + "symfony/security-acl": "~2.7|~3.0.0", + "symfony/polyfill-apcu": "~1.1", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php54": "~1.0", @@ -78,17 +81,21 @@ "symfony/yaml": "self.version" }, "require-dev": { + "doctrine/annotations": "~1.0", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", "doctrine/orm": "~2.4,>=2.4.5", "doctrine/doctrine-bundle": "~1.2", "monolog/monolog": "~1.11", - "ocramius/proxy-manager": "~0.4|~1.0", - "egulias/email-validator": "~1.2", - "phpdocumentor/reflection": "^1.0.7" + "ocramius/proxy-manager": "~0.4|~1.0|~2.0", + "symfony/phpunit-bridge": "~3.4|~4.0", + "egulias/email-validator": "~1.2,>=1.2.1", + "phpdocumentor/reflection": "^1.0.7", + "sensio/framework-extra-bundle": "^3.0.2" }, "conflict": { - "phpdocumentor/reflection": "<1.0.7" + "phpdocumentor/reflection": "<1.0.7", + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" }, "autoload": { "psr-4": { @@ -102,8 +109,14 @@ }, "classmap": [ "src/Symfony/Component/Intl/Resources/stubs" + ], + "exclude-from-classmap": [ + "**/Tests/" ] }, + "autoload-dev": { + "files": [ "src/Symfony/Component/VarDumper/Resources/functions/dump.php" ] + }, "minimum-stability": "dev", "extra": { "branch-alias": { diff --git a/link b/link new file mode 100755 index 0000000000000..6a2ec15e22102 --- /dev/null +++ b/link @@ -0,0 +1,71 @@ +#!/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(); +$braces = array('Bundle', 'Bridge', 'Component', 'Component/Security'); +$directories = call_user_func_array('array_merge', array_values(array_map(function ($part) { + return glob(__DIR__.'/src/Symfony/'.$part.'/*', GLOB_ONLYDIR | GLOB_NOSORT); +}, $braces))); + +foreach ($directories 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 79810f626e87f..f4b80ed064121 100755 --- a/phpunit +++ b/phpunit @@ -1,192 +1,14 @@ #!/usr/bin/env php = 70000 ? '5.0' : '4.8'; -$PHPUNIT_DIR = __DIR__.'/.phpunit'; -$PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php'; - -if (!file_exists($COMPOSER = __DIR__.'/composer.phar')) { - $COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? `where.exe composer.phar` : (`which composer.phar` ?: `which composer`)); - if (!file_exists($COMPOSER)) { - stream_copy_to_stream( - fopen('https://getcomposer.org/composer.phar', 'rb'), - fopen($COMPOSER = __DIR__.'/composer.phar', 'wb') - ); - } +if (!file_exists(__DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit')) { + echo "Unable to find the `simple-phpunit` script in `vendor/symfony/phpunit-bridge/bin/`.\nPlease run `composer update` before running this command.\n"; + exit(1); } - -$PHP = ProcessUtils::escapeArgument($PHP); -$COMPOSER = $PHP.' '.ProcessUtils::escapeArgument($COMPOSER); - -if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__) !== @file_get_contents("$PHPUNIT_DIR/.md5")) { - // Build a standalone phpunit without symfony/yaml - - $oldPwd = getcwd(); - @mkdir($PHPUNIT_DIR); - chdir($PHPUNIT_DIR); - if (file_exists("phpunit-$PHPUNIT_VERSION")) { - 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')) { - stream_copy_to_stream(fopen("https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip", 'rb'), fopen("$PHPUNIT_VERSION.zip", 'wb')); - } else { - @unlink("$PHPUNIT_VERSION.zip"); - passthru("wget https://github.com/sebastianbergmann/phpunit/archive/$PHPUNIT_VERSION.zip"); - } - $zip = new ZipArchive(); - $zip->open("$PHPUNIT_VERSION.zip"); - $zip->extractTo(getcwd()); - $zip->close(); - chdir("phpunit-$PHPUNIT_VERSION"); - passthru("$COMPOSER remove --no-update symfony/yaml"); - passthru("$COMPOSER require --dev --no-update symfony/phpunit-bridge \">=2.8@dev\""); - passthru("$COMPOSER install --prefer-source --no-progress --ansi"); - file_put_contents('phpunit', <<nul': 'rm -rf %s', str_replace('/', DIRECTORY_SEPARATOR, "phpunit-$PHPUNIT_VERSION/vendor/symfony/phpunit-bridge"))); - symlink(realpath('../src/Symfony/Bridge/PhpUnit'), "phpunit-$PHPUNIT_VERSION/vendor/symfony/phpunit-bridge"); - } - file_put_contents('.md5', md5_file(__FILE__)); - chdir($oldPwd); - -} - -$cmd = array_map('Symfony\Component\Process\ProcessUtils::escapeArgument', $argv); -$exit = 0; - -if (isset($argv[1]) && 'symfony' === $argv[1]) { - array_shift($cmd); -} - -$cmd[0] = sprintf('%s %s --colors=always', $PHP, ProcessUtils::escapeArgument("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit")); -$cmd = str_replace('%', '%%', implode(' ', $cmd)).' %1$s'; - -if ('\\' === DIRECTORY_SEPARATOR) { - $cmd = 'cmd /v:on /d /c "('.$cmd.')%2$s"'; -} else { - $cmd .= '%2$s'; -} - -if (isset($argv[1]) && 'symfony' === $argv[1]) { - // Find Symfony components in plain php for Windows portability - - $oldPwd = getcwd(); - chdir(__DIR__); - $finder = new RecursiveDirectoryIterator('src/Symfony', FilesystemIterator::KEY_AS_FILENAME | FilesystemIterator::UNIX_PATHS); - $finder = new RecursiveIteratorIterator($finder); - $finder->setMaxDepth(3); - - $skippedTests = isset($_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS']) ? $_SERVER['SYMFONY_PHPUNIT_SKIPPED_TESTS'] : false; - $runningProcs = array(); - - foreach ($finder as $file => $fileInfo) { - if ('phpunit.xml.dist' === $file) { - $component = dirname($fileInfo->getPathname()); - - // Run phpunit tests in parallel - - if ($skippedTests) { - putenv("SYMFONY_PHPUNIT_SKIPPED_TESTS=$component/$skippedTests"); - } - - $c = ProcessUtils::escapeArgument($component); - - if ($proc = proc_open(sprintf($cmd, $c, " > $c/phpunit.stdout 2> $c/phpunit.stderr"), array(), $pipes)) { - $runningProcs[$component] = $proc; - } else { - $exit = 1; - echo "\033[41mKO\033[0m $component\n\n"; - } - } - } - chdir($oldPwd); - - // Fixes for colors support on appveyor - // See https://github.com/appveyor/ci/issues/373 - $colorFixes = array( - array("S\033[0m\033[0m\033[36m\033[1mS", "E\033[0m\033[0m\033[31m\033[1mE", "I\033[0m\033[0m\033[33m\033[1mI", "F\033[0m\033[0m\033[41m\033[37mF"), - array("SS", "EE", "II", "FF"), - ); - $colorFixes[0] = array_merge($colorFixes[0], $colorFixes[0]); - $colorFixes[1] = array_merge($colorFixes[1], $colorFixes[1]); - - while ($runningProcs) { - usleep(300000); - $terminatedProcs = array(); - foreach ($runningProcs as $component => $proc) { - $procStatus = proc_get_status($proc); - if (!$procStatus['running']) { - $terminatedProcs[$component] = $procStatus['exitcode']; - unset($runningProcs[$component]); - proc_close($proc); - } - } - - foreach ($terminatedProcs as $component => $procStatus) { - foreach (array('out', 'err') as $file) { - $file = "$component/phpunit.std$file"; - - if ('\\' === DIRECTORY_SEPARATOR) { - $h = fopen($file, 'rb'); - while (false !== $line = fgets($h)) { - echo str_replace($colorFixes[0], $colorFixes[1], preg_replace( - '/(\033\[[0-9]++);([0-9]++m)(?:(.)(\033\[0m))?/', - "$1m\033[$2$3$4$4", - $line - )); - } - fclose($h); - } else { - readfile($file); - } - unlink($file); - } - - if ($procStatus) { - $exit = 1; - echo "\033[41mKO\033[0m $component\n\n"; - } else { - echo "\033[32mOK\033[0m $component\n\n"; - } - } - } -} elseif (!isset($argv[1]) || 'install' !== $argv[1]) { - // Run regular phpunit in a subprocess - - $errFile = tempnam(sys_get_temp_dir(), 'phpunit.stderr.'); - if ($proc = proc_open(sprintf($cmd, '', ' 2> '.ProcessUtils::escapeArgument($errFile)), array(1 => array('pipe', 'w')), $pipes)) { - stream_copy_to_stream($pipes[1], STDOUT); - fclose($pipes[1]); - $exit = proc_close($proc); - - readfile($errFile); - unlink($errFile); - } - - if (!file_exists($component = array_pop($argv))) { - $component = basename(getcwd()); - } - - if ($exit) { - echo "\033[41mKO\033[0m $component\n\n"; - } else { - echo "\033[32mOK\033[0m $component\n\n"; - } +if (\PHP_VERSION_ID >= 70000 && !getenv('SYMFONY_PHPUNIT_VERSION')) { + putenv('SYMFONY_PHPUNIT_VERSION=6.5'); } - -exit($exit); +putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit'); +require __DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit'; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 726b91ef23668..9adb7837d83d9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,12 @@ @@ -40,7 +42,26 @@ ./src/Symfony/Bundle/*/Resources ./src/Symfony/Component/*/Resources ./src/Symfony/Component/*/*/Resources + ./src/Symfony/Bridge/*/vendor + ./src/Symfony/Bundle/*/vendor + ./src/Symfony/Component/*/vendor + ./src/Symfony/Component/*/*/vendor + + + + + + + + Symfony\Component\Console + Symfony\Component\HttpFoundation + + + + + + diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 4d8c44701dd3a..71a2707bab709 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +2.8.0 +----- + + * deprecated using the entity provider with a Doctrine repository implementing UserProviderInterface + * added UserLoaderInterface for loading users through Doctrine. + 2.7.0 ----- diff --git a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php index b1e4f6a9d93b1..9bf22357df895 100644 --- a/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php +++ b/src/Symfony/Bridge/Doctrine/CacheWarmer/ProxyCacheWarmer.php @@ -26,11 +26,6 @@ class ProxyCacheWarmer implements CacheWarmerInterface { private $registry; - /** - * Constructor. - * - * @param ManagerRegistry $registry A ManagerRegistry instance - */ public function __construct(ManagerRegistry $registry) { $this->registry = $registry; diff --git a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php index 5d07cee69b94a..baa99fac5d3d0 100644 --- a/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php +++ b/src/Symfony/Bridge/Doctrine/ContainerAwareEventManager.php @@ -26,8 +26,6 @@ class ContainerAwareEventManager extends EventManager * Map of registered listeners. * * => - * - * @var array */ private $listeners = array(); private $initialized = array(); @@ -56,7 +54,7 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null) $initialized = isset($this->initialized[$eventName]); foreach ($this->listeners[$eventName] as $hash => $listener) { - if (!$initialized && is_string($listener)) { + if (!$initialized && \is_string($listener)) { $this->listeners[$eventName][$hash] = $listener = $this->container->get($listener); } @@ -69,9 +67,9 @@ public function dispatchEvent($eventName, EventArgs $eventArgs = null) /** * Gets the listeners of a specific event or all listeners. * - * @param string $event The name of the event. + * @param string $event The name of the event * - * @return array The event listeners for the specified event, or all event listeners. + * @return array The event listeners for the specified event, or all event listeners */ public function getListeners($event = null) { @@ -83,7 +81,7 @@ public function getListeners($event = null) * * @param string $event * - * @return bool TRUE if the specified event has any listeners, FALSE otherwise. + * @return bool TRUE if the specified event has any listeners, FALSE otherwise */ public function hasListeners($event) { @@ -93,14 +91,14 @@ public function hasListeners($event) /** * Adds an event listener that listens on the specified events. * - * @param string|array $events The event(s) to listen on. - * @param object|string $listener The listener object. + * @param string|array $events The event(s) to listen on + * @param object|string $listener The listener object * * @throws \RuntimeException */ public function addEventListener($events, $listener) { - if (is_string($listener)) { + if (\is_string($listener)) { if ($this->initialized) { throw new \RuntimeException('Adding lazy-loading listeners after construction is not supported.'); } @@ -126,7 +124,7 @@ public function addEventListener($events, $listener) */ public function removeEventListener($events, $listener) { - if (is_string($listener)) { + if (\is_string($listener)) { $hash = '_service_'.$listener; } else { // Picks the hash code related to that listener diff --git a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php index a57b9ae6ea151..65df5c5ea6966 100644 --- a/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php +++ b/src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php @@ -13,10 +13,11 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\DBAL\Logging\DebugStack; +use Doctrine\DBAL\Types\ConversionException; use Doctrine\DBAL\Types\Type; -use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; /** * DoctrineDataCollector. @@ -120,19 +121,26 @@ private function sanitizeQuery($connectionName, $query) if (null === $query['params']) { $query['params'] = array(); } - if (!is_array($query['params'])) { + if (!\is_array($query['params'])) { $query['params'] = array($query['params']); } foreach ($query['params'] as $j => $param) { if (isset($query['types'][$j])) { // Transform the param according to the type $type = $query['types'][$j]; - if (is_string($type)) { + if (\is_string($type)) { $type = Type::getType($type); } if ($type instanceof Type) { $query['types'][$j] = $type->getBindingType(); - $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + try { + $param = $type->convertToDatabaseValue($param, $this->registry->getConnection($connectionName)->getDatabasePlatform()); + } catch (\TypeError $e) { + // Error thrown while processing params, query is not explainable. + $query['explainable'] = false; + } catch (ConversionException $e) { + $query['explainable'] = false; + } } } @@ -158,11 +166,11 @@ private function sanitizeQuery($connectionName, $query) */ private function sanitizeParam($var) { - if (is_object($var)) { - return array(sprintf('Object(%s)', get_class($var)), false); + if (\is_object($var)) { + return array(sprintf('Object(%s)', \get_class($var)), false); } - if (is_array($var)) { + if (\is_array($var)) { $a = array(); $original = true; foreach ($var as $k => $v) { @@ -174,7 +182,7 @@ private function sanitizeParam($var) return array($a, $original); } - if (is_resource($var)) { + if (\is_resource($var)) { return array(sprintf('Resource(%s)', get_resource_type($var)), false); } diff --git a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php index 0b1052719f559..7ccd1df106f70 100644 --- a/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php +++ b/src/Symfony/Bridge/Doctrine/DataFixtures/ContainerAwareLoader.php @@ -25,16 +25,8 @@ */ class ContainerAwareLoader extends Loader { - /** - * @var ContainerInterface - */ private $container; - /** - * Constructor. - * - * @param ContainerInterface $container A ContainerInterface instance - */ public function __construct(ContainerInterface $container) { $this->container = $container; diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php index 063b9359c7978..7a2a7adab0cf1 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php @@ -11,12 +11,12 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** * This abstract classes groups common code that Doctrine Object Manager extensions (ORM, MongoDB, CouchDB) need. @@ -27,20 +27,16 @@ abstract class AbstractDoctrineExtension extends Extension { /** * Used inside metadata driver method to simplify aggregation of data. - * - * @var array */ protected $aliasMap = array(); /** * Used inside metadata driver method to simplify aggregation of data. - * - * @var array */ protected $drivers = array(); /** - * @param array $objectManager A configured object manager. + * @param array $objectManager A configured object manager * @param ContainerBuilder $container A ContainerBuilder instance * * @throws \InvalidArgumentException @@ -134,10 +130,7 @@ protected function setMappingDriverConfig(array $mappingConfig, $mappingName) throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName)); } - if (substr($mappingDirectory, 0, 7) !== 'phar://') { - $mappingDirectory = realpath($mappingDirectory); - } - $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = $mappingDirectory; + $this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingDirectory) ?: $mappingDirectory; } /** @@ -145,15 +138,11 @@ protected function setMappingDriverConfig(array $mappingConfig, $mappingName) * * Returns false when autodetection failed, an array of the completed information otherwise. * - * @param array $bundleConfig - * @param \ReflectionClass $bundle - * @param ContainerBuilder $container A ContainerBuilder instance - * * @return array|false */ protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \ReflectionClass $bundle, ContainerBuilder $container) { - $bundleDir = dirname($bundle->getFilename()); + $bundleDir = \dirname($bundle->getFileName()); if (!$bundleConfig['type']) { $bundleConfig['type'] = $this->detectMetadataDriver($bundleDir, $container); @@ -165,7 +154,7 @@ protected function getMappingDriverBundleConfigDefaults(array $bundleConfig, \Re } if (!$bundleConfig['dir']) { - if (in_array($bundleConfig['type'], array('annotation', 'staticphp'))) { + if (\in_array($bundleConfig['type'], array('annotation', 'staticphp'))) { $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingObjectDefaultName(); } else { $bundleConfig['dir'] = $bundleDir.'/'.$this->getMappingResourceConfigDirectory(); @@ -202,13 +191,13 @@ protected function registerMappingDrivers($objectManager, ContainerBuilder $cont if ($container->hasDefinition($mappingService)) { $mappingDriverDef = $container->getDefinition($mappingService); $args = $mappingDriverDef->getArguments(); - if ($driverType == 'annotation') { + if ('annotation' == $driverType) { $args[1] = array_merge(array_values($driverPaths), $args[1]); } else { $args[0] = array_merge(array_values($driverPaths), $args[0]); } $mappingDriverDef->setArguments($args); - } elseif ($driverType == 'annotation') { + } elseif ('annotation' == $driverType) { $mappingDriverDef = new Definition('%'.$this->getObjectManagerElementName('metadata.'.$driverType.'.class%'), array( new Reference($this->getObjectManagerElementName('metadata.annotation_reader')), array_values($driverPaths), @@ -252,7 +241,7 @@ protected function assertValidMappingConfiguration(array $mappingConfig, $object throw new \InvalidArgumentException(sprintf('Specified non-existing directory "%s" as Doctrine mapping source.', $mappingConfig['dir'])); } - if (!in_array($mappingConfig['type'], array('xml', 'yml', 'annotation', 'php', 'staticphp'))) { + if (!\in_array($mappingConfig['type'], array('xml', 'yml', 'annotation', 'php', 'staticphp'))) { throw new \InvalidArgumentException(sprintf('Can only configure "xml", "yml", "annotation", "php" or '. '"staticphp" through the DoctrineBundle. Use your own bundle to configure other metadata drivers. '. 'You can register them by adding a new driver to the '. @@ -275,17 +264,17 @@ protected function detectMetadataDriver($dir, ContainerBuilder $container) $configPath = $this->getMappingResourceConfigDirectory(); $resource = $dir.'/'.$configPath; while (!is_dir($resource)) { - $resource = dirname($resource); + $resource = \dirname($resource); } $container->addResource(new FileResource($resource)); $extension = $this->getMappingResourceExtension(); - if (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.xml')) && count($files)) { + if (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.xml')) && \count($files)) { return 'xml'; - } elseif (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.yml')) && count($files)) { + } elseif (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.yml')) && \count($files)) { return 'yml'; - } elseif (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.php')) && count($files)) { + } elseif (($files = glob($dir.'/'.$configPath.'/*.'.$extension.'.php')) && \count($files)) { return 'php'; } @@ -300,11 +289,11 @@ protected function detectMetadataDriver($dir, ContainerBuilder $container) /** * Loads a configured object manager metadata, query or result cache driver. * - * @param array $objectManager A configured object manager. - * @param ContainerBuilder $container A ContainerBuilder instance. + * @param array $objectManager A configured object manager + * @param ContainerBuilder $container A ContainerBuilder instance * @param string $cacheName * - * @throws \InvalidArgumentException In case of unknown driver type. + * @throws \InvalidArgumentException in case of unknown driver type */ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerBuilder $container, $cacheName) { @@ -314,10 +303,10 @@ protected function loadObjectManagerCacheDriver(array $objectManager, ContainerB /** * Loads a cache driver. * - * @param string $cacheDriverServiceId The cache driver name. - * @param string $objectManagerName The object manager name. - * @param array $cacheDriver The cache driver mapping. - * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container The ContainerBuilder instance. + * @param string $cacheName The cache driver name + * @param string $objectManagerName The object manager name + * @param array $cacheDriver The cache driver mapping + * @param ContainerBuilder $container The ContainerBuilder instance * * @return string * @@ -336,7 +325,7 @@ protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheD $memcacheClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.memcache.class').'%'; $memcacheInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.memcache_instance.class').'%'; $memcacheHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.memcache_host').'%'; - $memcachePort = !empty($cacheDriver['port']) || (isset($cacheDriver['port']) && $cacheDriver['port'] === 0) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcache_port').'%'; + $memcachePort = !empty($cacheDriver['port']) || (isset($cacheDriver['port']) && 0 === $cacheDriver['port']) ? $cacheDriver['port'] : '%'.$this->getObjectManagerElementName('cache.memcache_port').'%'; $cacheDef = new Definition($memcacheClass); $memcacheInstance = new Definition($memcacheInstanceClass); $memcacheInstance->addMethodCall('connect', array( @@ -403,12 +392,9 @@ protected function loadCacheDriver($cacheName, $objectManagerName, array $cacheD /** * Returns a modified version of $managerConfigs. * - * The manager called $autoMappedManager will map all bundles that are not mepped by other managers. + * The manager called $autoMappedManager will map all bundles that are not mapped by other managers. * - * @param array $managerConfigs - * @param array $bundles - * - * @return array The modified version of $managerConfigs. + * @return array The modified version of $managerConfigs */ protected function fixManagersAutoMappings(array $managerConfigs, array $bundles) { @@ -467,9 +453,7 @@ abstract protected function getMappingResourceExtension(); /** * Search for a manager that is declared as 'auto_mapping' = true. * - * @param array $managerConfigs - * - * @return null|string The name of the manager. If no one manager is found, returns null + * @return string|null The name of the manager. If no one manager is found, returns null * * @throws \LogicException */ diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php index de35c55219f2e..bec5d09563904 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/DoctrineValidationPass.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; /** * Registers additional validators. @@ -22,11 +22,11 @@ */ class DoctrineValidationPass implements CompilerPassInterface { - /** - * @var string - */ private $managerType; + /** + * @param string $managerType + */ public function __construct($managerType) { $this->managerType = $managerType; @@ -60,8 +60,8 @@ private function updateValidatorMappingFiles(ContainerBuilder $container, $mappi foreach ($container->getParameter('kernel.bundles') as $bundle) { $reflection = new \ReflectionClass($bundle); - if (is_file($file = dirname($reflection->getFilename()).'/'.$validationPath)) { - $files[] = realpath($file); + if (is_file($file = \dirname($reflection->getFileName()).'/'.$validationPath)) { + $files[] = $file; $container->addResource(new FileResource($file)); } } diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php index 94f72fd8c8c05..2ba7747e3881f 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPass.php @@ -11,27 +11,27 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** * Registers event listeners and subscribers to the available doctrine connections. * * @author Jeremy Mikola * @author Alexander + * @author David Maicher */ class RegisterEventListenersAndSubscribersPass implements CompilerPassInterface { private $connections; - private $container; private $eventManagers; private $managerTemplate; private $tagPrefix; /** - * Constructor. - * * @param string $connections Parameter ID for connections * @param string $managerTemplate sprintf() template for generating the event * manager's service ID for a connection name @@ -53,105 +53,109 @@ public function process(ContainerBuilder $container) return; } - $taggedSubscribers = $container->findTaggedServiceIds($this->tagPrefix.'.event_subscriber'); - $taggedListeners = $container->findTaggedServiceIds($this->tagPrefix.'.event_listener'); - - if (empty($taggedSubscribers) && empty($taggedListeners)) { - return; - } - - $this->container = $container; $this->connections = $container->getParameter($this->connections); - $sortFunc = function ($a, $b) { - $a = isset($a['priority']) ? $a['priority'] : 0; - $b = isset($b['priority']) ? $b['priority'] : 0; - - return $a > $b ? -1 : 1; - }; + $this->addTaggedSubscribers($container); + $this->addTaggedListeners($container); + } - if (!empty($taggedSubscribers)) { - $subscribersPerCon = $this->groupByConnection($taggedSubscribers); - foreach ($subscribersPerCon as $con => $subscribers) { - $em = $this->getEventManager($con); + private function addTaggedSubscribers(ContainerBuilder $container) + { + $subscriberTag = $this->tagPrefix.'.event_subscriber'; + $taggedSubscribers = $this->findAndSortTags($subscriberTag, $container); - uasort($subscribers, $sortFunc); - foreach ($subscribers as $id => $instance) { - if ($container->getDefinition($id)->isAbstract()) { - throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event subscriber.', $id)); - } + foreach ($taggedSubscribers as $taggedSubscriber) { + $id = $taggedSubscriber[0]; + $taggedSubscriberDef = $container->getDefinition($id); - $em->addMethodCall('addEventSubscriber', array(new Reference($id))); - } + if ($taggedSubscriberDef->isAbstract()) { + throw new InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event subscriber.', $id)); } - } - if (!empty($taggedListeners)) { - $listenersPerCon = $this->groupByConnection($taggedListeners, true); - foreach ($listenersPerCon as $con => $listeners) { - $em = $this->getEventManager($con); - - uasort($listeners, $sortFunc); - foreach ($listeners as $id => $instance) { - if ($container->getDefinition($id)->isAbstract()) { - throw new \InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event listener.', $id)); - } - - $em->addMethodCall('addEventListener', array( - array_unique($instance['event']), - isset($instance['lazy']) && $instance['lazy'] ? $id : new Reference($id), - )); + $tag = $taggedSubscriber[1]; + $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); } + + $this->getEventManagerDef($container, $con)->addMethodCall('addEventSubscriber', array(new Reference($id))); } } } - private function groupByConnection(array $services, $isListener = false) + private function addTaggedListeners(ContainerBuilder $container) { - $grouped = array(); - foreach ($allCons = array_keys($this->connections) as $con) { - $grouped[$con] = array(); - } + $listenerTag = $this->tagPrefix.'.event_listener'; + $taggedListeners = $this->findAndSortTags($listenerTag, $container); + + foreach ($taggedListeners as $taggedListener) { + $id = $taggedListener[0]; + $taggedListenerDef = $container->getDefinition($taggedListener[0]); + if ($taggedListenerDef->isAbstract()) { + throw new InvalidArgumentException(sprintf('The abstract service "%s" cannot be tagged as a doctrine event listener.', $id)); + } + + $tag = $taggedListener[1]; + if (!isset($tag['event'])) { + throw new InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); + } - foreach ($services as $id => $instances) { - foreach ($instances as $instance) { - if ($isListener) { - if (!isset($instance['event'])) { - throw new \InvalidArgumentException(sprintf('Doctrine event listener "%s" must specify the "event" attribute.', $id)); - } - $instance['event'] = array($instance['event']); - - if (isset($instance['lazy']) && $instance['lazy']) { - $this->container->getDefinition($id)->setPublic(true); - } + $connections = isset($tag['connection']) ? array($tag['connection']) : array_keys($this->connections); + foreach ($connections as $con) { + if (!isset($this->connections[$con])) { + throw new RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); } - $cons = isset($instance['connection']) ? array($instance['connection']) : $allCons; - foreach ($cons as $con) { - if (!isset($grouped[$con])) { - throw new \RuntimeException(sprintf('The Doctrine connection "%s" referenced in service "%s" does not exist. Available connections names: %s', $con, $id, implode(', ', array_keys($this->connections)))); - } - - if ($isListener && isset($grouped[$con][$id])) { - $grouped[$con][$id]['event'] = array_merge($grouped[$con][$id]['event'], $instance['event']); - } else { - $grouped[$con][$id] = $instance; - } + if ($lazy = !empty($tag['lazy'])) { + $taggedListenerDef->setPublic(true); } + + // we add one call per event per service so we have the correct order + $this->getEventManagerDef($container, $con)->addMethodCall('addEventListener', array(array($tag['event']), $lazy ? $id : new Reference($id))); } } + } + + private function getEventManagerDef(ContainerBuilder $container, $name) + { + if (!isset($this->eventManagers[$name])) { + $this->eventManagers[$name] = $container->getDefinition(sprintf($this->managerTemplate, $name)); + } - return $grouped; + return $this->eventManagers[$name]; } - private function getEventManager($name) + /** + * Finds and orders all service tags with the given name by their priority. + * + * The order of additions must be respected for services having the same priority, + * and knowing that the \SplPriorityQueue class does not respect the FIFO method, + * we should not use this class. + * + * @see https://bugs.php.net/bug.php?id=53710 + * @see https://bugs.php.net/bug.php?id=60926 + * + * @param string $tagName + * @param ContainerBuilder $container + * + * @return array + */ + private function findAndSortTags($tagName, ContainerBuilder $container) { - if (null === $this->eventManagers) { - $this->eventManagers = array(); - foreach ($this->connections as $n => $id) { - $this->eventManagers[$n] = $this->container->getDefinition(sprintf($this->managerTemplate, $n)); + $sortedTags = array(); + + foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) { + foreach ($tags as $attributes) { + $priority = isset($attributes['priority']) ? $attributes['priority'] : 0; + $sortedTags[$priority][] = array($serviceId, $attributes); } } - return $this->eventManagers[$name]; + if ($sortedTags) { + krsort($sortedTags); + $sortedTags = \call_user_func_array('array_merge', $sortedTags); + } + + return $sortedTags; } } diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php index fd32b8d4ceb64..90709d42753a0 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/CompilerPass/RegisterMappingsPass.php @@ -11,11 +11,11 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass; -use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; /** * Base class for the doctrine bundles to provide a compiler pass class that @@ -69,7 +69,7 @@ abstract class RegisterMappingsPass implements CompilerPassInterface * only do anything if the parameter is present. (But regardless of the * value of that parameter. * - * @var string + * @var string|false */ protected $enabledParameter; @@ -97,8 +97,6 @@ abstract class RegisterMappingsPass implements CompilerPassInterface private $aliasMap; /** - * Constructor. - * * The $managerParameters is an ordered list of container parameters that could provide the * name of the manager to register these namespaces and alias on. The first non-empty name * is used, the others skipped. @@ -106,20 +104,18 @@ abstract class RegisterMappingsPass implements CompilerPassInterface * The $aliasMap parameter can be used to define bundle namespace shortcuts like the * DoctrineBundle provides automatically for objects in the default Entity/Document folder. * - * @param Definition|Reference $driver Driver DI definition or reference. - * @param string[] $namespaces List of namespaces handled by $driver. - * @param string[] $managerParameters List of container parameters that could - * hold the manager name. - * @param string $driverPattern Pattern for the metadata driver service name. - * @param string $enabledParameter Service container parameter that must be + * @param Definition|Reference $driver Driver DI definition or reference + * @param string[] $namespaces List of namespaces handled by $driver + * @param string[] $managerParameters list of container parameters that could + * hold the manager name + * @param string $driverPattern Pattern for the metadata driver service name + * @param string|false $enabledParameter Service container parameter that must be * present to enable the mapping. Set to false * to not do any check, optional. - * @param string $configurationPattern Pattern for the Configuration service name. + * @param string $configurationPattern Pattern for the Configuration service name * @param string $registerAliasMethodName Name of Configuration class method to - * register alias. - * @param string[] $aliasMap Map of alias to namespace. - * - * @since Support for bundle alias was added in Symfony 2.6 + * register alias + * @param string[] $aliasMap Map of alias to namespace */ public function __construct($driver, array $namespaces, array $managerParameters, $driverPattern, $enabledParameter = false, $configurationPattern = '', $registerAliasMethodName = '', array $aliasMap = array()) { @@ -128,7 +124,7 @@ public function __construct($driver, array $namespaces, array $managerParameters $this->managerParameters = $managerParameters; $this->driverPattern = $driverPattern; $this->enabledParameter = $enabledParameter; - if (count($aliasMap) && (!$configurationPattern || !$registerAliasMethodName)) { + if (\count($aliasMap) && (!$configurationPattern || !$registerAliasMethodName)) { throw new \InvalidArgumentException('configurationPattern and registerAliasMethodName are required to register namespace alias'); } $this->configurationPattern = $configurationPattern; @@ -138,8 +134,6 @@ public function __construct($driver, array $namespaces, array $managerParameters /** * Register mappings and alias with the metadata drivers. - * - * @param ContainerBuilder $container */ public function process(ContainerBuilder $container) { @@ -155,7 +149,7 @@ public function process(ContainerBuilder $container) $chainDriverDef->addMethodCall('addDriver', array($mappingDriverDef, $namespace)); } - if (!count($this->aliasMap)) { + if (!\count($this->aliasMap)) { return; } @@ -171,12 +165,10 @@ public function process(ContainerBuilder $container) * Get the service name of the metadata chain driver that the mappings * should be registered with. * - * @param ContainerBuilder $container - * * @return string The name of the chain driver service * * @throws ParameterNotFoundException if non of the managerParameters has a - * non-empty value. + * non-empty value */ protected function getChainDriverServiceName(ContainerBuilder $container) { @@ -186,8 +178,8 @@ protected function getChainDriverServiceName(ContainerBuilder $container) /** * Create the service definition for the metadata driver. * - * @param ContainerBuilder $container passed on in case an extending class - * needs access to the container. + * @param ContainerBuilder $container Passed on in case an extending class + * needs access to the container * * @return Definition|Reference the metadata driver to add to all chain drivers */ @@ -199,12 +191,10 @@ protected function getDriver(ContainerBuilder $container) /** * Get the service name from the pattern and the configured manager name. * - * @param ContainerBuilder $container - * * @return string a service definition name * * @throws ParameterNotFoundException if none of the managerParameters has a - * non-empty value. + * non-empty value */ private function getConfigurationServiceName(ContainerBuilder $container) { @@ -217,11 +207,9 @@ private function getConfigurationServiceName(ContainerBuilder $container) * The default implementation loops over the managerParameters and returns * the first non-empty parameter. * - * @param ContainerBuilder $container - * - * @return string The name of the active manager. + * @return string The name of the active manager * - * @throws ParameterNotFoundException If none of the managerParameters is found in the container. + * @throws ParameterNotFoundException if none of the managerParameters is found in the container */ private function getManagerName(ContainerBuilder $container) { @@ -244,8 +232,6 @@ private function getManagerName(ContainerBuilder $container) * This default implementation checks if the class has the enabledParameter * configured and if so if that parameter is present in the container. * - * @param ContainerBuilder $container - * * @return bool whether this compiler pass really should register the mappings */ protected function enabled(ContainerBuilder $container) diff --git a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php index ebcdf82f585a3..cdb27b81987cd 100644 --- a/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php +++ b/src/Symfony/Bridge/Doctrine/DependencyInjection/Security/UserProvider/EntityFactory.php @@ -11,10 +11,10 @@ namespace Symfony\Bridge\Doctrine\DependencyInjection\Security\UserProvider; -use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface; -use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\DefinitionDecorator; /** * EntityFactory creates services for Doctrine user provider. diff --git a/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.php b/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.php index 1c0e8cdfee26e..0aac26dae3671 100644 --- a/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.php +++ b/src/Symfony/Bridge/Doctrine/ExpressionLanguage/DoctrineParserCache.php @@ -20,9 +20,6 @@ */ class DoctrineParserCache implements ParserCacheInterface { - /** - * @var Cache - */ private $cache; public function __construct(Cache $cache) diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php index 2335af713128b..ab92e61edd068 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php @@ -23,29 +23,10 @@ */ class DoctrineChoiceLoader implements ChoiceLoaderInterface { - /** - * @var ChoiceListFactoryInterface - */ private $factory; - - /** - * @var ObjectManager - */ private $manager; - - /** - * @var string - */ private $class; - - /** - * @var IdReader - */ private $idReader; - - /** - * @var null|EntityLoaderInterface - */ private $objectLoader; /** @@ -60,14 +41,11 @@ class DoctrineChoiceLoader implements ChoiceLoaderInterface * passed which optimizes the object loading for one of the Doctrine * mapper implementations. * - * @param ChoiceListFactoryInterface $factory The factory for creating - * the loaded choice list + * @param ChoiceListFactoryInterface $factory The factory for creating the loaded choice list * @param ObjectManager $manager The object manager - * @param string $class The class name of the - * loaded objects - * @param IdReader $idReader The reader for the object - * IDs. - * @param null|EntityLoaderInterface $objectLoader The objects loader + * @param string $class The class name of the loaded objects + * @param IdReader $idReader The reader for the object IDs + * @param EntityLoaderInterface|null $objectLoader The objects loader */ public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null) { @@ -110,9 +88,10 @@ public function loadValuesForChoices(array $choices, $value = null) // Optimize performance for single-field identifiers. We already // know that the IDs are used as values + $optimize = null === $value || \is_array($value) && $value[0] === $this->idReader; // Attention: This optimization does not check choices for existence - if (!$this->choiceList && $this->idReader->isSingleId()) { + if ($optimize && !$this->choiceList && $this->idReader->isSingleId()) { $values = array(); // Maintain order and indices of the given objects @@ -146,7 +125,9 @@ public function loadChoicesForValues(array $values, $value = null) // Optimize performance in case we have an object loader and // a single-field identifier - if (!$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) { + $optimize = null === $value || \is_array($value) && $value[0] === $this->idReader; + + if ($optimize && !$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) { $unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values); $objectsById = array(); $objects = array(); @@ -156,7 +137,7 @@ public function loadChoicesForValues(array $values, $value = null) // "INDEX BY" clause to the Doctrine query in the loader, // but I'm not sure whether that's doable in a generic fashion. foreach ($unorderedObjects as $object) { - $objectsById[$this->idReader->getIdValue($object)] = $object; + $objectsById[(string) $this->idReader->getIdValue($object)] = $object; } foreach ($values as $i => $id) { diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php index bd3fa8eb27921..6b0766fada05a 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; -@trigger_error('The '.__NAMESPACE__.'\EntityChoiceList class is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\EntityChoiceList class is deprecated since Symfony 2.7 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.', E_USER_DEPRECATED); use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\Persistence\ObjectManager; @@ -109,7 +109,7 @@ class EntityChoiceList extends ObjectChoiceList * @param string $groupPath A property path pointing to the property used * to group the choices. Only allowed if * the choices are given as flat array. - * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths. + * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths */ public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null) { @@ -117,7 +117,7 @@ public function __construct(ObjectManager $manager, $class, $labelPath = null, E $this->entityLoader = $entityLoader; $this->classMetadata = $manager->getClassMetadata($class); $this->class = $this->classMetadata->getName(); - $this->loaded = is_array($entities) || $entities instanceof \Traversable; + $this->loaded = \is_array($entities) || $entities instanceof \Traversable; $this->preferredEntities = $preferredEntities; list( $this->idAsIndex, @@ -214,8 +214,6 @@ public function getRemainingViews() /** * Returns the entities corresponding to the given values. * - * @param array $values - * * @return array * * @see ChoiceListInterface @@ -267,8 +265,6 @@ public function getChoicesForValues(array $values) /** * Returns the values corresponding to the given entities. * - * @param array $entities - * * @return array * * @see ChoiceListInterface @@ -316,7 +312,7 @@ public function getValuesForChoices(array $entities) */ public function getIndicesForChoices(array $entities) { - @trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); // Performance optimization if (empty($entities)) { @@ -359,7 +355,7 @@ public function getIndicesForChoices(array $entities) */ public function getIndicesForValues(array $values) { - @trigger_error('The '.__METHOD__.' method is deprecated since version 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); // Performance optimization if (empty($values)) { @@ -389,8 +385,8 @@ public function getIndicesForValues(array $values) * * @param mixed $entity The choice to create an index for * - * @return int|string A unique index containing only ASCII letters, - * digits and underscores. + * @return int|string a unique index containing only ASCII letters, + * digits and underscores */ protected function createIndex($entity) { @@ -410,7 +406,7 @@ protected function createIndex($entity) * * @param mixed $entity The choice to create a value for * - * @return int|string A unique value without character limitations. + * @return int|string A unique value without character limitations */ protected function createValue($entity) { @@ -453,13 +449,13 @@ private function getIdentifierInfoForClass(ClassMetadata $classMetadata) $identifiers = $classMetadata->getIdentifierFieldNames(); - if (1 === count($identifiers)) { + if (1 === \count($identifiers)) { $identifier = $identifiers[0]; if (!$classMetadata->hasAssociation($identifier)) { $idAsValue = true; - if (in_array($classMetadata->getTypeOfField($identifier), array('integer', 'smallint', 'bigint'))) { + if (\in_array($classMetadata->getTypeOfField($identifier), array('integer', 'smallint', 'bigint'))) { $idAsIndex = true; } } @@ -534,10 +530,7 @@ private function getSingleIdentifierValue($entity) private function getIdentifierValues($entity) { if (!$this->em->contains($entity)) { - throw new RuntimeException( - 'Entities passed to the choice field must be managed. Maybe '. - 'persist them in the entity manager?' - ); + throw new RuntimeException('Entities passed to the choice field must be managed. Maybe persist them in the entity manager?'); } $this->em->initializeObject($entity); diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php index 2de34afc889e2..e36043af63cb8 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php @@ -21,7 +21,7 @@ interface EntityLoaderInterface /** * Returns an array of entities that are valid choices in the corresponding choice list. * - * @return array The entities. + * @return array The entities */ public function getEntities(); @@ -31,9 +31,9 @@ public function getEntities(); * @param string $identifier The identifier field of the object. This method * is not applicable for fields with multiple * identifiers. - * @param array $values The values of the identifiers. + * @param array $values The values of the identifiers * - * @return array The entities. + * @return array The entities */ public function getEntitiesByIds($identifier, array $values); } diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index 6ae98b57f8d42..9c632330354b1 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -18,37 +18,16 @@ /** * A utility for reading object IDs. * - * @since 1.0 - * * @author Bernhard Schussek * - * @internal This class is meant for internal use only. + * @internal this class is meant for internal use only */ class IdReader { - /** - * @var ObjectManager - */ private $om; - - /** - * @var ClassMetadata - */ private $classMetadata; - - /** - * @var bool - */ private $singleId; - - /** - * @var bool - */ private $intId; - - /** - * @var string - */ private $idField; /** @@ -63,8 +42,8 @@ public function __construct(ObjectManager $om, ClassMetadata $classMetadata) $this->om = $om; $this->classMetadata = $classMetadata; - $this->singleId = 1 === count($ids); - $this->intId = $this->singleId && in_array($idType, array('integer', 'smallint', 'bigint')); + $this->singleId = 1 === \count($ids); + $this->intId = $this->singleId && \in_array($idType, array('integer', 'smallint', 'bigint')); $this->idField = current($ids); // single field association are resolved, since the schema column could be an int @@ -81,8 +60,8 @@ public function __construct(ObjectManager $om, ClassMetadata $classMetadata) /** * Returns whether the class has a single-column ID. * - * @return bool Returns `true` if the class has a single-column ID and - * `false` otherwise. + * @return bool returns `true` if the class has a single-column ID and + * `false` otherwise */ public function isSingleId() { @@ -92,8 +71,8 @@ public function isSingleId() /** * Returns whether the class has a single-column integer ID. * - * @return bool Returns `true` if the class has a single-column integer ID - * and `false` otherwise. + * @return bool returns `true` if the class has a single-column integer ID + * and `false` otherwise */ public function isIntId() { @@ -105,9 +84,9 @@ public function isIntId() * * This method assumes that the object has a single-column ID. * - * @param object $object The object. + * @param object $object The object * - * @return mixed The ID value. + * @return mixed The ID value */ public function getIdValue($object) { @@ -116,10 +95,7 @@ public function getIdValue($object) } if (!$this->om->contains($object)) { - throw new RuntimeException( - 'Entities passed to the choice field must be managed. Maybe '. - 'persist them in the entity manager?' - ); + throw new RuntimeException(sprintf('Entity of type "%s" passed to the choice field must be managed. Maybe you forget to persist it in the entity manager?', \get_class($object))); } $this->om->initializeObject($object); @@ -138,7 +114,7 @@ public function getIdValue($object) * * This method assumes that the object has a single-column ID. * - * @return string The name of the ID field. + * @return string The name of the ID field */ public function getIdField() { diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index fe0ad19367103..dfe552eb4478e 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -11,10 +11,10 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; -use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Doctrine\ORM\QueryBuilder; -use Doctrine\DBAL\Connection; use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\DBAL\Connection; +use Doctrine\ORM\QueryBuilder; +use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Loads entities using a {@link QueryBuilder} instance. @@ -43,8 +43,8 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface * deprecated and will not be * supported anymore as of * Symfony 3.0. - * @param ObjectManager $manager Deprecated. - * @param string $class Deprecated. + * @param ObjectManager $manager Deprecated + * @param string $class Deprecated * * @throws UnexpectedTypeException */ @@ -57,14 +57,14 @@ public function __construct($queryBuilder, $manager = null, $class = null) } if ($queryBuilder instanceof \Closure) { - @trigger_error('Passing a QueryBuilder closure to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('Passing a QueryBuilder closure to '.__CLASS__.'::__construct() is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); if (!$manager instanceof ObjectManager) { throw new UnexpectedTypeException($manager, 'Doctrine\Common\Persistence\ObjectManager'); } - @trigger_error('Passing an EntityManager to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); - @trigger_error('Passing a class to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('Passing an EntityManager to '.__CLASS__.'::__construct() is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('Passing a class to '.__CLASS__.'::__construct() is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); $queryBuilder = $queryBuilder($manager->getRepository($class)); @@ -89,21 +89,29 @@ public function getEntities() */ public function getEntitiesByIds($identifier, array $values) { - $qb = clone ($this->queryBuilder); + $qb = clone $this->queryBuilder; $alias = current($qb->getRootAliases()); $parameter = 'ORMQueryBuilderLoader_getEntitiesByIds_'.$identifier; + $parameter = str_replace('.', '_', $parameter); $where = $qb->expr()->in($alias.'.'.$identifier, ':'.$parameter); // Guess type $entity = current($qb->getRootEntities()); $metadata = $qb->getEntityManager()->getClassMetadata($entity); - if (in_array($metadata->getTypeOfField($identifier), array('integer', 'bigint', 'smallint'))) { + if (\in_array($metadata->getTypeOfField($identifier), array('integer', 'bigint', 'smallint'))) { $parameterType = Connection::PARAM_INT_ARRAY; // Filter out non-integer values (e.g. ""). If we don't, some // databases such as PostgreSQL fail. $values = array_values(array_filter($values, function ($v) { - return (string) $v === (string) (int) $v; + return (string) $v === (string) (int) $v || ctype_digit($v); + })); + } elseif (\in_array($metadata->getTypeOfField($identifier), array('uuid', 'guid'))) { + $parameterType = Connection::PARAM_STR_ARRAY; + + // Like above, but we just filter out empty strings. + $values = array_values(array_filter($values, function ($v) { + return '' !== (string) $v; })); } else { $parameterType = Connection::PARAM_STR_ARRAY; diff --git a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php index e56674be351c5..4010512ba9c49 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php +++ b/src/Symfony/Bridge/Doctrine/Form/DataTransformer/CollectionToArrayTransformer.php @@ -11,10 +11,10 @@ namespace Symfony\Bridge\Doctrine\Form\DataTransformer; -use Symfony\Component\Form\Exception\TransformationFailedException; -use Symfony\Component\Form\DataTransformerInterface; -use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek @@ -24,8 +24,6 @@ class CollectionToArrayTransformer implements DataTransformerInterface /** * Transforms a collection into an array. * - * @param Collection $collection A collection of entities - * * @return mixed An array of entities * * @throws TransformationFailedException @@ -38,7 +36,7 @@ public function transform($collection) // For cases when the collection getter returns $collection->toArray() // in order to prevent modifications of the returned collection - if (is_array($collection)) { + if (\is_array($collection)) { return $collection; } diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php index ed8e0a793444c..469cbea192fc8 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php @@ -25,14 +25,7 @@ class DoctrineOrmExtension extends AbstractExtension { protected $registry; - /** - * @var PropertyAccessorInterface - */ private $propertyAccessor; - - /** - * @var ChoiceListFactoryInterface - */ private $choiceListFactory; public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null) diff --git a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php index 2e6af7a0d6a45..53a2c3560c1f2 100644 --- a/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php +++ b/src/Symfony/Bridge/Doctrine/Form/DoctrineOrmTypeGuesser.php @@ -13,6 +13,7 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\Mapping\MappingException; +use Doctrine\Common\Util\ClassUtils; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as LegacyMappingException; @@ -20,7 +21,6 @@ use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; use Symfony\Component\Form\Guess\ValueGuess; -use Doctrine\Common\Util\ClassUtils; class DoctrineOrmTypeGuesser implements FormTypeGuesserInterface { @@ -95,7 +95,7 @@ public function guessRequired($class, $property) $classMetadata = $classMetadatas[0]; // Check whether the field exists and is nullable or not - if ($classMetadata->hasField($property)) { + if (isset($classMetadata->fieldMappings[$property])) { if (!$classMetadata->isNullable($property) && Type::BOOLEAN !== $classMetadata->getTypeOfField($property)) { return new ValueGuess(true, Guess::HIGH_CONFIDENCE); } @@ -124,14 +124,14 @@ public function guessRequired($class, $property) public function guessMaxLength($class, $property) { $ret = $this->getMetadata($class); - if ($ret && $ret[0]->hasField($property) && !$ret[0]->hasAssociation($property)) { + if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { $mapping = $ret[0]->getFieldMapping($property); if (isset($mapping['length'])) { return new ValueGuess($mapping['length'], Guess::HIGH_CONFIDENCE); } - if (in_array($ret[0]->getTypeOfField($property), array(Type::DECIMAL, Type::FLOAT))) { + if (\in_array($ret[0]->getTypeOfField($property), array(Type::DECIMAL, Type::FLOAT))) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } } @@ -143,8 +143,8 @@ public function guessMaxLength($class, $property) public function guessPattern($class, $property) { $ret = $this->getMetadata($class); - if ($ret && $ret[0]->hasField($property) && !$ret[0]->hasAssociation($property)) { - if (in_array($ret[0]->getTypeOfField($property), array(Type::DECIMAL, Type::FLOAT))) { + if ($ret && isset($ret[0]->fieldMappings[$property]) && !$ret[0]->hasAssociation($property)) { + if (\in_array($ret[0]->getTypeOfField($property), array(Type::DECIMAL, Type::FLOAT))) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php index 4edf1043c59fc..64b497ceb2a39 100644 --- a/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php +++ b/src/Symfony/Bridge/Doctrine/Form/EventListener/MergeDoctrineCollectionListener.php @@ -12,9 +12,9 @@ namespace Symfony\Bridge\Doctrine\Form\EventListener; use Doctrine\Common\Collections\Collection; -use Symfony\Component\Form\FormEvents; -use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; /** * Merge changes from the request to a Doctrine\Common\Collections\Collection instance. @@ -41,7 +41,7 @@ public function onBind(FormEvent $event) // If all items were removed, call clear which has a higher // performance on persistent collections - if ($collection instanceof Collection && count($data) === 0) { + if ($collection instanceof Collection && 0 === \count($data)) { $collection->clear(); } } diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 223129097d3b3..8fef469f2c042 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -56,9 +56,9 @@ abstract class DoctrineType extends AbstractType * * For backwards compatibility, objects are cast to strings by default. * - * @param object $choice The object. + * @param object $choice The object * - * @return string The string representation of the object. + * @return string The string representation of the object * * @internal This method is public to be usable as callback. It should not * be used in user code. @@ -75,12 +75,12 @@ public static function createChoiceLabel($choice) * a single-column integer ID. In that case, the value of the field is * the ID of the object. That ID is also used as field name. * - * @param object $choice The object. - * @param int|string $key The choice key. + * @param object $choice The object + * @param int|string $key The choice key * @param string $value The choice value. Corresponds to the object's * ID here. * - * @return string The field name. + * @return string The field name * * @internal This method is public to be usable as callback. It should not * be used in user code. @@ -111,7 +111,12 @@ public function getQueryBuilderPartsForCachingHash($queryBuilder) public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null) { $this->registry = $registry; - $this->choiceListFactory = $choiceListFactory ?: new PropertyAccessDecorator(new DefaultChoiceListFactory(), $propertyAccessor); + $this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator( + new PropertyAccessDecorator( + new DefaultChoiceListFactory(), + $propertyAccessor + ) + ); } public function buildForm(FormBuilderInterface $builder, array $options) @@ -133,7 +138,6 @@ public function configureOptions(OptionsResolver $resolver) $type = $this; $choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders, $type) { - // Unless the choices are given explicitly, load them on demand if (null === $options['choices']) { $hash = null; @@ -172,7 +176,7 @@ public function configureOptions(OptionsResolver $resolver) $entityLoader ); - if ($hash !== null) { + if (null !== $hash) { $choiceLoaders[$hash] = $doctrineChoiceLoader; } @@ -233,11 +237,7 @@ public function configureOptions(OptionsResolver $resolver) $em = $registry->getManagerForClass($options['class']); if (null === $em) { - throw new RuntimeException(sprintf( - 'Class "%s" seems not to be a managed Doctrine entity. '. - 'Did you forget to map it?', - $options['class'] - )); + throw new RuntimeException(sprintf('Class "%s" seems not to be a managed Doctrine entity. Did you forget to map it?', $options['class'])); } return $em; @@ -246,7 +246,7 @@ public function configureOptions(OptionsResolver $resolver) // deprecation note $propertyNormalizer = function (Options $options, $propertyName) { if ($propertyName) { - @trigger_error('The "property" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_label" instead.', E_USER_DEPRECATED); + @trigger_error('The "property" option is deprecated since Symfony 2.7 and will be removed in 3.0. Use "choice_label" instead.', E_USER_DEPRECATED); } return $propertyName; @@ -255,8 +255,8 @@ public function configureOptions(OptionsResolver $resolver) // Invoke the query builder closure so that we can cache choice lists // for equal query builders $queryBuilderNormalizer = function (Options $options, $queryBuilder) { - if (is_callable($queryBuilder)) { - $queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); + if (\is_callable($queryBuilder)) { + $queryBuilder = \call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); } return $queryBuilder; @@ -265,7 +265,7 @@ public function configureOptions(OptionsResolver $resolver) // deprecation note $loaderNormalizer = function (Options $options, $loader) { if ($loader) { - @trigger_error('The "loader" option is deprecated since version 2.7 and will be removed in 3.0. Override getLoader() instead.', E_USER_DEPRECATED); + @trigger_error('The "loader" option is deprecated since Symfony 2.7 and will be removed in 3.0. Override getLoader() instead.', E_USER_DEPRECATED); } return $loader; diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 7e0c6cb6cda70..99ce535e23c01 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form\Type; use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\Query\Parameter; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Component\Form\Exception\UnexpectedTypeException; @@ -27,8 +28,8 @@ public function configureOptions(OptionsResolver $resolver) // Invoke the query builder closure so that we can cache choice lists // for equal query builders $queryBuilderNormalizer = function (Options $options, $queryBuilder) { - if (is_callable($queryBuilder)) { - $queryBuilder = call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); + if (\is_callable($queryBuilder)) { + $queryBuilder = \call_user_func($queryBuilder, $options['em']->getRepository($options['class'])); if (null !== $queryBuilder && !$queryBuilder instanceof QueryBuilder) { throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); @@ -86,8 +87,18 @@ public function getBlockPrefix() public function getQueryBuilderPartsForCachingHash($queryBuilder) { return array( - $queryBuilder->getQuery()->getSQL(), - $queryBuilder->getParameters()->toArray(), + $queryBuilder->getQuery()->getSQL(), + array_map(array($this, 'parameterToArray'), $queryBuilder->getParameters()->toArray()), ); } + + /** + * Converts a query parameter to an array. + * + * @return array The array representation of the parameter + */ + private function parameterToArray(Parameter $parameter) + { + return array($parameter->getName(), $parameter->getType(), $parameter->getValue()); + } } diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php index fd7dcff62c3d3..6ae9c469bb166 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php @@ -13,6 +13,7 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\DriverException; +use Doctrine\DBAL\Driver\ServerInfoAwareConnection; use Doctrine\DBAL\Platforms\SQLServer2008Platform; /** @@ -53,8 +54,6 @@ class DbalSessionHandler implements \SessionHandlerInterface private $timeCol = 'sess_time'; /** - * Constructor. - * * @param Connection $con A connection * @param string $tableName Table name */ @@ -180,7 +179,7 @@ public function write($sessionId, $data) $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); - // When MERGE is not supported, like in Postgres, we have to use this approach that can result in + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in // duplicate key errors when the same session is written simultaneously. We can just catch such an // error and re-execute the update. This is similar to a serializable transaction with retry logic // on serialization failures but without the overhead and without possible false positives due to @@ -224,11 +223,11 @@ private function getMergeSql() { $platform = $this->con->getDatabasePlatform()->getName(); - switch ($platform) { - case 'mysql': + switch (true) { + case 'mysql' === $platform: return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)"; - case 'oracle': + case 'oracle' === $platform: // DUAL is Oracle specific dummy table return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ". "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". @@ -239,8 +238,35 @@ private function getMergeSql() return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ". "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;"; - case 'sqlite': + case 'sqlite' === $platform: return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"; + case 'postgresql' === $platform && version_compare($this->getServerVersion(), '9.5', '>='): + return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->timeCol)"; + } + } + + private function getServerVersion() + { + $params = $this->con->getParams(); + + // Explicit platform version requested (supersedes auto-detection), so we respect it. + if (isset($params['serverVersion'])) { + return $params['serverVersion']; } + + $wrappedConnection = $this->con->getWrappedConnection(); + + if ($wrappedConnection instanceof ServerInfoAwareConnection) { + return $wrappedConnection->getServerVersion(); + } + + // Support DBAL 2.4 by accessing it directly when using PDO PgSQL + if ($wrappedConnection instanceof \PDO) { + return $wrappedConnection->getAttribute(\PDO::ATTR_SERVER_VERSION); + } + + // If we cannot guess the version, the empty string will mean we won't use the code for newer versions when doing version checks. + return ''; } } diff --git a/src/Symfony/Bridge/Doctrine/LICENSE b/src/Symfony/Bridge/Doctrine/LICENSE index 43028bc600f26..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Doctrine/LICENSE +++ b/src/Symfony/Bridge/Doctrine/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php index 51f9f75e4b62c..0200c2657a0e6 100644 --- a/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php +++ b/src/Symfony/Bridge/Doctrine/Logger/DbalLogger.php @@ -11,13 +11,11 @@ namespace Symfony\Bridge\Doctrine\Logger; +use Doctrine\DBAL\Logging\SQLLogger; use Psr\Log\LoggerInterface; use Symfony\Component\Stopwatch\Stopwatch; -use Doctrine\DBAL\Logging\SQLLogger; /** - * DbalLogger. - * * @author Fabien Potencier */ class DbalLogger implements SQLLogger @@ -28,12 +26,6 @@ class DbalLogger implements SQLLogger protected $logger; protected $stopwatch; - /** - * Constructor. - * - * @param LoggerInterface $logger A LoggerInterface instance - * @param Stopwatch $stopwatch A Stopwatch instance - */ public function __construct(LoggerInterface $logger = null, Stopwatch $stopwatch = null) { $this->logger = $logger; @@ -49,12 +41,8 @@ public function startQuery($sql, array $params = null, array $types = null) $this->stopwatch->start('doctrine', 'doctrine'); } - if (is_array($params)) { - $params = $this->normalizeParams($params); - } - if (null !== $this->logger) { - $this->log($sql, null === $params ? array() : $params); + $this->log($sql, null === $params ? array() : $this->normalizeParams($params)); } } @@ -83,12 +71,12 @@ private function normalizeParams(array $params) { foreach ($params as $index => $param) { // normalize recursively - if (is_array($param)) { + if (\is_array($param)) { $params[$index] = $this->normalizeParams($param); continue; } - if (!is_string($params[$index])) { + if (!\is_string($params[$index])) { continue; } @@ -99,8 +87,8 @@ private function normalizeParams(array $params) } // detect if the too long string must be shorten - if (self::MAX_STRING_LENGTH < iconv_strlen($params[$index], 'UTF-8')) { - $params[$index] = iconv_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; + if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) { + $params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]'; continue; } } diff --git a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php index 6efcefb63736c..a051d63eac2e0 100644 --- a/src/Symfony/Bridge/Doctrine/ManagerRegistry.php +++ b/src/Symfony/Bridge/Doctrine/ManagerRegistry.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine; +use Doctrine\Common\Persistence\AbstractManagerRegistry; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Doctrine\Common\Persistence\AbstractManagerRegistry; /** * References Doctrine connections and entity/document managers. diff --git a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php index dc22e235959bd..4bf684bf3aec6 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -13,7 +13,9 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; use Doctrine\Common\Persistence\Mapping\MappingException; +use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type; @@ -25,9 +27,6 @@ */ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface { - /** - * @var ClassMetadataFactory - */ private $classMetadataFactory; public function __construct(ClassMetadataFactory $classMetadataFactory) @@ -44,9 +43,21 @@ public function getProperties($class, array $context = array()) $metadata = $this->classMetadataFactory->getMetadataFor($class); } catch (MappingException $exception) { return; + } catch (OrmMappingException $exception) { + return; } - return array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); + $properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames()); + + if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && $metadata->embeddedClasses) { + $properties = array_filter($properties, function ($property) { + return false === strpos($property, '.'); + }); + + $properties = array_merge($properties, array_keys($metadata->embeddedClasses)); + } + + return $properties; } /** @@ -58,6 +69,8 @@ public function getTypes($class, $property, array $context = array()) $metadata = $this->classMetadataFactory->getMetadataFor($class); } catch (MappingException $exception) { return; + } catch (OrmMappingException $exception) { + return; } if ($metadata->hasAssociation($property)) { @@ -65,7 +78,9 @@ public function getTypes($class, $property, array $context = array()) if ($metadata->isSingleValuedAssociation($property)) { if ($metadata instanceof ClassMetadataInfo) { - $nullable = isset($metadata->discriminatorColumn['nullable']) ? $metadata->discriminatorColumn['nullable'] : false; + $associationMapping = $metadata->getAssociationMapping($property); + + $nullable = $this->isAssociationNullable($associationMapping); } else { $nullable = false; } @@ -73,77 +88,135 @@ public function getTypes($class, $property, array $context = array()) return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class)); } + $collectionKeyType = Type::BUILTIN_TYPE_INT; + + if ($metadata instanceof ClassMetadataInfo) { + $associationMapping = $metadata->getAssociationMapping($property); + + if (isset($associationMapping['indexBy'])) { + $indexProperty = $associationMapping['indexBy']; + /** @var ClassMetadataInfo $subMetadata */ + $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $typeOfField = $subMetadata->getTypeOfField($indexProperty); + + if (null === $typeOfField) { + $associationMapping = $subMetadata->getAssociationMapping($indexProperty); + + /** @var ClassMetadataInfo $subMetadata */ + $indexProperty = $subMetadata->getSingleAssociationReferencedJoinColumnName($indexProperty); + $subMetadata = $this->classMetadataFactory->getMetadataFor($associationMapping['targetEntity']); + $typeOfField = $subMetadata->getTypeOfField($indexProperty); + } + + $collectionKeyType = $this->getPhpType($typeOfField); + } + } + return array(new Type( Type::BUILTIN_TYPE_OBJECT, false, 'Doctrine\Common\Collections\Collection', true, - new Type(Type::BUILTIN_TYPE_INT), + new Type($collectionKeyType), new Type(Type::BUILTIN_TYPE_OBJECT, false, $class) )); } + if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && isset($metadata->embeddedClasses[$property])) { + return array(new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class'])); + } + if ($metadata->hasField($property)) { $typeOfField = $metadata->getTypeOfField($property); $nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property); switch ($typeOfField) { - case 'date': - case 'datetime': - case 'datetimetz': - case 'time': + case DBALType::DATE: + case DBALType::DATETIME: + case DBALType::DATETIMETZ: + case 'vardatetime': + case DBALType::TIME: return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime')); - case 'array': + case DBALType::TARRAY: return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)); - case 'simple_array': + case DBALType::SIMPLE_ARRAY: return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))); - case 'json_array': + case DBALType::JSON_ARRAY: return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true)); default: - return array(new Type($this->getPhpType($typeOfField), $nullable)); + $builtinType = $this->getPhpType($typeOfField); + + return $builtinType ? array(new Type($builtinType, $nullable)) : null; } } } + /** + * Determines whether an association is nullable. + * + * @param array $associationMapping + * + * @return bool + * + * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 + */ + private function isAssociationNullable(array $associationMapping) + { + if (isset($associationMapping['id']) && $associationMapping['id']) { + return false; + } + + if (!isset($associationMapping['joinColumns'])) { + return true; + } + + $joinColumns = $associationMapping['joinColumns']; + foreach ($joinColumns as $joinColumn) { + if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) { + return false; + } + } + + return true; + } + /** * Gets the corresponding built-in PHP type. * * @param string $doctrineType * - * @return string + * @return string|null */ private function getPhpType($doctrineType) { switch ($doctrineType) { - case 'smallint': - // No break - case 'bigint': - // No break - case 'integer': + case DBALType::SMALLINT: + case DBALType::INTEGER: return Type::BUILTIN_TYPE_INT; - case 'decimal': + case DBALType::FLOAT: return Type::BUILTIN_TYPE_FLOAT; - case 'text': - // No break - case 'guid': + case DBALType::BIGINT: + case DBALType::STRING: + case DBALType::TEXT: + case DBALType::GUID: + case DBALType::DECIMAL: return Type::BUILTIN_TYPE_STRING; - case 'boolean': + case DBALType::BOOLEAN: return Type::BUILTIN_TYPE_BOOL; - case 'blob': - // No break + case DBALType::BLOB: case 'binary': return Type::BUILTIN_TYPE_RESOURCE; - default: - return $doctrineType; + case DBALType::OBJECT: + return Type::BUILTIN_TYPE_OBJECT; } } } diff --git a/src/Symfony/Bridge/Doctrine/README.md b/src/Symfony/Bridge/Doctrine/README.md index 3dabc3cd6df88..46d897d061e0f 100644 --- a/src/Symfony/Bridge/Doctrine/README.md +++ b/src/Symfony/Bridge/Doctrine/README.md @@ -7,8 +7,7 @@ various Symfony components. Resources --------- -You can run the unit tests with the following command: - - $ cd path/to/Symfony/Bridge/Doctrine/ - $ composer install - $ phpunit + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 562a2952daf05..5e41b10e14bb2 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -11,12 +11,12 @@ namespace Symfony\Bridge\Doctrine\Security\RememberMe; -use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; -use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; -use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; -use Symfony\Component\Security\Core\Exception\TokenNotFoundException; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Types\Type as DoctrineType; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; +use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface; +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; /** * This class provides storage for the tokens that is set in "remember me" @@ -27,29 +27,19 @@ * and to do the conversion of the datetime column. * * In order to use this class, you need the following table in your database: - * CREATE TABLE `rememberme_token` ( - * `series` char(88) UNIQUE PRIMARY KEY NOT NULL, - * `value` char(88) NOT NULL, - * `lastUsed` datetime NOT NULL, - * `class` varchar(100) NOT NULL, - * `username` varchar(200) NOT NULL - * ); + * + * CREATE TABLE `rememberme_token` ( + * `series` char(88) UNIQUE PRIMARY KEY NOT NULL, + * `value` char(88) NOT NULL, + * `lastUsed` datetime NOT NULL, + * `class` varchar(100) NOT NULL, + * `username` varchar(200) NOT NULL + * ); */ class DoctrineTokenProvider implements TokenProviderInterface { - /** - * Doctrine DBAL database connection - * F.ex. service id: doctrine.dbal.default_connection. - * - * @var Connection - */ private $conn; - /** - * new DoctrineTokenProvider for the RememberMe authentication service. - * - * @param Connection $conn - */ public function __construct(Connection $conn) { $this->conn = $conn; @@ -60,7 +50,8 @@ public function __construct(Connection $conn) */ public function loadTokenBySeries($series) { - $sql = 'SELECT class, username, value, lastUsed' + // the alias for lastUsed works around case insensitivity in PostgreSQL + $sql = 'SELECT class, username, value, lastUsed AS last_used' .' FROM rememberme_token WHERE series=:series'; $paramValues = array('series' => $series); $paramTypes = array('series' => \PDO::PARAM_STR); @@ -68,7 +59,7 @@ public function loadTokenBySeries($series) $row = $stmt->fetch(\PDO::FETCH_ASSOC); if ($row) { - return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['lastUsed'])); + return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); } throw new TokenNotFoundException('No token found.'); @@ -92,12 +83,16 @@ public function updateToken($series, $tokenValue, \DateTime $lastUsed) { $sql = 'UPDATE rememberme_token SET value=:value, lastUsed=:lastUsed' .' WHERE series=:series'; - $paramValues = array('value' => $tokenValue, - 'lastUsed' => $lastUsed, - 'series' => $series,); - $paramTypes = array('value' => \PDO::PARAM_STR, - 'lastUsed' => DoctrineType::DATETIME, - 'series' => \PDO::PARAM_STR,); + $paramValues = array( + 'value' => $tokenValue, + 'lastUsed' => $lastUsed, + 'series' => $series, + ); + $paramTypes = array( + 'value' => \PDO::PARAM_STR, + 'lastUsed' => DoctrineType::DATETIME, + 'series' => \PDO::PARAM_STR, + ); $updated = $this->conn->executeUpdate($sql, $paramValues, $paramTypes); if ($updated < 1) { throw new TokenNotFoundException('No token found.'); @@ -112,16 +107,20 @@ public function createNewToken(PersistentTokenInterface $token) $sql = 'INSERT INTO rememberme_token' .' (class, username, series, value, lastUsed)' .' VALUES (:class, :username, :series, :value, :lastUsed)'; - $paramValues = array('class' => $token->getClass(), - 'username' => $token->getUsername(), - 'series' => $token->getSeries(), - 'value' => $token->getTokenValue(), - 'lastUsed' => $token->getLastUsed(),); - $paramTypes = array('class' => \PDO::PARAM_STR, - 'username' => \PDO::PARAM_STR, - 'series' => \PDO::PARAM_STR, - 'value' => \PDO::PARAM_STR, - 'lastUsed' => DoctrineType::DATETIME,); + $paramValues = array( + 'class' => $token->getClass(), + 'username' => $token->getUsername(), + 'series' => $token->getSeries(), + 'value' => $token->getTokenValue(), + 'lastUsed' => $token->getLastUsed(), + ); + $paramTypes = array( + 'class' => \PDO::PARAM_STR, + 'username' => \PDO::PARAM_STR, + 'series' => \PDO::PARAM_STR, + 'value' => \PDO::PARAM_STR, + 'lastUsed' => DoctrineType::DATETIME, + ); $this->conn->executeUpdate($sql, $paramValues, $paramTypes); } } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 692aae0127508..6cdc51587db06 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -14,8 +14,8 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; -use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; /** * Wrapper around a Doctrine ObjectManager. @@ -27,22 +27,17 @@ */ class EntityUserProvider implements UserProviderInterface { + private $registry; + private $managerName; + private $classOrAlias; private $class; - private $repository; private $property; - private $metadata; - public function __construct(ManagerRegistry $registry, $class, $property = null, $managerName = null) + public function __construct(ManagerRegistry $registry, $classOrAlias, $property = null, $managerName = null) { - $em = $registry->getManager($managerName); - $this->class = $class; - $this->metadata = $em->getClassMetadata($class); - - if (false !== strpos($this->class, ':')) { - $this->class = $this->metadata->getName(); - } - - $this->repository = $em->getRepository($class); + $this->registry = $registry; + $this->managerName = $managerName; + $this->classOrAlias = $classOrAlias; $this->property = $property; } @@ -51,18 +46,19 @@ public function __construct(ManagerRegistry $registry, $class, $property = null, */ public function loadUserByUsername($username) { + $repository = $this->getRepository(); if (null !== $this->property) { - $user = $this->repository->findOneBy(array($this->property => $username)); + $user = $repository->findOneBy(array($this->property => $username)); } else { - if (!$this->repository instanceof UserLoaderInterface) { - if (!$this->repository instanceof UserProviderInterface) { - throw new \InvalidArgumentException(sprintf('The Doctrine repository "%s" must implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface.', get_class($this->repository))); + if (!$repository instanceof UserLoaderInterface) { + if (!$repository instanceof UserProviderInterface) { + throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, \get_class($repository))); } - @trigger_error('Implementing loadUserByUsername from Symfony\Component\Security\Core\User\UserProviderInterface is deprecated since version 2.8 and will be removed in 3.0. Implement the Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface instead.', E_USER_DEPRECATED); + @trigger_error('Implementing Symfony\Component\Security\Core\User\UserProviderInterface in a Doctrine repository when using the entity provider is deprecated since Symfony 2.8 and will not be supported in 3.0. Make the repository implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface instead.', E_USER_DEPRECATED); } - $user = $this->repository->loadUserByUsername($username); + $user = $repository->loadUserByUsername($username); } if (null === $user) { @@ -77,18 +73,20 @@ public function loadUserByUsername($username) */ public function refreshUser(UserInterface $user) { - if (!$user instanceof $this->class) { - throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + $class = $this->getClass(); + if (!$user instanceof $class) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user))); } - if ($this->repository instanceof UserProviderInterface) { - $refreshedUser = $this->repository->refreshUser($user); + $repository = $this->getRepository(); + if ($repository instanceof UserProviderInterface) { + $refreshedUser = $repository->refreshUser($user); } else { // The user must be reloaded via the primary key as all other data // might have changed without proper persistence in the database. // That's the case when the user has been changed by a form with // validation errors. - if (!$id = $this->metadata->getIdentifierValues($user)) { + if (!$id = $this->getClassMetadata()->getIdentifierValues($user)) { throw new \InvalidArgumentException('You cannot refresh a user '. 'from the EntityUserProvider that does not contain an identifier. '. 'The user object has to be serialized with its own identifier '. @@ -96,7 +94,7 @@ public function refreshUser(UserInterface $user) ); } - $refreshedUser = $this->repository->find($id); + $refreshedUser = $repository->find($id); if (null === $refreshedUser) { throw new UsernameNotFoundException(sprintf('User with id %s not found', json_encode($id))); } @@ -110,6 +108,36 @@ public function refreshUser(UserInterface $user) */ public function supportsClass($class) { - return $class === $this->class || is_subclass_of($class, $this->class); + return $class === $this->getClass() || is_subclass_of($class, $this->getClass()); + } + + private function getObjectManager() + { + return $this->registry->getManager($this->managerName); + } + + private function getRepository() + { + return $this->getObjectManager()->getRepository($this->classOrAlias); + } + + private function getClass() + { + if (null === $this->class) { + $class = $this->classOrAlias; + + if (false !== strpos($class, ':')) { + $class = $this->getClassMetadata()->getName(); + } + + $this->class = $class; + } + + return $this->class; + } + + private function getClassMetadata() + { + return $this->getObjectManager()->getClassMetadata($this->classOrAlias); } } diff --git a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php index 962099e36a7bc..86f9e9d3f0514 100644 --- a/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php +++ b/src/Symfony/Bridge/Doctrine/Test/DoctrineTestHelper.php @@ -12,8 +12,9 @@ namespace Symfony\Bridge\Doctrine\Test; use Doctrine\Common\Annotations\AnnotationReader; -use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\Driver\AnnotationDriver; +use PHPUnit\Framework\TestCase; /** * Provides utility functions needed in tests. @@ -29,8 +30,8 @@ class DoctrineTestHelper */ public static function createTestEntityManager() { - if (!extension_loaded('pdo_sqlite')) { - \PHPUnit_Framework_TestCase::markTestSkipped('Extension pdo_sqlite is required.'); + if (!\extension_loaded('pdo_sqlite')) { + TestCase::markTestSkipped('Extension pdo_sqlite is required.'); } $config = new \Doctrine\ORM\Configuration(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php index 55991dbf4f653..f40d1ca710ed1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ContainerAwareEventManagerTest.php @@ -11,10 +11,11 @@ namespace Symfony\Bridge\Doctrine\Tests; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\ContainerAwareEventManager; use Symfony\Component\DependencyInjection\Container; -class ContainerAwareEventManagerTest extends \PHPUnit_Framework_TestCase +class ContainerAwareEventManagerTest extends TestCase { private $container; private $evm; diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php index 45d0310e6dac8..f968bbd257abd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php @@ -12,11 +12,13 @@ namespace Symfony\Bridge\Doctrine\Tests\DataCollector; use Doctrine\DBAL\Platforms\MySqlPlatform; +use Doctrine\DBAL\Version; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -class DoctrineDataCollectorTest extends \PHPUnit_Framework_TestCase +class DoctrineDataCollectorTest extends TestCase { public function testCollectConnections() { @@ -119,7 +121,7 @@ public function testSerialization($param, $types, $expected, $explainable) public function paramProvider() { - return array( + $tests = array( array('some value', array(), 'some value', true), array(1, array(), 1, true), array(true, array(), true, true), @@ -128,6 +130,13 @@ public function paramProvider() array(fopen(__FILE__, 'r'), array(), 'Resource(stream)', false), array(new \SplFileInfo(__FILE__), array(), 'Object(SplFileInfo)', false), ); + + if (version_compare(Version::VERSION, '2.6', '>=')) { + $tests[] = array('this is not a date', array('date'), 'this is not a date', false); + $tests[] = array(new \stdClass(), array('date'), 'Object(stdClass)', false); + } + + return $tests; } private function createCollector($queries) @@ -139,20 +148,20 @@ private function createCollector($queries) ->method('getDatabasePlatform') ->will($this->returnValue(new MySqlPlatform())); - $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); $registry - ->expects($this->any()) - ->method('getConnectionNames') - ->will($this->returnValue(array('default' => 'doctrine.dbal.default_connection'))); + ->expects($this->any()) + ->method('getConnectionNames') + ->will($this->returnValue(array('default' => 'doctrine.dbal.default_connection'))); $registry - ->expects($this->any()) - ->method('getManagerNames') - ->will($this->returnValue(array('default' => 'doctrine.orm.default_entity_manager'))); + ->expects($this->any()) + ->method('getManagerNames') + ->will($this->returnValue(array('default' => 'doctrine.orm.default_entity_manager'))); $registry->expects($this->any()) ->method('getConnection') ->will($this->returnValue($connection)); - $logger = $this->getMock('Doctrine\DBAL\Logging\DebugStack'); + $logger = $this->getMockBuilder('Doctrine\DBAL\Logging\DebugStack')->getMock(); $logger->queries = $queries; $collector = new DoctrineDataCollector($registry); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php index 53ad5a0e3a8a7..422c459b46afa 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DataFixtures/ContainerAwareLoaderTest.php @@ -11,14 +11,15 @@ namespace Symfony\Bridge\Doctrine\Tests\DataFixtures; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader; use Symfony\Bridge\Doctrine\Tests\Fixtures\ContainerAwareFixture; -class ContainerAwareLoaderTest extends \PHPUnit_Framework_TestCase +class ContainerAwareLoaderTest extends TestCase { public function testShouldSetContainerOnContainerAwareFixture() { - $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); $loader = new ContainerAwareLoader($container); $fixture = new ContainerAwareFixture(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php index ba73678541e7e..7e99a7d9356c2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/CompilerPass/RegisterEventListenersAndSubscribersPassTest.php @@ -11,11 +11,13 @@ namespace Symfony\Bridge\Doctrine\Tests\DependencyInjection\CompilerPass; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\DependencyInjection\CompilerPass\RegisterEventListenersAndSubscribersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; -class RegisterEventListenersAndSubscribersPassTest extends \PHPUnit_Framework_TestCase +class RegisterEventListenersAndSubscribersPassTest extends TestCase { /** * @expectedException \InvalidArgumentException @@ -55,12 +57,18 @@ public function testProcessEventListenersWithPriorities() $container ->register('a', 'stdClass') + ->setPublic(false) + ->addTag('doctrine.event_listener', array( + 'event' => 'bar', + )) ->addTag('doctrine.event_listener', array( 'event' => 'foo', 'priority' => -5, )) ->addTag('doctrine.event_listener', array( - 'event' => 'bar', + 'event' => 'foo_bar', + 'priority' => 3, + 'lazy' => true, )) ; $container @@ -69,12 +77,34 @@ public function testProcessEventListenersWithPriorities() 'event' => 'foo', )) ; + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'foo_bar', + 'priority' => 4, + )) + ; $this->process($container); - $this->assertEquals(array('b', 'a'), $this->getServiceOrder($container, 'addEventListener')); - - $calls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); - $this->assertEquals(array('foo', 'bar'), $calls[1][1][0]); + $methodCalls = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + + $this->assertEquals( + array( + array('addEventListener', array(array('foo_bar'), new Reference('c'))), + array('addEventListener', array(array('foo_bar'), new Reference('a'))), + array('addEventListener', array(array('bar'), new Reference('a'))), + array('addEventListener', array(array('foo'), new Reference('b'))), + array('addEventListener', array(array('foo'), new Reference('a'))), + ), + $methodCalls + ); + + // not lazy so must be reference + $this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $methodCalls[0][1][1]); + + // lazy so id instead of reference and must mark service public + $this->assertSame('a', $methodCalls[1][1][1]); + $this->assertTrue($container->getDefinition('a')->isPublic()); } public function testProcessEventListenersWithMultipleConnections() @@ -87,15 +117,86 @@ public function testProcessEventListenersWithMultipleConnections() 'event' => 'onFlush', )) ; + + $container + ->register('b', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'onFlush', + 'connection' => 'default', + )) + ; + + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_listener', array( + 'event' => 'onFlush', + 'connection' => 'second', + )) + ; + $this->process($container); - $callsDefault = $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls(); + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), new Reference('a'))), + array('addEventListener', array(array('onFlush'), new Reference('b'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); + + $this->assertEquals( + array( + array('addEventListener', array(array('onFlush'), new Reference('a'))), + array('addEventListener', array(array('onFlush'), new Reference('c'))), + ), + $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + ); + } - $this->assertEquals('addEventListener', $callsDefault[0][0]); - $this->assertEquals(array('onFlush'), $callsDefault[0][1][0]); + public function testProcessEventSubscribersWithMultipleConnections() + { + $container = $this->createBuilder(true); - $callsSecond = $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls(); - $this->assertEquals($callsDefault, $callsSecond); + $container + ->register('a', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + )) + ; + + $container + ->register('b', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + 'connection' => 'default', + )) + ; + + $container + ->register('c', 'stdClass') + ->addTag('doctrine.event_subscriber', array( + 'event' => 'onFlush', + 'connection' => 'second', + )) + ; + + $this->process($container); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('a'))), + array('addEventSubscriber', array(new Reference('b'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('a'))), + array('addEventSubscriber', array(new Reference('c'))), + ), + $container->getDefinition('doctrine.dbal.second_connection.event_manager')->getMethodCalls() + ); } public function testProcessEventSubscribersWithPriorities() @@ -132,11 +233,17 @@ public function testProcessEventSubscribersWithPriorities() ; $this->process($container); - $serviceOrder = $this->getServiceOrder($container, 'addEventSubscriber'); - $unordered = array_splice($serviceOrder, 0, 3); - sort($unordered); - $this->assertEquals(array('c', 'd', 'e'), $unordered); - $this->assertEquals(array('b', 'a'), $serviceOrder); + + $this->assertEquals( + array( + array('addEventSubscriber', array(new Reference('c'))), + array('addEventSubscriber', array(new Reference('d'))), + array('addEventSubscriber', array(new Reference('e'))), + array('addEventSubscriber', array(new Reference('b'))), + array('addEventSubscriber', array(new Reference('a'))), + ), + $container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() + ); } public function testProcessNoTaggedServices() @@ -156,26 +263,6 @@ private function process(ContainerBuilder $container) $pass->process($container); } - private function getServiceOrder(ContainerBuilder $container, $method) - { - $order = array(); - foreach ($container->getDefinition('doctrine.dbal.default_connection.event_manager')->getMethodCalls() as $call) { - list($name, $arguments) = $call; - if ($method !== $name) { - continue; - } - - if ('addEventListener' === $name) { - $order[] = (string) $arguments[1]; - continue; - } - - $order[] = (string) $arguments[0]; - } - - return $order; - } - private function createBuilder($multipleConnections = false) { $container = new ContainerBuilder(); diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 760c0fada0fab..39e3eb7634328 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -11,14 +11,15 @@ namespace Symfony\Bridge\Doctrine\Tests\DependencyInjection; -use Symfony\Component\DependencyInjection\Definition; +use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** * @author Fabio B. Silva */ -class DoctrineExtensionTest extends \PHPUnit_Framework_TestCase +class DoctrineExtensionTest extends TestCase { /** * @var \Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension @@ -44,7 +45,7 @@ protected function setUp() $this->extension->expects($this->any()) ->method('getObjectManagerElementName') ->will($this->returnCallback(function ($name) { - return 'doctrine.orm.'.$name; + return 'doctrine.orm.'.$name; })); } @@ -67,7 +68,7 @@ public function testFixManagersAutoMappingsWithTwoAutomappings() 'SecondBundle' => 'My\SecondBundle', ); - $reflection = new \ReflectionClass(get_class($this->extension)); + $reflection = new \ReflectionClass(\get_class($this->extension)); $method = $reflection->getMethod('fixManagersAutoMappings'); $method->setAccessible(true); @@ -156,7 +157,7 @@ public function testFixManagersAutoMappings(array $originalEm1, array $originalE 'SecondBundle' => 'My\SecondBundle', ); - $reflection = new \ReflectionClass(get_class($this->extension)); + $reflection = new \ReflectionClass(\get_class($this->extension)); $method = $reflection->getMethod('fixManagersAutoMappings'); $method->setAccessible(true); @@ -266,8 +267,6 @@ protected function invokeLoadCacheDriver(array $objectManager, ContainerBuilder } /** - * @param array $data - * * @return \Symfony\Component\DependencyInjection\ContainerBuilder */ protected function createContainer(array $data = array()) diff --git a/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php b/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php index 283c65ee4d139..04721d5de1b9b 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DoctrineOrmTestCase.php @@ -11,9 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests; -@trigger_error('The '.__NAMESPACE__.'\DoctrineOrmTestCase class is deprecated since version 2.4 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper class instead.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\DoctrineOrmTestCase class is deprecated since Symfony 2.4 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper class instead.', E_USER_DEPRECATED); use Doctrine\ORM\EntityManager; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; /** @@ -22,7 +23,7 @@ * @deprecated since version 2.4, to be removed in 3.0. * Use {@link DoctrineTestHelper} instead. */ -abstract class DoctrineOrmTestCase extends \PHPUnit_Framework_TestCase +abstract class DoctrineOrmTestCase extends TestCase { /** * @return EntityManager diff --git a/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php b/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php index a473b3ace2fe5..d7abe2c75a54f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/ExpressionLanguage/DoctrineParserCacheTest.php @@ -11,13 +11,14 @@ namespace Symfony\Bridge\Doctrine\Tests\ExpressionLanguage; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\ExpressionLanguage\DoctrineParserCache; -class DoctrineParserCacheTest extends \PHPUnit_Framework_TestCase +class DoctrineParserCacheTest extends TestCase { public function testFetch() { - $doctrineCacheMock = $this->getMock('Doctrine\Common\Cache\Cache'); + $doctrineCacheMock = $this->getMockBuilder('Doctrine\Common\Cache\Cache')->getMock(); $parserCache = new DoctrineParserCache($doctrineCacheMock); $doctrineCacheMock->expects($this->once()) @@ -31,7 +32,7 @@ public function testFetch() public function testFetchUnexisting() { - $doctrineCacheMock = $this->getMock('Doctrine\Common\Cache\Cache'); + $doctrineCacheMock = $this->getMockBuilder('Doctrine\Common\Cache\Cache')->getMock(); $parserCache = new DoctrineParserCache($doctrineCacheMock); $doctrineCacheMock @@ -44,7 +45,7 @@ public function testFetchUnexisting() public function testSave() { - $doctrineCacheMock = $this->getMock('Doctrine\Common\Cache\Cache'); + $doctrineCacheMock = $this->getMockBuilder('Doctrine\Common\Cache\Cache')->getMock(); $parserCache = new DoctrineParserCache($doctrineCacheMock); $expression = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParsedExpression') diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php index 740a4f55f49cd..8a9b00ddc73e7 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeIntIdEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class CompositeIntIdEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php index 10e083a8f4298..0755a89e6a923 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CompositeStringIdEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class CompositeStringIdEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php index 5141e1669c9f3..6c3f880eaacf9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/ContainerAwareFixture.php @@ -13,8 +13,8 @@ use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Common\Persistence\ObjectManager; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; class ContainerAwareFixture implements FixtureInterface, ContainerAwareInterface { diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php index cfb8e8b6664ff..3559568787bcd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNameEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class DoubleNameEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php new file mode 100644 index 0000000000000..20ef14fd1b578 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoubleNullableNameEntity.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; + +/** @Entity */ +class DoubleNullableNameEntity +{ + /** @Id @Column(type="integer") */ + protected $id; + + /** @Column(type="string", nullable=true) */ + public $name; + + /** @Column(type="string", nullable=true) */ + public $name2; + + public function __construct($id, $name, $name2) + { + $this->id = $id; + $this->name = $name; + $this->name2 = $name2; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php new file mode 100644 index 0000000000000..f8000dbfd9814 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Embeddable/Identifier.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Embeddable + */ +class Identifier +{ + /** + * @var int + * + * @ORM\Id + * @ORM\Column(type="integer") + */ + protected $value; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php new file mode 100644 index 0000000000000..6d7b2670962c7 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/EmbeddedIdentifierEntity.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + */ +class EmbeddedIdentifierEntity +{ + /** + * @var Embeddable\Identifier + * + * @ORM\Embedded(class="Symfony\Bridge\Doctrine\Tests\Fixtures\Embeddable\Identifier") + */ + protected $id; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php index 2e36204bdfdad..730a9b2b1dbf8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GroupableEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class GroupableEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php new file mode 100644 index 0000000000000..0d447ffc1e62c --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/GuidIdEntity.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; + +/** @Entity */ +class GuidIdEntity +{ + /** @Id @Column(type="guid") */ + protected $id; + + public function __construct($id) + { + $this->id = $id; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php index 954de338d3ca3..5cd6d407962aa 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleAssociationToIntIdEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\OneToOne; /** @Entity */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php index 44630a1fc51f1..d9d661efca772 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class SingleIntIdEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php index bcbe7a5f7bdeb..c94815ca02cad 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleIntIdNoToStringEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class SingleIntIdNoToStringEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php new file mode 100644 index 0000000000000..e457f69dd091b --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringCastableIdEntity.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\GeneratedValue; +use Doctrine\ORM\Mapping\Id; + +/** @Entity */ +class SingleStringCastableIdEntity +{ + /** + * @Id + * @Column(type="string") + * @GeneratedValue(strategy="NONE") + */ + protected $id; + + /** @Column(type="string", nullable=true) */ + public $name; + + public function __construct($id, $name) + { + $this->id = new StringCastableObjectIdentity($id); + $this->name = $name; + } + + public function __toString() + { + return (string) $this->name; + } +} + +class StringCastableObjectIdentity +{ + protected $id; + + public function __construct($id) + { + $this->id = $id; + } + + public function __toString() + { + return (string) $this->id; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php index 258c5a65158e7..3e25e2aea52bd 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/SingleStringIdEntity.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; /** @Entity */ class SingleStringIdEntity diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php index e59e32c27e82c..c2ad425b61eac 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; -use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; use Symfony\Component\Security\Core\User\UserInterface; /** @Entity */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php new file mode 100644 index 0000000000000..46084ab292d49 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UuidIdEntity.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; + +/** @Entity */ +class UuidIdEntity +{ + /** @Id @Column(type="uuid") */ + protected $id; + + public function __construct($id) + { + $this->id = $id; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php index 5980d9c734c54..8bf2fb804f939 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListCompositeIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php index 7324f721ec340..c46ece7239731 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,9 +11,13 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; -use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; + +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} /** * Test choices generated from an entity with a primary foreign key. diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php index 74af66db360e2..85ea10a451c22 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleIntIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php index 56b4c21319826..1bd5ac8de4695 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleStringIdTest.php @@ -13,6 +13,10 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php index 4f3d54a30f15f..cff0d91b55db4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php @@ -11,11 +11,15 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; -use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Doctrine\ORM\Tools\SchemaTool; +use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; +use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php new file mode 100644 index 0000000000000..5999e8e581000 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/DoctrineChoiceLoaderTest.php @@ -0,0 +1,460 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; + +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Common\Persistence\ObjectRepository; +use Doctrine\ORM\Mapping\ClassMetadata; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader; +use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface; +use Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader; +use Symfony\Component\Form\ChoiceList\ArrayChoiceList; +use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface; + +/** + * @author Bernhard Schussek + */ +class DoctrineChoiceLoaderTest extends TestCase +{ + /** + * @var ChoiceListFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $factory; + + /** + * @var ObjectManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $om; + + /** + * @var ObjectRepository|\PHPUnit_Framework_MockObject_MockObject + */ + private $repository; + + /** + * @var string + */ + private $class; + + /** + * @var IdReader|\PHPUnit_Framework_MockObject_MockObject + */ + private $idReader; + + /** + * @var EntityLoaderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectLoader; + + /** + * @var \stdClass + */ + private $obj1; + + /** + * @var \stdClass + */ + private $obj2; + + /** + * @var \stdClass + */ + private $obj3; + + protected function setUp() + { + $this->factory = $this->getMockBuilder('Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface')->getMock(); + $this->om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock(); + $this->repository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock(); + $this->class = 'stdClass'; + $this->idReader = $this->getMockBuilder('Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader') + ->disableOriginalConstructor() + ->getMock(); + $this->objectLoader = $this->getMockBuilder('Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface')->getMock(); + $this->obj1 = (object) array('name' => 'A'); + $this->obj2 = (object) array('name' => 'B'); + $this->obj3 = (object) array('name' => 'C'); + + $this->om->expects($this->any()) + ->method('getRepository') + ->with($this->class) + ->willReturn($this->repository); + + $this->om->expects($this->any()) + ->method('getClassMetadata') + ->with($this->class) + ->willReturn(new ClassMetadata($this->class)); + } + + public function testLoadChoiceList() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $choices = array($this->obj1, $this->obj2, $this->obj3); + $choiceList = new ArrayChoiceList(array()); + $value = function () {}; + + $this->repository->expects($this->once()) + ->method('findAll') + ->willReturn($choices); + + $this->factory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices, $value) + ->willReturn($choiceList); + + $this->assertSame($choiceList, $loader->loadChoiceList($value)); + + // no further loads on subsequent calls + + $this->assertSame($choiceList, $loader->loadChoiceList($value)); + } + + public function testLoadChoiceListUsesObjectLoaderIfAvailable() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader, + $this->objectLoader + ); + + $choices = array($this->obj1, $this->obj2, $this->obj3); + $choiceList = new ArrayChoiceList(array()); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->objectLoader->expects($this->once()) + ->method('getEntities') + ->willReturn($choices); + + $this->factory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices) + ->willReturn($choiceList); + + $this->assertSame($choiceList, $loader->loadChoiceList()); + + // no further loads on subsequent calls + + $this->assertSame($choiceList, $loader->loadChoiceList()); + } + + public function testLoadValuesForChoices() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $choices = array($this->obj1, $this->obj2, $this->obj3); + $choiceList = new ArrayChoiceList($choices); + + $this->repository->expects($this->once()) + ->method('findAll') + ->willReturn($choices); + + $this->factory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices) + ->willReturn($choiceList); + + $this->assertSame(array('1', '2'), $loader->loadValuesForChoices(array($this->obj2, $this->obj3))); + + // no further loads on subsequent calls + + $this->assertSame(array('1', '2'), $loader->loadValuesForChoices(array($this->obj2, $this->obj3))); + } + + public function testLoadValuesForChoicesDoesNotLoadIfEmptyChoices() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->factory->expects($this->never()) + ->method('createListFromChoices'); + + $this->assertSame(array(), $loader->loadValuesForChoices(array())); + } + + public function testLoadValuesForChoicesDoesNotLoadIfSingleIntId() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $this->idReader->expects($this->any()) + ->method('isSingleId') + ->willReturn(true); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->factory->expects($this->never()) + ->method('createListFromChoices'); + + $this->idReader->expects($this->any()) + ->method('getIdValue') + ->with($this->obj2) + ->willReturn('2'); + + $this->assertSame(array('2'), $loader->loadValuesForChoices(array($this->obj2))); + } + + public function testLoadValuesForChoicesLoadsIfSingleIntIdAndValueGiven() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $choices = array($this->obj1, $this->obj2, $this->obj3); + $value = function (\stdClass $object) { return $object->name; }; + $choiceList = new ArrayChoiceList($choices, $value); + + $this->idReader->expects($this->any()) + ->method('isSingleId') + ->willReturn(true); + + $this->repository->expects($this->once()) + ->method('findAll') + ->willReturn($choices); + + $this->factory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices, $value) + ->willReturn($choiceList); + + $this->assertSame(array('B'), $loader->loadValuesForChoices( + array($this->obj2), + $value + )); + } + + public function testLoadValuesForChoicesDoesNotLoadIfValueIsIdReader() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $value = array($this->idReader, 'getIdValue'); + + $this->idReader->expects($this->any()) + ->method('isSingleId') + ->willReturn(true); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->factory->expects($this->never()) + ->method('createListFromChoices'); + + $this->idReader->expects($this->any()) + ->method('getIdValue') + ->with($this->obj2) + ->willReturn('2'); + + $this->assertSame(array('2'), $loader->loadValuesForChoices( + array($this->obj2), + $value + )); + } + + public function testLoadChoicesForValues() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $choices = array($this->obj1, $this->obj2, $this->obj3); + $choiceList = new ArrayChoiceList($choices); + + $this->repository->expects($this->once()) + ->method('findAll') + ->willReturn($choices); + + $this->factory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices) + ->willReturn($choiceList); + + $this->assertSame(array($this->obj2, $this->obj3), $loader->loadChoicesForValues(array('1', '2'))); + + // no further loads on subsequent calls + + $this->assertSame(array($this->obj2, $this->obj3), $loader->loadChoicesForValues(array('1', '2'))); + } + + public function testLoadChoicesForValuesDoesNotLoadIfEmptyValues() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->factory->expects($this->never()) + ->method('createListFromChoices'); + + $this->assertSame(array(), $loader->loadChoicesForValues(array())); + } + + public function testLoadChoicesForValuesLoadsOnlyChoicesIfSingleIntId() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader, + $this->objectLoader + ); + + $choices = array($this->obj2, $this->obj3); + + $this->idReader->expects($this->any()) + ->method('isSingleId') + ->willReturn(true); + + $this->idReader->expects($this->any()) + ->method('getIdField') + ->willReturn('idField'); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->factory->expects($this->never()) + ->method('createListFromChoices'); + + $this->objectLoader->expects($this->once()) + ->method('getEntitiesByIds') + ->with('idField', array(4 => '3', 7 => '2')) + ->willReturn($choices); + + $this->idReader->expects($this->any()) + ->method('getIdValue') + ->willReturnMap(array( + array($this->obj2, '2'), + array($this->obj3, '3'), + )); + + $this->assertSame( + array(4 => $this->obj3, 7 => $this->obj2), + $loader->loadChoicesForValues(array(4 => '3', 7 => '2') + )); + } + + public function testLoadChoicesForValuesLoadsAllIfSingleIntIdAndValueGiven() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader + ); + + $choices = array($this->obj1, $this->obj2, $this->obj3); + $value = function (\stdClass $object) { return $object->name; }; + $choiceList = new ArrayChoiceList($choices, $value); + + $this->idReader->expects($this->any()) + ->method('isSingleId') + ->willReturn(true); + + $this->repository->expects($this->once()) + ->method('findAll') + ->willReturn($choices); + + $this->factory->expects($this->once()) + ->method('createListFromChoices') + ->with($choices, $value) + ->willReturn($choiceList); + + $this->assertSame(array($this->obj2), $loader->loadChoicesForValues( + array('B'), + $value + )); + } + + public function testLoadChoicesForValuesLoadsOnlyChoicesIfValueIsIdReader() + { + $loader = new DoctrineChoiceLoader( + $this->factory, + $this->om, + $this->class, + $this->idReader, + $this->objectLoader + ); + + $choices = array($this->obj2, $this->obj3); + $value = array($this->idReader, 'getIdValue'); + + $this->idReader->expects($this->any()) + ->method('isSingleId') + ->willReturn(true); + + $this->idReader->expects($this->any()) + ->method('getIdField') + ->willReturn('idField'); + + $this->repository->expects($this->never()) + ->method('findAll'); + + $this->factory->expects($this->never()) + ->method('createListFromChoices'); + + $this->objectLoader->expects($this->once()) + ->method('getEntitiesByIds') + ->with('idField', array('2')) + ->willReturn($choices); + + $this->idReader->expects($this->any()) + ->method('getIdValue') + ->willReturnMap(array( + array($this->obj2, '2'), + array($this->obj3, '3'), + )); + + $this->assertSame(array($this->obj2), $loader->loadChoicesForValues(array('2'), $value)); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/GenericEntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/GenericEntityChoiceListTest.php index f4530091fbb0b..2503370cb1e12 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/GenericEntityChoiceListTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/GenericEntityChoiceListTest.php @@ -11,18 +11,19 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +use Doctrine\ORM\Tools\SchemaTool; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; -use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Component\Form\Extension\Core\View\ChoiceView; -use Doctrine\ORM\Tools\SchemaTool; /** * @group legacy */ -class GenericEntityChoiceListTest extends \PHPUnit_Framework_TestCase +class GenericEntityChoiceListTest extends TestCase { const SINGLE_INT_ID_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php index a2ee7cdc8a64f..21fa4c56a0b57 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListCompositeIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php index 60e3797bac865..73344be670eba 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php index f655784004fbb..f64e0cec3e538 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php index 629b399ac36a7..d0b04b8013656 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/LoadedEntityChoiceListSingleStringIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index 5af59445e1825..c3fea897258c0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -11,11 +11,13 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +use Doctrine\DBAL\Connection; +use Doctrine\ORM\Version; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; -use Doctrine\DBAL\Connection; -class ORMQueryBuilderLoaderTest extends \PHPUnit_Framework_TestCase +class ORMQueryBuilderLoaderTest extends TestCase { /** * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException @@ -86,7 +88,7 @@ public function testFilterNonIntegerValues() $query->expects($this->once()) ->method('setParameter') - ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', array(1, 2, 3), Connection::PARAM_INT_ARRAY) + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', array(1, 2, 3, '9223372036854775808'), Connection::PARAM_INT_ARRAY) ->willReturn($query); $qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder') @@ -102,6 +104,80 @@ public function testFilterNonIntegerValues() ->from('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity', 'e'); $loader = new ORMQueryBuilderLoader($qb); - $loader->getEntitiesByIds('id', array(1, '', 2, 3, 'foo')); + $loader->getEntitiesByIds('id', array(1, '', 2, 3, 'foo', '9223372036854775808')); + } + + /** + * @dataProvider provideGuidEntityClasses + */ + public function testFilterEmptyUuids($entityClass) + { + $em = DoctrineTestHelper::createTestEntityManager(); + + $query = $this->getMockBuilder('QueryMock') + ->setMethods(array('setParameter', 'getResult', 'getSql', '_doExecute')) + ->getMock(); + + $query->expects($this->once()) + ->method('setParameter') + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', array('71c5fd46-3f16-4abb-bad7-90ac1e654a2d', 'b98e8e11-2897-44df-ad24-d2627eb7f499'), Connection::PARAM_STR_ARRAY) + ->willReturn($query); + + $qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder') + ->setConstructorArgs(array($em)) + ->setMethods(array('getQuery')) + ->getMock(); + + $qb->expects($this->once()) + ->method('getQuery') + ->willReturn($query); + + $qb->select('e') + ->from($entityClass, 'e'); + + $loader = new ORMQueryBuilderLoader($qb); + $loader->getEntitiesByIds('id', array('71c5fd46-3f16-4abb-bad7-90ac1e654a2d', '', 'b98e8e11-2897-44df-ad24-d2627eb7f499')); + } + + public function testEmbeddedIdentifierName() + { + if (Version::compare('2.5.0') > 0) { + $this->markTestSkipped('Applicable only for Doctrine >= 2.5.0'); + + return; + } + + $em = DoctrineTestHelper::createTestEntityManager(); + + $query = $this->getMockBuilder('QueryMock') + ->setMethods(array('setParameter', 'getResult', 'getSql', '_doExecute')) + ->getMock(); + + $query->expects($this->once()) + ->method('setParameter') + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id_value', array(1, 2, 3), Connection::PARAM_INT_ARRAY) + ->willReturn($query); + + $qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder') + ->setConstructorArgs(array($em)) + ->setMethods(array('getQuery')) + ->getMock(); + $qb->expects($this->once()) + ->method('getQuery') + ->willReturn($query); + + $qb->select('e') + ->from('Symfony\Bridge\Doctrine\Tests\Fixtures\EmbeddedIdentifierEntity', 'e'); + + $loader = new ORMQueryBuilderLoader($qb); + $loader->getEntitiesByIds('id.value', array(1, '', 2, 3, 'foo')); + } + + public function provideGuidEntityClasses() + { + return array( + array('Symfony\Bridge\Doctrine\Tests\Fixtures\GuidIdEntity'), + array('Symfony\Bridge\Doctrine\Tests\Fixtures\UuidIdEntity'), + ); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php index 114eee661efe5..305f365075ebe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php index 422295feb1e04..75e36f90cf4e3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListCompositeIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php index 20267bd020775..ee3afeb89909f 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php index 45ab799585cb6..1b637949bbd09 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleAssociationToIntIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Premi Giorgio * @author Bernhard Schussek diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php index 0668c09bdb63f..0197f19b3b1fa 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php index c093782ff0ec7..4e75cf7491cca 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleIntIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php index 39363bae26a25..3e9847f0e2eff 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdTest.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php index 23329e80df3c2..51adaabba80db 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/UnloadedEntityChoiceListSingleStringIdWithQueryBuilderTest.php @@ -14,6 +14,10 @@ use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; +if (!class_exists('Symfony\Component\Form\Tests\Extension\Core\ChoiceList\AbstractChoiceListTest')) { + return; +} + /** * @author Bernhard Schussek * @group legacy diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php index 50b9bcd19e172..fa3ff911ad355 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DataTransformer/CollectionToArrayTransformerTest.php @@ -12,12 +12,13 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\DataTransformer; use Doctrine\Common\Collections\ArrayCollection; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer; /** * @author Bernhard Schussek */ -class CollectionToArrayTransformerTest extends \PHPUnit_Framework_TestCase +class CollectionToArrayTransformerTest extends TestCase { /** * @var CollectionToArrayTransformer diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php index 0cb900f6d04c4..0eda4a3ba6f0a 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/DoctrineOrmTypeGuesserTest.php @@ -12,11 +12,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Form; use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\ValueGuess; -class DoctrineOrmTypeGuesserTest extends \PHPUnit_Framework_TestCase +class DoctrineOrmTypeGuesserTest extends TestCase { /** * @dataProvider requiredProvider @@ -32,21 +33,20 @@ public function requiredProvider() // Simple field, not nullable $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); - $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(true)); + $classMetadata->fieldMappings['field'] = true; $classMetadata->expects($this->once())->method('isNullable')->with('field')->will($this->returnValue(false)); $return[] = array($classMetadata, new ValueGuess(true, Guess::HIGH_CONFIDENCE)); // Simple field, nullable $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); - $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(true)); + $classMetadata->fieldMappings['field'] = true; $classMetadata->expects($this->once())->method('isNullable')->with('field')->will($this->returnValue(true)); $return[] = array($classMetadata, new ValueGuess(false, Guess::MEDIUM_CONFIDENCE)); // One-to-one, nullable (by default) $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); - $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(true)); $mapping = array('joinColumns' => array(array())); @@ -56,7 +56,6 @@ public function requiredProvider() // One-to-one, nullable (explicit) $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); - $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(true)); $mapping = array('joinColumns' => array(array('nullable' => true))); @@ -66,7 +65,6 @@ public function requiredProvider() // One-to-one, not nullable $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); - $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(true)); $mapping = array('joinColumns' => array(array('nullable' => false))); @@ -76,7 +74,6 @@ public function requiredProvider() // One-to-many, no clue $classMetadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')->disableOriginalConstructor()->getMock(); - $classMetadata->expects($this->once())->method('hasField')->with('field')->will($this->returnValue(false)); $classMetadata->expects($this->once())->method('isAssociationWithSingleJoinColumn')->with('field')->will($this->returnValue(false)); $return[] = array($classMetadata, null); @@ -86,10 +83,10 @@ public function requiredProvider() private function getGuesser(ClassMetadata $classMetadata) { - $em = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $em = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock(); $em->expects($this->once())->method('getClassMetaData')->with('TestEntity')->will($this->returnValue($classMetadata)); - $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); $registry->expects($this->once())->method('getManagers')->will($this->returnValue(array($em))); return new DoctrineOrmTypeGuesser($registry); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php index 57df4195bcec4..f4f7effa61c20 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypePerformanceTest.php @@ -11,12 +11,12 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\Type; -use Symfony\Component\Form\Test\FormPerformanceTestCase; -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Doctrine\ORM\Tools\SchemaTool; +use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Component\Form\Extension\Core\CoreExtension; -use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension; +use Symfony\Component\Form\Test\FormPerformanceTestCase; /** * @author Bernhard Schussek @@ -32,7 +32,7 @@ class EntityTypePerformanceTest extends FormPerformanceTestCase protected function getExtensions() { - $manager = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $manager = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); $manager->expects($this->any()) ->method('getManager') @@ -72,7 +72,7 @@ protected function setUp() $ids = range(1, 300); foreach ($ids as $id) { - $name = 65 + chr($id % 57); + $name = 65 + (int) \chr($id % 57); $this->em->persist(new SingleIntIdEntity($id, $name)); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index fb78d65ef1813..351ff941d55b1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -23,23 +23,27 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringCastableIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\Forms; -use Symfony\Component\Form\Test\TypeTestCase; -use Symfony\Component\PropertyAccess\PropertyAccess; -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity; -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; +use Symfony\Component\Form\Tests\Extension\Core\Type\BaseTypeTest; +use Symfony\Component\Form\Tests\Extension\Core\Type\FormTypeTest; -class EntityTypeTest extends TypeTestCase +class EntityTypeTest extends BaseTypeTest { + const TESTED_TYPE = 'Symfony\Bridge\Doctrine\Form\Type\EntityType'; + const ITEM_GROUP_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\GroupableEntity'; const SINGLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'; const SINGLE_IDENT_NO_TO_STRING_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity'; const SINGLE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity'; const SINGLE_ASSOC_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleAssociationToIntIdEntity'; + const SINGLE_STRING_CASTABLE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringCastableIdEntity'; const COMPOSITE_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity'; const COMPOSITE_STRING_IDENT_CLASS = 'Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeStringIdEntity'; @@ -67,6 +71,7 @@ protected function setUp() $this->em->getClassMetadata(self::SINGLE_IDENT_NO_TO_STRING_CLASS), $this->em->getClassMetadata(self::SINGLE_STRING_IDENT_CLASS), $this->em->getClassMetadata(self::SINGLE_ASSOC_IDENT_CLASS), + $this->em->getClassMetadata(self::SINGLE_STRING_CASTABLE_IDENT_CLASS), $this->em->getClassMetadata(self::COMPOSITE_IDENT_CLASS), $this->em->getClassMetadata(self::COMPOSITE_STRING_IDENT_CLASS), ); @@ -113,7 +118,7 @@ protected function persist(array $entities) */ public function testLegacyName() { - $field = $this->factory->createNamed('name', 'entity', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, )); @@ -126,7 +131,7 @@ public function testLegacyName() */ public function testClassOptionIsRequired() { - $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType'); + $this->factory->createNamed('name', static::TESTED_TYPE); } /** @@ -134,7 +139,7 @@ public function testClassOptionIsRequired() */ public function testInvalidClassOption() { - $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'class' => 'foo', )); } @@ -146,7 +151,7 @@ public function testSetDataToUninitializedEntityWithNonRequired() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, @@ -163,13 +168,14 @@ public function testSetDataToUninitializedEntityWithNonRequiredToString() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $view = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, - )); + )) + ->createView(); - $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); + $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $view->vars['choices']); } public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() @@ -180,15 +186,16 @@ public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() $this->persist(array($entity1, $entity2)); $qb = $this->em->createQueryBuilder()->select('e')->from(self::SINGLE_IDENT_CLASS, 'e'); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $view = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'choice_label' => 'name', 'query_builder' => $qb, - )); + )) + ->createView(); - $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); + $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $view->vars['choices']); } /** @@ -196,7 +203,7 @@ public function testSetDataToUninitializedEntityWithNonRequiredQueryBuilder() */ public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => new \stdClass(), @@ -208,7 +215,7 @@ public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() */ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function () { @@ -219,9 +226,14 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() $field->submit('2'); } - public function testConfigureQueryBuilderWithClosureReturningNull() + public function testConfigureQueryBuilderWithClosureReturningNullUseDefault() { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function () { @@ -229,12 +241,12 @@ public function testConfigureQueryBuilderWithClosureReturningNull() }, )); - $this->assertEquals(array(), $field->createView()->vars['choices']); + $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); } public function testSetDataSingleNull() { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, @@ -247,7 +259,7 @@ public function testSetDataSingleNull() public function testSetDataMultipleExpandedNull() { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -261,7 +273,7 @@ public function testSetDataMultipleExpandedNull() public function testSetDataMultipleNonExpandedNull() { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -273,47 +285,6 @@ public function testSetDataMultipleNonExpandedNull() $this->assertSame(array(), $field->getViewData()); } - public function testSubmitSingleExpandedNull() - { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( - 'multiple' => false, - 'expanded' => true, - 'em' => 'default', - 'class' => self::SINGLE_IDENT_CLASS, - )); - $field->submit(null); - - $this->assertNull($field->getData()); - $this->assertNull($field->getViewData()); - } - - public function testSubmitSingleNonExpandedNull() - { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( - 'multiple' => false, - 'expanded' => false, - 'em' => 'default', - 'class' => self::SINGLE_IDENT_CLASS, - )); - $field->submit(null); - - $this->assertNull($field->getData()); - $this->assertSame('', $field->getViewData()); - } - - public function testSubmitMultipleNull() - { - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( - 'multiple' => true, - 'em' => 'default', - 'class' => self::SINGLE_IDENT_CLASS, - )); - $field->submit(null); - - $this->assertEquals(new ArrayCollection(), $field->getData()); - $this->assertSame(array(), $field->getViewData()); - } - public function testSubmitSingleNonExpandedSingleIdentifier() { $entity1 = new SingleIntIdEntity(1, 'Foo'); @@ -321,7 +292,7 @@ public function testSubmitSingleNonExpandedSingleIdentifier() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -346,7 +317,7 @@ public function testSubmitSingleNonExpandedSingleAssocIdentifier() $this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -368,7 +339,7 @@ public function testSubmitSingleNonExpandedCompositeIdentifier() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -392,7 +363,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifier() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -421,7 +392,7 @@ public function testSubmitMultipleNonExpandedSingleAssocIdentifier() $this->persist(array($innerEntity1, $innerEntity2, $innerEntity3, $entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -446,7 +417,7 @@ public function testSubmitMultipleNonExpandedSingleIdentifierForExistingData() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -477,7 +448,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifier() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -503,7 +474,7 @@ public function testSubmitMultipleNonExpandedCompositeIdentifierExistingData() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => false, 'em' => 'default', @@ -533,7 +504,7 @@ public function testSubmitSingleExpanded() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => true, 'em' => 'default', @@ -559,7 +530,7 @@ public function testSubmitMultipleExpanded() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -588,7 +559,7 @@ public function testSubmitMultipleExpandedWithNegativeIntegerId() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => true, 'expanded' => true, 'em' => 'default', @@ -606,6 +577,139 @@ public function testSubmitMultipleExpandedWithNegativeIntegerId() $this->assertFalse($field['2']->getData()); } + public function testSubmitSingleNonExpandedStringCastableIdentifier() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'multiple' => false, + 'expanded' => false, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit('2'); + + $this->assertTrue($field->isSynchronized()); + $this->assertSame($entity2, $field->getData()); + $this->assertSame('2', $field->getViewData()); + } + + public function testSubmitSingleStringCastableIdentifierExpanded() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'multiple' => false, + 'expanded' => true, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit('2'); + + $this->assertTrue($field->isSynchronized()); + $this->assertSame($entity2, $field->getData()); + $this->assertFalse($field['0']->getData()); + $this->assertTrue($field['1']->getData()); + $this->assertNull($field['0']->getViewData()); + $this->assertSame('2', $field['1']->getViewData()); + } + + public function testSubmitMultipleNonExpandedStringCastableIdentifierForExistingData() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + $entity3 = new SingleStringCastableIdEntity(3, 'Baz'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'multiple' => true, + 'expanded' => false, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $existing = new ArrayCollection(array(0 => $entity2)); + + $field->setData($existing); + $field->submit(array('1', '3')); + + // entry with index 0 ($entity2) was replaced + $expected = new ArrayCollection(array(0 => $entity1, 1 => $entity3)); + + $this->assertTrue($field->isSynchronized()); + $this->assertEquals($expected, $field->getData()); + // same object still, useful if it is a PersistentCollection + $this->assertSame($existing, $field->getData()); + $this->assertSame(array('1', '3'), $field->getViewData()); + } + + public function testSubmitMultipleNonExpandedStringCastableIdentifier() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + $entity3 = new SingleStringCastableIdEntity(3, 'Baz'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'multiple' => true, + 'expanded' => false, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit(array('1', '3')); + + $expected = new ArrayCollection(array($entity1, $entity3)); + + $this->assertTrue($field->isSynchronized()); + $this->assertEquals($expected, $field->getData()); + $this->assertSame(array('1', '3'), $field->getViewData()); + } + + public function testSubmitMultipleStringCastableIdentifierExpanded() + { + $entity1 = new SingleStringCastableIdEntity(1, 'Foo'); + $entity2 = new SingleStringCastableIdEntity(2, 'Bar'); + $entity3 = new SingleStringCastableIdEntity(3, 'Bar'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'multiple' => true, + 'expanded' => true, + 'em' => 'default', + 'class' => self::SINGLE_STRING_CASTABLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $field->submit(array('1', '3')); + + $expected = new ArrayCollection(array($entity1, $entity3)); + + $this->assertTrue($field->isSynchronized()); + $this->assertEquals($expected, $field->getData()); + $this->assertTrue($field['0']->getData()); + $this->assertFalse($field['1']->getData()); + $this->assertTrue($field['2']->getData()); + $this->assertSame('1', $field['0']->getViewData()); + $this->assertNull($field['1']->getViewData()); + $this->assertSame('3', $field['2']->getViewData()); + } + public function testOverrideChoices() { $entity1 = new SingleIntIdEntity(1, 'Foo'); @@ -614,7 +718,7 @@ public function testOverrideChoices() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, // not all persisted entities should be displayed @@ -630,6 +734,83 @@ public function testOverrideChoices() $this->assertSame('2', $field->getViewData()); } + public function testOverrideChoicesValues() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'choice_label' => 'name', + 'choice_value' => 'name', + )); + + $field->submit('Bar'); + + $this->assertEquals(array('Foo' => new ChoiceView($entity1, 'Foo', 'Foo'), 'Bar' => new ChoiceView($entity2, 'Bar', 'Bar')), $field->createView()->vars['choices']); + $this->assertTrue($field->isSynchronized(), 'Field should be synchronized.'); + $this->assertSame($entity2, $field->getData(), 'Entity should be loaded by custom value.'); + $this->assertSame('Bar', $field->getViewData()); + } + + public function testOverrideChoicesValuesWithCallable() + { + $entity1 = new GroupableEntity(1, 'Foo', 'BazGroup'); + $entity2 = new GroupableEntity(2, 'Bar', 'BooGroup'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::ITEM_GROUP_CLASS, + 'choice_label' => 'name', + 'choice_value' => function (GroupableEntity $entity = null) { + if (null === $entity) { + return ''; + } + + return $entity->groupName.'/'.$entity->name; + }, + )); + + $field->submit('BooGroup/Bar'); + + $this->assertEquals(array( + 'BazGroup/Foo' => new ChoiceView($entity1, 'BazGroup/Foo', 'Foo'), + 'BooGroup/Bar' => new ChoiceView($entity2, 'BooGroup/Bar', 'Bar'), + ), $field->createView()->vars['choices']); + $this->assertTrue($field->isSynchronized(), 'Field should be synchronized.'); + $this->assertSame($entity2, $field->getData(), 'Entity should be loaded by custom value.'); + $this->assertSame('BooGroup/Bar', $field->getViewData()); + } + + public function testChoicesForValuesOptimization() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + + $this->persist(array($entity1, $entity2)); + + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'choice_label' => 'name', + )); + + $this->em->clear(); + + $field->submit(1); + + $unitOfWorkIdentityMap = $this->em->getUnitOfWork()->getIdentityMap(); + $managedEntitiesNames = array_map('strval', $unitOfWorkIdentityMap['Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity']); + + $this->assertContains((string) $entity1, $managedEntitiesNames); + $this->assertNotContains((string) $entity2, $managedEntitiesNames); + } + public function testGroupByChoices() { $item1 = new GroupableEntity(1, 'Foo', 'Group1'); @@ -639,7 +820,7 @@ public function testGroupByChoices() $this->persist(array($item1, $item2, $item3, $item4)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::ITEM_GROUP_CLASS, 'choices' => array($item1, $item2, $item3, $item4), @@ -670,7 +851,7 @@ public function testPreferredChoices() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'preferred_choices' => array($entity3, $entity2), @@ -689,7 +870,7 @@ public function testOverrideChoicesWithPreferredChoices() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'choices' => array($entity2, $entity3), @@ -709,7 +890,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesSingleIdentifier() $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -732,7 +913,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesSingleAssocIdentifie $this->persist(array($innerEntity1, $innerEntity2, $entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_ASSOC_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -753,7 +934,7 @@ public function testDisallowChoicesThatAreNotIncludedChoicesCompositeIdentifier( $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, 'choices' => array($entity1, $entity2), @@ -776,7 +957,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleIdentifie $repository = $this->em->getRepository(self::SINGLE_IDENT_CLASS); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => $repository->createQueryBuilder('e') @@ -804,7 +985,7 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderSingleAssocIden $repository = $this->em->getRepository(self::SINGLE_ASSOC_IDENT_CLASS); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_ASSOC_IDENT_CLASS, 'query_builder' => $repository->createQueryBuilder('e') @@ -826,10 +1007,10 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureSingle $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, - 'query_builder' => function ($repository) { + 'query_builder' => function (EntityRepository $repository) { return $repository->createQueryBuilder('e') ->where('e.id IN (1, 2)'); }, @@ -850,10 +1031,10 @@ public function testDisallowChoicesThatAreNotIncludedQueryBuilderAsClosureCompos $this->persist(array($entity1, $entity2, $entity3)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::COMPOSITE_IDENT_CLASS, - 'query_builder' => function ($repository) { + 'query_builder' => function (EntityRepository $repository) { return $repository->createQueryBuilder('e') ->where('e.id1 IN (10, 50)'); }, @@ -872,7 +1053,7 @@ public function testSubmitSingleStringIdentifier() $this->persist(array($entity1)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -893,7 +1074,7 @@ public function testSubmitCompositeStringIdentifier() $this->persist(array($entity1)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'multiple' => false, 'expanded' => false, 'em' => 'default', @@ -919,7 +1100,7 @@ public function testGetManagerForClassIfNoEm() ->with(self::SINGLE_IDENT_CLASS) ->will($this->returnValue($this->em)); - $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'choice_label' => 'name', @@ -934,7 +1115,7 @@ public function testExplicitEm() $this->emRegistry->expects($this->never()) ->method('getManagerForClass'); - $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => $this->em, 'class' => self::SINGLE_IDENT_CLASS, 'choice_label' => 'name', @@ -951,10 +1132,7 @@ public function testLoaderCaching() $repo = $this->em->getRepository(self::SINGLE_IDENT_CLASS); - $entityType = new EntityType( - $this->emRegistry, - PropertyAccess::createPropertyAccessor() - ); + $entityType = new EntityType($this->emRegistry); $entityTypeGuesser = new DoctrineOrmTypeGuesser($this->emRegistry); @@ -963,15 +1141,15 @@ public function testLoaderCaching() ->addTypeGuesser($entityTypeGuesser) ->getFormFactory(); - $formBuilder = $factory->createNamedBuilder('form', 'Symfony\Component\Form\Extension\Core\Type\FormType'); + $formBuilder = $factory->createNamedBuilder('form', FormTypeTest::TESTED_TYPE); - $formBuilder->add('property1', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array( + $formBuilder->add('property1', static::TESTED_TYPE, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => $repo->createQueryBuilder('e')->where('e.id IN (1, 2)'), )); - $formBuilder->add('property2', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array( + $formBuilder->add('property2', static::TESTED_TYPE, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function (EntityRepository $repo) { @@ -979,7 +1157,7 @@ public function testLoaderCaching() }, )); - $formBuilder->add('property3', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array( + $formBuilder->add('property3', static::TESTED_TYPE, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => function (EntityRepository $repo) { @@ -995,37 +1173,100 @@ public function testLoaderCaching() 'property3' => 2, )); - $choiceList1 = $form->get('property1')->getConfig()->getOption('choice_list'); - $choiceList2 = $form->get('property2')->getConfig()->getOption('choice_list'); - $choiceList3 = $form->get('property3')->getConfig()->getOption('choice_list'); + $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); + $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); + $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); + + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); + $this->assertSame($choiceLoader1, $choiceLoader2); + $this->assertSame($choiceLoader1, $choiceLoader3); + } + + public function testLoaderCachingWithParameters() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $entity2 = new SingleIntIdEntity(2, 'Bar'); + $entity3 = new SingleIntIdEntity(3, 'Baz'); + + $this->persist(array($entity1, $entity2, $entity3)); + + $repo = $this->em->getRepository(self::SINGLE_IDENT_CLASS); + + $entityType = new EntityType($this->emRegistry); + + $entityTypeGuesser = new DoctrineOrmTypeGuesser($this->emRegistry); + + $factory = Forms::createFormFactoryBuilder() + ->addType($entityType) + ->addTypeGuesser($entityTypeGuesser) + ->getFormFactory(); + + $formBuilder = $factory->createNamedBuilder('form', FormTypeTest::TESTED_TYPE); + + $formBuilder->add('property1', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1), + )); + + $formBuilder->add('property2', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1); + }, + )); + + $formBuilder->add('property3', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'query_builder' => function (EntityRepository $repo) { + return $repo->createQueryBuilder('e')->where('e.id = :id')->setParameter('id', 1); + }, + )); + + $form = $formBuilder->getForm(); + + $form->submit(array( + 'property1' => 1, + 'property2' => 1, + 'property3' => 2, + )); + + $choiceLoader1 = $form->get('property1')->getConfig()->getOption('choice_loader'); + $choiceLoader2 = $form->get('property2')->getConfig()->getOption('choice_loader'); + $choiceLoader3 = $form->get('property3')->getConfig()->getOption('choice_loader'); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $choiceList1); - $this->assertSame($choiceList1, $choiceList2); - $this->assertSame($choiceList1, $choiceList3); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $choiceLoader1); + $this->assertSame($choiceLoader1, $choiceLoader2); + $this->assertSame($choiceLoader1, $choiceLoader3); } + /** + * @group legacy + */ public function testCacheChoiceLists() { $entity1 = new SingleIntIdEntity(1, 'Foo'); $this->persist(array($entity1)); - $field1 = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field1 = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'choice_label' => 'name', )); - $field2 = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $field2 = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'choice_label' => 'name', )); - $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\ChoiceListInterface', $field1->getConfig()->getOption('choice_list')); - $this->assertSame($field1->getConfig()->getOption('choice_list'), $field2->getConfig()->getOption('choice_list')); + $this->assertInstanceOf('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface', $field1->getConfig()->getOption('choice_loader')); + $this->assertSame($field1->getConfig()->getOption('choice_loader'), $field2->getConfig()->getOption('choice_loader')); } /** @@ -1038,19 +1279,20 @@ public function testPropertyOption() $this->persist(array($entity1, $entity2)); - $field = $this->factory->createNamed('name', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', null, array( + $view = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( 'em' => 'default', 'class' => self::SINGLE_IDENT_CLASS, 'required' => false, 'property' => 'name', - )); + )) + ->createView(); - $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $field->createView()->vars['choices']); + $this->assertEquals(array(1 => new ChoiceView($entity1, '1', 'Foo'), 2 => new ChoiceView($entity2, '2', 'Bar')), $view->vars['choices']); } protected function createRegistryMock($name, $em) { - $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); $registry->expects($this->any()) ->method('getManager') ->with($this->equalTo($name)) @@ -1058,4 +1300,286 @@ protected function createRegistryMock($name, $em) return $registry; } + + public function testPassDisabledAsOption() + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'disabled' => true, + 'class' => self::SINGLE_IDENT_CLASS, + )); + + $this->assertTrue($form->isDisabled()); + } + + public function testPassIdAndNameToView() + { + $view = $this->factory->createNamed('name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->createView(); + + $this->assertEquals('name', $view->vars['id']); + $this->assertEquals('name', $view->vars['name']); + $this->assertEquals('name', $view->vars['full_name']); + } + + public function testStripLeadingUnderscoresAndDigitsFromId() + { + $view = $this->factory->createNamed('_09name', static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->createView(); + + $this->assertEquals('name', $view->vars['id']); + $this->assertEquals('_09name', $view->vars['name']); + $this->assertEquals('_09name', $view->vars['full_name']); + } + + public function testPassIdAndNameToViewWithParent() + { + $view = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE) + ->add('child', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->getForm() + ->createView(); + + $this->assertEquals('parent_child', $view['child']->vars['id']); + $this->assertEquals('child', $view['child']->vars['name']); + $this->assertEquals('parent[child]', $view['child']->vars['full_name']); + } + + public function testPassIdAndNameToViewWithGrandParent() + { + $builder = $this->factory->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE) + ->add('child', FormTypeTest::TESTED_TYPE); + $builder->get('child')->add('grand_child', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )); + $view = $builder->getForm()->createView(); + + $this->assertEquals('parent_child_grand_child', $view['child']['grand_child']->vars['id']); + $this->assertEquals('grand_child', $view['child']['grand_child']->vars['name']); + $this->assertEquals('parent[child][grand_child]', $view['child']['grand_child']->vars['full_name']); + } + + public function testPassTranslationDomainToView() + { + $view = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'translation_domain' => 'domain', + )) + ->createView(); + + $this->assertSame('domain', $view->vars['translation_domain']); + } + + public function testInheritTranslationDomainFromParent() + { + $view = $this->factory + ->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE, null, array( + 'translation_domain' => 'domain', + )) + ->add('child', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->getForm() + ->createView(); + + $this->assertEquals('domain', $view['child']->vars['translation_domain']); + } + + public function testPreferOwnTranslationDomain() + { + $view = $this->factory + ->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE, null, array( + 'translation_domain' => 'parent_domain', + )) + ->add('child', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'translation_domain' => 'domain', + )) + ->getForm() + ->createView(); + + $this->assertEquals('domain', $view['child']->vars['translation_domain']); + } + + public function testDefaultTranslationDomain() + { + $view = $this->factory + ->createNamedBuilder('parent', FormTypeTest::TESTED_TYPE) + ->add('child', static::TESTED_TYPE, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->getForm() + ->createView(); + + $this->assertNull($view['child']->vars['translation_domain']); + } + + public function testPassLabelToView() + { + $view = $this->factory->createNamed('__test___field', static::TESTED_TYPE, null, array( + 'label' => 'My label', + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->createView(); + + $this->assertSame('My label', $view->vars['label']); + } + + public function testPassMultipartFalseToView() + { + $view = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )) + ->createView(); + + $this->assertFalse($view->vars['multipart']); + } + + public function testSubmitNull($expected = null, $norm = null, $view = null) + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + )); + $form->submit(null); + + $this->assertNull($form->getData()); + $this->assertNull($form->getNormData()); + $this->assertSame('', $form->getViewData(), 'View data is always a string'); + } + + public function testSubmitNullExpanded() + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'expanded' => true, + )); + $form->submit(null); + + $this->assertNull($form->getData()); + $this->assertNull($form->getNormData()); + $this->assertSame('', $form->getViewData(), 'View data is always a string'); + } + + public function testSubmitNullMultiple() + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'multiple' => true, + )); + $form->submit(null); + + $collection = new ArrayCollection(); + + $this->assertEquals($collection, $form->getData()); + $this->assertEquals($collection, $form->getNormData()); + $this->assertSame(array(), $form->getViewData(), 'View data is always an array'); + } + + public function testSubmitNullExpandedMultiple() + { + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'expanded' => true, + 'multiple' => true, + )); + $form->submit(null); + + $collection = new ArrayCollection(); + + $this->assertEquals($collection, $form->getData()); + $this->assertEquals($collection, $form->getNormData()); + $this->assertSame(array(), $form->getViewData(), 'View data is always an array'); + } + + public function testSetDataEmptyArraySubmitNullMultiple() + { + $emptyArray = array(); + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'multiple' => true, + )); + $form->setData($emptyArray); + $form->submit(null); + $this->assertInternalType('array', $form->getData()); + $this->assertEquals(array(), $form->getData()); + $this->assertEquals(array(), $form->getNormData()); + $this->assertSame(array(), $form->getViewData(), 'View data is always an array'); + } + + public function testSetDataNonEmptyArraySubmitNullMultiple() + { + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $this->persist(array($entity1)); + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'multiple' => true, + )); + $existing = array(0 => $entity1); + $form->setData($existing); + $form->submit(null); + $this->assertInternalType('array', $form->getData()); + $this->assertEquals(array(), $form->getData()); + $this->assertEquals(array(), $form->getNormData()); + $this->assertSame(array(), $form->getViewData(), 'View data is always an array'); + } + + public function testSubmitNullUsesDefaultEmptyData($emptyData = 'empty', $expectedData = null) + { + $emptyData = '1'; + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $this->persist(array($entity1)); + + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertSame($entity1, $form->getNormData()); + $this->assertSame($entity1, $form->getData()); + } + + public function testSubmitNullMultipleUsesDefaultEmptyData() + { + $emptyData = array('1'); + $entity1 = new SingleIntIdEntity(1, 'Foo'); + $this->persist(array($entity1)); + + $form = $this->factory->create(static::TESTED_TYPE, null, array( + 'em' => 'default', + 'class' => self::SINGLE_IDENT_CLASS, + 'multiple' => true, + 'empty_data' => $emptyData, + )); + $form->submit(null); + + $collection = new ArrayCollection(array($entity1)); + + $this->assertSame($emptyData, $form->getViewData()); + $this->assertEquals($collection, $form->getNormData()); + $this->assertEquals($collection, $form->getData()); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php b/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php index 4f82bc37bbb79..1c9c82bc129e6 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/HttpFoundation/DbalSessionHandlerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Doctrine\Tests\HttpFoundation; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionHandler; /** @@ -18,11 +19,13 @@ * * @author Drak */ -class DbalSessionHandlerTest extends \PHPUnit_Framework_TestCase +class DbalSessionHandlerTest extends TestCase { public function testConstruct() { $connection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock(); $handler = new DbalSessionHandler($connection); + + $this->assertInstanceOf('Symfony\Bridge\Doctrine\HttpFoundation\DbalSessionHandler', $handler); } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php index 6acc47fe904e0..38bbed12945fe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Logger/DbalLoggerTest.php @@ -11,16 +11,17 @@ namespace Symfony\Bridge\Doctrine\Tests\Logger; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Logger\DbalLogger; -class DbalLoggerTest extends \PHPUnit_Framework_TestCase +class DbalLoggerTest extends TestCase { /** * @dataProvider getLogFixtures */ public function testLog($sql, $params, $logParams) { - $logger = $this->getMock('Psr\\Log\\LoggerInterface'); + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); $dbalLogger = $this ->getMockBuilder('Symfony\\Bridge\\Doctrine\\Logger\\DbalLogger') @@ -52,7 +53,7 @@ public function getLogFixtures() public function testLogNonUtf8() { - $logger = $this->getMock('Psr\\Log\\LoggerInterface'); + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); $dbalLogger = $this ->getMockBuilder('Symfony\\Bridge\\Doctrine\\Logger\\DbalLogger') @@ -75,7 +76,7 @@ public function testLogNonUtf8() public function testLogNonUtf8Array() { - $logger = $this->getMock('Psr\\Log\\LoggerInterface'); + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); $dbalLogger = $this ->getMockBuilder('Symfony\\Bridge\\Doctrine\\Logger\\DbalLogger') @@ -106,7 +107,7 @@ public function testLogNonUtf8Array() public function testLogLongString() { - $logger = $this->getMock('Psr\\Log\\LoggerInterface'); + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); $dbalLogger = $this ->getMockBuilder('Symfony\\Bridge\\Doctrine\\Logger\\DbalLogger') @@ -134,7 +135,7 @@ public function testLogLongString() public function testLogUTF8LongString() { - $logger = $this->getMock('Psr\\Log\\LoggerInterface'); + $logger = $this->getMockBuilder('Psr\\Log\\LoggerInterface')->getMock(); $dbalLogger = $this ->getMockBuilder('Symfony\\Bridge\\Doctrine\\Logger\\DbalLogger') @@ -144,7 +145,7 @@ public function testLogUTF8LongString() ; $testStringArray = array('é', 'á', 'ű', 'ő', 'ú', 'ö', 'ü', 'ó', 'í'); - $testStringCount = count($testStringArray); + $testStringCount = \count($testStringArray); $shortString = ''; $longString = ''; diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php index df6c355895086..24e30593469fc 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -9,28 +9,35 @@ * file that was distributed with this source code. */ -namespace Symfony\Bridge\Doctrine\PropertyInfo\Tests; +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo; +use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Setup; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor; use Symfony\Component\PropertyInfo\Type; /** * @author Kévin Dunglas */ -class DoctrineExtractorTest extends \PHPUnit_Framework_TestCase +class DoctrineExtractorTest extends TestCase { /** * @var DoctrineExtractor */ private $extractor; - public function setUp() + protected function setUp() { - $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'), true); + $config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'), true); $entityManager = EntityManager::create(array('driver' => 'pdo_sqlite'), $config); + if (!DBALType::hasType('foo')) { + DBALType::addType('foo', 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineFooType'); + $entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('custom_foo', 'foo'); + } + $this->extractor = new DoctrineExtractor($entityManager->getMetadataFactory()); } @@ -43,15 +50,36 @@ public function testGetProperties() 'time', 'json', 'simpleArray', + 'float', + 'decimal', 'bool', 'binary', + 'customFoo', + 'bigint', 'foo', 'bar', + 'indexedBar', + 'indexedFoo', ), $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy') ); } + public function testGetPropertiesWithEmbedded() + { + if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { + $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); + } + + $this->assertEquals( + array( + 'id', + 'embedded', + ), + $this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded') + ); + } + /** * @dataProvider typesProvider */ @@ -60,15 +88,39 @@ public function testExtract($property, array $type = null) $this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array())); } + public function testExtractWithEmbedded() + { + if (!class_exists('Doctrine\ORM\Mapping\Embedded')) { + $this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.'); + } + + $expectedTypes = array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable' + )); + + $actualTypes = $this->extractor->getTypes( + 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded', + 'embedded', + array() + ); + + $this->assertEquals($expectedTypes, $actualTypes); + } + public function typesProvider() { return array( array('id', array(new Type(Type::BUILTIN_TYPE_INT))), array('guid', array(new Type(Type::BUILTIN_TYPE_STRING))), + array('bigint', array(new Type(Type::BUILTIN_TYPE_STRING))), + array('float', array(new Type(Type::BUILTIN_TYPE_FLOAT))), + array('decimal', array(new Type(Type::BUILTIN_TYPE_STRING))), array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))), array('binary', array(new Type(Type::BUILTIN_TYPE_RESOURCE))), array('json', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))), - array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))), + array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))), array('bar', array(new Type( Type::BUILTIN_TYPE_OBJECT, false, @@ -77,8 +129,35 @@ public function typesProvider() new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') ))), + array('indexedBar', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_STRING), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), + array('indexedFoo', array(new Type( + Type::BUILTIN_TYPE_OBJECT, + false, + 'Doctrine\Common\Collections\Collection', + true, + new Type(Type::BUILTIN_TYPE_STRING), + new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation') + ))), array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))), + array('customFoo', null), array('notMapped', null), ); } + + public function testGetPropertiesCatchException() + { + $this->assertNull($this->extractor->getProperties('Not\Exist')); + } + + public function testGetTypesCatchException() + { + $this->assertNull($this->extractor->getTypes('Not\Exist', 'baz')); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php index 864bd78407c48..c4cea715ad17d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineDummy.php @@ -16,6 +16,7 @@ use Doctrine\ORM\Mapping\Id; use Doctrine\ORM\Mapping\ManyToMany; use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\OneToMany; /** * @Entity @@ -40,6 +41,16 @@ class DoctrineDummy */ public $bar; + /** + * @ManyToMany(targetEntity="DoctrineRelation", indexBy="rguid") + */ + protected $indexedBar; + + /** + * @OneToMany(targetEntity="DoctrineRelation", mappedBy="foo", indexBy="foo") + */ + protected $indexedFoo; + /** * @Column(type="guid") */ @@ -60,6 +71,16 @@ class DoctrineDummy */ private $simpleArray; + /** + * @Column(type="float") + */ + private $float; + + /** + * @Column(type="decimal", precision=10, scale=2) + */ + private $decimal; + /** * @Column(type="boolean") */ @@ -70,5 +91,15 @@ class DoctrineDummy */ private $binary; + /** + * @Column(type="custom_foo") + */ + private $customFoo; + + /** + * @Column(type="bigint") + */ + private $bigint; + public $notMapped; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php new file mode 100644 index 0000000000000..a00856ed7331e --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineEmbeddable.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embeddable; + +/** + * @Embeddable + * + * @author Udaltsov Valentin + */ +class DoctrineEmbeddable +{ + /** + * @Column(type="string") + */ + protected $field; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php new file mode 100644 index 0000000000000..8d0a9381143df --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineFooType.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Type; + +/** + * @author Teoh Han Hui + */ +class DoctrineFooType extends Type +{ + /** + * Type name. + */ + const NAME = 'foo'; + + /** + * {@inheritdoc} + */ + public function getName() + { + return self::NAME; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL(array()); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return; + } + if (!$value instanceof Foo) { + throw new ConversionException(sprintf('Expected %s, got %s', 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\Foo', \gettype($value))); + } + + return $foo->bar; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return; + } + if (!\is_string($value)) { + throw ConversionException::conversionFailed($value, self::NAME); + } + + $foo = new Foo(); + $foo->bar = $value; + + return $foo; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php index bfb27e9338d99..85660d3d6b66c 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineRelation.php @@ -12,7 +12,9 @@ namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; +use Doctrine\ORM\Mapping\ManyToOne; /** * @Entity @@ -26,4 +28,15 @@ class DoctrineRelation * @Column(type="smallint") */ public $id; + + /** + * @Column(type="guid") + */ + protected $rguid; + + /** + * @Column(type="guid") + * @ManyToOne(targetEntity="DoctrineDummy", inversedBy="indexedFoo") + */ + protected $foo; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php new file mode 100644 index 0000000000000..aace866128b0e --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/DoctrineWithEmbedded.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Embedded; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; + +/** + * @Entity + * + * @author Udaltsov Valentin + */ +class DoctrineWithEmbedded +{ + /** + * @Id + * @Column(type="smallint") + */ + public $id; + + /** + * @Embedded(class="DoctrineEmbeddable") + */ + protected $embedded; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/Foo.php b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/Foo.php new file mode 100644 index 0000000000000..3e4016cc56ab6 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/Fixtures/Foo.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures; + +/** + * @author Teoh Han Hui + */ +class Foo +{ + /** + * @var string + */ + public $bar; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 0b5292d4a671e..c9b6a92216d22 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -11,12 +11,13 @@ namespace Symfony\Bridge\Doctrine\Tests\Security\User; +use Doctrine\ORM\Tools\SchemaTool; +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\User; -use Symfony\Bridge\Doctrine\Security\User\EntityUserProvider; -use Doctrine\ORM\Tools\SchemaTool; -class EntityUserProviderTest extends \PHPUnit_Framework_TestCase +class EntityUserProviderTest extends TestCase { public function testRefreshUserGetsUserByPrimaryKey() { @@ -38,6 +39,95 @@ public function testRefreshUserGetsUserByPrimaryKey() $this->assertSame($user1, $provider->refreshUser($user1)); } + public function testLoadUserByUsername() + { + $em = DoctrineTestHelper::createTestEntityManager(); + $this->createSchema($em); + + $user = new User(1, 1, 'user1'); + + $em->persist($user); + $em->flush(); + + $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); + + $this->assertSame($user, $provider->loadUserByUsername('user1')); + } + + public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty() + { + $user = new User(1, 1, 'user1'); + + $repository = $this->getMockBuilder('Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface') + ->disableOriginalConstructor() + ->getMock(); + $repository + ->expects($this->once()) + ->method('loadUserByUsername') + ->with('user1') + ->willReturn($user); + + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $em + ->expects($this->once()) + ->method('getRepository') + ->with('Symfony\Bridge\Doctrine\Tests\Fixtures\User') + ->willReturn($repository); + + $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User'); + $this->assertSame($user, $provider->loadUserByUsername('user1')); + } + + /** + * @group legacy + * @expectedDeprecation Implementing Symfony\Component\Security\Core\User\UserProviderInterface in a Doctrine repository when using the entity provider is deprecated since Symfony 2.8 and will not be supported in 3.0. Make the repository implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface instead. + */ + public function testLoadUserByUsernameWithUserProviderRepositoryAndWithoutProperty() + { + $user = new User(1, 1, 'user1'); + + $repository = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserProviderInterface') + ->disableOriginalConstructor() + ->getMock(); + $repository + ->expects($this->once()) + ->method('loadUserByUsername') + ->with('user1') + ->willReturn($user); + + $em = $this->getMockBuilder('Doctrine\ORM\EntityManager') + ->disableOriginalConstructor() + ->getMock(); + $em + ->expects($this->once()) + ->method('getRepository') + ->with('Symfony\Bridge\Doctrine\Tests\Fixtures\User') + ->willReturn($repository); + + $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User'); + $this->assertSame($user, $provider->loadUserByUsername('user1')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage You must either make the "Symfony\Bridge\Doctrine\Tests\Fixtures\User" entity Doctrine Repository ("Doctrine\ORM\EntityRepository") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration. + */ + public function testLoadUserByUsernameWithNonUserLoaderRepositoryAndWithoutProperty() + { + $em = DoctrineTestHelper::createTestEntityManager(); + $this->createSchema($em); + + $user = new User(1, 1, 'user1'); + + $em->persist($user); + $em->flush(); + + $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User'); + $provider->loadUserByUsername('user1'); + } + public function testRefreshUserRequiresId() { $em = DoctrineTestHelper::createTestEntityManager(); @@ -45,7 +135,7 @@ public function testRefreshUserRequiresId() $user1 = new User(null, null, 'user1'); $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); - $this->setExpectedException( + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}( 'InvalidArgumentException', 'You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine' ); @@ -65,7 +155,7 @@ public function testRefreshInvalidUser() $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); $user2 = new User(1, 2, 'user2'); - $this->setExpectedException( + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}( 'Symfony\Component\Security\Core\Exception\UsernameNotFoundException', 'User with id {"id1":1,"id2":2} not found' ); @@ -86,17 +176,17 @@ public function testSupportProxy() $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); $user2 = $em->getReference('Symfony\Bridge\Doctrine\Tests\Fixtures\User', array('id1' => 1, 'id2' => 1)); - $this->assertTrue($provider->supportsClass(get_class($user2))); + $this->assertTrue($provider->supportsClass(\get_class($user2))); } public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided() { - $repository = $this->getMock('\Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface'); + $repository = $this->getMockBuilder('\Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface')->getMock(); $repository->expects($this->once()) ->method('loadUserByUsername') ->with('name') ->willReturn( - $this->getMock('\Symfony\Component\Security\Core\User\UserInterface') + $this->getMockBuilder('\Symfony\Component\Security\Core\User\UserInterface')->getMock() ); $provider = new EntityUserProvider( @@ -112,7 +202,7 @@ public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided( */ public function testLoadUserByUserNameShouldDeclineInvalidInterface() { - $repository = $this->getMock('\Symfony\Component\Security\Core\User\AdvancedUserInterface'); + $repository = $this->getMockBuilder('\Symfony\Component\Security\Core\User\AdvancedUserInterface')->getMock(); $provider = new EntityUserProvider( $this->getManager($this->getObjectManager($repository)), @@ -124,8 +214,8 @@ public function testLoadUserByUserNameShouldDeclineInvalidInterface() private function getManager($em, $name = null) { - $manager = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); - $manager->expects($this->once()) + $manager = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); + $manager->expects($this->any()) ->method('getManager') ->with($this->equalTo($name)) ->will($this->returnValue($em)); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php index cde865cc1d87a..91c57118d91a7 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/LegacyUniqueEntityValidatorLegacyApiTest.php @@ -14,8 +14,6 @@ use Symfony\Component\Validator\Validation; /** - * @since 2.5.4 - * * @author Bernhard Schussek * @group legacy */ diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index 6efeebc451818..84e5717e550ca 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -11,19 +11,20 @@ namespace Symfony\Bridge\Doctrine\Tests\Validator\Constraints; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectRepository; +use Doctrine\ORM\Tools\SchemaTool; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; -use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; -use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; -use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNullableNameEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator; use Symfony\Component\Validator\Tests\Constraints\AbstractConstraintValidatorTest; use Symfony\Component\Validator\Validation; -use Doctrine\ORM\Tools\SchemaTool; /** * @author Bernhard Schussek @@ -63,7 +64,7 @@ protected function setUp() protected function createRegistryMock(ObjectManager $em = null) { - $registry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); $registry->expects($this->any()) ->method('getManager') ->with($this->equalTo(self::EM_NAME)) @@ -88,11 +89,11 @@ protected function createEntityManagerMock($repositoryMock) ->getMock() ; $em->expects($this->any()) - ->method('getRepository') - ->will($this->returnValue($repositoryMock)) + ->method('getRepository') + ->will($this->returnValue($repositoryMock)) ; - $classMetadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $classMetadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock(); $classMetadata ->expects($this->any()) ->method('hasField') @@ -114,8 +115,8 @@ protected function createEntityManagerMock($repositoryMock) ; $classMetadata->reflFields = array('name' => $refl); $em->expects($this->any()) - ->method('getClassMetadata') - ->will($this->returnValue($classMetadata)) + ->method('getClassMetadata') + ->will($this->returnValue($classMetadata)) ; return $em; @@ -132,6 +133,7 @@ private function createSchema(ObjectManager $em) $schemaTool->createSchema(array( $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity'), $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity'), + $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNullableNameEntity'), $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity'), $em->getClassMetadata('Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity'), )); @@ -167,6 +169,7 @@ public function testValidateUniqueness() $this->buildViolation('myMessage') ->atPath('property.path.name') ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -190,6 +193,7 @@ public function testValidateCustomErrorPath() $this->buildViolation('myMessage') ->atPath('property.path.bar') ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -213,7 +217,7 @@ public function testValidateUniquenessWithNull() $this->assertNoViolation(); } - public function testValidateUniquenessWithIgnoreNull() + public function testValidateUniquenessWithIgnoreNullDisabled() { $constraint = new UniqueEntity(array( 'message' => 'myMessage', @@ -241,9 +245,55 @@ public function testValidateUniquenessWithIgnoreNull() $this->buildViolation('myMessage') ->atPath('property.path.name') ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } + /** + * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException + */ + public function testAllConfiguredFieldsAreCheckedOfBeingMappedByDoctrineWithIgnoreNullEnabled() + { + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('name', 'name2'), + 'em' => self::EM_NAME, + 'ignoreNull' => true, + )); + + $entity1 = new SingleIntIdEntity(1, null); + + $this->validator->validate($entity1, $constraint); + } + + public function testNoValidationIfFirstFieldIsNullAndNullValuesAreIgnored() + { + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('name', 'name2'), + 'em' => self::EM_NAME, + 'ignoreNull' => true, + )); + + $entity1 = new DoubleNullableNameEntity(1, null, 'Foo'); + $entity2 = new DoubleNullableNameEntity(2, null, 'Foo'); + + $this->validator->validate($entity1, $constraint); + + $this->assertNoViolation(); + + $this->em->persist($entity1); + $this->em->flush(); + + $this->validator->validate($entity1, $constraint); + + $this->assertNoViolation(); + + $this->validator->validate($entity2, $constraint); + + $this->assertNoViolation(); + } + public function testValidateUniquenessWithValidCustomErrorPath() { $constraint = new UniqueEntity(array( @@ -272,6 +322,7 @@ public function testValidateUniquenessWithValidCustomErrorPath() $this->buildViolation('myMessage') ->atPath('property.path.name2') ->setInvalidValue('Bar') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -286,8 +337,8 @@ public function testValidateUniquenessUsingCustomRepositoryMethod() $repository = $this->createRepositoryMock(); $repository->expects($this->once()) - ->method('findByCustom') - ->will($this->returnValue(array())) + ->method('findByCustom') + ->will($this->returnValue(array())) ; $this->em = $this->createEntityManagerMock($repository); $this->registry = $this->createRegistryMock($this->em); @@ -336,6 +387,44 @@ public function testValidateUniquenessWithUnrewoundArray() $this->assertNoViolation(); } + /** + * @dataProvider resultTypesProvider + */ + public function testValidateResultTypes($entity1, $result) + { + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('name'), + 'em' => self::EM_NAME, + 'repositoryMethod' => 'findByCustom', + )); + + $repository = $this->createRepositoryMock(); + $repository->expects($this->once()) + ->method('findByCustom') + ->will($this->returnValue($result)) + ; + $this->em = $this->createEntityManagerMock($repository); + $this->registry = $this->createRegistryMock($this->em); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $this->validator->validate($entity1, $constraint); + + $this->assertNoViolation(); + } + + public function resultTypesProvider() + { + $entity = new SingleIntIdEntity(1, 'foo'); + + return array( + array($entity, array($entity)), + array($entity, new \ArrayIterator(array($entity))), + array($entity, new ArrayCollection(array($entity))), + ); + } + public function testAssociatedEntity() { $constraint = new UniqueEntity(array( @@ -365,7 +454,8 @@ public function testAssociatedEntity() $this->buildViolation('myMessage') ->atPath('property.path.single') - ->setInvalidValue(1) + ->setInvalidValue($entity1) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -389,29 +479,6 @@ public function testAssociatedEntityWithNull() $this->assertNoViolation(); } - /** - * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException - * @expectedExceptionMessage Associated entities are not allowed to have more than one identifier field - */ - public function testAssociatedCompositeEntity() - { - $constraint = new UniqueEntity(array( - 'message' => 'myMessage', - 'fields' => array('composite'), - 'em' => self::EM_NAME, - )); - - $composite = new CompositeIntIdEntity(1, 1, 'test'); - $associated = new AssociationEntity(); - $associated->composite = $composite; - - $this->em->persist($composite); - $this->em->persist($associated); - $this->em->flush(); - - $this->validator->validate($associated, $constraint); - } - /** * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException * @expectedExceptionMessage Object manager "foo" does not exist. @@ -455,4 +522,32 @@ public function testEntityManagerNullObject() $this->validator->validate($entity, $constraint); } + + public function testValidateUniquenessOnNullResult() + { + $repository = $this->createRepositoryMock(); + $repository + ->method('find') + ->will($this->returnValue(null)) + ; + + $this->em = $this->createEntityManagerMock($repository); + $this->registry = $this->createRegistryMock($this->em); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $constraint = new UniqueEntity(array( + 'message' => 'myMessage', + 'fields' => array('name'), + 'em' => self::EM_NAME, + )); + + $entity = new SingleIntIdEntity(1, null); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($entity, $constraint); + $this->assertNoViolation(); + } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index fc6e213bd772e..315b1654e8f7e 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -23,6 +23,8 @@ */ class UniqueEntity extends Constraint { + const NOT_UNIQUE_ERROR = '23bd9dbf-6b9b-41cd-a99e-4844bcf3077f'; + public $message = 'This value is already used.'; public $service = 'doctrine.orm.validator.unique'; public $em = null; @@ -31,6 +33,10 @@ class UniqueEntity extends Constraint public $errorPath = null; public $ignoreNull = true; + protected static $errorNames = array( + self::NOT_UNIQUE_ERROR => 'NOT_UNIQUE_ERROR', + ); + public function getRequiredOptions() { return array('fields'); diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 0fbf42c5bec65..9a1f80389aea8 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -12,11 +12,11 @@ namespace Symfony\Bridge\Doctrine\Validator\Constraints; use Doctrine\Common\Persistence\ManagerRegistry; -use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\Exception\UnexpectedTypeException; -use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Validator\Exception\UnexpectedTypeException; /** * Unique Entity Validator checks if one or a set of fields contain unique values. @@ -25,9 +25,6 @@ */ class UniqueEntityValidator extends ConstraintValidator { - /** - * @var ManagerRegistry - */ private $registry; public function __construct(ManagerRegistry $registry) @@ -48,20 +45,24 @@ public function validate($entity, Constraint $constraint) throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UniqueEntity'); } - if (!is_array($constraint->fields) && !is_string($constraint->fields)) { + if (!\is_array($constraint->fields) && !\is_string($constraint->fields)) { throw new UnexpectedTypeException($constraint->fields, 'array'); } - if (null !== $constraint->errorPath && !is_string($constraint->errorPath)) { + if (null !== $constraint->errorPath && !\is_string($constraint->errorPath)) { throw new UnexpectedTypeException($constraint->errorPath, 'string or null'); } $fields = (array) $constraint->fields; - if (0 === count($fields)) { + if (0 === \count($fields)) { throw new ConstraintDefinitionException('At least one field has to be specified.'); } + if (null === $entity) { + return; + } + if ($constraint->em) { $em = $this->registry->getManager($constraint->em); @@ -69,66 +70,86 @@ public function validate($entity, Constraint $constraint) throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint->em)); } } else { - $em = $this->registry->getManagerForClass(get_class($entity)); + $em = $this->registry->getManagerForClass(\get_class($entity)); if (!$em) { - throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_class($entity))); + throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', \get_class($entity))); } } - $class = $em->getClassMetadata(get_class($entity)); + $class = $em->getClassMetadata(\get_class($entity)); /* @var $class \Doctrine\Common\Persistence\Mapping\ClassMetadata */ $criteria = array(); + $hasNullValue = false; + foreach ($fields as $fieldName) { if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) { throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $fieldName)); } - $criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity); + $fieldValue = $class->reflFields[$fieldName]->getValue($entity); - if ($constraint->ignoreNull && null === $criteria[$fieldName]) { - return; + if (null === $fieldValue) { + $hasNullValue = true; } + if ($constraint->ignoreNull && null === $fieldValue) { + continue; + } + + $criteria[$fieldName] = $fieldValue; + if (null !== $criteria[$fieldName] && $class->hasAssociation($fieldName)) { /* Ensure the Proxy is initialized before using reflection to * read its identifiers. This is necessary because the wrapped * getter methods in the Proxy are being bypassed. */ $em->initializeObject($criteria[$fieldName]); + } + } - $relatedClass = $em->getClassMetadata($class->getAssociationTargetClass($fieldName)); - $relatedId = $relatedClass->getIdentifierValues($criteria[$fieldName]); + // validation doesn't fail if one of the fields is null and if null values should be ignored + if ($hasNullValue && $constraint->ignoreNull) { + return; + } - if (count($relatedId) > 1) { - throw new ConstraintDefinitionException( - 'Associated entities are not allowed to have more than one identifier field to be '. - 'part of a unique constraint in: '.$class->getName().'#'.$fieldName - ); - } - $criteria[$fieldName] = array_pop($relatedId); - } + // skip validation if there are no criteria (this can happen when the + // "ignoreNull" option is enabled and fields to be checked are null + if (empty($criteria)) { + return; } - $repository = $em->getRepository(get_class($entity)); + $repository = $em->getRepository(\get_class($entity)); $result = $repository->{$constraint->repositoryMethod}($criteria); + if ($result instanceof \IteratorAggregate) { + $result = $result->getIterator(); + } + /* If the result is a MongoCursor, it must be advanced to the first * element. Rewinding should have no ill effect if $result is another * iterator implementation. */ if ($result instanceof \Iterator) { $result->rewind(); - } elseif (is_array($result)) { + if ($result instanceof \Countable && 1 < \count($result)) { + $result = array($result->current(), $result->current()); + } else { + $result = $result->current(); + $result = null === $result ? array() : array($result); + } + } elseif (\is_array($result)) { reset($result); + } else { + $result = null === $result ? array() : array($result); } /* If no entity matched the query criteria or a single entity matched, * which is the same as the entity being validated, the criteria is * unique. */ - if (0 === count($result) || (1 === count($result) && $entity === ($result instanceof \Iterator ? $result->current() : current($result)))) { + if (!$result || (1 === \count($result) && current($result) === $entity)) { return; } @@ -139,11 +160,13 @@ public function validate($entity, Constraint $constraint) $this->context->buildViolation($constraint->message) ->atPath($errorPath) ->setInvalidValue($invalidValue) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->addViolation(); } else { $this->buildViolation($constraint->message) ->atPath($errorPath) ->setInvalidValue($invalidValue) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->addViolation(); } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php index 42cafdd129472..010c051581e70 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineInitializer.php @@ -30,7 +30,7 @@ public function __construct(ManagerRegistry $registry) public function initialize($object) { - $manager = $this->registry->getManagerForClass(get_class($object)); + $manager = $this->registry->getManagerForClass(\get_class($object)); if (null !== $manager) { $manager->initializeObject($object); } diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index aef8b81a5803f..e5b5a39a38269 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -17,22 +17,27 @@ ], "require": { "php": ">=5.3.9", - "doctrine/common": "~2.4" + "doctrine/common": "~2.4", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "symfony/stopwatch": "~2.2|~3.0.0", "symfony/dependency-injection": "~2.2|~3.0.0", - "symfony/form": "~2.8|~3.0.0", + "symfony/form": "^2.8.28|~3.3.10", "symfony/http-kernel": "~2.2|~3.0.0", "symfony/property-access": "~2.3|~3.0.0", "symfony/property-info": "~2.8|3.0", - "symfony/security": "~2.2|~3.0.0", + "symfony/security": "^2.8.31|^3.3.13", "symfony/expression-language": "~2.2|~3.0.0", - "symfony/validator": "~2.5,>=2.5.5|~3.0.0", - "symfony/translation": "~2.0,>=2.0.5|~3.0.0", + "symfony/validator": "~2.7.25|^2.8.18|~3.2.5", + "symfony/translation": "^2.0.5|~3.0.0", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", - "doctrine/orm": "~2.4,>=2.4.5" + "doctrine/orm": "^2.4.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" }, "suggest": { "symfony/form": "", @@ -43,7 +48,10 @@ "doctrine/orm": "" }, "autoload": { - "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" } + "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist index 5b576e52c95e9..fa76fa9b500e7 100644 --- a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist +++ b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist @@ -1,10 +1,12 @@ @@ -22,6 +24,7 @@ ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php index 267b012966901..1203a0d999c99 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ChromePhpHandler.php @@ -22,9 +22,6 @@ */ class ChromePhpHandler extends BaseChromePhpHandler { - /** - * @var array - */ private $headers = array(); /** @@ -41,7 +38,7 @@ public function onKernelResponse(FilterResponseEvent $event) return; } - if (!preg_match('{\bChrome/\d+[\.\d+]*\b}', $event->getRequest()->headers->get('User-Agent'))) { + if (!preg_match('{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}', $event->getRequest()->headers->get('User-Agent'))) { $this->sendHeaders = false; $this->headers = array(); diff --git a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php index 592584ffa4af0..263b2b7a6879b 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ConsoleHandler.php @@ -40,14 +40,7 @@ */ class ConsoleHandler extends AbstractProcessingHandler implements EventSubscriberInterface { - /** - * @var OutputInterface|null - */ private $output; - - /** - * @var array - */ private $verbosityLevelMap = array( OutputInterface::VERBOSITY_NORMAL => Logger::WARNING, OutputInterface::VERBOSITY_VERBOSE => Logger::NOTICE, @@ -56,8 +49,6 @@ class ConsoleHandler extends AbstractProcessingHandler implements EventSubscribe ); /** - * Constructor. - * * @param OutputInterface|null $output The console output to use (the handler remains disabled when passing null * until the output is set, e.g. by using console events) * @param bool $bubble Whether the messages that are handled can bubble up the stack @@ -94,8 +85,6 @@ public function handle(array $record) /** * Sets the console output to use for printing logs. - * - * @param OutputInterface $output The console output to use */ public function setOutput(OutputInterface $output) { @@ -115,8 +104,6 @@ public function close() /** * Before a command is executed, the handler gets activated and the console output * is set in order to know where to write the logs. - * - * @param ConsoleCommandEvent $event */ public function onCommand(ConsoleCommandEvent $event) { @@ -130,8 +117,6 @@ public function onCommand(ConsoleCommandEvent $event) /** * After a command has been executed, it disables the output. - * - * @param ConsoleTerminateEvent $event */ public function onTerminate(ConsoleTerminateEvent $event) { @@ -168,7 +153,7 @@ protected function getDefaultFormatter() /** * Updates the logging level based on the verbosity setting of the console output. * - * @return bool Whether the handler is enabled and verbosity is not set to quiet. + * @return bool Whether the handler is enabled and verbosity is not set to quiet */ private function updateLevel() { diff --git a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php index f1046c96a6ad1..8c0060303116a 100644 --- a/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/DebugHandler.php @@ -11,8 +11,8 @@ namespace Symfony\Bridge\Monolog\Handler; -use Monolog\Logger; use Monolog\Handler\TestHandler; +use Monolog\Logger; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; /** @@ -51,7 +51,7 @@ public function countErrors() $levels = array(Logger::ERROR, Logger::CRITICAL, Logger::ALERT, Logger::EMERGENCY); foreach ($levels as $level) { if (isset($this->recordsByLevel[$level])) { - $cnt += count($this->recordsByLevel[$level]); + $cnt += \count($this->recordsByLevel[$level]); } } diff --git a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php index 413b476f2938d..ed41929a2cef3 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php +++ b/src/Symfony/Bridge/Monolog/Handler/FingersCrossed/NotFoundActivationStrategy.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Monolog\Handler\FingersCrossed; use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; -use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Exception\HttpException; /** * Activation strategy that ignores 404s for certain URLs. @@ -42,7 +42,7 @@ public function isHandlerActivated(array $record) $isActivated && isset($record['context']['exception']) && $record['context']['exception'] instanceof HttpException - && $record['context']['exception']->getStatusCode() == 404 + && 404 == $record['context']['exception']->getStatusCode() && ($request = $this->requestStack->getMasterRequest()) ) { return !preg_match($this->blacklist, $request->getPathInfo()); diff --git a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php index 339843c1d4ff5..9956edad386d1 100644 --- a/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/FirePHPHandler.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Monolog\Handler; use Monolog\Handler\FirePHPHandler as BaseFirePHPHandler; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; /** * FirePHPHandler. @@ -22,9 +22,6 @@ */ class FirePHPHandler extends BaseFirePHPHandler { - /** - * @var array - */ private $headers = array(); /** @@ -41,9 +38,10 @@ public function onKernelResponse(FilterResponseEvent $event) return; } - if (!preg_match('{\bFirePHP/\d+\.\d+\b}', $event->getRequest()->headers->get('User-Agent')) - && !$event->getRequest()->headers->has('X-FirePHP-Version')) { - $this->sendHeaders = false; + $request = $event->getRequest(); + if (!preg_match('{\bFirePHP/\d+\.\d+\b}', $request->headers->get('User-Agent')) + && !$request->headers->has('X-FirePHP-Version')) { + self::$sendHeaders = false; $this->headers = array(); return; @@ -61,7 +59,7 @@ public function onKernelResponse(FilterResponseEvent $event) */ protected function sendHeader($header, $content) { - if (!$this->sendHeaders) { + if (!self::$sendHeaders) { return; } diff --git a/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php b/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php index 0412e94f223a3..fcbd98ac7dc64 100644 --- a/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/SwiftMailerHandler.php @@ -26,9 +26,6 @@ class SwiftMailerHandler extends BaseSwiftMailerHandler protected $instantFlush = false; - /** - * @param \Swift_Transport $transport - */ public function setTransport(\Swift_Transport $transport) { $this->transport = $transport; @@ -36,8 +33,6 @@ public function setTransport(\Swift_Transport $transport) /** * After the kernel has been terminated we will always flush messages. - * - * @param PostResponseEvent $event */ public function onKernelTerminate(PostResponseEvent $event) { @@ -46,8 +41,6 @@ public function onKernelTerminate(PostResponseEvent $event) /** * After the CLI application has been terminated we will always flush messages. - * - * @param ConsoleTerminateEvent $event */ public function onCliTerminate(ConsoleTerminateEvent $event) { diff --git a/src/Symfony/Bridge/Monolog/LICENSE b/src/Symfony/Bridge/Monolog/LICENSE index 43028bc600f26..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Monolog/LICENSE +++ b/src/Symfony/Bridge/Monolog/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Monolog/Logger.php b/src/Symfony/Bridge/Monolog/Logger.php index fbc2f5ff33296..8a9fced16cdf9 100644 --- a/src/Symfony/Bridge/Monolog/Logger.php +++ b/src/Symfony/Bridge/Monolog/Logger.php @@ -12,8 +12,8 @@ namespace Symfony\Bridge\Monolog; use Monolog\Logger as BaseLogger; -use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Log\LoggerInterface; /** * Logger. @@ -27,7 +27,7 @@ class Logger extends BaseLogger implements LoggerInterface, DebugLoggerInterface */ public function emerg($message, array $context = array()) { - @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since version 2.2 and will be removed in 3.0. Use the emergency() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since Symfony 2.2 and will be removed in 3.0. Use the emergency() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); return parent::addRecord(BaseLogger::EMERGENCY, $message, $context); } @@ -37,7 +37,7 @@ public function emerg($message, array $context = array()) */ public function crit($message, array $context = array()) { - @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since version 2.2 and will be removed in 3.0. Use the method critical() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since Symfony 2.2 and will be removed in 3.0. Use the method critical() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); return parent::addRecord(BaseLogger::CRITICAL, $message, $context); } @@ -47,7 +47,7 @@ public function crit($message, array $context = array()) */ public function err($message, array $context = array()) { - @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since version 2.2 and will be removed in 3.0. Use the error() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since Symfony 2.2 and will be removed in 3.0. Use the error() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); return parent::addRecord(BaseLogger::ERROR, $message, $context); } @@ -57,7 +57,7 @@ public function err($message, array $context = array()) */ public function warn($message, array $context = array()) { - @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since version 2.2 and will be removed in 3.0. Use the warning() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method inherited from the Symfony\Component\HttpKernel\Log\LoggerInterface interface is deprecated since Symfony 2.2 and will be removed in 3.0. Use the warning() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); return parent::addRecord(BaseLogger::WARNING, $message, $context); } diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index 2752d03a58564..5222258e46936 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -31,6 +31,7 @@ public function onKernelRequest(GetResponseEvent $event) { if ($event->isMasterRequest()) { $this->serverData = $event->getRequest()->server->all(); + $this->serverData['REMOTE_ADDR'] = $event->getRequest()->getClientIp(); } } } diff --git a/src/Symfony/Bridge/Monolog/README.md b/src/Symfony/Bridge/Monolog/README.md index b0d91ca4f4486..2d19b3e27cfd4 100644 --- a/src/Symfony/Bridge/Monolog/README.md +++ b/src/Symfony/Bridge/Monolog/README.md @@ -6,8 +6,7 @@ Provides integration for Monolog with various Symfony components. Resources --------- -You can run the unit tests with the following command: - - $ cd path/to/Symfony/Bridge/Monolog/ - $ composer install - $ phpunit + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php index 6cb315967e4fc..eac2537020fdf 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php @@ -12,21 +12,22 @@ namespace Symfony\Bridge\Monolog\Tests\Handler; use Monolog\Logger; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Monolog\Handler\ConsoleHandler; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\Console\Command\Command; /** * Tests the ConsoleHandler and also the ConsoleFormatter. * * @author Tobias Schultze */ -class ConsoleHandlerTest extends \PHPUnit_Framework_TestCase +class ConsoleHandlerTest extends TestCase { public function testConstructor() { @@ -45,7 +46,7 @@ public function testIsHandling() */ public function testVerbosityMapping($verbosity, $level, $isHandling, array $map = array()) { - $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $output = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $output ->expects($this->atLeastOnce()) ->method('getVerbosity') @@ -80,7 +81,7 @@ public function provideVerbosityMappingTests() public function testVerbosityChanged() { - $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $output = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $output ->expects($this->at(0)) ->method('getVerbosity') @@ -110,7 +111,7 @@ public function testGetFormatter() public function testWritingAndFormatting() { - $output = $this->getMock('Symfony\Component\Console\Output\OutputInterface'); + $output = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $output ->expects($this->any()) ->method('getVerbosity') @@ -165,12 +166,12 @@ public function testLogsFromListeners() $logger->addInfo('After terminate message.'); }); - $event = new ConsoleCommandEvent(new Command('foo'), $this->getMock('Symfony\Component\Console\Input\InputInterface'), $output); + $event = new ConsoleCommandEvent(new Command('foo'), $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(), $output); $dispatcher->dispatch(ConsoleEvents::COMMAND, $event); $this->assertContains('Before command message.', $out = $output->fetch()); $this->assertContains('After command message.', $out); - $event = new ConsoleTerminateEvent(new Command('foo'), $this->getMock('Symfony\Component\Console\Input\InputInterface'), $output, 0); + $event = new ConsoleTerminateEvent(new Command('foo'), $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(), $output, 0); $dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); $this->assertContains('Before terminate message.', $out = $output->fetch()); $this->assertContains('After terminate message.', $out); diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php index 48bddc99a5eef..3c34f065eb038 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/FingersCrossed/NotFoundActivationStrategyTest.php @@ -11,13 +11,14 @@ namespace Symfony\Bridge\Monolog\Tests\Handler\FingersCrossed; +use Monolog\Logger; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Monolog\Handler\FingersCrossed\NotFoundActivationStrategy; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Exception\HttpException; -use Monolog\Logger; -class NotFoundActivationStrategyTest extends \PHPUnit_Framework_TestCase +class NotFoundActivationStrategyTest extends TestCase { /** * @dataProvider isActivatedProvider diff --git a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php new file mode 100644 index 0000000000000..fbf5579e340c4 --- /dev/null +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -0,0 +1,107 @@ +pushHandler($handler); + + $this->assertTrue($logger->emerg('test')); + $this->assertTrue($handler->hasEmergency('test')); + } + + /** + * @group legacy + */ + public function testCrit() + { + $handler = new TestHandler(); + $logger = new Logger('test'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->crit('test')); + $this->assertTrue($handler->hasCritical('test')); + } + + /** + * @group legacy + */ + public function testErr() + { + $handler = new TestHandler(); + $logger = new Logger('test'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->err('test')); + $this->assertTrue($handler->hasError('test')); + } + + /** + * @group legacy + */ + public function testWarn() + { + $handler = new TestHandler(); + $logger = new Logger('test'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->warn('test')); + $this->assertTrue($handler->hasWarning('test')); + } + + public function testGetLogs() + { + $logger = new Logger('test'); + $logger->pushHandler(new DebugHandler()); + + $logger->addInfo('test'); + $this->assertCount(1, $logger->getLogs()); + list($record) = $logger->getLogs(); + + $this->assertEquals('test', $record['message']); + $this->assertEquals(Logger::INFO, $record['priority']); + } + + public function testGetLogsWithoutDebugHandler() + { + $logger = new Logger('test'); + $logger->pushHandler(new TestHandler()); + $logger->addInfo('test'); + + $this->assertSame(array(), $logger->getLogs()); + } + + public function testCountErrors() + { + $logger = new Logger('test'); + $logger->pushHandler(new DebugHandler()); + + $logger->addInfo('test'); + $logger->addError('uh-oh'); + + $this->assertEquals(1, $logger->countErrors()); + } + + public function testCountErrorsWithoutDebugHandler() + { + $logger = new Logger('test'); + $logger->pushHandler(new TestHandler()); + + $logger->addInfo('test'); + $logger->addError('uh-oh'); + + $this->assertEquals(0, $logger->countErrors()); + } +} diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php index 1232bfacf104a..6ce418d317319 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php @@ -12,10 +12,11 @@ namespace Symfony\Bridge\Monolog\Tests\Processor; use Monolog\Logger; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Monolog\Processor\WebProcessor; use Symfony\Component\HttpFoundation\Request; -class WebProcessorTest extends \PHPUnit_Framework_TestCase +class WebProcessorTest extends TestCase { public function testUsesRequestServerData() { @@ -33,6 +34,25 @@ public function testUsesRequestServerData() $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); } + public function testUseRequestClientIp() + { + Request::setTrustedProxies(array('192.168.0.1')); + list($event, $server) = $this->createRequestEvent(array('X_FORWARDED_FOR' => '192.168.0.2')); + + $processor = new WebProcessor(); + $processor->onKernelRequest($event); + $record = $processor($this->getRecord()); + + $this->assertCount(5, $record['extra']); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['X_FORWARDED_FOR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + + Request::setTrustedProxies(array()); + } + public function testCanBeConstructedWithExtraFields() { if (!$this->isExtraFieldsSupported()) { @@ -53,18 +73,22 @@ public function testCanBeConstructedWithExtraFields() /** * @return array */ - private function createRequestEvent() + private function createRequestEvent($additionalServerParameters = array()) { - $server = array( - 'REQUEST_URI' => 'A', - 'REMOTE_ADDR' => 'B', - 'REQUEST_METHOD' => 'C', - 'SERVER_NAME' => 'D', - 'HTTP_REFERER' => 'E', + $server = array_merge( + array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => '192.168.0.1', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'D', + 'HTTP_REFERER' => 'E', + ), + $additionalServerParameters ); $request = new Request(); $request->server->replace($server); + $request->headers->replace($server); $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') ->disableOriginalConstructor() diff --git a/src/Symfony/Bridge/Monolog/composer.json b/src/Symfony/Bridge/Monolog/composer.json index 6260385a63a4f..e65fb26e7ef74 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -17,20 +17,26 @@ ], "require": { "php": ">=5.3.9", - "monolog/monolog": "~1.11" + "monolog/monolog": "~1.11", + "symfony/http-kernel": "~2.4" }, "require-dev": { - "symfony/http-kernel": "~2.4|~3.0.0", "symfony/console": "~2.4|~3.0.0", "symfony/event-dispatcher": "~2.2|~3.0.0" }, + "conflict": { + "symfony/http-kernel": ">=3.0" + }, "suggest": { "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings. You need version ~2.3 of the console for it.", "symfony/event-dispatcher": "Needed when using log messages in console commands." }, "autoload": { - "psr-4": { "Symfony\\Bridge\\Monolog\\": "" } + "psr-4": { "Symfony\\Bridge\\Monolog\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Bridge/Monolog/phpunit.xml.dist b/src/Symfony/Bridge/Monolog/phpunit.xml.dist index 34063ac548676..1bda3eca9cd05 100644 --- a/src/Symfony/Bridge/Monolog/phpunit.xml.dist +++ b/src/Symfony/Bridge/Monolog/phpunit.xml.dist @@ -1,10 +1,12 @@ @@ -22,6 +24,7 @@ ./Resources ./Tests + ./vendor diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index 4b745e38e87fc..962649bcd6281 100644 --- a/src/Symfony/Bridge/PhpUnit/ClockMock.php +++ b/src/Symfony/Bridge/PhpUnit/ClockMock.php @@ -66,12 +66,12 @@ public static function microtime($asFloat = false) return self::$now; } - return sprintf("%0.6f %d\n", $now - (int) $now, (int) self::$now); + return sprintf('%0.6f00 %d', self::$now - (int) self::$now, (int) self::$now); } public static function register($class) { - $self = get_called_class(); + $self = \get_called_class(); $mockedNs = array(substr($class, 0, strrpos($class, '\\'))); if (strpos($class, '\\Tests\\')) { @@ -79,7 +79,7 @@ public static function register($class) $mockedNs[] = substr($ns, 0, strrpos($ns, '\\')); } foreach ($mockedNs as $ns) { - if (function_exists($ns.'\time')) { + if (\function_exists($ns.'\time')) { continue; } eval(<< 0, 'remainingCount' => 0, @@ -35,36 +66,58 @@ public static function register($mode = false) 'legacy' => array(), 'other' => array(), ); - $deprecationHandler = function ($type, $msg, $file, $line, $context) use (&$deprecations, $mode) { + $deprecationHandler = function ($type, $msg, $file, $line, $context = array()) use (&$deprecations, $getMode) { if (E_USER_DEPRECATED !== $type) { return \PHPUnit_Util_ErrorHandler::handleError($type, $msg, $file, $line, $context); } - $trace = debug_backtrace(PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT : true); + $mode = $getMode(); + $trace = debug_backtrace(); + $group = 'other'; - $i = count($trace); - while (isset($trace[--$i]['class']) && ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_'))) { + $i = \count($trace); + while (1 < $i && (!isset($trace[--$i]['class']) || ('ReflectionMethod' === $trace[$i]['class'] || 0 === strpos($trace[$i]['class'], 'PHPUnit_')))) { // No-op } - if (0 !== error_reporting()) { - $group = 'unsilenced'; - $ref = &$deprecations[$group][$msg]['count']; - ++$ref; - } elseif (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { - $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class']; + if (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { + $class = isset($trace[$i]['object']) ? \get_class($trace[$i]['object']) : $trace[$i]['class']; $method = $trace[$i]['function']; - $group = 0 === strpos($method, 'testLegacy') || 0 === strpos($method, 'provideLegacy') || 0 === strpos($method, 'getLegacy') || strpos($class, '\Legacy') || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) ? 'legacy' : 'remaining'; + if (0 !== error_reporting()) { + $group = 'unsilenced'; + } elseif (0 === strpos($method, 'testLegacy') + || 0 === strpos($method, 'provideLegacy') + || 0 === strpos($method, 'getLegacy') + || strpos($class, '\Legacy') + || \in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) + ) { + $group = 'legacy'; + } else { + $group = 'remaining'; + } + + if (isset($mode[0]) && '/' === $mode[0] && preg_match($mode, $msg)) { + $e = new \Exception($msg); + $r = new \ReflectionProperty($e, 'trace'); + $r->setAccessible(true); + $r->setValue($e, \array_slice($trace, 1, $i)); - if ('legacy' !== $group && 'weak' !== $mode) { + echo "\n".ucfirst($group).' deprecation triggered by '.$class.'::'.$method.':'; + echo "\n".$msg; + echo "\nStack trace:"; + echo "\n".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); + echo "\n"; + + exit(1); + } + if ('legacy' !== $group && DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; $ref = &$deprecations[$group][$msg][$class.'::'.$method]; ++$ref; } - } else { - $group = 'other'; + } elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; } @@ -87,12 +140,19 @@ public static function register($mode = false) return "\x1B[{$color}m{$str}\x1B[0m"; }; } else { - $colorize = function ($str) {return $str;}; + $colorize = function ($str) { return $str; }; } - register_shutdown_function(function () use ($mode, &$deprecations, $deprecationHandler, $colorize) { + register_shutdown_function(function () use ($getMode, &$deprecations, $deprecationHandler, $colorize) { + $mode = $getMode(); + if (isset($mode[0]) && '/' === $mode[0]) { + return; + } $currErrorHandler = set_error_handler('var_dump'); restore_error_handler(); + if (DeprecationErrorHandler::MODE_WEAK === $mode) { + $colorize = function ($str) { return $str; }; + } if ($currErrorHandler !== $deprecationHandler) { echo "\n", $colorize('THE ERROR HANDLER HAS CHANGED!', true), "\n"; } @@ -101,41 +161,93 @@ public static function register($mode = false) return $b['count'] - $a['count']; }; - foreach (array('unsilenced', 'remaining', 'legacy', 'other') as $group) { - if ($deprecations[$group.'Count']) { - echo "\n", $colorize(sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), 'legacy' !== $group), "\n"; + $displayDeprecations = function ($deprecations) use ($colorize, $cmp) { + foreach (array('unsilenced', 'remaining', 'legacy', 'other') as $group) { + if ($deprecations[$group.'Count']) { + echo "\n", $colorize(sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), 'legacy' !== $group), "\n"; - uasort($deprecations[$group], $cmp); + uasort($deprecations[$group], $cmp); - foreach ($deprecations[$group] as $msg => $notices) { - echo "\n", rtrim($msg, '.'), ': ', $notices['count'], "x\n"; + foreach ($deprecations[$group] as $msg => $notices) { + echo "\n ", $notices['count'], 'x: ', $msg, "\n"; - arsort($notices); + arsort($notices); - foreach ($notices as $method => $count) { - if ('count' !== $method) { - echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + foreach ($notices as $method => $count) { + if ('count' !== $method) { + echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + } } } } } + if (!empty($notices)) { + echo "\n"; + } + }; + + $displayDeprecations($deprecations); + + // store failing status + $isFailing = DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']; + + // reset deprecations array + foreach ($deprecations as $group => $arrayOrInt) { + $deprecations[$group] = \is_int($arrayOrInt) ? 0 : array(); } - if (!empty($notices)) { - echo "\n"; - } - if ('weak' !== $mode && ($deprecations['unsilenced'] || $deprecations['remaining'] || $deprecations['other'])) { - exit(1); - } + + register_shutdown_function(function () use (&$deprecations, $isFailing, $displayDeprecations, $mode) { + foreach ($deprecations as $group => $arrayOrInt) { + if (0 < (\is_int($arrayOrInt) ? $arrayOrInt : \count($arrayOrInt))) { + echo "Shutdown-time deprecations:\n"; + break; + } + } + $displayDeprecations($deprecations); + if ($isFailing || DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { + exit(1); + } + }); }); } } + /** + * Returns true if STDOUT is defined and supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool + */ private static function hasColorSupport() { - if ('\\' === DIRECTORY_SEPARATOR) { - return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); + if (!\defined('STDOUT')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && sapi_windows_vt100_support(STDOUT)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (\function_exists('stream_isatty')) { + return stream_isatty(STDOUT); + } + + if (\function_exists('posix_isatty')) { + return posix_isatty(STDOUT); } - return defined('STDOUT') && function_exists('posix_isatty') && @posix_isatty(STDOUT); + $stat = fstat(STDOUT); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } } diff --git a/src/Symfony/Bridge/PhpUnit/LICENSE b/src/Symfony/Bridge/PhpUnit/LICENSE index ef1cde91a61c3..15fc1c88d330b 100644 --- a/src/Symfony/Bridge/PhpUnit/LICENSE +++ b/src/Symfony/Bridge/PhpUnit/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2015 Fabien Potencier +Copyright (c) 2014-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/PhpUnit/README.md b/src/Symfony/Bridge/PhpUnit/README.md index afc9d8dd3bde0..8c4e6e59ccb47 100644 --- a/src/Symfony/Bridge/PhpUnit/README.md +++ b/src/Symfony/Bridge/PhpUnit/README.md @@ -3,51 +3,11 @@ PHPUnit Bridge Provides utilities for PHPUnit, especially user deprecation notices management. -It comes with the following features: - - * disable the garbage collector; - * enforce a consistent `C` locale; - * auto-register `class_exists` to load Doctrine annotations; - * print a user deprecation notices summary at the end of the test suite. - -By default any non-legacy-tagged or any non-@-silenced deprecation notices will -make tests fail. -This can be changed by setting the SYMFONY_DEPRECATIONS_HELPER environment -variable to `weak`. This will make the bridge ignore deprecation notices and -is useful to projects that must use deprecated interfaces for backward -compatibility reasons. - -A summary of deprecation notices is displayed at the end of the test suite: - - * **Unsilenced** reports deprecation notices that were triggered without the - recommended @-silencing operator; - * **Legacy** deprecation notices denote tests that explicitly test some legacy - interfaces. There are four ways to mark a test as legacy: - - make its class start with the `Legacy` prefix; - - make its method start with `testLegacy`; - - make its data provider start with `provideLegacy` or `getLegacy`; - - add the `@group legacy` annotation to its class or method. - * **Remaining/Other** deprecation notices are all other (non-legacy) - notices, grouped by message, test class and method. - -Usage ------ - -Add this bridge to the `require-dev` section of your composer.json file -(not in `require`) with e.g. `composer require --dev "symfony/phpunit-bridge"`. - -When running `phpunit`, you will see a summary of deprecation notices at the end -of the test suite. - -Deprecation notices in the **Unsilenced** section should just be @-silenced: -`@trigger_error('...', E_USER_DEPRECATED);`. Without the @-silencing operator, -users would need to opt-out from deprecation notices. Silencing by default swaps -this behavior and allows users to opt-in when they are ready to cope with them -(by adding a custom error handler like the one provided by this bridge.) - -Deprecation notices in the **Remaining/Other** section need some thought. -You have to decide either to: - - * update your code to not use deprecated interfaces anymore, thus gaining better - forward compatibility; - * or move them to the **Legacy** section (by using one of the above way). +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/phpunit_bridge.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php index 330cad105b892..986963712e2a2 100644 --- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php @@ -13,6 +13,10 @@ use Doctrine\Common\Annotations\AnnotationRegistry; +if (!class_exists('PHPUnit_Framework_BaseTestListener')) { + return; +} + /** * Collects and replays skipped tests. * @@ -20,11 +24,53 @@ */ class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener { + private static $globallyEnabled = false; private $state = -1; private $skippedFile = false; private $wasSkipped = array(); private $isSkipped = array(); + /** + * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive) + */ + public function __construct(array $mockedNamespaces = array()) + { + $warn = false; + foreach ($mockedNamespaces as $type => $namespaces) { + if (!is_array($namespaces)) { + $namespaces = array($namespaces); + } + if (is_int($type)) { + // @deprecated BC with v2.8 to v3.0 + $type = 'time-sensitive'; + $warn = true; + } + if ('time-sensitive' === $type) { + foreach ($namespaces as $ns) { + ClockMock::register($ns.'\DummyClass'); + } + } + } + if (self::$globallyEnabled) { + $this->state = -2; + } else { + self::$globallyEnabled = true; + if ($warn) { + echo "Clock-mocked namespaces for SymfonyTestsListener need to be nested in a \"time-sensitive\" key. This will be enforced in Symfony 4.0.\n"; + } + } + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { if (0 < $this->state) { @@ -32,6 +78,12 @@ public function __destruct() } } + public function globalListenerDisabled() + { + self::$globallyEnabled = false; + $this->state = -1; + } + public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { $suiteName = $suite->getName(); @@ -56,6 +108,21 @@ public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) } } } + $testSuites = array($suite); + for ($i = 0; isset($testSuites[$i]); ++$i) { + foreach ($testSuites[$i]->tests() as $test) { + if ($test instanceof \PHPUnit_Framework_TestSuite) { + if (!class_exists($test->getName(), false)) { + $testSuites[] = $test; + continue; + } + $groups = \PHPUnit_Util_Test::getGroups($test->getName()); + if (in_array('time-sensitive', $groups, true)) { + ClockMock::register($test->getName()); + } + } + } + } } elseif (2 === $this->state) { $skipped = array(); foreach ($suite->tests() as $test) { @@ -86,8 +153,8 @@ public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $ti public function startTest(\PHPUnit_Framework_Test $test) { - if ($test instanceof \PHPUnit_Framework_TestCase) { - $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName()); + if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { + $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false)); if (in_array('time-sensitive', $groups, true)) { ClockMock::register(get_class($test)); @@ -98,8 +165,8 @@ public function startTest(\PHPUnit_Framework_Test $test) public function endTest(\PHPUnit_Framework_Test $test, $time) { - if ($test instanceof \PHPUnit_Framework_TestCase) { - $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName()); + if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { + $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false)); if (in_array('time-sensitive', $groups, true)) { ClockMock::withClockMock(false); diff --git a/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php b/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php new file mode 100644 index 0000000000000..82cfb6f566d9e --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/ClockMockTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ClockMock; + +/** + * @author Dominic Tubach + * + * @covers \Symfony\Bridge\PhpUnit\ClockMock + */ +class ClockMockTest extends TestCase +{ + public static function setUpBeforeClass() + { + ClockMock::register(__CLASS__); + } + + protected function setUp() + { + ClockMock::withClockMock(1234567890.125); + } + + public function testTime() + { + $this->assertSame(1234567890, time()); + } + + public function testSleep() + { + sleep(2); + $this->assertSame(1234567892, time()); + } + + public function testMicrotime() + { + $this->assertSame('0.12500000 1234567890', microtime()); + } + + public function testMicrotimeAsFloat() + { + $this->assertSame(1234567890.125, microtime(true)); + } + + public function testUsleep() + { + usleep(2); + $this->assertSame(1234567890.125002, microtime(true)); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt new file mode 100644 index 0000000000000..7a0595a7ddebc --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -0,0 +1,87 @@ +--TEST-- +Test DeprecationErrorHandler in default mode +--FILE-- +testLegacyFoo(); +$foo->testNonLegacyBar(); + +register_shutdown_function(function () { + exit('I get precedence over any exit statements inside the deprecation error handler.'); +}); + +?> +--EXPECTF-- +Unsilenced deprecation notices (3) + + 2x: unsilenced foo deprecation + 2x in FooTestCase::testLegacyFoo + + 1x: unsilenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Remaining deprecation notices (1) + + 1x: silenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Legacy deprecation notices (1) + +Other deprecation notices (1) + + 1x: root deprecation + +I get precedence over any exit statements inside the deprecation error handler. diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/regexp.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/regexp.phpt new file mode 100644 index 0000000000000..3b7207b85f8ee --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/regexp.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test DeprecationErrorHandler in weak mode +--FILE-- +testLegacyFoo(); + +?> +--EXPECTF-- +Legacy deprecation triggered by FooTestCase::testLegacyFoo: +silenced foo deprecation +Stack trace: +#%A(%d): FooTestCase->testLegacyFoo() +#%d {main} + diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt new file mode 100644 index 0000000000000..fddeed6085dea --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt @@ -0,0 +1,91 @@ +--TEST-- +Test DeprecationErrorHandler in default mode +--FILE-- +testLegacyFoo(); +$foo->testNonLegacyBar(); + +register_shutdown_function(function () { + @trigger_error('root deprecation during shutdown', E_USER_DEPRECATED); +}); + +?> +--EXPECTF-- +Unsilenced deprecation notices (3) + + 2x: unsilenced foo deprecation + 2x in FooTestCase::testLegacyFoo + + 1x: unsilenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Remaining deprecation notices (1) + + 1x: silenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Legacy deprecation notices (1) + +Other deprecation notices (1) + + 1x: root deprecation + +Shutdown-time deprecations: + +Other deprecation notices (1) + + 1x: root deprecation during shutdown diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt new file mode 100644 index 0000000000000..9e78d96e70efb --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/weak.phpt @@ -0,0 +1,40 @@ +--TEST-- +Test DeprecationErrorHandler in weak mode +--FILE-- +testLegacyFoo(); + +?> +--EXPECTF-- +Unsilenced deprecation notices (1) + +Legacy deprecation notices (1) + +Other deprecation notices (1) + diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php index 203fd16414820..82d6ab32e03c2 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/Command.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/Command.php @@ -11,6 +11,10 @@ namespace Symfony\Bridge\PhpUnit\TextUI; +if (!class_exists('PHPUnit_TextUI_Command')) { + return; +} + /** * {@inheritdoc} */ @@ -23,4 +27,24 @@ protected function createRunner() { return new TestRunner($this->arguments['loader']); } + + /** + * {@inheritdoc} + */ + protected function handleBootstrap($filename) + { + parent::handleBootstrap($filename); + + // By default, we want PHPUnit's autoloader before Symfony's one + if (!getenv('SYMFONY_PHPUNIT_OVERLOAD')) { + $filename = realpath(stream_resolve_include_path($filename)); + $symfonyLoader = realpath(\dirname(PHPUNIT_COMPOSER_INSTALL).'/../../../vendor/autoload.php'); + + if ($filename === $symfonyLoader) { + $symfonyLoader = require $symfonyLoader; + $symfonyLoader->unregister(); + $symfonyLoader->register(false); + } + } + } } diff --git a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php index 94602bb3d63ca..eaad394d980f0 100644 --- a/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php +++ b/src/Symfony/Bridge/PhpUnit/TextUI/TestRunner.php @@ -13,6 +13,10 @@ use Symfony\Bridge\PhpUnit\SymfonyTestsListener; +if (!class_exists('PHPUnit_TextUI_TestRunner')) { + return; +} + /** * {@inheritdoc} */ @@ -23,9 +27,26 @@ class TestRunner extends \PHPUnit_TextUI_TestRunner */ protected function handleConfiguration(array &$arguments) { + $listener = new SymfonyTestsListener(); + + $result = parent::handleConfiguration($arguments); + $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : array(); - $arguments['listeners'][] = new SymfonyTestsListener(); - return parent::handleConfiguration($arguments); + $registeredLocally = false; + + foreach ($arguments['listeners'] as $registeredListener) { + if ($registeredListener instanceof SymfonyTestsListener) { + $registeredListener->globalListenerDisabled(); + $registeredLocally = true; + break; + } + } + + if (!$registeredLocally) { + $arguments['listeners'][] = $listener; + } + + return $result; } } diff --git a/src/Symfony/Bridge/PhpUnit/bootstrap.php b/src/Symfony/Bridge/PhpUnit/bootstrap.php index 0a43d98bab851..1283da08fa408 100644 --- a/src/Symfony/Bridge/PhpUnit/bootstrap.php +++ b/src/Symfony/Bridge/PhpUnit/bootstrap.php @@ -17,12 +17,6 @@ return; } -if (PHP_VERSION_ID >= 50400 && gc_enabled()) { - // Disabling Zend Garbage Collection to prevent segfaults with PHP5.4+ - // https://bugs.php.net/bug.php?id=53976 - gc_disable(); -} - // Enforce a consistent locale setlocale(LC_ALL, 'C'); diff --git a/src/Symfony/Bridge/PhpUnit/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 1d7c3e23b7ee6..791f8b6ef210d 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -16,14 +16,22 @@ } ], "require": { - "php": ">=5.3.9" + "php": ">=5.3.3 EVEN ON LATEST SYMFONY VERSIONS TO ALLOW USING", + "php": "THIS BRIDGE WHEN TESTING LOWEST SYMFONY VERSIONS.", + "php": ">=5.3.3" }, "suggest": { "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, + "conflict": { + "phpunit/phpunit": ">=6.0" + }, "autoload": { "files": [ "bootstrap.php" ], - "psr-4": { "Symfony\\Bridge\\PhpUnit\\": "" } + "psr-4": { "Symfony\\Bridge\\PhpUnit\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist b/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist index 7f631b2ece48b..d37d2eac3650a 100644 --- a/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist +++ b/src/Symfony/Bridge/PhpUnit/phpunit.xml.dist @@ -1,10 +1,12 @@ @@ -13,6 +15,7 @@ ./Tests/ + ./Tests/DeprecationErrorHandler/ diff --git a/src/Symfony/Bridge/Propel1/README.md b/src/Symfony/Bridge/Propel1/README.md new file mode 100644 index 0000000000000..41286745ab61a --- /dev/null +++ b/src/Symfony/Bridge/Propel1/README.md @@ -0,0 +1,12 @@ +Propel Bridge +============= + +Provides integration for Propel with various Symfony components. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Bridge/ProxyManager/LICENSE b/src/Symfony/Bridge/ProxyManager/LICENSE index 43028bc600f26..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/ProxyManager/LICENSE +++ b/src/Symfony/Bridge/ProxyManager/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php new file mode 100644 index 0000000000000..3298b84d46278 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderFactoryV1 extends BaseFactory +{ + private $generatorV1; + + /** + * {@inheritdoc} + */ + protected function getGenerator() + { + return $this->generatorV1 ?: $this->generatorV1 = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php new file mode 100644 index 0000000000000..a643f33710dd2 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; +use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderFactoryV2 extends BaseFactory +{ + private $generator; + + /** + * {@inheritdoc} + */ + protected function getGenerator(): ProxyGeneratorInterface + { + return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php index 0101026794c7c..7d083a6981e25 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -36,7 +36,11 @@ public function __construct() $config = new Configuration(); $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); - $this->factory = new LazyLoadingValueHolderFactory($config); + if (method_exists('ProxyManager\Version', 'getVersion')) { + $this->factory = new LazyLoadingValueHolderFactoryV2($config); + } else { + $this->factory = new LazyLoadingValueHolderFactoryV1($config); + } } /** @@ -47,7 +51,7 @@ public function instantiateProxy(ContainerInterface $container, Definition $defi return $this->factory->createProxy( $definition->getClass(), function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) { - $wrappedInstance = call_user_func($realInstantiator); + $wrappedInstance = \call_user_func($realInstantiator); $proxy->setProxyInitializer(null); diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php new file mode 100644 index 0000000000000..1d9432f622b41 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper; + +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator; +use Zend\Code\Generator\ClassGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderGenerator extends BaseGenerator +{ + /** + * {@inheritdoc} + */ + public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + parent::generate($originalClass, $classGenerator); + + if ($classGenerator->hasMethod('__destruct')) { + $destructor = $classGenerator->getMethod('__destruct'); + $body = $destructor->getBody(); + $newBody = preg_replace('/^(\$this->initializer[a-zA-Z0-9]++) && .*;\n\nreturn (\$this->valueHolder)/', '$1 || $2', $body); + + if ($body === $newBody) { + throw new \UnexpectedValueException(sprintf('Unexpected lazy-proxy format generated for method %s::__destruct()', $originalClass->name)); + } + + $destructor->setBody($newBody); + } + } +} diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index e1ec80608c633..b7eba1bda5488 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -13,7 +13,6 @@ use ProxyManager\Generator\ClassGenerator; use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; -use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -26,24 +25,11 @@ */ class ProxyDumper implements DumperInterface { - /** - * @var string - */ private $salt; - - /** - * @var LazyLoadingValueHolderGenerator - */ private $proxyGenerator; - - /** - * @var BaseGeneratorStrategy - */ private $classGenerator; /** - * Constructor. - * * @param string $salt */ public function __construct($salt = '') @@ -67,23 +53,31 @@ public function isProxyCandidate(Definition $definition) public function getProxyFactoryCode(Definition $definition, $id) { $instantiation = 'return'; + $scope = ''; if ($definition->isShared()) { - $instantiation .= " \$this->services['$id'] ="; + $instantiation .= ' $this->services[%s] ='; - if (defined('Symfony\Component\DependencyInjection\ContainerInterface::SCOPE_CONTAINER') && ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { - $instantiation .= " \$this->scopedServices['$scope']['$id'] ="; + if (\defined('Symfony\Component\DependencyInjection\ContainerInterface::SCOPE_CONTAINER') && ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { + $instantiation .= ' $this->scopedServices[%s][%1$s] ='; } - } + } + $instantiation = sprintf($instantiation, var_export($id, true), var_export($scope, true)); $methodName = 'get'.Container::camelize($id).'Service'; $proxyClass = $this->getProxyClassName($definition); + $generatedClass = $this->generateProxyClass($definition); + + $constructorCall = $generatedClass->hasMethod('staticProxyConstructor') + ? $proxyClass.'::staticProxyConstructor' + : 'new '.$proxyClass; + return <<$methodName(false); @@ -103,22 +97,32 @@ function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) */ public function getProxyCode(Definition $definition) { - $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); - - $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); - - return $this->classGenerator->generate($generatedClass); + return preg_replace( + '/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/', + '$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;', + $this->classGenerator->generate($this->generateProxyClass($definition)) + ); } /** * Produces the proxy class name for the given definition. * - * @param Definition $definition - * * @return string */ private function getProxyClassName(Definition $definition) { return str_replace('\\', '', $definition->getClass()).'_'.spl_object_hash($definition).$this->salt; } + + /** + * @return ClassGenerator + */ + private function generateProxyClass(Definition $definition) + { + $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); + + $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); + + return $generatedClass; + } } diff --git a/src/Symfony/Bridge/ProxyManager/README.md b/src/Symfony/Bridge/ProxyManager/README.md index 35d41998b82e0..38d3d6964527f 100644 --- a/src/Symfony/Bridge/ProxyManager/README.md +++ b/src/Symfony/Bridge/ProxyManager/README.md @@ -6,10 +6,9 @@ Provides integration for [ProxyManager][1] with various Symfony components. Resources --------- -You can run the unit tests with the following command: - - $ cd path/to/Symfony/Bridge/ProxyManager/ - $ composer install - $ phpunit + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) [1]: https://github.com/Ocramius/ProxyManager diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php index b6c3b4fd36b42..858e9d76b64c9 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/ContainerBuilderTest.php @@ -13,6 +13,7 @@ require_once __DIR__.'/Fixtures/includes/foo.php'; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -22,11 +23,8 @@ * * @author Marco Pivetta */ -class ContainerBuilderTest extends \PHPUnit_Framework_TestCase +class ContainerBuilderTest extends TestCase { - /** - * @covers Symfony\Component\DependencyInjection\ContainerBuilder::createService - */ public function testCreateProxyServiceWithRuntimeInstantiator() { $builder = new ContainerBuilder(); @@ -36,9 +34,14 @@ public function testCreateProxyServiceWithRuntimeInstantiator() $builder->register('foo1', 'ProxyManagerBridgeFooClass')->setFile(__DIR__.'/Fixtures/includes/foo.php'); $builder->getDefinition('foo1')->setLazy(true); + $builder->compile(); + /* @var $foo1 \ProxyManager\Proxy\LazyLoadingInterface|\ProxyManager\Proxy\ValueHolderInterface */ $foo1 = $builder->get('foo1'); + $foo1->__destruct(); + $this->assertSame(0, $foo1::$destructorCount); + $this->assertSame($foo1, $builder->get('foo1'), 'The same proxy is retrieved on multiple subsequent calls'); $this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1); $this->assertInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1); @@ -50,5 +53,8 @@ public function testCreateProxyServiceWithRuntimeInstantiator() $this->assertTrue($foo1->isProxyInitialized()); $this->assertInstanceOf('\ProxyManagerBridgeFooClass', $foo1->getWrappedValueHolderValue()); $this->assertNotInstanceOf('\ProxyManager\Proxy\LazyLoadingInterface', $foo1->getWrappedValueHolderValue()); + + $foo1->__destruct(); + $this->assertSame(1, $foo1::$destructorCount); } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index 412674ee3f680..62cc3cd38d38f 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; @@ -21,25 +22,13 @@ * * @author Marco Pivetta */ -class PhpDumperTest extends \PHPUnit_Framework_TestCase +class PhpDumperTest extends TestCase { public function testDumpContainerWithProxyService() { - $container = new ContainerBuilder(); - - $container->register('foo', 'stdClass'); - $container->getDefinition('foo')->setLazy(true); - $container->compile(); - - $dumper = new PhpDumper($container); - - $dumper->setProxyDumper(new ProxyDumper()); - - $dumpedString = $dumper->dump(); - $this->assertStringMatchesFormatFile( __DIR__.'/../Fixtures/php/lazy_service_structure.txt', - $dumpedString, + $this->dumpLazyServiceProjectServiceContainer(), '->dump() does generate proxy lazy loading logic.' ); } @@ -49,13 +38,15 @@ public function testDumpContainerWithProxyService() */ public function testDumpContainerWithProxyServiceWillShareProxies() { - require_once __DIR__.'/../Fixtures/php/lazy_service.php'; + if (!class_exists('LazyServiceProjectServiceContainer', false)) { + eval('?>'.$this->dumpLazyServiceProjectServiceContainer()); + } $container = new \LazyServiceProjectServiceContainer(); - /* @var $proxy \stdClass_c1d194250ee2e2b7d2eab8b8212368a8 */ $proxy = $container->get('foo'); - $this->assertInstanceOf('stdClass_c1d194250ee2e2b7d2eab8b8212368a8', $proxy); + $this->assertInstanceOf('stdClass', $proxy); + $this->assertInstanceOf('ProxyManager\Proxy\LazyLoadingInterface', $proxy); $this->assertSame($proxy, $container->get('foo')); $this->assertFalse($proxy->isProxyInitialized()); @@ -65,4 +56,19 @@ public function testDumpContainerWithProxyServiceWillShareProxies() $this->assertTrue($proxy->isProxyInitialized()); $this->assertSame($proxy, $container->get('foo')); } + + private function dumpLazyServiceProjectServiceContainer() + { + $container = new ContainerBuilder(); + + $container->register('foo', 'stdClass'); + $container->getDefinition('foo')->setLazy(true); + $container->compile(); + + $dumper = new PhpDumper($container); + + $dumper->setProxyDumper(new ProxyDumper()); + + return $dumper->dump(array('class' => 'LazyServiceProjectServiceContainer')); + } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php index 1013a8c572325..8ffc5be9af40a 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/includes/foo.php @@ -2,9 +2,16 @@ class ProxyManagerBridgeFooClass { - public $foo, $moo; + public static $destructorCount = 0; - public $bar = null, $initialized = false, $configured = false, $called = false, $arguments = array(); + public $foo; + public $moo; + + public $bar = null; + public $initialized = false; + public $configured = false; + public $called = false; + public $arguments = array(); public function __construct($arguments = array()) { @@ -33,4 +40,9 @@ public function setBar($value = null) { $this->bar = $value; } + + public function __destruct() + { + ++self::$destructorCount; + } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php deleted file mode 100644 index 69e1b9b3e7016..0000000000000 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service.php +++ /dev/null @@ -1,194 +0,0 @@ -services = - $this->scopedServices = - $this->scopeStacks = array(); - $this->scopes = array(); - $this->scopeChildren = array(); - } - - /** - * Gets the 'foo' service. - * - * This service is shared. - * This method always returns the same instance of the service. - * - * @param bool $lazyLoad whether to try lazy-loading the service with a proxy - * - * @return stdClass A stdClass instance. - */ - public function getFooService($lazyLoad = true) - { - if ($lazyLoad) { - $container = $this; - - return $this->services['foo'] = new stdClass_c1d194250ee2e2b7d2eab8b8212368a8( - function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { - $wrappedInstance = $container->getFooService(false); - - $proxy->setProxyInitializer(null); - - return true; - } - ); - } - - return new \stdClass(); - } -} - -class stdClass_c1d194250ee2e2b7d2eab8b8212368a8 extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface -{ - /** - * @var \Closure|null initializer responsible for generating the wrapped object - */ - private $valueHolder5157dd96e88c0 = null; - - /** - * @var \Closure|null initializer responsible for generating the wrapped object - */ - private $initializer5157dd96e8924 = null; - - /** - * @override constructor for lazy initialization - * - * @param \Closure|null $initializer - */ - public function __construct($initializer) - { - $this->initializer5157dd96e8924 = $initializer; - } - - /** - * @param string $name - */ - public function __get($name) - { - $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__get', array('name' => $name)); - - return $this->valueHolder5157dd96e88c0->$name; - } - - /** - * @param string $name - * @param mixed $value - */ - public function __set($name, $value) - { - $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__set', array('name' => $name, 'value' => $value)); - - $this->valueHolder5157dd96e88c0->$name = $value; - } - - /** - * @param string $name - * - * @return bool - */ - public function __isset($name) - { - $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__isset', array('name' => $name)); - - return isset($this->valueHolder5157dd96e88c0->$name); - } - - /** - * @param string $name - */ - public function __unset($name) - { - $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__unset', array('name' => $name)); - - unset($this->valueHolder5157dd96e88c0->$name); - } - - /** - * - */ - public function __clone() - { - $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__clone', array()); - - $this->valueHolder5157dd96e88c0 = clone $this->valueHolder5157dd96e88c0; - } - - /** - * - */ - public function __sleep() - { - $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__sleep', array()); - - return array('valueHolder5157dd96e88c0'); - } - - /** - * - */ - public function __wakeup() - { - } - - /** - * {@inheritdoc} - */ - public function setProxyInitializer(\Closure $initializer = null) - { - $this->initializer5157dd96e8924 = $initializer; - } - - /** - * {@inheritdoc} - */ - public function getProxyInitializer() - { - return $this->initializer5157dd96e8924; - } - - /** - * {@inheritdoc} - */ - public function initializeProxy() - { - return $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, 'initializeProxy', array()); - } - - /** - * {@inheritdoc} - */ - public function isProxyInitialized() - { - return null !== $this->valueHolder5157dd96e88c0; - } - - /** - * {@inheritdoc} - */ - public function getWrappedValueHolderValue() - { - return $this->valueHolder5157dd96e88c0; - } -} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 332370c87eb11..ad9a3fe1587f5 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -1,14 +1,14 @@ services['foo'] = new stdClass_%s( + return $this->services['foo'] =%sstdClass_%s( function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { $wrappedInstance = $container->getFooService(false); @@ -23,5 +23,5 @@ class ProjectServiceContainer extends Container } } -class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\VirtualProxyInterface -{%a}%A \ No newline at end of file +class stdClass_%s extends %SstdClass implements \ProxyManager\%s +{%a}%A diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index a6605c2a533e8..e58b7d6356161 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\ProxyManager\Tests\LazyProxy\Instantiator; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; use Symfony\Component\DependencyInjection\Definition; @@ -18,10 +19,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator}. * * @author Marco Pivetta - * - * @covers \Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator */ -class RuntimeInstantiatorTest extends \PHPUnit_Framework_TestCase +class RuntimeInstantiatorTest extends TestCase { /** * @var RuntimeInstantiator @@ -39,7 +38,7 @@ protected function setUp() public function testInstantiateProxy() { $instance = new \stdClass(); - $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock(); $definition = new Definition('stdClass'); $instantiator = function () use ($instance) { return $instance; diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index 8ef46b4d962fe..838c33a8368b1 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\ProxyManager\Tests\LazyProxy\PhpDumper; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use Symfony\Component\DependencyInjection\Definition; @@ -18,10 +19,8 @@ * Tests for {@see \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper}. * * @author Marco Pivetta - * - * @covers \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper */ -class ProxyDumperTest extends \PHPUnit_Framework_TestCase +class ProxyDumperTest extends TestCase { /** * @var ProxyDumper @@ -71,7 +70,7 @@ public function testGetProxyFactoryCode() $code = $this->dumper->getProxyFactoryCode($definition, 'foo'); $this->assertStringMatchesFormat( - '%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] = new ' + '%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] =%s' .'SymfonyBridgeProxyManagerTestsLazyProxyPhpDumperProxyDumperTest_%s(%wfunction ' .'(&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {' .'%w$wrappedInstance = $container->getFooService(false);%w$proxy->setProxyInitializer(null);' @@ -88,7 +87,7 @@ public function getProxyCandidates() $definitions = array( array(new Definition(__CLASS__), true), array(new Definition('stdClass'), true), - array(new Definition('foo'.uniqid()), false), + array(new Definition(uniqid('foo', true)), false), array(new Definition(), false), ); diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 5adb3a55a8a69..7b560bd6e0dda 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -18,13 +18,16 @@ "require": { "php": ">=5.3.9", "symfony/dependency-injection": "~2.8|~3.0.0", - "ocramius/proxy-manager": "~0.4|~1.0" + "ocramius/proxy-manager": "~0.4|~1.0|~2.0" }, "require-dev": { "symfony/config": "~2.3|~3.0.0" }, "autoload": { - "psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" } + "psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist b/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist index 60980be9e531e..60d6ffc3aab3e 100644 --- a/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist +++ b/src/Symfony/Bridge/ProxyManager/phpunit.xml.dist @@ -1,10 +1,12 @@ diff --git a/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php b/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php index 9e1d75ee9401f..22acbd50f555b 100644 --- a/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php +++ b/src/Symfony/Bridge/Swiftmailer/DataCollector/MessageDataCollector.php @@ -11,16 +11,14 @@ namespace Symfony\Bridge\Swiftmailer\DataCollector; -@trigger_error(__CLASS__.' class is deprecated since version 2.4 and will be removed in 3.0. Use the Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector class from SwiftmailerBundle instead. Require symfony/swiftmailer-bundle package to download SwiftmailerBundle with Composer.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\MessageDataCollector class is deprecated since Symfony 2.4 and will be removed in 3.0. Use the Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector class from SwiftmailerBundle instead. Require symfony/swiftmailer-bundle package to download SwiftmailerBundle with Composer.', E_USER_DEPRECATED); -use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; /** - * MessageDataCollector. - * * @author Fabien Potencier * @author Clément JOBEILI * @@ -33,8 +31,6 @@ class MessageDataCollector extends DataCollector private $isSpool; /** - * Constructor. - * * We don't inject the message logger and mailer here * to avoid the creation of these objects when no emails are sent. * diff --git a/src/Symfony/Bridge/Swiftmailer/LICENSE b/src/Symfony/Bridge/Swiftmailer/LICENSE index 43028bc600f26..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Swiftmailer/LICENSE +++ b/src/Symfony/Bridge/Swiftmailer/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Swiftmailer/README.md b/src/Symfony/Bridge/Swiftmailer/README.md new file mode 100644 index 0000000000000..daa99dfe5042e --- /dev/null +++ b/src/Symfony/Bridge/Swiftmailer/README.md @@ -0,0 +1,13 @@ +Swiftmailer Bridge +================== + +Provides integration for [Swiftmailer](http://swiftmailer.org/) into the +Symfony web development toolbar. + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Bridge/Swiftmailer/composer.json b/src/Symfony/Bridge/Swiftmailer/composer.json index 24fc833365652..2a5be2e73bd5e 100644 --- a/src/Symfony/Bridge/Swiftmailer/composer.json +++ b/src/Symfony/Bridge/Swiftmailer/composer.json @@ -23,7 +23,10 @@ "symfony/http-kernel": "" }, "autoload": { - "psr-4": { "Symfony\\Bridge\\Swiftmailer\\": "" } + "psr-4": { "Symfony\\Bridge\\Swiftmailer\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Bridge/Twig/AppVariable.php b/src/Symfony/Bridge/Twig/AppVariable.php index 88d9835ce2399..5f8be807cc48f 100644 --- a/src/Symfony/Bridge/Twig/AppVariable.php +++ b/src/Symfony/Bridge/Twig/AppVariable.php @@ -11,12 +11,12 @@ namespace Symfony\Bridge\Twig; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\SecurityContext; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Exposes some Symfony parameters and services as an "app" global variable. @@ -68,7 +68,7 @@ public function setDebug($debug) */ public function getSecurity() { - @trigger_error('The "app.security" variable is deprecated since version 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); + @trigger_error('The "app.security" variable is deprecated since Symfony 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); if (null === $this->container) { throw new \RuntimeException('The "app.security" variable is not available.'); @@ -103,7 +103,7 @@ public function getUser() } $user = $token->getUser(); - if (is_object($user)) { + if (\is_object($user)) { return $user; } } diff --git a/src/Symfony/Bridge/Twig/Command/DebugCommand.php b/src/Symfony/Bridge/Twig/Command/DebugCommand.php index 6100eb87a5e17..58b429ca6df3e 100644 --- a/src/Symfony/Bridge/Twig/Command/DebugCommand.php +++ b/src/Symfony/Bridge/Twig/Command/DebugCommand.php @@ -13,10 +13,11 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Twig\Environment; /** * Lists twig functions, filters, globals and tests present in the current project. @@ -35,18 +36,13 @@ public function __construct($name = 'debug:twig') parent::__construct($name); } - /** - * Sets the twig environment. - * - * @param \Twig_Environment $twig - */ - public function setTwigEnvironment(\Twig_Environment $twig) + public function setTwigEnvironment(Environment $twig) { $this->twig = $twig; } /** - * @return \Twig_Environment $twig + * @return Environment $twig */ protected function getTwigEnvironment() { @@ -61,7 +57,7 @@ protected function configure() new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), )) ->setDescription('Shows a list of twig functions, filters, globals and tests') - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, filters, globals and tests. Output can be filtered with an optional argument. @@ -83,18 +79,18 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); $twig = $this->getTwigEnvironment(); if (null === $twig) { - $output->error('The Twig environment needs to be set.'); + $io->error('The Twig environment needs to be set.'); return 1; } $types = array('functions', 'filters', 'tests', 'globals'); - if ($input->getOption('format') === 'json') { + if ('json' === $input->getOption('format')) { $data = array(); foreach ($types as $type) { foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { @@ -102,7 +98,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } } $data['tests'] = array_keys($data['tests']); - $output->writeln(json_encode($data)); + $io->writeln(json_encode($data)); return 0; } @@ -121,10 +117,10 @@ protected function execute(InputInterface $input, OutputInterface $output) continue; } - $output->section(ucfirst($type)); + $io->section(ucfirst($type)); ksort($items); - $output->listing($items); + $io->listing($items); } return 0; @@ -132,41 +128,46 @@ protected function execute(InputInterface $input, OutputInterface $output) private function getMetadata($type, $entity) { - if ($type === 'globals') { + if ('globals' === $type) { return $entity; } - if ($type === 'tests') { + if ('tests' === $type) { return; } - if ($type === 'functions' || $type === 'filters') { - $args = array(); + if ('functions' === $type || 'filters' === $type) { $cb = $entity->getCallable(); - if (is_null($cb)) { + if (null === $cb) { return; } - if (is_array($cb)) { + if (\is_array($cb)) { if (!method_exists($cb[0], $cb[1])) { return; } $refl = new \ReflectionMethod($cb[0], $cb[1]); - } elseif (is_object($cb) && method_exists($cb, '__invoke')) { + } elseif (\is_object($cb) && method_exists($cb, '__invoke')) { $refl = new \ReflectionMethod($cb, '__invoke'); - } elseif (function_exists($cb)) { + } elseif (\function_exists($cb)) { $refl = new \ReflectionFunction($cb); - } elseif (is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) { + } elseif (\is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) { $refl = new \ReflectionMethod($m[1], $m[2]); } else { throw new \UnexpectedValueException('Unsupported callback type'); } + $args = $refl->getParameters(); + // filter out context/environment args - $args = array_filter($refl->getParameters(), function ($param) use ($entity) { - if ($entity->needsContext() && $param->getName() === 'context') { - return false; - } + if ($entity->needsEnvironment()) { + array_shift($args); + } + if ($entity->needsContext()) { + array_shift($args); + } - return !$param->getClass() || $param->getClass()->getName() !== 'Twig_Environment'; - }); + if ('filters' === $type) { + // remove the value the filter is applied on + array_shift($args); + } // format args $args = array_map(function ($param) { @@ -177,43 +178,38 @@ private function getMetadata($type, $entity) return $param->getName(); }, $args); - if ($type === 'filters') { - // remove the value the filter is applied on - array_shift($args); - } - return $args; } } private function getPrettyMetadata($type, $entity) { - if ($type === 'tests') { + if ('tests' === $type) { return ''; } try { $meta = $this->getMetadata($type, $entity); - if ($meta === null) { + if (null === $meta) { return '(unknown?)'; } } catch (\UnexpectedValueException $e) { return ' '.$e->getMessage().''; } - if ($type === 'globals') { - if (is_object($meta)) { - return ' = object('.get_class($meta).')'; + if ('globals' === $type) { + if (\is_object($meta)) { + return ' = object('.\get_class($meta).')'; } return ' = '.substr(@json_encode($meta), 0, 50); } - if ($type === 'functions') { + if ('functions' === $type) { return '('.implode(', ', $meta).')'; } - if ($type === 'filters') { + if ('filters' === $type) { return $meta ? '('.implode(', ', $meta).')' : ''; } } diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 44d53da3bd069..3a0fffca6f276 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -18,6 +18,10 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Loader\ArrayLoader; +use Twig\Source; /** * Command that will validate your template syntax and output encountered errors. @@ -37,18 +41,13 @@ public function __construct($name = 'lint:twig') parent::__construct($name); } - /** - * Sets the twig environment. - * - * @param \Twig_Environment $twig - */ - public function setTwigEnvironment(\Twig_Environment $twig) + public function setTwigEnvironment(Environment $twig) { $this->twig = $twig; } /** - * @return \Twig_Environment $twig + * @return Environment $twig */ protected function getTwigEnvironment() { @@ -62,22 +61,23 @@ protected function configure() ->setDescription('Lints a template and outputs encountered errors') ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addArgument('filename', InputArgument::IS_ARRAY) - ->setHelp(<<setHelp(<<<'EOF' The %command.name% command lints a template and outputs to STDOUT the first encountered syntax error. -You can validate the syntax of a file: +You can validate the syntax of contents passed from STDIN: -php %command.full_name% filename + cat filename | php %command.full_name% -Or of a whole directory: +Or the syntax of a file: -php %command.full_name% dirname -php %command.full_name% dirname --format=json + php %command.full_name% filename -You can also pass the template contents from STDIN: +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json -cat filename | php %command.full_name% EOF ) ; @@ -85,24 +85,21 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { - $stdout = $output; - $output = new SymfonyStyle($input, $output); + $io = new SymfonyStyle($input, $output); if (false !== strpos($input->getFirstArgument(), ':l')) { - $output->caution('The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.'); + $io->caution('The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.'); } - $twig = $this->getTwigEnvironment(); - - if (null === $twig) { - $output->error('The Twig environment needs to be set.'); + if (null === $twig = $this->getTwigEnvironment()) { + $io->error('The Twig environment needs to be set.'); return 1; } $filenames = $input->getArgument('filename'); - if (0 === count($filenames)) { + if (0 === \count($filenames)) { if (0 !== ftell(STDIN)) { throw new \RuntimeException('Please provide a filename or pipe template content to STDIN.'); } @@ -112,15 +109,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $template .= fread(STDIN, 1024); } - return $this->display($input, $stdout, $output, array($this->validate($twig, $template, uniqid('sf_')))); + return $this->display($input, $output, $io, array($this->validate($twig, $template, uniqid('sf_', true)))); } $filesInfo = $this->getFilesInfo($twig, $filenames); - return $this->display($input, $stdout, $output, $filesInfo); + return $this->display($input, $output, $io, $filesInfo); } - private function getFilesInfo(\Twig_Environment $twig, array $filenames) + private function getFilesInfo(Environment $twig, array $filenames) { $filesInfo = array(); foreach ($filenames as $filename) { @@ -143,16 +140,16 @@ protected function findFiles($filename) throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); } - private function validate(\Twig_Environment $twig, $template, $file) + private function validate(Environment $twig, $template, $file) { $realLoader = $twig->getLoader(); try { - $temporaryLoader = new \Twig_Loader_Array(array((string) $file => $template)); + $temporaryLoader = new ArrayLoader(array((string) $file => $template)); $twig->setLoader($temporaryLoader); - $nodeTree = $twig->parse($twig->tokenize($template, (string) $file)); + $nodeTree = $twig->parse($twig->tokenize(new Source($template, (string) $file))); $twig->compile($nodeTree); $twig->setLoader($realLoader); - } catch (\Twig_Error $e) { + } catch (Error $e) { $twig->setLoader($realLoader); return array('template' => $template, 'file' => $file, 'valid' => false, 'exception' => $e); @@ -161,35 +158,35 @@ private function validate(\Twig_Environment $twig, $template, $file) return array('template' => $template, 'file' => $file, 'valid' => true); } - private function display(InputInterface $input, OutputInterface $stdout, $output, $files) + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files) { switch ($input->getOption('format')) { case 'txt': - return $this->displayTxt($stdout, $output, $files); + return $this->displayTxt($output, $io, $files); case 'json': - return $this->displayJson($stdout, $files); + return $this->displayJson($output, $files); default: throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); } } - private function displayTxt(OutputInterface $stdout, $output, $filesInfo) + private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo) { $errors = 0; foreach ($filesInfo as $info) { - if ($info['valid'] && $stdout->isVerbose()) { - $output->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + if ($info['valid'] && $output->isVerbose()) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$errors; - $this->renderException($output, $info['template'], $info['exception'], $info['file']); + $this->renderException($io, $info['template'], $info['exception'], $info['file']); } } - if ($errors === 0) { - $output->success(sprintf('All %d Twig files contain valid syntax.', count($filesInfo))); + if (0 === $errors) { + $io->success(sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); } else { - $output->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', count($filesInfo) - $errors, $errors)); + $io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); } return min($errors, 1); @@ -209,12 +206,12 @@ private function displayJson(OutputInterface $output, $filesInfo) } }); - $output->writeln(json_encode($filesInfo, defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0)); + $output->writeln(json_encode($filesInfo, \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES : 0)); return min($errors, 1); } - private function renderException(OutputInterface $output, $template, \Twig_Error $exception, $file = null) + private function renderException(OutputInterface $output, $template, Error $exception, $file = null) { $line = $exception->getTemplateLine(); @@ -242,7 +239,7 @@ private function getContext($template, $line, $context = 3) $lines = explode("\n", $template); $position = max(0, $line - $context); - $max = min(count($lines), $line - 1 + $context); + $max = min(\count($lines), $line - 1 + $context); $result = array(); while ($position < $max) { diff --git a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php index 90b21d0495ff7..4496a542b3b3a 100644 --- a/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php +++ b/src/Symfony/Bridge/Twig/DataCollector/TwigDataCollector.php @@ -11,10 +11,13 @@ namespace Symfony\Bridge\Twig\DataCollector; -use Symfony\Component\HttpKernel\DataCollector\DataCollector; -use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Twig\Markup; +use Twig\Profiler\Dumper\HtmlDumper; +use Twig\Profiler\Profile; /** * TwigDataCollector. @@ -26,7 +29,7 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf private $profile; private $computed; - public function __construct(\Twig_Profiler_Profile $profile) + public function __construct(Profile $profile) { $this->profile = $profile; } @@ -73,7 +76,7 @@ public function getMacroCount() public function getHtmlCallGraph() { - $dumper = new \Twig_Profiler_Dumper_Html(); + $dumper = new HtmlDumper(); $dump = $dumper->dump($this->getProfile()); // needed to remove the hardcoded CSS styles @@ -87,7 +90,7 @@ public function getHtmlCallGraph() '', ), $dump); - return new \Twig_Markup($dump, 'UTF-8'); + return new Markup($dump, 'UTF-8'); } public function getProfile() @@ -108,7 +111,7 @@ private function getComputedData($index) return $this->computed[$index]; } - private function computeData(\Twig_Profiler_Profile $profile) + private function computeData(Profile $profile) { $data = array( 'template_count' => 0, diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php index d90d13f6fd3d4..271d71ad2bbec 100644 --- a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -13,13 +13,15 @@ use Symfony\Component\Asset\Packages; use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; /** * Twig extension for the Symfony Asset component. * * @author Fabien Potencier */ -class AssetExtension extends \Twig_Extension +class AssetExtension extends AbstractExtension { private $packages; private $foundationExtension; @@ -40,9 +42,9 @@ public function __construct(Packages $packages, HttpFoundationExtension $foundat public function getFunctions() { return array( - new \Twig_SimpleFunction('asset', array($this, 'getAssetUrl')), - new \Twig_SimpleFunction('asset_version', array($this, 'getAssetVersion')), - new \Twig_SimpleFunction('assets_version', array($this, 'getAssetsVersion'), array('deprecated' => true, 'alternative' => 'asset_version')), + new TwigFunction('asset', array($this, 'getAssetUrl')), + new TwigFunction('asset_version', array($this, 'getAssetVersion')), + new TwigFunction('assets_version', array($this, 'getAssetsVersion'), array('deprecated' => true, 'alternative' => 'asset_version')), ); } @@ -60,13 +62,13 @@ public function getFunctions() public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null) { // BC layer to be removed in 3.0 - if (2 < $count = func_num_args()) { + if (2 < $count = \func_num_args()) { @trigger_error('Generating absolute URLs with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0. Please use absolute_url() instead.', E_USER_DEPRECATED); if (4 === $count) { @trigger_error('Forcing a version with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); } - $args = func_get_args(); + $args = \func_get_args(); return $this->getLegacyAssetUrl($path, $packageName, $args[2], isset($args[3]) ? $args[3] : null); } @@ -98,19 +100,16 @@ private function getLegacyAssetUrl($path, $packageName = null, $absolute = false { if ($version) { $package = $this->packages->getPackage($packageName); - $class = new \ReflectionClass($package); - while ('Symfony\Component\Asset\Package' !== $class->getName()) { - $class = $class->getParentClass(); - } - - $v = $class->getProperty('versionStrategy'); + $v = new \ReflectionProperty('Symfony\Component\Asset\Package', 'versionStrategy'); $v->setAccessible(true); + $currentVersionStrategy = $v->getValue($package); if (property_exists($currentVersionStrategy, 'format')) { $f = new \ReflectionProperty($currentVersionStrategy, 'format'); $f->setAccessible(true); + $format = $f->getValue($currentVersionStrategy); $v->setValue($package, new StaticVersionStrategy($version, $format)); diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index b7c3605d9572f..4e85fe1cf2220 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -11,20 +11,21 @@ namespace Symfony\Bridge\Twig\Extension; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + /** * Twig extension relate to PHP code and used by the profiler and the default exception templates. * * @author Fabien Potencier */ -class CodeExtension extends \Twig_Extension +class CodeExtension extends AbstractExtension { private $fileLinkFormat; private $rootDir; private $charset; /** - * Constructor. - * * @param string $fileLinkFormat The format for links to source files * @param string $rootDir The project root directory * @param string $charset The charset @@ -32,7 +33,7 @@ class CodeExtension extends \Twig_Extension public function __construct($fileLinkFormat, $rootDir, $charset) { $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->rootDir = str_replace('/', DIRECTORY_SEPARATOR, dirname($rootDir)).DIRECTORY_SEPARATOR; + $this->rootDir = str_replace('/', \DIRECTORY_SEPARATOR, \dirname($rootDir)).\DIRECTORY_SEPARATOR; $this->charset = $charset; } @@ -42,14 +43,14 @@ public function __construct($fileLinkFormat, $rootDir, $charset) public function getFilters() { return array( - new \Twig_SimpleFilter('abbr_class', array($this, 'abbrClass'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('abbr_method', array($this, 'abbrMethod'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('format_args', array($this, 'formatArgs'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('format_args_as_text', array($this, 'formatArgsAsText')), - new \Twig_SimpleFilter('file_excerpt', array($this, 'fileExcerpt'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('format_file', array($this, 'formatFile'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))), - new \Twig_SimpleFilter('file_link', array($this, 'getFileLink')), + new TwigFilter('abbr_class', array($this, 'abbrClass'), array('is_safe' => array('html'))), + new TwigFilter('abbr_method', array($this, 'abbrMethod'), array('is_safe' => array('html'))), + new TwigFilter('format_args', array($this, 'formatArgs'), array('is_safe' => array('html'))), + new TwigFilter('format_args_as_text', array($this, 'formatArgsAsText')), + new TwigFilter('file_excerpt', array($this, 'fileExcerpt'), array('is_safe' => array('html'))), + new TwigFilter('format_file', array($this, 'formatFile'), array('is_safe' => array('html'))), + new TwigFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))), + new TwigFilter('file_link', array($this, 'getFileLink')), ); } @@ -67,9 +68,9 @@ public function abbrMethod($method) list($class, $method) = explode('::', $method, 2); $result = sprintf('%s::%s()', $this->abbrClass($class), $method); } elseif ('Closure' === $method) { - $result = sprintf('%s', $method, $method); + $result = sprintf('%1$s', $method); } else { - $result = sprintf('%s()', $method, $method); + $result = sprintf('%1$s()', $method); } return $result; @@ -91,7 +92,7 @@ public function formatArgs($args) $short = array_pop($parts); $formattedValue = sprintf('object(%s)', $item[1], $short); } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); } elseif ('string' === $item[0]) { $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->charset)); } elseif ('null' === $item[0]) { @@ -104,7 +105,7 @@ public function formatArgs($args) $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->charset), true)); } - $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); } return implode(', ', $result); @@ -138,10 +139,10 @@ public function fileExcerpt($file, $line) $code = @highlight_file($file, true); // remove main code/span tags $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - $content = preg_split('#
#', $code); + $content = explode('
', $code); $lines = array(); - for ($i = max($line - 3, 1), $max = min($line + 3, count($content)); $i <= $max; ++$i) { + for ($i = max($line - 3, 1), $max = min($line + 3, \count($content)); $i <= $max; ++$i) { $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; } @@ -163,18 +164,18 @@ public function formatFile($file, $line, $text = null) $file = trim($file); if (null === $text) { - $text = str_replace('/', DIRECTORY_SEPARATOR, $file); + $text = str_replace('/', \DIRECTORY_SEPARATOR, $file); if (0 === strpos($text, $this->rootDir)) { - $text = substr($text, strlen($this->rootDir)); - $text = explode(DIRECTORY_SEPARATOR, $text, 2); - $text = sprintf('%s%s', $this->rootDir, $text[0], isset($text[1]) ? DIRECTORY_SEPARATOR.$text[1] : ''); + $text = substr($text, \strlen($this->rootDir)); + $text = explode(\DIRECTORY_SEPARATOR, $text, 2); + $text = sprintf('%s%s', $this->rootDir, $text[0], isset($text[1]) ? \DIRECTORY_SEPARATOR.$text[1] : ''); } } $text = "$text at line $line"; if (false !== $link = $this->getFileLink($file, $line)) { - if (PHP_VERSION_ID >= 50400) { + if (\PHP_VERSION_ID >= 50400) { $flags = ENT_QUOTES | ENT_SUBSTITUTE; } else { $flags = ENT_QUOTES; @@ -192,7 +193,7 @@ public function formatFile($file, $line, $text = null) * @param string $file An absolute file path * @param int $line The line number * - * @return string A link of false + * @return string|false A link or false */ public function getFileLink($file, $line) { diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php index 30318ecac6d02..70be3e9127d95 100644 --- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -14,13 +14,17 @@ use Symfony\Bridge\Twig\TokenParser\DumpTokenParser; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\Template; +use Twig\TwigFunction; /** * Provides integration of the dump() function with Twig. * * @author Nicolas Grekas */ -class DumpExtension extends \Twig_Extension +class DumpExtension extends AbstractExtension { private $cloner; @@ -32,7 +36,7 @@ public function __construct(ClonerInterface $cloner) public function getFunctions() { return array( - new \Twig_SimpleFunction('dump', array($this, 'dump'), array('is_safe' => array('html'), 'needs_context' => true, 'needs_environment' => true)), + new TwigFunction('dump', array($this, 'dump'), array('is_safe' => array('html'), 'needs_context' => true, 'needs_environment' => true)), ); } @@ -46,28 +50,28 @@ public function getName() return 'dump'; } - public function dump(\Twig_Environment $env, $context) + public function dump(Environment $env, $context) { if (!$env->isDebug()) { return; } - if (2 === func_num_args()) { + if (2 === \func_num_args()) { $vars = array(); foreach ($context as $key => $value) { - if (!$value instanceof \Twig_Template) { + if (!$value instanceof Template) { $vars[$key] = $value; } } $vars = array($vars); } else { - $vars = func_get_args(); + $vars = \func_get_args(); unset($vars[0], $vars[1]); } $dump = fopen('php://memory', 'r+b'); - $dumper = new HtmlDumper($dump); + $dumper = new HtmlDumper($dump, $env->getCharset()); foreach ($vars as $value) { $dumper->dump($this->cloner->cloneVar($value)); diff --git a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php index 6b30a279419b7..fc64fa3e3775d 100644 --- a/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ExpressionExtension.php @@ -12,13 +12,15 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\ExpressionLanguage\Expression; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; /** * ExpressionExtension gives a way to create Expressions from a template. * * @author Fabien Potencier */ -class ExpressionExtension extends \Twig_Extension +class ExpressionExtension extends AbstractExtension { /** * {@inheritdoc} @@ -26,7 +28,7 @@ class ExpressionExtension extends \Twig_Extension public function getFunctions() { return array( - new \Twig_SimpleFunction('expression', array($this, 'createExpression')), + new TwigFunction('expression', array($this, 'createExpression')), ); } diff --git a/src/Symfony/Bridge/Twig/Extension/FormExtension.php b/src/Symfony/Bridge/Twig/Extension/FormExtension.php index 924d49d581863..ca5e716fda627 100644 --- a/src/Symfony/Bridge/Twig/Extension/FormExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/FormExtension.php @@ -11,9 +11,16 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Bridge\Twig\Form\TwigRendererInterface; +use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; use Symfony\Component\Form\Extension\Core\View\ChoiceView; +use Symfony\Component\Form\FormView; +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\Extension\InitRuntimeInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; /** * FormExtension extends Twig with form capabilities. @@ -21,7 +28,7 @@ * @author Fabien Potencier * @author Bernhard Schussek */ -class FormExtension extends \Twig_Extension +class FormExtension extends AbstractExtension implements InitRuntimeInterface { /** * This property is public so that it can be accessed directly from compiled @@ -39,7 +46,7 @@ public function __construct(TwigRendererInterface $renderer) /** * {@inheritdoc} */ - public function initRuntime(\Twig_Environment $environment) + public function initRuntime(Environment $environment) { $this->renderer->setEnvironment($environment); } @@ -61,16 +68,16 @@ public function getTokenParsers() public function getFunctions() { return array( - new \Twig_SimpleFunction('form_enctype', null, array('node_class' => 'Symfony\Bridge\Twig\Node\FormEnctypeNode', 'is_safe' => array('html'), 'deprecated' => true, 'alternative' => 'form_start')), - new \Twig_SimpleFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_errors', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_label', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_row', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_rest', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_start', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('form_end', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), - new \Twig_SimpleFunction('csrf_token', array($this, 'renderCsrfToken')), + new TwigFunction('form_enctype', null, array('node_class' => 'Symfony\Bridge\Twig\Node\FormEnctypeNode', 'is_safe' => array('html'), 'deprecated' => true, 'alternative' => 'form_start')), + new TwigFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_errors', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_label', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_row', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_rest', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_start', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_end', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('csrf_token', array($this, 'renderCsrfToken')), ); } @@ -80,7 +87,8 @@ public function getFunctions() public function getFilters() { return array( - new \Twig_SimpleFilter('humanize', array($this, 'humanize')), + new TwigFilter('humanize', array($this, 'humanize')), + new TwigFilter('form_encode_currency', array($this, 'encodeCurrency'), array('is_safe' => array('html'), 'needs_environment' => true)), ); } @@ -90,16 +98,17 @@ public function getFilters() public function getTests() { return array( - new \Twig_SimpleTest('selectedchoice', array($this, 'isSelectedChoice')), + new TwigTest('selectedchoice', array($this, 'isSelectedChoice')), + new TwigTest('rootform', array($this, 'isRootForm')), ); } /** * Renders a CSRF token. * - * @param string $intention The intention of the protected action. + * @param string $intention The intention of the protected action * - * @return string A CSRF token. + * @return string A CSRF token */ public function renderCsrfToken($intention) { @@ -109,9 +118,9 @@ public function renderCsrfToken($intention) /** * Makes a technical name human readable. * - * @param string $text The text to humanize. + * @param string $text The text to humanize * - * @return string The humanized text. + * @return string The humanized text */ public function humanize($text) { @@ -134,22 +143,46 @@ public function humanize($text) * seems to be much more efficient at executing filters than at executing * methods of an object. * - * @param ChoiceView $choice The choice to check. - * @param string|array $selectedValue The selected value to compare. + * @param ChoiceView $choice The choice to check + * @param string|array $selectedValue The selected value to compare * - * @return bool Whether the choice is selected. + * @return bool Whether the choice is selected * * @see ChoiceView::isSelected() */ public function isSelectedChoice(ChoiceView $choice, $selectedValue) { - if (is_array($selectedValue)) { - return in_array($choice->value, $selectedValue, true); + if (\is_array($selectedValue)) { + return \in_array($choice->value, $selectedValue, true); } return $choice->value === $selectedValue; } + /** + * @internal + */ + public function isRootForm(FormView $formView) + { + return null === $formView->parent; + } + + /** + * @internal + */ + public function encodeCurrency(Environment $environment, $text, $widget = '') + { + if ('UTF-8' === $charset = $environment->getCharset()) { + $text = htmlspecialchars($text, ENT_QUOTES | (\defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8'); + } else { + $text = htmlentities($text, ENT_QUOTES | (\defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8'); + $text = iconv('UTF-8', $charset, $text); + $widget = iconv('UTF-8', $charset, $widget); + } + + return str_replace('{{ widget }}', $widget, $text); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php index ad7949dfaa682..fe2778393cfd2 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php @@ -11,21 +11,26 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RequestContext; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; /** * Twig extension for the Symfony HttpFoundation component. * * @author Fabien Potencier */ -class HttpFoundationExtension extends \Twig_Extension +class HttpFoundationExtension extends AbstractExtension { private $requestStack; + private $requestContext; - public function __construct(RequestStack $requestStack) + public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) { $this->requestStack = $requestStack; + $this->requestContext = $requestContext; } /** @@ -34,8 +39,8 @@ public function __construct(RequestStack $requestStack) public function getFunctions() { return array( - new \Twig_SimpleFunction('absolute_url', array($this, 'generateAbsoluteUrl')), - new \Twig_SimpleFunction('relative_path', array($this, 'generateRelativePath')), + new TwigFunction('absolute_url', array($this, 'generateAbsoluteUrl')), + new TwigFunction('relative_path', array($this, 'generateRelativePath')), ); } @@ -57,12 +62,42 @@ public function generateAbsoluteUrl($path) } if (!$request = $this->requestStack->getMasterRequest()) { + if (null !== $this->requestContext && '' !== $host = $this->requestContext->getHost()) { + $scheme = $this->requestContext->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 != $this->requestContext->getHttpPort()) { + $port = ':'.$this->requestContext->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->requestContext->getHttpsPort()) { + $port = ':'.$this->requestContext->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $this->requestContext->getQueryString(); + $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $this->requestContext->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } + return $path; } + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + if (!$path || '/' !== $path[0]) { $prefix = $request->getPathInfo(); - $last = strlen($prefix) - 1; + $last = \strlen($prefix) - 1; if ($last !== $pos = strrpos($prefix, '/')) { $prefix = substr($prefix, 0, $pos).'/'; } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index 1da12aaf5eb10..74449a35c2443 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -11,23 +11,20 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; /** * Provides integration with the HttpKernel component. * * @author Fabien Potencier */ -class HttpKernelExtension extends \Twig_Extension +class HttpKernelExtension extends AbstractExtension { private $handler; - /** - * Constructor. - * - * @param FragmentHandler $handler A FragmentHandler instance - */ public function __construct(FragmentHandler $handler) { $this->handler = $handler; @@ -36,9 +33,9 @@ public function __construct(FragmentHandler $handler) public function getFunctions() { return array( - new \Twig_SimpleFunction('render', array($this, 'renderFragment'), array('is_safe' => array('html'))), - new \Twig_SimpleFunction('render_*', array($this, 'renderFragmentStrategy'), array('is_safe' => array('html'))), - new \Twig_SimpleFunction('controller', array($this, 'controller')), + new TwigFunction('render', array($this, 'renderFragment'), array('is_safe' => array('html'))), + new TwigFunction('render_*', array($this, 'renderFragmentStrategy'), array('is_safe' => array('html'))), + new TwigFunction('controller', array($this, 'controller')), ); } diff --git a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php index 7fc278758eb38..17abb779899ac 100644 --- a/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/LogoutUrlExtension.php @@ -11,15 +11,16 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; /** * LogoutUrlHelper provides generator functions for the logout URL to Twig. * * @author Jeremy Mikola */ -class LogoutUrlExtension extends \Twig_Extension +class LogoutUrlExtension extends AbstractExtension { private $generator; @@ -34,8 +35,8 @@ public function __construct(LogoutUrlGenerator $generator) public function getFunctions() { return array( - new \Twig_SimpleFunction('logout_url', array($this, 'getLogoutUrl')), - new \Twig_SimpleFunction('logout_path', array($this, 'getLogoutPath')), + new TwigFunction('logout_url', array($this, 'getLogoutUrl')), + new TwigFunction('logout_path', array($this, 'getLogoutPath')), ); } @@ -48,7 +49,7 @@ public function getFunctions() */ public function getLogoutPath($key = null) { - return $this->generator->getLogoutPath($key, UrlGeneratorInterface::ABSOLUTE_PATH); + return $this->generator->getLogoutPath($key); } /** @@ -60,7 +61,7 @@ public function getLogoutPath($key = null) */ public function getLogoutUrl($key = null) { - return $this->generator->getLogoutUrl($key, UrlGeneratorInterface::ABSOLUTE_URL); + return $this->generator->getLogoutUrl($key); } /** diff --git a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php index 648a6c8036d75..21214f81196ad 100644 --- a/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/ProfilerExtension.php @@ -12,16 +12,18 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Extension\ProfilerExtension as BaseProfilerExtension; +use Twig\Profiler\Profile; /** * @author Fabien Potencier */ -class ProfilerExtension extends \Twig_Extension_Profiler +class ProfilerExtension extends BaseProfilerExtension { private $stopwatch; private $events; - public function __construct(\Twig_Profiler_Profile $profile, Stopwatch $stopwatch = null) + public function __construct(Profile $profile, Stopwatch $stopwatch = null) { parent::__construct($profile); @@ -29,7 +31,7 @@ public function __construct(\Twig_Profiler_Profile $profile, Stopwatch $stopwatc $this->events = new \SplObjectStorage(); } - public function enter(\Twig_Profiler_Profile $profile) + public function enter(Profile $profile) { if ($this->stopwatch && $profile->isTemplate()) { $this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template'); @@ -38,7 +40,7 @@ public function enter(\Twig_Profiler_Profile $profile) parent::enter($profile); } - public function leave(\Twig_Profiler_Profile $profile) + public function leave(Profile $profile) { parent::leave($profile); diff --git a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php index 7469183e75de1..f347239d0aca1 100644 --- a/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/RoutingExtension.php @@ -12,13 +12,18 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Twig\Extension\AbstractExtension; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; +use Twig\TwigFunction; /** * Provides integration of the Routing component with Twig. * * @author Fabien Potencier */ -class RoutingExtension extends \Twig_Extension +class RoutingExtension extends AbstractExtension { private $generator; @@ -35,16 +40,30 @@ public function __construct(UrlGeneratorInterface $generator) public function getFunctions() { return array( - new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), - new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + new TwigFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + new TwigFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), ); } + /** + * @param string $name + * @param array $parameters + * @param bool $relative + * + * @return string + */ public function getPath($name, $parameters = array(), $relative = false) { return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); } + /** + * @param string $name + * @param array $parameters + * @param bool $schemeRelative + * + * @return string + */ public function getUrl($name, $parameters = array(), $schemeRelative = false) { return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); @@ -68,9 +87,11 @@ public function getUrl($name, $parameters = array(), $schemeRelative = false) * - path('route', {'param1': 'value1', 'param2': 'value2'}) * If param1 and param2 reference placeholder in the route, it would still be safe. But we don't know. * - * @param \Twig_Node $argsNode The arguments of the path/url function + * @param Node $argsNode The arguments of the path/url function * * @return array An array with the contexts the URL is safe + * + * To be made @final in 3.4, and the type-hint be changed to "\Twig\Node\Node" in 4.0. */ public function isUrlGenerationSafe(\Twig_Node $argsNode) { @@ -79,8 +100,8 @@ public function isUrlGenerationSafe(\Twig_Node $argsNode) $argsNode->hasNode(1) ? $argsNode->getNode(1) : null ); - if (null === $paramsNode || $paramsNode instanceof \Twig_Node_Expression_Array && count($paramsNode) <= 2 && - (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof \Twig_Node_Expression_Constant) + if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 && + (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) ) { return array('html'); } diff --git a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php index fa5fae2e77a01..193726a684371 100644 --- a/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/SecurityExtension.php @@ -14,13 +14,15 @@ use Symfony\Component\Security\Acl\Voter\FieldVote; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; /** * SecurityExtension exposes security context features. * * @author Fabien Potencier */ -class SecurityExtension extends \Twig_Extension +class SecurityExtension extends AbstractExtension { private $securityChecker; @@ -52,7 +54,7 @@ public function isGranted($role, $object = null, $field = null) public function getFunctions() { return array( - new \Twig_SimpleFunction('is_granted', array($this, 'isGranted')), + new TwigFunction('is_granted', array($this, 'isGranted')), ); } diff --git a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php index 52af92324c228..ffbfcf8845fe4 100644 --- a/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/StopwatchExtension.php @@ -11,21 +11,18 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser; +use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Extension\AbstractExtension; /** * Twig extension for the stopwatch helper. * * @author Wouter J */ -class StopwatchExtension extends \Twig_Extension +class StopwatchExtension extends AbstractExtension { private $stopwatch; - - /** - * @var bool - */ private $enabled; public function __construct(Stopwatch $stopwatch = null, $enabled = true) @@ -47,7 +44,7 @@ public function getTokenParsers() * Some stuff which will be recorded on the timeline * {% endstopwatch %} */ - new StopwatchTokenParser($this->stopwatch !== null && $this->enabled), + new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled), ); } diff --git a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php index f1f2fbd20b82e..d10b5ff2c6da1 100644 --- a/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/TranslationExtension.php @@ -11,24 +11,28 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Bridge\Twig\TokenParser\TransTokenParser; +use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; +use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser; use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; +use Symfony\Bridge\Twig\TokenParser\TransTokenParser; use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; +use Twig\Extension\AbstractExtension; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TokenParser\AbstractTokenParser; +use Twig\TwigFilter; /** * Provides integration of the Translation component with Twig. * * @author Fabien Potencier */ -class TranslationExtension extends \Twig_Extension +class TranslationExtension extends AbstractExtension { private $translator; private $translationNodeVisitor; - public function __construct(TranslatorInterface $translator, \Twig_NodeVisitorInterface $translationNodeVisitor = null) + public function __construct(TranslatorInterface $translator, NodeVisitorInterface $translationNodeVisitor = null) { if (!$translationNodeVisitor) { $translationNodeVisitor = new TranslationNodeVisitor(); @@ -49,15 +53,15 @@ public function getTranslator() public function getFilters() { return array( - new \Twig_SimpleFilter('trans', array($this, 'trans')), - new \Twig_SimpleFilter('transchoice', array($this, 'transchoice')), + new TwigFilter('trans', array($this, 'trans')), + new TwigFilter('transchoice', array($this, 'transchoice')), ); } /** * Returns the token parser instance to add to the existing list. * - * @return array An array of Twig_TokenParser instances + * @return AbstractTokenParser[] */ public function getTokenParsers() { diff --git a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php index fc9bf0e9e3308..cb0f9e0fd33e7 100644 --- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php @@ -12,13 +12,16 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Yaml\Dumper as YamlDumper; +use Symfony\Component\Yaml\Yaml; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; /** * Provides integration of the Yaml component with Twig. * * @author Fabien Potencier */ -class YamlExtension extends \Twig_Extension +class YamlExtension extends AbstractExtension { /** * {@inheritdoc} @@ -26,8 +29,8 @@ class YamlExtension extends \Twig_Extension public function getFilters() { return array( - new \Twig_SimpleFilter('yaml_encode', array($this, 'encode')), - new \Twig_SimpleFilter('yaml_dump', array($this, 'dump')), + new TwigFilter('yaml_encode', array($this, 'encode')), + new TwigFilter('yaml_dump', array($this, 'dump')), ); } @@ -39,17 +42,21 @@ public function encode($input, $inline = 0, $dumpObjects = false) $dumper = new YamlDumper(); } + if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { + return $dumper->dump($input, $inline, 0, \is_bool($dumpObjects) ? Yaml::DUMP_OBJECT : 0); + } + return $dumper->dump($input, $inline, 0, false, $dumpObjects); } public function dump($value, $inline = 0, $dumpObjects = false) { - if (is_resource($value)) { + if (\is_resource($value)) { return '%Resource%'; } - if (is_array($value) || is_object($value)) { - return '%'.gettype($value).'% '.$this->encode($value, $inline, $dumpObjects); + if (\is_array($value) || \is_object($value)) { + return '%'.\gettype($value).'% '.$this->encode($value, $inline, $dumpObjects); } return $this->encode($value, $inline, $dumpObjects); diff --git a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php index ac139e44a1331..9f4d7c56ea886 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php @@ -12,29 +12,33 @@ namespace Symfony\Bridge\Twig\Form; use Symfony\Component\Form\FormRenderer; +use Twig\Environment; /** * @author Bernhard Schussek */ class TwigRenderer extends FormRenderer implements TwigRendererInterface { - /** - * @var TwigRendererEngineInterface - */ - private $engine; - public function __construct(TwigRendererEngineInterface $engine, $csrfTokenManager = null) { parent::__construct($engine, $csrfTokenManager); + } - $this->engine = $engine; + /** + * Returns the engine used by this renderer. + * + * @return TwigRendererEngineInterface The renderer engine + */ + public function getEngine() + { + return parent::getEngine(); } /** * {@inheritdoc} */ - public function setEnvironment(\Twig_Environment $environment) + public function setEnvironment(Environment $environment) { - $this->engine->setEnvironment($environment); + $this->getEngine()->setEnvironment($environment); } } diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php index f9dfea3571790..74b2e51b76ad4 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngine.php @@ -13,6 +13,8 @@ use Symfony\Component\Form\AbstractRendererEngine; use Symfony\Component\Form\FormView; +use Twig\Environment; +use Twig\Template; /** * @author Bernhard Schussek @@ -20,19 +22,19 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface { /** - * @var \Twig_Environment + * @var Environment */ private $environment; /** - * @var \Twig_Template + * @var Template */ private $template; /** * {@inheritdoc} */ - public function setEnvironment(\Twig_Environment $environment) + public function setEnvironment(Environment $environment) { $this->environment = $environment; } @@ -70,11 +72,11 @@ public function renderBlock(FormView $view, $resource, $blockName, array $variab * * @see getResourceForBlock() * - * @param string $cacheKey The cache key of the form view. - * @param FormView $view The form view for finding the applying themes. - * @param string $blockName The name of the block to load. + * @param string $cacheKey The cache key of the form view + * @param FormView $view The form view for finding the applying themes + * @param string $blockName The name of the block to load * - * @return bool True if the resource could be loaded, false otherwise. + * @return bool True if the resource could be loaded, false otherwise */ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName) { @@ -98,7 +100,7 @@ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockNam // Check each theme whether it contains the searched block if (isset($this->themes[$cacheKey])) { - for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { + for ($i = \count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]); // CONTINUE LOADING (see doc comment) } @@ -106,7 +108,7 @@ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockNam // Check the default themes once we reach the root view without success if (!$view->parent) { - for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) { + for ($i = \count($this->defaultThemes) - 1; $i >= 0; --$i) { $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]); // CONTINUE LOADING (see doc comment) } @@ -141,7 +143,7 @@ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockNam /** * Loads the resources for all blocks in a theme. * - * @param string $cacheKey The cache key for storing the resource. + * @param string $cacheKey The cache key for storing the resource * @param mixed $theme The theme to load the block from. This parameter * is passed by reference, because it might be necessary * to initialize the theme first. Any changes made to @@ -150,13 +152,13 @@ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockNam */ protected function loadResourcesFromTheme($cacheKey, &$theme) { - if (!$theme instanceof \Twig_Template) { - /* @var \Twig_Template $theme */ + if (!$theme instanceof Template) { + /* @var Template $theme */ $theme = $this->environment->loadTemplate($theme); } if (null === $this->template) { - // Store the first \Twig_Template instance that we find so that + // Store the first Template instance that we find so that // we can call displayBlock() later on. It doesn't matter *which* // template we use for that, since we pass the used blocks manually // anyway. diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php b/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php index ef764a248f339..3e8a8e1f4678d 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererEngineInterface.php @@ -12,16 +12,15 @@ namespace Symfony\Bridge\Twig\Form; use Symfony\Component\Form\FormRendererEngineInterface; +use Twig\Environment; + +// BC/FC with namespaced Twig +class_exists('Twig\Environment'); /** * @author Bernhard Schussek */ interface TwigRendererEngineInterface extends FormRendererEngineInterface { - /** - * Sets Twig's environment. - * - * @param \Twig_Environment $environment - */ - public function setEnvironment(\Twig_Environment $environment); + public function setEnvironment(Environment $environment); } diff --git a/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php b/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php index 4682f520008ac..cb45c17e1afc2 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRendererInterface.php @@ -12,16 +12,15 @@ namespace Symfony\Bridge\Twig\Form; use Symfony\Component\Form\FormRendererInterface; +use Twig\Environment; + +// BC/FC with namespaced Twig +class_exists('Twig\Environment'); /** * @author Bernhard Schussek */ interface TwigRendererInterface extends FormRendererInterface { - /** - * Sets Twig's environment. - * - * @param \Twig_Environment $environment - */ - public function setEnvironment(\Twig_Environment $environment); + public function setEnvironment(Environment $environment); } diff --git a/src/Symfony/Bridge/Twig/LICENSE b/src/Symfony/Bridge/Twig/LICENSE index 43028bc600f26..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Twig/LICENSE +++ b/src/Symfony/Bridge/Twig/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index 522497ba655dc..d820d75cc7db9 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -11,37 +11,43 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Node; + /** * @author Julien Galenski */ -class DumpNode extends \Twig_Node +class DumpNode extends Node { private $varPrefix; - public function __construct($varPrefix, \Twig_Node $values = null, $lineno, $tag = null) + public function __construct($varPrefix, Node $values = null, $lineno, $tag = null) { - parent::__construct(array('values' => $values), array(), $lineno, $tag); + $nodes = array(); + if (null !== $values) { + $nodes['values'] = $values; + } + + parent::__construct($nodes, array(), $lineno, $tag); $this->varPrefix = $varPrefix; } /** * {@inheritdoc} */ - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { $compiler ->write("if (\$this->env->isDebug()) {\n") ->indent(); - $values = $this->getNode('values'); - - if (null === $values) { + if (!$this->hasNode('values')) { // remove embedded templates (macros) from the context $compiler ->write(sprintf('$%svars = array();'."\n", $this->varPrefix)) ->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) ->indent() - ->write(sprintf('if (!$%sval instanceof \Twig_Template) {'."\n", $this->varPrefix)) + ->write(sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) ->indent() ->write(sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) ->outdent() @@ -50,7 +56,7 @@ public function compile(\Twig_Compiler $compiler) ->write("}\n") ->addDebugInfo($this) ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); - } elseif (1 === $values->count()) { + } elseif (($values = $this->getNode('values')) && 1 === $values->count()) { $compiler ->addDebugInfo($this) ->write('\Symfony\Component\VarDumper\VarDumper::dump(') @@ -62,7 +68,7 @@ public function compile(\Twig_Compiler $compiler) ->write('\Symfony\Component\VarDumper\VarDumper::dump(array('."\n") ->indent(); foreach ($values as $node) { - $compiler->addIndentation(); + $compiler->write(''); if ($node->hasAttribute('name')) { $compiler ->string($node->getAttribute('name')) diff --git a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php index e598ae104a728..b2eb100d5bfdc 100644 --- a/src/Symfony/Bridge/Twig/Node/FormThemeNode.php +++ b/src/Symfony/Bridge/Twig/Node/FormThemeNode.php @@ -11,26 +11,24 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Node; + /** * @author Fabien Potencier */ -class FormThemeNode extends \Twig_Node +class FormThemeNode extends Node { - public function __construct(\Twig_Node $form, \Twig_Node $resources, $lineno, $tag = null) + public function __construct(Node $form, Node $resources, $lineno, $tag = null) { parent::__construct(array('form' => $form, 'resources' => $resources), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param \Twig_Compiler $compiler A Twig_Compiler instance - */ - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { $compiler ->addDebugInfo($this) - ->write('$this->env->getExtension(\'form\')->renderer->setTheme(') + ->write('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->setTheme(') ->subcompile($this->getNode('form')) ->raw(', ') ->subcompile($this->getNode('resources')) diff --git a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php index 35dcb40b627cb..4a06907b0b43a 100644 --- a/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php +++ b/src/Symfony/Bridge/Twig/Node/RenderBlockNode.php @@ -11,6 +11,9 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Expression\FunctionExpression; + /** * Compiles a call to {@link \Symfony\Component\Form\FormRendererInterface::renderBlock()}. * @@ -19,13 +22,13 @@ * * @author Bernhard Schussek */ -class RenderBlockNode extends \Twig_Node_Expression_Function +class RenderBlockNode extends FunctionExpression { - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { $compiler->addDebugInfo($this); $arguments = iterator_to_array($this->getNode('arguments')); - $compiler->write('$this->env->getExtension(\'form\')->renderer->renderBlock('); + $compiler->write('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->renderBlock('); if (isset($arguments[0])) { $compiler->subcompile($arguments[0]); diff --git a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php index 93bba43b4c945..7c95199953b2f 100644 --- a/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php +++ b/src/Symfony/Bridge/Twig/Node/SearchAndRenderBlockNode.php @@ -11,15 +11,20 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FunctionExpression; + /** * @author Bernhard Schussek */ -class SearchAndRenderBlockNode extends \Twig_Node_Expression_Function +class SearchAndRenderBlockNode extends FunctionExpression { - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { $compiler->addDebugInfo($this); - $compiler->raw('$this->env->getExtension(\'form\')->renderer->searchAndRenderBlock('); + $compiler->raw('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->searchAndRenderBlock('); preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches); @@ -37,9 +42,9 @@ public function compile(\Twig_Compiler $compiler) // the variables in the third argument $label = $arguments[1]; $variables = isset($arguments[2]) ? $arguments[2] : null; - $lineno = $label->getLine(); + $lineno = $label->getTemplateLine(); - if ($label instanceof \Twig_Node_Expression_Constant) { + if ($label instanceof ConstantExpression) { // If the label argument is given as a constant, we can either // strip it away if it is empty, or integrate it into the array // of variables at compile time. @@ -48,8 +53,8 @@ public function compile(\Twig_Compiler $compiler) // Only insert the label into the array if it is not empty if (!twig_test_empty($label->getAttribute('value'))) { $originalVariables = $variables; - $variables = new \Twig_Node_Expression_Array(array(), $lineno); - $labelKey = new \Twig_Node_Expression_Constant('label', $lineno); + $variables = new ArrayExpression(array(), $lineno); + $labelKey = new ConstantExpression('label', $lineno); if (null !== $originalVariables) { foreach ($originalVariables->getKeyValuePairs() as $pair) { diff --git a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php index 06eeb492728c6..fac770c2499ba 100644 --- a/src/Symfony/Bridge/Twig/Node/StopwatchNode.php +++ b/src/Symfony/Bridge/Twig/Node/StopwatchNode.php @@ -11,19 +11,23 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Node; + /** * Represents a stopwatch node. * * @author Wouter J */ -class StopwatchNode extends \Twig_Node +class StopwatchNode extends Node { - public function __construct(\Twig_Node $name, $body, \Twig_Node_Expression_AssignName $var, $lineno = 0, $tag = null) + public function __construct(Node $name, Node $body, AssignNameExpression $var, $lineno = 0, $tag = null) { parent::__construct(array('body' => $body, 'name' => $name, 'var' => $var), array(), $lineno, $tag); } - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { $compiler ->addDebugInfo($this) @@ -32,11 +36,11 @@ public function compile(\Twig_Compiler $compiler) ->raw(' = ') ->subcompile($this->getNode('name')) ->write(";\n") - ->write("\$this->env->getExtension('stopwatch')->getStopwatch()->start(") + ->write("\$this->env->getExtension('Symfony\Bridge\Twig\Extension\StopwatchExtension')->getStopwatch()->start(") ->subcompile($this->getNode('var')) ->raw(", 'template');\n") ->subcompile($this->getNode('body')) - ->write("\$this->env->getExtension('stopwatch')->getStopwatch()->stop(") + ->write("\$this->env->getExtension('Symfony\Bridge\Twig\Extension\StopwatchExtension')->getStopwatch()->stop(") ->subcompile($this->getNode('var')) ->raw(");\n") ; diff --git a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php index adee71ffc5dc4..c9c82b33e541c 100644 --- a/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransDefaultDomainNode.php @@ -11,22 +11,21 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; + /** * @author Fabien Potencier */ -class TransDefaultDomainNode extends \Twig_Node +class TransDefaultDomainNode extends Node { - public function __construct(\Twig_Node_Expression $expr, $lineno = 0, $tag = null) + public function __construct(AbstractExpression $expr, $lineno = 0, $tag = null) { parent::__construct(array('expr' => $expr), array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param \Twig_Compiler $compiler A Twig_Compiler instance - */ - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { // noop as this node is just a marker for TranslationDefaultDomainNodeVisitor } diff --git a/src/Symfony/Bridge/Twig/Node/TransNode.php b/src/Symfony/Bridge/Twig/Node/TransNode.php index af85d8f1858ec..020810f7b7c55 100644 --- a/src/Symfony/Bridge/Twig/Node/TransNode.php +++ b/src/Symfony/Bridge/Twig/Node/TransNode.php @@ -11,43 +11,62 @@ namespace Symfony\Bridge\Twig\Node; +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Node; +use Twig\Node\TextNode; + +// BC/FC with namespaced Twig +class_exists('Twig\Node\Expression\ArrayExpression'); + /** * @author Fabien Potencier */ -class TransNode extends \Twig_Node +class TransNode extends Node { - public function __construct(\Twig_Node $body, \Twig_Node $domain = null, \Twig_Node_Expression $count = null, \Twig_Node_Expression $vars = null, \Twig_Node_Expression $locale = null, $lineno = 0, $tag = null) + public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, $lineno = 0, $tag = null) { - parent::__construct(array('count' => $count, 'body' => $body, 'domain' => $domain, 'vars' => $vars, 'locale' => $locale), array(), $lineno, $tag); + $nodes = array('body' => $body); + if (null !== $domain) { + $nodes['domain'] = $domain; + } + if (null !== $count) { + $nodes['count'] = $count; + } + if (null !== $vars) { + $nodes['vars'] = $vars; + } + if (null !== $locale) { + $nodes['locale'] = $locale; + } + + parent::__construct($nodes, array(), $lineno, $tag); } - /** - * Compiles the node to PHP. - * - * @param \Twig_Compiler $compiler A Twig_Compiler instance - */ - public function compile(\Twig_Compiler $compiler) + public function compile(Compiler $compiler) { $compiler->addDebugInfo($this); - $vars = $this->getNode('vars'); - $defaults = new \Twig_Node_Expression_Array(array(), -1); - if ($vars instanceof \Twig_Node_Expression_Array) { + $defaults = new ArrayExpression(array(), -1); + if ($this->hasNode('vars') && ($vars = $this->getNode('vars')) instanceof ArrayExpression) { $defaults = $this->getNode('vars'); $vars = null; } list($msg, $defaults) = $this->compileString($this->getNode('body'), $defaults, (bool) $vars); - $method = null === $this->getNode('count') ? 'trans' : 'transChoice'; + $method = !$this->hasNode('count') ? 'trans' : 'transChoice'; $compiler - ->write('echo $this->env->getExtension(\'translator\')->getTranslator()->'.$method.'(') + ->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->getTranslator()->'.$method.'(') ->subcompile($msg) ; $compiler->raw(', '); - if (null !== $this->getNode('count')) { + if ($this->hasNode('count')) { $compiler ->subcompile($this->getNode('count')) ->raw(', ') @@ -68,13 +87,13 @@ public function compile(\Twig_Compiler $compiler) $compiler->raw(', '); - if (null === $this->getNode('domain')) { + if (!$this->hasNode('domain')) { $compiler->repr('messages'); } else { $compiler->subcompile($this->getNode('domain')); } - if (null !== $this->getNode('locale')) { + if ($this->hasNode('locale')) { $compiler ->raw(', ') ->subcompile($this->getNode('locale')) @@ -83,11 +102,11 @@ public function compile(\Twig_Compiler $compiler) $compiler->raw(");\n"); } - protected function compileString(\Twig_Node $body, \Twig_Node_Expression_Array $vars, $ignoreStrictCheck = false) + protected function compileString(Node $body, ArrayExpression $vars, $ignoreStrictCheck = false) { - if ($body instanceof \Twig_Node_Expression_Constant) { + if ($body instanceof ConstantExpression) { $msg = $body->getAttribute('value'); - } elseif ($body instanceof \Twig_Node_Text) { + } elseif ($body instanceof TextNode) { $msg = $body->getAttribute('data'); } else { return array($body, $vars); @@ -96,18 +115,18 @@ protected function compileString(\Twig_Node $body, \Twig_Node_Expression_Array $ preg_match_all('/(?getLine()); + $key = new ConstantExpression('%'.$var.'%', $body->getTemplateLine()); if (!$vars->hasElement($key)) { - if ('count' === $var && null !== $this->getNode('count')) { + if ('count' === $var && $this->hasNode('count')) { $vars->addElement($this->getNode('count'), $key); } else { - $varExpr = new \Twig_Node_Expression_Name($var, $body->getLine()); + $varExpr = new NameExpression($var, $body->getTemplateLine()); $varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck); $vars->addElement($varExpr, $key); } } } - return array(new \Twig_Node_Expression_Constant(str_replace('%%', '%', trim($msg)), $body->getLine()), $vars); + return array(new ConstantExpression(str_replace('%%', '%', trim($msg)), $body->getTemplateLine()), $vars); } } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php index 1712bd5afee8e..1c3451bbebf46 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/Scope.php @@ -16,24 +16,10 @@ */ class Scope { - /** - * @var Scope|null - */ private $parent; - - /** - * @var array - */ private $data = array(); - - /** - * @var bool - */ private $left = false; - /** - * @param Scope $parent - */ public function __construct(Scope $parent = null) { $this->parent = $parent; @@ -42,7 +28,7 @@ public function __construct(Scope $parent = null) /** * Opens a new child scope. * - * @return Scope + * @return self */ public function enter() { @@ -52,7 +38,7 @@ public function enter() /** * Closes current scope and returns parent one. * - * @return Scope|null + * @return self|null */ public function leave() { @@ -67,9 +53,9 @@ public function leave() * @param string $key * @param mixed $value * - * @throws \LogicException + * @return $this * - * @return Scope Current scope + * @throws \LogicException */ public function set($key, $value) { diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index c923df9944c60..6a34a037e48f2 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -11,24 +11,27 @@ namespace Symfony\Bridge\Twig\NodeVisitor; -use Symfony\Bridge\Twig\Node\TransNode; use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Environment; +use Twig\Node\BlockNode; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Node\SetNode; +use Twig\NodeVisitor\AbstractNodeVisitor; /** - * TranslationDefaultDomainNodeVisitor. - * * @author Fabien Potencier */ -class TranslationDefaultDomainNodeVisitor extends \Twig_BaseNodeVisitor +class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor { - /** - * @var Scope - */ private $scope; - /** - * Constructor. - */ public function __construct() { $this->scope = new Scope(); @@ -37,23 +40,23 @@ public function __construct() /** * {@inheritdoc} */ - protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) + protected function doEnterNode(Node $node, Environment $env) { - if ($node instanceof \Twig_Node_Block || $node instanceof \Twig_Node_Module) { + if ($node instanceof BlockNode || $node instanceof ModuleNode) { $this->scope = $this->scope->enter(); } if ($node instanceof TransDefaultDomainNode) { - if ($node->getNode('expr') instanceof \Twig_Node_Expression_Constant) { + if ($node->getNode('expr') instanceof ConstantExpression) { $this->scope->set('domain', $node->getNode('expr')); return $node; } else { - $var = $env->getParser()->getVarName(); - $name = new \Twig_Node_Expression_AssignName($var, $node->getLine()); - $this->scope->set('domain', new \Twig_Node_Expression_Name($var, $node->getLine())); + $var = $this->getVarName(); + $name = new AssignNameExpression($var, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); - return new \Twig_Node_Set(false, new \Twig_Node(array($name)), new \Twig_Node(array($node->getNode('expr'))), $node->getLine()); + return new SetNode(false, new Node(array($name)), new Node(array($node->getNode('expr'))), $node->getTemplateLine()); } } @@ -61,7 +64,7 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) return $node; } - if ($node instanceof \Twig_Node_Expression_Filter && in_array($node->getNode('filter')->getAttribute('value'), array('trans', 'transchoice'))) { + if ($node instanceof FilterExpression && \in_array($node->getNode('filter')->getAttribute('value'), array('trans', 'transchoice'))) { $arguments = $node->getNode('arguments'); $ind = 'trans' === $node->getNode('filter')->getAttribute('value') ? 1 : 2; if ($this->isNamedArguments($arguments)) { @@ -71,14 +74,14 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) } else { if (!$arguments->hasNode($ind)) { if (!$arguments->hasNode($ind - 1)) { - $arguments->setNode($ind - 1, new \Twig_Node_Expression_Array(array(), $node->getLine())); + $arguments->setNode($ind - 1, new ArrayExpression(array(), $node->getTemplateLine())); } $arguments->setNode($ind, $this->scope->get('domain')); } } } elseif ($node instanceof TransNode) { - if (null === $node->getNode('domain')) { + if (!$node->hasNode('domain')) { $node->setNode('domain', $this->scope->get('domain')); } } @@ -89,13 +92,13 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) /** * {@inheritdoc} */ - protected function doLeaveNode(\Twig_Node $node, \Twig_Environment $env) + protected function doLeaveNode(Node $node, Environment $env) { if ($node instanceof TransDefaultDomainNode) { return false; } - if ($node instanceof \Twig_Node_Block || $node instanceof \Twig_Node_Module) { + if ($node instanceof BlockNode || $node instanceof ModuleNode) { $this->scope = $this->scope->leave(); } @@ -116,11 +119,16 @@ public function getPriority() private function isNamedArguments($arguments) { foreach ($arguments as $name => $node) { - if (!is_int($name)) { + if (!\is_int($name)) { return true; } } return false; } + + private function getVarName() + { + return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + } } diff --git a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php index 4426a9d3b6666..1fbce9c6af811 100644 --- a/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php +++ b/src/Symfony/Bridge/Twig/NodeVisitor/TranslationNodeVisitor.php @@ -12,13 +12,18 @@ namespace Symfony\Bridge\Twig\NodeVisitor; use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Environment; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Node; +use Twig\NodeVisitor\AbstractNodeVisitor; /** * TranslationNodeVisitor extracts translation messages. * * @author Fabien Potencier */ -class TranslationNodeVisitor extends \Twig_BaseNodeVisitor +class TranslationNodeVisitor extends AbstractNodeVisitor { const UNDEFINED_DOMAIN = '_undefined'; @@ -45,16 +50,16 @@ public function getMessages() /** * {@inheritdoc} */ - protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) + protected function doEnterNode(Node $node, Environment $env) { if (!$this->enabled) { return $node; } if ( - $node instanceof \Twig_Node_Expression_Filter && + $node instanceof FilterExpression && 'trans' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof \Twig_Node_Expression_Constant + $node->getNode('node') instanceof ConstantExpression ) { // extract constant nodes with a trans filter $this->messages[] = array( @@ -62,9 +67,9 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) $this->getReadDomainFromArguments($node->getNode('arguments'), 1), ); } elseif ( - $node instanceof \Twig_Node_Expression_Filter && + $node instanceof FilterExpression && 'transchoice' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof \Twig_Node_Expression_Constant + $node->getNode('node') instanceof ConstantExpression ) { // extract constant nodes with a trans filter $this->messages[] = array( @@ -75,7 +80,7 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) // extract trans nodes $this->messages[] = array( $node->getNode('body')->getAttribute('data'), - $this->getReadDomainFromNode($node->getNode('domain')), + $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, ); } @@ -85,7 +90,7 @@ protected function doEnterNode(\Twig_Node $node, \Twig_Environment $env) /** * {@inheritdoc} */ - protected function doLeaveNode(\Twig_Node $node, \Twig_Environment $env) + protected function doLeaveNode(Node $node, Environment $env) { return $node; } @@ -99,12 +104,12 @@ public function getPriority() } /** - * @param \Twig_Node $arguments - * @param int $index + * @param Node $arguments + * @param int $index * * @return string|null */ - private function getReadDomainFromArguments(\Twig_Node $arguments, $index) + private function getReadDomainFromArguments(Node $arguments, $index) { if ($arguments->hasNode('domain')) { $argument = $arguments->getNode('domain'); @@ -118,17 +123,11 @@ private function getReadDomainFromArguments(\Twig_Node $arguments, $index) } /** - * @param \Twig_Node $node - * * @return string|null */ - private function getReadDomainFromNode(\Twig_Node $node = null) + private function getReadDomainFromNode(Node $node) { - if (null === $node) { - return; - } - - if ($node instanceof \Twig_Node_Expression_Constant) { + if ($node instanceof ConstantExpression) { return $node->getAttribute('value'); } diff --git a/src/Symfony/Bridge/Twig/README.md b/src/Symfony/Bridge/Twig/README.md index 1f92af944f1f1..602f5a54c3dd6 100644 --- a/src/Symfony/Bridge/Twig/README.md +++ b/src/Symfony/Bridge/Twig/README.md @@ -1,15 +1,13 @@ Twig Bridge =========== -Provides integration for [Twig](http://twig.sensiolabs.org/) with various +Provides integration for [Twig](https://twig.symfony.com/) with various Symfony components. Resources --------- -If you want to run the unit tests, install dev dependencies before -running PHPUnit: - - $ cd path/to/Symfony/Bridge/Twig/ - $ composer install - $ phpunit + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig index 767e2798f3ee5..5de20b1b8f181 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -25,15 +25,13 @@ col-sm-2 {# Rows #} {% block form_row -%} -{% spaceless %}
- {{ form_label(form) }} + {{- form_label(form) -}}
- {{ form_widget(form) }} - {{ form_errors(form) }} + {{- form_widget(form) -}} + {{- form_errors(form) -}}
-
-{% endspaceless %} +{##} {%- endblock form_row %} {% block checkbox_row -%} @@ -67,6 +65,17 @@ col-sm-2 {% endspaceless %} {% endblock submit_row %} +{% block reset_row -%} +{% spaceless %} +
+
+
+ {{ form_widget(form) }} +
+
+{% endspaceless %} +{% endblock reset_row %} + {% block form_group_class -%} col-sm-10 {%- endblock form_group_class %} 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 165236ef613ec..215f0ce754531 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 @@ -3,7 +3,7 @@ {# Widgets #} {% block form_widget_simple -%} - {% if type is not defined or 'file' != type %} + {% if type is not defined or type not in ['file', 'hidden'] %} {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} {% endif %} {{- parent() -}} @@ -20,16 +20,21 @@ {%- endblock %} {% block money_widget -%} -
- {% set prepend = '{{' == money_pattern[0:2] %} - {% if not prepend %} - {{ money_pattern|replace({ '{{ widget }}':''}) }} - {% endif %} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
+ {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
+ {% else %} {{- block('form_widget_simple') -}} - {% if prepend %} - {{ money_pattern|replace({ '{{ widget }}':''}) }} - {% endif %} -
+ {% endif %} {%- endblock money_widget %} {% block percent_widget -%} @@ -94,14 +99,12 @@ {% block choice_widget_expanded -%} {% if '-inline' in label_attr.class|default('') -%} -
- {%- for child in form %} - {{- form_widget(child, { - parent_label_class: label_attr.class|default(''), - translation_domain: choice_translation_domain, - }) -}} - {% endfor -%} -
+ {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {% endfor -%} {%- else -%}
{%- for child in form %} @@ -115,7 +118,7 @@ {%- endblock choice_widget_expanded %} {% block checkbox_widget -%} - {% set parent_label_class = parent_label_class|default('') -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} {% if 'checkbox-inline' in parent_label_class %} {{- form_label(form, null, { widget: parent() }) -}} {% else -%} @@ -126,7 +129,7 @@ {%- endblock checkbox_widget %} {% block radio_widget -%} - {%- set parent_label_class = parent_label_class|default('') -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} {% if 'radio-inline' in parent_label_class %} {{- form_label(form, null, { widget: parent() }) -}} {% else -%} @@ -150,10 +153,14 @@ {% endblock %} {% block checkbox_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + {{- block('checkbox_radio_label') -}} {%- endblock checkbox_label %} {% block radio_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + {{- block('checkbox_radio_label') -}} {%- endblock radio_label %} @@ -167,7 +174,14 @@ {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) %} {% endif %} {% if label is not same as(false) and label is empty %} - {% set label = name|humanize %} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} {% endif %} {{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}} @@ -229,12 +243,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 89f9910890879..e424998d2bd32 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') -}} @@ -52,12 +52,12 @@ {%- endblock choice_widget_expanded -%} {%- block choice_widget_collapsed -%} - {%- if required and placeholder is none and not placeholder_in_choices and not multiple -%} + {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%} {% set required = false %} {%- endif -%} +
{%- if form_method != method -%} {%- endif -%} @@ -306,7 +312,21 @@ {% if not child.rendered %} {{- form_row(child) -}} {% endif %} - {%- endfor %} + {%- endfor -%} + + {% if not form.methodRendered and form is rootform %} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} + {% endif -%} {% endblock form_rest %} {# Support #} @@ -319,12 +339,13 @@ {%- block widget_attributes -%} id="{{ id }}" name="{{ full_name }}" + {%- if read_only %} readonly="readonly"{% endif -%} {%- if disabled %} disabled="disabled"{% endif -%} {%- if required %} required="required"{% endif -%} - {%- for attrname, attrvalue in attr -%} + {%- for attrname, attrvalue in attr if 'readonly' != attrname or not read_only -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} @@ -338,7 +359,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} @@ -352,7 +373,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} @@ -365,7 +386,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} 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 6e2b106082350..dc7bec9fb6ccd 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 @@ -150,7 +150,7 @@ {%- endif -%}