From 548f4fd0ea2396653e28bef6550f5be8890474d0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 27 Jun 2019 20:37:35 +0200 Subject: [PATCH 01/33] [HttpClient] Add support for NTLM authentication --- .../DependencyInjection/Configuration.php | 3 +++ src/Symfony/Component/HttpClient/CHANGELOG.md | 1 + .../Component/HttpClient/CurlHttpClient.php | 24 ++++++++++++++++++- .../Component/HttpClient/HttpClientTrait.php | 4 ++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde..1b16809d62 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1414,6 +1414,9 @@ class Configuration implements ConfigurationInterface ->scalarNode('auth_bearer') ->info('A token enabling HTTP Bearer authorization.') ->end() + ->scalarNode('auth_ntlm') + ->info('A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).') + ->end() ->arrayNode('query') ->info('Associative array of query string values merged with the base URI.') ->useAttributeAsKey('key') diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index b2f900bf39..5348259f63 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * made `Psr18Client` implement relevant PSR-17 factories * added `HttplugClient` + * added support for NTLM authentication 4.3.0 ----- diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 199a3b4ea1..bdc12f61e9 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -39,7 +39,10 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface use HttpClientTrait; use LoggerAwareTrait; - private $defaultOptions = self::OPTIONS_DEFAULTS; + private $defaultOptions = self::OPTIONS_DEFAULTS + [ + 'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the + // password as the second one; or string like username:password - enabling NTLM auth + ]; /** * An internal object to share state between the client and its responses. @@ -152,6 +155,25 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface CURLOPT_CERTINFO => $options['capture_peer_cert_chain'], ]; + if (isset($options['auth_ntlm'])) { + $curlopts[CURLOPT_HTTPAUTH] = CURLAUTH_NTLM; + + if (\is_array($options['auth_ntlm'])) { + $count = \count($options['auth_ntlm']); + if ($count <= 0 || $count > 2) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %s given.', $count)); + } + + $options['auth_ntlm'] = implode(':', $options['auth_ntlm']); + } + + if (!\is_string($options['auth_ntlm'])) { + throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be string or an array, %s given.', \gettype($options['auth_ntlm']))); + } + + $curlopts[CURLOPT_USERPWD] = $options['auth_ntlm']; + } + if (!ZEND_THREAD_SAFE) { $curlopts[CURLOPT_DNS_USE_GLOBAL_CACHE] = false; } diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 4d263f46db..73921c2a75 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -181,6 +181,10 @@ trait HttpClientTrait } } + if ('auth_ntlm' === $name) { + throw new InvalidArgumentException(sprintf('Option "%s" is not supported by %s, try using CurlHttpClient instead.', __CLASS__)); + } + throw new InvalidArgumentException(sprintf('Unsupported option "%s" passed to %s, did you mean "%s"?', $name, __CLASS__, implode('", "', $alternatives ?: array_keys($defaultOptions)))); } From 063e880861eb8ec28d14efdce0042f7ce8c4bba9 Mon Sep 17 00:00:00 2001 From: Bastien Jaillot Date: Wed, 26 Jun 2019 13:59:17 +0200 Subject: [PATCH 02/33] [PropertyInfo] add static cache to ContextFactory ContextFactory::createFromReflector is heavy, and it's called redundanlty by Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor for each property and methods. Avoid that by parsing it only once and then use static cache --- .../Extractor/PhpDocExtractor.php | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php index 4837d2200c..d4060f4fa4 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php @@ -14,6 +14,7 @@ namespace Symfony\Component\PropertyInfo\Extractor; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactoryInterface; +use phpDocumentor\Reflection\Types\Context; use phpDocumentor\Reflection\Types\ContextFactory; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; @@ -38,6 +39,11 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property */ private $docBlocks = []; + /** + * @var Context[] + */ + private $contexts = []; + private $docBlockFactory; private $contextFactory; private $phpDocTypeHelper; @@ -191,7 +197,7 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property } try { - return $this->docBlockFactory->create($reflectionProperty, $this->contextFactory->createFromReflector($reflectionProperty->getDeclaringClass())); + return $this->docBlockFactory->create($reflectionProperty, $this->createFromReflector($reflectionProperty->getDeclaringClass())); } catch (\InvalidArgumentException $e) { return null; } @@ -227,9 +233,25 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property } try { - return [$this->docBlockFactory->create($reflectionMethod, $this->contextFactory->createFromReflector($reflectionMethod)), $prefix]; + return [$this->docBlockFactory->create($reflectionMethod, $this->createFromReflector($reflectionMethod->getDeclaringClass())), $prefix]; } catch (\InvalidArgumentException $e) { return null; } } + + /** + * Prevents a lot of redundant calls to ContextFactory::createForNamespace(). + */ + private function createFromReflector(\ReflectionClass $reflector): Context + { + $cacheKey = $reflector->getNamespaceName().':'.$reflector->getFileName(); + + if (isset($this->contexts[$cacheKey])) { + return $this->contexts[$cacheKey]; + } + + $this->contexts[$cacheKey] = $this->contextFactory->createFromReflector($reflector); + + return $this->contexts[$cacheKey]; + } } From e21772906691f6513f93ab47ad82efb9ee456a6b Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 28 Jun 2019 18:16:38 +0200 Subject: [PATCH 03/33] deprecate non-string constraint violation codes --- UPGRADE-4.4.md | 3 ++ UPGRADE-5.0.md | 2 ++ src/Symfony/Component/Validator/CHANGELOG.md | 3 ++ .../Validator/ConstraintViolation.php | 4 +++ .../Validator/Constraints/FileValidator.php | 16 ++++----- .../Tests/ConstraintViolationTest.php | 22 +++++++++++- .../Tests/Constraints/FileValidatorTest.php | 20 +++++------ .../ConstraintViolationBuilderTest.php | 36 +++++++++++++++++++ .../Violation/ConstraintViolationBuilder.php | 4 +++ 9 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 96c170675c..c8fdb04bcc 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -90,5 +90,8 @@ TwigBridge Validator --------- + * Deprecated using anything else than a `string` as the code of a `ConstraintViolation`, a `string` type-hint will + be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()` + method in 5.0. * Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 442572c468..bfa569bb0b 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -454,6 +454,8 @@ TwigBridge Validator -------- + * Removed support for non-string codes of a `ConstraintViolation`. A `string` type-hint was added to the constructor of + the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()` method. * An `ExpressionLanguage` instance or null must be passed as the first argument of `ExpressionValidator::__construct()` * The `checkMX` and `checkHost` options of the `Email` constraint were removed * The `Email::__construct()` 'strict' property has been removed. Use 'mode'=>"strict" instead. diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 8a85ee35ef..f12cae5dfb 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -4,6 +4,9 @@ CHANGELOG 4.4.0 ----- + * using anything else than a `string` as the code of a `ConstraintViolation` is deprecated, a `string` type-hint will + be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()` + method in 5.0 * deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead. * added the `compared_value_path` parameter in violations when using any comparison constraint with the `propertyPath` option. diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php index 2ec15e4309..e615cd3921 100644 --- a/src/Symfony/Component/Validator/ConstraintViolation.php +++ b/src/Symfony/Component/Validator/ConstraintViolation.php @@ -56,6 +56,10 @@ class ConstraintViolation implements ConstraintViolationInterface $message = ''; } + if (null !== $code && !\is_string($code)) { + @trigger_error(sprintf('Not using a string as the error code in %s() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.', __METHOD__), E_USER_DEPRECATED); + } + $this->message = $message; $this->messageTemplate = $messageTemplate; $this->parameters = $parameters; diff --git a/src/Symfony/Component/Validator/Constraints/FileValidator.php b/src/Symfony/Component/Validator/Constraints/FileValidator.php index 7266669213..ac23f6fb8e 100644 --- a/src/Symfony/Component/Validator/Constraints/FileValidator.php +++ b/src/Symfony/Component/Validator/Constraints/FileValidator.php @@ -65,49 +65,49 @@ class FileValidator extends ConstraintValidator $this->context->buildViolation($constraint->uploadIniSizeErrorMessage) ->setParameter('{{ limit }}', $limitAsString) ->setParameter('{{ suffix }}', $suffix) - ->setCode(UPLOAD_ERR_INI_SIZE) + ->setCode((string) UPLOAD_ERR_INI_SIZE) ->addViolation(); return; case UPLOAD_ERR_FORM_SIZE: $this->context->buildViolation($constraint->uploadFormSizeErrorMessage) - ->setCode(UPLOAD_ERR_FORM_SIZE) + ->setCode((string) UPLOAD_ERR_FORM_SIZE) ->addViolation(); return; case UPLOAD_ERR_PARTIAL: $this->context->buildViolation($constraint->uploadPartialErrorMessage) - ->setCode(UPLOAD_ERR_PARTIAL) + ->setCode((string) UPLOAD_ERR_PARTIAL) ->addViolation(); return; case UPLOAD_ERR_NO_FILE: $this->context->buildViolation($constraint->uploadNoFileErrorMessage) - ->setCode(UPLOAD_ERR_NO_FILE) + ->setCode((string) UPLOAD_ERR_NO_FILE) ->addViolation(); return; case UPLOAD_ERR_NO_TMP_DIR: $this->context->buildViolation($constraint->uploadNoTmpDirErrorMessage) - ->setCode(UPLOAD_ERR_NO_TMP_DIR) + ->setCode((string) UPLOAD_ERR_NO_TMP_DIR) ->addViolation(); return; case UPLOAD_ERR_CANT_WRITE: $this->context->buildViolation($constraint->uploadCantWriteErrorMessage) - ->setCode(UPLOAD_ERR_CANT_WRITE) + ->setCode((string) UPLOAD_ERR_CANT_WRITE) ->addViolation(); return; case UPLOAD_ERR_EXTENSION: $this->context->buildViolation($constraint->uploadExtensionErrorMessage) - ->setCode(UPLOAD_ERR_EXTENSION) + ->setCode((string) UPLOAD_ERR_EXTENSION) ->addViolation(); return; default: $this->context->buildViolation($constraint->uploadErrorMessage) - ->setCode($value->getError()) + ->setCode((string) $value->getError()) ->addViolation(); return; diff --git a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php index b43e51f273..d38431b640 100644 --- a/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php +++ b/src/Symfony/Component/Validator/Tests/ConstraintViolationTest.php @@ -64,7 +64,7 @@ EOF; 'some_value', null, null, - 0 + '0' ); $expected = <<<'EOF' @@ -108,4 +108,24 @@ EOF; $this->assertSame($expected, (string) $violation); } + + /** + * @group legacy + * @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\ConstraintViolation::__construct() is deprecated since Symfony 4.4. A type-hint will be added in 5.0. + */ + public function testNonStringCode() + { + $violation = new ConstraintViolation( + '42 cannot be used here', + 'this is the message template', + [], + ['some_value' => 42], + 'some_value', + null, + null, + 42 + ); + + self::assertSame(42, $violation->getCode()); + } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php index c5105c20cf..7e75f8151f 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php @@ -435,23 +435,23 @@ abstract class FileValidatorTest extends ConstraintValidatorTestCase public function uploadedFileErrorProvider() { $tests = [ - [UPLOAD_ERR_FORM_SIZE, 'uploadFormSizeErrorMessage'], - [UPLOAD_ERR_PARTIAL, 'uploadPartialErrorMessage'], - [UPLOAD_ERR_NO_FILE, 'uploadNoFileErrorMessage'], - [UPLOAD_ERR_NO_TMP_DIR, 'uploadNoTmpDirErrorMessage'], - [UPLOAD_ERR_CANT_WRITE, 'uploadCantWriteErrorMessage'], - [UPLOAD_ERR_EXTENSION, 'uploadExtensionErrorMessage'], + [(string) UPLOAD_ERR_FORM_SIZE, 'uploadFormSizeErrorMessage'], + [(string) UPLOAD_ERR_PARTIAL, 'uploadPartialErrorMessage'], + [(string) UPLOAD_ERR_NO_FILE, 'uploadNoFileErrorMessage'], + [(string) UPLOAD_ERR_NO_TMP_DIR, 'uploadNoTmpDirErrorMessage'], + [(string) UPLOAD_ERR_CANT_WRITE, 'uploadCantWriteErrorMessage'], + [(string) UPLOAD_ERR_EXTENSION, 'uploadExtensionErrorMessage'], ]; if (class_exists('Symfony\Component\HttpFoundation\File\UploadedFile')) { // when no maxSize is specified on constraint, it should use the ini value - $tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ + $tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ '{{ limit }}' => UploadedFile::getMaxFilesize() / 1048576, '{{ suffix }}' => 'MiB', ]]; // it should use the smaller limitation (maxSize option in this case) - $tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ + $tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ '{{ limit }}' => 1, '{{ suffix }}' => 'bytes', ], '1']; @@ -464,14 +464,14 @@ abstract class FileValidatorTest extends ConstraintValidatorTestCase // it correctly parses the maxSize option and not only uses simple string comparison // 1000M should be bigger than the ini value - $tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ + $tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ '{{ limit }}' => $limit, '{{ suffix }}' => $suffix, ], '1000M']; // it correctly parses the maxSize option and not only uses simple string comparison // 1000M should be bigger than the ini value - $tests[] = [UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ + $tests[] = [(string) UPLOAD_ERR_INI_SIZE, 'uploadIniSizeErrorMessage', [ '{{ limit }}' => '0.1', '{{ suffix }}' => 'MB', ], '100K']; diff --git a/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php b/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php new file mode 100644 index 0000000000..622bcbd8c9 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Violation; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Tests\Fixtures\ConstraintA; +use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; + +class ConstraintViolationBuilderTest extends TestCase +{ + /** + * @group legacy + * @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\Violation\ConstraintViolationBuilder::setCode() is deprecated since Symfony 4.4. A type-hint will be added in 5.0. + * @expectedDeprecation Not using a string as the error code in Symfony\Component\Validator\ConstraintViolation::__construct() is deprecated since Symfony 4.4. A type-hint will be added in 5.0. + */ + public function testNonStringCode() + { + $constraintViolationList = new ConstraintViolationList(); + (new ConstraintViolationBuilder($constraintViolationList, new ConstraintA(), 'invalid message', [], null, 'foo', 'baz', new IdentityTranslator())) + ->setCode(42) + ->addViolation(); + + self::assertSame(42, $constraintViolationList->get(0)->getCode()); + } +} diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index ed18f6fa8e..9b3cfa68ab 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -132,6 +132,10 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface */ public function setCode($code) { + if (null !== $code && !\is_string($code)) { + @trigger_error(sprintf('Not using a string as the error code in %s() is deprecated since Symfony 4.4. A type-hint will be added in 5.0.', __METHOD__), E_USER_DEPRECATED); + } + $this->code = $code; return $this; From ba241ce3cc9957fbfac1206e5e14a4f6b031f59a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 28 Jun 2019 18:36:51 +0200 Subject: [PATCH 04/33] deprecate the framework.templating option --- UPGRADE-4.3.md | 1 + UPGRADE-5.0.md | 2 +- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../FrameworkBundle/DependencyInjection/Configuration.php | 1 + .../Tests/DependencyInjection/ConfigurationTest.php | 3 +++ .../Tests/DependencyInjection/FrameworkExtensionTest.php | 4 ++++ 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md index 7a6abcca81..5b581405ce 100644 --- a/UPGRADE-4.3.md +++ b/UPGRADE-4.3.md @@ -71,6 +71,7 @@ Form FrameworkBundle --------------- + * Deprecated the `framework.templating` option, use Twig instead. * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will be mandatory in 5.0. * Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index e16fb86c79..f8e2c70d25 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -169,8 +169,8 @@ Form FrameworkBundle --------------- + * Remved the `framework.templating` option, use Twig instead. * The project dir argument of the constructor of `AssetsInstallCommand` is required. - * Removed support for `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn` is either the service ID when using controllers as services or the FQCN of the controller. diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 8c877f562d..d8c07db3fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.3.0 ----- + * Deprecated the `framework.templating` option, use Twig instead. * Added `WebTestAssertionsTrait` (included by default in `WebTestCase`) * Renamed `Client` to `KernelBrowser` * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde..e7233cd49e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -605,6 +605,7 @@ class Configuration implements ConfigurationInterface ->arrayNode('templating') ->info('templating configuration') ->canBeEnabled() + ->setDeprecated('The "%path%.%node%" configuration is deprecated since Symfony 4.3. Use the "twig" service directly instead.') ->beforeNormalization() ->ifTrue(function ($v) { return false === $v || \is_array($v) && false === $v['enabled']; }) ->then(function () { return ['enabled' => false, 'engines' => false]; }) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 3980036006..4a5001e106 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -35,6 +35,9 @@ class ConfigurationTest extends TestCase ); } + /** + * @group legacy + */ public function testDoNoDuplicateDefaultFormResources() { $input = ['templating' => [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index fab27d4897..01cd04a743 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -602,6 +602,9 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertEquals('global_hinclude_template', $container->getParameter('fragment.renderer.hinclude.global_template'), '->registerTemplatingConfiguration() registers the global hinclude.js template'); } + /** + * @group legacy + */ public function testTemplatingCanBeDisabled() { $container = $this->createContainerFromFile('templating_disabled'); @@ -867,6 +870,7 @@ abstract class FrameworkExtensionTest extends TestCase } /** + * @group legacy * @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException */ public function testTemplatingRequiresAtLeastOneEngine() From 28882f52cbe8fc086d6ed32beff6290ee997793c Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 29 Jun 2019 18:43:59 +0200 Subject: [PATCH 05/33] Annotated correct return type for getInEdges()/getOutEdges(). --- .../Compiler/ServiceReferenceGraphNode.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php index 6abd6c86af..50140b5fff 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php @@ -81,7 +81,7 @@ class ServiceReferenceGraphNode /** * Returns the in edges. * - * @return array The in ServiceReferenceGraphEdge array + * @return ServiceReferenceGraphEdge[] */ public function getInEdges() { @@ -91,7 +91,7 @@ class ServiceReferenceGraphNode /** * Returns the out edges. * - * @return array The out ServiceReferenceGraphEdge array + * @return ServiceReferenceGraphEdge[] */ public function getOutEdges() { From e113e7f812b9f6cc4f36ea40eecd91fd4dc20553 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Wed, 22 May 2019 11:46:46 +0200 Subject: [PATCH 06/33] [Validator] Add a Length::$allowEmptyString option to reject empty strings which defaults to `true` in 4.4 but will trigger a deprecation if not set explicitly in order to make the default `false` in 5.0. --- UPGRADE-4.4.md | 4 ++ .../Doctrine/Tests/Fixtures/BaseUser.php | 14 ++++++ .../Tests/Fixtures/DoctrineLoaderEntity.php | 11 ++++- .../Tests/Resources/validator/BaseUser.xml | 5 --- .../Tests/Validator/DoctrineLoaderTest.php | 2 + .../Type/FormTypeValidatorExtensionTest.php | 4 +- .../Validator/ValidatorTypeGuesserTest.php | 8 +++- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Validator/Constraints/Length.php | 8 ++++ .../Validator/Constraints/LengthValidator.php | 2 +- .../Tests/Constraints/LengthTest.php | 6 +-- .../Tests/Constraints/LengthValidatorTest.php | 43 +++++++++++++++---- .../Validator/RecursiveValidatorTest.php | 7 +-- 13 files changed, 89 insertions(+), 26 deletions(-) diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index d3de7b168c..168b78a47f 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -87,3 +87,7 @@ Validator * Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead. + * The `Length` constraint expects the `allowEmptyString` option to be defined + when the `min` option is used. + Set it to `true` to keep the current behavior and `false` to reject empty strings. + In 5.0, it'll become optional and will default to `false`. diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php index abf8819a4c..50b5845581 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php @@ -2,6 +2,9 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Mapping\ClassMetadata; + /** * Class BaseUser. */ @@ -46,4 +49,15 @@ class BaseUser { return $this->username; } + + public static function loadValidatorMetadata(ClassMetadata $metadata): void + { + $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $metadata->addPropertyConstraint('username', new Assert\Length([ + 'min' => 2, + 'max' => 120, + 'groups' => ['Registration'], + ] + $allowEmptyString)); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php index dc06d37fa3..9a2111f2b9 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php @@ -14,6 +14,7 @@ namespace Symfony\Bridge\Doctrine\Tests\Fixtures; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Mapping\ClassMetadata; /** * @ORM\Entity @@ -36,13 +37,11 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity /** * @ORM\Column(length=20) - * @Assert\Length(min=5) */ public $mergedMaxLength; /** * @ORM\Column(length=20) - * @Assert\Length(min=1, max=10) */ public $alreadyMappedMaxLength; @@ -69,4 +68,12 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity /** @ORM\Column(type="simple_array", length=100) */ public $simpleArrayField = []; + + public static function loadValidatorMetadata(ClassMetadata $metadata): void + { + $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $metadata->addPropertyConstraint('mergedMaxLength', new Assert\Length(['min' => 5] + $allowEmptyString)); + $metadata->addPropertyConstraint('alreadyMappedMaxLength', new Assert\Length(['min' => 1, 'max' => 10] + $allowEmptyString)); + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml b/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml index bf64b92ca4..ddb8a13bc1 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml +++ b/src/Symfony/Bridge/Doctrine/Tests/Resources/validator/BaseUser.xml @@ -9,11 +9,6 @@ - - - - - diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index 2dcab2533d..45cae2da41 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -40,6 +40,7 @@ class DoctrineLoaderTest extends TestCase } $validator = Validation::createValidatorBuilder() + ->addMethodMapping('loadValidatorMetadata') ->enableAnnotationMapping() ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager(), '{^Symfony\\\\Bridge\\\\Doctrine\\\\Tests\\\\Fixtures\\\\DoctrineLoader}')) ->getValidator() @@ -142,6 +143,7 @@ class DoctrineLoaderTest extends TestCase } $validator = Validation::createValidatorBuilder() + ->addMethodMapping('loadValidatorMetadata') ->enableAnnotationMapping() ->addXmlMappings([__DIR__.'/../Resources/validator/BaseUser.xml']) ->addLoader( diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php index 57f92b6574..a920e3be5b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php @@ -57,13 +57,15 @@ class FormTypeValidatorExtensionTest extends BaseValidatorExtensionTest public function testGroupSequenceWithConstraintsOption() { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + $form = Forms::createFormFactoryBuilder() ->addExtension(new ValidatorExtension(Validation::createValidator())) ->getFormFactory() ->create(FormTypeTest::TESTED_TYPE, null, (['validation_groups' => new GroupSequence(['First', 'Second'])])) ->add('field', TextTypeTest::TESTED_TYPE, [ 'constraints' => [ - new Length(['min' => 10, 'groups' => ['First']]), + new Length(['min' => 10, 'groups' => ['First']] + $allowEmptyString), new Email(['groups' => ['Second']]), ], ]) diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php index 878bbfad21..fd11342bea 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ValidatorTypeGuesserTest.php @@ -61,11 +61,13 @@ class ValidatorTypeGuesserTest extends TestCase public function guessRequiredProvider() { + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + return [ [new NotNull(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)], [new NotBlank(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)], [new IsTrue(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)], - [new Length(10), new ValueGuess(false, Guess::LOW_CONFIDENCE)], + [new Length(['min' => 10, 'max' => 10] + $allowEmptyString), new ValueGuess(false, Guess::LOW_CONFIDENCE)], [new Range(['min' => 1, 'max' => 20]), new ValueGuess(false, Guess::LOW_CONFIDENCE)], ]; } @@ -101,7 +103,9 @@ class ValidatorTypeGuesserTest extends TestCase public function testGuessMaxLengthForConstraintWithMinValue() { - $constraint = new Length(['min' => '2']); + $allowEmptyString = property_exists(Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; + + $constraint = new Length(['min' => '2'] + $allowEmptyString); $result = $this->guesser->guessMaxLengthForConstraint($constraint); $this->assertNull($result); diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 8a85ee35ef..a86679dd1c 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * added the `compared_value_path` parameter in violations when using any comparison constraint with the `propertyPath` option. * added support for checking an array of types in `TypeValidator` + * added a new `allowEmptyString` option to the `Length` constraint to allow rejecting empty strings when `min` is set, by setting it to `false`. 4.3.0 ----- diff --git a/src/Symfony/Component/Validator/Constraints/Length.php b/src/Symfony/Component/Validator/Constraints/Length.php index 0edd0e97e0..d9b0d1f1c5 100644 --- a/src/Symfony/Component/Validator/Constraints/Length.php +++ b/src/Symfony/Component/Validator/Constraints/Length.php @@ -41,6 +41,7 @@ class Length extends Constraint public $min; public $charset = 'UTF-8'; public $normalizer; + public $allowEmptyString; public function __construct($options = null) { @@ -56,6 +57,13 @@ class Length extends Constraint parent::__construct($options); + if (null === $this->allowEmptyString) { + $this->allowEmptyString = true; + if (null !== $this->min) { + @trigger_error(sprintf('Using the "%s" constraint with the "min" option without setting the "allowEmptyString" one is deprecated and defaults to true. In 5.0, it will become optional and default to false.', self::class), E_USER_DEPRECATED); + } + } + if (null === $this->min && null === $this->max) { throw new MissingOptionsException(sprintf('Either option "min" or "max" must be given for constraint %s', __CLASS__), ['min', 'max']); } diff --git a/src/Symfony/Component/Validator/Constraints/LengthValidator.php b/src/Symfony/Component/Validator/Constraints/LengthValidator.php index f3cf245cf4..b1b5d7c770 100644 --- a/src/Symfony/Component/Validator/Constraints/LengthValidator.php +++ b/src/Symfony/Component/Validator/Constraints/LengthValidator.php @@ -30,7 +30,7 @@ class LengthValidator extends ConstraintValidator throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Length'); } - if (null === $value || '' === $value) { + if (null === $value || ('' === $value && $constraint->allowEmptyString)) { return; } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php index 6a20ff541f..b7aa2339aa 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LengthTest.php @@ -21,7 +21,7 @@ class LengthTest extends TestCase { public function testNormalizerCanBeSet() { - $length = new Length(['min' => 0, 'max' => 10, 'normalizer' => 'trim']); + $length = new Length(['min' => 0, 'max' => 10, 'normalizer' => 'trim', 'allowEmptyString' => false]); $this->assertEquals('trim', $length->normalizer); } @@ -32,7 +32,7 @@ class LengthTest extends TestCase */ public function testInvalidNormalizerThrowsException() { - new Length(['min' => 0, 'max' => 10, 'normalizer' => 'Unknown Callable']); + new Length(['min' => 0, 'max' => 10, 'normalizer' => 'Unknown Callable', 'allowEmptyString' => false]); } /** @@ -41,6 +41,6 @@ class LengthTest extends TestCase */ public function testInvalidNormalizerObjectThrowsException() { - new Length(['min' => 0, 'max' => 10, 'normalizer' => new \stdClass()]); + new Length(['min' => 0, 'max' => 10, 'normalizer' => new \stdClass(), 'allowEmptyString' => false]); } } diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php index 96c388ae5b..6e94a0233e 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/LengthValidatorTest.php @@ -22,26 +22,47 @@ class LengthValidatorTest extends ConstraintValidatorTestCase return new LengthValidator(); } - public function testNullIsValid() + public function testLegacyNullIsValid() { - $this->validator->validate(null, new Length(6)); + $this->validator->validate(null, new Length(['value' => 6, 'allowEmptyString' => false])); $this->assertNoViolation(); } - public function testEmptyStringIsValid() + /** + * @group legacy + * @expectedDeprecation Using the "Symfony\Component\Validator\Constraints\Length" constraint with the "min" option without setting the "allowEmptyString" one is deprecated and defaults to true. In 5.0, it will become optional and default to false. + */ + public function testLegacyEmptyStringIsValid() { $this->validator->validate('', new Length(6)); $this->assertNoViolation(); } + public function testEmptyStringIsInvalid() + { + $this->validator->validate('', new Length([ + 'value' => $limit = 6, + 'allowEmptyString' => false, + 'exactMessage' => 'myMessage', + ])); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '""') + ->setParameter('{{ limit }}', $limit) + ->setInvalidValue('') + ->setPlural($limit) + ->setCode(Length::TOO_SHORT_ERROR) + ->assertRaised(); + } + /** * @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException */ public function testExpectsStringCompatibleType() { - $this->validator->validate(new \stdClass(), new Length(5)); + $this->validator->validate(new \stdClass(), new Length(['value' => 5, 'allowEmptyString' => false])); } public function getThreeOrLessCharacters() @@ -109,7 +130,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase */ public function testValidValuesMin($value) { - $constraint = new Length(['min' => 5]); + $constraint = new Length(['min' => 5, 'allowEmptyString' => false]); $this->validator->validate($value, $constraint); $this->assertNoViolation(); @@ -131,7 +152,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase */ public function testValidValuesExact($value) { - $constraint = new Length(4); + $constraint = new Length(['value' => 4, 'allowEmptyString' => false]); $this->validator->validate($value, $constraint); $this->assertNoViolation(); @@ -142,7 +163,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase */ public function testValidNormalizedValues($value) { - $constraint = new Length(['min' => 3, 'max' => 3, 'normalizer' => 'trim']); + $constraint = new Length(['min' => 3, 'max' => 3, 'normalizer' => 'trim', 'allowEmptyString' => false]); $this->validator->validate($value, $constraint); $this->assertNoViolation(); @@ -156,6 +177,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase $constraint = new Length([ 'min' => 4, 'minMessage' => 'myMessage', + 'allowEmptyString' => false, ]); $this->validator->validate($value, $constraint); @@ -199,6 +221,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase 'min' => 4, 'max' => 4, 'exactMessage' => 'myMessage', + 'allowEmptyString' => false, ]); $this->validator->validate($value, $constraint); @@ -221,6 +244,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase 'min' => 4, 'max' => 4, 'exactMessage' => 'myMessage', + 'allowEmptyString' => false, ]); $this->validator->validate($value, $constraint); @@ -244,6 +268,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase 'max' => 1, 'charset' => $charset, 'charsetMessage' => 'myMessage', + 'allowEmptyString' => false, ]); $this->validator->validate($value, $constraint); @@ -262,7 +287,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase public function testConstraintDefaultOption() { - $constraint = new Length(5); + $constraint = new Length(['value' => 5, 'allowEmptyString' => false]); $this->assertEquals(5, $constraint->min); $this->assertEquals(5, $constraint->max); @@ -270,7 +295,7 @@ class LengthValidatorTest extends ConstraintValidatorTestCase public function testConstraintAnnotationDefaultOption() { - $constraint = new Length(['value' => 5, 'exactMessage' => 'message']); + $constraint = new Length(['value' => 5, 'exactMessage' => 'message', 'allowEmptyString' => false]); $this->assertEquals(5, $constraint->min); $this->assertEquals(5, $constraint->max); diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index 8109b6b9bf..c0c7c3e96d 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -103,7 +103,7 @@ class RecursiveValidatorTest extends AbstractTest public function testCollectionConstraintValidateAllGroupsForNestedConstraints() { $this->metadata->addPropertyConstraint('data', new Collection(['fields' => [ - 'one' => [new NotBlank(['groups' => 'one']), new Length(['min' => 2, 'groups' => 'two'])], + 'one' => [new NotBlank(['groups' => 'one']), new Length(['min' => 2, 'groups' => 'two', 'allowEmptyString' => false])], 'two' => [new NotBlank(['groups' => 'two'])], ]])); @@ -121,7 +121,7 @@ class RecursiveValidatorTest extends AbstractTest { $this->metadata->addPropertyConstraint('data', new All(['constraints' => [ new NotBlank(['groups' => 'one']), - new Length(['min' => 2, 'groups' => 'two']), + new Length(['min' => 2, 'groups' => 'two', 'allowEmptyString' => false]), ]])); $entity = new Entity(); @@ -129,8 +129,9 @@ class RecursiveValidatorTest extends AbstractTest $violations = $this->validator->validate($entity, null, ['one', 'two']); - $this->assertCount(2, $violations); + $this->assertCount(3, $violations); $this->assertInstanceOf(NotBlank::class, $violations->get(0)->getConstraint()); $this->assertInstanceOf(Length::class, $violations->get(1)->getConstraint()); + $this->assertInstanceOf(Length::class, $violations->get(2)->getConstraint()); } } From c986c86d1c015f611f1683e5db35135abecbef98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Mon, 1 Jul 2019 09:18:49 +0200 Subject: [PATCH 07/33] =?UTF-8?q?[Lock]=C2=A0Stores=20must=20implement=20`?= =?UTF-8?q?putOffExpiration`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Symfony/Component/Lock/Store/ZookeeperStore.php | 2 +- src/Symfony/Component/Lock/StoreInterface.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Component/Lock/Store/ZookeeperStore.php b/src/Symfony/Component/Lock/Store/ZookeeperStore.php index 4f8b4ee374..0302899921 100644 --- a/src/Symfony/Component/Lock/Store/ZookeeperStore.php +++ b/src/Symfony/Component/Lock/Store/ZookeeperStore.php @@ -91,7 +91,7 @@ class ZookeeperStore implements StoreInterface */ public function putOffExpiration(Key $key, $ttl) { - throw new NotSupportedException(); + // do nothing, zookeeper locks forever. } /** diff --git a/src/Symfony/Component/Lock/StoreInterface.php b/src/Symfony/Component/Lock/StoreInterface.php index 519c3e4731..e5a1dfa588 100644 --- a/src/Symfony/Component/Lock/StoreInterface.php +++ b/src/Symfony/Component/Lock/StoreInterface.php @@ -49,7 +49,6 @@ interface StoreInterface * @param float $ttl amount of seconds to keep the lock in the store * * @throws LockConflictedException - * @throws NotSupportedException */ public function putOffExpiration(Key $key, $ttl); From f82e28c5333f3d9d069522119819b38b6fecee84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 28 Jun 2019 15:18:47 +0200 Subject: [PATCH 08/33] [HttpFoundation] Deprecated ApacheRequest --- UPGRADE-4.4.md | 13 +++++++++---- UPGRADE-5.0.md | 5 ++--- .../Component/HttpFoundation/ApacheRequest.php | 4 ++++ src/Symfony/Component/HttpFoundation/CHANGELOG.md | 1 + .../HttpFoundation/Tests/ApacheRequestTest.php | 1 + 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 96c170675c..d745ce3252 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -26,7 +26,7 @@ DependencyInjection services: App\Handler: tags: ['app.handler'] - + App\HandlerCollection: arguments: [!tagged app.handler] ``` @@ -36,7 +36,7 @@ DependencyInjection services: App\Handler: tags: ['app.handler'] - + App\HandlerCollection: arguments: [!tagged_iterator app.handler] ``` @@ -60,6 +60,11 @@ HttpClient * Added method `cancel()` to `ResponseInterface` +HttpFoundation +-------------- + + * `ApacheRequest` is deprecated, use `Request` class instead. + HttpKernel ---------- @@ -84,11 +89,11 @@ Security TwigBridge ---------- - * Deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the + * Deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the `DebugCommand::__construct()` method, swap the variables position. Validator --------- - * Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. + * Deprecated passing an `ExpressionLanguage` instance as the second argument of `ExpressionValidator::__construct()`. Pass it as the first argument instead. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index bb6f87dd00..a396c7edcc 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -101,7 +101,7 @@ DependencyInjection services: App\Handler: tags: ['app.handler'] - + App\HandlerCollection: arguments: [!tagged_iterator app.handler] ``` @@ -114,7 +114,6 @@ DoctrineBridge * Passing an `IdReader` to the `DoctrineChoiceLoader` when the query cannot be optimized with single id field will throw an exception, pass `null` instead * Not passing an `IdReader` to the `DoctrineChoiceLoader` when the query can be optimized with single id field will not apply any optimization - DomCrawler ---------- @@ -268,6 +267,7 @@ HttpFoundation use `Symfony\Component\Mime\FileBinaryMimeTypeGuesser` instead. * The `FileinfoMimeTypeGuesser` class has been removed, use `Symfony\Component\Mime\FileinfoMimeTypeGuesser` instead. + * `ApacheRequest` has been removed, use the `Request` class instead. HttpKernel ---------- @@ -518,7 +518,6 @@ Workflow property: state ``` - * Support for using a workflow with a single state marking is dropped. Use a state machine instead. Before: diff --git a/src/Symfony/Component/HttpFoundation/ApacheRequest.php b/src/Symfony/Component/HttpFoundation/ApacheRequest.php index 4e99186dcd..f189cde585 100644 --- a/src/Symfony/Component/HttpFoundation/ApacheRequest.php +++ b/src/Symfony/Component/HttpFoundation/ApacheRequest.php @@ -11,9 +11,13 @@ namespace Symfony\Component\HttpFoundation; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', ApacheRequest::class, Request::class), E_USER_DEPRECATED); + /** * Request represents an HTTP request from an Apache server. * + * @deprecated since Symfony 4.4. Use the Request class instead. + * * @author Fabien Potencier */ class ApacheRequest extends Request diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 7ecbdffa9e..29e06e678c 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. 4.3.0 ----- diff --git a/src/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php index 6fa3b88917..7a5bd378a2 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\HttpFoundation\Tests; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\ApacheRequest; +/** @group legacy */ class ApacheRequestTest extends TestCase { /** From 8544a35e52a4a1acdfc134a9fb68c31fa3a677ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Mon, 1 Jul 2019 10:51:14 +0200 Subject: [PATCH 09/33] Remove @internal annotations for the serilize methods --- src/Symfony/Component/Mime/Message.php | 6 ------ src/Symfony/Component/Mime/RawMessage.php | 6 ------ 2 files changed, 12 deletions(-) diff --git a/src/Symfony/Component/Mime/Message.php b/src/Symfony/Component/Mime/Message.php index db6e13fae6..4d6af1c94d 100644 --- a/src/Symfony/Component/Mime/Message.php +++ b/src/Symfony/Component/Mime/Message.php @@ -130,17 +130,11 @@ class Message extends RawMessage return bin2hex(random_bytes(16)).strstr($email, '@'); } - /** - * @internal - */ public function __serialize(): array { return [$this->headers, $this->body]; } - /** - * @internal - */ public function __unserialize(array $data): void { [$this->headers, $this->body] = $data; diff --git a/src/Symfony/Component/Mime/RawMessage.php b/src/Symfony/Component/Mime/RawMessage.php index 16b090c954..40a2795e23 100644 --- a/src/Symfony/Component/Mime/RawMessage.php +++ b/src/Symfony/Component/Mime/RawMessage.php @@ -69,17 +69,11 @@ class RawMessage implements \Serializable $this->__unserialize(unserialize($serialized)); } - /** - * @internal - */ public function __serialize(): array { return [$this->message]; } - /** - * @internal - */ public function __unserialize(array $data): void { [$this->message] = $data; From c670d5120e20cdbe4a1e75dfb152ee29e0a23500 Mon Sep 17 00:00:00 2001 From: Amrouche Hamza Date: Mon, 1 Jul 2019 14:02:11 +0200 Subject: [PATCH 10/33] [HttpKernel][DX] Improve the error message when not defining the controller as a service but using contruct parameters --- .../HttpKernel/Controller/ContainerControllerResolver.php | 2 +- .../Tests/Controller/ContainerControllerResolverTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php index 4f80921cf5..b9e8de7893 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php @@ -59,7 +59,7 @@ class ContainerControllerResolver extends ControllerResolver $this->throwExceptionIfControllerWasRemoved($class, $e); if ($e instanceof \ArgumentCountError) { - throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define such a service?', $class), 0, $e); + throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e); } throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class', $class), 0, $e); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php index 27f35b64c6..2e856d97e4 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php @@ -184,16 +184,16 @@ class ContainerControllerResolverTest extends ControllerResolverTest $tests[] = [ [ControllerTestService::class, 'action'], \InvalidArgumentException::class, - 'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define such a service?', + 'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', ]; $tests[] = [ ControllerTestService::class.'::action', - \InvalidArgumentException::class, 'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define such a service?', + \InvalidArgumentException::class, 'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerTestService" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', ]; $tests[] = [ InvokableControllerService::class, \InvalidArgumentException::class, - 'Controller "Symfony\Component\HttpKernel\Tests\Controller\InvokableControllerService" has required constructor arguments and does not exist in the container. Did you forget to define such a service?', + 'Controller "Symfony\Component\HttpKernel\Tests\Controller\InvokableControllerService" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', ]; return $tests; From 58651409b47e86ece0473f22b08b9e63c067c8eb Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Mon, 1 Jul 2019 14:42:30 +0200 Subject: [PATCH 11/33] Removed unused field. --- .../Compiler/AnalyzeServiceReferencesPass.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 2dafb53789..87bb14780d 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -34,7 +34,6 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe private $onlyConstructorArguments; private $hasProxyDumper; private $lazy; - private $expressionLanguage; private $byConstructor; private $definitions; private $aliases; From c6e18374a25518384ae094c8871cb5196a719890 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 1 Jul 2019 10:32:47 -0400 Subject: [PATCH 12/33] Fixing validation for messenger transports retry_strategy service key --- .../DependencyInjection/Configuration.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde..e7034b411e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1195,9 +1195,14 @@ class Configuration implements ConfigurationInterface ->end() ->arrayNode('retry_strategy') ->addDefaultsIfNotSet() - ->validate() - ->ifTrue(function ($v) { return null !== $v['service'] && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay'])); }) - ->thenInvalid('"service" cannot be used along with the other retry_strategy options.') + ->beforeNormalization() + ->always(function ($v) { + if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) { + throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.'); + } + + return $v; + }) ->end() ->children() ->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end() From 0d27af93ea50dda5ea5f54784ea98c7b2647b0a4 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Jul 2019 09:12:04 +0200 Subject: [PATCH 13/33] [Ldap] Document the new exceptions thrown by the code --- .../Component/Ldap/Adapter/ConnectionInterface.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php b/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php index 347a852a82..ac8ccba534 100644 --- a/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php +++ b/src/Symfony/Component/Ldap/Adapter/ConnectionInterface.php @@ -11,6 +11,10 @@ namespace Symfony\Component\Ldap\Adapter; +use Symfony\Component\Ldap\Exception\AlreadyExistsException; +use Symfony\Component\Ldap\Exception\ConnectionTimeoutException; +use Symfony\Component\Ldap\Exception\InvalidCredentialsException; + /** * @author Charles Sarrazin */ @@ -28,6 +32,10 @@ interface ConnectionInterface * * @param string $dn The user's DN * @param string $password The associated password + * + * @throws AlreadyExistsException When the connection can't be created because of an LDAP_ALREADY_EXISTS error + * @throws ConnectionTimeoutException When the connection can't be created because of an LDAP_TIMEOUT error + * @throws InvalidCredentialsException When the connection can't be created because of an LDAP_INVALID_CREDENTIALS error */ public function bind($dn = null, $password = null); } From fa6ae8ce5073f8ccdcffcc8d719e01caa470d4b3 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Jul 2019 17:10:36 +0200 Subject: [PATCH 14/33] [FrmaeworkBundle] More simplifications in the DI configuration --- .../FrameworkBundle/DependencyInjection/Configuration.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde..443332f2ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -339,10 +339,7 @@ class Configuration implements ConfigurationInterface ->defaultNull() ->end() ->arrayNode('initial_marking') - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->defaultValue([]) ->prototype('scalar')->end() ->end() From 7fe06bc5f605519f2fd14c9bd16ffb93db841383 Mon Sep 17 00:00:00 2001 From: Yanick Witschi Date: Mon, 3 Jun 2019 17:58:20 +0200 Subject: [PATCH 15/33] [Messenger] Added support for auto trimming of redis streams --- src/Symfony/Component/Messenger/CHANGELOG.md | 1 + .../Transport/RedisExt/ConnectionTest.php | 16 ++++++++-- .../Transport/RedisExt/Connection.php | 29 ++++++++++++++++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Messenger/CHANGELOG.md b/src/Symfony/Component/Messenger/CHANGELOG.md index fc01fa52cc..6df7ddcd1f 100644 --- a/src/Symfony/Component/Messenger/CHANGELOG.md +++ b/src/Symfony/Component/Messenger/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecated passing a `ContainerInterface` instance as first argument of the `ConsumeMessagesCommand` constructor, pass a `RoutableMessageBus` instance instead. + * Added support for auto trimming of Redis streams. 4.3.0 ----- diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php index 177f6b9038..f329222bb3 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php @@ -42,13 +42,13 @@ class ConnectionTest extends TestCase public function testFromDsnWithOptions() { $this->assertEquals( - new Connection(['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false], [ + new Connection(['stream' => 'queue', 'group' => 'group1', 'consumer' => 'consumer1', 'auto_setup' => false, 'stream_max_entries' => 20000], [ 'host' => 'localhost', 'port' => 6379, ], [ 'serializer' => 2, ]), - Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2, 'auto_setup' => false]) + Connection::fromDsn('redis://localhost/queue/group1/consumer1', ['serializer' => 2, 'auto_setup' => false, 'stream_max_entries' => 20000]) ); } @@ -142,4 +142,16 @@ class ConnectionTest extends TestCase $connection->reject($message['id']); $redis->del('messenger-getnonblocking'); } + + public function testMaxEntries() + { + $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); + + $redis->expects($this->exactly(1))->method('xadd') + ->with('queue', '*', ['message' => '{"body":"1","headers":[]}'], 20000, true) + ->willReturn(1); + + $connection = Connection::fromDsn('redis://localhost/queue?stream_max_entries=20000', [], $redis); // 1 = always + $connection->add('1', []); + } } diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php index 5757f0be7f..c9069aa38c 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php @@ -32,6 +32,7 @@ class Connection 'group' => 'symfony', 'consumer' => 'consumer', 'auto_setup' => true, + 'stream_max_entries' => 0, // any value higher than 0 defines an approximate maximum number of stream entries ]; private $connection; @@ -39,6 +40,7 @@ class Connection private $group; private $consumer; private $autoSetup; + private $maxEntries; private $couldHavePendingMessages = true; public function __construct(array $configuration, array $connectionCredentials = [], array $redisOptions = [], \Redis $redis = null) @@ -50,6 +52,7 @@ class Connection $this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group']; $this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer']; $this->autoSetup = $configuration['auto_setup'] ?? self::DEFAULT_OPTIONS['auto_setup']; + $this->maxEntries = $configuration['stream_max_entries'] ?? self::DEFAULT_OPTIONS['stream_max_entries']; } public static function fromDsn(string $dsn, array $redisOptions = [], \Redis $redis = null): self @@ -79,7 +82,19 @@ class Connection unset($redisOptions['auto_setup']); } - return new self(['stream' => $stream, 'group' => $group, 'consumer' => $consumer, 'auto_setup' => $autoSetup], $connectionCredentials, $redisOptions, $redis); + $maxEntries = null; + if (\array_key_exists('stream_max_entries', $redisOptions)) { + $maxEntries = filter_var($redisOptions['stream_max_entries'], FILTER_VALIDATE_INT); + unset($redisOptions['stream_max_entries']); + } + + return new self([ + 'stream' => $stream, + 'group' => $group, + 'consumer' => $consumer, + 'auto_setup' => $autoSetup, + 'stream_max_entries' => $maxEntries, + ], $connectionCredentials, $redisOptions, $redis); } public function get(): ?array @@ -166,9 +181,15 @@ class Connection $e = null; try { - $added = $this->connection->xadd($this->stream, '*', ['message' => json_encode( - ['body' => $body, 'headers' => $headers] - )]); + if ($this->maxEntries) { + $added = $this->connection->xadd($this->stream, '*', ['message' => json_encode( + ['body' => $body, 'headers' => $headers] + )], $this->maxEntries, true); + } else { + $added = $this->connection->xadd($this->stream, '*', ['message' => json_encode( + ['body' => $body, 'headers' => $headers] + )]); + } } catch (\RedisException $e) { } From f19f28a41d62acf89f11ad077550ebb38767aad6 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 3 Jul 2019 09:52:02 +0200 Subject: [PATCH 16/33] only decorate when an event dispatcher was passed --- src/Symfony/Component/Workflow/Workflow.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index e3df8cb0d0..76e53eda51 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -43,7 +43,7 @@ class Workflow implements WorkflowInterface { $this->definition = $definition; $this->markingStore = $markingStore ?: new MultipleStateMarkingStore(); - $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); + $this->dispatcher = null !== $dispatcher ? LegacyEventDispatcherProxy::decorate($dispatcher) : null; $this->name = $name; } From bedae5dde9ee9430e4ac077d905159e1968f272d Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Wed, 3 Jul 2019 11:22:31 +0200 Subject: [PATCH 17/33] Fix authentication for redis transport --- .../Tests/Transport/RedisExt/ConnectionTest.php | 10 ++++++++++ .../Messenger/Transport/RedisExt/Connection.php | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php index 177f6b9038..0d80e920c3 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/RedisExt/ConnectionTest.php @@ -79,6 +79,16 @@ class ConnectionTest extends TestCase $this->assertNotNull($connection->get()); } + public function testAuth() + { + $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); + + $redis->expects($this->exactly(1))->method('auth') + ->with('password'); + + Connection::fromDsn('redis://password@localhost/queue', [], $redis); + } + public function testFirstGetPendingMessagesThenNewMessages() { $redis = $this->getMockBuilder(\Redis::class)->disableOriginalConstructor()->getMock(); diff --git a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php index 18b6091e9c..8524c1fe03 100644 --- a/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/RedisExt/Connection.php @@ -51,6 +51,11 @@ class Connection $this->connection = $redis ?: new \Redis(); $this->connection->connect($connectionCredentials['host'] ?? '127.0.0.1', $connectionCredentials['port'] ?? 6379); $this->connection->setOption(\Redis::OPT_SERIALIZER, $redisOptions['serializer'] ?? \Redis::SERIALIZER_PHP); + + if (isset($connectionCredentials['auth'])) { + $this->connection->auth($connectionCredentials['auth']); + } + $this->stream = $configuration['stream'] ?? self::DEFAULT_OPTIONS['stream']; $this->group = $configuration['group'] ?? self::DEFAULT_OPTIONS['group']; $this->consumer = $configuration['consumer'] ?? self::DEFAULT_OPTIONS['consumer']; @@ -72,6 +77,7 @@ class Connection $connectionCredentials = [ 'host' => $parsedUrl['host'] ?? '127.0.0.1', 'port' => $parsedUrl['port'] ?? 6379, + 'auth' => $parsedUrl['pass'] ?? $parsedUrl['user'] ?? null, ]; if (isset($parsedUrl['query'])) { From 93190182f6796cffb9f16d1b55d4aceab17bffc3 Mon Sep 17 00:00:00 2001 From: smoench Date: Tue, 2 Jul 2019 08:30:32 +0200 Subject: [PATCH 18/33] [Filesystem] depreacte calling isAbsolutePath with a null --- UPGRADE-4.4.md | 5 +++++ UPGRADE-5.0.md | 1 + src/Symfony/Component/Filesystem/CHANGELOG.md | 5 +++++ src/Symfony/Component/Filesystem/Filesystem.php | 4 ++++ .../Component/Filesystem/Tests/FilesystemTest.php | 10 +++++++++- 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 96c170675c..9d6ad7b3dc 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -41,6 +41,11 @@ DependencyInjection arguments: [!tagged_iterator app.handler] ``` +Filesystem +---------- + + * Support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated. + Form ---- diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index bb6f87dd00..3d613e647d 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -135,6 +135,7 @@ EventDispatcher Filesystem ---------- + * The `Filesystem::isAbsolutePath()` method no longer supports `null` in the `$file` argument. * The `Filesystem::dumpFile()` method no longer supports arrays in the `$content` argument. * The `Filesystem::appendToFile()` method no longer supports arrays in the `$content` argument. diff --git a/src/Symfony/Component/Filesystem/CHANGELOG.md b/src/Symfony/Component/Filesystem/CHANGELOG.md index f6453c16e3..0b633ef2a7 100644 --- a/src/Symfony/Component/Filesystem/CHANGELOG.md +++ b/src/Symfony/Component/Filesystem/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + + * support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0 + 4.3.0 ----- diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index dd3d8b471e..969f5b05f9 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -600,6 +600,10 @@ class Filesystem */ public function isAbsolutePath($file) { + if (null === $file) { + @trigger_error(sprintf('Calling "%s()" with a null in the $file argument is deprecated since Symfony 4.4.', __METHOD__), E_USER_DEPRECATED); + } + return strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 7fa2ecd3de..80fb44ec34 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1397,10 +1397,18 @@ class FilesystemTest extends FilesystemTestCase ['var/lib', false], ['../var/lib', false], ['', false], - [null, false], ]; } + /** + * @group legacy + * @expectedDeprecation Calling "Symfony\Component\Filesystem\Filesystem::isAbsolutePath()" with a null in the $file argument is deprecated since Symfony 4.4. + */ + public function testIsAbsolutePathWithNull() + { + $this->assertFalse($this->filesystem->isAbsolutePath(null)); + } + public function testTempnam() { $dirname = $this->workspace; From b5c6a349ab38d8526bb6747b66a8c8da7bb4349a Mon Sep 17 00:00:00 2001 From: smoench Date: Wed, 3 Jul 2019 13:34:39 +0200 Subject: [PATCH 19/33] [Filesystem] added missing deprecations to UPGRADE-4.3.md --- UPGRADE-4.3.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md index 20b970c15a..40d50e583f 100644 --- a/UPGRADE-4.3.md +++ b/UPGRADE-4.3.md @@ -57,6 +57,12 @@ EventDispatcher * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated * The `Event` class has been deprecated, use `Symfony\Contracts\EventDispatcher\Event` instead +Filesystem +---------- + + * Support for passing arrays to `Filesystem::dumpFile()` is deprecated. + * Support for passing arrays to `Filesystem::appendToFile()` is deprecated. + Form ---- From e6d76bae9f4e094fb12bf6da8ef7035c98f27a71 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Jul 2019 17:01:08 +0200 Subject: [PATCH 20/33] [FrameworkBundle] Simplified some code in the DI configuration --- .../DependencyInjection/Configuration.php | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde..a5e36e3f36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -585,10 +585,7 @@ class Configuration implements ConfigurationInterface ->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); }) ->then(function ($v) { return $v['mime_type']; }) ->end() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -646,10 +643,7 @@ class Configuration implements ConfigurationInterface ->fixXmlConfig('loader') ->children() ->arrayNode('loaders') - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -674,10 +668,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('base_path')->defaultValue('')->end() ->arrayNode('base_urls') ->requiresAtLeastOneElement() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -719,10 +710,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('base_path')->defaultValue('')->end() ->arrayNode('base_urls') ->requiresAtLeastOneElement() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -817,10 +805,7 @@ class Configuration implements ConfigurationInterface ->defaultValue(['loadValidatorMetadata']) ->prototype('scalar')->end() ->treatFalseLike([]) - ->validate() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return (array) $v; }) - ->end() + ->validate()->castToArray()->end() ->end() ->scalarNode('translation_domain')->defaultValue('validators')->end() ->booleanNode('strict_email')->end() From 5909e6f336320ed661459395225f735472b790a1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 3 Jul 2019 14:31:50 +0200 Subject: [PATCH 21/33] fixed typo --- UPGRADE-5.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index f8e2c70d25..6e7bb0a736 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -169,7 +169,7 @@ Form FrameworkBundle --------------- - * Remved the `framework.templating` option, use Twig instead. + * Removed the `framework.templating` option, use Twig instead. * The project dir argument of the constructor of `AssetsInstallCommand` is required. * Removed support for `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn` is either the service ID when using controllers as services or the FQCN of the controller. From c4afbf376f34f1b934e6ef80d76dd13b9da5d676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4dlich?= Date: Fri, 28 Jun 2019 19:05:52 +0200 Subject: [PATCH 22/33] [PropertyAccess] Adds entries to CHANGELOG and UPGRADE --- UPGRADE-4.4.md | 5 +++++ src/Symfony/Component/PropertyAccess/CHANGELOG.md | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index 96c170675c..85783ec4c0 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -76,6 +76,11 @@ MonologBridge * The `RouteProcessor` has been marked final. +PropertyAccess +-------------- + + * Deprecated passing `null` as 2nd argument of `PropertyAccessor::createCache()` method (`$defaultLifetime`), pass `0` instead. + Security -------- diff --git a/src/Symfony/Component/PropertyAccess/CHANGELOG.md b/src/Symfony/Component/PropertyAccess/CHANGELOG.md index 0a012bb476..d733c41481 100644 --- a/src/Symfony/Component/PropertyAccess/CHANGELOG.md +++ b/src/Symfony/Component/PropertyAccess/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.4.0 +----- + + * deprecated passing `null` as `$defaultLifetime` 2nd argument of `PropertyAccessor::createCache()` method, + pass `0` instead + 4.3.0 ----- From 9432f1f970496690dac71a7440adb636b7d0d5c9 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Tue, 2 Jul 2019 09:24:49 +0200 Subject: [PATCH 23/33] [Mime] Updated some PHPDoc contents --- src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php | 3 ++- src/Symfony/Component/Mime/Crypto/SMimeSigner.php | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php b/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php index 72f59ce1bc..d4c4a42aae 100644 --- a/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php +++ b/src/Symfony/Component/Mime/Crypto/SMimeEncrypter.php @@ -23,7 +23,8 @@ final class SMimeEncrypter extends SMime private $cipher; /** - * @param string|string[] $certificate Either a lone X.509 certificate, or an array of X.509 certificates + * @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s) + * @param int $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php */ public function __construct($certificate, int $cipher = OPENSSL_CIPHER_AES_256_CBC) { diff --git a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php index 71ce5df962..ef4375cc1e 100644 --- a/src/Symfony/Component/Mime/Crypto/SMimeSigner.php +++ b/src/Symfony/Component/Mime/Crypto/SMimeSigner.php @@ -30,13 +30,11 @@ final class SMimeSigner extends SMime private $privateKeyPassphrase; /** - * @see https://secure.php.net/manual/en/openssl.pkcs7.flags.php - * - * @param string $certificate - * @param string $privateKey A file containing the private key (in PEM format) + * @param string $certificate The path of the file containing the signing certificate (in PEM format) + * @param string $privateKey The path of the file containing the private key (in PEM format) * @param string|null $privateKeyPassphrase A passphrase of the private key (if any) - * @param string $extraCerts A file containing intermediate certificates (in PEM format) needed by the signing certificate - * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() + * @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate + * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php) */ public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, int $signOptions = PKCS7_DETACHED) { From 5f4ab23991ee5d70746366d2384d8e33215d3482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 3 Jul 2019 14:56:57 +0200 Subject: [PATCH 24/33] [Messenger] Added more test for MessageBus --- .../Messenger/Tests/MessageBusTest.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php index bcddcffd8a..c8644623b7 100644 --- a/src/Symfony/Component/Messenger/Tests/MessageBusTest.php +++ b/src/Symfony/Component/Messenger/Tests/MessageBusTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\Middleware\MiddlewareInterface; +use Symfony\Component\Messenger\Middleware\StackInterface; use Symfony\Component\Messenger\Stamp\BusNameStamp; use Symfony\Component\Messenger\Stamp\DelayStamp; use Symfony\Component\Messenger\Stamp\ReceivedStamp; @@ -148,4 +149,44 @@ class MessageBusTest extends TestCase $finalEnvelope = (new MessageBus())->dispatch(new Envelope(new \stdClass()), [new DelayStamp(5), new BusNameStamp('bar')]); $this->assertCount(2, $finalEnvelope->all()); } + + public function provideConstructorDataStucture() + { + yield 'iterator' => [new \ArrayObject([ + new SimpleMiddleware(), + new SimpleMiddleware(), + ])]; + + yield 'array' => [[ + new SimpleMiddleware(), + new SimpleMiddleware(), + ]]; + + yield 'generator' => [(function (): \Generator { + yield new SimpleMiddleware(); + yield new SimpleMiddleware(); + })()]; + } + + /** @dataProvider provideConstructorDataStucture */ + public function testConstructDataStructure($dataStructure) + { + $bus = new MessageBus($dataStructure); + $envelope = new Envelope(new DummyMessage('Hello')); + $newEnvelope = $bus->dispatch($envelope); + $this->assertSame($envelope->getMessage(), $newEnvelope->getMessage()); + + // Test rewindable capacity + $envelope = new Envelope(new DummyMessage('Hello')); + $newEnvelope = $bus->dispatch($envelope); + $this->assertSame($envelope->getMessage(), $newEnvelope->getMessage()); + } +} + +class SimpleMiddleware implements MiddlewareInterface +{ + public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + return $envelope; + } } From 8e0c8006d88429d129aebf3bbe2f776d11f76d8b Mon Sep 17 00:00:00 2001 From: Chris Tanaskoski Date: Tue, 11 Jun 2019 12:05:04 +0200 Subject: [PATCH 25/33] [WIP][Mailer] Overwrite envelope sender and recipients from config --- .../DependencyInjection/Configuration.php | 16 +++++ .../FrameworkExtension.php | 7 +++ .../Resources/config/mailer.xml | 6 ++ .../Tests/Functional/MailerTest.php | 62 +++++++++++++++++++ .../Tests/Functional/app/Mailer/bundles.php | 18 ++++++ .../Tests/Functional/app/Mailer/config.yml | 9 +++ 6 files changed, 118 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/bundles.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index ea64157fde..86eb3bb798 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1525,6 +1525,22 @@ class Configuration implements ConfigurationInterface ->{!class_exists(FullStack::class) && class_exists(Mailer::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() ->scalarNode('dsn')->defaultValue('smtp://null')->end() + ->arrayNode('envelope') + ->info('Mailer Envelope configuration') + ->children() + ->scalarNode('sender')->end() + ->arrayNode('recipients') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() + ->then(function ($v) { + return array_filter(array_values($v)); + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 19386d9721..34ab211f04 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1918,6 +1918,13 @@ class FrameworkExtension extends Extension $loader->load('mailer.xml'); $container->getDefinition('mailer.default_transport')->setArgument(0, $config['dsn']); + + $recipients = $config['envelope']['recipients'] ?? null; + $sender = $config['envelope']['sender'] ?? null; + + $envelopeListener = $container->getDefinition('mailer.envelope_listener'); + $envelopeListener->setArgument(0, $sender); + $envelopeListener->setArgument(1, $recipients); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml index 1f567a6c93..cfe98f21dc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer.xml @@ -25,5 +25,11 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php new file mode 100644 index 0000000000..85987fe28f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/MailerTest.php @@ -0,0 +1,62 @@ + 'Mailer']); + + $onDoSend = function (SentMessage $message) { + $envelope = $message->getEnvelope(); + + $this->assertEquals( + [new Address('redirected@example.org')], + $envelope->getRecipients() + ); + + $this->assertEquals('sender@example.org', $envelope->getSender()->getAddress()); + }; + + $eventDispatcher = self::$container->get(EventDispatcherInterface::class); + $logger = self::$container->get('logger'); + + $testTransport = new class($eventDispatcher, $logger, $onDoSend) extends AbstractTransport { + /** + * @var callable + */ + private $onDoSend; + + public function __construct(EventDispatcherInterface $eventDispatcher, LoggerInterface $logger, callable $onDoSend) + { + parent::__construct($eventDispatcher, $logger); + $this->onDoSend = $onDoSend; + } + + protected function doSend(SentMessage $message): void + { + $onDoSend = $this->onDoSend; + $onDoSend($message); + } + }; + + $mailer = new Mailer($testTransport, null); + + $message = (new Email()) + ->subject('Test subject') + ->text('Hello world') + ->from('from@example.org') + ->to('to@example.org'); + + $mailer->send($message); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/bundles.php new file mode 100644 index 0000000000..15ff182c6f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; + +return [ + new FrameworkBundle(), + new TestBundle(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml new file mode 100644 index 0000000000..196869945e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Mailer/config.yml @@ -0,0 +1,9 @@ +imports: + - { resource: ../config/default.yml } + +framework: + mailer: + envelope: + sender: sender@example.org + recipients: + - redirected@example.org From 6b5671f5aebbe694fcc595970df70e8f26f35fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 3 Jul 2019 15:11:52 +0200 Subject: [PATCH 26/33] [Messager] Simplified MessageBus::__construct() The third path deals with generator (or other king of iterator that are not an IteratorAggregate). It means, very few cases in practice The previous code saved one object on the first call of `self::dispatch()` by replacing it at runtime. More over, this object (anon. class) is very light in memory. The performance optimization (even if fun) is not useful here. Let's make the code readable to everyone. --- .../Component/Messenger/MessageBus.php | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Messenger/MessageBus.php b/src/Symfony/Component/Messenger/MessageBus.php index aadeacfff2..5809a1fcbe 100644 --- a/src/Symfony/Component/Messenger/MessageBus.php +++ b/src/Symfony/Component/Messenger/MessageBus.php @@ -33,17 +33,26 @@ class MessageBus implements MessageBusInterface } elseif (\is_array($middlewareHandlers)) { $this->middlewareAggregate = new \ArrayObject($middlewareHandlers); } else { - $this->middlewareAggregate = new class() { - public $aggregate; - public $iterator; + // $this->middlewareAggregate should be an instance of IteratorAggregate. + // When $middlewareHandlers is an Iterator, we wrap it to ensure it is lazy-loaded and can be rewound. + $this->middlewareAggregate = new class($middlewareHandlers) implements \IteratorAggregate { + private $middlewareHandlers; + private $cachedIterator; + + public function __construct($middlewareHandlers) + { + $this->middlewareHandlers = $middlewareHandlers; + } public function getIterator() { - return $this->aggregate = new \ArrayObject(iterator_to_array($this->iterator, false)); + if (null === $this->cachedIterator) { + $this->cachedIterator = new \ArrayObject(iterator_to_array($this->middlewareHandlers, false)); + } + + return $this->cachedIterator; } }; - $this->middlewareAggregate->aggregate = &$this->middlewareAggregate; - $this->middlewareAggregate->iterator = $middlewareHandlers; } } From 195292847120349d66e9572a48dfa3e7a5b41f24 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Tue, 2 Jul 2019 17:48:31 -0400 Subject: [PATCH 27/33] Improving the request/response format autodetection --- .../Component/HttpFoundation/Request.php | 23 ++++++++++++++++ .../Component/HttpFoundation/Response.php | 2 +- .../HttpFoundation/Tests/RequestTest.php | 26 +++++++++++++++++++ .../EventListener/DebugHandlersListener.php | 2 +- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 83cdf60169..a950b0a159 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -192,6 +192,10 @@ class Request protected static $requestFactory; + /** + * @var string|null + */ + private $preferredFormat; private $isHostValid = true; private $isForwardedValid = true; @@ -1559,6 +1563,25 @@ class Request return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (null !== $this->preferredFormat) { + return $this->preferredFormat; + } + + $this->preferredFormat = $this->getRequestFormat($this->getContentType()); + + if (null === $this->preferredFormat) { + foreach ($this->getAcceptableContentTypes() as $contentType) { + if (null !== $this->preferredFormat = $this->getFormat($contentType)) { + break; + } + } + } + + return $this->preferredFormat ?: $default; + } + /** * Returns the preferred language. * diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index d1263ca7a1..1681558499 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -270,7 +270,7 @@ class Response } else { // Content-type based on the Request if (!$headers->has('Content-Type')) { - $format = $request->getRequestFormat(); + $format = $request->getPreferredFormat(); if (null !== $format && $mimeType = $request->getMimeType($format)) { $headers->set('Content-Type', $mimeType); } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index c1168f5e45..6573da0785 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -399,6 +399,32 @@ class RequestTest extends TestCase $this->assertEquals('xml', $dup->getRequestFormat()); } + public function testGetPreferredFormat() + { + $request = new Request(); + $this->assertNull($request->getPreferredFormat(null)); + $this->assertSame('html', $request->getPreferredFormat()); + $this->assertSame('json', $request->getPreferredFormat('json')); + + $request->setRequestFormat('atom'); + $request->headers->set('Content-Type', 'application/json'); + $request->headers->set('Accept', 'application/xml'); + $this->assertSame('atom', $request->getPreferredFormat()); + + $request = new Request(); + $request->headers->set('Content-Type', 'application/json'); + $request->headers->set('Accept', 'application/xml'); + $this->assertSame('json', $request->getPreferredFormat()); + + $request = new Request(); + $request->headers->set('Accept', 'application/xml'); + $this->assertSame('xml', $request->getPreferredFormat()); + + $request = new Request(); + $request->headers->set('Accept', 'application/json;q=0.8,application/xml;q=0.9'); + $this->assertSame('xml', $request->getPreferredFormat()); + } + /** * @dataProvider getFormatToMimeTypeMapProviderWithAdditionalNullFormat */ diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index b0b9a94c28..ebf6c96fcb 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -174,7 +174,7 @@ class DebugHandlersListener implements EventSubscriberInterface $e = $request->attributes->get('exception'); try { - return new Response($this->errorFormatter->render($e, $request->getRequestFormat()), $e->getStatusCode(), $e->getHeaders()); + return new Response($this->errorFormatter->render($e, $request->getPreferredFormat()), $e->getStatusCode(), $e->getHeaders()); } catch (ErrorRendererNotFoundException $_) { return new Response($this->errorFormatter->render($e), $e->getStatusCode(), $e->getHeaders()); } From 1f8927a9a64dc80124389fe482ba9ccc40ed8689 Mon Sep 17 00:00:00 2001 From: misterx <216417+misterx@users.noreply.github.com> Date: Wed, 26 Jun 2019 14:51:54 +0300 Subject: [PATCH 28/33] Fixes windows error --- src/Symfony/Bridge/PhpUnit/bin/simple-phpunit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index c0f4b782b6..3f5d14574d 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -95,7 +95,7 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__ $prevRoot = getenv('COMPOSER_ROOT_VERSION'); putenv("COMPOSER_ROOT_VERSION=$PHPUNIT_VERSION.99"); // --no-suggest is not in the list to keep compat with composer 1.0, which is shipped with Ubuntu 16.04LTS - $exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p, getcwd(), null, array('bypass_shell' => true))); + $exit = proc_close(proc_open("$COMPOSER install --no-dev --prefer-dist --no-progress --ansi", array(), $p)); putenv('COMPOSER_ROOT_VERSION'.(false !== $prevRoot ? '='.$prevRoot : '')); if ($exit) { exit($exit); From ce6a5ad235b0e06b0ac8fc121d46e68d33dd70ed Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 4 Jul 2019 02:03:26 +0200 Subject: [PATCH 29/33] Use ConnectionRegistry instead of RegistryInterface. --- .../Transport/Doctrine/DoctrineTransportFactoryTest.php | 8 ++++---- .../Transport/Doctrine/DoctrineTransportFactory.php | 4 ++-- src/Symfony/Component/Messenger/composer.json | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php index 9129ac6299..cdde9284d7 100644 --- a/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php +++ b/src/Symfony/Component/Messenger/Tests/Transport/Doctrine/DoctrineTransportFactoryTest.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Messenger\Tests\Transport\Doctrine; +use Doctrine\Common\Persistence\ConnectionRegistry; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\Doctrine\RegistryInterface; use Symfony\Component\Messenger\Transport\Doctrine\Connection; use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransport; use Symfony\Component\Messenger\Transport\Doctrine\DoctrineTransportFactory; @@ -23,7 +23,7 @@ class DoctrineTransportFactoryTest extends TestCase public function testSupports() { $factory = new DoctrineTransportFactory( - $this->getMockBuilder(RegistryInterface::class)->getMock() + $this->getMockBuilder(ConnectionRegistry::class)->getMock() ); $this->assertTrue($factory->supports('doctrine://default', [])); @@ -35,7 +35,7 @@ class DoctrineTransportFactoryTest extends TestCase $connection = $this->getMockBuilder(\Doctrine\DBAL\Connection::class) ->disableOriginalConstructor() ->getMock(); - $registry = $this->getMockBuilder(RegistryInterface::class)->getMock(); + $registry = $this->getMockBuilder(ConnectionRegistry::class)->getMock(); $registry->expects($this->once()) ->method('getConnection') ->willReturn($connection); @@ -55,7 +55,7 @@ class DoctrineTransportFactoryTest extends TestCase */ public function testCreateTransportMustThrowAnExceptionIfManagerIsNotFound() { - $registry = $this->getMockBuilder(RegistryInterface::class)->getMock(); + $registry = $this->getMockBuilder(ConnectionRegistry::class)->getMock(); $registry->expects($this->once()) ->method('getConnection') ->willReturnCallback(function () { diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php index 54ca901eb2..9d7676353d 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/DoctrineTransportFactory.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Messenger\Transport\Doctrine; -use Symfony\Bridge\Doctrine\RegistryInterface; +use Doctrine\Common\Persistence\ConnectionRegistry; use Symfony\Component\Messenger\Exception\TransportException; use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; use Symfony\Component\Messenger\Transport\TransportFactoryInterface; @@ -24,7 +24,7 @@ class DoctrineTransportFactory implements TransportFactoryInterface { private $registry; - public function __construct(RegistryInterface $registry) + public function __construct(ConnectionRegistry $registry) { $this->registry = $registry; } diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index 77a0e456af..9d8bd56ad9 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -25,7 +25,6 @@ "symfony/console": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4.19|^4.1.8|^5.0", "symfony/error-catcher": "^4.4|^5.0", - "symfony/doctrine-bridge": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3|^5.0", "symfony/http-kernel": "^4.4|^5.0", "symfony/process": "^3.4|^4.0|^5.0", From cd0341e69bd7234da800320357c4daa70a81863d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 27 Jun 2019 08:20:20 +0200 Subject: [PATCH 30/33] [FrameworkBundle] Allow to use the BrowserKit assertions with Panther and API Platform's test client --- .../Test/BrowserKitAssertionsTrait.php | 159 +++++++++++++ .../Test/DomCrawlerAssertionsTrait.php | 94 ++++++++ .../Test/WebTestAssertionsTrait.php | 211 +----------------- .../CrawlerSelectorAttributeValueSame.php | 2 +- 4 files changed, 256 insertions(+), 210 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php new file mode 100644 index 0000000000..086d83e8ad --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Test/BrowserKitAssertionsTrait.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint; + +/** + * Ideas borrowed from Laravel Dusk's assertions. + * + * @see https://laravel.com/docs/5.7/dusk#available-assertions + */ +trait BrowserKitAssertionsTrait +{ + public static function assertResponseIsSuccessful(string $message = ''): void + { + self::assertThat(self::getResponse(), new ResponseConstraint\ResponseIsSuccessful(), $message); + } + + public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void + { + self::assertThat(self::getResponse(), new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message); + } + + public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void + { + $constraint = new ResponseConstraint\ResponseIsRedirected(); + if ($expectedLocation) { + $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation)); + } + if ($expectedCode) { + $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode)); + } + + self::assertThat(self::getResponse(), $constraint, $message); + } + + public static function assertResponseHasHeader(string $headerName, string $message = ''): void + { + self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasHeader($headerName), $message); + } + + public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void + { + self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message); + } + + public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message); + } + + public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message); + } + + public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message); + } + + public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message); + } + + public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThat(self::getResponse(), LogicalAnd::fromConstraints( + new ResponseConstraint\ResponseHasCookie($name, $path, $domain), + new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain) + ), $message); + } + + public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThat(self::getClient(), new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message); + } + + public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThat(self::getClient(), new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); + } + + public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void + { + self::assertThat(self::getClient(), LogicalAnd::fromConstraints( + new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), + new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain) + ), $message); + } + + public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message); + } + + public static function assertRouteSame($expectedRoute, array $parameters = [], string $message = ''): void + { + $constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute); + $constraints = []; + foreach ($parameters as $key => $value) { + $constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value); + } + if ($constraints) { + $constraint = LogicalAnd::fromConstraints($constraint, ...$constraints); + } + + self::assertThat(self::getRequest(), $constraint, $message); + } + + private static function getClient(AbstractBrowser $newClient = null): ?AbstractBrowser + { + static $client; + + if (0 < \func_num_args()) { + return $client = $newClient; + } + + if (!$client instanceof AbstractBrowser) { + static::fail(sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__)); + } + + return $client; + } + + private static function getResponse(): Response + { + if (!$response = self::getClient()->getResponse()) { + static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?'); + } + + return $response; + } + + private static function getRequest(): Request + { + if (!$request = self::getClient()->getRequest()) { + static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?'); + } + + return $request; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php new file mode 100644 index 0000000000..465c265f69 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Test/DomCrawlerAssertionsTrait.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Test; + +use PHPUnit\Framework\Constraint\LogicalAnd; +use PHPUnit\Framework\Constraint\LogicalNot; +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint; + +/** + * Ideas borrowed from Laravel Dusk's assertions. + * + * @see https://laravel.com/docs/5.7/dusk#available-assertions + */ +trait DomCrawlerAssertionsTrait +{ + public static function assertSelectorExists(string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message); + } + + public static function assertSelectorNotExists(string $selector, string $message = ''): void + { + self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); + } + + public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text) + ), $message); + } + + public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text) + ), $message); + } + + public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists($selector), + new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)) + ), $message); + } + + public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void + { + self::assertSelectorTextSame('title', $expectedTitle, $message); + } + + public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void + { + self::assertSelectorTextContains('title', $expectedTitle, $message); + } + + public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) + ), $message); + } + + public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void + { + self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( + new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), + new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)) + ), $message); + } + + private static function getCrawler(): Crawler + { + if (!$crawler = self::getClient()->getCrawler()) { + static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?'); + } + + return $crawler; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestAssertionsTrait.php b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestAssertionsTrait.php index f820e8d080..197f2131bd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Test/WebTestAssertionsTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Test/WebTestAssertionsTrait.php @@ -11,16 +11,6 @@ namespace Symfony\Bundle\FrameworkBundle\Test; -use PHPUnit\Framework\Constraint\LogicalAnd; -use PHPUnit\Framework\Constraint\LogicalNot; -use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Component\BrowserKit\Test\Constraint as BrowserKitConstraint; -use Symfony\Component\DomCrawler\Crawler; -use Symfony\Component\DomCrawler\Test\Constraint as DomCrawlerConstraint; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint; - /** * Ideas borrowed from Laravel Dusk's assertions. * @@ -28,203 +18,6 @@ use Symfony\Component\HttpFoundation\Test\Constraint as ResponseConstraint; */ trait WebTestAssertionsTrait { - public static function assertResponseIsSuccessful(string $message = ''): void - { - self::assertThat(self::getResponse(), new ResponseConstraint\ResponseIsSuccessful(), $message); - } - - public static function assertResponseStatusCodeSame(int $expectedCode, string $message = ''): void - { - self::assertThat(self::getResponse(), new ResponseConstraint\ResponseStatusCodeSame($expectedCode), $message); - } - - public static function assertResponseRedirects(string $expectedLocation = null, int $expectedCode = null, string $message = ''): void - { - $constraint = new ResponseConstraint\ResponseIsRedirected(); - if ($expectedLocation) { - $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseHeaderSame('Location', $expectedLocation)); - } - if ($expectedCode) { - $constraint = LogicalAnd::fromConstraints($constraint, new ResponseConstraint\ResponseStatusCodeSame($expectedCode)); - } - - self::assertThat(self::getResponse(), $constraint, $message); - } - - public static function assertResponseHasHeader(string $headerName, string $message = ''): void - { - self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasHeader($headerName), $message); - } - - public static function assertResponseNotHasHeader(string $headerName, string $message = ''): void - { - self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasHeader($headerName)), $message); - } - - public static function assertResponseHeaderSame(string $headerName, string $expectedValue, string $message = ''): void - { - self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue), $message); - } - - public static function assertResponseHeaderNotSame(string $headerName, string $expectedValue, string $message = ''): void - { - self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHeaderSame($headerName, $expectedValue)), $message); - } - - public static function assertResponseHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void - { - self::assertThat(self::getResponse(), new ResponseConstraint\ResponseHasCookie($name, $path, $domain), $message); - } - - public static function assertResponseNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void - { - self::assertThat(self::getResponse(), new LogicalNot(new ResponseConstraint\ResponseHasCookie($name, $path, $domain)), $message); - } - - public static function assertResponseCookieValueSame(string $name, string $expectedValue, string $path = '/', string $domain = null, string $message = ''): void - { - self::assertThat(self::getResponse(), LogicalAnd::fromConstraints( - new ResponseConstraint\ResponseHasCookie($name, $path, $domain), - new ResponseConstraint\ResponseCookieValueSame($name, $expectedValue, $path, $domain) - ), $message); - } - - public static function assertBrowserHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void - { - self::assertThat(self::getClient(), new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), $message); - } - - public static function assertBrowserNotHasCookie(string $name, string $path = '/', string $domain = null, string $message = ''): void - { - self::assertThat(self::getClient(), new LogicalNot(new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain)), $message); - } - - public static function assertBrowserCookieValueSame(string $name, string $expectedValue, bool $raw = false, string $path = '/', string $domain = null, string $message = ''): void - { - self::assertThat(self::getClient(), LogicalAnd::fromConstraints( - new BrowserKitConstraint\BrowserHasCookie($name, $path, $domain), - new BrowserKitConstraint\BrowserCookieValueSame($name, $expectedValue, $raw, $path, $domain) - ), $message); - } - - public static function assertSelectorExists(string $selector, string $message = ''): void - { - self::assertThat(self::getCrawler(), new DomCrawlerConstraint\CrawlerSelectorExists($selector), $message); - } - - public static function assertSelectorNotExists(string $selector, string $message = ''): void - { - self::assertThat(self::getCrawler(), new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorExists($selector)), $message); - } - - public static function assertSelectorTextContains(string $selector, string $text, string $message = ''): void - { - self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), - new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text) - ), $message); - } - - public static function assertSelectorTextSame(string $selector, string $text, string $message = ''): void - { - self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), - new DomCrawlerConstraint\CrawlerSelectorTextSame($selector, $text) - ), $message); - } - - public static function assertSelectorTextNotContains(string $selector, string $text, string $message = ''): void - { - self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists($selector), - new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorTextContains($selector, $text)) - ), $message); - } - - public static function assertPageTitleSame(string $expectedTitle, string $message = ''): void - { - self::assertSelectorTextSame('title', $expectedTitle, $message); - } - - public static function assertPageTitleContains(string $expectedTitle, string $message = ''): void - { - self::assertSelectorTextContains('title', $expectedTitle, $message); - } - - public static function assertInputValueSame(string $fieldName, string $expectedValue, string $message = ''): void - { - self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), - new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue) - ), $message); - } - - public static function assertInputValueNotSame(string $fieldName, string $expectedValue, string $message = ''): void - { - self::assertThat(self::getCrawler(), LogicalAnd::fromConstraints( - new DomCrawlerConstraint\CrawlerSelectorExists("input[name=\"$fieldName\"]"), - new LogicalNot(new DomCrawlerConstraint\CrawlerSelectorAttributeValueSame("input[name=\"$fieldName\"]", 'value', $expectedValue)) - ), $message); - } - - public static function assertRequestAttributeValueSame(string $name, string $expectedValue, string $message = ''): void - { - self::assertThat(self::getRequest(), new ResponseConstraint\RequestAttributeValueSame($name, $expectedValue), $message); - } - - public static function assertRouteSame($expectedRoute, array $parameters = [], string $message = ''): void - { - $constraint = new ResponseConstraint\RequestAttributeValueSame('_route', $expectedRoute); - $constraints = []; - foreach ($parameters as $key => $value) { - $constraints[] = new ResponseConstraint\RequestAttributeValueSame($key, $value); - } - if ($constraints) { - $constraint = LogicalAnd::fromConstraints($constraint, ...$constraints); - } - - self::assertThat(self::getRequest(), $constraint, $message); - } - - private static function getClient(KernelBrowser $newClient = null): ?KernelBrowser - { - static $client; - - if (0 < \func_num_args()) { - return $client = $newClient; - } - - if (!$client instanceof KernelBrowser) { - static::fail(sprintf('A client must be set to make assertions on it. Did you forget to call "%s::createClient()"?', __CLASS__)); - } - - return $client; - } - - private static function getCrawler(): Crawler - { - if (!$crawler = self::getClient()->getCrawler()) { - static::fail('A client must have a crawler to make assertions. Did you forget to make an HTTP request?'); - } - - return $crawler; - } - - private static function getResponse(): Response - { - if (!$response = self::getClient()->getResponse()) { - static::fail('A client must have an HTTP Response to make assertions. Did you forget to make an HTTP request?'); - } - - return $response; - } - - private static function getRequest(): Request - { - if (!$request = self::getClient()->getRequest()) { - static::fail('A client must have an HTTP Request to make assertions. Did you forget to make an HTTP request?'); - } - - return $request; - } + use BrowserKitAssertionsTrait; + use DomCrawlerAssertionsTrait; } diff --git a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php index 962b6bf0b1..7008779e14 100644 --- a/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php +++ b/src/Symfony/Component/DomCrawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php @@ -47,7 +47,7 @@ final class CrawlerSelectorAttributeValueSame extends Constraint return false; } - return $this->expectedText === trim($crawler->getNode(0)->getAttribute($this->attribute)); + return $this->expectedText === trim($crawler->attr($this->attribute)); } /** From 60d997df75c6eb018bf88fe0b37a77f31ec1f157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 3 Jul 2019 22:32:34 +0200 Subject: [PATCH 31/33] [HttpFoundation] Accept must take the lead for Request::getPreferredFormat() --- .../Component/HttpFoundation/Request.php | 21 ++++++++++++------- .../HttpFoundation/Tests/RequestTest.php | 8 +++---- .../HttpFoundation/Tests/ResponseTest.php | 1 + 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index a950b0a159..4c52605208 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1347,6 +1347,8 @@ class Request * * _format request attribute * * $default * + * @see getPreferredFormat + * * @param string|null $default The default format * * @return string|null The request format @@ -1563,22 +1565,27 @@ class Request return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat + * * the values of the Accept HTTP header + * * the content type of the body of the request. + */ public function getPreferredFormat(?string $default = 'html'): ?string { if (null !== $this->preferredFormat) { return $this->preferredFormat; } - $this->preferredFormat = $this->getRequestFormat($this->getContentType()); - - if (null === $this->preferredFormat) { - foreach ($this->getAcceptableContentTypes() as $contentType) { - if (null !== $this->preferredFormat = $this->getFormat($contentType)) { - break; - } + $preferredFormat = null; + foreach ($this->getAcceptableContentTypes() as $contentType) { + if ($preferredFormat = $this->getFormat($contentType)) { + break; } } + $this->preferredFormat = $this->getRequestFormat($preferredFormat ?: $this->getContentType()); + return $this->preferredFormat ?: $default; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 6573da0785..500d590f78 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -407,14 +407,14 @@ class RequestTest extends TestCase $this->assertSame('json', $request->getPreferredFormat('json')); $request->setRequestFormat('atom'); - $request->headers->set('Content-Type', 'application/json'); - $request->headers->set('Accept', 'application/xml'); + $request->headers->set('Accept', 'application/ld+json'); + $request->headers->set('Content-Type', 'application/merge-patch+json'); $this->assertSame('atom', $request->getPreferredFormat()); $request = new Request(); - $request->headers->set('Content-Type', 'application/json'); $request->headers->set('Accept', 'application/xml'); - $this->assertSame('json', $request->getPreferredFormat()); + $request->headers->set('Content-Type', 'application/json'); + $this->assertSame('xml', $request->getPreferredFormat()); $request = new Request(); $request->headers->set('Accept', 'application/xml'); diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php index 7856a77c03..550d7ce0b0 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -504,6 +504,7 @@ class ResponseTest extends ResponseTestCase $response = new Response('foo'); $request = Request::create('/'); $request->setRequestFormat('json'); + $request->headers->remove('accept'); $response->prepare($request); From 5ff45bac6669c3269f460c5490d0c8f754a5fcf2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 4 Jul 2019 10:44:43 +0200 Subject: [PATCH 32/33] [FrameworkBundle] reset cache pools between requests --- .../FrameworkBundle/Resources/config/cache.xml | 14 +++++++------- .../DependencyInjection/FrameworkExtensionTest.php | 5 ----- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index 36db49a497..a7aaaec7c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -8,7 +8,7 @@ - + @@ -29,7 +29,7 @@ - + 0 @@ -39,7 +39,7 @@ - + 0 @@ -50,7 +50,7 @@ - + @@ -61,7 +61,7 @@ - + 0 @@ -72,14 +72,14 @@ - + 0 - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 51b4ff1524..73c33a3f50 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -19,7 +19,6 @@ use Symfony\Bundle\FullStack; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ApcuAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\Adapter\ChainAdapter; use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\ProxyAdapter; @@ -1244,10 +1243,6 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertSame(DoctrineAdapter::class, $parentDefinition->getClass()); break; case 'cache.app': - if (ChainAdapter::class === $parentDefinition->getClass()) { - break; - } - // no break case 'cache.adapter.filesystem': $this->assertSame(FilesystemAdapter::class, $parentDefinition->getClass()); break; From b06d0003a3ab73d7e583cfb687d43e8e5a0c0870 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 4 Jul 2019 11:24:58 +0200 Subject: [PATCH 33/33] [DI] fix processing of regular parameter bags by MergeExtensionConfigurationPass --- .../Compiler/MergeExtensionConfigurationPass.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php index 63f8013c31..caa1fd2251 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php @@ -186,6 +186,10 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder $bag = $this->getParameterBag(); $value = $bag->resolveValue($value); + if (!$bag instanceof EnvPlaceholderParameterBag) { + return parent::resolveEnvPlaceholders($value, $format, $usedEnvs); + } + foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { if (false === strpos($env, ':')) { continue;