Merge branch '3.3' into 3.4

* 3.3:
  fix merge
  fix merge
  fix merge
  Fix 7.2 compat layer
  Fix PHP 7.2 support
  [HttpFoundation] Add missing session.lazy_write config option
  [HttpFoundation] Combine Cache-Control headers
  [Form] fix parsing invalid floating point numbers
  Escape command usage when displaying it in the text descriptor
  Use for=ID on radio/checkbox label.
This commit is contained in:
Nicolas Grekas 2017-10-10 12:38:39 +02:00
commit d3bc436cd2
26 changed files with 185 additions and 40 deletions

View File

@ -25,9 +25,10 @@ matrix:
group: edge group: edge
- php: 5.5 - php: 5.5
- php: 5.6 - php: 5.6
- php: 7.0
- php: 7.1 - php: 7.1
env: deps=high env: deps=high
- php: 7.0 - php: 7.2
env: deps=low env: deps=low
fast_finish: true fast_finish: true

View File

@ -50,10 +50,14 @@
{% endblock %} {% endblock %}
{% block checkbox_label -%} {% block checkbox_label -%}
{%- set label_attr = label_attr|merge({'for': id}) -%}
{{- block('checkbox_radio_label') -}} {{- block('checkbox_radio_label') -}}
{%- endblock checkbox_label %} {%- endblock checkbox_label %}
{% block radio_label -%} {% block radio_label -%}
{%- set label_attr = label_attr|merge({'for': id}) -%}
{{- block('checkbox_radio_label') -}} {{- block('checkbox_radio_label') -}}
{%- endblock radio_label %} {%- endblock radio_label %}

View File

