Skip to content

Commit edccbb9

Browse files
authored
Merge pull request #106 from CakeDC/issue/64
Implement QueueTransport to enqueue emails automatically Fixes #64
2 parents fb2bd80 + 357c9f4 commit edccbb9

File tree

6 files changed

+464
-0
lines changed

6 files changed

+464
-0
lines changed

src/Job/SendMailJob.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 0.1.9
15+
* @license https://opensource.org/licenses/MIT MIT License
16+
*/
17+
namespace Cake\Queue\Job;
18+
19+
use Cake\Log\Log;
20+
use Cake\Mailer\AbstractTransport;
21+
use Cake\Queue\Queue\Processor;
22+
23+
/**
24+
* SendMailJob class to be used by QueueTransport to enqueue emails
25+
*/
26+
class SendMailJob implements JobInterface
27+
{
28+
/**
29+
* @inheritDoc
30+
*/
31+
public function execute(Message $message): ?string
32+
{
33+
$result = false;
34+
try {
35+
$transportClassName = $message->getArgument('transport');
36+
$config = $message->getArgument('config', []);
37+
/** @var \Cake\Mailer\AbstractTransport $transport */
38+
$transport = $this->getTransport($transportClassName, $config);
39+
40+
$emailMessage = new \Cake\Mailer\Message();
41+
$data = json_decode($message->getArgument('emailMessage'), true);
42+
if (!is_array($data)) {
43+
throw new \InvalidArgumentException('Email Message cannot be decoded.');
44+
}
45+
$emailMessage->createFromArray($data);
46+
$result = $transport->send($emailMessage);
47+
} catch (\Exception $e) {
48+
Log::error(sprintf('An error has occurred processing message: %s', $e->getMessage()));
49+
}
50+
51+
if (!$result) {
52+
return Processor::REJECT;
53+
}
54+
55+
return Processor::ACK;
56+
}
57+
58+
/**
59+
* Initialize transport
60+
*
61+
* @param string $transportClassName Transport class name
62+
* @param array $config Transport config
63+
* @return \Cake\Mailer\AbstractTransport
64+
* @throws \InvalidArgumentException if empty transport class name, class does not exist or send method is not defined for class
65+
*/
66+
protected function getTransport(string $transportClassName, array $config): AbstractTransport
67+
{
68+
if (
69+
empty($transportClassName) ||
70+
!class_exists($transportClassName) ||
71+
!method_exists($transportClassName, 'send')
72+
) {
73+
throw new \InvalidArgumentException(sprintf('Transport class name is not valid: %s', $transportClassName));
74+
}
75+
76+
$transport = new $transportClassName($config);
77+
78+
if (!($transport instanceof AbstractTransport)) {
79+
throw new \InvalidArgumentException('Provided class does not extend AbstractTransport.');
80+
}
81+
82+
return $transport;
83+
}
84+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 0.1.9
15+
* @license https://opensource.org/licenses/MIT MIT License
16+
*/
17+
namespace Cake\Queue\Mailer\Transport;
18+
19+
use Cake\Mailer\Message;
20+
use Cake\Mailer\Transport\MailTransport;
21+
use Cake\Queue\Job\SendMailJob;
22+
use Cake\Queue\QueueManager;
23+
24+
class QueueTransport extends \Cake\Mailer\AbstractTransport
25+
{
26+
/**
27+
* Default config for this class
28+
*
29+
* @var array<string, mixed>
30+
*/
31+
protected $_defaultConfig = [
32+
'options' => [],
33+
'transport' => MailTransport::class,
34+
];
35+
36+
/**
37+
* @inheritDoc
38+
*/
39+
public function send(Message $message): array
40+
{
41+
$data = $this->prepareData($message);
42+
$options = $this->getConfig('options');
43+
$this->enqueueJob($data, $options);
44+
45+
$headers = $message->getHeadersString(
46+
[
47+
'from',
48+
'to',
49+
'subject',
50+
'sender',
51+
'replyTo',
52+
'readReceipt',
53+
'returnPath',
54+
'cc',
55+
'bcc',
56+
]
57+
);
58+
59+
return ['headers' => $headers, 'message' => 'Message has been enqueued'];
60+
}
61+
62+
/**
63+
* Add job to queue
64+
*
65+
* @param array $data Data to be sent to job
66+
* @param array $options Job options
67+
* @return void
68+
*/
69+
protected function enqueueJob(array $data, array $options): void
70+
{
71+
QueueManager::push(
72+
[SendMailJob::class, 'execute'],
73+
$data,
74+
$options
75+
);
76+
}
77+
78+
/**
79+
* Prepare data for job
80+
*
81+
* @param \Cake\Mailer\Message $message Email message
82+
* @return array
83+
*/
84+
protected function prepareData(Message $message): array
85+
{
86+
return [
87+
'transport' => $this->getConfig('transport'),
88+
'config' => $this->getConfig(),
89+
'emailMessage' => json_encode($message),
90+
];
91+
}
92+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org/)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 0.1.9
15+
* @license https://opensource.org/licenses/MIT MIT License
16+
*/
17+
namespace Cake\Queue\Test\TestCase\Job;
18+
19+
use Cake\Mailer\Mailer;
20+
use Cake\Mailer\Transport\DebugTransport;
21+
use Cake\Queue\Job\Message;
22+
use Cake\Queue\Job\SendMailJob;
23+
use Cake\Queue\Queue\Processor;
24+
use Cake\TestSuite\TestCase;
25+
use Enqueue\Null\NullConnectionFactory;
26+
use Enqueue\Null\NullMessage;
27+
28+
class SendMailJobTest extends TestCase
29+
{
30+
/**
31+
* @var \Cake\Queue\Job\SendMailJob
32+
*/
33+
protected $job;
34+
35+
/**
36+
* @var \Cake\Mailer\Message
37+
*/
38+
protected $message;
39+
40+
/**
41+
* @inheritDoc
42+
*/
43+
public function setUp(): void
44+
{
45+
parent::setUp();
46+
47+
$this->job = new SendMailJob();
48+
$this->message = (new \Cake\Mailer\Message())
49+
->setFrom('[email protected]')
50+
->setTo('[email protected]')
51+
->setSubject('Sample Subject');
52+
}
53+
54+
/**
55+
* Test execute method
56+
*
57+
* @return void
58+
*/
59+
public function testExecute()
60+
{
61+
$job = $this->getMockBuilder(SendMailJob::class)
62+
->onlyMethods(['getTransport'])
63+
->getMock();
64+
$message = $this->createMessage(DebugTransport::class, [], $this->message);
65+
$emailMessage = new \Cake\Mailer\Message();
66+
$data = json_decode($message->getArgument('emailMessage'), true);
67+
$emailMessage->createFromArray($data);
68+
$transport = $this->getMockBuilder(DebugTransport::class)->getMock();
69+
$transport->expects($this->once())
70+
->method('send')
71+
->with($emailMessage)
72+
->willReturn(['message' => 'test', 'headers' => []]);
73+
$job->expects($this->once())
74+
->method('getTransport')
75+
->with(DebugTransport::class, [])
76+
->willReturn($transport);
77+
$actual = $job->execute($message);
78+
$this->assertSame(Processor::ACK, $actual);
79+
}
80+
81+
/**
82+
* Test execute with attachments method
83+
*
84+
* @return void
85+
*/
86+
public function testExecuteWithAttachments()
87+
{
88+
$emailMessage = clone $this->message;
89+
$emailMessage->addAttachments(['test.txt' => ROOT . 'files' . DS . 'test.txt']);
90+
$message = $this->createMessage(DebugTransport::class, [], $emailMessage);
91+
$actual = $this->job->execute($message);
92+
$this->assertSame(Processor::ACK, $actual);
93+
}
94+
95+
/**
96+
* Test execute method with invalid transport
97+
*
98+
* @return void
99+
*/
100+
public function testExecuteInvalidTransport()
101+
{
102+
$message = $this->createMessage('WrongTransport', [], $this->message);
103+
$actual = $this->job->execute($message);
104+
$this->assertSame(Processor::REJECT, $actual);
105+
}
106+
107+
/**
108+
* Test execute method with unserializable message
109+
*
110+
* @return void
111+
*/
112+
public function testExecuteUnserializableMessage()
113+
{
114+
$message = $this->createMessage(DebugTransport::class, [], 'unserializable');
115+
$actual = $this->job->execute($message);
116+
$this->assertSame(Processor::REJECT, $actual);
117+
}
118+
119+
public function testExecuteNoAbstractTransport()
120+
{
121+
$message = $this->createMessage(Mailer::class, [], $this->message);
122+
$actual = $this->job->execute($message);
123+
$this->assertSame(Processor::REJECT, $actual);
124+
}
125+
126+
/**
127+
* Create a simple message for testing.
128+
*
129+
* @return \Cake\Queue\Job\Message
130+
*/
131+
protected function createMessage($transport, $config, $emailMessage): Message
132+
{
133+
$messageBody = [
134+
'class' => ['Queue\\Job\\SendMailJob', 'execute'],
135+
'data' => [
136+
'transport' => $transport,
137+
'config' => $config,
138+
'emailMessage' => json_encode($emailMessage),
139+
140+
],
141+
];
142+
$connectionFactory = new NullConnectionFactory();
143+
$context = $connectionFactory->createContext();
144+
$originalMessage = new NullMessage(json_encode($messageBody));
145+
$message = new Message($originalMessage, $context);
146+
147+
return $message;
148+
}
149+
}

0 commit comments

Comments
 (0)