Merge branch '4.4' into 5.0

* 4.4: (26 commits)
  [HttpClient] NativeHttpClient should not send >1.1 protocol version
  [HttpClient] fix support for non-blocking resource streams
  [Mailer] Make sure you can pass custom headers to Mailgun
  [Mailer] Remove line breaks in email attachment content
  Update links to documentation
  [Validator] Add the missing translations for the Arabic (ar) locale
  ensure to expect no validation for the right reasons
  [Security-Guard] fixed 35203 missing name tag in param docblock
  [HttpClient] fix casting responses to PHP streams
  [PhpUnitBridge] Add test case for @expectedDeprecation annotation
  [PhpUnitBridge][SymfonyTestsListenerTrait] Remove $testsWithWarnings stack
  [Mailer] Fix addresses management in Sendgrid API payload
  [Mailer][MailchimpBridge] Fix missing attachments when sending via Mandrill API
  [Mailer][MailchimpBridge] Fix incorrect sender address when sender has name
  [HttpClient] fix capturing SSL certificates with NativeHttpClient
  Update year in license files
  [TwigBridge][Form] Added missing help messages in form themes
  Update year in license files
  Update year in license files
  fix version when "anonymous: lazy" was introduced
  ...
This commit is contained in:
Nicolas Grekas 2020-01-04 15:08:26 +01:00
commit 581b439931
122 changed files with 596 additions and 159 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2014-2019 Fabien Potencier
Copyright (c) 2014-2020 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

View File

@ -47,11 +47,6 @@ class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener
$this->trait->startTest($test);
}
public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time)
{
$this->trait->addWarning($test, $e, $time);
}
public function endTest(\PHPUnit_Framework_Test $test, $time)
{
$this->trait->endTest($test, $time);

View File

@ -52,11 +52,6 @@ class SymfonyTestsListenerForV6 extends BaseTestListener
$this->trait->startTest($test);
}
public function addWarning(Test $test, Warning $e, $time)
{
$this->trait->addWarning($test, $e, $time);
}
public function endTest(Test $test, $time)
{
$this->trait->endTest($test, $time);

View File

@ -55,11 +55,6 @@ class SymfonyTestsListenerForV7 implements TestListener
$this->trait->startTest($test);
}
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->trait->addWarning($test, $e, $time);
}
public function endTest(Test $test, float $time): void
{
$this->trait->endTest($test, $time);

View File

@ -40,7 +40,6 @@ class SymfonyTestsListenerTrait
private $expectedDeprecations = array();
private $gatheredDeprecations = array();
private $previousErrorHandler;
private $testsWithWarnings;
private $reportUselessTests;
private $error;
private $runsInSeparateProcess = false;
@ -112,7 +111,6 @@ class SymfonyTestsListenerTrait
public function startTestSuite($suite)
{
$suiteName = $suite->getName();
$this->testsWithWarnings = array();
foreach ($suite->tests() as $test) {
if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) {
@ -236,13 +234,6 @@ class SymfonyTestsListenerTrait
}
}
public function addWarning($test, $e, $time)
{
if ($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase) {
$this->testsWithWarnings[$test->getName()] = true;
}
}
public function endTest($test, $time)
{
if (class_exists(DebugClassLoader::class, false)) {

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
final class ExpectedDeprecationAnnotationTest extends TestCase
{
/**
* Do not remove this test in the next major versions.
*
* @group legacy
*
* @expectedDeprecation foo
*/
public function testOne()
{
@trigger_error('foo', E_USER_DEPRECATED);
}
/**
* Do not remove this test in the next major versions.
*
* @group legacy
*
* @expectedDeprecation foo
* @expectedDeprecation bar
*/
public function testMany()
{
@trigger_error('foo', E_USER_DEPRECATED);
@trigger_error('bar', E_USER_DEPRECATED);
}
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -64,6 +64,7 @@ col-sm-10
<div class="{{ block('form_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}}
</div>{#--#}
</div>

View File

@ -148,6 +148,7 @@
{% block checkbox_row -%}
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group' ~ (not valid ? ' has-error'))|trim})} %}{{ block('attributes') }}{% endwith %}>
{{- form_widget(form) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock checkbox_row %}
@ -155,6 +156,7 @@
{% block radio_row -%}
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group' ~ (not valid ? ' has-error'))|trim})} %}{{ block('attributes') }}{% endwith %}>
{{- form_widget(form) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock radio_row %}

View File

@ -311,6 +311,7 @@
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' row')|trim})} %}{{ block('attributes') }}{% endwith %}>
<div class="large-12 columns{% if not valid %} error{% endif %}">
{{ form_widget(form) }}
{{- form_help(form) -}}
{{ form_errors(form) }}
</div>
</div>
@ -320,6 +321,7 @@
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' row')|trim})} %}{{ block('attributes') }}{% endwith %}>
<div class="large-12 columns{% if not valid %} error{% endif %}">
{{ form_widget(form) }}
{{- form_help(form) -}}
{{ form_errors(form) }}
</div>
</div>