@ -45,6 +45,9 @@ class AppVariableTest extends TestCase
$this->assertEquals('dev', $this->appVariable->getEnvironment()); $this->assertEquals('dev', $this->appVariable->getEnvironment());
} }
/**
* @runInSeparateProcess
*/
public function testGetSession() public function testGetSession()
{ {
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock(); $session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
@ -166,6 +169,9 @@ class AppVariableTest extends TestCase
$this->assertEquals(array(), $this->appVariable->getFlashes()); $this->assertEquals(array(), $this->appVariable->getFlashes());
} }
/**
* @runInSeparateProcess
*/
public function testGetFlashesWithNoSessionStarted() public function testGetFlashesWithNoSessionStarted()
{ {
$session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock(); $session = $this->getMockBuilder(Session::class)->disableOriginalConstructor()->getMock();
@ -177,6 +183,9 @@ class AppVariableTest extends TestCase
$this->assertEquals(array(), $this->appVariable->getFlashes()); $this->assertEquals(array(), $this->appVariable->getFlashes());
} }
/**
* @runInSeparateProcess
*/
public function testGetFlashes() public function testGetFlashes()
{ {
$flashMessages = $this->setFlashMessages(); $flashMessages = $this->setFlashMessages();

View File

@ -24,6 +24,7 @@
"symfony/asset": "~2.8|~3.0|~4.0", "symfony/asset": "~2.8|~3.0|~4.0",
"symfony/finder": "~2.8|~3.0|~4.0", "symfony/finder": "~2.8|~3.0|~4.0",
"symfony/form": "~3.4|~4.0", "symfony/form": "~3.4|~4.0",
"symfony/http-foundation": "^3.3.11|~4.0",
"symfony/http-kernel": "~3.2|~4.0", "symfony/http-kernel": "~3.2|~4.0",
"symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-intl-icu": "~1.0",
"symfony/routing": "~2.8|~3.0|~4.0", "symfony/routing": "~2.8|~3.0|~4.0",

View File

@ -55,7 +55,7 @@ class CacheClearCommandTest extends TestCase
$finder = new Finder(); $finder = new Finder();
$metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta'); $metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta');
// simply check that cache is warmed up // simply check that cache is warmed up
$this->assertGreaterThanOrEqual(1, count($metaFiles)); $this->assertNotEmpty($metaFiles);
$configCacheFactory = new ConfigCacheFactory(true); $configCacheFactory = new ConfigCacheFactory(true);
foreach ($metaFiles as $file) { foreach ($metaFiles as $file) {

View File

@ -373,6 +373,9 @@ abstract class ControllerTraitTest extends TestCase
$this->assertSame(302, $response->getStatusCode()); $this->assertSame(302, $response->getStatusCode());
} }
/**
* @runInSeparateProcess
*/
public function testAddFlash() public function testAddFlash()
{ {
$flashBag = new FlashBag(); $flashBag = new FlashBag();

View File

@ -2,7 +2,9 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation; namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;
class Article implements NotExistingInterface if (!function_exists('__phpunit_run_isolated_test')) {
{ class Article implements NotExistingInterface
public $category; {
public $category;
}
} }

View File

@ -23,7 +23,7 @@
"symfony/dependency-injection": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0",
"symfony/config": "~3.4|~4.0", "symfony/config": "~3.4|~4.0",
"symfony/event-dispatcher": "^3.3.1|~4.0", "symfony/event-dispatcher": "^3.3.1|~4.0",
"symfony/http-foundation": "~3.3|~4.0", "symfony/http-foundation": "^3.3.11|~4.0",
"symfony/http-kernel": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0",
"symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-mbstring": "~1.0",
"symfony/filesystem": "~2.8|~3.0|~4.0", "symfony/filesystem": "~2.8|~3.0|~4.0",

View File

@ -2,6 +2,8 @@
namespace Symfony\Component\Config\Tests\Fixtures; namespace Symfony\Component\Config\Tests\Fixtures;
class BadParent extends MissingParent if (!function_exists('__phpunit_run_isolated_test')) {
{ class BadParent extends MissingParent
{
}
} }

View File

@ -143,7 +143,7 @@ class TextDescriptor extends Descriptor
$this->writeText('<comment>Usage:</comment>', $options); $this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n"); $this->writeText("\n");
$this->writeText(' '.$usage, $options); $this->writeText(' '.OutputFormatter::escape($usage), $options);
} }
$this->writeText("\n"); $this->writeText("\n");

View File

@ -1,7 +1,7 @@
<comment>Usage:</comment> <comment>Usage:</comment>
descriptor:command2 [options] [--] <argument_name> descriptor:command2 [options] [--] \<argument_name>
descriptor:command2 -o|--option_name <argument_name> descriptor:command2 -o|--option_name \<argument_name>
descriptor:command2 <argument_name> descriptor:command2 \<argument_name>
<comment>Arguments:</comment> <comment>Arguments:</comment>
<info>argument_name</info> <info>argument_name</info>

View File

@ -1,7 +1,7 @@
<comment>Usage:</comment> <comment>Usage:</comment>
descriptor:åèä [options] [--] <argument_åèä> descriptor:åèä [options] [--] \<argument_åèä>
descriptor:åèä -o|--option_name <argument_name> descriptor:åèä -o|--option_name \<argument_name>
descriptor:åèä <argument_name> descriptor:åèä \<argument_name>
<comment>Arguments:</comment> <comment>Arguments:</comment>
<info>argument_åèä</info> <info>argument_åèä</info>

View File

@ -1,3 +1,5 @@
<?php <?php
throw new \Exception('boo'); if (!function_exists('__phpunit_run_isolated_test')) {
throw new \Exception('boo');
}

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use Symfony\Bug\NotExistClass; use Symfony\Bug\NotExistClass;
class OptionalServiceClass extends NotExistClass if (!function_exists('__phpunit_run_isolated_test')) {
{ class OptionalServiceClass extends NotExistClass
{
}
} }

View File

@ -458,6 +458,9 @@ class XmlFileLoaderTest extends TestCase
if (extension_loaded('suhosin') && false === strpos(ini_get('suhosin.executor.include.whitelist'), 'phar')) { if (extension_loaded('suhosin') && false === strpos(ini_get('suhosin.executor.include.whitelist'), 'phar')) {
$this->markTestSkipped('To run this test, add "phar" to the "suhosin.executor.include.whitelist" settings in your php.ini file.'); $this->markTestSkipped('To run this test, add "phar" to the "suhosin.executor.include.whitelist" settings in your php.ini file.');
} }
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVM makes this test conflict with those run in separate processes.');
}
require_once self::$fixturesPath.'/includes/ProjectWithXsdExtensionInPhar.phar'; require_once self::$fixturesPath.'/includes/ProjectWithXsdExtensionInPhar.phar';

