Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 07a54d2

Browse files
committed
[Mime] Simplify adding Parts to an Email
1 parent 0ca5051 commit 07a54d2

File tree

13 files changed

+85
-133
lines changed

13 files changed

+85
-133
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,16 @@ public function testSymfonySerialize()
7474
"htmlCharset": null,
7575
"attachments": [
7676
{
77+
"filename": "test.txt",
78+
"mediaType": "application",
7779
"body": "Some Text file",
80+
"charset": null,
81+
"subtype": "octet-stream",
82+
"disposition": "attachment",
7883
"name": "test.txt",
79-
"content-type": null,
80-
"inline": false
84+
"encoding": "base64",
85+
"headers": [],
86+
"class": "Symfony\\\Component\\\Mime\\\Part\\\DataPart"
8187
}
8288
],
8389
"headers": {

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": "^5.4|^6.0",
4444
"symfony/security-csrf": "^5.4|^6.0",
4545
"symfony/security-http": "^5.4|^6.0",
46-
"symfony/serializer": "^5.4|^6.0",
46+
"symfony/serializer": "^6.2",
4747
"symfony/stopwatch": "^5.4|^6.0",
4848
"symfony/console": "^5.4|^6.0",
4949
"symfony/expression-language": "^5.4|^6.0",

src/Symfony/Component/Mailer/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"psr/event-dispatcher": "^1",
2222
"psr/log": "^1|^2|^3",
2323
"symfony/event-dispatcher": "^5.4|^6.0",
24-
"symfony/mime": "^5.4|^6.0",
24+
"symfony/mime": "^6.2",
2525
"symfony/service-contracts": "^1.1|^2|^3"
2626
},
2727
"require-dev": {

src/Symfony/Component/Mime/Email.php

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -326,25 +326,15 @@ public function getHtmlCharset(): ?string
326326
*/
327327
public function attach($body, string $name = null, string $contentType = null): static
328328
{
329-
if (!\is_string($body) && !\is_resource($body)) {
330-
throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
331-
}
332-
333-
$this->cachedBody = null;
334-
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
335-
336-
return $this;
329+
return $this->attachPart(new DataPart($body, $name, $contentType));
337330
}
338331

339332
/**
340333
* @return $this
341334
*/
342335
public function attachFromPath(string $path, string $name = null, string $contentType = null): static
343336
{
344-
$this->cachedBody = null;
345-
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
346-
347-
return $this;
337+
return $this->attachPart(new DataPart('file://'.$path, $name, $contentType));
348338
}
349339

350340
/**
@@ -354,25 +344,15 @@ public function attachFromPath(string $path, string $name = null, string $conten
354344
*/
355345
public function embed($body, string $name = null, string $contentType = null): static
356346
{
357-
if (!\is_string($body) && !\is_resource($body)) {
358-
throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
359-
}
360-
361-
$this->cachedBody = null;
362-
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
363-
364-
return $this;
347+
return $this->attachPart((new DataPart($body, $name, $contentType))->asInline());
365348
}
366349

367350
/**
368351
* @return $this
369352
*/
370353
public function embedFromPath(string $path, string $name = null, string $contentType = null): static
371354
{
372-
$this->cachedBody = null;
373-
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
374-
375-
return $this;
355+
return $this->attachPart((new DataPart('file://'.$path, $name, $contentType))->asInline());
376356
}
377357

378358
/**
@@ -381,22 +361,17 @@ public function embedFromPath(string $path, string $name = null, string $content
381361
public function attachPart(DataPart $part): static
382362
{
383363
$this->cachedBody = null;
384-
$this->attachments[] = ['part' => $part];
364+
$this->attachments[] = $part;
385365

386366
return $this;
387367
}
388368

389369
/**
390-
* @return array|DataPart[]
370+
* @return DataPart[]
391371
*/
392372
public function getAttachments(): array
393373
{
394-
$parts = [];
395-
foreach ($this->attachments as $attachment) {
396-
$parts[] = $this->createDataPart($attachment);
397-
}
398-
399-
return $parts;
374+
return $this->attachments;
400375
}
401376

402377
public function getBody(): AbstractPart
@@ -502,35 +477,23 @@ private function prepareParts(): ?array
502477
}
503478

