tobento/service-mail

PHP 应用程序的邮件接口。

1.0.5 2024-08-08 12:49 UTC

This package is auto-updated.

Last update: 2024-09-08 12:58:58 UTC


README

使用 Symfony Mailer 作为默认邮件实现方式的 PHP 应用程序邮件接口。

目录

入门

使用以下命令添加运行中的邮件服务项目最新版本。

composer require tobento/service-mail

要求

  • PHP 8.0 或更高版本

亮点

  • 框架无关,适用于任何项目
  • 解耦设计

文档

基本用法

创建和发送消息

use Tobento\Service\Mail\MailerInterface;
use Tobento\Service\Mail\Message;

class SomeService
{
    public function send(MailerInterface $mailer): void
    {
        $message = (new Message())
            ->from('from@example.com')
            ->to('to@example.com')
            //->cc('cc@example.com')
            //->bcc('bcc@example.com')
            //->replyTo('replyto@example.com')
            ->subject('Subject')
            //->textTemplate('welcome-text')
            //->htmlTemplate('welcome')
            //->text('Lorem Ipsum')
            ->html('<p>Lorem Ipsum</p>');

        $mailer->send($message);
    }
}

查看可用的 邮件发送器

查看 消息 了解更多。

消息

电子邮件地址

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Address;

$message = (new Message())
    // email address as a simple string:
    ->from('from@example.com')
    
    // email address and name (optional) as object:
    ->from(new Address('from@example.com', 'Name'))
    
    ->replyTo('replyto@example.com')
    
    // the following methods support multiple addresses
    // as strings or objects:
    ->to('to@example.com', 'anotherTo@example.com')
    
    ->to(
        new Address('to@example.com'),
        new Address('anotherTo@example.com')
    )
    
    ->cc('cc@example.com', new Address('anotherCc@example.com'))
    
    ->bcc('bcc@example.com', new Address('anotherBcc@example.com'));

内容

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Template;

$message = (new Message())
    // content defined as a string:
    ->subject('Subject')
    ->text('Lorem Ipsum')
    ->html('<p>Lorem Ipsum</p>')
    
    // content defined with a template object:
    ->text(new Template(
        name: 'welcome-text',
        data: ['name' => 'John'],
    ))
    
    ->html(new Template('welcome', []))    
    
    // using template methods:
    ->textTemplate(name: 'welcome-text', data: [])
    
    ->htmlTemplate('welcome', []);

标题

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Parameter;
use Tobento\Service\Mail\Address;

$message = (new Message())
    // Text header:
    ->parameter(new Parameter\TextHeader(
        name: 'X-Custom-Header',
        value: 'value',
    ))
    
    // Id header:
    ->parameter(new Parameter\IdHeader(
        name: 'References',
        ids: ['a@example.com', 'b@example.com'],
    ))
    
    // Path header:
    ->parameter(new Parameter\PathHeader(
        name: 'Return-Path',
        address: 'return@example.com',
        
        // or as object
        // address: new Address('return@example.com'),
    ));

文件附件

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Parameter;
use Tobento\Service\Filesystem\File;
use Psr\Http\Message\StreamInterface;

$message = (new Message())
    // File defined as string:
    ->parameter(new Parameter\File(
        file: '/path/to/document.pdf',
        
        // optional parameters:
        filename: 'Document',
        mimeType: 'application/pdf',
    ))

    // File defined with File object:
    ->parameter(new Parameter\File(
        file: new File('/path/to/document.pdf'),
    ))

    // StreamFile:
    ->parameter(new Parameter\StreamFile(
        stream: $stream, // StreamInterface
        filename: 'Filename.png',
        
        // optional parameters:
        mimeType: 'image/png',        
    ))
    
    // ResourceFile:
    ->parameter(new Parameter\ResourceFile(
        resource: fopen('/path/to/image.png', 'r+'),
        filename: 'Image.png',
        
        // optional parameters:
        mimeType: 'image/png',        
    ));    

标签和元数据

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Parameter;

$message = (new Message())
    // Tags:
    ->parameter(new Parameter\Tags(['tagname']))
    
    // Metadata:
    ->parameter(new Parameter\Metadata([
        'name' => 'value',
    ]));

队列

如果你的邮件发送器配置为支持队列,你可以将消息排队。

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Parameter;

$message = (new Message())
    ->parameter(new Parameter\Queue(
        // you may specify the queue to be used:
        name: 'secondary',
        
        // you may specify a delay in seconds:
        delay: 30,
        
        // you may specify how many times to retry:
        retry: 3,
        
        // you may specify a priority:
        priority: 100,
        
        // you may specify if you want to encrypt the message:
        encrypt: true,
        
        // you may specify if you want to render the message templates
        // before queuing:
        renderTemplates: false, // true default
    ));