View File

@ -116,6 +116,7 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface
return; return;
} }
$position = 0;
$formatter = $this->getNumberFormatter(); $formatter = $this->getNumberFormatter();
$groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); $groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL);
$decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); $decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
@ -129,18 +130,44 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface
$value = str_replace(',', $decSep, $value); $value = str_replace(',', $decSep, $value);
} }
if (false !== strpos($value, $decSep)) {
$type = \NumberFormatter::TYPE_DOUBLE;
} else {
$type = \PHP_INT_SIZE === 8 ? \NumberFormatter::TYPE_INT64 : \NumberFormatter::TYPE_INT32;
}
// replace normal spaces so that the formatter can read them // replace normal spaces so that the formatter can read them
$value = $formatter->parse(str_replace(' ', "\xc2\xa0", $value)); $result = $formatter->parse(str_replace(' ', "\xc2\xa0", $value), $type, $position);
if (intl_is_failure($formatter->getErrorCode())) { if (intl_is_failure($formatter->getErrorCode())) {
throw new TransformationFailedException($formatter->getErrorMessage()); throw new TransformationFailedException($formatter->getErrorMessage());
} }
if (self::FRACTIONAL == $this->type) { if (self::FRACTIONAL == $this->type) {
$value /= 100; $result /= 100;
} }
return $value; if (\function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value, null, true)) {
$length = mb_strlen($value, $encoding);
$remainder = mb_substr($value, $position, $length, $encoding);
} else {
$length = \strlen($value);
$remainder = substr($value, $position, $length);
}
// After parsing, position holds the index of the character where the
// parsing stopped
if ($position < $length) {
// Check if there are unrecognized characters at the end of the
// number (excluding whitespace characters)
$remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0");
if ('' !== $remainder) {
throw new TransformationFailedException(sprintf('The number contains unrecognized characters: "%s"', $remainder));
}
}
return $result;
} }
/** /**

View File

@ -141,10 +141,10 @@ class PercentToLocalizedStringTransformerTest extends TestCase
*/ */
public function testDecimalSeparatorMayNotBeDotIfGroupingSeparatorIsDot() public function testDecimalSeparatorMayNotBeDotIfGroupingSeparatorIsDot()
{ {
// Since we test against "de_AT", we need the full implementation // Since we test against "de_DE", we need the full implementation
IntlTestHelper::requireFullIntl($this, '4.8.1.1'); IntlTestHelper::requireFullIntl($this, '4.8.1.1');
\Locale::setDefault('de_AT'); \Locale::setDefault('de_DE');
$transformer = new PercentToLocalizedStringTransformer(1, 'integer'); $transformer = new PercentToLocalizedStringTransformer(1, 'integer');
@ -236,4 +236,70 @@ class PercentToLocalizedStringTransformerTest extends TestCase
$this->assertEquals(1234.5, $transformer->reverseTransform('1234,5')); $this->assertEquals(1234.5, $transformer->reverseTransform('1234,5'));
$this->assertEquals(1234.5, $transformer->reverseTransform('1234.5')); $this->assertEquals(1234.5, $transformer->reverseTransform('1234.5'));
} }
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransformDisallowsLeadingExtraCharacters()
{
$transformer = new PercentToLocalizedStringTransformer();
$transformer->reverseTransform('foo123');
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo3"
*/
public function testReverseTransformDisallowsCenteredExtraCharacters()
{
$transformer = new PercentToLocalizedStringTransformer();
$transformer->reverseTransform('12foo3');
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo8"
* @requires extension mbstring
*/
public function testReverseTransformDisallowsCenteredExtraCharactersMultibyte()
{
// Since we test against other locales, we need the full implementation
IntlTestHelper::requireFullIntl($this, false);
\Locale::setDefault('ru');
$transformer = new PercentToLocalizedStringTransformer();
$transformer->reverseTransform("12\xc2\xa0345,67foo8");
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo"
*/
public function testReverseTransformDisallowsTrailingExtraCharacters()
{
$transformer = new PercentToLocalizedStringTransformer();
$transformer->reverseTransform('123foo');
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
* @expectedExceptionMessage The number contains unrecognized characters: "foo"
* @requires extension mbstring
*/
public function testReverseTransformDisallowsTrailingExtraCharactersMultibyte()
{
// Since we test against other locales, we need the full implementation
IntlTestHelper::requireFullIntl($this, false);
\Locale::setDefault('ru');
$transformer = new PercentToLocalizedStringTransformer();
$transformer->reverseTransform("12\xc2\xa0345,678foo");
}
} }

View File

@ -147,7 +147,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
} }
if ('cache-control' === $key) { if ('cache-control' === $key) {
$this->cacheControl = $this->parseCacheControl($values[0]); $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key]));
} }
} }

