Merge branch '4.0'

* 4.0:
  [DI] Fix missing unset leading to false-positive circular ref
  [DI] Fix deep-inlining of non-shared refs
  parse newlines in quoted multiline strings
  Fix collision between view properties and form fields
  Fix collision between view properties and form fields
  [SecurityBundle] Fix compat with HttpFoundation >=3.4
  [DI] turn $private to protected in dumped container, to make cache:clear BC
  Fix collision between view properties and form fields
This commit is contained in:
Nicolas Grekas 2017-12-04 19:35:44 +01:00
commit 19b03b5d7e
36 changed files with 279 additions and 42 deletions

View File

@ -13,6 +13,7 @@ namespace Symfony\Bridge\Twig\Extension;
use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser;
use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\FormView;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
@ -72,9 +73,15 @@ class FormExtension extends AbstractExtension
{
return array(
new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'),
new TwigTest('rootform', array($this, 'isRootForm')),
);
}
public function isRootForm(FormView $formView)
{
return null === $formView->parent;
}
/**
* {@inheritdoc}
*/

View File

@ -140,12 +140,12 @@
{% block form_errors -%}
{% if errors|length > 0 -%}
{% if form.parent %}<span class="help-block">{% else %}<div class="alert alert-danger">{% endif %}
{% if form is not rootform %}<span class="help-block">{% else %}<div class="alert alert-danger">{% endif %}
<ul class="list-unstyled">
{%- for error in errors -%}
<li><span class="glyphicon glyphicon-exclamation-sign"></span> {{ error.message }}</li>
{%- endfor -%}
</ul>
{% if form.parent %}</span>{% else %}</div>{% endif %}
{% if form is not rootform %}</span>{% else %}</div>{% endif %}
{%- endif %}
{%- endblock form_errors %}

View File

@ -173,7 +173,7 @@
{% block form_errors -%}
{%- if errors|length > 0 -%}
<div class="{% if form.parent %}invalid-feedback{% else %}alert alert-danger{% endif %}">
<div class="{% if form is not rootform %}invalid-feedback{% else %}alert alert-danger{% endif %}">
<ul class="list-unstyled mb-0">
{%- for error in errors -%}
<li>{{ error.message }}</li>

View File

@ -15,7 +15,7 @@
{%- block form_widget_compound -%}
<div {{ block('widget_container_attributes') }}>
{%- if form.parent is empty -%}
{%- if form is rootform -%}
{{ form_errors(form) }}
{%- endif -%}
{{- block('form_rows') -}}
@ -349,7 +349,7 @@
{% endif %}
{%- endfor %}
{% if not form.methodRendered and form.parent is null %}
{% if not form.methodRendered and form is rootform %}
{%- do form.setMethodRendered() -%}
{% set method = method|upper %}
{%- if method in ["GET", "POST"] -%}

View File

@ -31,7 +31,7 @@
{%- block form_widget_compound -%}
<table {{ block('widget_container_attributes') }}>
{%- if form.parent is empty and errors|length > 0 -%}
{%- if form is rootform and errors|length > 0 -%}
<tr>
<td colspan="2">
{{- form_errors(form) -}}

View File

@ -318,11 +318,11 @@
{% block form_errors -%}
{% if errors|length > 0 -%}
{% if form.parent %}<small class="error">{% else %}<div data-alert class="alert-box alert">{% endif %}
{% if form is not rootform %}<small class="error">{% else %}<div data-alert class="alert-box alert">{% endif %}
{%- for error in errors -%}
{{ error.message }}
{% if not loop.last %}, {% endif %}
{%- endfor -%}
{% if form.parent %}</small>{% else %}</div>{% endif %}
{% if form is not rootform %}</small>{% else %}</div>{% endif %}
{%- endif %}
{%- endblock form_errors %}

View File

