内容简介:当我使用 Laravel 的邮件发送功能时,脑子里浮现出这么几个问题:下面就让我们开始徒手扒一扒「邮件发送功能」的实现原理。我们使用阿里云提供的免费邮,和采用「smtp」驱动,作为测试,参考
当我使用 Laravel 的邮件发送功能时,脑子里浮现出这么几个问题:
-
Laravel 集成了 SMTP 、Mailgun 、SparkPost 、 Amazon SES 等驱动,是怎么做到的?
-
Laravel 提供全文本格式、网页格式和 Markdown 格式,是怎么实现的?
-
整个邮件发送流程是什么样的?
下面就让我们开始徒手扒一扒「邮件发送功能」的实现原理。
写个 demo
我们使用阿里云提供的免费邮,和采用「smtp」驱动,作为测试,参考 .env
配置:
MAIL_DRIVER=smtp MAIL_HOST=smtp.mxhichina.com MAIL_PORT=25 MAIL_USERNAME=***@coding01.cn MAIL_PASSWORD=**** MAIL_ENCRYPTION=tls MAIL_FROM=***@coding01.cn MAIL_NAME=coding01 复制代码
写个测试流程,还是挺简单的,具体如下:
// 1. 创建测试类 php artisan make:mail TestEmail // 2. 在 TestEmail 类,载入视图 public function build() { return $this->view('mail.test'); } // 3. 输出 hello coding01 <p>hello coding01</p> 复制代码
最后写个命令函数:
Artisan::command('test', function () { Mail::to('yemeishu@126.com')->send(new \App\Mail\TestEmail()); }); 复制代码
执行 php artisan test
看测试是否发送成功:
解析 MailServiceProvider
写了不少 Laravel 代码,看
Mail::to('yemeishu@126.com')->send(new \App\Mail\TestEmail()); 复制代码
自然而然的想到是不是有一个 MailServiceProvider
,果不其然,在 config/app.php
的数组 providers
就包含了该 ServiceProvider
所以我们就开始围绕这个 MailServiceProvider
来解析了
/** * Register the service provider. * * @return void */ public function register() { $this->registerSwiftMailer(); $this->registerIlluminateMailer(); $this->registerMarkdownRenderer(); } 复制代码
看 register
函数,一目了然,我们将重点看看这三个方法都是干嘛用的。
registerSwiftMailer
看代码:
/** * Register the Swift Mailer instance. * * @return void */ public function registerSwiftMailer() { $this->registerSwiftTransport(); // Once we have the transporter registered, we will register the actual Swift // mailer instance, passing in the transport instances, which allows us to // override this transporter instances during app start-up if necessary. $this->app->singleton('swift.mailer', function ($app) { if ($domain = $app->make('config')->get('mail.domain')) { Swift_DependencyContainer::getInstance() ->register('mime.idgenerator.idright') ->asValue($domain); } return new Swift_Mailer($app['swift.transport']->driver()); }); } 复制代码
很好理解,就是注册 Swift Mailer
实例。在创建实例之前,执行 $this->registerSwiftTransport();
方法:
/** * Register the Swift Transport instance. * * @return void */ protected function registerSwiftTransport() { $this->app->singleton('swift.transport', function ($app) { return new TransportManager($app); }); } 复制代码
看看这个 TransportManager
类是干嘛用的:
<?php namespace Illuminate\Mail; use Aws\Ses\SesClient; use Illuminate\Support\Arr; use Psr\Log\LoggerInterface; use Illuminate\Support\Manager; use GuzzleHttp\Client as HttpClient; use Swift_SmtpTransport as SmtpTransport; use Illuminate\Mail\Transport\LogTransport; use Illuminate\Mail\Transport\SesTransport; use Illuminate\Mail\Transport\ArrayTransport; use Swift_SendmailTransport as MailTransport; use Illuminate\Mail\Transport\MailgunTransport; use Illuminate\Mail\Transport\MandrillTransport; use Illuminate\Mail\Transport\SparkPostTransport; use Swift_SendmailTransport as SendmailTransport; class TransportManager extends Manager { /** * Create an instance of the SMTP Swift Transport driver. * * @return \Swift_SmtpTransport */ protected function createSmtpDriver() { $config = $this->app->make('config')->get('mail'); // The Swift SMTP transport instance will allow us to use any SMTP backend // for delivering mail such as Sendgrid, Amazon SES, or a custom server // a developer has available. We will just pass this configured host. $transport = new SmtpTransport($config['host'], $config['port']); if (isset($config['encryption'])) { $transport->setEncryption($config['encryption']); } // Once we have the transport we will check for the presence of a username // and password. If we have it we will set the credentials on the Swift // transporter instance so that we'll properly authenticate delivery. if (isset($config['username'])) { $transport->setUsername($config['username']); $transport->setPassword($config['password']); } // Next we will set any stream context options specified for the transport // and then return it. The option is not required any may not be inside // the configuration array at all so we'll verify that before adding. if (isset($config['stream'])) { $transport->setStreamOptions($config['stream']); } return $transport; } /** * Create an instance of the Sendmail Swift Transport driver. * * @return \Swift_SendmailTransport */ protected function createSendmailDriver() { return new SendmailTransport($this->app['config']['mail']['sendmail']); } /** * Create an instance of the Amazon SES Swift Transport driver. * * @return \Illuminate\Mail\Transport\SesTransport */ protected function createSesDriver() { $config = array_merge($this->app['config']->get('services.ses', []), [ 'version' => 'latest', 'service' => 'email', ]); return new SesTransport(new SesClient( $this->addSesCredentials($config) )); } /** * Add the SES credentials to the configuration array. * * @param array $config * @return array */ protected function addSesCredentials(array $config) { if ($config['key'] && $config['secret']) { $config['credentials'] = Arr::only($config, ['key', 'secret']); } return $config; } /** * Create an instance of the Mail Swift Transport driver. * * @return \Swift_SendmailTransport */ protected function createMailDriver() { return new MailTransport; } /** * Create an instance of the Mailgun Swift Transport driver. * * @return \Illuminate\Mail\Transport\MailgunTransport */ protected function createMailgunDriver() { $config = $this->app['config']->get('services.mailgun', []); return new MailgunTransport( $this->guzzle($config), $config['secret'], $config['domain'] ); } /** * Create an instance of the Mandrill Swift Transport driver. * * @return \Illuminate\Mail\Transport\MandrillTransport */ protected function createMandrillDriver() { $config = $this->app['config']->get('services.mandrill', []); return new MandrillTransport( $this->guzzle($config), $config['secret'] ); } /** * Create an instance of the SparkPost Swift Transport driver. * * @return \Illuminate\Mail\Transport\SparkPostTransport */ protected function createSparkPostDriver() { $config = $this->app['config']->get('services.sparkpost', []); return new SparkPostTransport( $this->guzzle($config), $config['secret'], $config['options'] ?? [] ); } /** * Create an instance of the Log Swift Transport driver. * * @return \Illuminate\Mail\Transport\LogTransport */ protected function createLogDriver() { return new LogTransport($this->app->make(LoggerInterface::class)); } /** * Create an instance of the Array Swift Transport Driver. * * @return \Illuminate\Mail\Transport\ArrayTransport */ protected function createArrayDriver() { return new ArrayTransport; } /** * Get a fresh Guzzle HTTP client instance. * * @param array $config * @return \GuzzleHttp\Client */ protected function guzzle($config) { return new HttpClient(Arr::add( $config['guzzle'] ?? [], 'connect_timeout', 60 )); } /** * Get the default mail driver name. * * @return string */ public function getDefaultDriver() { return $this->app['config']['mail.driver']; } /** * Set the default mail driver name. * * @param string $name * @return void */ public function setDefaultDriver($name) { $this->app['config']['mail.driver'] = $name; } } 复制代码
通过观察,可以看出, TransportManager
主要是为了创建各种驱动:
-
Smtp
—— 创建Swift_SmtpTransport
实例对象,主要使用的参数为:host
、port
、encryption
、username
、password
、stream
; -
Sendmail
、Mail
—— 创建Swift_SendmailTransport
实例对象,使用的参数为:sendmail
; -
Ses
—— 创建SesTransport
实例对象,使用的参数为config/services
下对应的值:
'ses' => [ 复制代码
'key' => env('SES_KEY'), 'secret' => env('SES_SECRET'), 'region' => 'us-east-1', 复制代码
],
- `Mailgun` —— 创建 `MailgunTransport` 实例对象,使用的参数为 `config/services` 下对应的值: 复制代码
'mailgun' => [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), ],
- `Mandrill` —— 创建 `MandrillTransport` 实例对象,使用的参数为 `config/services` 下对应的值:「暂无」,可以自行添加 - `SparkPost` —— 创建 `SparkPostTransport` 实例对象,使用的参数为 `config/services` 下对应的值: 复制代码
'sparkpost' => [ 'secret' => env('SPARKPOST_SECRET'), ],
此外,就是创建 `Log` 驱动,和设置默认的驱动,由 `app['config']['mail.driver']` 决定的。 复制代码
通过上文,我们还可以看出在使用 Mailgun
、 Mandrill
或者 SparkPost
都需要使用插件 guzzle
,这也是为什么官网提示要安装 guzzle
插件的原因了:
同时,这些驱动类都是 extends Illuminate\Mail\Transport
,而且抽象类 Transport
是实现 Swift_Transport
接口:
<?php /* * This file is part of SwiftMailer. * (c) 2004-2009 Chris Corbyn * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * Sends Messages via an abstract Transport subsystem. * * @author Chris Corbyn */ interface Swift_Transport { /** * Test if this Transport mechanism has started. * * @return bool */ public function isStarted(); /** * Start this Transport mechanism. */ public function start(); /** * Stop this Transport mechanism. */ public function stop(); /** * Check if this Transport mechanism is alive. * * If a Transport mechanism session is no longer functional, the method * returns FALSE. It is the responsibility of the developer to handle this * case and restart the Transport mechanism manually. * * @example * * if (!$transport->ping()) { * $transport->stop(); * $transport->start(); * } * * The Transport mechanism will be started, if it is not already. * * It is undefined if the Transport mechanism attempts to restart as long as * the return value reflects whether the mechanism is now functional. * * @return bool TRUE if the transport is alive */ public function ping(); /** * Send the given Message. * * Recipient/sender data will be retrieved from the Message API. * The return value is the number of recipients who were accepted for delivery. * * @param Swift_Mime_SimpleMessage $message * @param string[] $failedRecipients An array of failures by-reference * * @return int */ public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null); /** * Register a plugin in the Transport. * * @param Swift_Events_EventListener $plugin */ public function registerPlugin(Swift_Events_EventListener $plugin); } 复制代码
我们利用 PhpStorm
查看有多少类实现该接口:
好了,有了创建驱动的实例,接下来就是创建 Swift_Mailer
对象实例了:
$this->app->singleton('swift.mailer', function ($app) { ... return new Swift_Mailer($app['swift.transport']->driver()); }); 复制代码
下面借助 $app['swift.transport']->driver()
函数来说一说怎么拿到我们指定的驱动。
从 TransportManager
的父类 Manager
抽象类找到 driver()
函数:
/** * Get the default driver name. * * @return string */ abstract public function getDefaultDriver(); /** * Get a driver instance. * * @param string $driver * @return mixed */ public function driver($driver = null) { $driver = $driver ?: $this->getDefaultDriver(); if (is_null($driver)) { throw new InvalidArgumentException(sprintf( 'Unable to resolve NULL driver for [%s].', static::class )); } // If the given driver has not been created before, we will create the instances // here and cache it so we can return it next time very quickly. If there is // already a driver created by this name, we'll just return that instance. if (! isset($this->drivers[$driver])) { $this->drivers[$driver] = $this->createDriver($driver); } return $this->drivers[$driver]; } 复制代码
主要的使用各个继承类 ( TransportManager
) 实现的 $this->getDefaultDriver()
/** * Get the default mail driver name. * * @return string */ public function getDefaultDriver() { return $this->app['config']['mail.driver']; } 复制代码
这就好理解了,指定的驱动是由 config
自主指定的;当拿到驱动名称后,我们回到 driver()
函数,继续往下看到代码:
if (! isset($this->drivers[$driver])) { $this->drivers[$driver] = $this->createDriver($driver); } // 注:$this->createDriver($driver) 这才是真正创建指定驱动的方法 /** * Create a new driver instance. * * @param string $driver * @return mixed * * @throws \InvalidArgumentException */ protected function createDriver($driver) { // We'll check to see if a creator method exists for the given driver. If not we // will check for a custom driver creator, which allows developers to create // drivers using their own customized driver creator Closure to create it. if (isset($this->customCreators[$driver])) { return $this->callCustomCreator($driver); } else { $method = 'create'.Str::studly($driver).'Driver'; if (method_exists($this, $method)) { return $this->$method(); } } throw new InvalidArgumentException("Driver [$driver] not supported."); } 复制代码
当然我们的目标就定在这里:
$method = 'create'.Str::studly($driver).'Driver'; if (method_exists($this, $method)) { return $this->$method(); } 复制代码
通过拿到的「驱动名称」,拼接成函数名,假如我们的驱动名称为: mailgun
,则函数名: createMailgunDriver
,然后就可以直接执行该方法,拿到对应的驱动对象实例了。
注:推荐看看这个 Str::studly($driver)
函数源码
到此,我们知道了如何利用 config
配置文件,来创建指定的驱动器,最后创建 Swift_Mailer
对象,以供之后执行使用。
registerIlluminateMailer
看代码:
/** * Register the Illuminate mailer instance. * * @return void */ protected function registerIlluminateMailer() { $this->app->singleton('mailer', function ($app) { $config = $app->make('config')->get('mail'); // Once we have create the mailer instance, we will set a container instance // on the mailer. This allows us to resolve mailer classes via containers // for maximum testability on said classes instead of passing Closures. $mailer = new Mailer( $app['view'], $app['swift.mailer'], $app['events'] ); if ($app->bound('queue')) { $mailer->setQueue($app['queue']); } // Next we will set all of the global addresses on this mailer, which allows // for easy unification of all "from" addresses as well as easy debugging // of sent messages since they get be sent into a single email address. foreach (['from', 'reply_to', 'to'] as $type) { $this->setGlobalAddress($mailer, $config, $type); } return $mailer; }); } 复制代码
光看这个,比较简单,就是传入 view
、第一步创建好的邮件发送器 Swift_Mailer
对象,和 events
事件分发器,如果有队列,传入队列,创建 Illuminate mailer
对象,供我们真正场景使用;最后就是配置全局参数了。
registerMarkdownRenderer
Laravel 能够捕获很多开发者的:two_hearts:,还有一个核心的地方在于:知道开发者想要什么。其中 Markdown 基本就是开发者的必备。用 Markdown 写邮件,是一个不错的方案,下面看看怎么做到的?
为了扒 Markdown
代码,先写个 demo 看怎么使用。
使用命令,带上 --markdown
选项:
php artisan make:mail TestMdEmail --markdown=mail.testmd 复制代码
这样就可以为我们创建了 TestMdEmail
类
<?php namespace App\Mail; use Illuminate\Bus\Queueable; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Queue\ShouldQueue; class TestMdEmail extends Mailable { use Queueable, SerializesModels; /** * Create a new message instance. * * @return void */ public function __construct() { // } /** * Build the message. * * @return $this */ public function build() { return $this->markdown('mail.testmd'); } } 复制代码
和视图 testmd.blade.php
,默认视图内容:
@component('mail::message') # Introduction The body of your message. @component('mail::button', ['url' => '']) Button Text @endcomponent Thanks,<br> {{ config('app.name') }} @endcomponent 复制代码
写个测试,发送看看运行效果:
Artisan::command('testmd', function () { Mail::to('yemeishu@126.com')->send(new \App\Mail\TestMdEmail()); }); 复制代码
一切使用默认的,就可以很轻易的创建 markdown
格式的邮件内容,并发送。
我们可以看看源码了:
/** * Register the Markdown renderer instance. * * @return void */ protected function registerMarkdownRenderer() { if ($this->app->runningInConsole()) { $this->publishes([ __DIR__.'/resources/views' => $this->app->resourcePath('views/vendor/mail'), ], 'laravel-mail'); } $this->app->singleton(Markdown::class, function ($app) { $config = $app->make('config'); return new Markdown($app->make('view'), [ 'theme' => $config->get('mail.markdown.theme', 'default'), 'paths' => $config->get('mail.markdown.paths', []), ]); }); } 复制代码
目标很简单,就是利用配置信息,创建 Markdown
对象,为后续服务。
我们先看默认的 mail config
:
/* |-------------------------------------------------------------------------- | Markdown Mail Settings |-------------------------------------------------------------------------- | | If you are using Markdown based email rendering, you may configure your | theme and component paths here, allowing you to customize the design | of the emails. Or, you may simply stick with the Laravel defaults! | */ 'markdown' => [ 'theme' => 'default', 'paths' => [ resource_path('views/vendor/mail'), ], ], 复制代码
默认的 markdown
配置信息都存在 views/vendor/mail
文件夹下,我们可以通过命令:
$ php artisan vendor:publish --tag=laravel-mail Copied Directory [/vendor/laravel/framework/src/Illuminate/Mail/resources/views] To [/resources/views/vendor/mail] Publishing complete. 复制代码
所有的默认组件都存在这个文件夹下,还有页面的视图样式主题等:
*注:*我们可以自定组件和增加发布邮箱的 css
样式
看 Maikdown
构造函数:
/** * Create a new Markdown renderer instance. * * @param \Illuminate\Contracts\View\Factory $view * @param array $options * @return void */ public function __construct(ViewFactory $view, array $options = []) { $this->view = $view; $this->theme = $options['theme'] ?? 'default'; $this->loadComponentsFrom($options['paths'] ?? []); } 复制代码
主要是传入 View
视图构造器和主题样式,以及各个 markdown
组件。
邮件发送流程
下面我们结合上面的 demo 看看如何构造邮件内容,和发送邮件的,我们看代码:
Mail::to('yemeishu@126.com')->send(new \App\Mail\TestMdEmail()); 复制代码
这里的 Mail
就是上面 registerIlluminateMailer
注册的 Illuminate\Mail\Mailer
对象。
我们且看它的 send()
方法:
/** * Send a new message using a view. * * @param string|array|MailableContract $view * @param array $data * @param \Closure|string $callback * @return void */ public function send($view, array $data = [], $callback = null) { if ($view instanceof MailableContract) { return $this->sendMailable($view); } // First we need to parse the view, which could either be a string or an array // containing both an HTML and plain text versions of the view which should // be used when sending an e-mail. We will extract both of them out here. list($view, $plain, $raw) = $this->parseView($view); $data['message'] = $message = $this->createMessage(); // Once we have retrieved the view content for the e-mail we will set the body // of this message using the HTML type, which will provide a simple wrapper // to creating view based emails that are able to receive arrays of data. call_user_func($callback, $message); $this->addContent($message, $view, $plain, $raw, $data); // If a global "to" address has been set, we will set that address on the mail // message. This is primarily useful during local development in which each // message should be delivered into a single mail address for inspection. if (isset($this->to['address'])) { $this->setGlobalTo($message); } // Next we will determine if the message should be sent. We give the developer // one final chance to stop this message and then we will send it to all of // its recipients. We will then fire the sent event for the sent message. $swiftMessage = $message->getSwiftMessage(); if ($this->shouldSendMessage($swiftMessage, $data)) { $this->sendSwiftMessage($swiftMessage); $this->dispatchSentEvent($message, $data); } } 复制代码
我们看第一步:
if ($view instanceof MailableContract) { return $this->sendMailable($view); } 复制代码
执行的 $this->sendMailable($view)
:
/** * Send the message using the given mailer. * * @param \Illuminate\Contracts\Mail\Mailer $mailer * @return void */ public function send(MailerContract $mailer) { $translator = Container::getInstance()->make(Translator::class); $this->withLocale($this->locale, $translator, function () use ($mailer) { Container::getInstance()->call([$this, 'build']); $mailer->send($this->buildView(), $this->buildViewData(), function ($message) { $this->buildFrom($message) ->buildRecipients($message) ->buildSubject($message) ->runCallbacks($message) ->buildAttachments($message); }); }); } 复制代码
核心的在于先执行我们默认 build
方法:
/** * Build the message. * * @return $this */ public function build() { return $this->markdown('mail.testmd'); } 复制代码
这就是为什么在命令创建发送邮件模板类时,都会默认创建该 build
方法了,然后在该方法里,载入我们的构建内容和逻辑;在 markdown
视图中,默认的是运行 $this->markdown('mail.testmd')
:
/** * Set the Markdown template for the message. * * @param string $view * @param array $data * @return $this */ public function markdown($view, array $data = []) { $this->markdown = $view; $this->viewData = array_merge($this->viewData, $data); return $this; } 复制代码
将视图和视图内容载入对象中。
然后我们继续回到上个 send
方法中:
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) { $this->buildFrom($message) ->buildRecipients($message) ->buildSubject($message) ->runCallbacks($message) ->buildAttachments($message); }); 复制代码
我们一个个方法来解析:
$this->buildView()
/** * Build the view for the message. * * @return array|string */ protected function buildView() { if (isset($this->html)) { return array_filter([ 'html' => new HtmlString($this->html), 'text' => isset($this->textView) ? $this->textView : null, ]); } if (isset($this->markdown)) { return $this->buildMarkdownView(); } if (isset($this->view, $this->textView)) { return [$this->view, $this->textView]; } elseif (isset($this->textView)) { return ['text' => $this->textView]; } return $this->view; } 复制代码
很显然,执行 $this->buildMarkdownView()
/** * Build the Markdown view for the message. * * @return array */ protected function buildMarkdownView() { $markdown = Container::getInstance()->make(Markdown::class); if (isset($this->theme)) { $markdown->theme($this->theme); } $data = $this->buildViewData(); return [ 'html' => $markdown->render($this->markdown, $data), 'text' => $this->buildMarkdownText($markdown, $data), ]; } 复制代码
这时候, Markdown
对象就派上用场了,目标该放在这两个方法上了:
return [ 'html' => $markdown->render($this->markdown, $data), 'text' => $this->buildMarkdownText($markdown, $data), ]; 复制代码
看 $markdown->render()
方法:
/** * Render the Markdown template into HTML. * * @param string $view * @param array $data * @param \TijsVerkoyen\CssToInlineStyles\CssToInlineStyles|null $inliner * @return \Illuminate\Support\HtmlString */ public function render($view, array $data = [], $inliner = null) { $this->view->flushFinderCache(); $contents = $this->view->replaceNamespace( 'mail', $this->htmlComponentPaths() )->make($view, $data)->render(); return new HtmlString(($inliner ?: new CssToInlineStyles)->convert( $contents, $this->view->make('mail::themes.'.$this->theme)->render() )); } 复制代码
和 $markdown->renderText()
方法:
/** * Render the Markdown template into HTML. * * @param string $view * @param array $data * @return \Illuminate\Support\HtmlString */ public function renderText($view, array $data = []) { $this->view->flushFinderCache(); $contents = $this->view->replaceNamespace( 'mail', $this->markdownComponentPaths() )->make($view, $data)->render(); return new HtmlString( html_entity_decode(preg_replace("/[\r\n]{2,}/", "\n\n", $contents), ENT_QUOTES, 'UTF-8') ); } 复制代码
主要的逻辑,就是将 markdown
格式转变成 html
格式,以及构成数组 ['html', 'data']
输出,最后再次执行 send
方法,并传入闭包函数,供构建 message
服务:
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) { $this->buildFrom($message) ->buildRecipients($message) ->buildSubject($message) ->runCallbacks($message) ->buildAttachments($message); }); 复制代码
我们回头再看 send
方法,未解析的代码:
// First we need to parse the view, which could either be a string or an array // containing both an HTML and plain text versions of the view which should // be used when sending an e-mail. We will extract both of them out here. list($view, $plain, $raw) = $this->parseView($view); $data['message'] = $message = $this->createMessage(); // Once we have retrieved the view content for the e-mail we will set the body // of this message using the HTML type, which will provide a simple wrapper // to creating view based emails that are able to receive arrays of data. call_user_func($callback, $message); $this->addContent($message, $view, $plain, $raw, $data); // If a global "to" address has been set, we will set that address on the mail // message. This is primarily useful during local development in which each // message should be delivered into a single mail address for inspection. if (isset($this->to['address'])) { $this->setGlobalTo($message); } // Next we will determine if the message should be sent. We give the developer // one final chance to stop this message and then we will send it to all of // its recipients. We will then fire the sent event for the sent message. $swiftMessage = $message->getSwiftMessage(); if ($this->shouldSendMessage($swiftMessage, $data)) { $this->sendSwiftMessage($swiftMessage); $this->dispatchSentEvent($message, $data); } 复制代码
第一步无非就是将上面的数组遍历出来,然后再创建 Message
对象:
$data['message'] = $message = $this->createMessage(); /** * Create a new message instance. * * @return \Illuminate\Mail\Message */ protected function createMessage() { $message = new Message($this->swift->createMessage('message')); // If a global from address has been specified we will set it on every message // instance so the developer does not have to repeat themselves every time // they create a new message. We'll just go ahead and push this address. if (! empty($this->from['address'])) { $message->from($this->from['address'], $this->from['name']); } // When a global reply address was specified we will set this on every message // instance so the developer does not have to repeat themselves every time // they create a new message. We will just go ahead and push this address. if (! empty($this->replyTo['address'])) { $message->replyTo($this->replyTo['address'], $this->replyTo['name']); } return $message; } 复制代码
这个 Message
构造函数传入的 swift
服务对象,以后通过 message
传入的数据,都是传给 swift
服务对象。
$message = new Message($this->swift->createMessage('message')); ... /** * Create a new class instance of one of the message services. * * For example 'mimepart' would create a 'message.mimepart' instance * * @param string $service * * @return object */ public function createMessage($service = 'message') { return Swift_DependencyContainer::getInstance() ->lookup('message.'.$service); } 复制代码
如:
/** * Add a "from" address to the message. * * @param string|array $address * @param string|null $name * @return $this */ public function from($address, $name = null) { $this->swift->setFrom($address, $name); return $this; } /** * Set the "sender" of the message. * * @param string|array $address * @param string|null $name * @return $this */ public function sender($address, $name = null) { $this->swift->setSender($address, $name); return $this; } 复制代码
这样,我们就开始使用 MailServiceProvider
中创建的 Swift_Mailer
对象了。
好了,终于到最后一个步骤了:
// Next we will determine if the message should be sent. We give the developer // one final chance to stop this message and then we will send it to all of // its recipients. We will then fire the sent event for the sent message. $swiftMessage = $message->getSwiftMessage(); if ($this->shouldSendMessage($swiftMessage, $data)) { $this->sendSwiftMessage($swiftMessage); $this->dispatchSentEvent($message, $data); } 复制代码
获取 swift
服务对象,然后开始执行发送逻辑,和分发发送邮件事件了。
/** * Send a Swift Message instance. * * @param \Swift_Message $message * @return void */ protected function sendSwiftMessage($message) { try { return $this->swift->send($message, $this->failedRecipients); } finally { $this->forceReconnection(); } } ... /** * Dispatch the message sent event. * * @param \Illuminate\Mail\Message $message * @param array $data * @return void */ protected function dispatchSentEvent($message, $data = []) { if ($this->events) { $this->events->dispatch( new Events\MessageSent($message->getSwiftMessage(), $data) ); } } 复制代码
继续看如何利用 swift
对象发送邮件。
/** * Send a Swift Message instance. * * @param \Swift_Message $message * @return void */ protected function sendSwiftMessage($message) { try { return $this->swift->send($message, $this->failedRecipients); } finally { $this->forceReconnection(); } } 复制代码
看 $this->swift->send()
方法:
/** * Send the given Message like it would be sent in a mail client. * * All recipients (with the exception of Bcc) will be able to see the other * recipients this message was sent to. * * Recipient/sender data will be retrieved from the Message object. * * The return value is the number of recipients who were accepted for * delivery. * * @param Swift_Mime_SimpleMessage $message * @param array $failedRecipients An array of failures by-reference * * @return int The number of successful recipients. Can be 0 which indicates failure */ public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) { $failedRecipients = (array) $failedRecipients; if (!$this->transport->isStarted()) { $this->transport->start(); } $sent = 0; try { $sent = $this->transport->send($message, $failedRecipients); } catch (Swift_RfcComplianceException $e) { foreach ($message->getTo() as $address => $name) { $failedRecipients[] = $address; } } return $sent; } 复制代码
还记得一开始对每个发送驱动做封装了吧, send
动作,最终还是交给我们的邮件发送驱动去执行,默认的是使用 SmtpTransport
,即 Swift_SmtpTransport
发送。
$sent = $this->transport->send($message, $failedRecipients); 复制代码
总结
过了一遍代码,粗略了解下怎么封装各个驱动器,将 markdown
格式转成 html
格式,然后再封装成 Message
对象,交给驱动器去发送邮件。
下一步说一说 Swift_SmtpTransport
实现原理,和我们自己怎么制作一个驱动器,最后再说一说这过程用到了哪些设计模式?
未完待续
以上所述就是小编给大家介绍的《说一说 Laravel 邮件发送流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- SpringBoot系列(十四)集成邮件发送服务及邮件发送的几种方式
- Linux如何用脚本监控Oracle发送警告日志ORA-报错发送邮件
- Android 快速发送邮件
- Python 发送邮件
- Python邮件发送指南
- golang发送邮件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learn Python the Hard Way
Zed Shaw / Example Product Manufacturer / 2011
This is a very beginner book for people who want to learn to code. If you can already code then the book will probably drive you insane. It's intended for people who have no coding chops to build up t......一起来看看 《Learn Python the Hard Way》 这本书的介绍吧!