Skip to content

Commit 75dba78

Browse files
committed
added the Mailer component
0 parents  commit 75dba78

File tree

154 files changed

+9421
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

154 files changed

+9421
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml

AbstractApiTransport.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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\Mailer\Transport\Http\Api;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16+
use Symfony\Component\HttpClient\HttpClient;
17+
use Symfony\Component\Mailer\Exception\RuntimeException;
18+
use Symfony\Component\Mailer\SentMessage;
19+
use Symfony\Component\Mailer\SmtpEnvelope;
20+
use Symfony\Component\Mailer\Transport\AbstractTransport;
21+
use Symfony\Component\Mime\Address;
22+
use Symfony\Component\Mime\Email;
23+
use Symfony\Component\Mime\MessageConverter;
24+
use Symfony\Contracts\HttpClient\HttpClientInterface;
25+
26+
/**
27+
* @author Fabien Potencier <[email protected]>
28+
*
29+
* @experimental in 4.3
30+
*/
31+
abstract class AbstractApiTransport extends AbstractTransport
32+
{
33+
protected $client;
34+
35+
public function __construct(HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
36+
{
37+
$this->client = $client;
38+
if (null === $client) {
39+
if (!class_exists(HttpClient::class)) {
40+
throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
41+
}
42+
43+
$this->client = HttpClient::create();
44+
}
45+
46+
parent::__construct($dispatcher, $logger);
47+
}
48+
49+
abstract protected function doSendEmail(Email $email, SmtpEnvelope $envelope): void;
50+
51+
protected function doSend(SentMessage $message): void
52+
{
53+
try {
54+
$email = MessageConverter::toEmail($message->getOriginalMessage());
55+
} catch (\Exception $e) {
56+
throw new RuntimeException(sprintf('Unable to send message with the "%s" transport: %s', __CLASS__, $e->getMessage()), 0, $e);
57+
}
58+
59+
$this->doSendEmail($email, $message->getEnvelope());
60+
}
61+
62+
protected function getRecipients(Email $email, SmtpEnvelope $envelope): array
63+
{
64+
return \array_filter($envelope->getRecipients(), function (Address $address) use ($email) {
65+
return false === \in_array($address, \array_merge($email->getCc(), $email->getBcc()), true);
66+
});
67+
}
68+
}

AbstractStream.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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\Mailer\Transport\Smtp\Stream;
13+
14+
use Symfony\Component\Mailer\Exception\TransportException;
15+
16+
/**
17+
* A stream supporting remote sockets and local processes.
18+
*
19+
* @author Fabien Potencier <[email protected]>
20+
* @author Nicolas Grekas <[email protected]>
21+
* @author Chris Corbyn
22+
*
23+
* @internal
24+
*
25+
* @experimental in 4.3
26+
*/
27+
abstract class AbstractStream
28+
{
29+
protected $stream;
30+
protected $in;
31+
protected $out;
32+
33+
public function write(string $bytes): void
34+
{
35+
$bytesToWrite = \strlen($bytes);
36+
$totalBytesWritten = 0;
37+
while ($totalBytesWritten < $bytesToWrite) {
38+
$bytesWritten = fwrite($this->in, substr($bytes, $totalBytesWritten));
39+
if (false === $bytesWritten || 0 === $bytesWritten) {
40+
throw new TransportException('Unable to write bytes on the wire.');
41+
}
42+
43+
$totalBytesWritten += $bytesWritten;
44+
}
45+
}
46+
47+
/**
48+
* Flushes the contents of the stream (empty it) and set the internal pointer to the beginning.
49+
*/
50+
public function flush(): void
51+
{
52+
fflush($this->in);
53+
}
54+
55+
/**
56+
* Performs any initialization needed.
57+
*/
58+
abstract public function initialize(): void;
59+
60+
public function terminate(): void
61+
{
62+
$this->stream = $this->out = $this->in = null;
63+
}
64+
65+
public function readLine(): string
66+
{
67+
if (feof($this->out)) {
68+
return '';
69+
}
70+
71+
$line = fgets($this->out);
72+
if (0 === \strlen($line)) {
73+
$metas = stream_get_meta_data($this->out);
74+
if ($metas['timed_out']) {
75+
throw new TransportException(sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
76+
}
77+
}
78+
79+
return $line;
80+
}
81+
82+
public static function replace(string $from, string $to, iterable $chunks): \Generator
83+
{
84+
if ('' === $from) {
85+
yield from $chunks;
86+
87+
return;
88+
}
89+
90+
$carry = '';
91+
$fromLen = \strlen($from);
92+
93+
foreach ($chunks as $chunk) {
94+
if ('' === $chunk = $carry.$chunk) {
95+
continue;
96+
}
97+
98+
if (false !== strpos($chunk, $from)) {
99+
$chunk = explode($from, $chunk);
100+
$carry = array_pop($chunk);
101+
102+
yield implode($to, $chunk).$to;
103+
} else {
104+
$carry = $chunk;
105+
}
106+
107+
if (\strlen($carry) > $fromLen) {
108+
yield substr($carry, 0, -$fromLen);
109+
$carry = substr($carry, -$fromLen);
110+
}
111+
}
112+
113+
if ('' !== $carry) {
114+
yield $carry;
115+
}
116+
}
117+
118+
abstract protected function getReadConnectionDescription(): string;
119+
}

AbstractStreamTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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\Mailer\Tests\Transport\Smtp\Stream;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Mailer\Transport\Smtp\Stream\AbstractStream;
16+
17+
class AbstractStreamTest extends TestCase
18+
{
19+
/**
20+
* @dataProvider provideReplace
21+
*/
22+
public function testReplace(string $expected, string $from, string $to, array $chunks)
23+
{
24+
$result = '';
25+
foreach (AbstractStream::replace($from, $to, $chunks) as $chunk) {
26+
$result .= $chunk;
27+
}
28+
29+
$this->assertSame($expected, $result);
30+
}
31+
32+
public function provideReplace()
33+
{
34+
yield ['ca', 'ab', 'c', ['a', 'b', 'a']];
35+
yield ['ac', 'ab', 'c', ['a', 'ab']];
36+
yield ['cbc', 'aba', 'c', ['ababa', 'ba']];
37+
}
38+
}

AbstractTransport.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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\Mailer\Transport;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Psr\Log\NullLogger;
16+
use Symfony\Component\EventDispatcher\EventDispatcher;
17+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
18+
use Symfony\Component\Mailer\Event\MessageEvent;
19+
use Symfony\Component\Mailer\Exception\TransportException;
20+
use Symfony\Component\Mailer\SentMessage;
21+
use Symfony\Component\Mailer\SmtpEnvelope;
22+
use Symfony\Component\Mime\Address;
23+
use Symfony\Component\Mime\RawMessage;
24+
25+
/**
26+
* @author Fabien Potencier <[email protected]>
27+
*
28+
* @experimental in 4.3
29+
*/
30+
abstract class AbstractTransport implements TransportInterface
31+
{
32+
private $dispatcher;
33+
private $logger;
34+
private $rate = 0;
35+
private $lastSent = 0;
36+
37+
public function __construct(EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
38+
{
39+
$this->dispatcher = $dispatcher ?: new EventDispatcher();
40+
$this->logger = $logger ?: new NullLogger();
41+
}
42+
43+
/**
44+
* Sets the maximum number of messages to send per second (0 to disable).
45+
*/
46+
public function setMaxPerSecond(float $rate): self
47+
{
48+
if (0 >= $rate) {
49+
$rate = 0;
50+
}
51+
52+
$this->rate = $rate;
53+
$this->lastSent = 0;
54+
55+
return $this;
56+
}
57+
58+
public function send(RawMessage $message, SmtpEnvelope $envelope = null): ?SentMessage
59+
{
60+
$message = clone $message;
61+
if (null !== $envelope) {
62+
$envelope = clone $envelope;
63+
} else {
64+
try {
65+
$envelope = SmtpEnvelope::create($message);
66+
} catch (\Exception $e) {
67+
throw new TransportException('Cannot send message without a valid envelope.', 0, $e);
68+
}
69+
}
70+
71+
$event = new MessageEvent($message, $envelope);
72+
$this->dispatcher->dispatch($event);
73+
$envelope = $event->getEnvelope();
74+
if (!$envelope->getRecipients()) {
75+
return null;
76+
}
77+
78+
$message = new SentMessage($event->getMessage(), $envelope);
79+
$this->doSend($message);
80+
81+
$this->checkThrottling();
82+
83+
return $message;
84+
}
85+
86+
abstract protected function doSend(SentMessage $message): void;
87+
88+
/**
89+
* @param Address[] $addresses
90+
*
91+
* @return string[]
92+
*/
93+
protected function stringifyAddresses(array $addresses): array
94+
{
95+
return \array_map(function (Address $a) {
96+
return $a->toString();
97+
}, $addresses);
98+
}
99+
100+
protected function getLogger(): LoggerInterface
101+
{
102+
return $this->logger;
103+
}
104+
105+
private function checkThrottling()
106+
{
107+
if (0 == $this->rate) {
108+
return;
109+
}
110+
111+
$sleep = (1 / $this->rate) - (microtime(true) - $this->lastSent);
112+
if (0 < $sleep) {
113+
$this->logger->debug(sprintf('Email transport "%s" sleeps for %.2f seconds', __CLASS__, $sleep));
114+
usleep($sleep * 1000000);
115+
}
116+
$this->lastSent = microtime(true);
117+
}
118+
}

0 commit comments

Comments
 (0)