View File

@ -73,6 +73,7 @@ class NativeSessionStorage implements SessionStorageInterface
* gc_probability, "1" * gc_probability, "1"
* hash_bits_per_character, "4" * hash_bits_per_character, "4"
* hash_function, "0" * hash_function, "0"
* lazy_write, "1"
* name, "PHPSESSID" * name, "PHPSESSID"
* referer_check, "" * referer_check, ""
* serialize_handler, "php" * serialize_handler, "php"
@ -98,8 +99,11 @@ class NativeSessionStorage implements SessionStorageInterface
*/ */
public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
{ {
session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used) $options += array(
ini_set('session.use_cookies', 1); // disable by default because it's managed by HeaderBag (if used)
'cache_limiter' => '',
'use_cookies' => 1,
);
session_register_shutdown(); session_register_shutdown();
@ -187,6 +191,10 @@ class NativeSessionStorage implements SessionStorageInterface
return false; return false;
} }
if (headers_sent()) {
return false;
}
if (null !== $lifetime) { if (null !== $lifetime) {
ini_set('session.cookie_lifetime', $lifetime); ini_set('session.cookie_lifetime', $lifetime);
} }
@ -324,12 +332,16 @@ class NativeSessionStorage implements SessionStorageInterface
*/ */
public function setOptions(array $options) public function setOptions(array $options)
{ {
if (headers_sent()) {
return;
}
$validOptions = array_flip(array( $validOptions = array_flip(array(
'cache_limiter', 'cookie_domain', 'cookie_httponly', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_lifetime', 'cookie_path', 'cookie_secure',
'entropy_file', 'entropy_length', 'gc_divisor', 'entropy_file', 'entropy_length', 'gc_divisor',
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
'hash_function', 'name', 'referer_check', 'hash_function', 'lazy_write', 'name', 'referer_check',
'serialize_handler', 'use_strict_mode', 'use_cookies', 'serialize_handler', 'use_strict_mode', 'use_cookies',
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
@ -381,6 +393,10 @@ class NativeSessionStorage implements SessionStorageInterface
); );
} }
if (headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to set the session handler because headers have already been sent by "%s" at line %d.', $file, $line));
}
// Wrap $saveHandler in proxy and prevent double wrapping of proxy // Wrap $saveHandler in proxy and prevent double wrapping of proxy
if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
$saveHandler = new SessionHandlerProxy($saveHandler); $saveHandler = new SessionHandlerProxy($saveHandler);

View File

@ -86,6 +86,17 @@ class ResponseHeaderBagTest extends TestCase
$bag = new ResponseHeaderBag(); $bag = new ResponseHeaderBag();
$bag->set('Last-Modified', 'abcde'); $bag->set('Last-Modified', 'abcde');
$this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag();
$bag->set('Cache-Control', array('public', 'must-revalidate'));
$this->assertCount(1, $bag->get('Cache-Control', null, false));
$this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
$bag = new ResponseHeaderBag();
$bag->set('Cache-Control', 'public');
$bag->set('Cache-Control', 'must-revalidate', false);
$this->assertCount(1, $bag->get('Cache-Control', null, false));
$this->assertEquals('must-revalidate, public', $bag->get('Cache-Control'));
} }
public function testCacheControlClone() public function testCacheControlClone()