View File

@ -163,4 +163,23 @@ abstract class AbstractBootstrap3HorizontalLayoutTest extends AbstractBootstrap3
$this->assertMatchesXpath($html, '/div[@class="form-group"]/div[@class="col-sm-2" or @class="col-sm-10"]', 2);
}
public function testCheckboxRowWithHelp()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType');
$html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']);
$this->assertMatchesXpath($html,
'/div
[@class="form-group"]
[
./div[@class="col-sm-2" or @class="col-sm-10"]
/following-sibling::div[@class="col-sm-2" or @class="col-sm-10"]
[
./span[text() = "[trans]really helpful text[/trans]"]
]
]
'
);
}
}

View File

@ -333,6 +333,21 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
public function testCheckboxRowWithHelp()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType');
$html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']);
$this->assertMatchesXpath($html,
'/div
[@class="form-group"]
[
./span[text() = "[trans]really helpful text[/trans]"]
]
'
);
}
public function testSingleChoice()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [
@ -2265,6 +2280,21 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
);
}
public function testRadioRowWithHelp()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false);
$html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']);
$this->assertMatchesXpath($html,
'/div
[@class="form-group"]
[
./span[text() = "[trans]really helpful text[/trans]"]
]
'
);
}
public function testRange()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RangeType', 42, ['attr' => ['min' => 5]]);

View File

@ -231,6 +231,25 @@ abstract class AbstractBootstrap4HorizontalLayoutTest extends AbstractBootstrap4
./small[text() = "[trans]really helpful text[/trans]"]
]
]
'
);
}
public function testRadioRowWithHelp()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false);
$html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']);
$this->assertMatchesXpath($html,
'/div
[@class="form-group row"]
[
./div[@class="col-sm-2" or @class="col-sm-10"]
/following-sibling::div[@class="col-sm-2" or @class="col-sm-10"]
[
./small[text() = "[trans]really helpful text[/trans]"]
]
]
'
);
}

View File