查看 Symfony 邮件发送器 - 队列支持 了解支持。

使用邮件发送

如果你的邮件发送器支持,你可以定义用于发送消息的邮件发送器。

查看 邮件发送器 了解更多详情。

use Tobento\Service\Mail\Message;
use Tobento\Service\Mail\Parameter;

$message = (new Message())
    ->parameter(new Parameter\SendWithMailer(name: 'mailchimp'));

自定义参数

你可以按照以下方式编写自己的参数

use Tobento\Service\Mail\ParameterInterface;

class CustomParameter implements ParameterInterface
{
    /**
     * Create a new CustomParameter.
     *
     * @param string $name
     */
    public function __construct(
        protected string $name
    ) {}
    
    /**
     * Returns the name.
     *
     * @return string
     */
    public function name(): string
    {
        return $this->name;
    }
}

查看 Symfony 自定义参数支持 了解如何处理自定义参数。

邮件发送器

空邮件发送器

NullMailer::class 完全不发送任何邮件消息,这在开发(或测试)时可能很有用。

use Tobento\Service\Mail\NullMailer;
use Tobento\Service\Mail\MailerInterface;

$mailer = new NullMailer(name: 'null');

var_dump($mailer instanceof MailerInterface);
// bool(true)

SF 邮件发送器

文档在 Symfony 邮件发送器 部分。

邮件发送器

你可以使用以下邮件发送器作为你的 Send With Mailer 参数的 MailerInterface 实现。

默认邮件发送器

use Tobento\Service\Mail\Mailers;
use Tobento\Service\Mail\MailersInterface;
use Tobento\Service\Mail\MailerInterface;

$mailers = new Mailers(
    $mailer, // MailerInterface
    $anotherMailer, // MailerInterface
);

var_dump($mailers instanceof MailersInterface);
// bool(true)

var_dump($mailers instanceof MailerInterface);
// bool(true)

懒加载邮件发送器

懒加载邮件发送器类只在需要时创建邮件发送器。

use Tobento\Service\Mail\LazyMailers;
use Tobento\Service\Mail\MailersInterface;
use Tobento\Service\Mail\MailerInterface;
use Tobento\Service\Mail\MailerFactoryInterface;
use Tobento\Service\Mail\Symfony;
use Psr\Container\ContainerInterface;

$mailers = new LazyMailers(
    container: $container, // ContainerInterface
    mailers: [
        // using a factory:
        'default' => [
            // factory must implement MailerFactoryInterface
            'factory' => Symfony\SmtpMailerFactory::class,
            
            'config' => [
                'encryption' => '',
                'host' => 'host',
                'user' => 'user',
                'password' => '********',
                'port' => 465,

                // you may define default addresses and parameters
                // or set to null if defaults are used from email factory.
                'defaults' => [
                    'from' => 'from@example.com',
                ],
            ],
        ],
        
        // using a closure:
        'secondary' => static function (string $name, ContainerInterface $c): MailerInterface {
            // create mailer ...
            return $mailer;
        },
        
        'mailchimp' => [
            // ...
        ],
    ],
);

var_dump($mailers instanceof MailersInterface);
// bool(true)

var_dump($mailers instanceof MailerInterface);
// bool(true)

模板化

以下示例针对默认渲染器 Tobento\Service\Mail\ViewRenderer::class

编写视图

use Tobento\Service\Mail\Message;

$message = (new Message())
    //...
    ->htmlTemplate(
        name: 'email/welcome',
        data: ['name' => 'John', 'text' => 'Lorem ipsum'],
    );

欢迎视图模板

每个视图中都可用一个名为 message 的变量,它是一个 Tobento\Service\Mail\TemplateMessageInterface::class 的实例。

此外,使用 CSS 文件资源设计你的模板。当模板被渲染时,它将它们转换为内联样式,以更好地支持电子邮件客户端。

<!DOCTYPE html>
<html>
    <head>
        <title><?= $view->esc($message->subject()) ?></title>

        <?php
        // render assets only if inline styles are not used:
        if (!$withInlineCssStyles) {
            echo $view->assets()->render();
        }
        ?>

        <?php
        // assets can be included in every subview too.
        $view->asset('email.css');
        ?>
    </head>
    <body>
        <?= $view->render('email/header') ?>

        <h1>Hellow <?= $view->esc($name) ?></h1>

        <p><?= $view->esc($text) ?></p>
        
        <img src="<?= $message->embed('path/to/image.jpg') ?>">
        
        <?php
        // embed from stream: Psr\Http\Message\StreamInterface
        // <img src="<?= $message->embed(file: $stream, mimeType: 'image/jpeg') ?>">
        
        // embed from file: Tobento\Service\Filesystem\File
        // <img src="<?= $message->embed($file) ?>">        
        ?>

        <?= $view->render('email/footer') ?>
    </body>
