1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Charcoal\Email\Script; |
6
|
|
|
|
7
|
|
|
// From PSR-7 |
8
|
|
|
use Psr\Http\Message\RequestInterface; |
9
|
|
|
use Psr\Http\Message\ResponseInterface; |
10
|
|
|
|
11
|
|
|
// From Pimple |
12
|
|
|
use Pimple\Container; |
13
|
|
|
|
14
|
|
|
// From 'charcoal-app' |
15
|
|
|
use Charcoal\App\Script\AbstractScript; |
16
|
|
|
use Charcoal\App\Script\CronScriptInterface; |
17
|
|
|
use Charcoal\App\Script\CronScriptTrait; |
18
|
|
|
|
19
|
|
|
// From 'charcoal-factory' |
20
|
|
|
use Charcoal\Factory\FactoryInterface; |
21
|
|
|
|
22
|
|
|
// From 'charcoal-email' |
23
|
|
|
use Charcoal\Email\EmailQueueManager; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Script: Process Email Queue |
27
|
|
|
* |
28
|
|
|
* Can also be used as a cron script. |
29
|
|
|
*/ |
30
|
|
|
class ProcessQueueScript extends AbstractScript implements CronScriptInterface |
31
|
|
|
{ |
32
|
|
|
use CronScriptTrait; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var FactoryInterface |
36
|
|
|
*/ |
37
|
|
|
private $queueItemFactory; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Process all messages currently in queue. |
41
|
|
|
* |
42
|
|
|
* @param RequestInterface $request A PSR-7 compatible Request instance. |
43
|
|
|
* @param ResponseInterface $response A PSR-7 compatible Response instance. |
44
|
|
|
* @return ResponseInterface |
45
|
|
|
*/ |
46
|
|
|
public function run(RequestInterface $request, ResponseInterface $response): ResponseInterface |
47
|
|
|
{ |
48
|
|
|
// Unused parameter |
49
|
|
|
unset($request); |
50
|
|
|
|
51
|
|
|
// Lock script; Ensure it can not be run twice at the same time. |
52
|
|
|
$this->startLock(); |
53
|
|
|
|
54
|
|
|
$cli = $this->climate(); |
55
|
|
|
|
56
|
|
|
if ($this->dryRun()) { |
57
|
|
|
$cli->shout('This command does not support --dry-run'); |
58
|
|
|
$cli->br(); |
59
|
|
|
$cli->whisper('Doing nothing'); |
60
|
|
|
return $response; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
$queueManager = $this->makeQueueManager(); |
64
|
|
|
$queueManager->processQueue(); |
65
|
|
|
|
66
|
|
|
// Unlock script |
67
|
|
|
$this->stopLock(); |
68
|
|
|
|
69
|
|
|
return $response; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Default script arguments. |
74
|
|
|
* |
75
|
|
|
* @return array |
76
|
|
|
*/ |
77
|
|
|
public function defaultArguments() |
78
|
|
|
{ |
79
|
|
|
$arguments = [ |
80
|
|
|
'queue-id' => [ |
81
|
|
|
'longPrefix' => 'queue-id', |
82
|
|
|
'description' => 'The queue to process. If blank, all queues will be processed.', |
83
|
|
|
'defaultValue' => null, |
84
|
|
|
'castTo' => 'string', |
85
|
|
|
], |
86
|
|
|
'rate' => [ |
87
|
|
|
'longPrefix' => 'rate', |
88
|
|
|
'description' => 'Number of items to process per second.', |
89
|
|
|
'defaultValue' => 50, |
90
|
|
|
'castTo' => 'int', |
91
|
|
|
], |
92
|
|
|
'limit' => [ |
93
|
|
|
'longPrefix' => 'limit', |
94
|
|
|
'description' => 'Maximum number of items to process.', |
95
|
|
|
'defaultValue' => null, |
96
|
|
|
'castTo' => 'int', |
97
|
|
|
], |
98
|
|
|
'chunk-size' => [ |
99
|
|
|
'longPrefix' => 'chunk-size', |
100
|
|
|
'description' => 'Number of items to keep in memory at a given time.', |
101
|
|
|
'defaultValue' => 100, |
102
|
|
|
'castTo' => 'int', |
103
|
|
|
], |
104
|
|
|
]; |
105
|
|
|
|
106
|
|
|
$arguments = array_merge(parent::defaultArguments(), $arguments); |
107
|
|
|
return $arguments; |
|
|
|
|
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Create and prepare the queue manager. |
112
|
|
|
* |
113
|
|
|
* @return EmailQueueManager |
114
|
|
|
*/ |
115
|
|
|
protected function makeQueueManager() |
116
|
|
|
{ |
117
|
|
|
$cli = $this->climate(); |
118
|
|
|
|
119
|
|
|
$data = [ |
120
|
|
|
'logger' => $this->logger, |
121
|
|
|
'queue_item_factory' => $this->queueItemFactory, |
122
|
|
|
'chunkSize' => 100, |
123
|
|
|
]; |
124
|
|
|
|
125
|
|
|
$rate = $cli->arguments->get('rate'); |
126
|
|
|
if ($rate !== null) { |
127
|
|
|
$data['rate'] = $rate; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$limit = $cli->arguments->get('limit'); |
131
|
|
|
if ($limit !== null) { |
132
|
|
|
$data['limit'] = $limit; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$chunkSize = $cli->arguments->get('chunk-size'); |
136
|
|
|
if ($chunkSize !== null) { |
137
|
|
|
$data['chunkSize'] = $chunkSize; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
$class = $this->getQueueManagerClass(); |
141
|
|
|
$queueManager = new $class($data); |
142
|
|
|
$queueManager->setProcessedCallback($this->getProcessedQueueCallback()); |
143
|
|
|
|
144
|
|
|
$queueId = $cli->arguments->get('queue-id'); |
145
|
|
|
if ($queueId !== null) { |
146
|
|
|
$data['queue_id'] = $queueId; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
return $queueManager; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Retrieve the class name of the queue manager model. |
154
|
|
|
* |
155
|
|
|
* @return string |
156
|
|
|
*/ |
157
|
|
|
protected function getQueueManagerClass(): string |
158
|
|
|
{ |
159
|
|
|
return EmailQueueManager::class; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @return Closure |
164
|
|
|
*/ |
165
|
|
|
protected function getProcessedQueueCallback(): callable |
166
|
|
|
{ |
167
|
|
|
$climate = $this->climate(); |
168
|
|
|
|
169
|
|
|
$callback = function ($success, $failures, $skipped) use ($climate): void { |
170
|
|
|
if (!empty($success)) { |
171
|
|
|
$climate->green()->out(sprintf('%s emails were successfully sent.', count($success))); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
if (!empty($failures)) { |
175
|
|
|
$climate->red()->out(sprintf('%s emails failed to be sent', count($failures))); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
if (!empty($skipped)) { |
179
|
|
|
$climate->dim()->out(sprintf('%s emails were skipped.', count($skipped))); |
180
|
|
|
} |
181
|
|
|
}; |
182
|
|
|
|
183
|
|
|
return $callback; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @param Container $container Pimple DI container. |
188
|
|
|
* @return void |
189
|
|
|
*/ |
190
|
|
|
protected function setDependencies(Container $container): void |
191
|
|
|
{ |
192
|
|
|
parent::setDependencies($container); |
193
|
|
|
$this->setQueueItemFactory($container['model/factory']); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* @param FactoryInterface $factory The factory to create queue items. |
198
|
|
|
* @return void |
199
|
|
|
*/ |
200
|
|
|
private function setQueueItemFactory(FactoryInterface $factory): void |
201
|
|
|
{ |
202
|
|
|
$this->queueItemFactory = $factory; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.