@ -149,6 +149,23 @@ class FormExtensionDivLayoutTest extends AbstractDivLayoutTest
$this->assertSame('<form name="form" method="get" action="0">', $html);
}
public function isRootFormProvider()
{
return array(
array(true, new FormView()),
array(false, new FormView(new FormView())),
);
}
/**
* @dataProvider isRootFormProvider
*/
public function testIsRootForm($expected, FormView $formView)
{
$extension = new FormExtension();
$this->assertSame($expected, $extension->isRootForm($formView));
}
protected function renderForm(FormView $view, array $vars = array())
{
return (string) $this->renderer->renderBlock($view, 'form', $vars);

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
@ -23,6 +24,7 @@ use Symfony\Component\DependencyInjection\Reference;
class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface
{
private $repeatedPass;
private $cloningIds = array();
/**
* {@inheritdoc}
@ -41,17 +43,43 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe
// Reference found in ArgumentInterface::getValues() are not inlineable
return $value;
}
if ($value instanceof Reference && $this->container->hasDefinition($id = (string) $value)) {
$definition = $this->container->getDefinition($id);
if ($this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) {
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
return $definition->isShared() ? $definition : clone $definition;
if ($value instanceof Definition && $this->cloningIds) {
if ($value->isShared()) {
return $value;
}
$value = clone $value;
}
return parent::processValue($value, $isRoot);
if (!$value instanceof Reference || !$this->container->hasDefinition($id = (string) $value)) {
return parent::processValue($value, $isRoot);
}
$definition = $this->container->getDefinition($id);
if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) {
return $value;
}
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
if ($definition->isShared()) {
return $definition;
}
if (isset($this->cloningIds[$id])) {
$ids = array_keys($this->cloningIds);
$ids[] = $id;
throw new ServiceCircularReferenceException($id, array_slice($ids, array_search($id, $ids)));
}
$this->cloningIds[$id] = true;
try {
return $this->processValue($definition);
} finally {
unset($this->cloningIds[$id]);
}
}
/**

View File

@ -878,7 +878,11 @@ class $class extends $baseClass
{
private \$parameters;
private \$targetDirs = array();
private \$privates = array();
/*{$this->docStar}
* @internal but protected for BC on cache:clear
*/
protected \$privates = array();
public function __construct()
{
@ -1062,7 +1066,7 @@ EOF;
return $code." );\n";
}
private function addInlineRequires() :string
private function addInlineRequires(): string
{
if (!$this->hotPathTag || !$this->inlineRequires) {
return '';

View File

@ -111,6 +111,60 @@ class InlineServiceDefinitionsPassTest extends TestCase
$this->assertEquals($container->getDefinition('foo')->getArgument(0), $container->getDefinition('bar'));
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
* @expectedExceptionMessage Circular reference detected for service "bar", path: "bar -> foo -> bar".
*/
public function testProcessThrowsOnNonSharedLoops()
{
$container = new ContainerBuilder();
$container
->register('foo')
->addArgument(new Reference('bar'))
->setShared(false)
;
$container
->register('bar')
->setShared(false)
->addMethodCall('setFoo', array(new Reference('foo')))
;
$this->process($container);
}
public function testProcessNestedNonSharedServices()
{
$container = new ContainerBuilder();
$container
->register('foo')
->addArgument(new Reference('bar1'))
->addArgument(new Reference('bar2'))
;
$container
->register('bar1')
->setShared(false)
->addArgument(new Reference('baz'))
;
$container
->register('bar2')
->setShared(false)
->addArgument(new Reference('baz'))
;
$container
->register('baz')
->setShared(false)
;
$this->process($container);
$baz1 = $container->getDefinition('foo')->getArgument(0)->getArgument(0);
$baz2 = $container->getDefinition('foo')->getArgument(1)->getArgument(0);
$this->assertEquals($container->getDefinition('baz'), $baz1);
$this->assertEquals($container->getDefinition('baz'), $baz2);
$this->assertNotSame($baz1, $baz2);
}
public function testProcessInlinesIfMultipleReferencesButAllFromTheSameDefinition()
{
$container = new ContainerBuilder();

View File

@ -20,7 +20,11 @@ class Container extends \Symfony\Component\DependencyInjection\Dump\AbstractCont
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_EnvParameters extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -309,7 +309,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Private extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Almost_Circular_Public extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Base64Parameters extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -18,7 +18,11 @@ class Symfony_DI_PhpDumper_Test_Uninitialized_Reference extends Container
{
private $parameters;
private $targetDirs = array();
private $privates = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{

View File

@ -684,6 +684,8 @@ class Parser
return Inline::parse($value, $flags, $this->refs);
}
$lines = array();
while ($this->moveToNextLine()) {
// unquoted strings end before the first unindented line
if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
@ -692,7 +694,7 @@ class Parser
break;
}
$value .= ' '.trim($this->currentLine);
$lines[] = trim($this->currentLine);
// quoted string values end with a line that is terminated with the quotation character
if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) {
@ -700,6 +702,21 @@ class Parser
}
}
for ($i = 0, $linesCount = count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
if ('' === $lines[$i]) {
$value .= "\n";
$previousLineBlank = true;
} elseif ($previousLineBlank) {
$value .= $lines[$i];
$previousLineBlank = false;
} else {
$value .= ' '.$lines[$i];
$previousLineBlank = false;
}
}
Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
$parsedValue = Inline::parse($value, $flags, $this->refs);
if ('mapping' === $context && is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {

View File

@ -1451,6 +1451,20 @@ YAML;
$this->assertSame($expected, $this->parser->parse($yaml));
}
public function testBlankLinesInQuotedMultiLineString()
{
$yaml = <<<YAML
foobar: 'foo
bar'
YAML;
$expected = array(
'foobar' => "foo\nbar",
);
$this->assertSame($expected, $this->parser->parse($yaml));
}
public function testParseMultiLineUnquotedString()
{
$yaml = <<<EOT