Merge branch '5.1'
* 5.1: minor #37121 [Contracts] Add missing "extra.thanks" entries in composer.json (nicolas-grekas) [Process] Fix Permission Denied error when writing sf_proc_00 lock files on Windows fix handling null as empty data [Security\Http] Skip remember-me logout on empty token Missing return in loadValuesForChoices method No need to create an issue when creating a PR Use ">=" for the "php" requirement [HttpClient] Fix promise behavior in HttplugClient [Console] Fixes question input encoding on Windows
This commit is contained in:
commit
c046229c9e
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -4,7 +4,7 @@
|
|||||||
| Bug fix? | yes/no
|
| Bug fix? | yes/no
|
||||||
| New feature? | yes/no <!-- please update src/**/CHANGELOG.md files -->
|
| New feature? | yes/no <!-- please update src/**/CHANGELOG.md files -->
|
||||||
| Deprecations? | yes/no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
|
| Deprecations? | yes/no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
|
||||||
| Tickets | Fix #... <!-- prefix each issue number with "Fix #", if any -->
|
| Tickets | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
|
||||||
| License | MIT
|
| License | MIT
|
||||||
| Doc PR | symfony/symfony-docs#... <!-- required for new features -->
|
| Doc PR | symfony/symfony-docs#... <!-- required for new features -->
|
||||||
<!--
|
<!--
|
||||||
|
@ -110,6 +110,11 @@ class QuestionHelper extends Helper
|
|||||||
$inputStream = $this->inputStream ?: STDIN;
|
$inputStream = $this->inputStream ?: STDIN;
|
||||||
$autocomplete = $question->getAutocompleterCallback();
|
$autocomplete = $question->getAutocompleterCallback();
|
||||||
|
|
||||||
|
if (\function_exists('sapi_windows_cp_set')) {
|
||||||
|
// Codepage used by cmd.exe on Windows to allow special characters (éàüñ).
|
||||||
|
sapi_windows_cp_set(1252);
|
||||||
|
}
|
||||||
|
|
||||||
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
|
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
|
||||||
$ret = false;
|
$ret = false;
|
||||||
if ($question->isHidden()) {
|
if ($question->isHidden()) {
|
||||||
|
@ -46,6 +46,6 @@ final class ChoiceLoader extends AbstractStaticOption implements ChoiceLoaderInt
|
|||||||
*/
|
*/
|
||||||
public function loadValuesForChoices(array $choices, callable $value = null)
|
public function loadValuesForChoices(array $choices, callable $value = null)
|
||||||
{
|
{
|
||||||
$this->getOption()->loadValuesForChoices($choices, $value);
|
return $this->getOption()->loadValuesForChoices($choices, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,11 +126,11 @@ class NumberToLocalizedStringTransformer implements DataTransformerInterface
|
|||||||
*/
|
*/
|
||||||
public function reverseTransform($value)
|
public function reverseTransform($value)
|
||||||
{
|
{
|
||||||
if (!\is_string($value)) {
|
if (null !== $value && !\is_string($value)) {
|
||||||
throw new TransformationFailedException('Expected a string.');
|
throw new TransformationFailedException('Expected a string.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('' === $value) {
|
if (null === $value || '' === $value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
<?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\Component\Form\Tests\ChoiceList\Factory\Cache;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
|
||||||
|
use Symfony\Component\Form\ChoiceList\Factory\Cache\ChoiceLoader;
|
||||||
|
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||||
|
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
|
||||||
|
use Symfony\Component\Form\FormTypeInterface;
|
||||||
|
|
||||||
|
class ChoiceLoaderTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSameFormTypeUseCachedLoader()
|
||||||
|
{
|
||||||
|
$choices = ['f' => 'foo', 'b' => 'bar', 'z' => 'baz'];
|
||||||
|
$choiceList = new ArrayChoiceList($choices);
|
||||||
|
|
||||||
|
$type = $this->createMock(FormTypeInterface::class);
|
||||||
|
$decorated = new CallbackChoiceLoader(static function () use ($choices) {
|
||||||
|
return $choices;
|
||||||
|
});
|
||||||
|
$loader1 = new ChoiceLoader($type, $decorated);
|
||||||
|
$loader2 = new ChoiceLoader($type, $this->createMock(ChoiceLoaderInterface::class));
|
||||||
|
|
||||||
|
$this->assertEquals($choiceList, $loader1->loadChoiceList());
|
||||||
|
$this->assertEquals($choiceList, $loader2->loadChoiceList());
|
||||||
|
|
||||||
|
$this->assertSame($choices, $loader1->loadChoicesForValues($choices));
|
||||||
|
$this->assertSame($choices, $loader2->loadChoicesForValues($choices));
|
||||||
|
|
||||||
|
$this->assertSame($choices, $loader1->loadValuesForChoices($choices));
|
||||||
|
$this->assertSame($choices, $loader2->loadValuesForChoices($choices));
|
||||||
|
}
|
||||||
|
}
|
@ -124,6 +124,21 @@ class NumberTypeTest extends BaseTypeTest
|
|||||||
$this->assertSame($expectedData, $form->getData());
|
$this->assertSame($expectedData, $form->getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSubmitNullWithEmptyDataSetToNull()
|
||||||
|
{
|
||||||
|
$form = $this->factory->create(static::TESTED_TYPE, null, [
|
||||||
|
'empty_data' => null,
|
||||||
|
]);
|
||||||
|
$form->submit(null);
|
||||||
|
|
||||||
|
$this->assertTrue($form->isSubmitted());
|
||||||
|
$this->assertTrue($form->isSynchronized());
|
||||||
|
$this->assertTrue($form->isValid());
|
||||||
|
$this->assertSame('', $form->getViewData());
|
||||||
|
$this->assertNull($form->getNormData());
|
||||||
|
$this->assertNull($form->getData());
|
||||||
|
}
|
||||||
|
|
||||||
public function testSubmitNumericInput(): void
|
public function testSubmitNumericInput(): void
|
||||||
{
|
{
|
||||||
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'number']);
|
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'number']);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\HttpClient;
|
namespace Symfony\Component\HttpClient;
|
||||||
|
|
||||||
use GuzzleHttp\Promise\Promise as GuzzlePromise;
|
use GuzzleHttp\Promise\Promise as GuzzlePromise;
|
||||||
|
use GuzzleHttp\Promise\RejectedPromise;
|
||||||
use Http\Client\Exception\NetworkException;
|
use Http\Client\Exception\NetworkException;
|
||||||
use Http\Client\Exception\RequestException;
|
use Http\Client\Exception\RequestException;
|
||||||
use Http\Client\HttpAsyncClient;
|
use Http\Client\HttpAsyncClient;
|
||||||
@ -22,7 +23,6 @@ use Http\Message\RequestFactory;
|
|||||||
use Http\Message\StreamFactory;
|
use Http\Message\StreamFactory;
|
||||||
use Http\Message\UriFactory;
|
use Http\Message\UriFactory;
|
||||||
use Http\Promise\Promise;
|
use Http\Promise\Promise;
|
||||||
use Http\Promise\RejectedPromise;
|
|
||||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
use Nyholm\Psr7\Request;
|
use Nyholm\Psr7\Request;
|
||||||
use Nyholm\Psr7\Uri;
|
use Nyholm\Psr7\Uri;
|
||||||
@ -114,7 +114,7 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
|
|||||||
try {
|
try {
|
||||||
$response = $this->sendPsr7Request($request, true);
|
$response = $this->sendPsr7Request($request, true);
|
||||||
} catch (NetworkException $e) {
|
} catch (NetworkException $e) {
|
||||||
return new RejectedPromise($e);
|
return new HttplugPromise(new RejectedPromise($e));
|
||||||
}
|
}
|
||||||
|
|
||||||
$waitLoop = $this->waitLoop;
|
$waitLoop = $this->waitLoop;
|
||||||
|
@ -54,6 +54,12 @@ final class HttplugPromise implements HttplugPromiseInterface
|
|||||||
*/
|
*/
|
||||||
public function wait($unwrap = true)
|
public function wait($unwrap = true)
|
||||||
{
|
{
|
||||||
return $this->promise->wait($unwrap);
|
$result = $this->promise->wait($unwrap);
|
||||||
|
|
||||||
|
while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) {
|
||||||
|
$result = $result->wait($unwrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,18 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\HttpClient\Tests;
|
namespace Symfony\Component\HttpClient\Tests;
|
||||||
|
|
||||||
|
use GuzzleHttp\Promise\FulfilledPromise as GuzzleFulfilledPromise;
|
||||||
use Http\Client\Exception\NetworkException;
|
use Http\Client\Exception\NetworkException;
|
||||||
use Http\Client\Exception\RequestException;
|
use Http\Client\Exception\RequestException;
|
||||||
|
use Http\Promise\FulfilledPromise;
|
||||||
use Http\Promise\Promise;
|
use Http\Promise\Promise;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
use Symfony\Component\HttpClient\HttplugClient;
|
use Symfony\Component\HttpClient\HttplugClient;
|
||||||
|
use Symfony\Component\HttpClient\MockHttpClient;
|
||||||
use Symfony\Component\HttpClient\NativeHttpClient;
|
use Symfony\Component\HttpClient\NativeHttpClient;
|
||||||
|
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||||
use Symfony\Contracts\HttpClient\Test\TestHttpServer;
|
use Symfony\Contracts\HttpClient\Test\TestHttpServer;
|
||||||
|
|
||||||
class HttplugClientTest extends TestCase
|
class HttplugClientTest extends TestCase
|
||||||
@ -152,4 +157,114 @@ class HttplugClientTest extends TestCase
|
|||||||
$this->expectException(RequestException::class);
|
$this->expectException(RequestException::class);
|
||||||
$client->sendRequest($client->createRequest('BAD.METHOD', 'http://localhost:8057'));
|
$client->sendRequest($client->createRequest('BAD.METHOD', 'http://localhost:8057'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRetry404()
|
||||||
|
{
|
||||||
|
$client = new HttplugClient(new NativeHttpClient());
|
||||||
|
|
||||||
|
$successCallableCalled = false;
|
||||||
|
$failureCallableCalled = false;
|
||||||
|
|
||||||
|
$promise = $client
|
||||||
|
->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/404'))
|
||||||
|
->then(
|
||||||
|
function (ResponseInterface $response) use (&$successCallableCalled, $client) {
|
||||||
|
$this->assertSame(404, $response->getStatusCode());
|
||||||
|
$successCallableCalled = true;
|
||||||
|
|
||||||
|
return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057'));
|
||||||
|
},
|
||||||
|
function (\Exception $exception) use (&$failureCallableCalled) {
|
||||||
|
$failureCallableCalled = true;
|
||||||
|
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
$response = $promise->wait(true);
|
||||||
|
|
||||||
|
$this->assertTrue($successCallableCalled);
|
||||||
|
$this->assertFalse($failureCallableCalled);
|
||||||
|
$this->assertSame(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRetryNetworkError()
|
||||||
|
{
|
||||||
|
$client = new HttplugClient(new NativeHttpClient());
|
||||||
|
|
||||||
|
$successCallableCalled = false;
|
||||||
|
$failureCallableCalled = false;
|
||||||
|
|
||||||
|
$promise = $client
|
||||||
|
->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/chunked-broken'))
|
||||||
|
->then(function (ResponseInterface $response) use (&$successCallableCalled) {
|
||||||
|
$successCallableCalled = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}, function (\Exception $exception) use (&$failureCallableCalled, $client) {
|
||||||
|
$this->assertSame(NetworkException::class, \get_class($exception));
|
||||||
|
$this->assertSame(TransportException::class, \get_class($exception->getPrevious()));
|
||||||
|
$failureCallableCalled = true;
|
||||||
|
|
||||||
|
return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057'));
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
$response = $promise->wait(true);
|
||||||
|
|
||||||
|
$this->assertFalse($successCallableCalled);
|
||||||
|
$this->assertTrue($failureCallableCalled);
|
||||||
|
$this->assertSame(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRetryEarlierError()
|
||||||
|
{
|
||||||
|
$isFirstRequest = true;
|
||||||
|
$errorMessage = 'Error occurred before making the actual request.';
|
||||||
|
|
||||||
|
$client = new HttplugClient(new MockHttpClient(function () use (&$isFirstRequest, $errorMessage) {
|
||||||
|
if ($isFirstRequest) {
|
||||||
|
$isFirstRequest = false;
|
||||||
|
throw new TransportException($errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MockResponse('OK', ['http_code' => 200]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
$request = $client->createRequest('GET', 'http://test');
|
||||||
|
|
||||||
|
$successCallableCalled = false;
|
||||||
|
$failureCallableCalled = false;
|
||||||
|
|
||||||
|
$promise = $client
|
||||||
|
->sendAsyncRequest($request)
|
||||||
|
->then(
|
||||||
|
function (ResponseInterface $response) use (&$successCallableCalled) {
|
||||||
|
$successCallableCalled = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
},
|
||||||
|
function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $client, $request) {
|
||||||
|
$this->assertSame(NetworkException::class, \get_class($exception));
|
||||||
|
$this->assertSame($errorMessage, $exception->getMessage());
|
||||||
|
$failureCallableCalled = true;
|
||||||
|
|
||||||
|
// Ensure arbitrary levels of promises work.
|
||||||
|
return (new FulfilledPromise(null))->then(function () use ($client, $request) {
|
||||||
|
return (new GuzzleFulfilledPromise(null))->then(function () use ($client, $request) {
|
||||||
|
return $client->sendAsyncRequest($request);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
$response = $promise->wait(true);
|
||||||
|
|
||||||
|
$this->assertFalse($successCallableCalled);
|
||||||
|
$this->assertTrue($failureCallableCalled);
|
||||||
|
$this->assertSame(200, $response->getStatusCode());
|
||||||
|
$this->assertSame('OK', (string) $response->getBody());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,9 @@ class WindowsPipes extends AbstractPipes
|
|||||||
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
|
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
|
||||||
|
|
||||||
if (!$h = fopen($file.'.lock', 'w')) {
|
if (!$h = fopen($file.'.lock', 'w')) {
|
||||||
|
if (file_exists($file.'.lock')) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
|
throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,10 @@ class RememberMeLogoutListener implements EventSubscriberInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$event->getToken()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (null === $event->getResponse()) {
|
if (null === $event->getResponse()) {
|
||||||
throw new LogicException(sprintf('No response was set for this logout action. Make sure the DefaultLogoutListener or another listener has set the response before "%s" is called.', __CLASS__));
|
throw new LogicException(sprintf('No response was set for this logout action. Make sure the DefaultLogoutListener or another listener has set the response before "%s" is called.', __CLASS__));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?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\Component\Security\Http\Tests\EventListener;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Security\Http\Event\LogoutEvent;
|
||||||
|
use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener;
|
||||||
|
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
|
||||||
|
|
||||||
|
class RememberMeLogoutListenerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testOnLogoutDoesNothingIfNoToken()
|
||||||
|
{
|
||||||
|
$rememberMeServices = $this->createMock(AbstractRememberMeServices::class);
|
||||||
|
$rememberMeServices->expects($this->never())->method('logout');
|
||||||
|
|
||||||
|
$rememberMeLogoutListener = new RememberMeLogoutListener($rememberMeServices);
|
||||||
|
$rememberMeLogoutListener->onLogout(new LogoutEvent(new Request(), null));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user