@ -422,6 +422,21 @@ abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest
);
}
public function testCheckboxRowWithHelp()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\CheckboxType');
$html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']);
$this->assertMatchesXpath($html,
'/div
[@class="form-group"]
[
./small[text() = "[trans]really helpful text[/trans]"]
]
'
);
}
public function testSingleChoiceExpanded()
{
$form = $this->factory->createNamed('name', ChoiceType::class, '&a', [
@ -1027,6 +1042,21 @@ abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest
);
}
public function testRadioRowWithHelp()
{
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RadioType', false);
$html = $this->renderRow($form->createView(), ['label' => 'foo', 'help' => 'really helpful text']);
$this->assertMatchesXpath($html,
'/div
[@class="form-group"]
[
./small[text() = "[trans]really helpful text[/trans]"]
]
'
);
}
public function testButtonAttributeNameRepeatedIfTrue()
{
$form = $this->factory->createNamed('button', ButtonType::class, null, [

View File

@ -1,4 +1,4 @@
Copyright (c) 2014-2019 Fabien Potencier
Copyright (c) 2014-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -20,6 +20,7 @@ CHANGELOG
4.4.0
-----
* Added `anonymous: lazy` mode to firewalls to make them (not) start the session as late as possible
* Added `migrate_from` option to encoders configuration.
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
* Deprecated the usage of "query_string" without a "search_dn" and a "search_password" config key in Ldap factories.
@ -28,7 +29,6 @@ CHANGELOG
4.3.0
-----
* Added `anonymous: lazy` mode to firewalls to make them (not) start the session as late as possible
* Added new encoder types: `auto` (recommended), `native` and `sodium`
* The normalization of the cookie names configured in the `logout.delete_cookies`
option is deprecated and will be disabled in Symfony 5.0. This affects to cookies

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2016-2019 Fabien Potencier
Copyright (c) 2016-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -8,7 +8,7 @@ may be (YAML, XML, INI files, or for instance a database).
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/config/index.html)
* [Documentation](https://symfony.com/doc/current/components/config.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)

View File

@ -48,12 +48,12 @@ class FormatterHelper extends Helper
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
$len = max(self::strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? [str_repeat(' ', $len)] : [];
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i]));
$messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i]));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
@ -73,17 +73,13 @@ class FormatterHelper extends Helper
*/
public function truncate(string $message, int $length, string $suffix = '...')
{
$computedLength = $length - $this->strlen($suffix);
$computedLength = $length - self::strlen($suffix);
if ($computedLength > $this->strlen($message)) {
if ($computedLength > self::strlen($message)) {
return $message;
}
if (false === $encoding = mb_detect_encoding($message, null, true)) {
return substr($message, 0, $length).$suffix;
}
return mb_substr($message, 0, $length, $encoding).$suffix;
return self::substr($message, 0, $length).$suffix;
}
/**

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -7,7 +7,7 @@ interfaces.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/console/index.html)
* [Documentation](https://symfony.com/doc/current/components/console.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -7,7 +7,7 @@ way objects are constructed in your application.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/dependency_injection/index.html)
* [Documentation](https://symfony.com/doc/current/components/dependency_injection.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2016-2019 Fabien Potencier
Copyright (c) 2016-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -8,7 +8,7 @@ them.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/event_dispatcher/index.html)
* [Documentation](https://symfony.com/doc/current/components/event_dispatcher.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -6,7 +6,7 @@ The Filesystem component provides basic utilities for the filesystem.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/filesystem/index.html)
* [Documentation](https://symfony.com/doc/current/components/filesystem.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -6,7 +6,7 @@ The Form component allows you to easily create, process and reuse HTML forms.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/form/index.html)
* [Documentation](https://symfony.com/doc/current/components/form.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)

View File

@ -135,7 +135,10 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$parent->add($form);
$form->setData($object);
$parent->submit([]);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -190,10 +193,15 @@ class FormValidatorTest extends ConstraintValidatorTestCase
'validation_groups' => [],
])
->setData($object)
->setCompound(true)
->setDataMapper(new PropertyPathMapper())
->getForm();
$form->setData($object);
$form->submit([]);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -216,6 +224,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
// Launch transformer
$form->submit('foo');
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -238,6 +248,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$form->add($child);
$form->submit([]);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -266,6 +278,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
// Launch transformer
$form->submit('foo');
$this->assertTrue($form->isSubmitted());
$this->assertFalse($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -301,6 +315,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
// Launch transformer
$form->submit('foo');
$this->assertTrue($form->isSubmitted());
$this->assertFalse($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -412,6 +428,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
// Launch transformer
$form->submit(['child' => 'foo']);
$this->assertTrue($form->isSubmitted());
$this->assertFalse($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -617,7 +635,10 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$form = $this->getBuilder()
->setData('scalar')
->getForm();
$form->submit('foo');
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -635,6 +656,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$form->submit(['foo' => 'bar']);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());
@ -656,6 +679,8 @@ class FormValidatorTest extends ConstraintValidatorTestCase
$form->submit(['foo' => 'bar', 'baz' => 'qux', 'quux' => 'quuz']);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isSynchronized());
$this->expectNoValidate();
$this->validator->validate($form, new Form());

View File

@ -288,7 +288,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$pushedResponse = $pushedResponse->response;
$pushedResponse->__construct($this->multi, $url, $options, $this->logger);
} else {
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s".', $url));
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url));
$pushedResponse = null;
}
}
@ -412,7 +412,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
return false;
}
foreach (['proxy', 'no_proxy', 'bindto'] as $k) {
foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) {
if ($options[$k] !== $pushedResponse->parentOptions[$k]) {
return false;
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2018-2019 Fabien Potencier
Copyright (c) 2018-2020 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

View File

@ -169,7 +169,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache;
}
$this->logger && $this->logger->info(sprintf('Request: %s %s', $method, implode('', $url)));
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url)));
[$host, $port, $url['authority']] = self::dnsResolve($url, $this->multi, $info, $onProgress);
@ -187,7 +187,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$context = [
'http' => [
'protocol_version' => $options['http_version'] ?: '1.1',
'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'),
'method' => $method,
'content' => $options['body'],
'ignore_errors' => true,
@ -357,7 +357,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
});
if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
$redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
});
}

View File

@ -162,13 +162,13 @@ final class NativeResponse implements ResponseInterface
restore_error_handler();
}
stream_set_blocking($h, false);
$this->context = $this->resolveRedirect = null;
if (isset($context['ssl']['peer_certificate_chain'])) {
if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) {
$this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain'];
}
stream_set_blocking($h, false);
$this->context = $this->resolveRedirect = null;
// Create dechunk and inflate buffers
if (isset($this->headers['content-length'])) {
$this->remaining = (int) $this->headers['content-length'][0];

View File

@ -204,7 +204,11 @@ trait ResponseTrait
$this->getHeaders($throw);
}
return StreamWrapper::createResource($this, null, $this->content, $this->handle && 'stream' === get_resource_type($this->handle) ? $this->handle : null);
$stream = StreamWrapper::createResource($this);
stream_get_meta_data($stream)['wrapper_data']
->bindHandles($this->handle, $this->content);
return $stream;
}
/**

View File

@ -37,19 +37,17 @@ class StreamWrapper
/** @var resource|null */
private $handle;
private $blocking = true;
private $timeout;
private $eof = false;
private $offset = 0;
/**
* Creates a PHP stream resource from a ResponseInterface.
*
* @param resource|null $contentBuffer The seekable resource where the response body is buffered
* @param resource|null $selectHandle The resource handle that should be monitored when
* stream_select() is used on the created stream
*
* @return resource
*/
public static function createResource(ResponseInterface $response, HttpClientInterface $client = null, $contentBuffer = null, $selectHandle = null)
public static function createResource(ResponseInterface $response, HttpClientInterface $client = null)
{
if (null === $client && !method_exists($response, 'stream')) {
throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__));
@ -63,8 +61,6 @@ class StreamWrapper
$context = [
'client' => $client ?? $response,
'response' => $response,
'content' => $contentBuffer,
'handle' => $selectHandle,
];
return fopen('symfony://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])) ?: null;
@ -78,6 +74,17 @@ class StreamWrapper
return $this->response;
}
/**
* @param resource|null $handle The resource handle that should be monitored when
* stream_select() is used on the created stream
* @param resource|null $content The seekable resource where the response body is buffered
*/
public function bindHandles(&$handle, &$content): void
{
$this->handle = &$handle;
$this->content = &$content;
}
public function stream_open(string $path, string $mode, int $options): bool
{
if ('r' !== $mode) {
@ -91,8 +98,6 @@ class StreamWrapper
$context = stream_context_get_options($this->context)['symfony'] ?? null;
$this->client = $context['client'] ?? null;
$this->response = $context['response'] ?? null;
$this->content = $context['content'] ?? null;
$this->handle = $context['handle'] ?? null;
$this->context = null;
if (null !== $this->client && null !== $this->response) {
@ -147,7 +152,7 @@ class StreamWrapper
return $data;
}
foreach ($this->client->stream([$this->response]) as $chunk) {
foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) {
try {
$this->eof = true;
$this->eof = !$chunk->isTimeout();
@ -178,6 +183,19 @@ class StreamWrapper
return '';
}
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
{
if (STREAM_OPTION_BLOCKING === $option) {
$this->blocking = (bool) $arg1;
} elseif (STREAM_OPTION_READ_TIMEOUT === $option) {
$this->timeout = $arg1 + $arg2 / 1e6;
} else {
return false;
}
return true;
}
public function stream_tell(): int
{
return $this->offset;
@ -238,6 +256,8 @@ class StreamWrapper
public function stream_cast(int $castAs)
{
if (STREAM_CAST_FOR_SELECT === $castAs) {
$this->response->getHeaders(false);
return $this->handle ?? false;
}

View File

@ -75,4 +75,20 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
$response = $client->request('GET', 'http://localhost:8057/404');
$stream = $response->toStream();
}
public function testNonBlockingStream()
{
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
$stream = $response->toStream();
$this->assertTrue(stream_set_blocking($stream, false));
$this->assertSame('<1>', fread($stream, 8192));
$this->assertFalse(feof($stream));
$this->assertTrue(stream_set_blocking($stream, true));
$this->assertSame('<2>', fread($stream, 8192));
$this->assertSame('', fread($stream, 8192));
$this->assertTrue(feof($stream));
}
}

View File

@ -171,6 +171,10 @@ class MockHttpClientTest extends HttpClientTestCase
return $client;
case 'testNonBlockingStream':
$responses[] = new MockResponse((function () { yield '<1>'; yield ''; yield '<2>'; })(), ['response_headers' => $headers]);
break;
case 'testMaxDuration':
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
$mock->expects($this->any())

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -7,7 +7,7 @@ specification.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/http_foundation/index.html)
* [Documentation](https://symfony.com/doc/current/components/http_foundation.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -9,7 +9,7 @@ an advanced CMS system (Drupal).
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/http_kernel/index.html)
* [Documentation](https://symfony.com/doc/current/components/http_kernel.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2012-2019 Fabien Potencier
Copyright (c) 2012-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2016-2019 Fabien Potencier
Copyright (c) 2016-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -13,6 +13,9 @@ namespace Symfony\Component\Mailer\Bridge\Mailchimp\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillApiTransport;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
class MandrillApiTransportTest extends TestCase
{
@ -41,4 +44,21 @@ class MandrillApiTransportTest extends TestCase
],
];
}
public function testCustomHeader()
{
$email = new Email();
$email->getHeaders()->addTextHeader('foo', 'bar');
$envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]);
$transport = new MandrillApiTransport('ACCESS_KEY');
$method = new \ReflectionMethod(MandrillApiTransport::class, 'getPayload');
$method->setAccessible(true);
$payload = $method->invoke($transport, $email, $envelope);
$this->assertArrayHasKey('message', $payload);
$this->assertArrayHasKey('headers', $payload['message']);
$this->assertCount(1, $payload['message']['headers']);
$this->assertEquals('foo: bar', $payload['message']['headers'][0]);
}
}

View File

@ -94,10 +94,14 @@ class MandrillApiTransport extends AbstractApiTransport
'type' => $headers->get('Content-Type')->getBody(),
];
if ($name = $headers->getHeaderParameter('Content-Disposition', 'name')) {
$att['name'] = $name;
}
if ('inline' === $disposition) {
$payload['images'][] = $att;
$payload['message']['images'][] = $att;
} else {
$payload['attachments'][] = $att;
$payload['message']['attachments'][] = $att;
}
}
@ -107,7 +111,7 @@ class MandrillApiTransport extends AbstractApiTransport
continue;
}
$payload['message']['headers'][] = $name.': '.$header->toString();
$payload['message']['headers'][] = $name.': '.$header->getBodyAsString();
}
return $payload;

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -13,6 +13,9 @@ namespace Symfony\Component\Mailer\Bridge\Mailgun\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunApiTransport;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
class MailgunApiTransportTest extends TestCase
{
@ -45,4 +48,20 @@ class MailgunApiTransportTest extends TestCase
],
];
}
public function testCustomHeader()
{
$json = json_encode(['foo' => 'bar']);
$email = new Email();
$email->getHeaders()->addTextHeader('X-Mailgun-Variables', $json);
$envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]);
$transport = new MailgunApiTransport('ACCESS_KEY', 'DOMAIN');
$method = new \ReflectionMethod(MailgunApiTransport::class, 'getPayload');
$method->setAccessible(true);
$payload = $method->invoke($transport, $email, $envelope);
$this->assertArrayHasKey('h:x-mailgun-variables', $payload);
$this->assertEquals($json, $payload['h:x-mailgun-variables']);
}
}

