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-auth.json b/.composer-auth.json deleted file mode 100644 index bf40540cc644c..0000000000000 --- a/.composer-auth.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "github-oauth": { - "github.com": "PLEASE DO NOT USE THIS TOKEN IN YOUR OWN PROJECTS/FORKS", - "github.com": "This token is reserved for testing the symfony/symfony repository", - "github.com": "52270bad1071a099c8d24629f2db2b7f07db960d" - } -} 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 index b03e61ef338c1..b6f39741d9dbc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,20 @@ | Q | A | ------------- | --- -| Branch | master for features and deprecations / lowest applicable and maintained version otherwise +| Branch? | master for features / 2.8 up to 4.1 for bug fixes | Bug fix? | yes/no -| New feature? | yes/no -| BC breaks? | yes/no -| Deprecations? | yes/no -| Tests pass? | yes/no -| Fixed tickets | comma-separated list of tickets fixed by the PR, if any +| New feature? | yes/no +| BC breaks? | no +| Deprecations? | yes/no +| Tests pass? | yes +| Fixed tickets | #... | License | MIT -| Doc PR | reference to the documentation PR, if any +| 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 290e9bf5fbcba..0000000000000 --- a/.php_cs +++ /dev/null @@ -1,30 +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') - // explicit heredoc test - ->notPath('src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.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 511e05008a335..0000000000000 --- a/.travis.php +++ /dev/null @@ -1,50 +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 = 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://'.__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 6d6101dccaf8d..34ef0b343e0d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: php -sudo: false +dist: trusty git: - depth: 1 + depth: 2 addons: apt_packages: @@ -12,17 +12,19 @@ addons: env: global: - - MIN_PHP=5.3.9 + - 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.0 + - php: 7.2 env: deps=low fast_finish: true @@ -30,47 +32,218 @@ cache: directories: - .phpunit - php-$MIN_PHP + - ~/php-ext services: mongodb before_install: - # Matrix lines for intermediate PHP versions are skipped for pull requests - - if [[ ! $deps && ! $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} && $TRAVIS_PHP_VERSION != hhvm && $TRAVIS_PULL_REQUEST != false ]]; then deps=skip; fi; - # A sigchild-enabled-PHP is used 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; - - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi; - - echo memory_limit = -1 >> $INI_FILE - - echo session.gc_probability = 0 >> $INI_FILE - - if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then echo extension = mongo.so >> $INI_FILE; fi; - - if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then echo extension = memcache.so >> $INI_FILE; fi; - - if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.10 && echo apc.enable_cli = 1 >> $INI_FILE); fi; - - if [[ $TRAVIS_PHP_VERSION = 7.* ]]; then (echo yes | pecl install -f apcu-5.1.2 && echo apc.enable_cli = 1 >> $INI_FILE); fi; - - if [[ $TRAVIS_PHP_VERSION = 5.* && ! $deps ]]; then (cd src/Symfony/Component/Debug/Resources/ext && phpize && ./configure && make && echo extension = $(pwd)/modules/symfony_debug.so >> $INI_FILE); fi; - - if [[ $TRAVIS_PHP_VERSION = 5.* ]]; then pecl install -f memcached-2.1.0; fi; - - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then echo extension = ldap.so >> $INI_FILE; fi; - - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then phpenv config-rm xdebug.ini; fi; - - if [[ $TRAVIS_REPO_SLUG = symfony/symfony ]]; then cp .composer-auth.json ~/.composer/auth.json; fi; - - if [[ $deps != skip ]]; then composer self-update; 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 [[ $deps != skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; - # Create local composer packages for each patched components and reference them in composer.json files when cross-testing components - - if [[ $deps != skip && $deps ]]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; 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/.//); else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*'); fi; - - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then 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'); fi; - # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one - - if [[ $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) ]]; then LEGACY=,legacy; fi; - - export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev; - - if [[ ! $deps ]]; then composer update --prefer-dist; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi; - - if [[ $TRAVIS_PHP_VERSION != hhvm ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; 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 ]]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi; - - if [[ ! $deps ]]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi; - - if [[ ! $deps && $TRAVIS_PHP_VERSION = ${MIN_PHP%.*} ]]; then echo -e "1\\n0" | xargs -I{} sh -c 'echo "\\nPHP --enable-sigchild enhanced={}" && ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/'; fi; - - if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --prefer-dist; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY; fi; - - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --prefer-dist --prefer-lowest --prefer-stable; $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 c8daee4e9253b..2758f011f3cfd 100644 --- a/CHANGELOG-2.3.md +++ b/CHANGELOG-2.3.md @@ -7,6 +7,127 @@ 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) diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index c172caf540de5..a209a917388a5 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,818 @@ 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) diff --git a/CHANGELOG-2.8.md b/CHANGELOG-2.8.md index 38abdc97b27f1..aa13e1f9f1b12 100644 --- a/CHANGELOG-2.8.md +++ b/CHANGELOG-2.8.md @@ -7,6 +7,972 @@ 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) 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 ae18925cb6e20..7902d9aff3a77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ Symfony is an open source, community-driven project. If you'd like to contribute, please read the following documents: +* [Reviewing issues/pull requests][0] * [Reporting a Bug][1] * [Submitting a Patch][2] * [Symfony Core Team][3] @@ -14,6 +15,7 @@ If you'd like to contribute, please read the following documents: * [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 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5786f19dde7d6..1c329f2146c3e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -5,256 +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) - - Christian Flothmann (xabbuh) - - Jakub Zalas (jakubzalas) - 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) - - Javier Eguiluz (javier.eguiluz) - - Grégoire Pineau (lyrixx) - - 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) - - Maxime Steinhausser (ogizanagi) - - 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 - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Gábor Egyed (1ed) - - Christian Raue - - Michel Weimerskirch (mweimerskirch) - - Arnout Boks (aboks) - - Kevin Bond (kbond) - Douglas Greenshields (shieldo) + - Dariusz Ruminski + - Grégoire Paris (greg0ire) - Lee McDermott - Brandon Turner - Luis Cordova (cordoval) + - Graham Campbell (graham) - Daniel Holmes (dholmes) + - Toni Uebernickel (havvg) - Bart van den Burg (burgov) - Jordan Alliot (jalliot) - - Matthias Pigulla (mpdude) + - Jérôme Tamarelle (gromnan) - John Wards (johnwards) - Fran Moreno (franmomu) + - Valentin Udaltsov (vudaltsov) - Antoine Hérault (herzult) - - Toni Uebernickel (havvg) + - Paráda József (paradajozsef) - Arnaud Le Blanc (arnaud-lb) + - Maxime STEINHAUSSER + - Michal Piotrowski (eventhorizon) + - gadelat (gadelat) - Tim Nagel (merk) - Brice BERNARD (brikou) - - Graham Campbell (graham) - - Jérôme Tamarelle (gromnan) + - Baptiste Clavié (talus) - marc.weistroff - - Michal Piotrowski (eventhorizon) + - David Buchmann (dbu) - lenar + - Alexander Schwenn (xelaris) - Włodzimierz Gajda (gajdaw) + - Tomáš Votruba (tomas_votruba) + - Peter Kokot (maastermedia) + - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - - Peter Rehm (rpet) - Colin Frei - - Dariusz Ruminski - Adrien Brault (adrienbrault) + - Joshua Thijssen - excelwebzone - - Jacob Dreesen (jdreesen) - - Peter Kokot (maastermedia) - - Fabien Pennequin (fabienpennequin) - - Pierre du Plessis (pierredup) - - Alexander Schwenn (xelaris) - Gordon Franke (gimler) - - Iltar van der Berg (kjarli) + - 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) - - Joshua Thijssen + - Florian Lonqueu-Brochard (florianlb) + - Chris Wilkinson (thewilkybarkid) - Stefano Sala (stefano.sala) - - David Buchmann (dbu) - - Issei Murasawa (issei_m) + - 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) - - Vladimir Reznichenko (kalessil) + - Arnaud Kleinpeter (nanocom) + - Jannik Zschiesche (apfelbox) - Guilherme Blanco (guilhermeblanco) - Pablo Godel (pgodel) - - Tigran Azatyan (tigranazatyan) - Jérémie Augustin (jaugustin) - - Sebastiaan Stok (sstok) + - Andréia Bohner (andreia) + - Philipp Wahala (hifi) + - Julien Falque (julienfalque) - Rafael Dohms (rdohms) - - Arnaud Kleinpeter (nanocom) - - Alexander M. Turek (derrabus) + - jwdeitch + - Teoh Han Hui (teohhanhui) + - Mikael Pajunen + - Joel Wurtz (brouznouf) + - Oleg Voronkovich + - Vyacheslav Pavlov + - Richard van Laak (rvanlaak) - Richard Shank (iampersistent) - - Charles Sarrazin (csarrazi) + - Thomas Rabaix (rande) + - Rouven Weßling (realityking) - Clemens Tolboom - Helmer Aaviksoo - - Baptiste Clavié (talus) - - Tugdual Saunier (tucksaun) - - Andréia Bohner (andreia) - 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) + - 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) - - Thomas Rabaix (rande) - Dennis Benkert (denderello) - - 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) - - Evgeniy (ewgraf) + - 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) - - Jáchym Toušek + - 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) - - Lars Strojny (lstrojny) - - Daniel Wehner - Rui Marinho (ruimarinho) + - Eugene Wissner + - Pascal Montoya - Julien Brochet (mewt) - - Sergey Linnik (linniksa) + - 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) - - Florian Lonqueu-Brochard (florianlb) + - 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) - - Diego Saint Esteben (dosten) + - 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) - - Tomáš Votruba (tomas_votruba) - 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) - - Marcos Sánchez + - Roumen Damianoff (roumen) + - Antonio J. García Lagar (ajgarlag) - Kim Hemsø Rasmussen (kimhemsoe) - - 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) - - Blanchon Vincent (blanchonvincent) - - Dawid Nowak - - Kristen Gilden (kgilden) + - Christian Schmidt + - MatTheCat + - Chad Sikorra (chadsikorra) - Chris Smith (cs278) - - Richard van Laak (rvanlaak) - 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) - - Titouan Galopin (tgalopin) - 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 - - Jannik Zschiesche (apfelbox) - 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) - - Antonio J. García Lagar (ajgarlag) - - Roumen Damianoff (roumen) + - Magnus Nordlander (magnusnordlander) + - Thomas Royer (cydonia7) - alquerci - Francesco Levorato - Vitaliy Zakharov (zakharovvi) @@ -263,74 +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) + - Artur Eshenbrener + - Damien Alexandre (damienalexandre) + - Thomas Perez (scullwm) - Felix Labrecque - Yaroslav Kiliba - - Stepan Anchugov (kix) - Terje Bråten + - Mathieu Lechat - Robbert Klarenbeek (robbertkl) + - 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) - - Filippo Tessarotto - Greg Thornton (xdissent) - - jeremyFreeAgent (jeremyfreeagent) - 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 - - Diego Agulló (aeoris) + - 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) - 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 - - Mark Challoner (markchalloner) - - Gregor Harlan (gharlan) - Markus Bachmann (baachi) - lancergr + - Zan Baldwin + - Mihai Stancu + - Ivan Nikolaev (destillat) - Olivier Dolbeau (odolbeau) - - Ben Davies (bendavies) + - 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) - - Artur Melo (restless) + - Andreas Braun + - Boris Vujicic (boris.vujicic) - Chris Sedlmayr (catchamonkey) + - Mateusz Sip (mateusz_sip) + - Kamil Kokot (pamil) - Seb Koelen - Christoph Mewes (xrstf) - Vitaliy Tverdokhlib (vitaliytv) @@ -338,378 +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 - - Jakub Kucharovic (jkucharovic) + - 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 - - Ener-Getick (energetick) + - Sergio Santoro + - Robin van der Vleuten (robinvdvleuten) - Philipp Rieber (bicpi) - Manuel de Ruiter (manuel) - Eduardo Oliveira (entering) - - Eugene Wissner + - 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 - - Tristan Darricau (nicofuma) + - Arjen van der Meijden + - Adam Szaraniec (mimol) + - Dariusz Ruminski - Erik Trapman (eriktrapman) - De Cock Xavier (xdecock) - - Possum + - 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) - - Leo Feyer - - MatTheCat - - John Bafford (jbafford) - Denis Gorbachev (starfall) + - 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) - - Loïc Faugeron - 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 + - 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 - - Michael Hirschler (mvhirsch) + - 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 + - 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) - - 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 - - Hidde Wieringa + - flip111 + - Greg Anderson + - VJ + - Delf Tonder (leberknecht) - Mark Sonnabaum - - Alexander Obuhovich (aik099) + - Massimiliano Braglia (massimilianobraglia) + - Richard Quadling - jochenvdv - 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) - - Martin Hujer (martinhujer) + - 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 - - Gladhon - Venu - Lars Vierbergen + - 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ś - - Ilya Antipenko (aivus) - - 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 + - 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) - - 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) + - Ruud Kamphuis (ruudk) - Aleksey Podskrebyshev - - Steffen Roßkamp + - Calin Mihai Pristavu - David Marín Carreño (davefx) - - Hidde Boomsma (hboomsma) + - Fabien LUCAS (flucas2) - Jörn Lang (j.lang) + - 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) + - 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 - - Sébastien Santoro + - 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) - fabios - - Jérôme Vasseur - Sander Coolen (scoolen) - Nicolas Le Goff (nlegoff) - - Anne-Sophie Bachelard (annesophie) + - Ben Oman + - Guilhem N (guilhemn) + - Chris de Kok + - Andreas Kleemann - Manuele Menozzi - Anton Babenko (antonbabenko) - Irmantas Šiupšinskas (irmantas) - - Charles-Henri Bruyand - Danilo Silva - - Konstantin S. M. Möllers (ksmmoellers) + - 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 - - Mihai Stancu + - 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 - - EdgarPE + - 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 @@ -721,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 @@ -746,20 +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 + - Pawel Smolinski + - Oxan van Leeuwen + - pkowalczyk + - Soner Sayakci - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) @@ -771,38 +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 - - Chad Sikorra (chadsikorra) - 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) - - Christian Wahler + - 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) @@ -810,139 +1189,201 @@ Symfony is the result of the work of many people who made the code better - Sebastian Göttschkes (sgoettschkes) - Tatsuya Tsuruoka - Ross Tuck - - Zander Baldwin - 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 - - Dominik Ritter (dritter) - - Frank Neff (fneff) - - Roman Lapin (memphys) - Giorgio Premi + - Andrew Berry + - ncou + - Ian Carroll - caponica + - Daniel Kay (danielkay-cp) - Matt Daum (daum) - Alberto Pirovano (geezmo) - - Jules Pietri (heah) + - 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) - - Dariusz Ruminski + - 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 + - Gavin Staniforth - Alessandro Tagliapietra (alex88) - Biji (biji) - Gunnar Lium (gunnarlium) @@ -950,55 +1391,86 @@ 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 - Thomas Bibb - - Alessandro Chitolina - Matt Farmer - 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 - - Adrien Lucas (adrienlucas) + - 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ň - - Almog Baku (almogbaku) + - Kamil Madejski + - Jeremiah VALERIE + - Mike Francis + - Gerd Christian Kunze (derdu) - Christoph Nissle (derstoffel) - Ionel Scutelnicu (ionelscutelnicu) - Nicolas Tallefourtané (nicolab) @@ -1008,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) @@ -1042,36 +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 - - Mathieu MARCHOIS + - Alan Bondarchuk - dropfen - Andrey Chernykh - Edvinas Klovas - Drew Butler + - Peter Breuls + - Chansig + - Tischoi - J Bruni + - Fritz Michael Gschwantner - Alexey Prilipko - - Oleg Voronkovich + - 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) @@ -1079,62 +1600,85 @@ 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 + - Helmut Januschka - Hein Zaw Htet™ - Ruben Kruiswijk + - Cosmin-Romeo TANASE + - Julien Maulny - Michael J - Joseph Maarek - Alexander Menk - Alex Pods - hadriengem - timaschew - - Jelle Kapitein + - 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 - - NothingWeAre + - Derek Bonner - Alan Chen - Maerlyn - Even André Fiskvik + - 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 @@ -1142,65 +1686,88 @@ Symfony is the result of the work of many people who made the code better - 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) - - Andreas Schempp (aschempp) + - 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) - - Simone Di Maulo (toretto460) + - Marcin Szepczynski (szepczynski) - Cyrille Jouineau (tuxosaurus) - - Sander Toonen (xatoo) + - 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 - 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) @@ -1208,85 +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 - - Antoine LA - 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 - - pawel-lewtak - 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 - - Jeremy Benoist + - 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 @@ -1294,42 +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 - - Baptiste Lafontaine + - Nicolas + - Sergio Santoro - tirnanog06 - phc - Дмитрий Пацура - ilyes kooli + - Marko Kaznovac - Matthias Althaus - Michaël VEROUX - Julia + - Lin Lu - arduanov - sualko + - Bilge + - ADmad - Nicolas Roudaire - - Jérôme Vasseur - 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) @@ -1337,57 +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) - - Krzysztof Piasecki (krzysztek) - 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 @@ -1397,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 12a74531e40a4..21d7fb9e2f29b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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 5de67ebede36c..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 --------- @@ -599,7 +601,7 @@ FrameworkBundle * 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 + 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 18ea4dbc8d217..64a31dde75600 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.8.md @@ -514,12 +514,12 @@ FrameworkBundle * 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, + + * 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: @@ -527,7 +527,7 @@ FrameworkBundle ``` After: - + ```yaml framework: validation: @@ -547,6 +547,95 @@ Security * 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 + // ... + $container->loadFromExtension('security', array( + // ... + '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 + // ... + $container->loadFromExtension('security', array( + // ... + '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. @@ -612,3 +701,54 @@ Yaml ```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 6f77e27cc6c2e..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 @@ -98,8 +127,14 @@ 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()` @@ -182,6 +217,22 @@ 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. @@ -202,13 +253,27 @@ UPGRADE FROM 2.x to 3.0 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. @@ -348,6 +413,58 @@ UPGRADE FROM 2.x to 3.0 $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. @@ -365,8 +482,11 @@ UPGRADE FROM 2.x to 3.0 ``` - * The `ChoiceToBooleanArrayTransformer`, `ChoicesToBooleanArrayTransformer`, - `FixRadioInputListener`, and `FixCheckboxInputListener` classes were removed. + * 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. @@ -475,6 +595,24 @@ UPGRADE FROM 2.x to 3.0 } } ``` + + If the form is submitted with a different request method than `POST`, you need to configure this in the form: + + Before: + + ```php + $form = $this->createForm(FormType::class, $entity); + $form->submit($request); + ``` + + After: + + ```php + $form = $this->createForm(FormType::class, $entity, [ + 'method' => 'PUT', + ]); + $form->handleRequest($request); + ``` * The events `PRE_BIND`, `BIND` and `POST_BIND` were renamed to `PRE_SUBMIT`, `SUBMIT` and `POST_SUBMIT`. @@ -543,7 +681,7 @@ UPGRADE FROM 2.x to 3.0 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 form the `Symfony\Component\Form\Tests` namespace to the `Symfony\Component\Form\Test` namespace. + * 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`, @@ -603,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. @@ -729,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: @@ -738,6 +883,34 @@ 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` @@ -745,7 +918,7 @@ UPGRADE FROM 2.x to 3.0 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: @@ -896,8 +1069,38 @@ UPGRADE FROM 2.x to 3.0 * 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`, `remember_me` and `http_digest` are @@ -987,7 +1190,7 @@ UPGRADE FROM 2.x to 3.0 '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)` @@ -1022,7 +1225,7 @@ UPGRADE FROM 2.x to 3.0 ```php use Symfony\Component\Security\Core\Authorization\Voter\Voter; - + class MyVoter extends Voter { protected function supports($attribute, $object) @@ -1037,6 +1240,39 @@ UPGRADE FROM 2.x to 3.0 } ``` + * 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. @@ -1062,6 +1298,68 @@ UPGRADE FROM 2.x to 3.0 * 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`. @@ -1604,8 +1902,7 @@ UPGRADE FROM 2.x to 3.0 ### WebProfiler - * The `profiler:import` and `profiler:export` commands have been deprecated and - will be removed in 3.0. + * The `profiler:import` and `profiler:export` commands have been removed. * All the profiler storages different than `FileProfilerStorage` have been removed. The removed classes are: @@ -1624,3 +1921,41 @@ UPGRADE FROM 2.x to 3.0 * `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. + +### 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. + +### 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 89a66df7625d5..0000000000000 --- a/appveyor.yml +++ /dev/null @@ -1,66 +0,0 @@ -build: false -clone_depth: 1 -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 - - REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v DelayedExpansion /t REG_DWORD /d 1 /f - -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.10/php_apcu-4.0.10-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_apcu-4.0.10-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 - - mkdir %APPDATA%\Composer - - IF %APPVEYOR_REPO_NAME%==symfony/symfony copy /Y .composer-auth.json %APPDATA%\Composer\auth.json - - 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-dist --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=!errorlevel! - - copy /Y c:\php\php.ini-max c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! - - exit %X% diff --git a/composer.json b/composer.json index d438762932821..f09d3dfedbf32 100644 --- a/composer.json +++ b/composer.json @@ -17,11 +17,13 @@ ], "require": { "php": ">=5.3.9", + "ext-xml": "*", "doctrine/common": "~2.4", - "twig/twig": "~1.23|~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", @@ -79,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|~2.0", - "egulias/email-validator": "~1.2", - "phpdocumentor/reflection": "^1.0.7" + "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": { @@ -108,6 +114,9 @@ "**/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 9cf03f071ecbf..f4b80ed064121 100755 --- a/phpunit +++ b/phpunit @@ -1,196 +1,14 @@ #!/usr/bin/env php - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -// Please update when phpunit needs to be reinstalled with fresh deps: -// Cache-Id-Version: 2015-11-28 09:05 UTC - -use Symfony\Component\Process\ProcessUtils; - -error_reporting(-1); -require __DIR__.'/src/Symfony/Component/Process/ProcessUtils.php'; - -// PHPUnit 4.8 does not support PHP 7, while 5.1 requires PHP 5.6+ -$PHPUNIT_VERSION = PHP_VERSION_ID >= 50600 ? '5.1' : '4.8'; -$PHPUNIT_DIR = __DIR__.'/.phpunit'; -$PHP = defined('PHP_BINARY') ? PHP_BINARY : 'php'; -$PHP = ProcessUtils::escapeArgument($PHP); -if ('phpdbg' === PHP_SAPI) { - $PHP .= ' -qrr'; -} - -$COMPOSER = file_exists($COMPOSER = __DIR__.'/composer.phar') || ($COMPOSER = rtrim('\\' === DIRECTORY_SEPARATOR ? preg_replace('/[\r\n].*/', '', `where.exe composer.phar`) : `which composer.phar`)) - ? $PHP.' '.ProcessUtils::escapeArgument($COMPOSER) - : 'composer'; - -if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__FILE__) !== @file_get_contents("$PHPUNIT_DIR/.$PHPUNIT_VERSION.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-dist --no-progress --ansi"); - file_put_contents('phpunit', <<= 70000 && !getenv('SYMFONY_PHPUNIT_VERSION')) { + putenv('SYMFONY_PHPUNIT_VERSION=6.5'); } - -$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); - } - - // Fail on any individual component failures but ignore STATUS_STACK_BUFFER_OVERRUN (-1073740791/0xC0000409) and STATUS_ACCESS_VIOLATION (-1073741819/0xC0000005) on Windows when APCu is enabled - if ($procStatus && ('\\' !== DIRECTORY_SEPARATOR || !extension_loaded('apcu') || !ini_get('apc.enable_cli') || (-1073740791 !== $procStatus && -1073741819 !== $procStatus))) { - $exit = $procStatus; - 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"; - } -} - -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 0538884cc43f5..9adb7837d83d9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,10 +1,12 @@ @@ -52,7 +54,12 @@ - Symfony\Component\HttpFoundation + + + 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 af584579c8a6c..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 96f05eb5b60c9..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 2dc8c2cb2b28d..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 (null === $value && !$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(); 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 dae4bb0919ad3..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)); @@ -98,13 +98,20 @@ public function getEntitiesByIds($identifier, array $values) // 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 8f9b2ca7bf04c..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. @@ -138,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; @@ -177,7 +176,7 @@ public function configureOptions(OptionsResolver $resolver) $entityLoader ); - if ($hash !== null) { + if (null !== $hash) { $choiceLoaders[$hash] = $doctrineChoiceLoader; } @@ -238,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; @@ -251,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; @@ -260,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; @@ -270,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 196d7607356b7..99ce535e23c01 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -28,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'); @@ -95,8 +95,6 @@ public function getQueryBuilderPartsForCachingHash($queryBuilder) /** * Converts a query parameter to an array. * - * @param Parameter $parameter The query parameter - * * @return array The array representation of the parameter */ private function parameterToArray(Parameter $parameter) 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 12a74531e40a4..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Doctrine/LICENSE +++ b/src/Symfony/Bridge/Doctrine/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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 18fb341e5b74c..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; @@ -79,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; } @@ -95,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 6e330e558aa06..4bf684bf3aec6 100644 --- a/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php +++ b/src/Symfony/Bridge/Doctrine/PropertyInfo/DoctrineExtractor.php @@ -13,6 +13,7 @@ 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; @@ -26,9 +27,6 @@ */ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface { - /** - * @var ClassMetadataFactory - */ private $classMetadataFactory; public function __construct(ClassMetadataFactory $classMetadataFactory) @@ -49,7 +47,17 @@ public function getProperties($class, array $context = array()) 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; } /** @@ -70,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; } @@ -78,40 +88,100 @@ 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; } /** @@ -119,36 +189,34 @@ public function getTypes($class, $property, array $context = array()) * * @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 b2216b7ae31df..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.'); diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index 596cf46b01293..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. @@ -52,10 +52,10 @@ public function loadUserByUsername($username) } else { if (!$repository instanceof UserLoaderInterface) { if (!$repository instanceof UserProviderInterface) { - throw new \InvalidArgumentException(sprintf('The Doctrine repository "%s" must implement Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface.', get_class($repository))); + 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 = $repository->loadUserByUsername($username); @@ -75,7 +75,7 @@ public function refreshUser(UserInterface $user) { $class = $this->getClass(); if (!$user instanceof $class) { - throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user))); } $repository = $this->getRepository(); 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/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/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/AbstractEntityChoiceListSingleAssociationToIntIdTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListSingleAssociationToIntIdTest.php index 02e117d7baec4..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,9 @@ 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; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php index b2d5b4a888b37..cff0d91b55db4 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/AbstractEntityChoiceListTest.php @@ -11,9 +11,9 @@ 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')) { 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/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index d755af5430bc4..c3fea897258c0 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -11,12 +11,13 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; -use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; -use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; 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; -class ORMQueryBuilderLoaderTest extends \PHPUnit_Framework_TestCase +class ORMQueryBuilderLoaderTest extends TestCase { /** * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException @@ -87,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') @@ -103,7 +104,39 @@ 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() @@ -139,4 +172,12 @@ public function testEmbeddedIdentifierName() $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/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 bdcea8ae638a7..351ff941d55b1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -23,19 +23,21 @@ 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'; @@ -116,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, )); @@ -129,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); } /** @@ -137,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', )); } @@ -149,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, @@ -166,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() @@ -183,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']); } /** @@ -199,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(), @@ -211,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 () { @@ -222,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 () { @@ -232,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, @@ -250,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', @@ -264,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', @@ -276,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'); @@ -324,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', @@ -349,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', @@ -371,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', @@ -395,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', @@ -424,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', @@ -449,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', @@ -480,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', @@ -506,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', @@ -536,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', @@ -562,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', @@ -591,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', @@ -616,7 +584,7 @@ public function testSubmitSingleNonExpandedStringCastableIdentifier() $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', @@ -638,7 +606,7 @@ public function testSubmitSingleStringCastableIdentifierExpanded() $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', @@ -664,7 +632,7 @@ public function testSubmitMultipleNonExpandedStringCastableIdentifierForExisting $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', @@ -695,7 +663,7 @@ public function testSubmitMultipleNonExpandedStringCastableIdentifier() $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', @@ -720,7 +688,7 @@ public function testSubmitMultipleStringCastableIdentifierExpanded() $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', @@ -750,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 @@ -773,7 +741,7 @@ public function testOverrideChoicesValues() $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, 'choice_label' => 'name', @@ -795,11 +763,15 @@ public function testOverrideChoicesValuesWithCallable() $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::ITEM_GROUP_CLASS, 'choice_label' => 'name', - 'choice_value' => function (GroupableEntity $entity) { + 'choice_value' => function (GroupableEntity $entity = null) { + if (null === $entity) { + return ''; + } + return $entity->groupName.'/'.$entity->name; }, )); @@ -815,6 +787,30 @@ public function testOverrideChoicesValuesWithCallable() $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'); @@ -824,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), @@ -855,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), @@ -874,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), @@ -894,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), @@ -917,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), @@ -938,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), @@ -961,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') @@ -989,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') @@ -1011,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)'); }, @@ -1035,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)'); }, @@ -1057,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', @@ -1078,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', @@ -1104,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', @@ -1119,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', @@ -1136,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); @@ -1148,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) { @@ -1164,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) { @@ -1199,10 +1192,7 @@ public function testLoaderCachingWithParameters() $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); @@ -1211,15 +1201,15 @@ public function testLoaderCachingWithParameters() ->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 = :id')->setParameter('id', 1), )); - $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) { @@ -1227,7 +1217,7 @@ public function testLoaderCachingWithParameters() }, )); - $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) { @@ -1261,14 +1251,14 @@ public function testCacheChoiceLists() $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, @@ -1289,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)) @@ -1309,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 ac1bb5f96e41b..24e30593469fc 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/PropertyInfo/DoctrineExtractorTest.php @@ -9,17 +9,19 @@ * 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 @@ -28,9 +30,14 @@ class DoctrineExtractorTest extends \PHPUnit_Framework_TestCase 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,7 +129,24 @@ 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), ); } 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 c71378b730ee4..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,7 +214,7 @@ public function testLoadUserByUserNameShouldDeclineInvalidInterface() private function getManager($em, $name = null) { - $manager = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $manager = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock(); $manager->expects($this->any()) ->method('getManager') ->with($this->equalTo($name)) 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 33e484320a56a..84e5717e550ca 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -15,15 +15,16 @@ 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\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); @@ -404,6 +455,7 @@ public function testAssociatedEntity() $this->buildViolation('myMessage') ->atPath('property.path.single') ->setInvalidValue($entity1) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) ->assertRaised(); } @@ -470,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 f4c8671abae9a..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,28 +70,36 @@ 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 @@ -100,7 +109,18 @@ public function validate($entity, Constraint $constraint) } } - $repository = $em->getRepository(get_class($entity)); + // validation doesn't fail if one of the fields is null and if null values should be ignored + if ($hasNullValue && $constraint->ignoreNull) { + return; + } + + // 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)); $result = $repository->{$constraint->repositoryMethod}($criteria); if ($result instanceof \IteratorAggregate) { @@ -113,15 +133,23 @@ public function validate($entity, Constraint $constraint) */ 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; } @@ -132,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 f07527f5cb523..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": "", diff --git a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist index c006d232219a4..fa76fa9b500e7 100644 --- a/src/Symfony/Bridge/Doctrine/phpunit.xml.dist +++ b/src/Symfony/Bridge/Doctrine/phpunit.xml.dist @@ -1,10 +1,12 @@ 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 12a74531e40a4..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Monolog/LICENSE +++ b/src/Symfony/Bridge/Monolog/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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 index 3d3c74cb73dfc..fbf5579e340c4 100644 --- a/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/LoggerTest.php @@ -3,10 +3,11 @@ namespace Symfony\Bridge\Monolog\Tests; use Monolog\Handler\TestHandler; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Monolog\Handler\DebugHandler; use Symfony\Bridge\Monolog\Logger; -class LoggerTest extends \PHPUnit_Framework_TestCase +class LoggerTest extends TestCase { /** * @group legacy 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 500a9f57c43b1..e65fb26e7ef74 100644 --- a/src/Symfony/Bridge/Monolog/composer.json +++ b/src/Symfony/Bridge/Monolog/composer.json @@ -17,13 +17,16 @@ ], "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", "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.", diff --git a/src/Symfony/Bridge/Monolog/phpunit.xml.dist b/src/Symfony/Bridge/Monolog/phpunit.xml.dist index 8a60f06a7a310..1bda3eca9cd05 100644 --- a/src/Symfony/Bridge/Monolog/phpunit.xml.dist +++ b/src/Symfony/Bridge/Monolog/phpunit.xml.dist @@ -1,10 +1,12 @@ diff --git a/src/Symfony/Bridge/PhpUnit/ClockMock.php b/src/Symfony/Bridge/PhpUnit/ClockMock.php index fe5cd851258d2..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", self::$now - (int) self::$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, @@ -52,20 +66,22 @@ public static function register($mode = 0) '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 (isset($trace[$i]['object']) || isset($trace[$i]['class'])) { - $class = isset($trace[$i]['object']) ? get_class($trace[$i]['object']) : $trace[$i]['class']; + $class = isset($trace[$i]['object']) ? \get_class($trace[$i]['object']) : $trace[$i]['class']; $method = $trace[$i]['function']; if (0 !== error_reporting()) { @@ -74,7 +90,7 @@ public static function register($mode = 0) || 0 === strpos($method, 'provideLegacy') || 0 === strpos($method, 'getLegacy') || strpos($class, '\Legacy') - || in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) + || \in_array('legacy', \PHPUnit_Util_Test::getGroups($class, $method), true) ) { $group = 'legacy'; } else { @@ -85,12 +101,12 @@ public static function register($mode = 0) $e = new \Exception($msg); $r = new \ReflectionProperty($e, 'trace'); $r->setAccessible(true); - $r->setValue($e, array_slice($trace, 1, $i)); + $r->setValue($e, \array_slice($trace, 1, $i)); 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".str_replace(' '.getcwd().\DIRECTORY_SEPARATOR, ' ', $e->getTraceAsString()); echo "\n"; exit(1); @@ -101,8 +117,7 @@ public static function register($mode = 0) $ref = &$deprecations[$group][$msg][$class.'::'.$method]; ++$ref; } - } else { - $group = 'other'; + } elseif (DeprecationErrorHandler::MODE_WEAK !== $mode) { $ref = &$deprecations[$group][$msg]['count']; ++$ref; } @@ -116,7 +131,7 @@ public static function register($mode = 0) restore_error_handler(); self::register($mode); } - } elseif (!isset($mode[0]) || '/' !== $mode[0]) { + } else { self::$isRegistered = true; if (self::hasColorSupport()) { $colorize = function ($str, $red) { @@ -125,12 +140,19 @@ public static function register($mode = 0) 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"; } @@ -139,41 +161,93 @@ public static function register($mode = 0) 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 (DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { - 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 39fa189d2b5fc..15fc1c88d330b 100644 --- a/src/Symfony/Bridge/PhpUnit/LICENSE +++ b/src/Symfony/Bridge/PhpUnit/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2016 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 90dbe83bb8e79..8c4e6e59ccb47 100644 --- a/src/Symfony/Bridge/PhpUnit/README.md +++ b/src/Symfony/Bridge/PhpUnit/README.md @@ -3,58 +3,11 @@ PHPUnit Bridge Provides utilities for PHPUnit, especially user deprecation notices management. -It comes with the following features: - - * 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; - * display the stack trace of a deprecation on-demand. - -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 the maximum number of deprecations that are allowed to be -triggered before making the test suite fail. Alternatively, setting it to `weak` -will make the bridge ignore any 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). - -In case you need to inspect the stack trace of a particular deprecation triggered -by your unit tests, you can set the `SYMFONY_DEPRECATIONS_HELPER` env var to a -regular expression that matches this deprecation's message, encapsed between `/`. -For example, `SYMFONY_DEPRECATIONS_HELPER=/foobar/ phpunit` will stop your test -suite once a deprecation notice is triggered whose message contains the "foobar" -string. +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 4f4dc78508c22..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. * @@ -26,20 +30,47 @@ class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener private $wasSkipped = array(); private $isSkipped = array(); - public function __construct(array $extraClockMockedNamespaces = array()) + /** + * @param array $mockedNamespaces List of namespaces, indexed by mocked features (time-sensitive) + */ + public function __construct(array $mockedNamespaces = array()) { - if ($extraClockMockedNamespaces) { - foreach ($extraClockMockedNamespaces as $ns) { - ClockMock::register($ns.'\DummyClass'); + $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) { @@ -47,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(); @@ -75,10 +112,13 @@ public function startTestSuite(\PHPUnit_Framework_TestSuite $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) && in_array('time-sensitive', \PHPUnit_Util_Test::getGroups($test->getName()), true)) { - ClockMock::register($test->getName()); - } else { + 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()); } } } @@ -114,7 +154,7 @@ public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $ti public function startTest(\PHPUnit_Framework_Test $test) { if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { - $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName()); + $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName(false)); if (in_array('time-sensitive', $groups, true)) { ClockMock::register(get_class($test)); @@ -126,7 +166,7 @@ public function startTest(\PHPUnit_Framework_Test $test) public function endTest(\PHPUnit_Framework_Test $test, $time) { if (-2 < $this->state && $test instanceof \PHPUnit_Framework_TestCase) { - $groups = \PHPUnit_Util_Test::getGroups(get_class($test), $test->getName()); + $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/composer.json b/src/Symfony/Bridge/PhpUnit/composer.json index 4e9423652bcd7..791f8b6ef210d 100644 --- a/src/Symfony/Bridge/PhpUnit/composer.json +++ b/src/Symfony/Bridge/PhpUnit/composer.json @@ -16,11 +16,16 @@ } ], "require": { + "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\\": "" }, 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 12a74531e40a4..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/ProxyManager/LICENSE +++ b/src/Symfony/Bridge/ProxyManager/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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 417d94e47215e..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,15 +53,17 @@ 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); @@ -109,14 +97,16 @@ function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) */ public function getProxyCode(Definition $definition) { - return $this->classGenerator->generate($this->generateProxyClass($definition)); + 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) @@ -125,8 +115,6 @@ private function getProxyClassName(Definition $definition) } /** - * @param Definition $definition - * * @return ClassGenerator */ private function generateProxyClass(Definition $definition) 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 ae13d543b0e1e..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,7 +23,7 @@ * * @author Marco Pivetta */ -class ContainerBuilderTest extends \PHPUnit_Framework_TestCase +class ContainerBuilderTest extends TestCase { public function testCreateProxyServiceWithRuntimeInstantiator() { @@ -33,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); @@ -47,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 5e451c122f3c4..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,18 +38,15 @@ public function testDumpContainerWithProxyService() */ public function testDumpContainerWithProxyServiceWillShareProxies() { - // detecting ProxyManager v2 - if (class_exists('ProxyManager\ProxyGenerator\LazyLoading\MethodGenerator\StaticProxyConstructor')) { - require_once __DIR__.'/../Fixtures/php/lazy_service_with_hints.php'; - } else { - 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()); @@ -70,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 e2775cfa5f3d3..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,7 +1,7 @@ services = 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 = $this->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() : bool - { - return $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, 'initializeProxy', array()); - } - - /** - * {@inheritdoc} - */ - public function isProxyInitialized() : bool - { - return null !== $this->valueHolder5157dd96e88c0; - } - - /** - * {@inheritdoc} - */ - public function getWrappedValueHolderValue() - { - return $this->valueHolder5157dd96e88c0; - } -} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Instantiator/RuntimeInstantiatorTest.php index 8b2402b045f28..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; @@ -19,7 +20,7 @@ * * @author Marco Pivetta */ -class RuntimeInstantiatorTest extends \PHPUnit_Framework_TestCase +class RuntimeInstantiatorTest extends TestCase { /** * @var RuntimeInstantiator @@ -37,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 0b98419f16203..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; @@ -19,7 +20,7 @@ * * @author Marco Pivetta */ -class ProxyDumperTest extends \PHPUnit_Framework_TestCase +class ProxyDumperTest extends TestCase { /** * @var ProxyDumper @@ -86,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/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 12a74531e40a4..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Swiftmailer/LICENSE +++ b/src/Symfony/Bridge/Swiftmailer/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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/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 861de86909722..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. @@ -94,7 +90,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $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) { @@ -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 31009a75b9e8a..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 + +Or of a whole directory: -You can also pass the template contents from STDIN: + php %command.full_name% dirname + php %command.full_name% dirname --format=json -cat filename | php %command.full_name% EOF ) ; @@ -99,7 +99,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $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.'); } @@ -109,7 +109,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $template .= fread(STDIN, 1024); } - return $this->display($input, $output, $io, 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); @@ -117,7 +117,7 @@ protected function execute(InputInterface $input, OutputInterface $output) 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) { @@ -140,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); @@ -183,10 +183,10 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInf } } - if ($errors === 0) { - $io->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 { - $io->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); @@ -206,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(); @@ -239,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 a72f4503dd869..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); } 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 3f0a423337311..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 implements \Twig_Extension_InitRuntimeInterface +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 69d6d326f4d08..fe2778393cfd2 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpFoundationExtension.php @@ -11,16 +11,18 @@ 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; @@ -37,8 +39,8 @@ public function __construct(RequestStack $requestStack, RequestContext $requestC 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')), ); } @@ -70,6 +72,13 @@ public function generateAbsoluteUrl($path) $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; } @@ -80,9 +89,15 @@ public function generateAbsoluteUrl($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 2d46795b4a81e..cb0f9e0fd33e7 100644 --- a/src/Symfony/Bridge/Twig/Extension/YamlExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/YamlExtension.php @@ -13,13 +13,15 @@ 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} @@ -27,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')), ); } @@ -40,8 +42,8 @@ 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); + 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); @@ -49,12 +51,12 @@ public function encode($input, $inline = 0, $dumpObjects = false) 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 12a74531e40a4..21d7fb9e2f29b 100644 --- a/src/Symfony/Bridge/Twig/LICENSE +++ b/src/Symfony/Bridge/Twig/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 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_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig index dfb1965ca1048..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 %} @@ -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 %} @@ -236,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 e5e43bf4312ed..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') -}} @@ -79,8 +79,7 @@ {{- block('choice_widget_options') -}} {%- else -%} - {% set attr = choice.attr %} - + {%- endif -%} {% endfor %} {%- endblock choice_widget_options -%} @@ -143,7 +142,7 @@ {%- endblock integer_widget -%} {%- block money_widget -%} - {{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }} + {{ money_pattern|form_encode_currency(block('form_widget_simple')) }} {%- endblock money_widget -%} {%- block url_widget -%} @@ -225,7 +224,13 @@ {% set label = name|humanize %} {%- endif -%} {%- endif -%} - {{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }} + + {%- if translation_domain is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|trans({}, translation_domain) -}} + {%- endif -%} + {%- endif -%} {%- endblock form_label -%} @@ -268,13 +273,14 @@ {%- endblock form -%} {%- block form_start -%} + {%- 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 -%} @@ -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,9 +339,10 @@ {%- 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 }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" 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/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index 51a95bc4b57e0..b9aa168fd763d 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php @@ -2,11 +2,12 @@ namespace Symfony\Bridge\Twig\Tests; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\AppVariable; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; -class AppVariableTest extends \PHPUnit_Framework_TestCase +class AppVariableTest extends TestCase { /** * @var AppVariable @@ -43,9 +44,12 @@ public function testEnvironment() $this->assertEquals('dev', $this->appVariable->getEnvironment()); } + /** + * @runInSeparateProcess + */ public function testGetSession() { - $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->getMock(); $request->method('getSession')->willReturn($session = new Session()); $this->setRequestStack($request); @@ -69,7 +73,7 @@ public function testGetRequest() public function testGetUser() { - $this->setTokenStorage($user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface')); + $this->setTokenStorage($user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock()); $this->assertEquals($user, $this->appVariable->getUser()); } @@ -83,7 +87,7 @@ public function testGetUserWithUsernameAsTokenUser() public function testGetUserWithNoToken() { - $tokenStorage = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'); + $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); $this->appVariable->setTokenStorage($tokenStorage); $this->assertNull($this->appVariable->getUser()); @@ -131,7 +135,7 @@ public function testGetSessionWithRequestStackNotSet() protected function setRequestStack($request) { - $requestStackMock = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); + $requestStackMock = $this->getMockBuilder('Symfony\Component\HttpFoundation\RequestStack')->getMock(); $requestStackMock->method('getCurrentRequest')->willReturn($request); $this->appVariable->setRequestStack($requestStackMock); @@ -139,10 +143,10 @@ protected function setRequestStack($request) protected function setTokenStorage($user) { - $tokenStorage = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'); + $tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock(); $this->appVariable->setTokenStorage($tokenStorage); - $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); $tokenStorage->method('getToken')->willReturn($token); $token->method('getUser')->willReturn($user); diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index be15eaf942d99..6a4c5ff51b0e5 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -11,12 +11,15 @@ namespace Symfony\Bridge\Twig\Tests\Command; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Command\LintCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Tester\CommandTester; +use Twig\Environment; +use Twig\Loader\FilesystemLoader; -class LintCommandTest extends \PHPUnit_Framework_TestCase +class LintCommandTest extends TestCase { private $files; @@ -70,7 +73,7 @@ public function testLintFileCompileTimeException() */ private function createCommandTester() { - $twig = new \Twig_Environment(new \Twig_Loader_Filesystem()); + $twig = new Environment(new FilesystemLoader()); $command = new LintCommand(); $command->setTwigEnvironment($twig); diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AssetExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AssetExtensionTest.php index 474d8c33956b7..ba04fd4b7cf8a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AssetExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AssetExtensionTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\Twig\Tests\Extension; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\AssetExtension; use Symfony\Component\Asset\Package; use Symfony\Component\Asset\Packages; @@ -18,7 +19,7 @@ use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; -class AssetExtensionTest extends \PHPUnit_Framework_TestCase +class AssetExtensionTest extends TestCase { /** * @group legacy diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php index 5e4a9a307982d..64bebd709692e 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php @@ -11,9 +11,10 @@ namespace Symfony\Bridge\Twig\Tests\Extension; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\CodeExtension; -class CodeExtensionTest extends \PHPUnit_Framework_TestCase +class CodeExtensionTest extends TestCase { protected $helper; diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php index af93114e5ae57..fb33ff75ac0b4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php @@ -11,11 +11,14 @@ namespace Symfony\Bridge\Twig\Tests\Extension; +use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\DumpExtension; -use Symfony\Component\VarDumper\VarDumper; use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Environment; +use Twig\Loader\ArrayLoader; -class DumpExtensionTest extends \PHPUnit_Framework_TestCase +class DumpExtensionTest extends TestCase { /** * @dataProvider getDumpTags @@ -23,7 +26,7 @@ class DumpExtensionTest extends \PHPUnit_Framework_TestCase public function testDumpTag($template, $debug, $expectedOutput, $expectedDumped) { $extension = new DumpExtension(new VarCloner()); - $twig = new \Twig_Environment(new \Twig_Loader_Array(array('template' => $template)), array( + $twig = new Environment(new ArrayLoader(array('template' => $template)), array( 'debug' => $debug, 'cache' => false, 'optimizations' => 0, @@ -32,7 +35,7 @@ public function testDumpTag($template, $debug, $expectedOutput, $expectedDumped) $dumped = null; $exception = null; - $prevDumper = VarDumper::setHandler(function ($var) use (&$dumped) {$dumped = $var;}); + $prevDumper = VarDumper::setHandler(function ($var) use (&$dumped) { $dumped = $var; }); try { $this->assertEquals($expectedOutput, $twig->render('template')); @@ -63,7 +66,7 @@ public function getDumpTags() public function testDump($context, $args, $expectedOutput, $debug = true) { $extension = new DumpExtension(new VarCloner()); - $twig = new \Twig_Environment($this->getMock('Twig_LoaderInterface'), array( + $twig = new Environment($this->getMockBuilder('Twig\Loader\LoaderInterface')->getMock(), array( 'debug' => $debug, 'cache' => false, 'optimizations' => 0, @@ -72,7 +75,7 @@ public function testDump($context, $args, $expectedOutput, $debug = true) array_unshift($args, $context); array_unshift($args, $twig); - $dump = call_user_func_array(array($extension, 'dump'), $args); + $dump = \call_user_func_array(array($extension, 'dump'), $args); if ($debug) { $this->assertStringStartsWith(''), + array('http://example.com/exploit.html?hel lo'), + array('http://example.com/exploit.html?not_a%hex'), + array('http://'), ); } @@ -188,6 +201,36 @@ public function getValidCustomUrls() array('git://[::1]/'), ); } + + /** + * @dataProvider getCheckDns + * @requires function Symfony\Bridge\PhpUnit\DnsMock::withMockedHosts + */ + public function testCheckDns($violation) + { + DnsMock::withMockedHosts(array('example.com' => array(array('type' => $violation ? '' : 'A')))); + + $constraint = new Url(array( + 'checkDNS' => true, + 'dnsMessage' => 'myMessage', + )); + + $this->validator->validate('http://example.com', $constraint); + + if (!$violation) { + $this->assertNoViolation(); + } else { + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"example.com"') + ->setCode(Url::INVALID_URL_ERROR) + ->assertRaised(); + } + } + + public function getCheckDns() + { + return array(array(true), array(false)); + } } class EmailProvider diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php index 0abda39f02942..05a60684c2589 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UuidValidatorTest.php @@ -44,6 +44,16 @@ public function testEmptyStringIsValid() $this->assertNoViolation(); } + /** + * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException + */ + public function testExpectsUuidConstraintCompatibleType() + { + $constraint = $this->getMockForAbstractClass('Symfony\\Component\\Validator\\Constraint'); + + $this->validator->validate('216fff40-98d9-11e3-a5e2-0800200c9a66', $constraint); + } + /** * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException */ diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php index 3de6ab3217a3e..83722fd2df0e0 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/ValidTest.php @@ -11,12 +11,13 @@ namespace Symfony\Component\Validator\Tests\Constraints; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Valid; /** * @author Bernhard Schussek */ -class ValidTest extends \PHPUnit_Framework_TestCase +class ValidTest extends TestCase { /** * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Countable.php b/src/Symfony/Component/Validator/Tests/Fixtures/Countable.php index 282d78d45a672..afc42376a254b 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/Countable.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Countable.php @@ -22,6 +22,6 @@ public function __construct(array $content) public function count() { - return count($this->content); + return \count($this->content); } } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php b/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php index 41eac961acf91..feb25318eb5e5 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/CustomArrayObject.php @@ -55,7 +55,7 @@ public function getIterator() public function count() { - return count($this->array); + return \count($this->array); } public function serialize() diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php index 526ecc5f5c635..e30619103d906 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php @@ -19,7 +19,7 @@ * @Assert\GroupSequence({"Foo", "Entity"}) * @Assert\Callback({"Symfony\Component\Validator\Tests\Fixtures\CallbackClass", "callback"}) */ -class Entity extends EntityParent implements EntityInterface +class Entity extends EntityParent implements EntityInterfaceB { /** * @Assert\NotNull @@ -63,6 +63,10 @@ public function getLastName() return $this->lastName; } + public function getValid() + { + } + /** * @Assert\IsTrue */ @@ -85,7 +89,7 @@ public function getData() } /** - * @Assert\Callback + * @Assert\Callback(payload="foo") */ public function validateMe(ExecutionContextInterface $context) { diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterface.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterfaceA.php similarity index 91% rename from src/Symfony/Component/Validator/Tests/Fixtures/EntityInterface.php rename to src/Symfony/Component/Validator/Tests/Fixtures/EntityInterfaceA.php index 2901f26386b4e..a0afcf8163110 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterface.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterfaceA.php @@ -11,6 +11,6 @@ namespace Symfony\Component\Validator\Tests\Fixtures; -interface EntityInterface +interface EntityInterfaceA { } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterfaceB.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterfaceB.php new file mode 100644 index 0000000000000..93b389414fadf --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityInterfaceB.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +interface EntityInterfaceB extends EntityParentInterface +{ +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php index 422bb28d0b5ea..4674f8b35a226 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php @@ -13,7 +13,7 @@ use Symfony\Component\Validator\Constraints\NotNull; -class EntityParent +class EntityParent implements EntityInterfaceA { protected $firstName; private $internal; diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParentInterface.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParentInterface.php new file mode 100644 index 0000000000000..3aad6fec5f76e --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParentInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +interface EntityParentInterface +{ +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticCar.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticCar.php new file mode 100644 index 0000000000000..0b384872db597 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticCar.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\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +class EntityStaticCar extends EntityStaticVehicle +{ + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('wheels', new Length(array('max' => 99))); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticCarTurbo.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticCarTurbo.php new file mode 100644 index 0000000000000..abf1edbdd132d --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticCarTurbo.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\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +class EntityStaticCarTurbo extends EntityStaticCar +{ + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('wheels', new Length(array('max' => 99))); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticVehicle.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticVehicle.php new file mode 100644 index 0000000000000..9ce72935de514 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityStaticVehicle.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +class EntityStaticVehicle +{ + public $wheels; + + public static function loadValidatorMetadata(ClassMetadata $metadata) + { + $metadata->addPropertyConstraint('wheels', new Length(array('max' => 99))); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php b/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php index e3f0d9a007800..dbc255f36eb4c 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php @@ -23,13 +23,13 @@ public function getMetadataFor($class) { $hash = null; - if (is_object($class)) { + if (\is_object($class)) { $hash = spl_object_hash($class); - $class = get_class($class); + $class = \get_class($class); } - if (!is_string($class)) { - throw new NoSuchMetadataException(sprintf('No metadata for type %s', gettype($class))); + if (!\is_string($class)) { + throw new NoSuchMetadataException(sprintf('No metadata for type %s', \gettype($class))); } if (!isset($this->metadatas[$class])) { @@ -47,12 +47,12 @@ public function hasMetadataFor($class) { $hash = null; - if (is_object($class)) { + if (\is_object($class)) { $hash = spl_object_hash($class); - $class = get_class($class); + $class = \get_class($class); } - if (!is_string($class)) { + if (!\is_string($class)) { return false; } @@ -66,7 +66,7 @@ public function addMetadata($metadata) public function addMetadataForValue($value, MetadataInterface $metadata) { - $key = is_object($value) ? spl_object_hash($value) : $value; + $key = \is_object($value) ? spl_object_hash($value) : $value; $this->metadatas[$key] = $metadata; } } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderChildEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderChildEntity.php new file mode 100644 index 0000000000000..be7191f9b6e1d --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderChildEntity.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class GroupSequenceProviderChildEntity extends GroupSequenceProviderEntity +{ +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/StubGlobalExecutionContext.php b/src/Symfony/Component/Validator/Tests/Fixtures/StubGlobalExecutionContext.php index 84c5a80bf2d16..bd2f5c94e7a96 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/StubGlobalExecutionContext.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/StubGlobalExecutionContext.php @@ -16,8 +16,6 @@ use Symfony\Component\Validator\ValidationVisitorInterface; /** - * @since 2.5.3 - * * @author Bernhard Schussek * * @deprecated since version 2.5, to be removed in 3.0 diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php b/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php new file mode 100644 index 0000000000000..714fdb9e98f5f --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/ToString.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +class ToString +{ + public $data; + + public function __toString() + { + return 'toString'; + } +} diff --git a/src/Symfony/Component/Validator/Tests/LegacyExecutionContextTest.php b/src/Symfony/Component/Validator/Tests/LegacyExecutionContextTest.php index f0139c0da5ffc..1130551c2e8e0 100644 --- a/src/Symfony/Component/Validator/Tests/LegacyExecutionContextTest.php +++ b/src/Symfony/Component/Validator/Tests/LegacyExecutionContextTest.php @@ -11,8 +11,9 @@ namespace Symfony\Component\Validator\Tests; -use Symfony\Component\Validator\Constraints\Collection; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; @@ -23,7 +24,7 @@ /** * @group legacy */ -class LegacyExecutionContextTest extends \PHPUnit_Framework_TestCase +class LegacyExecutionContextTest extends TestCase { const TRANS_DOMAIN = 'trans_domain'; @@ -45,9 +46,9 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->violations = new ConstraintViolationList(); - $this->metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface'); - $this->metadataFactory = $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface'); - $this->globalContext = $this->getMock('Symfony\Component\Validator\GlobalExecutionContextInterface'); + $this->metadata = $this->getMockBuilder('Symfony\Component\Validator\MetadataInterface')->getMock(); + $this->metadataFactory = $this->getMockBuilder('Symfony\Component\Validator\MetadataFactoryInterface')->getMock(); + $this->globalContext = $this->getMockBuilder('Symfony\Component\Validator\GlobalExecutionContextInterface')->getMock(); $this->globalContext->expects($this->any()) ->method('getRoot') ->will($this->returnValue('Root')); @@ -60,7 +61,7 @@ protected function setUp() $this->globalContext->expects($this->any()) ->method('getMetadataFactory') ->will($this->returnValue($this->metadataFactory)); - $this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); + $this->translator = $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock(); $this->context = new ExecutionContext($this->globalContext, $this->translator, self::TRANS_DOMAIN, $this->metadata, 'currentValue', 'Group', 'foo.bar'); } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php index a2de306a2f46d..19b29227d6d8d 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php @@ -12,9 +12,10 @@ namespace Symfony\Component\Validator\Tests\Mapping\Cache; use Doctrine\Common\Cache\ArrayCache; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\Cache\DoctrineCache; -class DoctrineCacheTest extends \PHPUnit_Framework_TestCase +class DoctrineCacheTest extends TestCase { private $cache; diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Cache/LegacyApcCacheTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Cache/LegacyApcCacheTest.php index a5d1c239f8252..7102474e8444f 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Cache/LegacyApcCacheTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Cache/LegacyApcCacheTest.php @@ -11,17 +11,18 @@ namespace Symfony\Component\Validator\Tests\Mapping\Cache; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\Cache\ApcCache; /** * @group legacy * @requires extension apc */ -class LegacyApcCacheTest extends \PHPUnit_Framework_TestCase +class LegacyApcCacheTest extends TestCase { protected function setUp() { - if (!ini_get('apc.enabled') || !ini_get('apc.enable_cli')) { + if (!filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) || !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) { $this->markTestSkipped('APC is not enabled.'); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php index a238b1754aa03..a3f8a8c1ec8a3 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Tests\Mapping; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -18,11 +19,12 @@ use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint; -class ClassMetadataTest extends \PHPUnit_Framework_TestCase +class ClassMetadataTest extends TestCase { const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'; const PROVIDERCLASS = 'Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity'; + const PROVIDERCHILDCLASS = 'Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderChildEntity'; protected $metadata; @@ -38,14 +40,14 @@ protected function tearDown() public function testAddConstraintDoesNotAcceptValid() { - $this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); $this->metadata->addConstraint(new Valid()); } public function testAddConstraintRequiresClassConstraints() { - $this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); $this->metadata->addConstraint(new PropertyConstraint()); } @@ -134,20 +136,45 @@ public function testMergeConstraintsMergesMemberConstraints() { $parent = new ClassMetadata(self::PARENTCLASS); $parent->addPropertyConstraint('firstName', new ConstraintA()); + $parent->addPropertyConstraint('firstName', new ConstraintB(array('groups' => 'foo'))); $this->metadata->mergeConstraints($parent); $this->metadata->addPropertyConstraint('firstName', new ConstraintA()); + $constraintA1 = new ConstraintA(array('groups' => array( + 'Default', + 'EntityParent', + 'Entity', + ))); + $constraintA2 = new ConstraintA(array('groups' => array( + 'Default', + 'Entity', + ))); + $constraintB = new ConstraintB(array( + 'groups' => array('foo'), + )); + $constraints = array( - new ConstraintA(array('groups' => array( - 'Default', - 'EntityParent', - 'Entity', - ))), - new ConstraintA(array('groups' => array( - 'Default', - 'Entity', - ))), + $constraintA1, + $constraintB, + $constraintA2, + ); + + $constraintsByGroup = array( + 'Default' => array( + $constraintA1, + $constraintA2, + ), + 'EntityParent' => array( + $constraintA1, + ), + 'Entity' => array( + $constraintA1, + $constraintA2, + ), + 'foo' => array( + $constraintB, + ), ); $members = $this->metadata->getPropertyMetadata('firstName'); @@ -155,6 +182,7 @@ public function testMergeConstraintsMergesMemberConstraints() $this->assertCount(1, $members); $this->assertEquals(self::PARENTCLASS, $members[0]->getClassName()); $this->assertEquals($constraints, $members[0]->getConstraints()); + $this->assertEquals($constraintsByGroup, $members[0]->constraintsByGroup); } public function testMemberMetadatas() @@ -218,19 +246,23 @@ public function testSerialize() public function testGroupSequencesWorkIfContainingDefaultGroup() { $this->metadata->setGroupSequence(array('Foo', $this->metadata->getDefaultGroup())); + + $this->assertInstanceOf('Symfony\Component\Validator\Constraints\GroupSequence', $this->metadata->getGroupSequence()); } + /** + * @expectedException \Symfony\Component\Validator\Exception\GroupDefinitionException + */ public function testGroupSequencesFailIfNotContainingDefaultGroup() { - $this->setExpectedException('Symfony\Component\Validator\Exception\GroupDefinitionException'); - $this->metadata->setGroupSequence(array('Foo', 'Bar')); } + /** + * @expectedException \Symfony\Component\Validator\Exception\GroupDefinitionException + */ public function testGroupSequencesFailIfContainingDefault() { - $this->setExpectedException('Symfony\Component\Validator\Exception\GroupDefinitionException'); - $this->metadata->setGroupSequence(array('Foo', $this->metadata->getDefaultGroup(), Constraint::DEFAULT_GROUP)); } @@ -270,6 +302,17 @@ public function testGroupSequenceProvider() $this->assertTrue($metadata->isGroupSequenceProvider()); } + public function testMergeConstraintsMergesGroupSequenceProvider() + { + $parent = new ClassMetadata(self::PROVIDERCLASS); + $parent->setGroupSequenceProvider(true); + + $metadata = new ClassMetadata(self::PROVIDERCHILDCLASS); + $metadata->mergeConstraints($parent); + + $this->assertTrue($metadata->isGroupSequenceProvider()); + } + /** * https://github.com/symfony/symfony/issues/11604. */ diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php index 641bf919d4800..a323567d2316b 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Factory/BlackHoleMetadataFactoryTest.php @@ -11,9 +11,10 @@ namespace Symfony\Component\Validator\Tests\Mapping\Factory; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\Factory\BlackHoleMetadataFactory; -class BlackHoleMetadataFactoryTest extends \PHPUnit_Framework_TestCase +class BlackHoleMetadataFactoryTest extends TestCase { /** * @expectedException \LogicException diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php index 74ee912cb789e..de6852271e17f 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Factory/LazyLoadingMetadataFactoryTest.php @@ -11,23 +11,29 @@ namespace Symfony\Component\Validator\Tests\Mapping\Factory; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; -class LazyLoadingMetadataFactoryTest extends \PHPUnit_Framework_TestCase +class LazyLoadingMetadataFactoryTest extends TestCase { - const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; - const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'; + const CLASS_NAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; + const PARENT_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'; + const INTERFACE_A_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceA'; + const INTERFACE_B_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityInterfaceB'; + const PARENT_INTERFACE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParentInterface'; - public function testLoadClassMetadata() + public function testLoadClassMetadataWithInterface() { $factory = new LazyLoadingMetadataFactory(new TestLoader()); - $metadata = $factory->getMetadataFor(self::PARENTCLASS); + $metadata = $factory->getMetadataFor(self::PARENT_CLASS); $constraints = array( new ConstraintA(array('groups' => array('Default', 'EntityParent'))), + new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA', 'EntityParent'))), ); $this->assertEquals($constraints, $metadata->getConstraints()); @@ -36,21 +42,33 @@ public function testLoadClassMetadata() public function testMergeParentConstraints() { $factory = new LazyLoadingMetadataFactory(new TestLoader()); - $metadata = $factory->getMetadataFor(self::CLASSNAME); + $metadata = $factory->getMetadataFor(self::CLASS_NAME); $constraints = array( new ConstraintA(array('groups' => array( 'Default', + 'Entity', + ))), + new ConstraintA(array('groups' => array( + 'Default', + 'EntityParent', + 'Entity', + ))), + new ConstraintA(array('groups' => array( + 'Default', + 'EntityInterfaceA', 'EntityParent', 'Entity', ))), new ConstraintA(array('groups' => array( 'Default', - 'EntityInterface', + 'EntityInterfaceB', 'Entity', ))), new ConstraintA(array('groups' => array( 'Default', + 'EntityParentInterface', + 'EntityInterfaceB', 'Entity', ))), ); @@ -60,52 +78,134 @@ public function testMergeParentConstraints() public function testWriteMetadataToCache() { - $cache = $this->getMock('Symfony\Component\Validator\Mapping\Cache\CacheInterface'); + $cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock(); $factory = new LazyLoadingMetadataFactory(new TestLoader(), $cache); - $tester = $this; - $constraints = array( + $parentClassConstraints = array( new ConstraintA(array('groups' => array('Default', 'EntityParent'))), + new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA', 'EntityParent'))), + ); + $interfaceAConstraints = array( + new ConstraintA(array('groups' => array('Default', 'EntityInterfaceA'))), ); $cache->expects($this->never()) ->method('has'); - $cache->expects($this->once()) + $cache->expects($this->exactly(2)) ->method('read') - ->with($this->equalTo(self::PARENTCLASS)) + ->withConsecutive( + array($this->equalTo(self::PARENT_CLASS)), + array($this->equalTo(self::INTERFACE_A_CLASS)) + ) ->will($this->returnValue(false)); - $cache->expects($this->once()) + $cache->expects($this->exactly(2)) ->method('write') - ->will($this->returnCallback(function ($metadata) use ($tester, $constraints) { - $tester->assertEquals($constraints, $metadata->getConstraints()); - })); - - $metadata = $factory->getMetadataFor(self::PARENTCLASS); - - $this->assertEquals(self::PARENTCLASS, $metadata->getClassName()); - $this->assertEquals($constraints, $metadata->getConstraints()); + ->withConsecutive( + $this->callback(function ($metadata) use ($interfaceAConstraints) { + return $interfaceAConstraints == $metadata->getConstraints(); + }), + $this->callback(function ($metadata) use ($parentClassConstraints) { + return $parentClassConstraints == $metadata->getConstraints(); + }) + ); + + $metadata = $factory->getMetadataFor(self::PARENT_CLASS); + + $this->assertEquals(self::PARENT_CLASS, $metadata->getClassName()); + $this->assertEquals($parentClassConstraints, $metadata->getConstraints()); } public function testReadMetadataFromCache() { - $loader = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); - $cache = $this->getMock('Symfony\Component\Validator\Mapping\Cache\CacheInterface'); + $loader = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); + $cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock(); $factory = new LazyLoadingMetadataFactory($loader, $cache); - $tester = $this; - $metadata = new ClassMetadata(self::PARENTCLASS); + $metadata = new ClassMetadata(self::PARENT_CLASS); $metadata->addConstraint(new ConstraintA()); + $parentClass = self::PARENT_CLASS; + $interfaceClass = self::INTERFACE_A_CLASS; + $loader->expects($this->never()) ->method('loadClassMetadata'); $cache->expects($this->never()) ->method('has'); - $cache->expects($this->once()) + $cache->expects($this->exactly(2)) ->method('read') - ->will($this->returnValue($metadata)); + ->withConsecutive( + array(self::PARENT_CLASS), + array(self::INTERFACE_A_CLASS) + ) + ->willReturnCallback(function ($name) use ($metadata, $parentClass, $interfaceClass) { + if ($parentClass == $name) { + return $metadata; + } + + return new ClassMetadata($interfaceClass); + }); + + $this->assertEquals($metadata, $factory->getMetadataFor(self::PARENT_CLASS)); + } - $this->assertEquals($metadata, $factory->getMetadataFor(self::PARENTCLASS)); + /** + * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException + */ + public function testNonClassNameStringValues() + { + $testedValue = 'error@example.com'; + $loader = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); + $cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock(); + $factory = new LazyLoadingMetadataFactory($loader, $cache); + $cache + ->expects($this->never()) + ->method('read'); + $factory->getMetadataFor($testedValue); + } + + public function testMetadataCacheWithRuntimeConstraint() + { + $cache = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock(); + $factory = new LazyLoadingMetadataFactory(new TestLoader(), $cache); + + $cache + ->expects($this->any()) + ->method('write') + ->will($this->returnCallback(function ($metadata) { serialize($metadata); })) + ; + + $cache->expects($this->any()) + ->method('read') + ->will($this->returnValue(false)); + + $metadata = $factory->getMetadataFor(self::PARENT_CLASS); + $metadata->addConstraint(new Callback(function () {})); + + $this->assertCount(3, $metadata->getConstraints()); + + $metadata = $factory->getMetadataFor(self::CLASS_NAME); + + $this->assertCount(6, $metadata->getConstraints()); + } + + public function testGroupsFromParent() + { + $reader = new \Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader(); + $factory = new LazyLoadingMetadataFactory($reader); + $metadata = $factory->getMetadataFor('Symfony\Component\Validator\Tests\Fixtures\EntityStaticCarTurbo'); + $groups = array(); + + foreach ($metadata->getPropertyMetadata('wheels') as $propertyMetadata) { + $constraints = $propertyMetadata->getConstraints(); + $groups = array_replace($groups, $constraints[0]->groups); + } + + $this->assertCount(4, $groups); + $this->assertContains('Default', $groups); + $this->assertContains('EntityStaticCarTurbo', $groups); + $this->assertContains('EntityStaticCar', $groups); + $this->assertContains('EntityStaticVehicle', $groups); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/GetterMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/GetterMetadataTest.php index 078159971af83..05aef47e84aaf 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/GetterMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/GetterMetadataTest.php @@ -11,16 +11,17 @@ namespace Symfony\Component\Validator\Tests\Mapping; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\GetterMetadata; use Symfony\Component\Validator\Tests\Fixtures\Entity; -class GetterMetadataTest extends \PHPUnit_Framework_TestCase +class GetterMetadataTest extends TestCase { const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; public function testInvalidPropertyName() { - $this->setExpectedException('Symfony\Component\Validator\Exception\ValidatorException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Validator\Exception\ValidatorException'); new GetterMetadata(self::CLASSNAME, 'foobar'); } @@ -47,7 +48,7 @@ public function testGetPropertyValueFromOverriddenPublicGetter() public function testGetPropertyValueFromIsser() { $entity = new Entity(); - $metadata = new GetterMetadata(self::CLASSNAME, 'valid'); + $metadata = new GetterMetadata(self::CLASSNAME, 'valid', 'isValid'); $this->assertEquals('valid', $metadata->getPropertyValue($entity)); } @@ -59,4 +60,13 @@ public function testGetPropertyValueFromHasser() $this->assertEquals('permissions', $metadata->getPropertyValue($entity)); } + + /** + * @expectedException \Symfony\Component\Validator\Exception\ValidatorException + * @expectedExceptionMessage The hasLastName() method does not exist in class Symfony\Component\Validator\Tests\Fixtures\Entity. + */ + public function testUndefinedMethodNameThrowsException() + { + new GetterMetadata(self::CLASSNAME, 'lastName', 'hasLastName'); + } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/LegacyElementMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/LegacyElementMetadataTest.php index b68c88293f194..1fb2e6e7c8148 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/LegacyElementMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/LegacyElementMetadataTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Tests\Mapping; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\ElementMetadata; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; @@ -18,7 +19,7 @@ /** * @group legacy */ -class LegacyElementMetadataTest extends \PHPUnit_Framework_TestCase +class LegacyElementMetadataTest extends TestCase { protected $metadata; diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php index 75ab3a7bbb5b8..6e0119349918b 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php @@ -12,18 +12,19 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; use Doctrine\Common\Annotations\AnnotationReader; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Range; -use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; -class AnnotationLoaderTest extends \PHPUnit_Framework_TestCase +class AnnotationLoaderTest extends TestCase { public function testLoadClassMetadataReturnsTrueIfSuccessful() { @@ -53,7 +54,7 @@ public function testLoadClassMetadata() $expected->setGroupSequence(array('Foo', 'Entity')); $expected->addConstraint(new ConstraintA()); $expected->addConstraint(new Callback(array('Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'))); - $expected->addConstraint(new Callback('validateMe')); + $expected->addConstraint(new Callback(array('callback' => 'validateMe', 'payload' => 'foo'))); $expected->addConstraint(new Callback('validateMeStatic')); $expected->addPropertyConstraint('firstName', new NotNull()); $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); @@ -68,7 +69,7 @@ public function testLoadClassMetadata() 'choices' => array('A', 'B'), ))); $expected->addGetterConstraint('lastName', new NotNull()); - $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterMethodConstraint('valid', 'isValid', new IsTrue()); $expected->addGetterConstraint('permissions', new IsTrue()); // load reflection class so that the comparison passes @@ -123,7 +124,7 @@ public function testLoadClassMetadataAndMerge() $expected->setGroupSequence(array('Foo', 'Entity')); $expected->addConstraint(new ConstraintA()); $expected->addConstraint(new Callback(array('Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'))); - $expected->addConstraint(new Callback('validateMe')); + $expected->addConstraint(new Callback(array('callback' => 'validateMe', 'payload' => 'foo'))); $expected->addConstraint(new Callback('validateMeStatic')); $expected->addPropertyConstraint('firstName', new NotNull()); $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); @@ -138,7 +139,7 @@ public function testLoadClassMetadataAndMerge() 'choices' => array('A', 'B'), ))); $expected->addGetterConstraint('lastName', new NotNull()); - $expected->addGetterConstraint('valid', new IsTrue()); + $expected->addGetterMethodConstraint('valid', 'isValid', new IsTrue()); $expected->addGetterConstraint('permissions', new IsTrue()); // load reflection class so that the comparison passes diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php index 09e6e449e0309..6fee7b6ff5407 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php @@ -11,10 +11,11 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; -class FilesLoaderTest extends \PHPUnit_Framework_TestCase +class FilesLoaderTest extends TestCase { public function testCallsGetFileLoaderInstanceForeachPath() { @@ -43,6 +44,6 @@ public function getFilesLoader(LoaderInterface $loader) public function getFileLoader() { - return $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + return $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php index 647a568c2c3cc..0d28b0a399e48 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/LoaderChainTest.php @@ -11,24 +11,25 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\LoaderChain; -class LoaderChainTest extends \PHPUnit_Framework_TestCase +class LoaderChainTest extends TestCase { public function testAllLoadersAreCalled() { $metadata = new ClassMetadata('\stdClass'); - $loader1 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader1 = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); $loader1->expects($this->once()) - ->method('loadClassMetadata') - ->with($this->equalTo($metadata)); + ->method('loadClassMetadata') + ->with($this->equalTo($metadata)); - $loader2 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader2 = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); $loader2->expects($this->once()) - ->method('loadClassMetadata') - ->with($this->equalTo($metadata)); + ->method('loadClassMetadata') + ->with($this->equalTo($metadata)); $chain = new LoaderChain(array( $loader1, @@ -42,15 +43,15 @@ public function testReturnsTrueIfAnyLoaderReturnedTrue() { $metadata = new ClassMetadata('\stdClass'); - $loader1 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader1 = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); $loader1->expects($this->any()) - ->method('loadClassMetadata') - ->will($this->returnValue(true)); + ->method('loadClassMetadata') + ->will($this->returnValue(true)); - $loader2 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader2 = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); $loader2->expects($this->any()) - ->method('loadClassMetadata') - ->will($this->returnValue(false)); + ->method('loadClassMetadata') + ->will($this->returnValue(false)); $chain = new LoaderChain(array( $loader1, @@ -64,15 +65,15 @@ public function testReturnsFalseIfNoLoaderReturnedTrue() { $metadata = new ClassMetadata('\stdClass'); - $loader1 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader1 = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); $loader1->expects($this->any()) - ->method('loadClassMetadata') - ->will($this->returnValue(false)); + ->method('loadClassMetadata') + ->will($this->returnValue(false)); - $loader2 = $this->getMock('Symfony\Component\Validator\Mapping\Loader\LoaderInterface'); + $loader2 = $this->getMockBuilder('Symfony\Component\Validator\Mapping\Loader\LoaderInterface')->getMock(); $loader2->expects($this->any()) - ->method('loadClassMetadata') - ->will($this->returnValue(false)); + ->method('loadClassMetadata') + ->will($this->returnValue(false)); $chain = new LoaderChain(array( $loader1, diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php index db4076cb7090f..069ccd322929e 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/StaticMethodLoaderTest.php @@ -11,11 +11,12 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; -class StaticMethodLoaderTest extends \PHPUnit_Framework_TestCase +class StaticMethodLoaderTest extends TestCase { private $errorLevel; diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php index e6326b9a3154d..100e4fa062fc1 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php @@ -11,21 +11,23 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Constraints\Regex; -use Symfony\Component\Validator\Constraints\IsTrue; +use Symfony\Component\Validator\Constraints\Traverse; use Symfony\Component\Validator\Exception\MappingException; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; -class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase +class XmlFileLoaderTest extends TestCase { public function testLoadClassMetadataReturnsTrueIfSuccessful() { @@ -57,6 +59,7 @@ public function testLoadClassMetadata() $expected->addConstraint(new Callback('validateMe')); $expected->addConstraint(new Callback('validateMeStatic')); $expected->addConstraint(new Callback(array('Symfony\Component\Validator\Tests\Fixtures\CallbackClass', 'callback'))); + $expected->addConstraint(new Traverse(false)); $expected->addPropertyConstraint('firstName', new NotNull()); $expected->addPropertyConstraint('firstName', new Range(array('min' => 3))); $expected->addPropertyConstraint('firstName', new Choice(array('A', 'B'))); @@ -111,7 +114,7 @@ public function testThrowExceptionIfDocTypeIsSet() $loader = new XmlFileLoader(__DIR__.'/withdoctype.xml'); $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); - $this->setExpectedException('\Symfony\Component\Validator\Exception\MappingException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('\Symfony\Component\Validator\Exception\MappingException'); $loader->loadClassMetadata($metadata); } @@ -126,7 +129,7 @@ public function testDoNotModifyStateIfExceptionIsThrown() try { $loader->loadClassMetadata($metadata); } catch (MappingException $e) { - $this->setExpectedException('\Symfony\Component\Validator\Exception\MappingException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('\Symfony\Component\Validator\Exception\MappingException'); $loader->loadClassMetadata($metadata); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php index 2ca64122aafae..680c2cce0a62d 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php @@ -11,19 +11,20 @@ namespace Symfony\Component\Validator\Tests\Mapping\Loader; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Range; -use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; -class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase +class YamlFileLoaderTest extends TestCase { public function testLoadClassMetadataReturnsFalseIfEmpty() { @@ -31,6 +32,10 @@ public function testLoadClassMetadataReturnsFalseIfEmpty() $metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity'); $this->assertFalse($loader->loadClassMetadata($metadata)); + + $r = new \ReflectionProperty($loader, 'classes'); + $r->setAccessible(true); + $this->assertSame(array(), $r->getValue($loader)); } /** @@ -64,7 +69,7 @@ public function testDoNotModifyStateIfExceptionIsThrown() $loader->loadClassMetadata($metadata); } catch (\InvalidArgumentException $e) { // Call again. Again an exception should be thrown - $this->setExpectedException('\InvalidArgumentException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('\InvalidArgumentException'); $loader->loadClassMetadata($metadata); } } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml index 689184ecf2d81..b1426cf4a90cd 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/constraint-mapping.xml @@ -31,6 +31,11 @@ callback + + + false + + diff --git a/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php index fafde341ac062..f0726c863f01a 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php @@ -11,13 +11,14 @@ namespace Symfony\Component\Validator\Tests\Mapping; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Mapping\MemberMetadata; use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint; use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; use Symfony\Component\Validator\Tests\Fixtures\ConstraintB; -class MemberMetadataTest extends \PHPUnit_Framework_TestCase +class MemberMetadataTest extends TestCase { protected $metadata; @@ -61,7 +62,7 @@ public function testLegacyAddOtherConstraintDoesNotSetMemberToCascaded() public function testAddConstraintRequiresClassConstraints() { - $this->setExpectedException('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Validator\Exception\ConstraintDefinitionException'); $this->metadata->addConstraint(new ClassConstraint()); } diff --git a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php index f411d950e1708..9fea435dff279 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php @@ -11,17 +11,18 @@ namespace Symfony\Component\Validator\Tests\Mapping; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\PropertyMetadata; use Symfony\Component\Validator\Tests\Fixtures\Entity; -class PropertyMetadataTest extends \PHPUnit_Framework_TestCase +class PropertyMetadataTest extends TestCase { const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'; public function testInvalidPropertyName() { - $this->setExpectedException('Symfony\Component\Validator\Exception\ValidatorException'); + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Validator\Exception\ValidatorException'); new PropertyMetadata(self::CLASSNAME, 'foobar'); } @@ -42,4 +43,14 @@ public function testGetPropertyValueFromOverriddenPrivateProperty() $this->assertTrue($metadata->isPublic($entity)); $this->assertEquals('Overridden data', $metadata->getPropertyValue($entity)); } + + public function testGetPropertyValueFromRemovedProperty() + { + $entity = new Entity('foobar'); + $metadata = new PropertyMetadata(self::CLASSNAME, 'internal'); + $metadata->name = 'test'; + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Validator\Exception\ValidatorException'); + $metadata->getPropertyValue($entity); + } } diff --git a/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php b/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php index 8a766611cd4f9..64b3f78934f1d 100644 --- a/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php +++ b/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php @@ -11,21 +11,38 @@ namespace Symfony\Component\Validator\Tests\Resources; -class TranslationFilesTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class TranslationFilesTest extends TestCase { /** * @dataProvider provideTranslationFiles */ public function testTranslationFileIsValid($filePath) { - \PHPUnit_Util_XML::loadfile($filePath, false, false, true); + if (class_exists('PHPUnit_Util_XML')) { + \PHPUnit_Util_XML::loadfile($filePath, false, false, true); + } else { + \PHPUnit\Util\XML::loadfile($filePath, false, false, true); + } + + $this->addToAssertionCount(1); } public function provideTranslationFiles() { return array_map( function ($filePath) { return (array) $filePath; }, - glob(dirname(dirname(__DIR__)).'/Resources/translations/*.xlf') + glob(\dirname(\dirname(__DIR__)).'/Resources/translations/*.xlf') + ); + } + + public function testNorwegianAlias() + { + $this->assertFileEquals( + \dirname(\dirname(__DIR__)).'/Resources/translations/validators.nb.xlf', + \dirname(\dirname(__DIR__)).'/Resources/translations/validators.no.xlf', + 'The NO locale should be an alias for the NB variant of the Norwegian language.' ); } } diff --git a/src/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php b/src/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php index a8b9af9de85c9..235e1780d9078 100644 --- a/src/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php +++ b/src/Symfony/Component/Validator/Tests/Util/PropertyPathTest.php @@ -11,9 +11,10 @@ namespace Symfony\Component\Validator\Tests\Util; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Util\PropertyPath; -class PropertyPathTest extends \PHPUnit_Framework_TestCase +class PropertyPathTest extends TestCase { /** * @dataProvider provideAppendPaths diff --git a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php index 6995d25817988..0394431a4eae0 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Validator\Tests\Validator; use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Expression; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Traverse; @@ -29,8 +31,6 @@ /** * Verifies that a validator satisfies the API of Symfony 2.5+. * - * @since 2.5 - * * @author Bernhard Schussek */ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest @@ -41,8 +41,6 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest protected $validator; /** - * @param MetadataFactoryInterface $metadataFactory - * * @return ValidatorInterface */ abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array()); @@ -550,10 +548,10 @@ public function testMetadataMustImplementClassMetadataInterface() { $entity = new Entity(); - $metadata = $this->getMock('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata'); + $metadata = $this->getMockBuilder('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata')->getMock(); $metadata->expects($this->any()) ->method('getClassName') - ->will($this->returnValue(get_class($entity))); + ->will($this->returnValue(\get_class($entity))); $this->metadataFactory->addMetadata($metadata); @@ -569,10 +567,10 @@ public function testReferenceMetadataMustImplementClassMetadataInterface() $entity = new Entity(); $entity->reference = new Reference(); - $metadata = $this->getMock('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata'); + $metadata = $this->getMockBuilder('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata')->getMock(); $metadata->expects($this->any()) ->method('getClassName') - ->will($this->returnValue(get_class($entity->reference))); + ->will($this->returnValue(\get_class($entity->reference))); $this->metadataFactory->addMetadata($metadata); @@ -590,8 +588,8 @@ public function testLegacyPropertyMetadataMustImplementPropertyMetadataInterface $entity = new Entity(); // Legacy interface - $propertyMetadata = $this->getMock('Symfony\Component\Validator\MetadataInterface'); - $metadata = new FakeClassMetadata(get_class($entity)); + $propertyMetadata = $this->getMockBuilder('Symfony\Component\Validator\MetadataInterface')->getMock(); + $metadata = new FakeClassMetadata(\get_class($entity)); $metadata->addCustomPropertyMetadata('firstName', $propertyMetadata); $this->metadataFactory->addMetadata($metadata); @@ -651,6 +649,7 @@ public function testAccessCurrentObject() $called = false; $entity = new Entity(); $entity->firstName = 'Bernhard'; + $entity->data = array('firstName' => 'Bernhard'); $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, &$called) { $called = true; @@ -659,6 +658,7 @@ public function testAccessCurrentObject() $this->metadata->addConstraint(new Callback($callback)); $this->metadata->addPropertyConstraint('firstName', new Callback($callback)); + $this->metadata->addPropertyConstraint('data', new Collection(array('firstName' => new Expression('value == this.firstName')))); $this->validator->validate($entity); @@ -672,8 +672,8 @@ public function testInitializeObjectsOnFirstValidation() $entity->initialized = false; // prepare initializers that set "initialized" to true - $initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); - $initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); + $initializer1 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); + $initializer2 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); $initializer1->expects($this->once()) ->method('initialize') @@ -721,4 +721,22 @@ public function testPassConstraintToViolation() $this->assertCount(1, $violations); $this->assertSame($constraint, $violations[0]->getConstraint()); } + + public function testCollectionConstraitViolationHasCorrectContext() + { + $data = array( + 'foo' => 'fooValue', + ); + + // Missing field must not be the first in the collection validation + $constraint = new Collection(array( + 'foo' => new NotNull(), + 'bar' => new NotNull(), + )); + + $violations = $this->validate($data, $constraint); + + $this->assertCount(1, $violations); + $this->assertSame($constraint, $violations[0]->getConstraint()); + } } diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php index d78b67bee7e9a..47346324b88a3 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php @@ -23,8 +23,6 @@ /** * Verifies that a validator satisfies the API of Symfony < 2.5. * - * @since 2.5 - * * @author Bernhard Schussek * @group legacy */ @@ -269,8 +267,8 @@ public function testInitializeObjectsOnFirstValidation() $entity->initialized = false; // prepare initializers that set "initialized" to true - $initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); - $initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); + $initializer1 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); + $initializer2 = $this->getMockBuilder('Symfony\\Component\\Validator\\ObjectInitializerInterface')->getMock(); $initializer1->expects($this->once()) ->method('initialize') diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php index 678a3252a5a0b..8dd9adc87e136 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\Tests\Validator; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\Constraints\Valid; @@ -23,11 +24,9 @@ use Symfony\Component\Validator\Tests\Fixtures\Reference; /** - * @since 2.5 - * * @author Bernhard Schussek */ -abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase +abstract class AbstractValidatorTest extends TestCase { const ENTITY_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; @@ -846,7 +845,7 @@ public function testValidateProperty() public function testLegacyValidatePropertyFailsIfPropertiesNotSupported() { // $metadata does not implement PropertyMetadataContainerInterface - $metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface'); + $metadata = $this->getMockBuilder('Symfony\Component\Validator\MetadataInterface')->getMock(); $this->metadataFactory->addMetadataForValue('VALUE', $metadata); @@ -977,7 +976,7 @@ public function testValidatePropertyValueWithClassName() public function testLegacyValidatePropertyValueFailsIfPropertiesNotSupported() { // $metadata does not implement PropertyMetadataContainerInterface - $metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface'); + $metadata = $this->getMockBuilder('Symfony\Component\Validator\MetadataInterface')->getMock(); $this->metadataFactory->addMetadataForValue('VALUE', $metadata); @@ -1223,7 +1222,7 @@ public function testReplaceDefaultGroupWithObjectFromGroupSequenceProvider() $context->addViolation('Violation in Group 3'); }; - $metadata = new ClassMetadata(get_class($entity)); + $metadata = new ClassMetadata(\get_class($entity)); $metadata->addConstraint(new Callback(array( 'callback' => function () {}, 'groups' => 'Group 1', @@ -1259,7 +1258,7 @@ public function testReplaceDefaultGroupWithArrayFromGroupSequenceProvider() $context->addViolation('Violation in Group 3'); }; - $metadata = new ClassMetadata(get_class($entity)); + $metadata = new ClassMetadata(\get_class($entity)); $metadata->addConstraint(new Callback(array( 'callback' => function () {}, 'groups' => 'Group 1', diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php index b27e6454bea7a..1313fb9e31a7b 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php @@ -35,7 +35,7 @@ public function testEmptyGroupsArrayDoesNotTriggerDeprecation() { $entity = new Entity(); - $validatorContext = $this->getMock('Symfony\Component\Validator\Validator\ContextualValidatorInterface'); + $validatorContext = $this->getMockBuilder('Symfony\Component\Validator\Validator\ContextualValidatorInterface')->getMock(); $validatorContext ->expects($this->once()) ->method('validate') diff --git a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php index fbc863157d9af..77d586412b76b 100644 --- a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php +++ b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php @@ -11,10 +11,11 @@ namespace Symfony\Component\Validator\Tests; +use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\ValidatorBuilder; use Symfony\Component\Validator\ValidatorBuilderInterface; -class ValidatorBuilderTest extends \PHPUnit_Framework_TestCase +class ValidatorBuilderTest extends TestCase { /** * @var ValidatorBuilderInterface @@ -34,7 +35,7 @@ protected function tearDown() public function testAddObjectInitializer() { $this->assertSame($this->builder, $this->builder->addObjectInitializer( - $this->getMock('Symfony\Component\Validator\ObjectInitializerInterface') + $this->getMockBuilder('Symfony\Component\Validator\ObjectInitializerInterface')->getMock() )); } @@ -86,21 +87,21 @@ public function testDisableAnnotationMapping() public function testSetMetadataCache() { $this->assertSame($this->builder, $this->builder->setMetadataCache( - $this->getMock('Symfony\Component\Validator\Mapping\Cache\CacheInterface')) + $this->getMockBuilder('Symfony\Component\Validator\Mapping\Cache\CacheInterface')->getMock()) ); } public function testSetConstraintValidatorFactory() { $this->assertSame($this->builder, $this->builder->setConstraintValidatorFactory( - $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface')) + $this->getMockBuilder('Symfony\Component\Validator\ConstraintValidatorFactoryInterface')->getMock()) ); } public function testSetTranslator() { $this->assertSame($this->builder, $this->builder->setTranslator( - $this->getMock('Symfony\Component\Translation\TranslatorInterface')) + $this->getMockBuilder('Symfony\Component\Translation\TranslatorInterface')->getMock()) ); } diff --git a/src/Symfony/Component/Validator/Util/PropertyPath.php b/src/Symfony/Component/Validator/Util/PropertyPath.php index 3ef8a8b1dc5da..4108a02c24f25 100644 --- a/src/Symfony/Component/Validator/Util/PropertyPath.php +++ b/src/Symfony/Component/Validator/Util/PropertyPath.php @@ -16,8 +16,6 @@ * * For more extensive functionality, use Symfony's PropertyAccess component. * - * @since 2.5 - * * @author Bernhard Schussek */ class PropertyPath @@ -39,7 +37,7 @@ class PropertyPath public static function append($basePath, $subPath) { if ('' !== (string) $subPath) { - if ('[' === $subPath{0}) { + if ('[' === $subPath[0]) { return $basePath.$subPath; } diff --git a/src/Symfony/Component/Validator/Validation.php b/src/Symfony/Component/Validator/Validation.php index 1693f7311add4..a469b47e25ea2 100644 --- a/src/Symfony/Component/Validator/Validation.php +++ b/src/Symfony/Component/Validator/Validation.php @@ -21,7 +21,7 @@ final class Validation /** * The Validator API provided by Symfony 2.4 and older. * - * @deprecated use API_VERSION_2_5_BC instead. + * @deprecated use API_VERSION_2_5_BC instead */ const API_VERSION_2_4 = 1; @@ -42,7 +42,7 @@ final class Validation * If you want to configure the validator, use * {@link createValidatorBuilder()} instead. * - * @return ValidatorInterface The new validator. + * @return ValidatorInterface The new validator */ public static function createValidator() { @@ -52,7 +52,7 @@ public static function createValidator() /** * Creates a configurable builder for validator objects. * - * @return ValidatorBuilderInterface The new builder. + * @return ValidatorBuilderInterface The new builder */ public static function createValidatorBuilder() { diff --git a/src/Symfony/Component/Validator/ValidationVisitor.php b/src/Symfony/Component/Validator/ValidationVisitor.php index 838646260f36b..14fd88b5c6847 100644 --- a/src/Symfony/Component/Validator/ValidationVisitor.php +++ b/src/Symfony/Component/Validator/ValidationVisitor.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Validator; -@trigger_error('The '.__NAMESPACE__.'\ValidationVisitor class is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\ValidationVisitor class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\Exception\NoSuchMetadataException; @@ -27,55 +27,24 @@ */ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionContextInterface { - /** - * @var mixed - */ private $root; - - /** - * @var MetadataFactoryInterface - */ private $metadataFactory; - - /** - * @var ConstraintValidatorFactoryInterface - */ private $validatorFactory; - - /** - * @var TranslatorInterface - */ private $translator; - - /** - * @var null|string - */ private $translationDomain; - - /** - * @var array - */ private $objectInitializers; - - /** - * @var ConstraintViolationList - */ private $violations; - - /** - * @var array - */ private $validatedObjects = array(); /** * Creates a new validation visitor. * - * @param mixed $root The value passed to the validator. - * @param MetadataFactoryInterface $metadataFactory The factory for obtaining metadata instances. - * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating constraint validators. - * @param TranslatorInterface $translator The translator for translating violation messages. - * @param string|null $translationDomain The domain of the translation messages. - * @param ObjectInitializerInterface[] $objectInitializers The initializers for preparing objects before validation. + * @param mixed $root The value passed to the validator + * @param MetadataFactoryInterface $metadataFactory The factory for obtaining metadata instances + * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating constraint validators + * @param TranslatorInterface $translator The translator for translating violation messages + * @param string|null $translationDomain The domain of the translation messages + * @param ObjectInitializerInterface[] $objectInitializers The initializers for preparing objects before validation * * @throws UnexpectedTypeException If any of the object initializers is not an instance of ObjectInitializerInterface */ @@ -123,7 +92,7 @@ public function validate($value, $group, $propertyPath, $traverse = false, $deep return; } - if (is_object($value)) { + if (\is_object($value)) { $hash = spl_object_hash($value); // Exit, if the object is already validated for the current group @@ -149,10 +118,10 @@ public function validate($value, $group, $propertyPath, $traverse = false, $deep // Validate arrays recursively by default, otherwise every driver needs // to implement special handling for arrays. // https://github.com/symfony/symfony/issues/6246 - if (is_array($value) || ($traverse && $value instanceof \Traversable)) { + if (\is_array($value) || ($traverse && $value instanceof \Traversable)) { foreach ($value as $key => $element) { // Ignore any scalar values in the collection - if (is_object($element) || is_array($element)) { + if (\is_object($element) || \is_array($element)) { // Only repeat the traversal if $deep is set $this->validate($element, $group, $propertyPath.'['.$key.']', $deep, $deep); } diff --git a/src/Symfony/Component/Validator/ValidationVisitorInterface.php b/src/Symfony/Component/Validator/ValidationVisitorInterface.php index 0ab7b7366776c..28d492132b8a1 100644 --- a/src/Symfony/Component/Validator/ValidationVisitorInterface.php +++ b/src/Symfony/Component/Validator/ValidationVisitorInterface.php @@ -56,14 +56,14 @@ interface ValidationVisitorInterface * does not find metadata for the given value, it will fail with an * exception. * - * @param mixed $value The value to validate. - * @param string $group The validation group to validate. - * @param string $propertyPath The current property path in the validation graph. - * @param bool $traverse Whether to traverse the value if it is traversable. - * @param bool $deep Whether to traverse nested traversable values recursively. + * @param mixed $value The value to validate + * @param string $group The validation group to validate + * @param string $propertyPath The current property path in the validation graph + * @param bool $traverse Whether to traverse the value if it is traversable + * @param bool $deep Whether to traverse nested traversable values recursively * - * @throws Exception\NoSuchMetadataException If no metadata can be found for - * the given value. + * @throws Exception\NoSuchMetadataException if no metadata can be found for + * the given value */ public function validate($value, $group, $propertyPath, $traverse = false, $deep = false); @@ -73,10 +73,10 @@ public function validate($value, $group, $propertyPath, $traverse = false, $deep * This method implements the Visitor design pattern. See also * {@link ValidationVisitorInterface}. * - * @param MetadataInterface $metadata The metadata holding the constraints. - * @param mixed $value The value to validate. - * @param string $group The validation group to validate. - * @param string $propertyPath The current property path in the validation graph. + * @param MetadataInterface $metadata The metadata holding the constraints + * @param mixed $value The value to validate + * @param string $group The validation group to validate + * @param string $propertyPath The current property path in the validation graph */ public function visit(MetadataInterface $metadata, $value, $group, $propertyPath); } diff --git a/src/Symfony/Component/Validator/Validator.php b/src/Symfony/Component/Validator/Validator.php index 4da27e7b375a0..1aa5fd18d6f5e 100644 --- a/src/Symfony/Component/Validator/Validator.php +++ b/src/Symfony/Component/Validator/Validator.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Validator; -@trigger_error('The '.__NAMESPACE__.'\Validator class is deprecated since version 2.5 and will be removed in 3.0. Use the Symfony\Component\Validator\Validator\RecursiveValidator class instead.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\Validator class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Validator\Validator\RecursiveValidator class instead.', E_USER_DEPRECATED); use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\Constraints\Valid; @@ -28,38 +28,14 @@ */ class Validator implements ValidatorInterface, Mapping\Factory\MetadataFactoryInterface { - /** - * @var MetadataFactoryInterface - */ private $metadataFactory; - - /** - * @var ConstraintValidatorFactoryInterface - */ private $validatorFactory; - - /** - * @var TranslatorInterface - */ private $translator; - - /** - * @var null|string - */ private $translationDomain; - - /** - * @var array - */ private $objectInitializers; - public function __construct( - MetadataFactoryInterface $metadataFactory, - ConstraintValidatorFactoryInterface $validatorFactory, - TranslatorInterface $translator, - $translationDomain = 'validators', - array $objectInitializers = array() - ) { + public function __construct(MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, TranslatorInterface $translator, $translationDomain = 'validators', array $objectInitializers = array()) + { $this->metadataFactory = $metadataFactory; $this->validatorFactory = $validatorFactory; $this->translator = $translator; @@ -108,7 +84,7 @@ public function validate($value, $groups = null, $traverse = false, $deep = fals /** * {@inheritdoc} * - * @throws ValidatorException If the metadata for the value does not support properties. + * @throws ValidatorException if the metadata for the value does not support properties */ public function validateProperty($containingValue, $property, $groups = null) { @@ -118,7 +94,7 @@ public function validateProperty($containingValue, $property, $groups = null) if (!$metadata instanceof PropertyMetadataContainerInterface) { $valueAsString = is_scalar($containingValue) ? '"'.$containingValue.'"' - : 'the value of type '.gettype($containingValue); + : 'the value of type '.\gettype($containingValue); throw new ValidatorException(sprintf('The metadata for %s does not support properties.', $valueAsString)); } @@ -139,24 +115,24 @@ public function validateProperty($containingValue, $property, $groups = null) /** * {@inheritdoc} * - * @throws ValidatorException If the metadata for the value does not support properties. + * @throws ValidatorException if the metadata for the value does not support properties */ public function validatePropertyValue($containingValue, $property, $value, $groups = null) { - $visitor = $this->createVisitor(is_object($containingValue) ? $containingValue : $value); + $visitor = $this->createVisitor(\is_object($containingValue) ? $containingValue : $value); $metadata = $this->metadataFactory->getMetadataFor($containingValue); if (!$metadata instanceof PropertyMetadataContainerInterface) { $valueAsString = is_scalar($containingValue) ? '"'.$containingValue.'"' - : 'the value of type '.gettype($containingValue); + : 'the value of type '.\gettype($containingValue); - throw new ValidatorException(sprintf('The metadata for '.$valueAsString.' does not support properties.')); + throw new ValidatorException(sprintf('The metadata for %s does not support properties.', $valueAsString)); } // If $containingValue is passed as class name, take $value as root // and start the traversal with an empty property path - $propertyPath = is_object($containingValue) ? $property : ''; + $propertyPath = \is_object($containingValue) ? $property : ''; foreach ($this->resolveGroups($groups) as $group) { if (!$metadata->hasPropertyMetadata($property)) { @@ -178,7 +154,7 @@ public function validateValue($value, $constraints, $groups = null) { $context = new ExecutionContext($this->createVisitor($value), $this->translator, $this->translationDomain); - $constraints = is_array($constraints) ? $constraints : array($constraints); + $constraints = \is_array($constraints) ? $constraints : array($constraints); foreach ($constraints as $constraint) { if ($constraint instanceof Valid) { @@ -194,12 +170,7 @@ public function validateValue($value, $constraints, $groups = null) // // * Otherwise the validated group is propagated. - throw new ValidatorException( - sprintf( - 'The constraint %s cannot be validated. Use the method validate() instead.', - get_class($constraint) - ) - ); + throw new ValidatorException(sprintf('The constraint %s cannot be validated. Use the method validate() instead.', \get_class($constraint))); } $context->validateValue($value, $constraint, '', $groups); @@ -226,7 +197,7 @@ private function createVisitor($root) } /** - * @param null|string|string[] $groups + * @param string|string[]|null $groups * * @return string[] */ diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php index 048d6c7545f84..c8b545b54b4a6 100644 --- a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php +++ b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php @@ -12,13 +12,12 @@ namespace Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\ConstraintViolationListInterface; /** * A validator in a specific execution context. * - * @since 2.5 - * * @author Bernhard Schussek */ interface ContextualValidatorInterface @@ -31,7 +30,7 @@ interface ContextualValidatorInterface * * @param string $path The path to append * - * @return ContextualValidatorInterface This validator + * @return $this */ public function atPath($path); @@ -41,14 +40,11 @@ public function atPath($path); * If no constraint is passed, the constraint * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed. * - * @param mixed $value The value to validate - * @param Constraint|Constraint[] $constraints The constraint(s) to validate - * against - * @param array|null $groups The validation groups to - * validate. If none is given, - * "Default" is assumed + * @param mixed $value The value to validate + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against + * @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed * - * @return ContextualValidatorInterface This validator + * @return $this */ public function validate($value, $constraints = null, $groups = null); @@ -56,12 +52,11 @@ public function validate($value, $constraints = null, $groups = null); * Validates a property of an object against the constraints specified * for this property. * - * @param object $object The object - * @param string $propertyName The name of the validated property - * @param array|null $groups The validation groups to validate. If - * none is given, "Default" is assumed + * @param object $object The object + * @param string $propertyName The name of the validated property + * @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed * - * @return ContextualValidatorInterface This validator + * @return $this */ public function validateProperty($object, $propertyName, $groups = null); @@ -69,14 +64,12 @@ public function validateProperty($object, $propertyName, $groups = null); * Validates a value against the constraints specified for an object's * property. * - * @param object|string $objectOrClass The object or its class name - * @param string $propertyName The name of the property - * @param mixed $value The value to validate against the - * property's constraints - * @param array|null $groups The validation groups to validate. If - * none is given, "Default" is assumed + * @param object|string $objectOrClass The object or its class name + * @param string $propertyName The name of the property + * @param mixed $value The value to validate against the property's constraints + * @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed * - * @return ContextualValidatorInterface This validator + * @return $this */ public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null); diff --git a/src/Symfony/Component/Validator/Validator/LegacyValidator.php b/src/Symfony/Component/Validator/Validator/LegacyValidator.php index e35f4c91401b3..c4d818b39facc 100644 --- a/src/Symfony/Component/Validator/Validator/LegacyValidator.php +++ b/src/Symfony/Component/Validator/Validator/LegacyValidator.php @@ -11,13 +11,11 @@ namespace Symfony\Component\Validator\Validator; -@trigger_error('The '.__NAMESPACE__.'\LegacyValidator class is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\LegacyValidator class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); /** * A validator that supports both the API of Symfony < 2.5 and Symfony 2.5+. * - * @since 2.5 - * * @author Bernhard Schussek * * @see \Symfony\Component\Validator\ValidatorInterface diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index e27f6f637fc36..14456d8c9114c 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -14,6 +14,7 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; +use Symfony\Component\Validator\Context\ExecutionContext; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\NoSuchMetadataException; @@ -33,40 +34,15 @@ /** * Recursive implementation of {@link ContextualValidatorInterface}. * - * @since 2.5 - * * @author Bernhard Schussek */ class RecursiveContextualValidator implements ContextualValidatorInterface { - /** - * @var ExecutionContextInterface - */ private $context; - - /** - * @var string - */ private $defaultPropertyPath; - - /** - * @var array - */ private $defaultGroups; - - /** - * @var MetadataFactoryInterface - */ private $metadataFactory; - - /** - * @var ConstraintValidatorFactoryInterface - */ private $validatorFactory; - - /** - * @var ObjectInitializerInterface[] - */ private $objectInitializers; /** @@ -112,13 +88,18 @@ public function validate($value, $constraints = null, $groups = null) $previousMetadata = $this->context->getMetadata(); $previousPath = $this->context->getPropertyPath(); $previousGroup = $this->context->getGroup(); + $previousConstraint = null; + + if ($this->context instanceof ExecutionContext || method_exists($this->context, 'getConstraint')) { + $previousConstraint = $this->context->getConstraint(); + } // If explicit constraints are passed, validate the value against // those constraints if (null !== $constraints) { // You can pass a single constraint or an array of constraints // Make sure to deal with an array in the rest of the code - if (!is_array($constraints)) { + if (!\is_array($constraints)) { $constraints = array($constraints); } @@ -127,8 +108,8 @@ public function validate($value, $constraints = null, $groups = null) $this->validateGenericNode( $value, - null, - is_object($value) ? spl_object_hash($value) : null, + $previousObject, + \is_object($value) ? spl_object_hash($value) : null, $metadata, $this->defaultPropertyPath, $groups, @@ -140,12 +121,16 @@ public function validate($value, $constraints = null, $groups = null) $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath); $this->context->setGroup($previousGroup); + if (null !== $previousConstraint) { + $this->context->setConstraint($previousConstraint); + } + return $this; } // If an object is passed without explicit constraints, validate that // object against the constraints defined for the object's class - if (is_object($value)) { + if (\is_object($value)) { $this->validateObject( $value, $this->defaultPropertyPath, @@ -162,7 +147,7 @@ public function validate($value, $constraints = null, $groups = null) // If an array is passed without explicit constraints, validate each // object in the array - if (is_array($value)) { + if (\is_array($value)) { $this->validateEachObjectIn( $value, $this->defaultPropertyPath, @@ -177,11 +162,7 @@ public function validate($value, $constraints = null, $groups = null) return $this; } - throw new RuntimeException(sprintf( - 'Cannot validate values of type "%s" automatically. Please '. - 'provide a constraint.', - gettype($value) - )); + throw new RuntimeException(sprintf('Cannot validate values of type "%s" automatically. Please provide a constraint.', \gettype($value))); } /** @@ -194,12 +175,7 @@ public function validateProperty($object, $propertyName, $groups = null) if (!$classMetadata instanceof ClassMetadataInterface) { // Cannot be UnsupportedMetadataException because of BC with // Symfony < 2.5 - throw new ValidatorException(sprintf( - 'The metadata factory should return instances of '. - '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. - 'got: "%s".', - is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) - )); + throw new ValidatorException(sprintf('The metadata factory should return instances of "\Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', \is_object($classMetadata) ? \get_class($classMetadata) : \gettype($classMetadata))); } $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); @@ -245,18 +221,13 @@ public function validatePropertyValue($objectOrClass, $propertyName, $value, $gr if (!$classMetadata instanceof ClassMetadataInterface) { // Cannot be UnsupportedMetadataException because of BC with // Symfony < 2.5 - throw new ValidatorException(sprintf( - 'The metadata factory should return instances of '. - '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. - 'got: "%s".', - is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) - )); + throw new ValidatorException(sprintf('The metadata factory should return instances of "\Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', \is_object($classMetadata) ? \get_class($classMetadata) : \gettype($classMetadata))); } $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName); $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; - if (is_object($objectOrClass)) { + if (\is_object($objectOrClass)) { $object = $objectOrClass; $cacheKey = spl_object_hash($objectOrClass); $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName); @@ -304,18 +275,19 @@ public function getViolations() /** * Normalizes the given group or list of groups to an array. * - * @param mixed $groups The groups to normalize + * @param string|GroupSequence|(string|GroupSequence)[] $groups The groups to normalize * - * @return array A group array + * @return (string|GroupSequence)[] A group array */ protected function normalizeGroups($groups) { - if (is_array($groups)) { + if (\is_array($groups)) { return $groups; } return array($groups); } + /** * Validates an object against the constraints defined for its class. * @@ -326,7 +298,7 @@ protected function normalizeGroups($groups) * * @param object $object The object to cascade * @param string $propertyPath The current property path - * @param string[] $groups The validated groups + * @param (string|GroupSequence)[] $groups The validated groups * @param int $traversalStrategy The strategy for traversing the * cascaded object * @param ExecutionContextInterface $context The current execution context @@ -345,12 +317,7 @@ private function validateObject($object, $propertyPath, array $groups, $traversa $classMetadata = $this->metadataFactory->getMetadataFor($object); if (!$classMetadata instanceof ClassMetadataInterface) { - throw new UnsupportedMetadataException(sprintf( - 'The metadata factory should return instances of '. - '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '. - 'got: "%s".', - is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata) - )); + throw new UnsupportedMetadataException(sprintf('The metadata factory should return instances of "Symfony\Component\Validator\Mapping\ClassMetadataInterface", got: "%s".', \is_object($classMetadata) ? \get_class($classMetadata) : \gettype($classMetadata))); } $this->validateClassNode( @@ -392,9 +359,9 @@ private function validateObject($object, $propertyPath, array $groups, $traversa * objects are iterated as well. Nested arrays are always iterated, * regardless of the value of $recursive. * - * @param array|\Traversable $collection The collection + * @param iterable $collection The collection * @param string $propertyPath The current property path - * @param string[] $groups The validated groups + * @param (string|GroupSequence)[] $groups The validated groups * @param bool $stopRecursion Whether to disable * recursive iteration. For * backwards compatibility @@ -413,7 +380,7 @@ private function validateEachObjectIn($collection, $propertyPath, array $groups, } foreach ($collection as $key => $value) { - if (is_array($value)) { + if (\is_array($value)) { // Arrays are always cascaded, independent of the specified // traversal strategy // (BC with Symfony < 2.5) @@ -430,7 +397,7 @@ private function validateEachObjectIn($collection, $propertyPath, array $groups, // Scalar and null values in the collection are ignored // (BC with Symfony < 2.5) - if (is_object($value)) { + if (\is_object($value)) { $this->validateObject( $value, $propertyPath.'['.$key.']', @@ -474,7 +441,7 @@ private function validateEachObjectIn($collection, $propertyPath, array $groups, * the object * @param string $propertyPath The property path leading * to the object - * @param string[] $groups The groups in which the + * @param (string|GroupSequence)[] $groups The groups in which the * object should be validated * @param string[]|null $cascadedGroups The groups in which * cascaded objects should @@ -510,7 +477,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m $defaultOverridden = false; // Use the object hash for group sequences - $groupHash = is_object($group) ? spl_object_hash($group) : $group; + $groupHash = \is_object($group) ? spl_object_hash($group) : $group; if ($context->isGroupValidated($cacheKey, $groupHash)) { // Skip this group when validating the properties and when @@ -575,7 +542,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m // If no more groups should be validated for the property nodes, // we can safely quit - if (0 === count($groups)) { + if (0 === \count($groups)) { return; } @@ -586,12 +553,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m // returns two metadata objects, not just one foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { if (!$propertyMetadata instanceof PropertyMetadataInterface) { - throw new UnsupportedMetadataException(sprintf( - 'The property metadata instances should implement '. - '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '. - 'got: "%s".', - is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata) - )); + throw new UnsupportedMetadataException(sprintf('The property metadata instances should implement "Symfony\Component\Validator\Mapping\PropertyMetadataInterface", got: "%s".', \is_object($propertyMetadata) ? \get_class($propertyMetadata) : \gettype($propertyMetadata))); } $propertyValue = $propertyMetadata->getPropertyValue($object); @@ -632,11 +594,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m if (!$object instanceof \Traversable) { // Must throw a ConstraintDefinitionException for backwards // compatibility reasons with Symfony < 2.5 - throw new ConstraintDefinitionException(sprintf( - 'Traversal was enabled for "%s", but this class '. - 'does not implement "\Traversable".', - get_class($object) - )); + throw new ConstraintDefinitionException(sprintf('Traversal was enabled for "%s", but this class does not implement "\Traversable".', \get_class($object))); } $this->validateEachObjectIn( @@ -672,7 +630,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m * value * @param string $propertyPath The property path leading * to the value - * @param string[] $groups The groups in which the + * @param (string|GroupSequence)[] $groups The groups in which the * value should be validated * @param string[]|null $cascadedGroups The groups in which * cascaded objects should @@ -711,7 +669,7 @@ private function validateGenericNode($value, $object, $cacheKey, MetadataInterfa $this->validateInGroup($value, $cacheKey, $metadata, $group, $context); } - if (0 === count($groups)) { + if (0 === \count($groups)) { return; } @@ -722,7 +680,7 @@ private function validateGenericNode($value, $object, $cacheKey, MetadataInterfa $cascadingStrategy = $metadata->getCascadingStrategy(); // Quit unless we have an array or a cascaded object - if (!is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) { + if (!\is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) { return; } @@ -737,11 +695,9 @@ private function validateGenericNode($value, $object, $cacheKey, MetadataInterfa // The $cascadedGroups property is set, if the "Default" group is // overridden by a group sequence // See validateClassNode() - $cascadedGroups = count($cascadedGroups) > 0 - ? $cascadedGroups - : $groups; + $cascadedGroups = null !== $cascadedGroups && \count($cascadedGroups) > 0 ? $cascadedGroups : $groups; - if (is_array($value)) { + if (\is_array($value)) { // Arrays are always traversed, independent of the specified // traversal strategy // (BC with Symfony < 2.5) @@ -792,7 +748,7 @@ private function validateGenericNode($value, $object, $cacheKey, MetadataInterfa * @param int $traversalStrategy The strategy used for * traversing the value * @param GroupSequence $groupSequence The group sequence - * @param string[]|null $cascadedGroup The group that should + * @param string|null $cascadedGroup The group that should * be passed to cascaded * objects instead of * the group sequence @@ -800,7 +756,7 @@ private function validateGenericNode($value, $object, $cacheKey, MetadataInterfa */ private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context) { - $violationCount = count($context->getViolations()); + $violationCount = \count($context->getViolations()); $cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null; foreach ($groupSequence->groups as $groupInSequence) { @@ -832,7 +788,7 @@ private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataIn } // Abort sequence validation if a violation was generated - if (count($context->getViolations()) > $violationCount) { + if (\count($context->getViolations()) > $violationCount) { break; } } diff --git a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php index e4dc0fb057d29..bf0050d6f24a8 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php @@ -24,30 +24,13 @@ /** * Recursive implementation of {@link ValidatorInterface}. * - * @since 2.5 - * * @author Bernhard Schussek */ class RecursiveValidator implements ValidatorInterface, LegacyValidatorInterface { - /** - * @var ExecutionContextFactoryInterface - */ protected $contextFactory; - - /** - * @var MetadataFactoryInterface - */ protected $metadataFactory; - - /** - * @var ConstraintValidatorFactoryInterface - */ protected $validatorFactory; - - /** - * @var ObjectInitializerInterface[] - */ protected $objectInitializers; /** @@ -117,7 +100,7 @@ public function hasMetadataFor($object) */ public function validate($value, $groups = null, $traverse = false, $deep = false) { - $numArgs = func_num_args(); + $numArgs = \func_num_args(); // Use new signature if constraints are given in the second argument if (self::testConstraints($groups) && ($numArgs < 3 || 3 === $numArgs && self::testGroups($traverse))) { @@ -151,7 +134,7 @@ public function validateProperty($object, $propertyName, $groups = null) public function validatePropertyValue($objectOrClass, $propertyName, $value, $groups = null) { // If a class name is passed, take $value as root - return $this->startContext(is_object($objectOrClass) ? $objectOrClass : $value) + return $this->startContext(\is_object($objectOrClass) ? $objectOrClass : $value) ->validatePropertyValue($objectOrClass, $propertyName, $value, $groups) ->getViolations(); } @@ -178,11 +161,11 @@ public function getMetadataFactory() private static function testConstraints($constraints) { - return null === $constraints || $constraints instanceof Constraint || (is_array($constraints) && (0 === count($constraints) || current($constraints) instanceof Constraint)); + return null === $constraints || $constraints instanceof Constraint || (\is_array($constraints) && (0 === \count($constraints) || current($constraints) instanceof Constraint)); } private static function testGroups($groups) { - return null === $groups || is_string($groups) || $groups instanceof GroupSequence || (is_array($groups) && (0 === count($groups) || is_string(current($groups)) || current($groups) instanceof GroupSequence)); + return null === $groups || \is_string($groups) || $groups instanceof GroupSequence || (\is_array($groups) && (0 === \count($groups) || \is_string(current($groups)) || current($groups) instanceof GroupSequence)); } } diff --git a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php index 3aafa34ea09cc..78157465ab5ce 100644 --- a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php +++ b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface; @@ -19,8 +20,6 @@ /** * Validates PHP values against constraints. * - * @since 2.5 - * * @author Bernhard Schussek */ interface ValidatorInterface extends MetadataFactoryInterface @@ -31,14 +30,11 @@ interface ValidatorInterface extends MetadataFactoryInterface * If no constraint is passed, the constraint * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed. * - * @param mixed $value The value to validate - * @param Constraint|Constraint[] $constraints The constraint(s) to validate - * against - * @param array|null $groups The validation groups to - * validate. If none is given, - * "Default" is assumed + * @param mixed $value The value to validate + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against + * @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed * - * @return ConstraintViolationListInterface A list of constraint violations. + * @return ConstraintViolationListInterface A list of constraint violations * If the list is empty, validation * succeeded */ @@ -48,12 +44,11 @@ public function validate($value, $constraints = null, $groups = null); * Validates a property of an object against the constraints specified * for this property. * - * @param object $object The object - * @param string $propertyName The name of the validated property - * @param array|null $groups The validation groups to validate. If - * none is given, "Default" is assumed + * @param object $object The object + * @param string $propertyName The name of the validated property + * @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed * - * @return ConstraintViolationListInterface A list of constraint violations. + * @return ConstraintViolationListInterface A list of constraint violations * If the list is empty, validation * succeeded */ @@ -63,14 +58,12 @@ public function validateProperty($object, $propertyName, $groups = null); * Validates a value against the constraints specified for an object's * property. * - * @param object|string $objectOrClass The object or its class name - * @param string $propertyName The name of the property - * @param mixed $value The value to validate against the - * property's constraints - * @param array|null $groups The validation groups to validate. If - * none is given, "Default" is assumed + * @param object|string $objectOrClass The object or its class name + * @param string $propertyName The name of the property + * @param mixed $value The value to validate against the property's constraints + * @param string|GroupSequence|(string|GroupSequence)[]|null $groups The validation groups to validate. If none is given, "Default" is assumed * - * @return ConstraintViolationListInterface A list of constraint violations. + * @return ConstraintViolationListInterface A list of constraint violations * If the list is empty, validation * succeeded */ @@ -93,8 +86,6 @@ public function startContext(); * The returned validator adds all generated violations to the given * context. * - * @param ExecutionContextInterface $context The execution context - * * @return ContextualValidatorInterface The validator for that context */ public function inContext(ExecutionContextInterface $context); diff --git a/src/Symfony/Component/Validator/ValidatorBuilder.php b/src/Symfony/Component/Validator/ValidatorBuilder.php index 4a69976ed2b11..c99de8cc790f0 100644 --- a/src/Symfony/Component/Validator/ValidatorBuilder.php +++ b/src/Symfony/Component/Validator/ValidatorBuilder.php @@ -39,24 +39,9 @@ */ class ValidatorBuilder implements ValidatorBuilderInterface { - /** - * @var array - */ private $initializers = array(); - - /** - * @var array - */ private $xmlMappings = array(); - - /** - * @var array - */ private $yamlMappings = array(); - - /** - * @var array - */ private $methodMappings = array(); /** @@ -85,7 +70,7 @@ class ValidatorBuilder implements ValidatorBuilderInterface private $translator; /** - * @var null|string + * @var string|null */ private $translationDomain; @@ -235,7 +220,7 @@ public function disableAnnotationMapping() */ public function setMetadataFactory(MetadataFactoryInterface $metadataFactory) { - if (count($this->xmlMappings) > 0 || count($this->yamlMappings) > 0 || count($this->methodMappings) > 0 || null !== $this->annotationReader) { + if (\count($this->xmlMappings) > 0 || \count($this->yamlMappings) > 0 || \count($this->methodMappings) > 0 || null !== $this->annotationReader) { throw new ValidatorException('You cannot set a custom metadata factory after adding custom mappings. You should do either of both.'); } @@ -300,7 +285,7 @@ public function setTranslationDomain($translationDomain) */ public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor) { - @trigger_error('The '.__METHOD__.' method is deprecated since version 2.5 and will be removed in 3.0. The validator will function without a property accessor.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.5 and will be removed in 3.0. The validator will function without a property accessor.', E_USER_DEPRECATED); if (null !== $this->validatorFactory) { throw new ValidatorException('You cannot set a property accessor after setting a custom validator factory. Configure your validator factory instead.'); @@ -320,7 +305,7 @@ public function setApiVersion($apiVersion) { @trigger_error('The '.__METHOD__.' method is deprecated in version 2.7 and will be removed in version 3.0.', E_USER_DEPRECATED); - if (!in_array($apiVersion, array(Validation::API_VERSION_2_4, Validation::API_VERSION_2_5, Validation::API_VERSION_2_5_BC))) { + if (!\in_array($apiVersion, array(Validation::API_VERSION_2_4, Validation::API_VERSION_2_5, Validation::API_VERSION_2_5_BC))) { throw new InvalidArgumentException(sprintf('The requested API version is invalid: "%s"', $apiVersion)); } @@ -337,15 +322,15 @@ public function getValidator() if (!$metadataFactory) { $loaders = array(); - if (count($this->xmlMappings) > 1) { + if (\count($this->xmlMappings) > 1) { $loaders[] = new XmlFilesLoader($this->xmlMappings); - } elseif (1 === count($this->xmlMappings)) { + } elseif (1 === \count($this->xmlMappings)) { $loaders[] = new XmlFileLoader($this->xmlMappings[0]); } - if (count($this->yamlMappings) > 1) { + if (\count($this->yamlMappings) > 1) { $loaders[] = new YamlFilesLoader($this->yamlMappings); - } elseif (1 === count($this->yamlMappings)) { + } elseif (1 === \count($this->yamlMappings)) { $loaders[] = new YamlFileLoader($this->yamlMappings[0]); } @@ -359,9 +344,9 @@ public function getValidator() $loader = null; - if (count($loaders) > 1) { + if (\count($loaders) > 1) { $loader = new LoaderChain($loaders); - } elseif (1 === count($loaders)) { + } elseif (1 === \count($loaders)) { $loader = $loaders[0]; } diff --git a/src/Symfony/Component/Validator/ValidatorBuilderInterface.php b/src/Symfony/Component/Validator/ValidatorBuilderInterface.php index e15fb7aa14e67..1b0bd72996308 100644 --- a/src/Symfony/Component/Validator/ValidatorBuilderInterface.php +++ b/src/Symfony/Component/Validator/ValidatorBuilderInterface.php @@ -26,18 +26,16 @@ interface ValidatorBuilderInterface /** * Adds an object initializer to the validator. * - * @param ObjectInitializerInterface $initializer The initializer - * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addObjectInitializer(ObjectInitializerInterface $initializer); /** * Adds a list of object initializers to the validator. * - * @param array $initializers The initializer + * @param ObjectInitializerInterface[] $initializers * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addObjectInitializers(array $initializers); @@ -46,16 +44,16 @@ public function addObjectInitializers(array $initializers); * * @param string $path The path to the mapping file * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addXmlMapping($path); /** * Adds a list of XML constraint mapping files to the validator. * - * @param array $paths The paths to the mapping files + * @param string[] $paths The paths to the mapping files * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addXmlMappings(array $paths); @@ -64,16 +62,16 @@ public function addXmlMappings(array $paths); * * @param string $path The path to the mapping file * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addYamlMapping($path); /** * Adds a list of YAML constraint mappings file to the validator. * - * @param array $paths The paths to the mapping files + * @param string[] $paths The paths to the mapping files * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addYamlMappings(array $paths); @@ -82,68 +80,58 @@ public function addYamlMappings(array $paths); * * @param string $methodName The name of the method * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addMethodMapping($methodName); /** * Enables constraint mapping using the given static methods. * - * @param array $methodNames The names of the methods + * @param string[] $methodNames The names of the methods * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function addMethodMappings(array $methodNames); /** * Enables annotation based constraint mapping. * - * @param Reader $annotationReader The annotation reader to be used - * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function enableAnnotationMapping(Reader $annotationReader = null); /** * Disables annotation based constraint mapping. * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function disableAnnotationMapping(); /** * Sets the class metadata factory used by the validator. * - * @param MetadataFactoryInterface $metadataFactory The metadata factory - * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function setMetadataFactory(MetadataFactoryInterface $metadataFactory); /** * Sets the cache for caching class metadata. * - * @param CacheInterface $cache The cache instance - * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function setMetadataCache(CacheInterface $cache); /** * Sets the constraint validator factory used by the validator. * - * @param ConstraintValidatorFactoryInterface $validatorFactory The validator factory - * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function setConstraintValidatorFactory(ConstraintValidatorFactoryInterface $validatorFactory); /** * Sets the translator used for translating violation messages. * - * @param TranslatorInterface $translator The translator instance - * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function setTranslator(TranslatorInterface $translator); @@ -156,7 +144,7 @@ public function setTranslator(TranslatorInterface $translator); * * @param string $translationDomain The translation domain of the violation messages * - * @return ValidatorBuilderInterface The builder object + * @return $this */ public function setTranslationDomain($translationDomain); @@ -165,7 +153,7 @@ public function setTranslationDomain($translationDomain); * * @param PropertyAccessorInterface $propertyAccessor The property accessor * - * @return ValidatorBuilderInterface The builder object + * @return $this * * @deprecated since version 2.5, to be removed in 3.0. */ @@ -176,7 +164,7 @@ public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor) * * @param int $apiVersion The required API version * - * @return ValidatorBuilderInterface The builder object + * @return $this * * @see Validation::API_VERSION_2_5 * @see Validation::API_VERSION_2_5_BC @@ -187,7 +175,7 @@ public function setApiVersion($apiVersion); /** * Builds and returns a new validator object. * - * @return ValidatorInterface The built validator. + * @return ValidatorInterface The built validator */ public function getValidator(); } diff --git a/src/Symfony/Component/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/ValidatorInterface.php index cffc1388d7f6f..570ba99720dee 100644 --- a/src/Symfony/Component/Validator/ValidatorInterface.php +++ b/src/Symfony/Component/Validator/ValidatorInterface.php @@ -32,9 +32,9 @@ interface ValidatorInterface * disabled in Symfony 3.0. * * @param mixed $value The value to validate - * @param array|null $groups The validation groups to validate. - * @param bool $traverse Whether to traverse the value if it is traversable. - * @param bool $deep Whether to traverse nested traversable values recursively. + * @param array|null $groups The validation groups to validate + * @param bool $traverse Whether to traverse the value if it is traversable + * @param bool $deep Whether to traverse nested traversable values recursively * * @return ConstraintViolationListInterface A list of constraint violations. If the * list is empty, validation succeeded. @@ -47,9 +47,9 @@ public function validate($value, $groups = null, $traverse = false, $deep = fals * The accepted values depend on the {@link MetadataFactoryInterface} * implementation. * - * @param mixed $containingValue The value containing the property. - * @param string $property The name of the property to validate. - * @param array|null $groups The validation groups to validate. + * @param mixed $containingValue The value containing the property + * @param string $property The name of the property to validate + * @param array|null $groups The validation groups to validate * * @return ConstraintViolationListInterface A list of constraint violations. If the * list is empty, validation succeeded. @@ -62,11 +62,11 @@ public function validateProperty($containingValue, $property, $groups = null); * The accepted values depend on the {@link MetadataFactoryInterface} * implementation. * - * @param mixed $containingValue The value containing the property. + * @param mixed $containingValue The value containing the property * @param string $property The name of the property to validate * @param string $value The value to validate against the - * constraints of the property. - * @param array|null $groups The validation groups to validate. + * constraints of the property + * @param array|null $groups The validation groups to validate * * @return ConstraintViolationListInterface A list of constraint violations. If the * list is empty, validation succeeded. @@ -76,9 +76,9 @@ public function validatePropertyValue($containingValue, $property, $value, $grou /** * Validates a value against a constraint or a list of constraints. * - * @param mixed $value The value to validate. - * @param Constraint|Constraint[] $constraints The constraint(s) to validate against. - * @param array|null $groups The validation groups to validate. + * @param mixed $value The value to validate + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against + * @param array|null $groups The validation groups to validate * * @return ConstraintViolationListInterface A list of constraint violations. If the * list is empty, validation succeeded. @@ -92,7 +92,7 @@ public function validateValue($value, $constraints, $groups = null); /** * Returns the factory for metadata instances. * - * @return MetadataFactoryInterface The metadata factory. + * @return MetadataFactoryInterface The metadata factory * * @deprecated since version 2.5, to be removed in 3.0. * Use {@link Validator\ValidatorInterface::getMetadataFor()} or diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index d7d3877aed283..284841e2eb305 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -20,8 +20,6 @@ /** * Default implementation of {@link ConstraintViolationBuilderInterface}. * - * @since 2.5 - * * @author Bernhard Schussek * * @internal You should not instantiate or use this class. Code against @@ -29,59 +27,16 @@ */ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface { - /** - * @var ConstraintViolationList - */ private $violations; - - /** - * @var string - */ private $message; - - /** - * @var array - */ private $parameters; - - /** - * @var mixed - */ private $root; - - /** - * @var mixed - */ private $invalidValue; - - /** - * @var string - */ private $propertyPath; - - /** - * @var TranslatorInterface - */ private $translator; - - /** - * @var string|null - */ private $translationDomain; - - /** - * @var int|null - */ private $plural; - - /** - * @var Constraint - */ private $constraint; - - /** - * @var mixed - */ private $code; /** @@ -199,7 +154,7 @@ public function addViolation() $this->message, $this->plural, $this->parameters, - $this->translationDomain# + $this->translationDomain ); } catch (\InvalidArgumentException $e) { $translatedMessage = $this->translator->trans( diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php index e02d61b432d13..811b4842e888d 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php @@ -19,8 +19,6 @@ * Finally, call {@link addViolation()} to add the violation to the current * execution context. * - * @since 2.5 - * * @author Bernhard Schussek */ interface ConstraintViolationBuilderInterface @@ -33,7 +31,7 @@ interface ConstraintViolationBuilderInterface * * @param string $path The property path * - * @return ConstraintViolationBuilderInterface This builder + * @return $this */ public function atPath($path); @@ -43,7 +41,7 @@ public function atPath($path); * @param string $key The name of the parameter * @param string $value The value to be inserted in the parameter's place * - * @return ConstraintViolationBuilderInterface This builder + * @return $this */ public function setParameter($key, $value); @@ -54,7 +52,7 @@ public function setParameter($key, $value); * the values to be inserted in their place as * values * - * @return ConstraintViolationBuilderInterface This builder + * @return $this */ public function setParameters(array $parameters); @@ -64,7 +62,7 @@ public function setParameters(array $parameters); * * @param string $translationDomain The translation domain * - * @return ConstraintViolationBuilderInterface This builder + * @return $this * * @see \Symfony\Component\Translation\TranslatorInterface */ @@ -75,7 +73,7 @@ public function setTranslationDomain($translationDomain); * * @param mixed $invalidValue The invalid value * - * @return ConstraintViolationBuilderInterface This builder + * @return $this */ public function setInvalidValue($invalidValue); @@ -85,7 +83,7 @@ public function setInvalidValue($invalidValue); * * @param int $number The number for determining the plural form * - * @return ConstraintViolationBuilderInterface This builder + * @return $this * * @see \Symfony\Component\Translation\TranslatorInterface::transChoice() */ @@ -96,7 +94,7 @@ public function setPlural($number); * * @param string|null $code The violation code * - * @return ConstraintViolationBuilderInterface This builder + * @return $this */ public function setCode($code); @@ -105,7 +103,7 @@ public function setCode($code); * * @param mixed $cause The cause of the violation * - * @return ConstraintViolationBuilderInterface This builder + * @return $this */ public function setCause($cause); diff --git a/src/Symfony/Component/Validator/Violation/LegacyConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/LegacyConstraintViolationBuilder.php index 7410b0a6fc2d7..324e9d1e4dd4d 100644 --- a/src/Symfony/Component/Validator/Violation/LegacyConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/LegacyConstraintViolationBuilder.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Validator\Violation; -@trigger_error('The '.__NAMESPACE__.'\LegacyConstraintViolationBuilder class is deprecated since version 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); +@trigger_error('The '.__NAMESPACE__.'\LegacyConstraintViolationBuilder class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); use Symfony\Component\Validator\ExecutionContextInterface; diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json index 179e8cf0f0b34..99ccc33023d58 100644 --- a/src/Symfony/Component/Validator/composer.json +++ b/src/Symfony/Component/Validator/composer.json @@ -17,18 +17,20 @@ ], "require": { "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", "symfony/translation": "~2.4|~3.0.0" }, "require-dev": { - "symfony/http-foundation": "~2.1|~3.0.0", - "symfony/intl": "~2.4|~3.0.0", - "symfony/yaml": "~2.0,>=2.0.5|~3.0.0", + "symfony/http-foundation": "~2.3|~3.0.0", + "symfony/intl": "~2.7.25|^2.8.18|~3.2.5", + "symfony/yaml": "^2.0.5|~3.0.0", "symfony/config": "~2.2|~3.0.0", "symfony/property-access": "~2.3|~3.0.0", "symfony/expression-language": "~2.4|~3.0.0", "doctrine/annotations": "~1.0", "doctrine/cache": "~1.0", - "egulias/email-validator": "~1.2,>=1.2.1" + "egulias/email-validator": "^1.2.1" }, "suggest": { "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.", diff --git a/src/Symfony/Component/Validator/phpunit.xml.dist b/src/Symfony/Component/Validator/phpunit.xml.dist index cf8c343863e5d..5d07c4e64803d 100644 --- a/src/Symfony/Component/Validator/phpunit.xml.dist +++ b/src/Symfony/Component/Validator/phpunit.xml.dist @@ -1,10 +1,12 @@ diff --git a/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php b/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php index 98eede22d55d4..655262f4065ec 100644 --- a/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/AmqpCaster.php @@ -48,6 +48,15 @@ public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, { $prefix = Caster::PREFIX_VIRTUAL; + $a += array( + $prefix.'is_connected' => $c->isConnected(), + ); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPConnection\x00login"])) { + return $a; + } + // BC layer in the amqp lib if (method_exists($c, 'getReadTimeout')) { $timeout = $c->getReadTimeout(); @@ -56,13 +65,13 @@ public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, } $a += array( - $prefix.'isConnected' => $c->isConnected(), + $prefix.'is_connected' => $c->isConnected(), $prefix.'login' => $c->getLogin(), $prefix.'password' => $c->getPassword(), $prefix.'host' => $c->getHost(), - $prefix.'port' => $c->getPort(), $prefix.'vhost' => $c->getVhost(), - $prefix.'readTimeout' => $timeout, + $prefix.'port' => $c->getPort(), + $prefix.'read_timeout' => $timeout, ); return $a; @@ -73,11 +82,19 @@ public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNes $prefix = Caster::PREFIX_VIRTUAL; $a += array( - $prefix.'isConnected' => $c->isConnected(), - $prefix.'channelId' => $c->getChannelId(), - $prefix.'prefetchSize' => $c->getPrefetchSize(), - $prefix.'prefetchCount' => $c->getPrefetchCount(), + $prefix.'is_connected' => $c->isConnected(), + $prefix.'channel_id' => $c->getChannelId(), + ); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPChannel\x00connection"])) { + return $a; + } + + $a += array( $prefix.'connection' => $c->getConnection(), + $prefix.'prefetch_size' => $c->getPrefetchSize(), + $prefix.'prefetch_count' => $c->getPrefetchCount(), ); return $a; @@ -88,11 +105,19 @@ public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested) $prefix = Caster::PREFIX_VIRTUAL; $a += array( - $prefix.'name' => $c->getName(), $prefix.'flags' => self::extractFlags($c->getFlags()), - $prefix.'arguments' => $c->getArguments(), + ); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPQueue\x00name"])) { + return $a; + } + + $a += array( $prefix.'connection' => $c->getConnection(), $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'arguments' => $c->getArguments(), ); return $a; @@ -103,12 +128,24 @@ public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isN $prefix = Caster::PREFIX_VIRTUAL; $a += array( - $prefix.'name' => $c->getName(), $prefix.'flags' => self::extractFlags($c->getFlags()), - $prefix.'type' => isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType(), - $prefix.'arguments' => $c->getArguments(), - $prefix.'channel' => $c->getChannel(), + ); + + $type = isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType(); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPExchange\x00name"])) { + $a["\x00AMQPExchange\x00type"] = $type; + + return $a; + } + + $a += array( $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'type' => $type, + $prefix.'arguments' => $c->getArguments(), ); return $a; @@ -118,28 +155,37 @@ public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isN { $prefix = Caster::PREFIX_VIRTUAL; + $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPEnvelope\x00body"])) { + $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; + + return $a; + } + if (!($filter & Caster::EXCLUDE_VERBOSE)) { $a += array($prefix.'body' => $c->getBody()); } $a += array( - $prefix.'routingKey' => $c->getRoutingKey(), - $prefix.'deliveryTag' => $c->getDeliveryTag(), - $prefix.'deliveryMode' => new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()), - $prefix.'exchangeName' => $c->getExchangeName(), - $prefix.'isRedelivery' => $c->isRedelivery(), - $prefix.'contentType' => $c->getContentType(), - $prefix.'contentEncoding' => $c->getContentEncoding(), - $prefix.'type' => $c->getType(), - $prefix.'timestamp' => $c->getTimestamp(), + $prefix.'delivery_tag' => $c->getDeliveryTag(), + $prefix.'is_redelivery' => $c->isRedelivery(), + $prefix.'exchange_name' => $c->getExchangeName(), + $prefix.'routing_key' => $c->getRoutingKey(), + $prefix.'content_type' => $c->getContentType(), + $prefix.'content_encoding' => $c->getContentEncoding(), + $prefix.'headers' => $c->getHeaders(), + $prefix.'delivery_mode' => $deliveryMode, $prefix.'priority' => $c->getPriority(), + $prefix.'correlation_id' => $c->getCorrelationId(), + $prefix.'reply_to' => $c->getReplyTo(), $prefix.'expiration' => $c->getExpiration(), - $prefix.'userId' => $c->getUserId(), - $prefix.'appId' => $c->getAppId(), - $prefix.'messageId' => $c->getMessageId(), - $prefix.'replyTo' => $c->getReplyTo(), - $prefix.'correlationId' => $c->getCorrelationId(), - $prefix.'headers' => $c->getHeaders(), + $prefix.'message_id' => $c->getMessageId(), + $prefix.'timestamp' => $c->getTimeStamp(), + $prefix.'type' => $c->getType(), + $prefix.'user_id' => $c->getUserId(), + $prefix.'app_id' => $c->getAppId(), ); return $a; diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 23e72e87701ee..63862187ef3d0 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -36,15 +36,17 @@ class Caster /** * Casts objects to arrays and adds the dynamic property prefix. * - * @param object $obj The object to cast. - * @param \ReflectionClass $reflector The class reflector to use for inspecting the object definition. + * @param object $obj The object to cast + * @param \ReflectionClass $reflector The class reflector to use for inspecting the object definition * - * @return array The array-cast of the object, with prefixed dynamic properties. + * @return array The array-cast of the object, with prefixed dynamic properties */ public static function castObject($obj, \ReflectionClass $reflector) { if ($reflector->hasMethod('__debugInfo')) { $a = $obj->__debugInfo(); + } elseif ($obj instanceof \Closure) { + $a = array(); } else { $a = (array) $obj; } @@ -52,7 +54,7 @@ public static function castObject($obj, \ReflectionClass $reflector) if ($a) { $p = array_keys($a); foreach ($p as $i => $k) { - if (!isset($k[0]) || ("\0" !== $k[0] && !$reflector->hasProperty($k))) { + if (isset($k[0]) ? "\0" !== $k[0] && !$reflector->hasProperty($k) : \PHP_VERSION_ID >= 70200) { $p[$i] = self::PREFIX_DYNAMIC.$k; } elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) { $p[$i] = "\0".$reflector->getParentClass().'@anonymous'.strrchr($k, "\0"); @@ -70,9 +72,9 @@ public static function castObject($obj, \ReflectionClass $reflector) * By default, a single match in the $filter bit field filters properties out, following an "or" logic. * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. * - * @param array $a The array containing the properties to filter. - * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out. - * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set. + * @param array $a The array containing the properties to filter + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set * * @return array The filtered array */ @@ -87,10 +89,10 @@ public static function filter(array $a, $filter, array $listedProperties = array if (empty($v)) { $type |= self::EXCLUDE_EMPTY & $filter; } - if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) { + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { $type |= self::EXCLUDE_NOT_IMPORTANT; } - if ((self::EXCLUDE_VERBOSE & $filter) && in_array($k, $listedProperties, true)) { + if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { $type |= self::EXCLUDE_VERBOSE; } diff --git a/src/Symfony/Component/VarDumper/Caster/CutArrayStub.php b/src/Symfony/Component/VarDumper/Caster/CutArrayStub.php index f2a803053a6c0..0e4fb363d2d41 100644 --- a/src/Symfony/Component/VarDumper/Caster/CutArrayStub.php +++ b/src/Symfony/Component/VarDumper/Caster/CutArrayStub.php @@ -25,6 +25,6 @@ public function __construct(array $value, array $preservedKeys) parent::__construct($value); $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); - $this->cut -= count($this->preservedSubset); + $this->cut -= \count($this->preservedSubset); } } diff --git a/src/Symfony/Component/VarDumper/Caster/CutStub.php b/src/Symfony/Component/VarDumper/Caster/CutStub.php index 8781f5cf3c601..690338f542d97 100644 --- a/src/Symfony/Component/VarDumper/Caster/CutStub.php +++ b/src/Symfony/Component/VarDumper/Caster/CutStub.php @@ -24,31 +24,34 @@ public function __construct($value) { $this->value = $value; - switch (gettype($value)) { + switch (\gettype($value)) { case 'object': $this->type = self::TYPE_OBJECT; - $this->class = get_class($value); + $this->class = \get_class($value); $this->cut = -1; break; case 'array': $this->type = self::TYPE_ARRAY; $this->class = self::ARRAY_ASSOC; - $this->cut = $this->value = count($value); + $this->cut = $this->value = \count($value); break; case 'resource': case 'unknown type': + case 'resource (closed)': $this->type = self::TYPE_RESOURCE; $this->handle = (int) $value; - $this->class = @get_resource_type($value); + if ('Unknown' === $this->class = @get_resource_type($value)) { + $this->class = 'Closed'; + } $this->cut = -1; break; case 'string': $this->type = self::TYPE_STRING; $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; - $this->cut = self::STRING_BINARY === $this->class ? strlen($value) : mb_strlen($value, 'UTF-8'); + $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); $this->value = ''; break; } diff --git a/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php index f6573b34fd145..785d0270d7b05 100644 --- a/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php @@ -12,8 +12,8 @@ namespace Symfony\Component\VarDumper\Caster; use Doctrine\Common\Proxy\Proxy as CommonProxy; -use Doctrine\ORM\Proxy\Proxy as OrmProxy; use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; use Symfony\Component\VarDumper\Cloner\Stub; /** diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index 51c70dd5db9a8..72787d5e70854 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -11,8 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; -use Symfony\Component\VarDumper\Exception\ThrowingCasterException; use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; /** * Casts common Exception classes to array representation. @@ -65,14 +65,14 @@ public static function castThrowingCasterException(ThrowingCasterException $e, a $prefix = Caster::PREFIX_PROTECTED; $xPrefix = "\0Exception\0"; - if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace'])) { + if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace']) && $a[$xPrefix.'previous'] instanceof \Exception) { $b = (array) $a[$xPrefix.'previous']; array_unshift($b[$xPrefix.'trace'], array( - 'function' => 'new '.get_class($a[$xPrefix.'previous']), + 'function' => 'new '.\get_class($a[$xPrefix.'previous']), 'file' => $b[$prefix.'file'], 'line' => $b[$prefix.'line'], )); - $a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -1 - count($a[$xPrefix.'trace']->value)); + $a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -1 - \count($a[$xPrefix.'trace']->value)); } unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); @@ -90,7 +90,7 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is $frames = $trace->value; $a = array(); - $j = count($frames); + $j = \count($frames); if (0 > $i = $trace->sliceOffset) { $i = max(0, $j + $i); } @@ -126,7 +126,7 @@ public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $is true ); if (null !== $trace->sliceLength) { - $a = array_slice($a, 0, $trace->sliceLength, true); + $a = \array_slice($a, 0, $trace->sliceLength, true); } return $a; @@ -142,23 +142,28 @@ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $is if (isset($f['file'], $f['line'])) { if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { - $f['file'] = substr($f['file'], 0, -strlen($match[0])); + $f['file'] = substr($f['file'], 0, -\strlen($match[0])); $f['line'] = (int) $match[1]; } if (file_exists($f['file']) && 0 <= self::$srcContext) { $src[$f['file'].':'.$f['line']] = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], self::$srcContext); - if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) { - $template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem())); + if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { + $template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); - try { - $templateName = $template->getTemplateName(); - $templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName)); - $templateInfo = $template->getDebugInfo(); - if (isset($templateInfo[$f['line']])) { + $templateName = $template->getTemplateName(); + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (method_exists($template, 'getSourceContext')) { + $templateName = $template->getSourceContext()->getPath() ?: $templateName; + } + if ($templateSrc) { + $templateSrc = explode("\n", $templateSrc); $src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext); + } else { + $src[$templateName] = $templateInfo[$f['line']]; } - } catch (\Twig_Error_Loader $e) { } } } else { @@ -188,7 +193,7 @@ public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $is */ public static function filterTrace(&$trace, $dumpArgs, $offset = 0) { - @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use the castTraceStub method instead.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Use the castTraceStub method instead.', E_USER_DEPRECATED); if (0 > $offset || empty($trace[$offset])) { return $trace = null; @@ -229,12 +234,14 @@ private static function filterExceptionArray($xClass, array $a, $xPrefix, $filte } if (!($filter & Caster::EXCLUDE_VERBOSE)) { - array_unshift($trace, array( - 'function' => $xClass ? 'new '.$xClass : null, - 'file' => $a[Caster::PREFIX_PROTECTED.'file'], - 'line' => $a[Caster::PREFIX_PROTECTED.'line'], - )); - $a[$xPrefix.'trace'] = new TraceStub($trace); + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + array_unshift($trace, array( + 'function' => $xClass ? 'new '.$xClass : null, + 'file' => $a[Caster::PREFIX_PROTECTED.'file'], + 'line' => $a[Caster::PREFIX_PROTECTED.'line'], + )); + } + $a[$xPrefix.'trace'] = new TraceStub($trace, self::$traceArgs); } if (empty($a[$xPrefix.'previous'])) { unset($a[$xPrefix.'previous']); @@ -253,19 +260,24 @@ private static function extractSource(array $srcArray, $line, $srcContext) } $ltrim = 0; - while (' ' === $src[0][$ltrim] || "\t" === $src[0][$ltrim]) { - $i = $srcContext << 1; - while ($i > 0 && $src[0][$ltrim] === $src[$i][$ltrim]) { - --$i; - } - if ($i) { - break; + do { + $pad = null; + for ($i = $srcContext << 1; $i >= 0; --$i) { + if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { + if (null === $pad) { + $pad = $c; + } + if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { + break; + } + } } ++$ltrim; - } - if ($ltrim) { + } while (0 > $i && null !== $pad); + + if (--$ltrim) { foreach ($src as $i => $line) { - $src[$i] = substr($line, $ltrim); + $src[$i] = isset($line[$ltrim]) && "\r" !== $line[$ltrim] ? substr($line, $ltrim) : ltrim($line, " \t"); } } diff --git a/src/Symfony/Component/VarDumper/Caster/PdoCaster.php b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php index e60b9275fd89f..a2595328e2cb7 100644 --- a/src/Symfony/Component/VarDumper/Caster/PdoCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php @@ -70,7 +70,7 @@ public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested) } try { - $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(constant('PDO::ATTR_'.$k)); + $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); if ($v && isset($v[$attr[$k]])) { $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); } diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index 54157a6eb5a45..e96b993f90545 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -69,14 +69,27 @@ public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested) } $prefix = Caster::PREFIX_DYNAMIC; - unset($a['name'], $a[$prefix.'0'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']); + unset($a['name'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']); return $a; } public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested) { - return class_exists('ReflectionGenerator', false) ? self::castReflectionGenerator(new \ReflectionGenerator($c), $a, $stub, $isNested) : $a; + if (!class_exists('ReflectionGenerator', false)) { + return $a; + } + + // Cannot create ReflectionGenerator based on a terminated Generator + try { + $reflectionGenerator = new \ReflectionGenerator($c); + } catch (\Exception $e) { + $a[Caster::PREFIX_VIRTUAL.'closed'] = true; + + return $a; + } + + return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); } public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested) @@ -84,7 +97,7 @@ public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNes $prefix = Caster::PREFIX_VIRTUAL; $a += array( - $prefix.'type' => $c->__toString(), + $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : $c->__toString(), $prefix.'allowsNull' => $c->allowsNull(), $prefix.'isBuiltin' => $c->isBuiltin(), ); @@ -99,31 +112,33 @@ public static function castReflectionGenerator(\ReflectionGenerator $c, array $a if ($c->getThis()) { $a[$prefix.'this'] = new CutStub($c->getThis()); } - $x = $c->getFunction(); + $function = $c->getFunction(); $frame = array( - 'class' => isset($x->class) ? $x->class : null, - 'type' => isset($x->class) ? ($x->isStatic() ? '::' : '->') : null, - 'function' => $x->name, + 'class' => isset($function->class) ? $function->class : null, + 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, + 'function' => $function->name, 'file' => $c->getExecutingFile(), 'line' => $c->getExecutingLine(), ); if ($trace = $c->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS)) { - $x = new \ReflectionGenerator($c->getExecutingGenerator()); + $function = new \ReflectionGenerator($c->getExecutingGenerator()); array_unshift($trace, array( 'function' => 'yield', - 'file' => $x->getExecutingFile(), - 'line' => $x->getExecutingLine() - 1, + 'file' => $function->getExecutingFile(), + 'line' => $function->getExecutingLine() - 1, )); $trace[] = $frame; $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); } else { - $x = new FrameStub($frame, false, true); - $x = ExceptionCaster::castFrameStub($x, array(), $x, true); + $function = new FrameStub($frame, false, true); + $function = ExceptionCaster::castFrameStub($function, array(), $function, true); $a[$prefix.'executing'] = new EnumStub(array( - $frame['class'].$frame['type'].$frame['function'].'()' => $x[$prefix.'src'], + $frame['class'].$frame['type'].$frame['function'].'()' => $function[$prefix.'src'], )); } + $a[Caster::PREFIX_VIRTUAL.'closed'] = false; + return $a; } @@ -168,7 +183,9 @@ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, arra )); if (isset($a[$prefix.'returnType'])) { - $a[$prefix.'returnType'] = (string) $a[$prefix.'returnType']; + $v = $a[$prefix.'returnType']; + $v = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString(); + $a[$prefix.'returnType'] = $a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v; } if (isset($a[$prefix.'this'])) { $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); @@ -176,12 +193,12 @@ public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, arra foreach ($c->getParameters() as $v) { $k = '$'.$v->name; - if ($v->isPassedByReference()) { - $k = '&'.$k; - } if (method_exists($v, 'isVariadic') && $v->isVariadic()) { $k = '...'.$k; } + if ($v->isPassedByReference()) { + $k = '&'.$k; + } $a[$prefix.'parameters'][$k] = $v; } if (isset($a[$prefix.'parameters'])) { @@ -224,24 +241,18 @@ public static function castParameter(\ReflectionParameter $c, array $a, Stub $st 'position' => 'getPosition', 'isVariadic' => 'isVariadic', 'byReference' => 'isPassedByReference', + 'allowsNull' => 'allowsNull', )); - try { - if (method_exists($c, 'hasType')) { - if ($c->hasType()) { - $a[$prefix.'typeHint'] = $c->getType()->__toString(); - } - } elseif ($c->isArray()) { - $a[$prefix.'typeHint'] = 'array'; - } elseif (method_exists($c, 'isCallable') && $c->isCallable()) { - $a[$prefix.'typeHint'] = 'callable'; - } elseif ($v = $c->getClass()) { - $a[$prefix.'typeHint'] = $v->name; - } - } catch (\ReflectionException $e) { - if (preg_match('/^Class ([^ ]++) does not exist$/', $e->getMessage(), $m)) { - $a[$prefix.'typeHint'] = $m[1]; + if (method_exists($c, 'getType')) { + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString(); } + } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) { + $a[$prefix.'typeHint'] = $v[1]; + } + if (!isset($a[$prefix.'typeHint'])) { + unset($a[$prefix.'allowsNull']); } try { @@ -249,9 +260,13 @@ public static function castParameter(\ReflectionParameter $c, array $a, Stub $st if (method_exists($c, 'isDefaultValueConstant') && $c->isDefaultValueConstant()) { $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } } catch (\ReflectionException $e) { - if (isset($a[$prefix.'typeHint']) && $c->allowsNull()) { + if (isset($a[$prefix.'typeHint']) && $c->allowsNull() && !class_exists('ReflectionNamedType', false)) { $a[$prefix.'default'] = null; + unset($a[$prefix.'allowsNull']); } } diff --git a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php index 903641f69c636..d70c09ac4da62 100644 --- a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php @@ -45,7 +45,7 @@ public static function castStream($stream, array $a, Stub $stub, $isNested) public static function castStreamContext($stream, array $a, Stub $stub, $isNested) { - return stream_context_get_params($stream); + return @stream_context_get_params($stream) ?: $a; } public static function castGd($gd, array $a, Stub $stub, $isNested) diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php index 97f2146382148..73f152ab18b0c 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -29,30 +29,12 @@ class SplCaster public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested) { - $prefix = Caster::PREFIX_VIRTUAL; - $class = $stub->class; - $flags = $c->getFlags(); - - $b = array( - $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), - $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), - $prefix.'iteratorClass' => $c->getIteratorClass(), - $prefix.'storage' => $c->getArrayCopy(), - ); - - if ($class === 'ArrayObject') { - $a = $b; - } else { - if (!($flags & \ArrayObject::STD_PROP_LIST)) { - $c->setFlags(\ArrayObject::STD_PROP_LIST); - $a = Caster::castObject($c, new \ReflectionClass($class)); - $c->setFlags($flags); - } - - $a += $b; - } + return self::castSplArray($c, $a, $stub, $isNested); + } - return $a; + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); } public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested) @@ -71,7 +53,7 @@ public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, S $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); $a += array( - $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_KEEP) ? 'IT_MODE_KEEP' : 'IT_MODE_DELETE'), $mode), + $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), $prefix.'dllist' => iterator_to_array($c), ); $c->setIteratorMode($mode); @@ -180,10 +162,11 @@ public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $s $storage = array(); unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 - foreach ($c as $obj) { - $storage[spl_object_hash($obj)] = array( + $clone = clone $c; + foreach ($clone as $obj) { + $storage[] = array( 'object' => $obj, - 'info' => $c->getInfo(), + 'info' => $clone->getInfo(), ); } @@ -200,4 +183,27 @@ public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub return $a; } + + private static function castSplArray($c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $class = $stub->class; + $flags = $c->getFlags(); + + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + $a = Caster::castObject($c, new \ReflectionClass($class)); + $c->setFlags($flags); + } + $a += array( + $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + ); + if ($c instanceof \ArrayObject) { + $a[$prefix.'iteratorClass'] = $c->getIteratorClass(); + } + $a[$prefix.'storage'] = $c->getArrayCopy(); + + return $a; + } } diff --git a/src/Symfony/Component/VarDumper/Caster/StubCaster.php b/src/Symfony/Component/VarDumper/Caster/StubCaster.php index ebad5ba9844e4..62bceb53a85d4 100644 --- a/src/Symfony/Component/VarDumper/Caster/StubCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/StubCaster.php @@ -29,8 +29,10 @@ public static function castStub(Stub $c, array $a, Stub $stub, $isNested) $stub->handle = $c->handle; $stub->cut = $c->cut; - return array(); + $a = array(); } + + return $a; } public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested) @@ -41,7 +43,7 @@ public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNe public static function cutInternals($obj, array $a, Stub $stub, $isNested) { if ($isNested) { - $stub->cut += count($a); + $stub->cut += \count($a); return array(); } @@ -60,7 +62,7 @@ public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested) if ($c->value) { foreach (array_keys($c->value) as $k) { - $keys[] = Caster::PREFIX_VIRTUAL.$k; + $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; } // Preserve references with array_combine() $a = array_combine($keys, $c->value); diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 50dc79161bacf..86eab78ab4f75 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -89,6 +89,7 @@ abstract class AbstractCloner implements ClonerInterface 'AMQPEnvelope' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castEnvelope', 'ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject', + 'ArrayIterator' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayIterator', 'SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList', 'SplFileInfo' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileInfo', 'SplFileObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileObject', @@ -111,6 +112,7 @@ abstract class AbstractCloner implements ClonerInterface ':pgsql result' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castResult', ':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess', ':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', + ':persistent stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext', ':xml' => 'Symfony\Component\VarDumper\Caster\XmlResourceCaster::castXml', ); @@ -125,7 +127,7 @@ abstract class AbstractCloner implements ClonerInterface private $filter = 0; /** - * @param callable[]|null $casters A map of casters. + * @param callable[]|null $casters A map of casters * * @see addCasters */ @@ -135,7 +137,7 @@ public function __construct(array $casters = null) $casters = static::$defaultCasters; } $this->addCasters($casters); - $this->useExt = extension_loaded('symfony_debug'); + $this->useExt = \extension_loaded('symfony_debug'); } /** @@ -146,7 +148,7 @@ public function __construct(array $casters = null) * Resource types are to be prefixed with a `:`, * see e.g. static::$defaultCasters. * - * @param callable[] $casters A map of casters. + * @param callable[] $casters A map of casters */ public function addCasters(array $casters) { @@ -178,10 +180,10 @@ public function setMaxString($maxString) /** * Clones a PHP variable. * - * @param mixed $var Any PHP variable. - * @param int $filter A bit field of Caster::EXCLUDE_* constants. + * @param mixed $var Any PHP variable + * @param int $filter A bit field of Caster::EXCLUDE_* constants * - * @return Data The cloned variable represented by a Data object. + * @return Data The cloned variable represented by a Data object */ public function cloneVar($var, $filter = 0) { @@ -204,19 +206,19 @@ public function cloneVar($var, $filter = 0) /** * Effectively clones the PHP variable. * - * @param mixed $var Any PHP variable. + * @param mixed $var Any PHP variable * - * @return array The cloned variable represented in an array. + * @return array The cloned variable represented in an array */ abstract protected function doClone($var); /** * Casts an object to an array representation. * - * @param Stub $stub The Stub for the casted object. - * @param bool $isNested True if the object is nested in the dumped structure. + * @param Stub $stub The Stub for the casted object + * @param bool $isNested True if the object is nested in the dumped structure * - * @return array The object casted as array. + * @return array The object casted as array */ protected function castObject(Stub $stub, $isNested) { @@ -253,10 +255,10 @@ protected function castObject(Stub $stub, $isNested) /** * Casts a resource to an array representation. * - * @param Stub $stub The Stub for the casted resource. - * @param bool $isNested True if the object is nested in the dumped structure. + * @param Stub $stub The Stub for the casted resource + * @param bool $isNested True if the object is nested in the dumped structure * - * @return array The resource casted as array. + * @return array The resource casted as array */ protected function castResource(Stub $stub, $isNested) { @@ -276,20 +278,20 @@ protected function castResource(Stub $stub, $isNested) /** * Calls a custom caster. * - * @param callable $callback The caster. - * @param object|resource $obj The object/resource being casted. - * @param array $a The result of the previous cast for chained casters. - * @param Stub $stub The Stub for the casted object/resource. - * @param bool $isNested True if $obj is nested in the dumped structure. + * @param callable $callback The caster + * @param object|resource $obj The object/resource being casted + * @param array $a The result of the previous cast for chained casters + * @param Stub $stub The Stub for the casted object/resource + * @param bool $isNested True if $obj is nested in the dumped structure * - * @return array The casted object/resource. + * @return array The casted object/resource */ private function callCaster($callback, $obj, $a, $stub, $isNested) { try { - $cast = call_user_func($callback, $obj, $a, $stub, $isNested, $this->filter); + $cast = \call_user_func($callback, $obj, $a, $stub, $isNested, $this->filter); - if (is_array($cast)) { + if (\is_array($cast)) { $a = $cast; } } catch (\Exception $e) { @@ -312,7 +314,7 @@ public function handleError($type, $msg, $file, $line, $context) } if ($this->prevErrorHandler) { - return call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context); + return \call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context); } return false; diff --git a/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php b/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php index c1df5933dbf2e..7ed287a2ddf0d 100644 --- a/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php +++ b/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php @@ -19,9 +19,9 @@ interface ClonerInterface /** * Clones a PHP variable. * - * @param mixed $var Any PHP variable. + * @param mixed $var Any PHP variable * - * @return Data The cloned variable represented by a Data object. + * @return Data The cloned variable represented by a Data object */ public function cloneVar($var); } diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 0aa13ef1ecaf9..efdd0e8a27e75 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -22,7 +22,7 @@ class Data private $useRefHandles = -1; /** - * @param array $data A array as returned by ClonerInterface::cloneVar(). + * @param array $data An array as returned by ClonerInterface::cloneVar() */ public function __construct(array $data) { @@ -30,7 +30,7 @@ public function __construct(array $data) } /** - * @return array The raw data structure. + * @return array The raw data structure */ public function getRawData() { @@ -40,9 +40,9 @@ public function getRawData() /** * Returns a depth limited clone of $this. * - * @param int $maxDepth The max dumped depth level. + * @param int $maxDepth The max dumped depth level * - * @return self A clone of $this. + * @return self A clone of $this */ public function withMaxDepth($maxDepth) { @@ -55,9 +55,9 @@ public function withMaxDepth($maxDepth) /** * Limits the number of elements per depth level. * - * @param int $maxItemsPerDepth The max number of items dumped per depth level. + * @param int $maxItemsPerDepth The max number of items dumped per depth level * - * @return self A clone of $this. + * @return self A clone of $this */ public function withMaxItemsPerDepth($maxItemsPerDepth) { @@ -70,9 +70,9 @@ public function withMaxItemsPerDepth($maxItemsPerDepth) /** * Enables/disables objects' identifiers tracking. * - * @param bool $useRefHandles False to hide global ref. handles. + * @param bool $useRefHandles False to hide global ref. handles * - * @return self A clone of $this. + * @return self A clone of $this */ public function withRefHandles($useRefHandles) { @@ -85,11 +85,11 @@ public function withRefHandles($useRefHandles) /** * Returns a depth limited clone of $this. * - * @param int $maxDepth The max dumped depth level. - * @param int $maxItemsPerDepth The max number of items dumped per depth level. - * @param bool $useRefHandles False to hide ref. handles. + * @param int $maxDepth The max dumped depth level + * @param int $maxItemsPerDepth The max number of items dumped per depth level + * @param bool $useRefHandles False to hide ref. handles * - * @return self A depth limited clone of $this. + * @return self A depth limited clone of $this * * @deprecated since Symfony 2.7, to be removed in 3.0. Use withMaxDepth, withMaxItemsPerDepth or withRefHandles instead. */ @@ -117,10 +117,10 @@ public function dump(DumperInterface $dumper) /** * Depth-first dumping of items. * - * @param DumperInterface $dumper The dumper being used for dumping. - * @param Cursor $cursor A cursor used for tracking dumper state position. - * @param array &$refs A map of all references discovered while dumping. - * @param mixed $item A Stub object or the original value being dumped. + * @param DumperInterface $dumper The dumper being used for dumping + * @param Cursor $cursor A cursor used for tracking dumper state position + * @param array &$refs A map of all references discovered while dumping + * @param mixed $item A Stub object or the original value being dumped */ private function dumpItem($dumper, $cursor, &$refs, $item) { @@ -130,7 +130,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item) $firstSeen = true; if (!$item instanceof Stub) { - $type = gettype($item); + $type = \gettype($item); } elseif (Stub::TYPE_REF === $item->type) { if ($item->handle) { if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) { @@ -142,7 +142,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item) $cursor->hardRefHandle = $this->useRefHandles & $item->handle; $cursor->hardRefCount = $item->refCount; } - $type = $item->class ?: gettype($item->value); + $type = $item->class ?: \gettype($item->value); $item = $item->value; } if ($item instanceof Stub) { @@ -163,7 +163,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item) if ($cursor->stop) { if ($cut >= 0) { - $cut += count($children); + $cut += \count($children); } $children = array(); } @@ -179,7 +179,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item) $item = clone $item; $item->type = $item->class; $item->class = $item->value; - // No break; + // no break case Stub::TYPE_OBJECT: case Stub::TYPE_RESOURCE: $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; @@ -187,7 +187,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item) if ($withChildren) { $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type); } elseif ($children && 0 <= $cut) { - $cut += count($children); + $cut += \count($children); } $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); break; @@ -209,13 +209,13 @@ private function dumpItem($dumper, $cursor, &$refs, $item) * Dumps children of hash structures. * * @param DumperInterface $dumper - * @param Cursor $parentCursor The cursor of the parent hash. - * @param array &$refs A map of all references discovered while dumping. - * @param array $children The children to dump. - * @param int $hashCut The number of items removed from the original hash. - * @param string $hashType A Cursor::HASH_* const. + * @param Cursor $parentCursor The cursor of the parent hash + * @param array &$refs A map of all references discovered while dumping + * @param array $children The children to dump + * @param int $hashCut The number of items removed from the original hash + * @param string $hashType A Cursor::HASH_* const * - * @return int The final number of removed items. + * @return int The final number of removed items */ private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType) { @@ -223,7 +223,7 @@ private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCu ++$cursor->depth; $cursor->hashType = $hashType; $cursor->hashIndex = 0; - $cursor->hashLength = count($children); + $cursor->hashLength = \count($children); $cursor->hashCut = $hashCut; foreach ($children as $key => $child) { $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); diff --git a/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php index eba23d42297ec..cb7981694f981 100644 --- a/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php +++ b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php @@ -21,40 +21,40 @@ interface DumperInterface /** * Dumps a scalar value. * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $type The PHP type of the value being dumped. - * @param scalar $value The scalar value being dumped. + * @param Cursor $cursor The Cursor position in the dump + * @param string $type The PHP type of the value being dumped + * @param scalar $value The scalar value being dumped */ public function dumpScalar(Cursor $cursor, $type, $value); /** * Dumps a string. * - * @param Cursor $cursor The Cursor position in the dump. - * @param string $str The string being dumped. - * @param bool $bin Whether $str is UTF-8 or binary encoded. - * @param int $cut The number of characters $str has been cut by. + * @param Cursor $cursor The Cursor position in the dump + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by */ public function dumpString(Cursor $cursor, $str, $bin, $cut); /** * Dumps while entering an hash. * - * @param Cursor $cursor The Cursor position in the dump. - * @param int $type A Cursor::HASH_* const for the type of hash. - * @param string $class The object class, resource type or array count. - * @param bool $hasChild When the dump of the hash has child item. + * @param Cursor $cursor The Cursor position in the dump + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item */ public function enterHash(Cursor $cursor, $type, $class, $hasChild); /** * Dumps while leaving an hash. * - * @param Cursor $cursor The Cursor position in the dump. - * @param int $type A Cursor::HASH_* const for the type of hash. - * @param string $class The object class, resource type or array count. - * @param bool $hasChild When the dump of the hash has child item. - * @param int $cut The number of items the hash has been cut by. + * @param Cursor $cursor The Cursor position in the dump + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by */ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut); } diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php index 139f59eae643e..b6574d9985557 100644 --- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php @@ -25,10 +25,9 @@ class VarCloner extends AbstractCloner protected function doClone($var) { $useExt = $this->useExt; - $i = 0; // Current iteration position in $queue $len = 1; // Length of $queue $pos = 0; // Number of cloned items past the first level - $refs = 0; // Hard references counter + $refsCounter = 0; // Hard references counter $queue = array(array($var)); // This breadth-first queue is the return value $arrayRefs = array(); // Map of queue indexes to stub array objects $hardRefs = array(); // Map of original zval hashes to stub objects @@ -60,27 +59,32 @@ protected function doClone($var) for ($i = 0; $i < $len; ++$i) { $indexed = true; // Whether the currently iterated array is numerically indexed or not $j = -1; // Position in the currently iterated array - $step = $queue[$i]; // Copy of the currently iterated array used for hard references detection - foreach ($step as $k => $v) { + $fromObjCast = array_keys($queue[$i]); + $fromObjCast = array_keys(array_flip($fromObjCast)) !== $fromObjCast; + $refs = $vals = $fromObjCast ? array_values($queue[$i]) : $queue[$i]; + foreach ($queue[$i] as $k => $v) { // $k is the original key // $v is the original value or a stub object in case of hard references - if ($indexed && $k !== ++$j) { + if ($k !== ++$j) { $indexed = false; } + if ($fromObjCast) { + $k = $j; + } if ($useExt) { - $zval = symfony_zval_info($k, $step); + $zval = symfony_zval_info($k, $refs); } else { - $step[$k] = $cookie; - if ($zval['zval_isref'] = $queue[$i][$k] === $cookie) { + $refs[$k] = $cookie; + if ($zval['zval_isref'] = $vals[$k] === $cookie) { $zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null; } - $zval['type'] = gettype($v); + $zval['type'] = \gettype($v); } if ($zval['zval_isref']) { - $queue[$i][$k] = &$stub; // Break hard references to make $queue completely + $vals[$k] = &$stub; // Break hard references to make $queue completely unset($stub); // independent from the original structure if (isset($hardRefs[$zval['zval_hash']])) { - $queue[$i][$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($step[$k] = $v); + $vals[$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($refs[$k] = $v); if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { ++$v->value->refCount; } @@ -96,18 +100,18 @@ protected function doClone($var) $stub = new Stub(); $stub->type = Stub::TYPE_STRING; $stub->class = Stub::STRING_BINARY; - if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) { + if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { $stub->cut = $cut; $stub->value = substr($v, 0, -$cut); } else { $stub->value = $v; } - } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) { + } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { $stub = new Stub(); $stub->type = Stub::TYPE_STRING; $stub->class = Stub::STRING_UTF8; $stub->cut = $cut; - $stub->value = iconv_substr($v, 0, $maxString, 'UTF-8'); + $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); } break; @@ -136,7 +140,7 @@ protected function doClone($var) $a = $v; } - $stub->value = $zval['array_count'] ?: count($a); + $stub->value = $zval['array_count'] ?: \count($a); } break; @@ -144,7 +148,7 @@ protected function doClone($var) if (empty($objRefs[$h = $zval['object_handle'] ?: ($hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, PHP_INT_SIZE)))])) { $stub = new Stub(); $stub->type = Stub::TYPE_OBJECT; - $stub->class = $zval['object_class'] ?: get_class($v); + $stub->class = $zval['object_class'] ?: \get_class($v); $stub->value = $v; $stub->handle = $h; $a = $this->castObject($stub, 0 < $i); @@ -163,7 +167,7 @@ protected function doClone($var) } $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos) { - $stub->cut = count($a); + $stub->cut = \count($a); $a = null; } } @@ -178,16 +182,19 @@ protected function doClone($var) case 'resource': case 'unknown type': + case 'resource (closed)': if (empty($resRefs[$h = (int) $v])) { $stub = new Stub(); $stub->type = Stub::TYPE_RESOURCE; - $stub->class = $zval['resource_type'] ?: get_resource_type($v); + if ('Unknown' === $stub->class = $zval['resource_type'] ?: @get_resource_type($v)) { + $stub->class = 'Closed'; + } $stub->value = $v; $stub->handle = $h; $a = $this->castResource($stub, 0 < $i); $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos) { - $stub->cut = count($a); + $stub->cut = \count($a); $a = null; } } @@ -204,26 +211,26 @@ protected function doClone($var) if (isset($stub)) { if ($zval['zval_isref']) { if ($useExt) { - $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub(); + $vals[$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub(); $v->value = $stub; } else { - $step[$k] = new Stub(); - $step[$k]->value = $stub; - $h = spl_object_hash($step[$k]); - $queue[$i][$k] = $hardRefs[$h] = &$step[$k]; + $refs[$k] = new Stub(); + $refs[$k]->value = $stub; + $h = spl_object_hash($refs[$k]); + $vals[$k] = $hardRefs[$h] = &$refs[$k]; $values[$h] = $v; } - $queue[$i][$k]->handle = ++$refs; + $vals[$k]->handle = ++$refsCounter; } else { - $queue[$i][$k] = $stub; + $vals[$k] = $stub; } if ($a) { if ($i && 0 <= $maxItems) { - $k = count($a); + $k = \count($a); if ($pos < $maxItems) { if ($maxItems < $pos += $k) { - $a = array_slice($a, 0, $maxItems - $pos); + $a = \array_slice($a, 0, $maxItems - $pos); if ($stub->cut >= 0) { $stub->cut += $pos - $maxItems; } @@ -243,19 +250,38 @@ protected function doClone($var) $stub = $a = null; } elseif ($zval['zval_isref']) { if ($useExt) { - $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = new Stub(); - $queue[$i][$k]->value = $v; + $vals[$k] = $hardRefs[$zval['zval_hash']] = new Stub(); + $vals[$k]->value = $v; } else { - $step[$k] = $queue[$i][$k] = new Stub(); - $step[$k]->value = $v; - $h = spl_object_hash($step[$k]); - $hardRefs[$h] = &$step[$k]; + $refs[$k] = $vals[$k] = new Stub(); + $refs[$k]->value = $v; + $h = spl_object_hash($refs[$k]); + $hardRefs[$h] = &$refs[$k]; $values[$h] = $v; } - $queue[$i][$k]->handle = ++$refs; + $vals[$k]->handle = ++$refsCounter; } } + if ($fromObjCast) { + $refs = $vals; + $vals = array(); + $j = -1; + foreach ($queue[$i] as $k => $v) { + foreach (array($k => $v) as $a => $v) { + } + if ($a !== $k) { + $vals = (object) $vals; + $vals->{$k} = $refs[++$j]; + $vals = (array) $vals; + } else { + $vals[$k] = $refs[++$j]; + } + } + } + + $queue[$i] = $vals; + if (isset($arrayRefs[$i])) { if ($indexed) { $arrayRefs[$i]->class = Stub::ARRAY_INDEXED; @@ -277,13 +303,13 @@ private static function initHashMask() self::$hashOffset = 16 - PHP_INT_SIZE; self::$hashMask = -1; - if (defined('HHVM_VERSION')) { + if (\defined('HHVM_VERSION')) { self::$hashOffset += 16; } else { // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); - foreach (debug_backtrace(PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { - if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && in_array($frame['function'], $obFuncs)) { + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { $frame['line'] = 0; break; } @@ -291,7 +317,7 @@ private static function initHashMask() if (!empty($frame['line'])) { ob_start(); debug_zval_dump($obj); - self::$hashMask = substr(ob_get_clean(), 17); + self::$hashMask = (int) substr(ob_get_clean(), 17); } } diff --git a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php index f8b9c1077790a..3e12cd099cec1 100644 --- a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php @@ -32,16 +32,16 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface private $charset; /** - * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput. - * @param string $charset The default character encoding to use for non-UTF8 strings. + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput + * @param string $charset The default character encoding to use for non-UTF8 strings */ public function __construct($output = null, $charset = null) { $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'); - $this->decimalPoint = (string) 0.5; - $this->decimalPoint = $this->decimalPoint[1]; + $this->decimalPoint = localeconv(); + $this->decimalPoint = $this->decimalPoint['decimal_point']; $this->setOutput($output ?: static::$defaultOutput); - if (!$output && is_string(static::$defaultOutput)) { + if (!$output && \is_string(static::$defaultOutput)) { static::$defaultOutput = $this->outputStream; } } @@ -49,19 +49,19 @@ public function __construct($output = null, $charset = null) /** * Sets the output destination of the dumps. * - * @param callable|resource|string $output A line dumper callable, an opened stream or an output path. + * @param callable|resource|string $output A line dumper callable, an opened stream or an output path * - * @return callable|resource|string The previous output destination. + * @return callable|resource|string The previous output destination */ public function setOutput($output) { $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; - if (is_callable($output)) { + if (\is_callable($output)) { $this->outputStream = null; $this->lineDumper = $output; } else { - if (is_string($output)) { + if (\is_string($output)) { $output = fopen($output, 'wb'); } $this->outputStream = $output; @@ -74,9 +74,9 @@ public function setOutput($output) /** * Sets the default character encoding to use for non-UTF8 strings. * - * @param string $charset The default character encoding to use for non-UTF8 strings. + * @param string $charset The default character encoding to use for non-UTF8 strings * - * @return string The previous charset. + * @return string The previous charset */ public function setCharset($charset) { @@ -93,9 +93,9 @@ public function setCharset($charset) /** * Sets the indentation pad string. * - * @param string $pad A string the will be prepended to dumped lines, repeated by nesting level. + * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level * - * @return string The indent pad. + * @return string The previous indent pad */ public function setIndentPad($pad) { @@ -108,11 +108,14 @@ public function setIndentPad($pad) /** * Dumps a Data object. * - * @param Data $data A Data object. - * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path. + * @param Data $data A Data object + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path */ public function dump(Data $data, $output = null) { + $this->decimalPoint = localeconv(); + $this->decimalPoint = $this->decimalPoint['decimal_point']; + $exception = null; if ($output) { $prevOutput = $this->setOutput($output); @@ -122,6 +125,8 @@ public function dump(Data $data, $output = null) $this->dumpLine(-1); } catch (\Exception $exception) { // Re-thrown below + } catch (\Throwable $exception) { + // Re-thrown below } if ($output) { $this->setOutput($prevOutput); @@ -134,19 +139,21 @@ public function dump(Data $data, $output = null) /** * Dumps the current line. * - * @param int $depth The recursive depth in the dumped structure for the line being dumped. + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable */ protected function dumpLine($depth) { - call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad); + \call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad); $this->line = ''; } /** * Generic line dumper callback. * - * @param string $line The line to write. - * @param int $depth The recursive depth in the dumped structure. + * @param string $line The line to write + * @param int $depth The recursive depth in the dumped structure + * @param string $indentPad The line indent pad */ protected function echoLine($line, $depth, $indentPad) { @@ -158,12 +165,15 @@ protected function echoLine($line, $depth, $indentPad) /** * Converts a non-UTF-8 string to UTF-8. * - * @param string $s The non-UTF-8 string to convert. + * @param string $s The non-UTF-8 string to convert * - * @return string The string converted to UTF-8. + * @return string The string converted to UTF-8 */ protected function utf8Encode($s) { + if (!\function_exists('iconv')) { + throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { return $c; } diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index bca2abcb3d0c4..b238e7ab5c6f5 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -58,8 +58,8 @@ public function __construct($output = null, $charset = null) { parent::__construct($output, $charset); - if ('\\' === DIRECTORY_SEPARATOR && false !== @getenv('ANSICON')) { - // Use only the base 16 xterm colors when using ANSICON + if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { + // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI $this->setStyles(array( 'default' => '31', 'num' => '1;34', @@ -97,7 +97,7 @@ public function setMaxStringWidth($maxStringWidth) /** * Configures styles. * - * @param array $styles A map of style names to style definitions. + * @param array $styles A map of style names to style definitions */ public function setStyles(array $styles) { @@ -123,9 +123,9 @@ public function dumpScalar(Cursor $cursor, $type, $value) $style = 'num'; switch (true) { - case INF === $value: $value = 'INF'; break; + case INF === $value: $value = 'INF'; break; case -INF === $value: $value = '-INF'; break; - case is_nan($value): $value = 'NAN'; break; + case is_nan($value): $value = 'NAN'; break; default: $value = (string) $value; if (false === strpos($value, $this->decimalPoint)) { @@ -169,7 +169,7 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) $this->dumpLine($cursor->depth, true); } else { $attr = array( - 'length' => 0 <= $cut ? iconv_strlen($str, 'UTF-8') + $cut : 0, + 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, 'binary' => $bin, ); $str = explode("\n", $str); @@ -177,7 +177,7 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) unset($str[1]); $str[0] .= "\n"; } - $m = count($str) - 1; + $m = \count($str) - 1; $i = $lineCut = 0; if ($bin) { @@ -195,8 +195,8 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) if ($i < $m) { $str .= "\n"; } - if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = iconv_strlen($str, 'UTF-8')) { - $str = iconv_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { + $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); $lineCut = $len - $this->maxStringWidth; } if ($m && 0 < $cursor->depth) { @@ -280,9 +280,9 @@ public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) /** * Dumps an ellipsis for cut children. * - * @param Cursor $cursor The Cursor position in the dump. - * @param bool $hasChild When the dump of the hash has child item. - * @param int $cut The number of items the hash has been cut by. + * @param Cursor $cursor The Cursor position in the dump + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by */ protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) { @@ -300,7 +300,7 @@ protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) /** * Dumps a key in a hash structure. * - * @param Cursor $cursor The Cursor position in the dump. + * @param Cursor $cursor The Cursor position in the dump */ protected function dumpKey(Cursor $cursor) { @@ -315,8 +315,9 @@ protected function dumpKey(Cursor $cursor) default: case Cursor::HASH_INDEXED: $style = 'index'; + // no break case Cursor::HASH_ASSOC: - if (is_int($key)) { + if (\is_int($key)) { $this->line .= $this->style($style, $key).' => '; } else { $this->line .= $bin.'"'.$this->style($style, $key).'" => '; @@ -325,7 +326,7 @@ protected function dumpKey(Cursor $cursor) case Cursor::HASH_RESOURCE: $key = "\0~\0".$key; - // No break; + // no break case Cursor::HASH_OBJECT: if (!isset($key[0]) || "\0" !== $key[0]) { $this->line .= '+'.$bin.$this->style('public', $key).': '; @@ -368,11 +369,11 @@ protected function dumpKey(Cursor $cursor) /** * Decorates a value with some style. * - * @param string $style The type of style being applied. - * @param string $value The value being styled. - * @param array $attr Optional context information. + * @param string $style The type of style being applied + * @param string $value The value being styled + * @param array $attr Optional context information * - * @return string The value with style decoration. + * @return string The value with style decoration */ protected function style($style, $value, $attr = array()) { @@ -389,7 +390,7 @@ protected function style($style, $value, $attr = array()) $s = $startCchr; $c = $c[$i = 0]; do { - $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i])); + $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i])); } while (isset($c[++$i])); return $s.$endCchr; @@ -397,12 +398,12 @@ protected function style($style, $value, $attr = array()) if ($this->colors) { if ($cchrCount && "\033" === $value[0]) { - $value = substr($value, strlen($startCchr)); + $value = substr($value, \strlen($startCchr)); } else { $value = "\033[{$style}m".$value; } - if ($cchrCount && $endCchr === substr($value, -strlen($endCchr))) { - $value = substr($value, 0, -strlen($endCchr)); + if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) { + $value = substr($value, 0, -\strlen($endCchr)); } else { $value .= "\033[{$this->styles['default']}m"; } @@ -412,19 +413,19 @@ protected function style($style, $value, $attr = array()) } /** - * @return bool Tells if the current output stream supports ANSI colors or not. + * @return bool Tells if the current output stream supports ANSI colors or not */ protected function supportsColors() { if ($this->outputStream !== static::$defaultOutput) { - return @(is_resource($this->outputStream) && function_exists('posix_isatty') && posix_isatty($this->outputStream)); + return $this->hasColorSupport($this->outputStream); } if (null !== static::$defaultColors) { return static::$defaultColors; } if (isset($_SERVER['argv'][1])) { $colors = $_SERVER['argv']; - $i = count($colors); + $i = \count($colors); while (--$i > 0) { if (isset($colors[$i][5])) { switch ($colors[$i]) { @@ -445,17 +446,10 @@ protected function supportsColors() } } - if ('\\' === DIRECTORY_SEPARATOR) { - static::$defaultColors = @(false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM')); - } elseif (function_exists('posix_isatty')) { - $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null); - $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; - static::$defaultColors = @posix_isatty($h); - } else { - static::$defaultColors = false; - } + $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null); + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; - return static::$defaultColors; + return static::$defaultColors = $this->hasColorSupport($h); } /** @@ -468,4 +462,74 @@ protected function dumpLine($depth, $endOfValue = false) } parent::dumpLine($depth); } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @param mixed $stream A CLI output stream + * + * @return bool + */ + private function hasColorSupport($stream) + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (\function_exists('stream_isatty')) { + return @stream_isatty($stream); + } + + if (\function_exists('posix_isatty')) { + return @posix_isatty($stream); + } + + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + * + * @return bool + */ + private function isWindowsTrueColor() + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM'); + + if (!$result && \PHP_VERSION_ID >= 70200) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } } diff --git a/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php b/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php index ee6060cebf647..b173bccf38916 100644 --- a/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php +++ b/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php @@ -20,10 +20,5 @@ */ interface DataDumperInterface { - /** - * Dumps a Data object. - * - * @param Data $data A Data object. - */ public function dump(Data $data); } diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index 8a48554623ec7..64b72023a3604 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -54,18 +54,6 @@ public function __construct($output = null, $charset = null) $this->dumpId = 'sf-dump-'.mt_rand(); } - /** - * {@inheritdoc} - */ - public function setOutput($output) - { - if ($output !== $prev = parent::setOutput($output)) { - $this->headerIsDumped = false; - } - - return $prev; - } - /** * {@inheritdoc} */ @@ -78,7 +66,7 @@ public function setStyles(array $styles) /** * Sets an HTML header that will be dumped once in the output stream. * - * @param string $header An HTML string. + * @param string $header An HTML string */ public function setDumpHeader($header) { @@ -88,8 +76,8 @@ public function setDumpHeader($header) /** * Sets an HTML prefix and suffix that will encapse every single dump. * - * @param string $prefix The prepended HTML string. - * @param string $suffix The appended HTML string. + * @param string $prefix The prepended HTML string + * @param string $suffix The appended HTML string */ public function setDumpBoundaries($prefix, $suffix) { @@ -111,7 +99,7 @@ public function dump(Data $data, $output = null) */ protected function getDumpHeader() { - $this->headerIsDumped = true; + $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; if (null !== $this->dumpHeader) { return $this->dumpHeader; @@ -144,24 +132,31 @@ protected function getDumpHeader() function toggle(a, recursive) { var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass; - if ('sf-dump-compact' == oldClass) { + if (/\bsf-dump-compact\b/.test(oldClass)) { arrow = '▼'; newClass = 'sf-dump-expanded'; - } else if ('sf-dump-expanded' == oldClass) { + } else if (/\bsf-dump-expanded\b/.test(oldClass)) { arrow = '▶'; newClass = 'sf-dump-compact'; } else { return false; } + if (doc.createEvent && s.dispatchEvent) { + var event = doc.createEvent('Event'); + event.initEvent('sf-dump-expanded' === newClass ? 'sfbeforedumpexpand' : 'sfbeforedumpcollapse', true, false); + + s.dispatchEvent(event); + } + a.lastChild.innerHTML = arrow; - s.className = newClass; + s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass); if (recursive) { try { a = s.querySelectorAll('.'+oldClass); for (s = 0; s < a.length; ++s) { - if (a[s].className !== newClass) { + if (-1 == a[s].className.indexOf(newClass)) { a[s].className = newClass; a[s].previousSibling.lastChild.innerHTML = arrow; } @@ -217,7 +212,7 @@ function isCtrlKey(e) { if (f && t && f[0] !== t[0]) { r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]); } - if ('sf-dump-compact' == r.className) { + if (/\bsf-dump-compact\b/.test(r.className)) { toggle(s, isCtrlKey(e)); } } @@ -267,10 +262,10 @@ function isCtrlKey(e) { a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children'; a.innerHTML += ''; a.className += ' sf-dump-toggle'; - if ('sf-dump' != elt.parentNode.className) { + if (!/\bsf-dump\b/.test(elt.parentNode.className)) { toggle(a); } - } else if ("sf-dump-ref" == elt.className && (a = elt.getAttribute('href'))) { + } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) { a = a.substr(1); elt.className += ' '+a; @@ -300,8 +295,7 @@ function isCtrlKey(e) { }; })(document); - -