Skip to content

Commit e172491

Browse files
Merge branch '7.0' into 7.1
* 7.0: Fix RequestPayloadValueResolver handling error with no ExpectedTypes [Mime] Fix serializing uninitialized RawMessage::$message to null [Notifer][Smsapi] Set messageId of SentMessage [DX] Use Symfony "dark-mode"-responsive logo in README support lazy evaluated exception messages with Xdebug 3 Provide more precise phpdoc for FileLocatorInterface::locate [DependencyInjection] #[Autowire] attribute should have precedence over bindings
2 parents 5091cd5 + 0071107 commit e172491

File tree

16 files changed

+188
-19
lines changed

16 files changed

+188
-19
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p align="center"><a href="https://symfony.com" target="_blank">
2-
<img src="https://symfony.com/logos/symfony_black_02.svg">
2+
<img src="https://symfony.com/logos/symfony_dynamic_01.svg" alt="Symfony Logo">
33
</a></p>
44

55
[Symfony][1] is a **PHP framework** for web and console applications and a set

src/Symfony/Bridge/Twig/Tests/Mime/TemplatedEmailTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ public function testSymfonySerialize()
9999
}
100100
]
101101
},
102-
"body": null,
103-
"message": null
102+
"body": null
104103
}
105104
EOF;
106105

src/Symfony/Bridge/Twig/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"symfony/security-core": "^6.4|^7.0",
4444
"symfony/security-csrf": "^6.4|^7.0",
4545
"symfony/security-http": "^6.4|^7.0",
46-
"symfony/serializer": "^6.4|^7.0",
46+
"symfony/serializer": "^6.4.3|^7.0.3",
4747
"symfony/stopwatch": "^6.4|^7.0",
4848
"symfony/console": "^6.4|^7.0",
4949
"symfony/expression-language": "^6.4|^7.0",

src/Symfony/Component/Config/FileLocator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public function __construct(string|array $paths = [])
3030
$this->paths = (array) $paths;
3131
}
3232

33+
/**
34+
* @return string|string[]
35+
*
36+
* @psalm-return ($first is true ? string : string[])
37+
*/
3338
public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array
3439
{
3540
if ('' === $name) {

src/Symfony/Component/Config/FileLocatorInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ interface FileLocatorInterface
2525
* @param string|null $currentPath The current path
2626
* @param bool $first Whether to return the first occurrence or an array of filenames
2727
*
28-
* @return string|array The full path to the file or an array of file paths
28+
* @return string|string[] The full path to the file or an array of file paths
2929
*
3030
* @throws \InvalidArgumentException If $name is empty
3131
* @throws FileLocatorFileNotFoundException If a file is not found
32+
*
33+
* @psalm-return ($first is true ? string : string[])
3234
*/
3335
public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array;
3436
}

src/Symfony/Component/DependencyInjection/Compiler/ResolveBindingsPass.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
1515
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1616
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
17+
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1718
use Symfony\Component\DependencyInjection\Attribute\Target;
1819
use Symfony\Component\DependencyInjection\ContainerBuilder;
1920
use Symfony\Component\DependencyInjection\Definition;
@@ -184,6 +185,13 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
184185
if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) {
185186
continue;
186187
}
188+
if (
189+
$value->isAutowired()
190+
&& !$value->hasTag('container.ignore_attributes')
191+
&& $parameter->getAttributes(Autowire::class, \ReflectionAttribute::IS_INSTANCEOF)
192+
) {
193+
continue;
194+
}
187195

188196
$typeHint = ltrim(ProxyHelper::exportType($parameter) ?? '', '?');
189197