View File

@ -114,7 +114,7 @@ class MailgunApiTransport extends AbstractApiTransport
continue;
}
$payload['h:'.$name] = $header->toString();
$payload['h:'.$name] = $header->getBodyAsString();
}
return $payload;

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -13,6 +13,9 @@ namespace Symfony\Component\Mailer\Bridge\Postmark\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
class PostmarkApiTransportTest extends TestCase
{
@ -41,4 +44,21 @@ class PostmarkApiTransportTest extends TestCase
],
];
}
public function testCustomHeader()
{
$email = new Email();
$email->getHeaders()->addTextHeader('foo', 'bar');
$envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]);
$transport = new PostmarkApiTransport('ACCESS_KEY');
$method = new \ReflectionMethod(PostmarkApiTransport::class, 'getPayload');
$method->setAccessible(true);
$payload = $method->invoke($transport, $email, $envelope);
$this->assertArrayHasKey('Headers', $payload);
$this->assertCount(1, $payload['Headers']);
$this->assertEquals(['Name' => 'foo', 'Value' => 'bar'], $payload['Headers'][0]);
}
}

View File

@ -84,7 +84,7 @@ class PostmarkApiTransport extends AbstractApiTransport
$payload['Headers'][] = [
'Name' => $name,
'Value' => $header->toString(),
'Value' => $header->getBodyAsString(),
];
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -13,6 +13,8 @@ namespace Symfony\Component\Mailer\Bridge\Sendgrid\Tests\Transport;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridApiTransport;
use Symfony\Component\Mailer\Envelope;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Email;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
@ -48,8 +50,8 @@ class SendgridApiTransportTest extends TestCase
public function testSend()
{
$email = new Email();
$email->from('foo@example.com')
->to('bar@example.com')
$email->from(new Address('foo@example.com', 'Ms. Foo Bar'))
->to(new Address('bar@example.com', 'Mr. Recipient'))
->bcc('baz@example.com')
->text('content');
@ -73,12 +75,18 @@ class SendgridApiTransportTest extends TestCase
'json' => [
'personalizations' => [
[
'to' => [['email' => 'bar@example.com']],
'to' => [[
'email' => 'bar@example.com',
'name' => 'Mr. Recipient',
]],
'subject' => null,
'bcc' => [['email' => 'baz@example.com']],
],
],
'from' => ['email' => 'foo@example.com'],
'from' => [
'email' => 'foo@example.com',
'name' => 'Ms. Foo Bar',
],
'content' => [
['type' => 'text/plain', 'value' => 'content'],
],
@ -90,4 +98,72 @@ class SendgridApiTransportTest extends TestCase
$mailer = new SendgridApiTransport('foo', $httpClient);
$mailer->send($email);
}
public function testLineBreaksInEncodedAttachment()
{
$email = new Email();
$email->from('foo@example.com')
->to('bar@example.com')
// even if content doesn't include new lines, the base64 encoding performed later may add them
->attach('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod', 'lorem.txt');
$response = $this->createMock(ResponseInterface::class);
$response
->expects($this->once())
->method('getStatusCode')
->willReturn(202);
$response
->expects($this->once())
->method('getHeaders')
->willReturn(['x-message-id' => '1']);
$httpClient = $this->createMock(HttpClientInterface::class);
$httpClient
->expects($this->once())
->method('request')
->with('POST', 'https://api.sendgrid.com/v3/mail/send', [
'json' => [
'personalizations' => [
[
'to' => [['email' => 'bar@example.com']],
'subject' => null,
],
],
'from' => ['email' => 'foo@example.com'],
'content' => [],
'attachments' => [
[
'content' => 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2Q=',
'filename' => 'lorem.txt',
'type' => 'application/octet-stream',
'disposition' => 'attachment',
],
],
],
'auth_bearer' => 'foo',
])
->willReturn($response);
$mailer = new SendgridApiTransport('foo', $httpClient);
$mailer->send($email);
}
public function testCustomHeader()
{
$email = new Email();
$email->getHeaders()->addTextHeader('foo', 'bar');
$envelope = new Envelope(new Address('alice@system.com'), [new Address('bob@system.com')]);
$transport = new SendgridApiTransport('ACCESS_KEY');
$method = new \ReflectionMethod(SendgridApiTransport::class, 'getPayload');
$method->setAccessible(true);
$payload = $method->invoke($transport, $email, $envelope);
$this->assertArrayHasKey('headers', $payload);
$this->assertArrayHasKey('foo', $payload['headers']);
$this->assertEquals('bar', $payload['headers']['foo']);
}
}

