Skip to content

Commit 36548e6

Browse files
committed
Fix Mime message serialization
1 parent 95eb341 commit 36548e6

File tree

12 files changed

+428
-6
lines changed

12 files changed

+428
-6
lines changed

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

+80
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
use PHPUnit\Framework\TestCase;
66
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
7+
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
8+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
9+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
10+
use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer;
11+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
12+
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
13+
use Symfony\Component\Serializer\Serializer;
714

815
class TemplatedEmailTest extends TestCase
916
{
@@ -33,4 +40,77 @@ public function testSerialize()
3340
$this->assertEquals('text.html.twig', $email->getHtmlTemplate());
3441
$this->assertEquals($context, $email->getContext());
3542
}
43+
44+
public function testSymfonySerialize()
45+
{
46+
// we don't add from/sender to check that validation is not triggered to serialize an email
47+
$e = new TemplatedEmail();
48+
$e->to('you@example.com');
49+
$e->textTemplate('email.txt.twig');
50+
$e->htmlTemplate('email.html.twig');
51+
$e->context(['foo' => 'bar']);
52+
$e->attach('Some Text file', 'test.txt');
53+
$expected = clone $e;
54+
55+
$expectedJson = <<<EOF
56+
{
57+
"htmlTemplate": "email.html.twig",
58+
"textTemplate": "email.txt.twig",
59+
"context": {
60+
"foo": "bar"
61+
},
62+
"text": null,
63+
"textCharset": null,
64+
"html": null,
65+
"htmlCharset": null,
66+
"attachments": [
67+
{
68+
"body": "Some Text file",
69+
"name": "test.txt",
70+
"content-type": null,
71+
"inline": false
72+
}
73+
],
74+
"headers": {
75+
"to": [
76+
{
77+
"addresses": [
78+
{
79+
"address": "you@example.com",
80+
"name": ""
81+
}
82+
],
83+
"name": "To",
84+
"lineLength": 76,
85+
"lang": null,
86+
"charset": "utf-8"
87+
}
88+
]
89+
},
90+
"body": null,
91+
"message": null
92+
}
93+
EOF;
94+
95+
$extractor = new PhpDocExtractor();
96+
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor);
97+
$serializer = new Serializer([
98+
new ArrayDenormalizer(),
99+
new MimeMessageNormalizer($propertyNormalizer),
100+
new ObjectNormalizer(null, null, null, $extractor),
101+
$propertyNormalizer,
102+
], [new JsonEncoder()]);
103+
104+
$serialized = $serializer->serialize($e, 'json');
105+
$this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
106+
107+
$n = $serializer->deserialize($serialized, TemplatedEmail::class, 'json');
108+
$serialized = $serializer->serialize($e, 'json');
109+
$this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
110+
111+
$n->from('fabien@symfony.com');
112+
$expected->from('fabien@symfony.com');
113+
$this->assertEquals($expected->getHeaders(), $n->getHeaders());
114+
$this->assertEquals($expected->getBody(), $n->getBody());
115+
}
36116
}

src/Symfony/Bridge/Twig/composer.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,24 @@
2323
},
2424
"require-dev": {
2525
"egulias/email-validator": "^2.1.10",
26+
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
2627
"symfony/asset": "^4.4|^5.0",
2728
"symfony/dependency-injection": "^4.4|^5.0",
2829
"symfony/finder": "^4.4|^5.0",
2930
"symfony/form": "^5.1",
3031
"symfony/http-foundation": "^4.4|^5.0",
3132
"symfony/http-kernel": "^4.4|^5.0",
32-
"symfony/mime": "^4.4|^5.0",
33+
"symfony/mime": "^5.2",
3334
"symfony/polyfill-intl-icu": "~1.0",
35+
"symfony/property-info": "^4.4|^5.1",
3436
"symfony/routing": "^4.4|^5.0",
3537
"symfony/translation": "^5.0",
3638
"symfony/yaml": "^4.4|^5.0",
3739
"symfony/security-acl": "^2.8|^3.0",
3840
"symfony/security-core": "^4.4|^5.0",
3941
"symfony/security-csrf": "^4.4|^5.0",
4042
"symfony/security-http": "^4.4|^5.0",
43+
"symfony/serializer": "^5.2",
4144
"symfony/stopwatch": "^4.4|^5.0",
4245
"symfony/console": "^4.4|^5.0",
4346
"symfony/expression-language": "^4.4|^5.0",

src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php

+18
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer;
4040
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
4141
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
42+
use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer;
4243
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
4344
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
4445
use Symfony\Component\Serializer\Normalizer\ProblemNormalizer;
46+
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
4547
use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
4648
use Symfony\Component\Serializer\Serializer;
4749
use Symfony\Component\Serializer\SerializerInterface;
@@ -76,6 +78,10 @@
7678
->args([[], service('serializer.name_converter.metadata_aware')])
7779
->tag('serializer.normalizer', ['priority' => -915])
7880

81+
->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class)
82+
->args([service('serializer.normalizer.property')])
83+
->tag('serializer.normalizer', ['priority' => -915])
84+
7985
->set('serializer.normalizer.datetimezone', DateTimeZoneNormalizer::class)
8086
->tag('serializer.normalizer', ['priority' => -915])
8187

@@ -114,6 +120,18 @@
114120

