From da7fc36a434e3e23ed28c6692cfbc6bbe2c42f1b Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 10 Jun 2016 23:57:14 +0200 Subject: [PATCH 1/8] [Yaml] properly count skipped comment lines --- src/Symfony/Component/Yaml/Parser.php | 107 ++++++++---------- .../Component/Yaml/Tests/ParserTest.php | 19 ++++ 2 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index e134a4be99..dcc9a0150e 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -30,17 +30,21 @@ class Parser private $currentLineNb = -1; private $currentLine = ''; private $refs = array(); + private $skippedLineNumbers = array(); + private $locallySkippedLineNumbers = array(); /** * Constructor. * * @param int $offset The offset of YAML document (used for line numbers in error messages) * @param int|null $totalNumberOfLines The overall number of lines being parsed + * @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser */ - public function __construct($offset = 0, $totalNumberOfLines = null) + public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array()) { $this->offset = $offset; $this->totalNumberOfLines = $totalNumberOfLines; + $this->skippedLineNumbers = $skippedLineNumbers; } /** @@ -101,25 +105,18 @@ class Parser // array if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { - $c = $this->getRealCurrentLineNb() + 1; - $parser = new self($c, $this->totalNumberOfLines); - $parser->refs = &$this->refs; - $data[] = $parser->parse($this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); } else { if (isset($values['leadspaces']) && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) ) { // this is a compact notation element, add to next block and parse - $c = $this->getRealCurrentLineNb(); - $parser = new self($c, $this->totalNumberOfLines); - $parser->refs = &$this->refs; - $block = $values['value']; if ($this->isNextLineIndented()) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + strlen($values['leadspaces']) + 1); } - $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport, $objectForMap); + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap); } else { $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap); } @@ -175,10 +172,7 @@ class Parser } else { $value = $this->getNextEmbedBlock(); } - $c = $this->getRealCurrentLineNb() + 1; - $parser = new self($c, $this->totalNumberOfLines); - $parser->refs = &$this->refs; - $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap); + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap); if (!is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); @@ -226,10 +220,7 @@ class Parser $data[$key] = null; } } else { - $c = $this->getRealCurrentLineNb() + 1; - $parser = new self($c, $this->totalNumberOfLines); - $parser->refs = &$this->refs; - $value = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); // Spec: Keys MUST be unique; first one wins. // But overwriting is allowed when a merge node is used in current block. if ($allowOverwrite || !isset($data[$key])) { @@ -317,6 +308,24 @@ class Parser return empty($data) ? null : $data; } + private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers); + $parser->refs = &$this->refs; + + return $parser->parse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } + /** * Returns the current line number (takes the offset into account). * @@ -324,7 +333,17 @@ class Parser */ private function getRealCurrentLineNb() { - return $this->currentLineNb + $this->offset; + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; } /** @@ -426,7 +445,15 @@ class Parser } // we ignore "comment" lines only when we are not inside a scalar block - if (empty($blockScalarIndentations) && $this->isCurrentLineComment() && false === $this->checkIfPreviousNonCommentLineIsCollectionItem()) { + if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { + // remember ignored comment lines (they are used later in nested + // parser calls to determine real line numbers) + // + // CAUTION: beware to not populate the global property here as it + // will otherwise influence the getRealCurrentLineNb() call here + // for consecutive comment lines and subsequent embedded blocks + $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb(); + continue; } @@ -786,44 +813,4 @@ class Parser { return (bool) preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); } - - /** - * Returns true if the current line is a collection item. - * - * @return bool - */ - private function isCurrentLineCollectionItem() - { - $ltrimmedLine = ltrim($this->currentLine, ' '); - - return '' !== $ltrimmedLine && '-' === $ltrimmedLine[0]; - } - - /** - * Tests whether the current comment line is in a collection. - * - * @return bool - */ - private function checkIfPreviousNonCommentLineIsCollectionItem() - { - $isCollectionItem = false; - $moves = 0; - while ($this->moveToPreviousLine()) { - ++$moves; - // If previous line is a comment, move back again. - if ($this->isCurrentLineComment()) { - continue; - } - $isCollectionItem = $this->isCurrentLineCollectionItem(); - break; - } - - // Move parser back to previous line. - while ($moves > 0) { - $this->moveToNextLine(); - --$moves; - } - - return $isCollectionItem; - } } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 149dd2b073..1b6193a692 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -607,6 +607,25 @@ EOT; $this->assertSame($expected, $this->parser->parse($yaml)); } + public function testSequenceFollowedByCommentEmbeddedInMapping() + { + $yaml = << array( + 'b' => array('c'), + 'd' => 'e', + ), + ); + + $this->assertSame($expected, $this->parser->parse($yaml)); + } + /** * @expectedException \Symfony\Component\Yaml\Exception\ParseException */ From 12b5509d8b4fede49448f3977c018db322bd01bf Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 29 May 2016 12:39:28 +0200 Subject: [PATCH 2/8] force enabling the external XML entity loaders --- .../DependencyInjection/Loader/XmlFileLoader.php | 3 ++- .../Tests/Loader/XmlFileLoaderTest.php | 13 +++++++++++++ .../Translation/Loader/XliffFileLoader.php | 6 ++++++ .../Tests/Loader/XliffFileLoaderTest.php | 14 ++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 9e2e679e61..33b98a3aaa 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -315,7 +315,6 @@ class XmlFileLoader extends FileLoader foreach ($definitions as $id => $def) { list($domElement, $file, $wild) = $def; - if (null !== $definition = $this->parseDefinition($domElement, $file)) { $this->container->setDefinition($id, $definition); } @@ -485,7 +484,9 @@ $imports EOF ; + $disableEntities = libxml_disable_entity_loader(false); $valid = @$dom->schemaValidateSource($source); + libxml_disable_entity_loader($disableEntities); foreach ($tmpfiles as $tmpfile) { @unlink($tmpfile); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 865d90da5e..ca1e7b8500 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -84,6 +84,19 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase $this->assertInstanceOf('DOMDocument', $xml, '->parseFileToDOM() returns an SimpleXMLElement object'); } + public function testLoadWithExternalEntitiesDisabled() + { + $disableEntities = libxml_disable_entity_loader(true); + + $containerBuilder = new ContainerBuilder(); + $loader = new XmlFileLoader($containerBuilder, new FileLocator(self::$fixturesPath.'/xml')); + $loader->load('services2.xml'); + + libxml_disable_entity_loader($disableEntities); + + $this->assertTrue(count($containerBuilder->getParameterBag()->all()) > 0, 'Parameters can be read from the config file.'); + } + public function testLoadParameters() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index 0e335db425..46302506b3 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -143,10 +143,16 @@ class XliffFileLoader implements LoaderInterface $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); $source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source); + $disableEntities = libxml_disable_entity_loader(false); + if (!@$dom->schemaValidateSource($source)) { + libxml_disable_entity_loader($disableEntities); + throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors)))); } + libxml_disable_entity_loader($disableEntities); + $dom->normalizeDocument(); libxml_clear_errors(); diff --git a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php index eab501ae5f..ba135af6cb 100644 --- a/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php +++ b/src/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php @@ -46,6 +46,20 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase libxml_use_internal_errors($internalErrors); } + public function testLoadWithExternalEntitiesDisabled() + { + $disableEntities = libxml_disable_entity_loader(true); + + $loader = new XliffFileLoader(); + $resource = __DIR__.'/../fixtures/resources.xlf'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + libxml_disable_entity_loader($disableEntities); + + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + public function testLoadWithResname() { $loader = new XliffFileLoader(); From d92f3ea29ee75b97a6c5dc730b662fda509d31b0 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 12 Jun 2016 09:55:59 +0200 Subject: [PATCH 3/8] [Console] added explanation of messages usage in a progress bar --- src/Symfony/Component/Console/Helper/ProgressBar.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 1b2eefd29e..261141ff1d 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -41,7 +41,7 @@ class ProgressBar private $stepWidth; private $percent = 0.0; private $formatLineCount; - private $messages; + private $messages = array(); private $overwrite = true; private static $formatters; @@ -139,6 +139,16 @@ class ProgressBar return isset(self::$formats[$name]) ? self::$formats[$name] : null; } + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ public function setMessage($message, $name = 'message') { $this->messages[$name] = $message; From 445dcc8ae2e428a37ebb990bfcad2c3342199c24 Mon Sep 17 00:00:00 2001 From: Jules Pietri Date: Fri, 10 Jun 2016 11:21:48 +0200 Subject: [PATCH 4/8] [Form] Fixed collapsed choice attributes --- .../views/Form/choice_attributes.html.php | 9 +++ .../views/Form/choice_widget_options.html.php | 2 +- .../Tests/AbstractBootstrap3LayoutTest.php | 62 +++++++++++++++++++ .../Form/Tests/AbstractLayoutTest.php | 49 +++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_attributes.html.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_attributes.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_attributes.html.php new file mode 100644 index 0000000000..8d2d6d7fd6 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_attributes.html.php @@ -0,0 +1,9 @@ +id="escape($id) ?>" name="escape($full_name) ?>" +disabled="disabled" + $v): ?> + +escape($k), $view->escape($k)) ?> + +escape($k), $view->escape($v)) ?> + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php index a0363cc5a4..f1c6ad3b56 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form/choice_widget_options.html.php @@ -8,6 +8,6 @@ $translatorHelper = $view['translator']; // outside of the loop for performance block($form, 'choice_widget_options', array('choices' => $choice)) ?> - + diff --git a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php index ee260bd0c8..c3e1b3bd8a 100644 --- a/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractBootstrap3LayoutTest.php @@ -232,6 +232,68 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest ); } + public function testSingleChoiceAttributesWithMainAttributes() + { + $form = $this->factory->createNamed('name', 'choice', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false, + 'attr' => array('class' => 'bar&baz'), + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')), +'/select + [@name="name"] + [@class="bar&baz form-control"] + [not(@required)] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testSingleExpandedChoiceAttributesWithMainAttributes() + { + $form = $this->factory->createNamed('name', 'choice', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => true, + 'attr' => array('class' => 'bar&baz'), + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')), +'/div + [@class="bar&baz"] + [ + ./div + [@class="radio"] + [ + ./label + [.=" [trans]Choice&A[/trans]"] + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + ] + ] + /following-sibling::div + [@class="radio"] + [ + ./label + [.=" [trans]Choice&B[/trans]"] + [ + ./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + ] + ] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] +' + ); + } + public function testSelectWithSizeBiggerThanOneCanBeRequired() { $form = $this->factory->createNamed('name', 'choice', null, array( diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index c31513e209..cad57cabb5 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -596,6 +596,55 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg ); } + public function testSingleChoiceAttributesWithMainAttributes() + { + $form = $this->factory->createNamed('name', 'choice', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false, + 'attr' => array('class' => 'bar&baz'), + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')), +'/select + [@name="name"] + [@class="bar&baz"] + [not(@required)] + [ + ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] + /following-sibling::option[@value="&b"][not(@class)][not(@selected)][.="[trans]Choice&B[/trans]"] + ] + [count(./option)=2] +' + ); + } + + public function testSingleExpandedChoiceAttributesWithMainAttributes() + { + $form = $this->factory->createNamed('name', 'choice', '&a', array( + 'choices' => array('Choice&A' => '&a', 'Choice&B' => '&b'), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => true, + 'attr' => array('class' => 'bar&baz'), + )); + + $this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'bar&baz')), +'/div + [@class="bar&baz"] + [ + ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked] + /following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"] + /following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)] + /following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"] + /following-sibling::input[@type="hidden"][@id="name__token"] + ] + [count(./input)=3] +' + ); + } + public function testSingleChoiceWithPreferred() { $form = $this->factory->createNamed('name', 'choice', '&a', array( From b35658b1ff90d66a1f8e7ad392fcaa71d5f847e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bertolini?= Date: Mon, 13 Jun 2016 10:55:07 +0200 Subject: [PATCH 5/8] Fix feature detection for IE operator `!` has higher precedence (4) than operator `in` (8). Parentheses are mandatory here. --- .../Resources/views/Profiler/base_js.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig index 23e05030c5..a1b9c4ffd9 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/base_js.html.twig @@ -209,7 +209,7 @@ var addEventListener; var el = document.createElement('div'); - if (!'addEventListener' in el) { + if (!('addEventListener' in el)) { addEventListener = function (element, eventName, callback) { element.attachEvent('on' + eventName, callback); }; From 6da99593a1fb8552f66e58fdfac40ba61af3b731 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 13 Jun 2016 17:31:05 +0200 Subject: [PATCH 6/8] [travis] HHVM 3.12 LTS --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 778d0e9c04..b5a28f71fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ env: matrix: include: # Use the newer stack for HHVM as HHVM does not support Precise anymore since a long time and so Precise has an outdated version - - php: hhvm + - php: hhvm-3.12 sudo: required dist: trusty group: edge From 37fac3e44f694be2faf3bdf5ccacccdd8fba8273 Mon Sep 17 00:00:00 2001 From: Ahmed Tailouloute Date: Mon, 13 Jun 2016 22:48:59 +0000 Subject: [PATCH 7/8] [Console] fixed PHPDoc --- .../Component/Console/Helper/TableSeparator.php | 3 +-- src/Symfony/Component/Console/Output/NullOutput.php | 12 ++++++++++++ src/Symfony/Component/Console/Output/Output.php | 12 ++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/TableSeparator.php b/src/Symfony/Component/Console/Helper/TableSeparator.php index 8cbbc6613b..8cc73e69a2 100644 --- a/src/Symfony/Component/Console/Helper/TableSeparator.php +++ b/src/Symfony/Component/Console/Helper/TableSeparator.php @@ -19,8 +19,7 @@ namespace Symfony\Component\Console\Helper; class TableSeparator extends TableCell { /** - * @param string $value - * @param array $options + * @param array $options */ public function __construct(array $options = array()) { diff --git a/src/Symfony/Component/Console/Output/NullOutput.php b/src/Symfony/Component/Console/Output/NullOutput.php index a14ae74d00..bd3b7e6fbf 100644 --- a/src/Symfony/Component/Console/Output/NullOutput.php +++ b/src/Symfony/Component/Console/Output/NullOutput.php @@ -73,21 +73,33 @@ class NullOutput implements OutputInterface return self::VERBOSITY_QUIET; } + /** + * {@inheritdoc} + */ public function isQuiet() { return true; } + /** + * {@inheritdoc} + */ public function isVerbose() { return false; } + /** + * {@inheritdoc} + */ public function isVeryVerbose() { return false; } + /** + * {@inheritdoc} + */ public function isDebug() { return false; diff --git a/src/Symfony/Component/Console/Output/Output.php b/src/Symfony/Component/Console/Output/Output.php index 306dccf24d..32bfc8cc3b 100644 --- a/src/Symfony/Component/Console/Output/Output.php +++ b/src/Symfony/Component/Console/Output/Output.php @@ -94,21 +94,33 @@ abstract class Output implements OutputInterface return $this->verbosity; } + /** + * {@inheritdoc} + */ public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } + /** + * {@inheritdoc} + */ public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } + /** + * {@inheritdoc} + */ public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } + /** + * {@inheritdoc} + */ public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; From 9569c7453cf2a283d25acae3ca5fba79904ac474 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 14 Jun 2016 11:09:19 +0200 Subject: [PATCH 8/8] [HttpFoundation] Use UPSERT for sessions stored in PgSql >= 9.5 --- .../Doctrine/HttpFoundation/DbalSessionHandler.php | 13 ++++++++----- .../Session/Storage/Handler/PdoSessionHandler.php | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php index fd7dcff62c..c3570da7da 100644 --- a/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php +++ b/src/Symfony/Bridge/Doctrine/HttpFoundation/DbalSessionHandler.php @@ -180,7 +180,7 @@ class DbalSessionHandler implements \SessionHandlerInterface $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 +224,11 @@ class DbalSessionHandler implements \SessionHandlerInterface { $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 +239,11 @@ class DbalSessionHandler implements \SessionHandlerInterface 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->con->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) = (:data, :time) WHERE $this->idCol = :id"; } } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 48e81ee0f1..1aad67960a 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -347,7 +347,7 @@ class PdoSessionHandler implements \SessionHandlerInterface $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 (given the LOCK_NONE behavior). // 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 @@ -659,11 +659,11 @@ class PdoSessionHandler implements \SessionHandlerInterface */ private function getMergeSql() { - switch ($this->driver) { - case 'mysql': + switch (true) { + case 'mysql' === $this->driver: return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; - case 'oci': + case 'oci' === $this->driver: // 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->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". @@ -674,8 +674,11 @@ class PdoSessionHandler implements \SessionHandlerInterface 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->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time;"; - case 'sqlite': + case 'sqlite' === $this->driver: return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (:data, :lifetime, :time) WHERE $this->idCol = :id"; } }