src/Symfony/Component/DependencyInjection/Exception/AutowiringFailedException.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ public function __construct(string $serviceId, string|\Closure $message = '', in
2323
{
2424
$this->serviceId = $serviceId;
2525

26-
if ($message instanceof \Closure
27-
&& (\function_exists('xdebug_is_enabled') ? xdebug_is_enabled() : \function_exists('xdebug_info'))
28-
) {
26+
if ($message instanceof \Closure && \function_exists('xdebug_is_enabled') && xdebug_is_enabled()) {
2927
$message = $message();
3028
}
3129

src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Psr\Container\ContainerInterface;
1616
use Symfony\Component\Config\FileLocator;
1717
use Symfony\Component\DependencyInjection\Alias;
18+
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
1819
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1920
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
2021
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -50,6 +51,7 @@
5051
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithDefaultIndexMethodAndWithDefaultPriorityMethod;
5152
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithDefaultPriorityMethod;
5253
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithoutIndex;
54+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedLocatorConsumerWithServiceSubscriber;
5355
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService1;
5456
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService2;
5557
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
@@ -1118,6 +1120,66 @@ public function testTaggedIteratorAndLocatorWithExclude()
11181120
$this->assertTrue($locator->has(AutoconfiguredService2::class));
11191121
$this->assertFalse($locator->has(TaggedConsumerWithExclude::class));
11201122
}
1123+
1124+
public function testAutowireAttributeHasPriorityOverBindings()
1125+
{
1126+
$container = new ContainerBuilder();
1127+
$container->register(FooTagClass::class)
1128+
->setPublic(true)
1129+
->addTag('foo_bar', ['key' => 'tagged_service'])
1130+
;
1131+
$container->register(TaggedLocatorConsumerWithServiceSubscriber::class)
1132+
->setBindings([
1133+
'$locator' => new BoundArgument(new Reference('service_container'), false),
1134+
])
1135+
->setPublic(true)
1136+
->setAutowired(true)
1137+
->addTag('container.service_subscriber')
1138+
;
1139+
$container->register('subscribed_service', \stdClass::class)
1140+
->setPublic(true)
1141+
;
1142+
1143+
$container->compile();
1144+
1145+
/** @var TaggedLocatorConsumerWithServiceSubscriber $s */
1146+
$s = $container->get(TaggedLocatorConsumerWithServiceSubscriber::class);
1147+
1148+
self::assertInstanceOf(ContainerInterface::class, $subscriberLocator = $s->getContainer());
1149+
self::assertTrue($subscriberLocator->has('subscribed_service'));
1150+
self::assertNotSame($subscriberLocator, $taggedLocator = $s->getLocator());
1151+
self::assertInstanceOf(ContainerInterface::class, $taggedLocator);
1152+
self::assertTrue($taggedLocator->has('tagged_service'));
1153+
}
1154+
1155+
public function testBindingsWithAutowireAttributeAndAutowireFalse()
1156+
{
1157+
$container = new ContainerBuilder();
1158+
$container->register(FooTagClass::class)
1159+
->setPublic(true)
1160+
->addTag('foo_bar', ['key' => 'tagged_service'])
1161+
;
1162+
$container->register(TaggedLocatorConsumerWithServiceSubscriber::class)
1163+
->setBindings([
1164+
'$locator' => new BoundArgument(new Reference('service_container'), false),
1165+
])
1166+
->setPublic(true)
1167+
->setAutowired(false)
1168+
->addTag('container.service_subscriber')
1169+
;
1170+
$container->register('subscribed_service', \stdClass::class)
1171+
->setPublic(true)
1172+
;
1173+
1174+
$container->compile();
1175+
1176+
/** @var TaggedLocatorConsumerWithServiceSubscriber $s */
1177+
$s = $container->get(TaggedLocatorConsumerWithServiceSubscriber::class);
1178+
1179+
self::assertNull($s->getContainer());
1180+
self::assertInstanceOf(ContainerInterface::class, $taggedLocator = $s->getLocator());
1181+
self::assertSame($container, $taggedLocator);
1182+
}
11211183
}
11221184

11231185
class ServiceSubscriberStub implements ServiceSubscriberInterface
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
16+
use Symfony\Contracts\Service\Attribute\Required;
17+
use Symfony\Contracts\Service\ServiceSubscriberInterface;
18+
19+
final class TaggedLocatorConsumerWithServiceSubscriber implements ServiceSubscriberInterface
20+
{
21+
private ?ContainerInterface $container = null;
22+
23+
public function __construct(
24+
#[TaggedLocator('foo_bar', indexAttribute: 'key')]
25+
private ContainerInterface $locator,
26+
) {
27+
}
28+
29+
public function getLocator(): ContainerInterface
30+
{
31+
return $this->locator;
32+
}
33+
34+
public function getContainer(): ?ContainerInterface
35+
{
36+
return $this->container;
37+
}
38+
39+
#[Required]
40+
public function setContainer(ContainerInterface $container): void
41+
{
42+
$this->container = $container;
43+
}
44+
45+
public static function getSubscribedServices(): array
46+
{
47+
return [
48+
'subscribed_service',
49+
];
50+
}
51+
}

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestPayloadValueResolver.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,15 @@ public function onKernelControllerArguments(ControllerArgumentsEvent $event): vo
109109
} catch (PartialDenormalizationException $e) {
110110
$trans = $this->translator ? $this->translator->trans(...) : fn ($m, $p) => strtr($m, $p);
111111
foreach ($e->getErrors() as $error) {
112-
$parameters = ['{{ type }}' => implode('|', $error->getExpectedTypes())];
112+
$parameters = [];
113+
$template = 'This value was of an unexpected type.';
114+
if ($expectedTypes = $error->getExpectedTypes()) {
115+
$template = 'This value should be of type {{ type }}.';
116+
$parameters['{{ type }}'] = implode('|', $expectedTypes);
117+
}
113118
if ($error->canUseMessageForUser()) {
114119
$parameters['hint'] = $error->getMessage();
115120
}
116-
$template = 'This value should be of type {{ type }}.';
117121
$message = $trans($template, $parameters, 'validators');
118122
$violations->add(new ConstraintViolation($message, $template, $parameters, null, $error->getPath(), null));
119123
}

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
use Symfony\Component\HttpKernel\HttpKernelInterface;
2424
use Symfony\Component\Serializer\Encoder\JsonEncoder;
2525
use Symfony\Component\Serializer\Encoder\XmlEncoder;
26+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
2627
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
28+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
2729
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
2830
use Symfony\Component\Serializer\Serializer;
31+
use Symfony\Component\Serializer\SerializerInterface;
2932
use Symfony\Component\Validator\Constraints as Assert;
3033
use Symfony\Component\Validator\ConstraintViolation;
3134
use Symfony\Component\Validator\ConstraintViolationList;
@@ -334,6 +337,34 @@ public function testRequestContentValidationPassed()
334337
$this->assertEquals([$payload], $event->getArguments());
335338
}
336339