View File

@ -269,6 +269,9 @@ class PdoSessionHandlerTest extends TestCase
$this->assertSame('', $data, 'Destroyed session returns empty string'); $this->assertSame('', $data, 'Destroyed session returns empty string');
} }
/**
* @runInSeparateProcess
*/
public function testSessionGC() public function testSessionGC()
{ {
$previousLifeTime = ini_set('session.gc_maxlifetime', 1000); $previousLifeTime = ini_set('session.gc_maxlifetime', 1000);

View File

@ -38,8 +38,7 @@ class LocaleListenerTest extends TestCase
public function testLocaleFromRequestAttribute() public function testLocaleFromRequestAttribute()
{ {
$request = Request::create('/'); $request = Request::create('/');
session_name('foo'); $request->cookies->set(session_name(), 'value');
$request->cookies->set('foo', 'value');
$request->attributes->set('_locale', 'es'); $request->attributes->set('_locale', 'es');
$listener = new LocaleListener($this->requestStack, 'fr'); $listener = new LocaleListener($this->requestStack, 'fr');

View File

@ -65,8 +65,7 @@ class TestSessionListenerTest extends TestCase
{ {
$this->sessionHasBeenStarted(); $this->sessionHasBeenStarted();
$params = session_get_cookie_params(); @ini_set('session.cookie_lifetime', 0);
session_set_cookie_params(0, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
$response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST); $response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST);
$cookies = $response->headers->getCookies(); $cookies = $response->headers->getCookies();

View File

@ -18,7 +18,7 @@
"require": { "require": {
"php": "^5.5.9|>=7.0.8", "php": "^5.5.9|>=7.0.8",
"symfony/event-dispatcher": "~2.8|~3.0|~4.0", "symfony/event-dispatcher": "~2.8|~3.0|~4.0",
"symfony/http-foundation": "~3.3|~4.0", "symfony/http-foundation": "^3.3.11|~4.0",
"symfony/debug": "~2.8|~3.0|~4.0", "symfony/debug": "~2.8|~3.0|~4.0",
"psr/log": "~1.0" "psr/log": "~1.0"
}, },

View File

@ -1,5 +1,8 @@
<?php <?php
if (function_exists('__phpunit_run_isolated_test')) {
return;
}
/** @var $loader \Symfony\Component\Routing\Loader\PhpFileLoader */ /** @var $loader \Symfony\Component\Routing\Loader\PhpFileLoader */
/** @var \Symfony\Component\Routing\RouteCollection $collection */ /** @var \Symfony\Component\Routing\RouteCollection $collection */
$collection = $loader->import('validpattern.php'); $collection = $loader->import('validpattern.php');

View File

@ -29,14 +29,6 @@ class NativeSessionTokenStorageTest extends TestCase
*/ */
private $storage; private $storage;
public static function setUpBeforeClass()
{
ini_set('session.save_handler', 'files');
ini_set('session.save_path', sys_get_temp_dir());
parent::setUpBeforeClass();
}
protected function setUp() protected function setUp()
{ {
$_SESSION = array(); $_SESSION = array();