504479
$otherParts = $relatedParts = [];
505-
foreach ($this->attachments as $attachment) {
506-
$part = $this->createDataPart($attachment);
507-
if (isset($attachment['part'])) {
508-
$attachment['name'] = $part->getName();
509-
}
510-
511-
$related = false;
480+
foreach ($this->attachments as $part) {
512481
foreach ($names as $name) {
513-
if ($name !== $attachment['name']) {
482+
if ($name !== $part->getName()) {
514483
continue;
515484
}
516485
if (isset($relatedParts[$name])) {
517486
continue 2;
518487
}
519-
$part->setDisposition('inline');
488+
520489
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
521-
if ($count) {
522-
$related = true;
523-
}
524-
$part->setName($part->getContentId());
490+
$relatedParts[$name] = $part;
491+
$part->setName($part->getContentId())->asInline();
525492

526-
break;
493+
continue 2;
527494
}
528495

529-
if ($related) {
530-
$relatedParts[$attachment['name']] = $part;
531-
} else {
532-
$otherParts[] = $part;
533-
}
496+
$otherParts[] = $part;
534497
}
535498
if (null !== $htmlPart) {
536499
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
@@ -539,24 +502,6 @@ private function prepareParts(): ?array
539502
return [$htmlPart, $otherParts, array_values($relatedParts)];
540503
}
541504

542-
private function createDataPart(array $attachment): DataPart
543-
{
544-
if (isset($attachment['part'])) {
545-
return $attachment['part'];
546-
}
547-
548-
if (isset($attachment['body'])) {
549-
$part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
550-
} else {
551-
$part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
552-
}
553-
if ($attachment['inline']) {
554-
$part->asInline();
555-
}
556-
557-
return $part;
558-
}
559-
560505
/**
561506
* @return $this
562507
*/
@@ -606,12 +551,6 @@ public function __serialize(): array
606551
$this->html = (new TextPart($this->html))->getBody();
607552
}
608553

609-
foreach ($this->attachments as $i => $attachment) {
610-
if (isset($attachment['body']) && \is_resource($attachment['body'])) {
611-
$this->attachments[$i]['body'] = (new TextPart($attachment['body']))->getBody();
612-
}
613-
}
614-
615554
return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
616555
}
617556

src/Symfony/Component/Mime/MessageConverter.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ private static function attachParts(Email $email, array $parts): Email
114114
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($email)));
115115
}
116116

117-
$headers = $part->getPreparedHeaders();
118-
$method = 'inline' === $headers->getHeaderBody('Content-Disposition') ? 'embed' : 'attach';
119-
$name = $headers->getHeaderParameter('Content-Disposition', 'filename');
120-
$email->$method($part->getBody(), $name, $part->getMediaType().'/'.$part->getMediaSubtype());
117+
$email->attachPart($part);
121118
}
122119

123120
return $email;

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

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,28 @@ class DataPart extends TextPart
2828
private $filename;
2929
private $mediaType;
3030
private $cid;
31-
private $handle;
3231