115121
->alias(ObjectNormalizer::class, 'serializer.normalizer.object')
116122

123+
->set('serializer.normalizer.property', PropertyNormalizer::class)
124+
->args([
125+
service('serializer.mapping.class_metadata_factory'),
126+
service('serializer.name_converter.metadata_aware'),
127+
service('property_info')->ignoreOnInvalid(),
128+
service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(),
129+
null,
130+
[],
131+
])
132+
133+
->alias(PropertyNormalizer::class, 'serializer.normalizer.property')
134+
117135
->set('serializer.denormalizer.array', ArrayDenormalizer::class)
118136
->tag('serializer.normalizer', ['priority' => -990])
119137

src/Symfony/Bundle/FrameworkBundle/composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"symfony/security-bundle": "^5.1",
5353
"symfony/security-csrf": "^4.4|^5.0",
5454
"symfony/security-http": "^4.4|^5.0",
55-
"symfony/serializer": "^4.4|^5.0",
55+
"symfony/serializer": "^5.2",
5656
"symfony/stopwatch": "^4.4|^5.0",
5757
"symfony/string": "^5.0",
5858
"symfony/translation": "^5.0",
@@ -62,7 +62,7 @@
6262
"symfony/yaml": "^4.4|^5.0",
6363
"symfony/property-info": "^4.4|^5.0",
6464
"symfony/web-link": "^4.4|^5.0",
65-
"phpdocumentor/reflection-docblock": "^3.0|^4.0",
65+
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
6666
"paragonie/sodium_compat": "^1.8",
6767
"twig/twig": "^2.10|^3.0"
6868
},

src/Symfony/Component/Mime/Email.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ public function attachPart(DataPart $part)
378378
}
379379

380380
/**
381-
* @return DataPart[]
381+
* @return array|DataPart[]
382382
*/
383383
public function getAttachments(): array
384384
{

src/Symfony/Component/Mime/Header/Headers.php

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ final class Headers
3939
'return-path' => PathHeader::class,
4040
];
4141

42+
/**
43+
* @var HeaderInterface[][]
44+
*/
4245
private $headers = [];
4346
private $lineLength = 76;
4447

src/Symfony/Component/Mime/Part/TextPart.php

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class TextPart extends AbstractPart
2828
private $body;
2929
private $charset;
3030
private $subtype;
31+
/**
32+
* @var ?string
33+
*/
3134
private $disposition;
3235
private $name;
3336
private $encoding;

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

+74
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
use Symfony\Component\Mime\Part\Multipart\MixedPart;
2020
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
2121
use Symfony\Component\Mime\Part\TextPart;
22+
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
23+
use Symfony\Component\Serializer\Encoder\JsonEncoder;
24+
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
25+
use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer;
26+
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
27+
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
28+
use Symfony\Component\Serializer\Serializer;
2229

2330
class EmailTest extends TestCase
2431
{
@@ -384,4 +391,71 @@ public function testSerialize()
384391
$this->assertEquals($expected->getHeaders(), $n->getHeaders());
385392
$this->assertEquals($e->getBody(), $n->getBody());
386393
}
394+
395+
public function testSymfonySerialize()
396+
{
397+
// we don't add from/sender to check that validation is not triggered to serialize an email
398+
$e = new Email();
399+
$e->to('you@example.com');
400+
$e->text('Text content');
401+
$e->html('HTML <b>content</b>');
402+
$e->attach('Some Text file', 'test.txt');
403+
$expected = clone $e;
404+
405+
$expectedJson = <<<EOF
406+
{
407+
"text": "Text content",
408+
"textCharset": "utf-8",
409+
"html": "HTML <b>content</b>",
410+
"htmlCharset": "utf-8",
411+
"attachments": [
412+
{
413+
"body": "Some Text file",
414+
"name": "test.txt",
415+
"content-type": null,
416+
"inline": false
417+
}
418+
],
419+
"headers": {
420+
"to": [
421+
{
422+
"addresses": [
423+
{
424+
"address": "you@example.com",
425+
"name": ""
426+
}
427+
],
428+
"name": "To",
429+
"lineLength": 76,
430+
"lang": null,
431+
"charset": "utf-8"
432+
}
433+
]
434+
},
435+
"body": null,
436+
"message": null
437+
}
438+
EOF;
439+
440+
$extractor = new PhpDocExtractor();
441+
$propertyNormalizer = new PropertyNormalizer(null, null, $extractor);
442+
$serializer = new Serializer([
443+
new ArrayDenormalizer(),
444+
new MimeMessageNormalizer($propertyNormalizer),
445+
new ObjectNormalizer(null, null, null, $extractor),
446+
$propertyNormalizer,
447+
], [new JsonEncoder()]);
448+
449+
$serialized = $serializer->serialize($e, 'json');
450+
$this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
451+
452+
$n = $serializer->deserialize($serialized, Email::class, 'json');
453+
$serialized = $serializer->serialize($e, 'json');
454+
$this->assertSame($expectedJson, json_encode(json_decode($serialized), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
455+
456+
$n->from('fabien@symfony.com');
457+
$expected->from('fabien@symfony.com');
458+
$this->assertEquals($expected->getHeaders(), $n->getHeaders());
459+
$this->assertEquals($expected->getBody(), $n->getBody());
460+
}
387461
}

0 commit comments

Comments
 (0)