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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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); $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) public function endTest(\PHPUnit_Framework_Test $test, $time)
{ {
$this->trait->endTest($test, $time); $this->trait->endTest($test, $time);

View File

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

View File

@ -55,11 +55,6 @@ class SymfonyTestsListenerForV7 implements TestListener
$this->trait->startTest($test); $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 public function endTest(Test $test, float $time): void
{ {
$this->trait->endTest($test, $time); $this->trait->endTest($test, $time);

View File

@ -40,7 +40,6 @@ class SymfonyTestsListenerTrait
private $expectedDeprecations = array(); private $expectedDeprecations = array();
private $gatheredDeprecations = array(); private $gatheredDeprecations = array();
private $previousErrorHandler; private $previousErrorHandler;
private $testsWithWarnings;
private $reportUselessTests; private $reportUselessTests;
private $error; private $error;
private $runsInSeparateProcess = false; private $runsInSeparateProcess = false;
@ -112,7 +111,6 @@ class SymfonyTestsListenerTrait
public function startTestSuite($suite) public function startTestSuite($suite)
{ {
$suiteName = $suite->getName(); $suiteName = $suite->getName();
$this->testsWithWarnings = array();
foreach ($suite->tests() as $test) { foreach ($suite->tests() as $test) {
if (!($test instanceof \PHPUnit\Framework\TestCase || $test instanceof TestCase)) { 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) public function endTest($test, $time)
{ {
if (class_exists(DebugClassLoader::class, false)) { 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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_label_class') }}"></div>{#--#}
<div class="{{ block('form_group_class') }}"> <div class="{{ block('form_group_class') }}">
{{- form_widget(form) -}} {{- form_widget(form) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}} {{- form_errors(form) -}}
</div>{#--#} </div>{#--#}
</div> </div>

View File

@ -148,6 +148,7 @@
{% block checkbox_row -%} {% block checkbox_row -%}
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group' ~ (not valid ? ' has-error'))|trim})} %}{{ block('attributes') }}{% endwith %}> <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_widget(form) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}} {{- form_errors(form) -}}
</div> </div>
{%- endblock checkbox_row %} {%- endblock checkbox_row %}
@ -155,6 +156,7 @@
{% block radio_row -%} {% block radio_row -%}
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group' ~ (not valid ? ' has-error'))|trim})} %}{{ block('attributes') }}{% endwith %}> <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_widget(form) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}} {{- form_errors(form) -}}
</div> </div>
{%- endblock radio_row %} {%- 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{% 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 %}"> <div class="large-12 columns{% if not valid %} error{% endif %}">
{{ form_widget(form) }} {{ form_widget(form) }}
{{- form_help(form) -}}
{{ form_errors(form) }} {{ form_errors(form) }}
</div> </div>
</div> </div>
@ -320,6 +321,7 @@
<div{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' row')|trim})} %}{{ block('attributes') }}{% endwith %}> <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 %}"> <div class="large-12 columns{% if not valid %} error{% endif %}">
{{ form_widget(form) }} {{ form_widget(form) }}
{{- form_help(form) -}}
{{ form_errors(form) }} {{ form_errors(form) }}
</div> </div>
</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); $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() public function testSingleChoice()
{ {
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ $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() public function testRange()
{ {
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\RangeType', 42, ['attr' => ['min' => 5]]); $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]"] ./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() public function testSingleChoiceExpanded()
{ {
$form = $this->factory->createNamed('name', ChoiceType::class, '&a', [ $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() public function testButtonAttributeNameRepeatedIfTrue()
{ {
$form = $this->factory->createNamed('button', ButtonType::class, null, [ $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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -20,6 +20,7 @@ CHANGELOG
4.4.0 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 `migrate_from` option to encoders configuration.
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.) * 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. * 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 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` * Added new encoder types: `auto` (recommended), `native` and `sodium`
* The normalization of the cookie names configured in the `logout.delete_cookies` * 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 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

@ -48,12 +48,12 @@ class FormatterHelper extends Helper
foreach ($messages as $message) { foreach ($messages as $message) {
$message = OutputFormatter::escape($message); $message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $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)] : []; $messages = $large ? [str_repeat(' ', $len)] : [];
for ($i = 0; isset($lines[$i]); ++$i) { 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) { if ($large) {
$messages[] = str_repeat(' ', $len); $messages[] = str_repeat(' ', $len);
@ -73,17 +73,13 @@ class FormatterHelper extends Helper
*/ */
public function truncate(string $message, int $length, string $suffix = '...') 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; return $message;
} }
if (false === $encoding = mb_detect_encoding($message, null, true)) { return self::substr($message, 0, $length).$suffix;
return substr($message, 0, $length).$suffix;
}
return mb_substr($message, 0, $length, $encoding).$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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -7,7 +7,7 @@ interfaces.
Resources 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -8,7 +8,7 @@ them.
Resources 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

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

View File

@ -288,7 +288,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$pushedResponse = $pushedResponse->response; $pushedResponse = $pushedResponse->response;
$pushedResponse->__construct($this->multi, $url, $options, $this->logger); $pushedResponse->__construct($this->multi, $url, $options, $this->logger);
} else { } 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; $pushedResponse = null;
} }
} }
@ -412,7 +412,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
return false; 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]) { if ($options[$k] !== $pushedResponse->parentOptions[$k]) {
return false; 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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->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); [$host, $port, $url['authority']] = self::dnsResolve($url, $this->multi, $info, $onProgress);
@ -187,7 +187,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$context = [ $context = [
'http' => [ 'http' => [
'protocol_version' => $options['http_version'] ?: '1.1', 'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'),
'method' => $method, 'method' => $method,
'content' => $options['body'], 'content' => $options['body'],
'ignore_errors' => true, 'ignore_errors' => true,
@ -357,7 +357,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
}); });
if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) { 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:'); return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
}); });
} }

View File

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

View File

@ -204,7 +204,11 @@ trait ResponseTrait
$this->getHeaders($throw); $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 */ /** @var resource|null */
private $handle; private $handle;
private $blocking = true;
private $timeout;
private $eof = false; private $eof = false;
private $offset = 0; private $offset = 0;
/** /**
* Creates a PHP stream resource from a ResponseInterface. * 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 * @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')) { 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__)); 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 = [ $context = [
'client' => $client ?? $response, 'client' => $client ?? $response,
'response' => $response, 'response' => $response,
'content' => $contentBuffer,
'handle' => $selectHandle,
]; ];
return fopen('symfony://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])) ?: null; return fopen('symfony://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])) ?: null;
@ -78,6 +74,17 @@ class StreamWrapper
return $this->response; 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 public function stream_open(string $path, string $mode, int $options): bool
{ {
if ('r' !== $mode) { if ('r' !== $mode) {
@ -91,8 +98,6 @@ class StreamWrapper
$context = stream_context_get_options($this->context)['symfony'] ?? null; $context = stream_context_get_options($this->context)['symfony'] ?? null;
$this->client = $context['client'] ?? null; $this->client = $context['client'] ?? null;
$this->response = $context['response'] ?? null; $this->response = $context['response'] ?? null;
$this->content = $context['content'] ?? null;
$this->handle = $context['handle'] ?? null;
$this->context = null; $this->context = null;
if (null !== $this->client && null !== $this->response) { if (null !== $this->client && null !== $this->response) {
@ -147,7 +152,7 @@ class StreamWrapper
return $data; return $data;
} }
foreach ($this->client->stream([$this->response]) as $chunk) { foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) {
try { try {
$this->eof = true; $this->eof = true;
$this->eof = !$chunk->isTimeout(); $this->eof = !$chunk->isTimeout();
@ -178,6 +183,19 @@ class StreamWrapper
return ''; 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 public function stream_tell(): int
{ {
return $this->offset; return $this->offset;
@ -238,6 +256,8 @@ class StreamWrapper
public function stream_cast(int $castAs) public function stream_cast(int $castAs)
{ {
if (STREAM_CAST_FOR_SELECT === $castAs) { if (STREAM_CAST_FOR_SELECT === $castAs) {
$this->response->getHeaders(false);
return $this->handle ?? false; return $this->handle ?? false;
} }

View File

@ -75,4 +75,20 @@ abstract class HttpClientTestCase extends BaseHttpClientTestCase
$response = $client->request('GET', 'http://localhost:8057/404'); $response = $client->request('GET', 'http://localhost:8057/404');
$stream = $response->toStream(); $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; return $client;
case 'testNonBlockingStream':
$responses[] = new MockResponse((function () { yield '<1>'; yield ''; yield '<2>'; })(), ['response_headers' => $headers]);
break;
case 'testMaxDuration': case 'testMaxDuration':
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock(); $mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
$mock->expects($this->any()) $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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -7,7 +7,7 @@ specification.
Resources 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -9,7 +9,7 @@ an advanced CMS system (Drupal).
Resources 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillApiTransport; 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 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(), 'type' => $headers->get('Content-Type')->getBody(),
]; ];
if ($name = $headers->getHeaderParameter('Content-Disposition', 'name')) {
$att['name'] = $name;
}
if ('inline' === $disposition) { if ('inline' === $disposition) {
$payload['images'][] = $att; $payload['message']['images'][] = $att;
} else { } else {
$payload['attachments'][] = $att; $payload['message']['attachments'][] = $att;
} }
} }
@ -107,7 +111,7 @@ class MandrillApiTransport extends AbstractApiTransport
continue; continue;
} }
$payload['message']['headers'][] = $name.': '.$header->toString(); $payload['message']['headers'][] = $name.': '.$header->getBodyAsString();
} }
return $payload; 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunApiTransport; 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 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; continue;
} }
$payload['h:'.$name] = $header->toString(); $payload['h:'.$name] = $header->getBodyAsString();
} }
return $payload; 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport; 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 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'][] = [ $payload['Headers'][] = [
'Name' => $name, '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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 PHPUnit\Framework\TestCase;
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridApiTransport; 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\Component\Mime\Email;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Contracts\HttpClient\ResponseInterface;
@ -48,8 +50,8 @@ class SendgridApiTransportTest extends TestCase
public function testSend() public function testSend()
{ {
$email = new Email(); $email = new Email();
$email->from('foo@example.com') $email->from(new Address('foo@example.com', 'Ms. Foo Bar'))
->to('bar@example.com') ->to(new Address('bar@example.com', 'Mr. Recipient'))
->bcc('baz@example.com') ->bcc('baz@example.com')
->text('content'); ->text('content');
@ -73,12 +75,18 @@ class SendgridApiTransportTest extends TestCase
'json' => [ 'json' => [
'personalizations' => [ 'personalizations' => [
[ [
'to' => [['email' => 'bar@example.com']], 'to' => [[
'email' => 'bar@example.com',
'name' => 'Mr. Recipient',
]],
'subject' => null, 'subject' => null,
'bcc' => [['email' => 'baz@example.com']], 'bcc' => [['email' => 'baz@example.com']],
], ],
], ],
'from' => ['email' => 'foo@example.com'], 'from' => [
'email' => 'foo@example.com',
'name' => 'Ms. Foo Bar',
],
'content' => [ 'content' => [
['type' => 'text/plain', 'value' => 'content'], ['type' => 'text/plain', 'value' => 'content'],
], ],
@ -90,4 +98,72 @@ class SendgridApiTransportTest extends TestCase
$mailer = new SendgridApiTransport('foo', $httpClient); $mailer = new SendgridApiTransport('foo', $httpClient);
$mailer->send($email); $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 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 = [ $payload = [
'personalizations' => [], 'personalizations' => [],
'from' => ['email' => $envelope->getSender()->toString()], 'from' => $addressStringifier($envelope->getSender()),
'content' => $this->getContent($email), 'content' => $this->getContent($email),
]; ];
@ -96,7 +104,7 @@ class SendgridApiTransport extends AbstractApiTransport
continue; continue;
} }
$payload['headers'][$name] = $header->toString(); $payload['headers'][$name] = $header->getBodyAsString();
} }
return $payload; return $payload;
@ -124,7 +132,7 @@ class SendgridApiTransport extends AbstractApiTransport
$disposition = $headers->getHeaderBody('Content-Disposition'); $disposition = $headers->getHeaderBody('Content-Disposition');
$att = [ $att = [
'content' => $attachment->bodyToString(), 'content' => str_replace("\r\n", '', $attachment->bodyToString()),
'type' => $headers->get('Content-Type')->getBody(), 'type' => $headers->get('Content-Type')->getBody(),
'filename' => $filename, 'filename' => $filename,
'disposition' => $disposition, '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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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())); return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflectionProperty->getDeclaringClass()));
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
return null; 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]; return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflectionMethod->getDeclaringClass())), $prefix];
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
return null; 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -40,7 +40,6 @@ class CompiledUrlGenerator extends UrlGenerator
if (null !== $locale) { if (null !== $locale) {
do { do {
if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) { if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) {
unset($parameters['_locale']);
$name .= '.'.$locale; $name .= '.'.$locale;
break; break;
} }
@ -53,6 +52,14 @@ class CompiledUrlGenerator extends UrlGenerator
list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = $this->compiledRoutes[$name]; 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); 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) { if (null !== $locale) {
do { do {
if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) { if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) {
unset($parameters['_locale']);
break; break;
} }
} while (false !== $locale = strstr($locale, '_', true)); } 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 // the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile(); $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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [send Pull Requests](https://github.com/symfony/symfony/pulls)

View File

@ -131,9 +131,9 @@ class CompiledUrlGeneratorDumperTest extends TestCase
public function testDumpWithFallbackLocaleLocalizedRoutes() public function testDumpWithFallbackLocaleLocalizedRoutes()
{ {
$this->routeCollection->add('test.en', (new Route('/testing/is/fun'))->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('_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('_canonical_route', 'test')); $this->routeCollection->add('test.fr', (new Route('/tester/est/amusant'))->setDefault('_locale', 'fr')->setDefault('_canonical_route', 'test'));
$code = $this->generatorDumper->dump(); $code = $this->generatorDumper->dump();
file_put_contents($this->testTmpFilepath, $code); file_put_contents($this->testTmpFilepath, $code);
@ -231,4 +231,29 @@ class CompiledUrlGeneratorDumperTest extends TestCase
$this->assertEquals('https://localhost/app.php/testing', $absoluteUrl); $this->assertEquals('https://localhost/app.php/testing', $absoluteUrl);
$this->assertEquals('/app.php/testing', $relativeUrl); $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() public function testGenerateWithoutRoutes()
{ {
$this->expectException('Symfony\Component\Routing\Exception\RouteNotFoundException'); $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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -9,7 +9,7 @@ the Java Spring framework.
Resources 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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. * 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; public function getPassword($credentials): ?string;
} }

View File

@ -8,7 +8,7 @@ total control.
Resources 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) * [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls) [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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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