diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index ea426c377a..8ab5ee7a4b 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -96,7 +96,19 @@ class DeprecationErrorHandler return \call_user_func(self::getPhpUnitErrorHandler(), $type, $msg, $file, $line, $context); } - $deprecations[] = [error_reporting(), $msg, $file]; + $trace = debug_backtrace(); + $filesStack = []; + foreach ($trace as $line) { + if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { + continue; + } + + if (isset($line['file'])) { + $filesStack[] = $line['file']; + } + } + + $deprecations[] = [error_reporting(), $msg, $file, $filesStack]; return null; }); diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 6263267fef..55909ee6cf 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -44,6 +44,8 @@ class Deprecation */ private static $internalPaths = []; + private $originalFilesStack; + /** * @param string $message * @param string $file @@ -64,6 +66,7 @@ class Deprecation $this->message = $parsedMsg['deprecation']; $this->originClass = $parsedMsg['class']; $this->originMethod = $parsedMsg['method']; + $this->originalFilesStack = $parsedMsg['files_stack']; // If the deprecation has been triggered via // \Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait::endTest() // then we need to use the serialized information to determine @@ -162,6 +165,24 @@ class Deprecation return false !== strpos($this->triggeringFile, \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR.'phpunit'.\DIRECTORY_SEPARATOR); } + private function getOriginalFilesStack(): array + { + if (null === $this->originalFilesStack) { + $this->originalFilesStack = []; + foreach ($this->trace as $line) { + if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { + continue; + } + if (!isset($line['file'])) { + continue; + } + $this->originalFilesStack[] = $line['file']; + } + } + + return $this->originalFilesStack; + } + /** * Tells whether both the calling package and the called package are vendor * packages. @@ -178,14 +199,8 @@ class Deprecation return self::TYPE_UNDETERMINED; } $erroringFile = $erroringPackage = null; - foreach ($this->trace as $line) { - if (\in_array($line['function'], ['require', 'require_once', 'include', 'include_once'], true)) { - continue; - } - if (!isset($line['file'])) { - continue; - } - $file = $line['file']; + + foreach ($this->getOriginalFilesStack() as $file) { if ('-' === $file || 'Standard input code' === $file || !realpath($file)) { continue; } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index d59b2d9337..af3f688d5d 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -11,12 +11,32 @@ namespace Symfony\Bridge\PhpUnit\Tests\DeprecationErrorHandler; +use Composer\Autoload\ClassLoader; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler; use Symfony\Bridge\PhpUnit\DeprecationErrorHandler\Deprecation; +use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5; class DeprecationTest extends TestCase { + public static function setUpBeforeClass(): void + { + $vendorDir = self::getVendorDir(); + + mkdir($vendorDir.'/myfakevendor/myfakepackage1', 0777, true); + mkdir($vendorDir.'/myfakevendor/myfakepackage2'); + touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php'); + touch($vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php'); + touch($vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php'); + } + + private static function getVendorDir(): string + { + $reflection = new \ReflectionClass(ClassLoader::class); + + return \dirname($reflection->getFileName(), 2); + } + public function testItCanDetermineTheClassWhereTheDeprecationHappened() { $deprecation = new Deprecation('💩', $this->debugBacktrace(), __FILE__); @@ -118,12 +138,135 @@ class DeprecationTest extends TestCase $this->assertTrue($deprecation->isMuted()); } + public function providerGetTypeDetectsSelf(): array + { + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname(\dirname($r->getFileName())); + if (file_exists($v.'/composer/installed.json')) { + $loader = require $v.'/autoload.php'; + $reflection = new \ReflectionClass($loader); + $prop = $reflection->getProperty('prefixDirsPsr4'); + $prop->setAccessible(true); + $currentValue = $prop->getValue($loader); + $currentValue['Symfony\\Bridge\\PhpUnit\\'] = [realpath(__DIR__.'/../..')]; + $prop->setValue($loader, $currentValue); + } + } + } + + return [ + 'not_from_vendors_file' => [Deprecation::TYPE_SELF, '', 'MyClass1', ''], + 'nonexistent_file' => [Deprecation::TYPE_UNDETERMINED, '', 'MyClass1', 'dummy_vendor_path'], + 'serialized_trace_with_nonexistent_triggering_file' => [ + Deprecation::TYPE_UNDETERMINED, + serialize([ + 'class' => '', + 'method' => '', + 'deprecation' => '', + 'triggering_file' => 'dummy_vendor_path', + 'files_stack' => [], + ]), + SymfonyTestsListenerForV5::class, + '', + ], + ]; + } + + /** + * @dataProvider providerGetTypeDetectsSelf + */ + public function testGetTypeDetectsSelf(string $expectedType, string $message, string $traceClass, string $file): void + { + $trace = [ + ['class' => 'MyClass1', 'function' => 'myMethod'], + ['class' => $traceClass, 'function' => 'myMethod'], + ]; + $deprecation = new Deprecation($message, $trace, $file); + $this->assertEquals($expectedType, $deprecation->getType()); + } + + public function providerGetTypeUsesRightTrace(): array + { + $vendorDir = self::getVendorDir(); + + return [ + 'no_file_in_stack' => [Deprecation::TYPE_DIRECT, '', [['function' => 'myfunc1'], ['function' => 'myfunc2']]], + 'files_in_stack_from_various_packages' => [ + Deprecation::TYPE_INDIRECT, + '', + [ + ['function' => 'myfunc1', 'file' => $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php'], + ['function' => 'myfunc2', 'file' => $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php'], + ], + ], + 'serialized_stack_files_from_same_package' => [ + Deprecation::TYPE_DIRECT, + serialize([ + 'deprecation' => 'My deprecation message', + 'class' => 'MyClass', + 'method' => 'myMethod', + 'files_stack' => [ + $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php', + $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile2.php', + ], + ]), + [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], + ], + 'serialized_stack_files_from_various_packages' => [ + Deprecation::TYPE_INDIRECT, + serialize([ + 'deprecation' => 'My deprecation message', + 'class' => 'MyClass', + 'method' => 'myMethod', + 'files_stack' => [ + $vendorDir.'/myfakevendor/myfakepackage1/MyFakeFile1.php', + $vendorDir.'/myfakevendor/myfakepackage2/MyFakeFile.php', + ], + ]), + [['function' => 'myfunc1'], ['class' => SymfonyTestsListenerForV5::class, 'method' => 'mymethod']], + ], + ]; + } + + /** + * @dataProvider providerGetTypeUsesRightTrace + */ + public function testGetTypeUsesRightTrace(string $expectedType, string $message, array $trace): void + { + $deprecation = new Deprecation( + $message, + $trace, + self::getVendorDir().'/myfakevendor/myfakepackage2/MyFakeFile.php' + ); + $this->assertEquals($expectedType, $deprecation->getType()); + } + /** * This method is here to simulate the extra level from the piece of code - * triggering an error to the error handler + * triggering an error to the error handler. */ public function debugBacktrace(): array { return debug_backtrace(); } + + private static function removeDir($dir): void + { + $files = glob($dir.'/*'); + foreach ($files as $file) { + if (is_file($file)) { + unlink($file); + } else { + self::removeDir($file); + } + } + rmdir($dir); + } + + public static function tearDownAfterClass(): void + { + self::removeDir(self::getVendorDir().'/myfakevendor'); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 65cf6e5599..1632956f7e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1279,7 +1279,7 @@ class Configuration implements ConfigurationInterface if (!\is_array($config)) { return []; } - if (!isset($config['host'])) { + if (!isset($config['host'], $config['value']) || \count($config) > 2) { return $config; } @@ -1388,7 +1388,7 @@ class Configuration implements ConfigurationInterface if (!\is_array($config)) { return []; } - if (!isset($config['key'])) { + if (!isset($config['key'], $config['value']) || \count($config) > 2) { return $config; } @@ -1418,7 +1418,7 @@ class Configuration implements ConfigurationInterface if (!\is_array($config)) { return []; } - if (!isset($config['host'])) { + if (!isset($config['host'], $config['value']) || \count($config) > 2) { return $config; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_xml_key.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_xml_key.php new file mode 100644 index 0000000000..64778c6156 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/http_client_xml_key.php @@ -0,0 +1,22 @@ +loadFromExtension('framework', [ + 'http_client' => [ + 'default_options' => [ + 'resolve' => [ + 'host' => '127.0.0.1', + ], + ], + 'scoped_clients' => [ + 'foo' => [ + 'base_uri' => 'http://example.com', + 'query' => [ + 'key' => 'foo', + ], + 'resolve' => [ + 'host' => '127.0.0.1', + ], + ], + ], + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_xml_key.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_xml_key.xml new file mode 100644 index 0000000000..95ef0737f8 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/http_client_xml_key.xml @@ -0,0 +1,19 @@ + + + + + + + 127.0.0.1 + + + foo + 127.0.0.1 + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_xml_key.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_xml_key.yml new file mode 100644 index 0000000000..dc87555a90 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/http_client_xml_key.yml @@ -0,0 +1,12 @@ +framework: + http_client: + default_options: + resolve: + host: 127.0.0.1 + scoped_clients: + foo: + base_uri: http://example.com + query: + key: foo + resolve: + host: 127.0.0.1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 7ac19bcd18..86b3fae486 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1360,6 +1360,21 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)); } + public function testHttpClientWithQueryParameterKey() + { + $container = $this->createContainerFromFile('http_client_xml_key'); + + $expected = [ + 'key' => 'foo', + ]; + $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)['query']); + + $expected = [ + 'host' => '127.0.0.1', + ]; + $this->assertSame($expected, $container->getDefinition('foo')->getArgument(2)['resolve']); + } + public function testHttpClientFullDefaultOptions() { $container = $this->createContainerFromFile('http_client_full_default_options'); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php index 9b3760b497..1a2c829854 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/PriorityTaggedServiceTrait.php @@ -54,78 +54,81 @@ trait PriorityTaggedServiceTrait foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { $class = $r = null; - $priority = 0; - if (isset($attributes[0]['priority'])) { - $priority = $attributes[0]['priority']; - } elseif ($defaultPriorityMethod) { - $class = $container->getDefinition($serviceId)->getClass(); - $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (($r = $container->getReflectionClass($class)) && $r->hasMethod($defaultPriorityMethod)) { - if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); - } + $defaultPriority = null; + $defaultIndex = null; - if (!$rm->isPublic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); - } + foreach ($attributes as $attribute) { + $index = $priority = null; - $priority = $rm->invoke(null); + if (isset($attribute['priority'])) { + $priority = $attribute['priority']; + } elseif (null === $defaultPriority && $defaultPriorityMethod) { + $class = $container->getDefinition($serviceId)->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (!\is_int($priority)) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId)); + if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultPriorityMethod)) { + if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); + } + + if (!$rm->isPublic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId)); + } + + $defaultPriority = $rm->invoke(null); + + if (!\is_int($defaultPriority)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId)); + } } } - } - if (null === $indexAttribute && !$needsIndexes) { - $services[$priority][] = new Reference($serviceId); + $priority = $priority ?? $defaultPriority ?? 0; - continue; - } + if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { + $index = $attribute[$indexAttribute]; + } elseif (null === $defaultIndex && null === $indexAttribute && !$needsIndexes) { + // With partially associative array, insertion to get next key is simpler. + $services[$priority][] = null; + end($services[$priority]); + $defaultIndex = key($services[$priority]); + } elseif (null === $defaultIndex && $defaultIndexMethod) { + $class = $container->getDefinition($serviceId)->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; - if (!$class) { - $class = $container->getDefinition($serviceId)->getClass(); - $class = $container->getParameterBag()->resolveValue($class) ?: null; - } + if (($r = ($r ?? $container->getReflectionClass($class))) && $r->hasMethod($defaultIndexMethod)) { + if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } - if (null !== $indexAttribute && isset($attributes[0][$indexAttribute])) { - $services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]); + if (!$rm->isPublic()) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + } - continue; - } + $defaultIndex = $rm->invoke(null); - if (!$r && !$r = $container->getReflectionClass($class)) { - throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId)); - } + if (!\is_string($defaultIndex)) { + throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($defaultIndex), $tagName, $serviceId, $indexAttribute)); + } + } - $class = $r->name; - - if (!$r->hasMethod($defaultIndexMethod)) { - if ($needsIndexes) { - $services[$priority][$serviceId] = new TypedReference($serviceId, $class); - - continue; + $defaultIndex = $defaultIndex ?? $serviceId; } - throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); + $index = $index ?? $defaultIndex; + + $reference = null; + if (!$class || 'stdClass' === $class) { + $reference = new Reference($serviceId); + } elseif ($index === $serviceId) { + $reference = new TypedReference($serviceId, $class); + } else { + $reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, \is_string($index) ? $index : null); + } + + $services[$priority][$index] = $reference; } - - if (!($rm = $r->getMethod($defaultIndexMethod))->isStatic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } - - if (!$rm->isPublic()) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute)); - } - - $key = $rm->invoke(null); - - if (!\is_string($key)) { - throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($key), $tagName, $serviceId, $indexAttribute)); - } - - $services[$priority][$key] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $key); } if ($services) { diff --git a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php index bd25e3f0ec..987605f98f 100644 --- a/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php +++ b/src/Symfony/Component/DependencyInjection/EnvVarProcessor.php @@ -126,9 +126,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if (false !== $i || 'string' !== $prefix) { - if (null === $env = $getEnv($name)) { - return null; - } + $env = $getEnv($name); } elseif (isset($_ENV[$name])) { $env = $_ENV[$name]; } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { @@ -173,12 +171,18 @@ class EnvVarProcessor implements EnvVarProcessorInterface throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name)); } - if (null === $env = $this->container->getParameter("env($name)")) { - return null; - } + $env = $this->container->getParameter("env($name)"); } } + if (null === $env) { + if (!isset($this->getProvidedTypes()[$prefix])) { + throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); + } + + return null; + } + if (!is_scalar($env)) { throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to %s.', $name, $prefix)); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php index 7d4f01a265..f6c255484a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php @@ -314,6 +314,32 @@ class IntegrationTest extends TestCase $this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param); } + public function testTaggedIteratorWithMultipleIndexAttribute() + { + $container = new ContainerBuilder(); + $container->register(BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar']) + ->addTag('foo_bar', ['foo' => 'bar_duplicate']) + ; + $container->register(FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar') + ->addTag('foo_bar') + ; + $container->register(FooBarTaggedClass::class) + ->addArgument(new TaggedIteratorArgument('foo_bar', 'foo')) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get(FooBarTaggedClass::class); + + $param = iterator_to_array($s->getParam()->getIterator()); + $this->assertSame(['bar' => $container->get(BarTagClass::class), 'bar_duplicate' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $param); + } + public function testTaggedServiceWithDefaultPriorityMethod() { $container = new ContainerBuilder(); @@ -350,7 +376,7 @@ class IntegrationTest extends TestCase ->addTag('foo_bar') ; $container->register('foo_bar_tagged', FooBarTaggedClass::class) - ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo'))) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true))) ->setPublic(true) ; @@ -369,6 +395,40 @@ class IntegrationTest extends TestCase $this->assertSame(['bar' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same); } + public function testTaggedServiceLocatorWithMultipleIndexAttribute() + { + $container = new ContainerBuilder(); + $container->register('bar_tag', BarTagClass::class) + ->setPublic(true) + ->addTag('foo_bar', ['foo' => 'bar']) + ->addTag('foo_bar', ['foo' => 'bar_duplicate']) + ; + $container->register('foo_tag', FooTagClass::class) + ->setPublic(true) + ->addTag('foo_bar') + ->addTag('foo_bar') + ; + $container->register('foo_bar_tagged', FooBarTaggedClass::class) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', null, true))) + ->setPublic(true) + ; + + $container->compile(); + + $s = $container->get('foo_bar_tagged'); + + /** @var ServiceLocator $serviceLocator */ + $serviceLocator = $s->getParam(); + $this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator))); + + $same = [ + 'bar' => $serviceLocator->get('bar'), + 'bar_duplicate' => $serviceLocator->get('bar_duplicate'), + 'foo_tag_class' => $serviceLocator->get('foo_tag_class'), + ]; + $this->assertSame(['bar' => $container->get('bar_tag'), 'bar_duplicate' => $container->get('bar_tag'), 'foo_tag_class' => $container->get('foo_tag')], $same); + } + public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod() { $container = new ContainerBuilder(); @@ -381,7 +441,7 @@ class IntegrationTest extends TestCase ->addTag('foo_bar', ['foo' => 'foo']) ; $container->register('foo_bar_tagged', FooBarTaggedClass::class) - ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar'))) + ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar', true))) ->setPublic(true) ; diff --git a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php index df329b4f7e..313bce5973 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/EnvVarProcessorTest.php @@ -9,6 +9,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; use Symfony\Component\DependencyInjection\EnvVarProcessor; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; class EnvVarProcessorTest extends TestCase { @@ -595,4 +596,17 @@ CSV; $this->assertSame(2, $index); } + + public function testGetEnvInvalidPrefixWithDefault() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Unsupported env var prefix'); + $processor = new EnvVarProcessor(new Container()); + + $processor->getEnv('unknown', 'default::FAKE', function ($name) { + $this->assertSame('default::FAKE', $name); + + return null; + }); + } } diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php index b6b3bef07e..79201f7f8a 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php @@ -16,7 +16,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Mailer\Exception\TransportException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\SmtpEnvelope; -use Symfony\Component\Mailer\Transport\Http\AbstractHttpTransport; +use Symfony\Component\Mailer\Transport\AbstractHttpTransport; use Symfony\Component\Mime\Address; use Symfony\Contracts\HttpClient\HttpClientInterface; diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index c40899c0d9..f401198313 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -49,7 +49,9 @@ class MandrillHttpTransport extends AbstractHttpTransport $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send-raw.json', [ 'json' => [ 'key' => $this->key, - 'to' => $this->getRecipients($envelope), + 'to' => array_map(function (Address $recipient): string { + return $recipient->getAddress(); + }, $envelope->getRecipients()), 'from_email' => $envelope->getSender()->toString(), 'raw_message' => $message->toString(), ], @@ -73,14 +75,4 @@ class MandrillHttpTransport extends AbstractHttpTransport { return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : ''); } - - /** - * @return string[] - */ - private function getRecipients(Envelope $envelope): array - { - return array_map(function (Address $recipient): string { - return $recipient->getAddress(); - }, $envelope->getRecipients()); - } }