</html>

渲染模板

你可能想要为电子邮件网页视图、调试或其他目的渲染模板。

use Tobento\Service\Mail\RendererInterface;
use Tobento\Service\Mail\TemplateInterface;
use Tobento\Service\Mail\Template;
use Tobento\Service\Mail\Message;

class SomeController
{
    public function renderEmail(RendererInterface $renderer): string
    {
        // by using a template object:
        $content = $renderer->renderTemplate(
            template: new Template(
                name: 'email/welcome',
                data: ['name' => 'John'],
            ),
            
            // you may not want to convert css
            // to inline styles for web views
            // as you might use CSP with blocking inline css.
            withInlineCssStyles: false, // true is default
        );
        
        // render message contents:
        $message = (new Message())
            ->htmlTemplate('email/welcome', ['name' => 'John']);
        
        if ($message->getHtml() instanceof TemplateInterface) {
            $content = $renderer->renderTemplate($message->getHtml());
        }
        
        return $content;
    }
}

事件

如果你的邮件发送器配置为支持,你可以监听以下事件。

查看 Symfony 邮件发送器 - 事件支持 了解如何创建支持事件的邮件发送器。

Symfony

Symfony 邮件发送器

use Tobento\Service\Mail\MailerInterface;
use Tobento\Service\Mail\Symfony;
use Tobento\Service\Mail\ViewRenderer;
use Tobento\Service\View;
use Tobento\Service\Dir;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory;

// create the renderer:
$renderer = new ViewRenderer(
    new View\View(
        new View\PhpRenderer(
            new Dir\Dirs(
                new Dir\Dir('dir/views/'),
            )
        ),
        new View\Data(),
        new View\Assets('dir/src/', 'https://example.com/src/')
    )
);

// create email factory:
$emailFactory = new Symfony\EmailFactory(
    renderer: $renderer,
);

// create the transport:
$transport = (new EsmtpTransportFactory())->create(new Dsn(
    'smtp',
    'host',
    'user',
    'password',
    465,
    [],
));

// create the mailer:
$mailer = new Symfony\Mailer(
    name: 'default',
    emailFactory: $emailFactory,
    transport: $transport,
);

var_dump($mailer instanceof MailerInterface);
// bool(true)

查看服务查看以了解更多信息。

事件支持

为了支持事件,您需要将派发器传递给邮件发送器。

use Tobento\Service\Mail\Symfony;
use Psr\EventDispatcher\EventDispatcherInterface;

// create the mailer:
$mailer = new Symfony\Mailer(
    name: 'default',
    emailFactory: $emailFactory,
    transport: $transport,
    
    // pass your event dispatcher:
    eventDispatcher: $dispatcher, // EventDispatcherInterface
);

队列支持

为了支持消息队列,您需要将队列处理程序传递给邮件发送器。

考虑使用默认的队列处理程序,使用队列服务

首先,安装队列服务

composer require tobento/service-queue

接下来,将队列处理程序传递给邮件发送器

use Tobento\Service\Mail\Symfony;
use Tobento\Service\Mail\QueueHandlerInterface;
use Tobento\Service\Mail\RendererInterface;
use Tobento\Service\Mail\Queue\QueueHandler;
use Tobento\Service\Queue\QueueInterface;

// create the mailer:
$mailer = new Symfony\Mailer(
    name: 'default',
    emailFactory: $emailFactory,
    transport: $transport,
    
    // pass your queue handler implementing QueueHandlerInterface:
    queueHandler: new QueueHandler(
        queue: $queue, // QueueInterface
        renderer: $renderer, // RendererInterface
        // you may define the default queue used if no specific is defined on the message.
        queueName: 'mails', // null|string
    ),
);

最后,确保作业处理器的容器有以下接口可用

示例使用服务容器作为容器

use Tobento\Service\Mail\MailerInterface;
use Tobento\Service\Mail\MessageFactoryInterface;
use Tobento\Service\Mail\MessageFactory;
use Tobento\Service\Queue\JobProcessor;
use Tobento\Service\Container\Container;

$container = new Container();
$container->set(MessageFactoryInterface::class, MessageFactory::class);
$container->set(MailerInterface::class, function() {
    // create mailer:
    return $mailer;
});

$jobProcessor = new JobProcessor($container);

默认地址和参数

您可以设置默认地址和/或参数,以应用于每条消息

use Tobento\Service\Mail\Symfony;
use Tobento\Service\Mail\Address;
use Tobento\Service\Mail\Parameters;
use Tobento\Service\Mail\Parameter;

$emailFactory = new Symfony\EmailFactory(
    renderer: $renderer,
    
    // you may pass default addresses or parameters
    // to be applied to every message created.
    config: [
        'from' => 'from@example.com',
        // with object:
        'from' => new Address('from@example.com', 'Name'),
        
        'replyTo' => 'reply@example.com',
        // with object:
        'replyTo' => new Address('reply@example.com'),
        
        // You may define an address to send all emails to:
        'alwaysTo' => 'debug@example.com',
        // with object:
        'alwaysTo' => new Address('debug@example.com'),
        
        'parameters' => new Parameters(
            new Parameter\PathHeader('Return-Path', 'return@example.com'),
        ),
    ],
);

// create the mailer:
$mailer = new Symfony\Mailer(
    name: 'default',
    emailFactory: $emailFactory,
    transport: $transport,
);

HTML 到文本转换

如果您创建的消息没有文本内容,它将根据您的HTML内容创建。

use Tobento\Service\Mail\Message;

$message = (new Message())
    // will be created from the html:
    //->text('Lorem Ipsum')
    
    ->html('<p>Lorem Ipsum</p>');

Symfony Dsn 邮件发送器工厂

use Tobento\Service\Mail\MailerInterface;
use Tobento\Service\Mail\Symfony;
use Tobento\Service\Mail\ViewRenderer;
use Tobento\Service\Mail\Address;
use Tobento\Service\Mail\Parameters;
use Tobento\Service\Mail\Parameter;
use Tobento\Service\View;
use Tobento\Service\Dir;

// create the renderer:
$renderer = new ViewRenderer(
    new View\View(
        new View\PhpRenderer(
            new Dir\Dirs(
                new Dir\Dir('dir/views/'),
            )
        ),
        new View\Data(),
        new View\Assets('dir/src/', 'https://example.com/src/')
    )
);

// create email factory:
$emailFactory = new Symfony\EmailFactory(
    renderer: $renderer,
);

// create the factory:
$factory = new Symfony\DsnMailerFactory($emailFactory);

// create the mailer:
$mailer = $factory->createMailer(name: 'default', config: [
    'dsn' => 'smtp://user:pass@smtp.example.com:port',
    
    // If the username, password or host contain
    // any character considered special in a URI
    // (such as +, @, $, #, /, :, *, !),
    // use the following instead of dsn above:
    //'scheme' => 'smtp',
    //'host' => 'host',
    //'user' => 'user',
    //'password' => '********',
    //'port' => 465,
    
    // you may define default addresses and parameters
    // or set to null if defaults are used from email factory.
    'defaults' => [
        'from' => 'from@example.com',
    ],
]);

var_dump($mailer instanceof MailerInterface);
// bool(true)

Symfony Smtp 邮件发送器工厂

use Tobento\Service\Mail\MailerInterface;
use Tobento\Service\Mail\Symfony;
use Tobento\Service\Mail\ViewRenderer;
use Tobento\Service\Mail\Address;
use Tobento\Service\Mail\Parameters;
use Tobento\Service\Mail\Parameter;
use Tobento\Service\View;
use Tobento\Service\Dir;

// create the renderer:
$renderer = new ViewRenderer(
    new View\View(
        new View\PhpRenderer(
            new Dir\Dirs(
                new Dir\Dir('dir/views/'),
            )
        ),
        new View\Data(),
        new View\Assets('dir/src/', 'https://example.com/src/')
    )
);

// create email factory:
$emailFactory = new Symfony\EmailFactory(
    renderer: $renderer,
);

// create the factory:
$factory = new Symfony\SmtpMailerFactory($emailFactory);

// create the mailer:
$mailer = $factory->createMailer(name: 'default', config: [
    'encryption' => '',
    'host' => 'host',
    'user' => 'user',
    'password' => '********',
    'port' => 465,
    
    // you may define default addresses and parameters
    // or set to null if defaults are used from email factory.
    'defaults' => [
        'from' => 'from@example.com',
    ],
]);

var_dump($mailer instanceof MailerInterface);
// bool(true)

Symfony 自定义参数支持

为了支持自定义参数,您可以编写一个新的电子邮件工厂类或扩展默认类

use Tobento\Service\Mail\Symfony;
use Tobento\Service\Mail\MessageInterface;
use Tobento\Service\Mail\ParameterInterface;
use Symfony\Component\Mime\Email;

class CustomizedEmailFactory extends Symfony\EmailFactory
{
    /**
     * Create email from message.
     *
     * @param MessageInterface $message
     * @return Email
     */
    public function createEmailFromMessage(MessageInterface $message): Email
    {
        $email = parent::createEmailFromMessage($message);
        
        // filter your custom parameters:
        $parameters = $message->parameters()->filter(
            fn(ParameterInterface $p): bool => $p instanceof CustomParameter
        );
        
        // do something with it:
        foreach($parameters as $parameter) {}
    }
}

鸣谢