Complex classes like AzineTwigSwiftMailer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AzineTwigSwiftMailer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
18 | class AzineTwigSwiftMailer extends TwigSwiftMailer implements TemplateTwigSwiftMailerInterface |
||
19 | { |
||
20 | /** |
||
21 | * @var Translator |
||
22 | */ |
||
23 | protected $translator; |
||
24 | |||
25 | /** |
||
26 | * @var Logger |
||
27 | */ |
||
28 | protected $logger; |
||
29 | |||
30 | /** |
||
31 | * @var TemplateProviderInterface |
||
32 | */ |
||
33 | protected $templateProvider; |
||
34 | |||
35 | /** |
||
36 | * @var EntityManager |
||
37 | */ |
||
38 | protected $entityManager; |
||
39 | |||
40 | /** |
||
41 | * |
||
42 | * @var RequestContext |
||
43 | */ |
||
44 | protected $routerContext; |
||
45 | |||
46 | /** |
||
47 | * @var email to use for "no-reply" |
||
48 | */ |
||
49 | protected $noReplyEmail; |
||
50 | |||
51 | /** |
||
52 | * @var name to use for "no-reply" |
||
53 | */ |
||
54 | protected $noReplyName; |
||
55 | |||
56 | /** |
||
57 | * The Swift_Mailer to be used for sending emails immediately |
||
58 | * @var \Swift_Mailer |
||
59 | */ |
||
60 | private $immediateMailer; |
||
61 | |||
62 | /** |
||
63 | * @var EmailOpenTrackingCodeBuilderInterface |
||
64 | */ |
||
65 | private $emailOpenTrackingCodeBuilder; |
||
66 | |||
67 | private $encodedItemIdPattern; |
||
68 | private $currentHost; |
||
69 | private $templateCache = array(); |
||
70 | private $imageCache = array(); |
||
71 | |||
72 | |||
73 | /** |
||
74 | * |
||
75 | * @param \Swift_Mailer $mailer |
||
76 | * @param UrlGeneratorInterface $router |
||
77 | * @param \Twig_Environment $twig |
||
78 | * @param Logger $logger |
||
79 | * @param Translator $translator |
||
80 | * @param array $parameters |
||
81 | * @param \Swift_Mailer $immediateMailer |
||
82 | */ |
||
83 | 5 | public function __construct( \Swift_Mailer $mailer, |
|
107 | |||
108 | /** |
||
109 | * (non-PHPdoc) |
||
110 | * @see Azine\EmailBundle\Services.TemplateTwigSwiftMailerInterface::sendEmail() |
||
111 | */ |
||
112 | 5 | public function sendEmail(&$failedRecipients, $subject, $from, $fromName, $to, $toName, $cc, $ccName, $bcc, $bccName, $replyTo, $replyToName, array $params, $template, $attachments = array(), $emailLocale = null, \Swift_Message &$message = null) |
|
113 | { |
||
114 | // create the message |
||
115 | 5 | if ($message == null) { |
|
116 | 5 | $message = \Swift_Message::newInstance(); |
|
117 | 5 | } |
|
118 | |||
119 | 5 | $message->setSubject($subject); |
|
120 | |||
121 | // set the from-Name & -Emali to the default ones if not given |
||
122 | 5 | if ($from == null) { |
|
123 | 2 | $from = $this->noReplyEmail; |
|
124 | 2 | } |
|
125 | 5 | if ($fromName == null) { |
|
126 | 2 | $fromName = $this->noReplyName; |
|
127 | 2 | } |
|
128 | |||
129 | // add the from-email for the footer-text |
||
130 | 5 | if (!array_key_exists('fromEmail', $params)) { |
|
131 | 5 | $params['sendMailAccountName'] = $this->noReplyName; |
|
132 | 5 | $params['sendMailAccountAddress'] = $this->noReplyEmail; |
|
133 | 5 | } |
|
134 | |||
135 | // get the baseTemplate. => templateId without the ending. |
||
136 | 5 | $templateBaseId = substr($template, 0, strrpos($template, ".", -6)); |
|
137 | |||
138 | // check if this email should be stored for web-view |
||
139 | 5 | if ($this->templateProvider->saveWebViewFor($templateBaseId)) { |
|
140 | // keep a copy of the vars for the web-view |
||
141 | 4 | $webViewParams = $params; |
|
142 | |||
143 | // add the web-view token |
||
144 | 4 | $params[$this->templateProvider->getWebViewTokenId()] = SentEmail::getNewToken(); |
|
145 | 4 | } else { |
|
146 | 1 | $webViewParams = array(); |
|
147 | } |
||
148 | |||
149 | // recursively add all template-variables for the wrapper-templates and contentItems |
||
150 | 5 | $params = $this->templateProvider->addTemplateVariablesFor($templateBaseId, $params); |
|
151 | |||
152 | // recursively attach all messages in the array |
||
153 | 5 | $this->embedImages($message, $params); |
|
154 | |||
155 | // change the locale for the email-recipients |
||
156 | 5 | if ($emailLocale !== null && strlen($emailLocale) > 0) { |
|
157 | 4 | $currentUserLocale = $this->translator->getLocale(); |
|
158 | |||
159 | // change the router-context locale |
||
160 | 4 | $this->routerContext->setParameter("_locale", $emailLocale); |
|
161 | |||
162 | // change the translator locale |
||
163 | 4 | $this->translator->setLocale($emailLocale); |
|
164 | 4 | } else { |
|
165 | 1 | $emailLocale = $this->translator->getLocale(); |
|
166 | } |
||
167 | |||
168 | // recursively add snippets for the wrapper-templates and contentItems |
||
169 | 5 | $params = $this->templateProvider->addTemplateSnippetsWithImagesFor($templateBaseId, $params, $emailLocale); |
|
170 | |||
171 | // add the emailLocale (used for web-view) |
||
172 | 5 | $params['emailLocale'] = $emailLocale; |
|
173 | |||
174 | // render the email parts |
||
175 | 5 | $twigTemplate = $this->loadTemplate($template); |
|
176 | 5 | $textBody = $twigTemplate->renderBlock('body_text', $params); |
|
177 | 5 | $message->addPart($textBody, 'text/plain'); |
|
178 | |||
179 | 5 | $htmlBody = $twigTemplate->renderBlock('body_html', $params); |
|
180 | |||
181 | 5 | $campaignParams = $this->templateProvider->getCampaignParamsFor($templateBaseId, $params); |
|
182 | |||
183 | 5 | if (sizeof($campaignParams) > 0) { |
|
184 | 5 | $htmlBody = AzineEmailTwigExtension::addCampaignParamsToAllUrls($htmlBody, $campaignParams); |
|
185 | 5 | } |
|
186 | |||
187 | // if email-tracking is enabled |
||
188 | 5 | if($this->emailOpenTrackingCodeBuilder){ |
|
189 | // add an image at the end of the html tag with the tracking-params to track email-opens |
||
190 | 5 | $imgTrackingCode = $this->emailOpenTrackingCodeBuilder->getTrackingImgCode($templateBaseId, $campaignParams, $params, $message->getId(), $to, $cc, $bcc); |
|
191 | 5 | if($imgTrackingCode && strlen($imgTrackingCode) > 0) { |
|
192 | 3 | $htmlCloseTagPosition = strpos($htmlBody, "</body>"); |
|
193 | 3 | $htmlBody = substr_replace($htmlBody, $imgTrackingCode, $htmlCloseTagPosition, 0); |
|
194 | 3 | } |
|
195 | 5 | } |
|
196 | |||
197 | 5 | $message->setBody($htmlBody, 'text/html'); |
|
198 | |||
199 | // remove unused/unreferenced embeded items from the message |
||
200 | 5 | $message = $this->removeUnreferecedEmbededItemsFromMessage($message, $params, $htmlBody); |
|
201 | |||
202 | // change the locale back to the users locale |
||
203 | 5 | if (isset($currentUserLocale) && $currentUserLocale != null) { |
|
204 | 4 | $this->routerContext->setParameter("_locale", $currentUserLocale); |
|
205 | 4 | $this->translator->setLocale($currentUserLocale); |
|
206 | 4 | } |
|
207 | |||
208 | // add attachments |
||
209 | 5 | foreach ($attachments as $fileName => $file) { |
|
210 | |||
211 | // add attachment from existing file |
||
212 | 2 | if (is_string($file)) { |
|
213 | |||
214 | // check that the file really exists! |
||
215 | 2 | if (file_exists($file)) { |
|
216 | 1 | $attachment = \Swift_Attachment::fromPath($file); |
|
217 | 1 | if (strlen($fileName) >= 5 ) { |
|
218 | 1 | $attachment->setFilename($fileName); |
|
219 | 1 | } |
|
220 | 1 | } else { |
|
221 | 1 | throw new FileException("File not found: ".$file); |
|
222 | } |
||
223 | |||
224 | // add attachment from generated data |
||
225 | 1 | } else { |
|
226 | 1 | $attachment = \Swift_Attachment::newInstance($file, $fileName); |
|
227 | } |
||
228 | |||
229 | 1 | $message->attach($attachment); |
|
230 | 4 | } |
|
231 | |||
232 | // set the addresses |
||
233 | 4 | if ($from) { |
|
234 | 4 | $message->setFrom($from, $fromName); |
|
235 | 4 | } |
|
236 | 4 | if ($replyTo) { |
|
237 | 2 | $message->setReplyTo($replyTo, $replyToName); |
|
238 | 4 | } elseif ($from) { |
|
239 | 2 | $message->setReplyTo($from, $fromName); |
|
240 | 2 | } |
|
241 | 4 | if ($to) { |
|
242 | 4 | $message->setTo($to, $toName); |
|
243 | 4 | } |
|
244 | 4 | if ($cc) { |
|
245 | 2 | $message->setCc($cc, $ccName); |
|
246 | 2 | } |
|
247 | 4 | if ($bcc) { |
|
248 | 2 | $message->setBcc($bcc, $bccName); |
|
249 | 2 | } |
|
250 | |||
251 | // add custom headers |
||
252 | 4 | $this->templateProvider->addCustomHeaders($templateBaseId, $message, $params); |
|
253 | |||
254 | // send the message |
||
255 | 4 | $mailer = $this->getMailer($params); |
|
256 | 4 | $messagesSent = $mailer->send($message, $failedRecipients); |
|
257 | |||
258 | // if the message was successfully sent, |
||
259 | // and it should be made available in web-view |
||
260 | 4 | if ($messagesSent && array_key_exists($this->templateProvider->getWebViewTokenId(), $params)) { |
|
261 | |||
262 | // store the email |
||
263 | 2 | $sentEmail = new SentEmail(); |
|
264 | 2 | $sentEmail->setToken($params[$this->templateProvider->getWebViewTokenId()]); |
|
265 | 2 | $sentEmail->setTemplate($templateBaseId); |
|
266 | 2 | $sentEmail->setSent(new \DateTime()); |
|
267 | |||
268 | // recursively add all template-variables for the wrapper-templates and contentItems |
||
269 | 2 | $webViewParams = $this->templateProvider->addTemplateVariablesFor($template, $webViewParams); |
|
270 | |||
271 | // replace absolute image-paths with relative ones. |
||
272 | 2 | $webViewParams = $this->templateProvider->makeImagePathsWebRelative($webViewParams, $emailLocale); |
|
273 | |||
274 | // recursively add snippets for the wrapper-templates and contentItems |
||
275 | 2 | $webViewParams = $this->templateProvider->addTemplateSnippetsWithImagesFor($template, $webViewParams, $emailLocale, true); |
|
276 | |||
277 | 2 | $sentEmail->setVariables($webViewParams); |
|
278 | |||
279 | // save only successfull recipients |
||
280 | 2 | if (!is_array($to)) { |
|
281 | 2 | $to = array($to); |
|
282 | 2 | } |
|
283 | 2 | $successfulRecipients = array_diff($to, $failedRecipients); |
|
284 | 2 | $sentEmail->setRecipients($successfulRecipients); |
|
285 | |||
286 | // write to db |
||
287 | 2 | $this->entityManager->persist($sentEmail); |
|
288 | 2 | $this->entityManager->flush($sentEmail); |
|
289 | 2 | } |
|
290 | |||
291 | 4 | return $messagesSent; |
|
292 | } |
||
293 | |||
294 | /** |
||
295 | * Remove all Embeded Attachments that are not referenced in the html-body from the message |
||
296 | * to avoid using unneccary bandwidth. |
||
297 | * |
||
298 | * @param \Swift_Message $message |
||
299 | * @param array $params the parameters used to render the html |
||
300 | * @param string $htmlBody |
||
301 | * @return \Swift_Message |
||
302 | */ |
||
303 | 5 | private function removeUnreferecedEmbededItemsFromMessage(\Swift_Message $message, $params, $htmlBody) |
|
304 | { |
||
305 | 5 | foreach ($params as $key => $value) { |
|
306 | // remove unreferenced attachments from contentItems too. |
||
307 | 5 | if ($key === 'contentItems') { |
|
308 | 2 | foreach ($value as $contentItemParams) { |
|
309 | 2 | $message = $this->removeUnreferecedEmbededItemsFromMessage($message, $contentItemParams, $htmlBody); |
|
310 | 2 | } |
|
311 | 2 | } else { |
|
312 | |||
313 | // check if the embeded items are referenced in the templates |
||
314 | 5 | $isEmbededItem = is_string($value) && preg_match($this->encodedItemIdPattern, $value) == 1; |
|
315 | |||
316 | 5 | if ($isEmbededItem && stripos($htmlBody, $value) === false) { |
|
317 | // remove unreferenced items |
||
318 | 5 | $children = array(); |
|
319 | |||
320 | 5 | foreach ($message->getChildren() as $attachment) { |
|
321 | 5 | if ("cid:".$attachment->getId() != $value) { |
|
322 | 5 | $children[] = $attachment; |
|
323 | 5 | } |
|
324 | 5 | } |
|
325 | |||
326 | 5 | $message->setChildren($children); |
|
327 | 5 | } |
|
328 | } |
||
329 | 5 | } |
|
330 | |||
331 | 5 | return $message; |
|
332 | 5 | } |
|
333 | |||
334 | /** |
||
335 | * Get the template from the cache if it was loaded already |
||
336 | * @param string $template |
||
337 | * @return \Twig_Template |
||
338 | */ |
||
339 | 5 | private function loadTemplate($template) |
|
347 | |||
348 | /** |
||
349 | * Recursively embed all images in the array into the message |
||
350 | * @param \Swift_Message $message |
||
351 | * @param array $params |
||
352 | * @return array $params |
||
353 | */ |
||
354 | 5 | private function embedImages(&$message, &$params) |
|
355 | { |
||
356 | // loop throug the array |
||
357 | 5 | foreach ($params as $key => $value) { |
|
358 | |||
359 | // if the current value is an array |
||
360 | 5 | if (is_array($value)) { |
|
361 | // search for more images deeper in the arrays |
||
362 | 2 | $value = $this->embedImages($message, $value); |
|
363 | 2 | $params[$key] = $value; |
|
364 | |||
365 | // if the current value is an existing file from the image-folder, embed it |
||
366 | 5 | } elseif (is_string($value)) { |
|
367 | 5 | if (is_file($value)) { |
|
368 | |||
369 | // check if the file is from an allowed folder |
||
370 | 5 | if ($this->templateProvider->isFileAllowed($value) !== false) { |
|
371 | 5 | $encodedImage = $this->cachedEmbedImage($value); |
|
372 | 5 | if ($encodedImage != null) { |
|
373 | 5 | $id = $message->embed($encodedImage); |
|
374 | 5 | $params[$key] = $id; |
|
375 | 5 | } |
|
376 | 5 | } |
|
377 | |||
378 | // the $filePath isn't a regular file |
||
379 | 5 | } else { |
|
380 | // ignore the imageDir itself, but log all other directories and symlinks that were not embeded |
||
381 | 5 | if ($value != $this->templateProvider->getTemplateImageDir() ) { |
|
382 | 5 | $this->logger->info("'$value' is not a regular file and will not be embeded in the email."); |
|
383 | 5 | } |
|
384 | |||
385 | // add a null-value to the cache for this path, so we don't try again. |
||
386 | 5 | $this->imageCache[$value] = null; |
|
387 | } |
||
388 | |||
389 | //if the current value is a generated image |
||
390 | 5 | } elseif (is_resource($value) && stripos(get_resource_type($value), "gd") == 0) { |
|
391 | // get the image-data as string |
||
392 | 1 | ob_start(); |
|
393 | 1 | imagepng($value); |
|
394 | 1 | $imageData = ob_get_clean(); |
|
395 | |||
396 | // encode the image |
||
397 | 1 | $encodedImage = \Swift_Image::newInstance($imageData, "generatedImage".md5($imageData)); |
|
398 | 1 | $id = $message->embed($encodedImage); |
|
399 | 1 | $params[$key] = $id; |
|
400 | 1 | } else { |
|
401 | // don't do anything |
||
402 | } |
||
403 | 5 | } |
|
404 | |||
405 | // remove duplicate-attachments |
||
406 | 5 | $message->setChildren(array_unique($message->getChildren())); |
|
407 | |||
408 | 5 | return $params; |
|
409 | } |
||
410 | |||
411 | /** |
||
412 | * Get the Swift_Image for the file. |
||
413 | * @param string $filePath |
||
414 | * @return \Swift_Image|null |
||
415 | */ |
||
416 | 5 | private function cachedEmbedImage($filePath) |
|
445 | |||
446 | /** |
||
447 | * (non-PHPdoc) |
||
448 | * @see Azine\EmailBundle\Services.TemplateTwigSwiftMailerInterface::sendSingleEmail() |
||
449 | */ |
||
450 | 2 | public function sendSingleEmail($to, $toName, $subject, array $params, $template, $emailLocale, $from = null, $fromName = null, \Swift_Message &$message = null) |
|
457 | |||
458 | /** |
||
459 | * Override the fosuserbundles original sendMessage, to embed template variables etc. into html-emails. |
||
460 | * @param string $templateName |
||
461 | * @param array $context |
||
462 | * @param string $fromEmail |
||
463 | * @param string $toEmail |
||
464 | * @return boolean true if the mail was sent successfully, else false |
||
465 | */ |
||
466 | protected function sendMessage($templateName, $context, $fromEmail, $toEmail) |
||
467 | { |
||
468 | // get the subject from the template |
||
469 | // => make sure the subject block exists in your fos-templates (FOSUserBundle:Registration:email.txt.twig & FOSUserBundle:Resetting:email.txt.twig) |
||
470 | $twigTemplate = $this->loadTemplate($templateName); |
||
471 | $subject = $twigTemplate->renderBlock('subject', $context); |
||
472 | |||
473 | return $this->sendSingleEmail($toEmail, null, $subject, $context, $templateName, $this->translator->getLocale()); |
||
474 | } |
||
475 | |||
476 | /** |
||
477 | * Return the Swift_Mailer to be used for sending mails immediately (e.g. instead of spooling them) if it is configured |
||
478 | * @param $params |
||
479 | * @return \Swift_Mailer |
||
480 | */ |
||
481 | 4 | private function getMailer($params){ |
|
491 | } |
||
492 |
The
EntityManager
might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.