View File

@ -63,11 +63,19 @@ class SendgridApiTransport extends AbstractApiTransport
private function getPayload(Email $email, Envelope $envelope): array
{
$addressStringifier = function (Address $address) {return ['email' => $address->toString()]; };
$addressStringifier = function (Address $address) {
$stringified = ['email' => $address->getAddress()];
if ($address->getName()) {
$stringified['name'] = $address->getName();
}
return $stringified;
};
$payload = [
'personalizations' => [],
'from' => ['email' => $envelope->getSender()->toString()],
'from' => $addressStringifier($envelope->getSender()),
'content' => $this->getContent($email),
];
@ -96,7 +104,7 @@ class SendgridApiTransport extends AbstractApiTransport
continue;
}
$payload['headers'][$name] = $header->toString();
$payload['headers'][$name] = $header->getBodyAsString();
}
return $payload;
@ -124,7 +132,7 @@ class SendgridApiTransport extends AbstractApiTransport
$disposition = $headers->getHeaderBody('Content-Disposition');
$att = [
'content' => $attachment->bodyToString(),
'content' => str_replace("\r\n", '', $attachment->bodyToString()),
'type' => $headers->get('Content-Type')->getBody(),
'filename' => $filename,
'disposition' => $disposition,

View File

@ -1,4 +1,4 @@
Copyright (c) 2019 Fabien Potencier
Copyright (c) 2019-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2018-2019 Fabien Potencier
Copyright (c) 2018-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2010-2019 Fabien Potencier
Copyright (c) 2010-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -7,7 +7,7 @@ object or array using a simple string notation.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/property_access/index.html)
* [Documentation](https://symfony.com/doc/current/components/property_access.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)

View File

@ -201,6 +201,8 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflectionProperty->getDeclaringClass()));
} catch (\InvalidArgumentException $e) {
return null;
} catch (\RuntimeException $e) {
return null;
}
}
@ -237,6 +239,8 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflectionMethod->getDeclaringClass())), $prefix];
} catch (\InvalidArgumentException $e) {
return null;
} catch (\RuntimeException $e) {
return null;
}
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Fabien Potencier
Copyright (c) 2015-2020 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

