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

Lines changed: 80 additions & 0 deletions
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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 18 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 74 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy