内容简介:当我使用 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发送邮件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Big Red Fez
Seth Godin / Free Press / 2002-01-15 / USD 11.00
YOUR WEB SITE IS COSTING YOU MONEY. IT'S ALSO FILLED WITH SIMPLE MISTAKES THAT TURN OFF VISITORS BEFORE THEY HAVE A CHANCE TO BECOME CUSTOMERS. According to marketing guru Seth Godin, a web s......一起来看看 《The Big Red Fez》 这本书的介绍吧!