diff --git a/CHANGELOG-2.7.md b/CHANGELOG-2.7.md index d190ea2b7fd08..371e8c464e7a1 100644 --- a/CHANGELOG-2.7.md +++ b/CHANGELOG-2.7.md @@ -7,6 +7,11 @@ 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.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) diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 1f3e4ff14ac38..2034ce14a4c39 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -134,7 +134,7 @@ public function doAsk(OutputInterface $output, Question $question) $ret = trim($ret); } } else { - $ret = trim($this->autocomplete($output, $question, $inputStream)); + $ret = trim($this->autocomplete($output, $question, $inputStream, is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false))); } $ret = strlen($ret) > 0 ? $ret : $question->getDefault(); @@ -190,12 +190,12 @@ protected function writeError(OutputInterface $output, \Exception $error) * @param OutputInterface $output * @param Question $question * @param resource $inputStream + * @param array $autocomplete * * @return string */ - private function autocomplete(OutputInterface $output, Question $question, $inputStream) + private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete) { - $autocomplete = $question->getAutocompleterValues(); $ret = ''; $i = 0; diff --git a/src/Symfony/Component/Console/Question/Question.php b/src/Symfony/Component/Console/Question/Question.php index 64c9205749c51..bece3e59752ad 100644 --- a/src/Symfony/Component/Console/Question/Question.php +++ b/src/Symfony/Component/Console/Question/Question.php @@ -137,10 +137,8 @@ public function setAutocompleterValues($values) $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); } - if (null !== $values && !is_array($values)) { - if (!$values instanceof \Traversable || !$values instanceof \Countable) { - throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); - } + if (null !== $values && !is_array($values) && !$values instanceof \Traversable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.'); } if ($this->hidden) { diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 69a2256efad2b..3538cbc0b7ac1 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -514,6 +514,40 @@ public function testEmptyChoices() new ChoiceQuestion('Question', array(), 'irrelevant'); } + public function testTraversableAutocomplete() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new QuestionHelper(); + $dialog->setInputStream($inputStream); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question = new Question('Please select a bundle', 'FrameworkBundle'); + $question->setAutocompleterValues(new AutocompleteValues(array('irrelevant' => 'AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'))); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question)); + } + protected function getInputStream($input) { $stream = fopen('php://memory', 'r+', false); @@ -545,3 +579,18 @@ private function hasSttyAvailable() return 0 === $exitcode; } } + +class AutocompleteValues implements \IteratorAggregate +{ + private $values; + + public function __construct(array $values) + { + $this->values = $values; + } + + public function getIterator() + { + return new \ArrayIterator($this->values); + } +} diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php index 0ef278c901496..50a0eb5e95aae 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php @@ -116,6 +116,14 @@ public function testCachingForOverriddenVariableNames() $this->assertSame('($a + $B)', $result); } + public function testStrictEquality() + { + $expressionLanguage = new ExpressionLanguage(); + $expression = '123 === a'; + $result = $expressionLanguage->compile($expression, array('a')); + $this->assertSame('(123 === $a)', $result); + } + public function testCachingWithDifferentNamesOrder() { $cacheMock = $this->getMockBuilder('Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface')->getMock(); diff --git a/src/Symfony/Component/ExpressionLanguage/Token.php b/src/Symfony/Component/ExpressionLanguage/Token.php index b231bc25e73f2..4517335bbc739 100644 --- a/src/Symfony/Component/ExpressionLanguage/Token.php +++ b/src/Symfony/Component/ExpressionLanguage/Token.php @@ -30,9 +30,9 @@ class Token const PUNCTUATION_TYPE = 'punctuation'; /** - * @param string $type The type of the token (self::*_TYPE) - * @param string $value The token value - * @param int $cursor The cursor position in the source + * @param string $type The type of the token (self::*_TYPE) + * @param string|int|float|null $value The token value + * @param int $cursor The cursor position in the source */ public function __construct($type, $value, $cursor) { diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 83f0055e0c2e0..26c6c2077ecac 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -50,7 +50,7 @@ * either as "Y-m-d" string or as timestamp. Internally we still want to * use a DateTime object for processing. To convert the data from string/integer * to DateTime you can set a normalization transformer by calling - * addNormTransformer(). The normalized data is then converted to the displayed + * addModelTransformer(). The normalized data is then converted to the displayed * data as described before. * * The conversions (1) -> (2) -> (3) use the transform methods of the transformers. diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index e35af4e291a70..f03cdf343024d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -102,12 +102,6 @@ class NativeSessionStorage implements SessionStorageInterface */ public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) { - $this->setMetadataBag($metaBag); - - if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) { - return; - } - $options += array( // disable by default because it's managed by HeaderBag (if used) 'cache_limiter' => '', @@ -120,6 +114,7 @@ public function __construct(array $options = array(), $handler = null, MetadataB register_shutdown_function('session_write_close'); } + $this->setMetadataBag($metaBag); $this->setOptions($options); $this->setSaveHandler($handler); } @@ -292,7 +287,7 @@ public function getBag($name) throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); } - if ($this->saveHandler->isActive() && !$this->started) { + if (!$this->started && $this->saveHandler->isActive()) { $this->loadSession(); } elseif (!$this->started) { $this->start(); @@ -340,7 +335,7 @@ public function isStarted() */ public function setOptions(array $options) { - if (headers_sent()) { + if (headers_sent() || (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status())) { return; } @@ -395,10 +390,6 @@ public function setSaveHandler($saveHandler = null) throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.'); } - 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 if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { $saveHandler = new SessionHandlerProxy($saveHandler); @@ -408,6 +399,10 @@ public function setSaveHandler($saveHandler = null) } $this->saveHandler = $saveHandler; + if (headers_sent() || (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status())) { + return; + } + if ($this->saveHandler instanceof \SessionHandlerInterface) { if (\PHP_VERSION_ID >= 50400) { session_set_save_handler($this->saveHandler, false); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php index 673cd386bf6aa..7eb7e56478ce5 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -296,4 +296,19 @@ public function testSetSessionOptionsOnceSessionStartedIsIgnored() // Assert no exception has been thrown by `getStorage()` $this->addToAssertionCount(1); } + + /** + * @requires PHP 5.4 + */ + public function testGetBagsOnceSessionStartedIsIgnored() + { + session_start(); + $bag = new AttributeBag(); + $bag->setName('flashes'); + + $storage = $this->getStorage(); + $storage->registerBag($bag); + + $this->assertEquals($storage->getBag('flashes'), $bag); + } } diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 80e06235959ce..9bfee93d6270f 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -58,11 +58,11 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.7.36'; - const VERSION_ID = 20736; + const VERSION = '2.7.37'; + const VERSION_ID = 20737; const MAJOR_VERSION = 2; const MINOR_VERSION = 7; - const RELEASE_VERSION = 36; + const RELEASE_VERSION = 37; const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '05/2018';