View File

@ -40,7 +40,6 @@ class CompiledUrlGenerator extends UrlGenerator
if (null !== $locale) {
do {
if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
unset($parameters['_locale']);
$name .= '.'.$locale;
break;
}
@ -53,6 +52,14 @@ class CompiledUrlGenerator extends UrlGenerator
list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = $this->compiledRoutes[$name];
if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) {
if (!\in_array('_locale', $variables, true)) {
unset($parameters['_locale']);
} elseif (!isset($parameters['_locale'])) {
$parameters['_locale'] = $defaults['_locale'];
}
}
return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes);
}
}

View File

@ -134,7 +134,6 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
if (null !== $locale) {
do {
if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) {
unset($parameters['_locale']);
break;
}
} while (false !== $locale = strstr($locale, '_', true));
@ -147,7 +146,18 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt
// the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile();
return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes());
$defaults = $route->getDefaults();
$variables = $compiledRoute->getVariables();
if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) {
if (!\in_array('_locale', $variables, true)) {
unset($parameters['_locale']);
} elseif (!isset($parameters['_locale'])) {
$parameters['_locale'] = $defaults['_locale'];
}
}
return $this->doGenerate($variables, $defaults, $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes());
}
/**

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -6,7 +6,7 @@ The Routing component maps an HTTP request to a set of configuration variables.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/routing/index.html)
* [Documentation](https://symfony.com/doc/current/components/routing.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)

View File

@ -131,9 +131,9 @@ class CompiledUrlGeneratorDumperTest extends TestCase
public function testDumpWithFallbackLocaleLocalizedRoutes()
{
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_canonical_route', 'test'));
$this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_canonical_route', 'test'));
$this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_canonical_route', 'test'));
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'test'));
$this->routeCollection->add('test.nl', (new Route('/testen/is/leuk'))->setDefault('_locale', 'nl')->setDefault('_canonical_route', 'test'));
$this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'test'));
$code = $this->generatorDumper->dump();
file_put_contents($this->testTmpFilepath, $code);
@ -231,4 +231,29 @@ class CompiledUrlGeneratorDumperTest extends TestCase
$this->assertEquals('https://localhost/app.php/testing', $absoluteUrl);
$this->assertEquals('/app.php/testing', $relativeUrl);
}
public function testDumpWithLocalizedRoutesPreserveTheGoodLocaleInTheUrl()
{
$this->routeCollection->add('foo.en', (new Route('/{_locale}/foo'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'foo'));
$this->routeCollection->add('foo.fr', (new Route('/{_locale}/foo'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'foo'));
$this->routeCollection->add('fun.en', (new Route('/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'fun'));
$this->routeCollection->add('fun.fr', (new Route('/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'fun'));
file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump());
$requestContext = new RequestContext();
$requestContext->setParameter('_locale', 'fr');
$compiledUrlGenerator = new CompiledUrlGenerator(require $this->testTmpFilepath, $requestContext, null, null);
$this->assertSame('/fr/foo', $compiledUrlGenerator->generate('foo'));
$this->assertSame('/en/foo', $compiledUrlGenerator->generate('foo.en'));
$this->assertSame('/en/foo', $compiledUrlGenerator->generate('foo', ['_locale' => 'en']));
$this->assertSame('/en/foo', $compiledUrlGenerator->generate('foo.fr', ['_locale' => 'en']));
$this->assertSame('/amusant', $compiledUrlGenerator->generate('fun'));
$this->assertSame('/fun', $compiledUrlGenerator->generate('fun.en'));
$this->assertSame('/fun', $compiledUrlGenerator->generate('fun', ['_locale' => 'en']));
$this->assertSame('/amusant', $compiledUrlGenerator->generate('fun.fr', ['_locale' => 'en']));
}
}

View File

@ -236,6 +236,29 @@ class UrlGeneratorTest extends TestCase
);
}
public function testDumpWithLocalizedRoutesPreserveTheGoodLocaleInTheUrl()
{
$routeCollection = new RouteCollection();
$routeCollection->add('foo.en', (new Route('/{_locale}/foo'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'foo'));
$routeCollection->add('foo.fr', (new Route('/{_locale}/foo'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'foo'));
$routeCollection->add('fun.en', (new Route('/fun'))->setDefault('_locale', 'en')->setDefault('_canonical_route', 'fun'));
$routeCollection->add('fun.fr', (new Route('/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'fun'));
$urlGenerator = $this->getGenerator($routeCollection);
$urlGenerator->getContext()->setParameter('_locale', 'fr');
$this->assertSame('/app.php/fr/foo', $urlGenerator->generate('foo'));
$this->assertSame('/app.php/en/foo', $urlGenerator->generate('foo.en'));
$this->assertSame('/app.php/en/foo', $urlGenerator->generate('foo', ['_locale' => 'en']));
$this->assertSame('/app.php/en/foo', $urlGenerator->generate('foo.fr', ['_locale' => 'en']));
$this->assertSame('/app.php/amusant', $urlGenerator->generate('fun'));
$this->assertSame('/app.php/fun', $urlGenerator->generate('fun.en'));
$this->assertSame('/app.php/fun', $urlGenerator->generate('fun', ['_locale' => 'en']));
$this->assertSame('/app.php/amusant', $urlGenerator->generate('fun.fr', ['_locale' => 'en']));
}
public function testGenerateWithoutRoutes()
{
$this->expectException('Symfony\Component\Routing\Exception\RouteNotFoundException');

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -9,7 +9,7 @@ the Java Spring framework.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/security/index.html)
* [Documentation](https://symfony.com/doc/current/components/security.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -7,7 +7,7 @@ The Security CSRF (cross-site request forgery) component provides a class
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/security/index.html)
* [Documentation](https://symfony.com/doc/current/components/security.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

View File

@ -19,7 +19,7 @@ interface PasswordAuthenticatedInterface
/**
* Returns the clear-text password contained in credentials if any.
*
* @param mixed The user credentials
* @param mixed $credentials The user credentials
*/
public function getPassword($credentials): ?string;
}

View File

@ -8,7 +8,7 @@ total control.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/security/index.html)
* [Documentation](https://symfony.com/doc/current/components/security.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)

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2020 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

Some files were not shown because too many files have changed in this diff Show More