340+
/**
341+
* @testWith [null]
342+
* [[]]
343+
*/
344+
public function testRequestContentWithUntypedErrors(?array $types)
345+
{
346+
$this->expectException(HttpException::class);
347+
$this->expectExceptionMessage('This value was of an unexpected type.');
348+
$serializer = $this->createMock(SerializerDenormalizer::class);
349+
350+
if (null === $types) {
351+
$exception = new NotNormalizableValueException('Error with no types');
352+
} else {
353+
$exception = NotNormalizableValueException::createForUnexpectedDataType('Error with no types', '', []);
354+
}
355+
$serializer->method('deserialize')->willThrowException(new PartialDenormalizationException([], [$exception]));
356+
357+
$resolver = new RequestPayloadValueResolver($serializer, $this->createMock(ValidatorInterface::class));
358+
$request = Request::create('/', 'POST', server: ['CONTENT_TYPE' => 'application/json'], content: '{"price": 50}');
359+
360+
$arguments = $resolver->resolve($request, new ArgumentMetadata('valid', RequestPayload::class, false, false, null, false, [
361+
MapRequestPayload::class => new MapRequestPayload(),
362+
]));
363+
$event = new ControllerArgumentsEvent($this->createMock(HttpKernelInterface::class), function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
364+
365+
$resolver->onKernelControllerArguments($event);
366+
}
367+
337368
public function testQueryStringValidationPassed()
338369
{
339370
$payload = new RequestPayload(50);
@@ -703,6 +734,10 @@ public function __construct(public readonly float $price)
703734
}
704735
}
705736

737+
interface SerializerDenormalizer extends SerializerInterface, DenormalizerInterface
738+
{
739+
}
740+
706741
class QueryPayload
707742
{
708743
public function __construct(public readonly float $page)

src/Symfony/Component/Mime/RawMessage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
class RawMessage
2020
{
21-
private iterable|string|null $message = null;
21+
private iterable|string $message;
2222
private bool $isGeneratorClosed;
2323

2424
public function __construct(iterable|string $message)

src/Symfony/Component/Mime/Tests/EmailTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,7 @@ public function testSymfonySerialize()
564564
}
565565
]
566566
},
567-
"body": null,
568-
"message": null
567+
"body": null
569568
}
570569
EOF;
571570

src/Symfony/Component/Mime/Tests/MessageTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,7 @@ public function testSymfonySerialize()
254254
]
255255
},
256256
"class": "Symfony\\\\Component\\\\Mime\\\\Part\\\\Multipart\\\\MixedPart"
257-
},
258-
"message": null
257+
}
259258
}
260259
EOF;
261260

src/Symfony/Component/Notifier/Bridge/Smsapi/SmsapiTransport.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ protected function doSend(MessageInterface $message): SentMessage
121121
throw new TransportException(sprintf('Unable to send the SMS: "%s".', $content['message'] ?? 'unknown error'), $response);
122122
}
123123

124-
return new SentMessage($message, (string) $this);
124+
$sentMessage = new SentMessage($message, (string) $this);
125+
$sentMessage->setMessageId($content['list'][0]['id'] ?? '');
126+
127+
return $sentMessage;
125128
}
126129
}

src/Symfony/Component/Serializer/Normalizer/MimeMessageNormalizer.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Mime\Header\UnstructuredHeader;
1818
use Symfony\Component\Mime\Message;
1919
use Symfony\Component\Mime\Part\AbstractPart;
20+
use Symfony\Component\Mime\RawMessage;
2021
use Symfony\Component\Serializer\Exception\LogicException;
2122
use Symfony\Component\Serializer\SerializerAwareInterface;
2223
use Symfony\Component\Serializer\SerializerInterface;
@@ -72,15 +73,18 @@ public function normalize(mixed $object, ?string $format = null, array $context
7273
return $ret;
7374
}
7475

76+
$ret = $this->normalizer->normalize($object, $format, $context);
77+
7578
if ($object instanceof AbstractPart) {
76-
$ret = $this->normalizer->normalize($object, $format, $context);
7779
$ret['class'] = $object::class;
7880
unset($ret['seekable'], $ret['cid'], $ret['handle']);
81+
}
7982

80-
return $ret;
83+
if ($object instanceof RawMessage && \array_key_exists('message', $ret) && null === $ret['message']) {
84+
unset($ret['message']);
8185
}
8286

83-
return $this->normalizer->normalize($object, $format, $context);
87+
return $ret;
8488
}
8589

8690
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed

0 commit comments

Comments
 (0)