33-
/**
34-
* @param resource|string $body
35-
*/
3632
public function __construct($body, string $filename = null, string $contentType = null, string $encoding = null)
3733
{
3834
unset($this->_parent);
3935

36+
$path = null;
37+
if (\is_string($body) && str_starts_with($body, 'file://')) {
38+
$path = substr($body, \strlen('file://'));
39+
if (!$filename) {
40+
$filename = basename($path);
41+
}
42+
}
43+
4044
if (null === $contentType) {
4145
$contentType = 'application/octet-stream';
46+
if ($path) {
47+
$ext = strtolower(substr($path, strrpos($path, '.') + 1));
48+
if (null === self::$mimeTypes) {
49+
self::$mimeTypes = new MimeTypes();
50+
}
51+
$contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
52+
}
4253
}
4354
[$this->mediaType, $subtype] = explode('/', $contentType);
4455

@@ -53,32 +64,7 @@ public function __construct($body, string $filename = null, string $contentType
5364

5465
public static function fromPath(string $path, string $name = null, string $contentType = null): self
5566
{
56-
if (null === $contentType) {
57-
$ext = strtolower(substr($path, strrpos($path, '.') + 1));
58-
if (null === self::$mimeTypes) {
59-
self::$mimeTypes = new MimeTypes();
60-
}
61-
$contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
62-
}
63-
64-
if ((is_file($path) && !is_readable($path)) || is_dir($path)) {
65-
throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
66-
}
67-
68-
if (false === $handle = @fopen($path, 'r', false)) {
69-
throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
70-
}
71-
72-
if (!is_file($path)) {
73-
$cache = fopen('php://temp', 'r+');
74-
stream_copy_to_stream($handle, $cache);
75-
$handle = $cache;
76-
}
77-
78-
$p = new self($handle, $name ?: basename($path), $contentType);
79-
$p->handle = $handle;
80-
81-
return $p;
67+
return new self('file://'.$path, $name, $contentType);
8268
}
8369

8470
/**
@@ -158,13 +144,6 @@ private function generateContentId(): string
158144
return bin2hex(random_bytes(16)).'@symfony';
159145
}
160146

161-
public function __destruct()
162-
{
163-
if (null !== $this->handle && \is_resource($this->handle)) {
164-
fclose($this->handle);
165-
}
166-
}
167-
168147
public function __sleep(): array
169148
{
170149
// converts the body to a string

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class TextPart extends AbstractPart
4040
private $seekable;
4141

4242
/**
43-
* @param resource|string $body
43+
* @param resource|string $body Use file:// to defer loading the file until rendering
4444
*/
4545
public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain', string $encoding = null)
4646
{
@@ -52,6 +52,13 @@ public function __construct($body, ?string $charset = 'utf-8', string $subtype =
5252
throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, get_debug_type($body)));
5353
}
5454

55+
if (\is_string($body) && str_starts_with($body, 'file://')) {
56+
$path = substr($body, \strlen('file://'));
57+
if ((is_file($path) && !is_readable($path)) || is_dir($path)) {
58+
throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
59+
}
60+
}
61+
5562
$this->body = $body;
5663
$this->charset = $charset;
5764
$this->subtype = $subtype;
@@ -116,6 +123,10 @@ public function getName(): ?string
116123

117124
public function getBody(): string
118125
{
126+
if (\is_string($this->body) && str_starts_with($this->body, 'file://')) {
127+
return file_get_contents(substr($this->body, \strlen('file://')));
128+
}
129+
119130
if (null === $this->seekable) {
120131
return $this->body;
121132
}
@@ -134,7 +145,14 @@ public function bodyToString(): string
134145

135146
public function bodyToIterable(): iterable
136147
{
137-
if (null !== $this->seekable) {
148+
if (\is_string($this->body) && str_starts_with($this->body, 'file://')) {
149+
$path = substr($this->body, \strlen('file://'));
150+
if (false === $handle = @fopen($path, 'r', false)) {
151+
throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
152+
}
153+
154+
yield from $this->getEncoder()->encodeByteStream($handle);
155+
} elseif (null !== $this->seekable) {
138156
if ($this->seekable) {
139157
rewind($this->body);
140158
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,9 @@ public function testSerialize()
504504
$expected = clone $e;
505505
$n = unserialize(serialize($e));
506506
$this->assertEquals($expected->getHeaders(), $n->getHeaders());
507-
$this->assertEquals($e->getBody(), $n->getBody());
507+
$a = preg_replace(["{boundary=.+\r\n}", "{^\-\-.+\r\n}m"], ['boundary=x', '--x'], $e->getBody()->toString());
508+
$b = preg_replace(["{boundary=.+\r\n}", "{^\-\-.+\r\n}m"], ['boundary=x', '--x'], $n->getBody()->toString());
509+
$this->assertEquals($a, $b);
508510
}
509511

510512
public function testSymfonySerialize()
@@ -525,10 +527,16 @@ public function testSymfonySerialize()
525527
"htmlCharset": "utf-8",
526528
"attachments": [
527529
{
530+
"filename": "test.txt",
531+
"mediaType": "application",
528532
"body": "Some Text file",
533+
"charset": null,
534+
"subtype": "octet-stream",
535+
"disposition": "attachment",
529536
"name": "test.txt",
530-
"content-type": null,
531-
"inline": false
537+
"encoding": "base64",
538+
"headers": [],
539+
"class": "Symfony\\\Component\\\Mime\\\Part\\\DataPart"
532540
}
533541
],
534542
"headers": {
@@ -587,15 +595,15 @@ public function testMissingHeaderDoesNotThrowError()
587595
public function testAttachBodyExpectStringOrResource()
588596
{
589597
$this->expectException(\TypeError::class);
590-
$this->expectExceptionMessage('The body must be a string or a resource (got "bool").');
598+
$this->expectExceptionMessage('The body of "Symfony\Component\Mime\Part\TextPart" must be a string or a resource (got "bool").');
591599

592600
(new Email())->attach(false);
593601
}
594602

595603
public function testEmbedBodyExpectStringOrResource()
596604
{
597605
$this->expectException(\TypeError::class);
598-
$this->expectExceptionMessage('The body must be a string or a resource (got "bool").');
606+
$this->expectExceptionMessage('The body of "Symfony\Component\Mime\Part\TextPart" must be a string or a resource (got "bool").');
599607

600608
(new Email())->embed(false);
601609
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
content

0 commit comments

Comments
 (0)