tobento / service-notifier
PHP应用程序的通知接口。
Requires
- php: >=8.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-message: ^1.0
- symfony/notifier: ^6.0
- tobento/service-autowire: ^1.0
- tobento/service-filesystem: ^1.0.5
- tobento/service-mail: ^1.0
- tobento/service-repository: ^1.0.1
Requires (Dev)
- phpunit/phpunit: ^9.5
- symfony/one-signal-notifier: ^6.0
- symfony/slack-notifier: ^6.0
- symfony/vonage-notifier: ^6.0
- tobento/service-container: ^1.0
- tobento/service-event: ^1.0
- tobento/service-migration: ^1.0.8
- tobento/service-queue: ^1.0
- tobento/service-repository-storage: ^1.0
- tobento/service-user: ^1.0.1
- vimeo/psalm: ^4.0
Suggests
- tobento/service-migration: To support storage repository migration
- tobento/service-repository-storage: To support storage channel
README
使用Symfony Notifier(默认实现)作为通知接口的PHP应用程序。
目录
入门
运行以下命令添加通知服务项目的最新版本。
composer require tobento/service-notifier
要求
- PHP 8.0 或更高版本
亮点
- 框架无关,适用于任何项目
- 解耦设计
文档
基本用法
创建和发送通知
创建通知后,您可以创建并发送通知。
use Tobento\Service\Notifier\NotifierInterface; use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Recipient; class SomeService { public function send(NotifierInterface $notifier): void { // Create a Notification that has to be sent: // using the "mail" and "sms" channel $notification = (new Notification(subject: 'New Invoice', channels: ['mail', 'sms'])) ->content('You got a new invoice for 15 EUR.'); // The receiver of the notification: $recipient = new Recipient( email: 'mail@example.com', phone: '15556666666', ); // Send the notification to the recipient: $notifier->send($notification, $recipient); } }
查看通知部分,了解更多关于您可以创建的通知,或者您可能创建适合您应用程序的自定义通知类。
查看接收者部分,了解更多关于您可以创建的接收者,或者您可能创建适合您应用程序的自定义接收者类。
通知器
创建通知器
use Tobento\Service\Notifier\NotifierInterface; use Tobento\Service\Notifier\Notifier; use Tobento\Service\Notifier\ChannelsInterface; use Tobento\Service\Notifier\Channels; use Tobento\Service\Notifier\QueueHandlerInterface; $notifier = new Notifier( channels: new Channels(), // ChannelsInterface // you may set a queue handler if you want to support queuing notifications: queueHandler: null, // null|QueueHandlerInterface // you may set an event dispatcher if you want to support events: eventDispatcher: null, // null|EventDispatcherInterface ); var_dump($notifier instanceof NotifierInterface); // bool(true)
查看通道部分,了解更多关于可用的通道。
通知
单个通知
可以使用Notification::class
创建支持所有通道的简单通知消息。
use Tobento\Service\Notifier\Notification; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', channels: ['mail', 'sms'], );
如果您想支持自定义通道,您可以考虑创建自定义通知!
可用方法
use Tobento\Service\Notifier\Notification; $notification = (new Notification()) // you may prefer using the subject method: ->subject('New Invoice') // you may prefer using the content method: ->content('You got a new invoice for 15 EUR.') // you may specify a name for any later usage: ->name('New Invoice');
此外,您可以为特定通道添加消息
use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Message; $notification = (new Notification( subject: 'General subject used if no specific message', channels: ['mail', 'sms'], )) ->addMessage('sms', new Message\Sms( subject: 'Specific sms message', )) // or specific sms channel: ->addMessage('sms/vonage', new Message\Sms( subject: 'Specific sms message', ));
抽象通知
如果您想为每个通道创建特定消息,请使用AbstractNotification::class
。
简单地从AbstractNotification::class
扩展并添加带有您想要支持的方法的消息接口。
此外,任何to
方法,如toSms
,将接收到一个$recipient
实体、$channel
名称,您还可以请求容器解析(自动注入)的任何服务。
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; class OrderNotification extends AbstractNotification implements Message\ToSms { /** * Create an order notification. * * @param Order $order */ public function __construct( protected Order $order, ) {} /** * Returns the sms message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\SmsInterface */ public function toSms(RecipientInterface $recipient, string $channel, SomeService $service): Message\SmsInterface { return new Message\Sms( subject: sprintf('Thanks for your order %s', $this->order->name), ); } }
接收者
接收者
可以使用Recipient::class
创建支持所有通道的接收者。
use Tobento\Service\Notifier\Recipient; $recipient = new Recipient( email: 'mail@example.com', // null|string phone: '15556666666', // null|string id: 'unique-id', // null|string|int type: 'users', // null|string locale: 'en', // string (en default) // you may set the channels the recipient prefers: channels: [], ); // you may add specific addresses: $recipient->addAddress( channel: 'chat/slack', address: ['key' => 'value'] // mixed );
用户接收者
如果您已安装UserService,则可以使用UserRecipient::class
。
use Tobento\Service\Notifier\UserRecipient; use Tobento\Service\User\UserInterface; $recipient = new UserRecipient( user: $user, // UserInterface channels: [], );
通道
邮件通道
邮件通道使用Mail Service发送通知。
use Tobento\Service\Notifier\Mail; use Tobento\Service\Notifier\ChannelInterface; use Tobento\Service\Mail\MailerInterface; use Psr\Container\ContainerInterface; $channel = new Mail\Channel( name: 'mail', mailer: $mailer, // MailerInterface container: $container, // ContainerInterface ); var_dump($channel instanceof ChannelInterface); // bool(true)
邮件通知
要发送邮件通知,您有多种选择
使用抽象通知
只需从 AbstractNotification::class
扩展并实现 ToMail
接口。该接口需要一个 toMailHandler
方法,该方法已经在 AbstractNotification::class
中添加,定义了 toMail
方法作为消息处理程序。您只需添加一个 toMail
方法,该方法将接收一个 $recipient
实体、$channel
名称,并且您可以请求容器解析(自动装配)的任何服务。
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message\ToMail; use Tobento\Service\Mail\Message; class SampleNotification extends AbstractNotification implements ToMail { /** * Returns the mail message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message */ public function toMail(RecipientInterface $recipient, string $channel, SomeService $service): Message { return (new Message()) // not required if none is defined, the address will be added on sending: ->to('to@example.com') ->subject('Subject') //->textTemplate('welcome-text') //->htmlTemplate('welcome') //->text('Lorem Ipsum') ->html('<p>Lorem Ipsum</p>'); } }
如果您没有定义 默认的发件人地址,则需要在每个消息上设置它。
use Tobento\Service\Mail\Message; $message = (new Message()) ->from('from@example.com');
使用 通知
use Tobento\Service\Notifier\Notification; use Tobento\Service\Mail; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', ); // with specific mail message: $notification = (new Notification()) ->addMessage('mail', (new Mail\Message()) ->subject('Subject') ->html('<p>Lorem Ipsum</p>') );
查看 邮件消息 部分,了解更多关于邮件消息的信息。
邮件接收者
通过邮件渠道发送通知时,如果邮件消息上没有定义,渠道将调用接收者实体的 getAddressForChannel
方法来获取电子邮件地址。
use Tobento\Service\Notifier\Recipient; use Tobento\Service\Notifier\Address; use Tobento\Service\Notifier\Notification; $recipient = new Recipient( email: 'mail@example.com', // or email: new Address\Email('mail@example.com', 'Name'), ); $address = $recipient->getAddressForChannel('mail', new Notification('subject')); var_dump($address instanceof Address\EmailInterface); // bool(true)
邮件通道工厂
use Tobento\Service\Notifier\Mail\ChannelFactory; use Tobento\Service\Notifier\ChannelInterface; use Tobento\Service\Mail\MailerInterface; use Psr\Container\ContainerInterface; $factory = new ChannelFactory( mailer: $mailer, // MailerInterface container: $container, // ContainerInterface ); $channel = $factory->createChannel(name: 'mail'); // using a specific mailer: $channel = $factory->createChannel(name: 'mail/mailchimp', config: [ 'mailer' => 'mailchimp', ]); var_dump($channel instanceof ChannelInterface); // bool(true)
短信通道
使用 ChannelAdapter::class
通过 Symfony SMS Channel 创建 SMS 渠道。
您需要安装任何您想要的聊天服务,例如 composer require symfony/vonage-notifier
。
use Tobento\Service\Notifier\Symfony\ChannelAdapter; use Tobento\Service\Notifier\ChannelInterface; use Psr\Container\ContainerInterface; $channel = new ChannelAdapter( name: 'sms/vonage', channel: new \Symfony\Component\Notifier\Channel\SmsChannel( transport: new \Symfony\Component\Notifier\Bridge\Vonage\VonageTransport( apiKey: '******', apiSecret: '******', from: 'FROM', ) ), container: $container, // ContainerInterface ); var_dump($channel instanceof ChannelInterface); // bool(true)
短信通知
发送 SMS 通知有多种选择
使用抽象通知
只需从 AbstractNotification::class
扩展并实现 ToSms
接口。该接口需要一个 toSmsHandler
方法,该方法已经在 AbstractNotification::class
中添加,定义了 toSms
方法作为消息处理程序。您只需添加一个 toSms
方法,该方法将接收一个 $recipient
实体、$channel
名称,并且您可以请求容器解析(自动装配)的任何服务。
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; class SampleNotification extends AbstractNotification implements Message\ToSms { /** * Returns the sms message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\SmsInterface */ public function toSms(RecipientInterface $recipient, string $channel): Message\SmsInterface { return new Message\Sms( subject: 'Sms message', ); // you may set a specific to address: return new Message\Sms( subject: 'Sms message', to: $recipient->getAddressForChannel('sms/vonage'), ); } }
使用 通知
use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Message; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', ); // with specific sms message: $notification = (new Notification()) ->addMessage('sms', new Message\Sms( subject: 'Sms message', ));
短信接收者
通过 SMS 渠道发送通知时,如果未定义特定地址,渠道将调用接收者实体的 getAddressForChannel
方法来获取 SMS 地址。
use Tobento\Service\Notifier\Recipient; use Tobento\Service\Notifier\Address; use Tobento\Service\Notifier\Notification; $recipient = new Recipient( phone: '15556666666', // or phone: new Address\Phone('15556666666', 'Name'), ); $address = $recipient->getAddressForChannel('sms', new Notification('subject')); var_dump($address instanceof Address\PhoneInterface); // bool(true)
聊天通道
使用 ChannelAdapter::class
通过 Symfony Chat Channel 创建聊天渠道。
您需要安装任何您想要的聊天服务,例如 composer require symfony/slack-notifier
。
use Tobento\Service\Notifier\Symfony\ChannelAdapter; use Tobento\Service\Notifier\ChannelInterface; use Psr\Container\ContainerInterface; $channel = new ChannelAdapter( name: 'chat/slack', channel: new \Symfony\Component\Notifier\Channel\ChatChannel( transport: new \Symfony\Component\Notifier\Bridge\Slack\SlackTransport( accessToken: '******', ) ), container: $container, // ContainerInterface ); var_dump($channel instanceof ChannelInterface); // bool(true)
聊天通知
发送聊天通知有多种选择
使用抽象通知
只需从 AbstractNotification::class
扩展并实现 ToChat
接口。该接口需要一个 toChatHandler
方法,该方法已经在 AbstractNotification::class
中添加,定义了 toChat
方法作为消息处理程序。您只需添加一个 toChat
方法,该方法将接收一个 $recipient
实体、$channel
名称,并且您可以请求容器解析(自动装配)的任何服务。
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; use Tobento\Service\Notifier\Symfony\MessageOptions; use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; class SampleNotification extends AbstractNotification implements Message\ToChat { /** * Returns the chat message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\ChatInterface */ public function toChat(RecipientInterface $recipient, string $channel): Message\ChatInterface { if ($channel === 'chat/slack') { // you may set message options: $options = new SlackOptions(); return new (Message\Chat('Chat message')) ->parameter(new MessageOptions($options)); } // for any other chat channel: return new Message\Chat( subject: 'Chat message', ); } }
使用 通知
use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Message; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', ); // with specific chat message: $notification = (new Notification()) ->addMessage('chat/slack', new Message\Chat( subject: 'Chat message', ));
聊天接收者
通过聊天渠道发送通知时,您可以添加具有参数的特定渠道地址,以便以后使用。
use Tobento\Service\Notifier\Recipient; use Tobento\Service\Notifier\Address; use Tobento\Service\Notifier\Notification; $recipient = (new Recipient()) ->addAddress('chat/slack', ['channel' => 'name']); $address = $recipient->getAddressForChannel('chat/slack', new Notification('subject')); var_dump($address); // array(1) { ["channel"]=> string(4) "name" }
示例通知及地址使用
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; use Tobento\Service\Notifier\Symfony\MessageOptions; use Symfony\Component\Notifier\Bridge\Slack\SlackOptions; class SampleNotification extends AbstractNotification implements Message\ToChat { /** * Returns the chat message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\ChatInterface */ public function toChat(RecipientInterface $recipient, string $channel): Message\ChatInterface { if ($channel === 'chat/slack') { $address = $recipient->getAddressForChannel('chat/slack', $this); $options = new SlackOptions([ 'recipient_id' => $address['channel'] ?? null, ]); return new (Message\Chat('Chat message')) ->parameter(new MessageOptions($options)); } // for any other chat channel: return new Message\Chat( subject: 'Chat message', ); } }
推送通道
使用 ChannelAdapter::class
通过 Symfony Push Channel 创建推送渠道。
您需要安装任何您想要的推送服务,例如 composer require symfony/one-signal-notifier
。
use Tobento\Service\Notifier\Symfony\ChannelAdapter; use Tobento\Service\Notifier\ChannelInterface; use Psr\Container\ContainerInterface; $channel = new ChannelAdapter( name: 'push/one-signal', channel: new \Symfony\Component\Notifier\Channel\ChatChannel( transport: new \Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransport( appId: '******', apiKey: '******', ) ), container: $container, // ContainerInterface ); var_dump($channel instanceof ChannelInterface); // bool(true)
推送通知
发送推送通知有多种选择
使用抽象通知
只需从 AbstractNotification::class
扩展并实现 ToPush
接口。该接口需要一个 toPushHandler
方法,该方法已经在 AbstractNotification::class
中添加,定义了 toPush
方法作为消息处理程序。您只需添加一个 toPush
方法,该方法将接收一个 $recipient
实体、$channel
名称,并且您可以请求容器解析(自动装配)的任何服务。
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; use Tobento\Service\Notifier\Symfony\MessageOptions; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalOptions; class SampleNotification extends AbstractNotification implements Message\ToPush { /** * Returns the push message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\PushInterface */ public function toPush(RecipientInterface $recipient, string $channel): Message\PushInterface { if ($channel === 'push/one-signal') { // you may set message options: $options = new OneSignalOptions([]); return new (Message\Push('Push subject')) ->content('Push content') ->parameter(new MessageOptions($options)); } // for any other push channel: return new Message\Push( subject: 'Push subject', content: 'Push content', ); } }
使用 通知
use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Message; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', ); // with specific chat message: $notification = (new Notification()) ->addMessage('push/one-signal', new Message\Push( subject: 'Push subject', content: 'Push content', ));
推送接收者
通过推送渠道发送通知时,您可以添加具有参数的特定渠道地址,以便以后使用。
use Tobento\Service\Notifier\Recipient; use Tobento\Service\Notifier\Address; use Tobento\Service\Notifier\Notification; $recipient = (new Recipient()) ->addAddress('push/one-signal', ['recipient_id' => 'id']); $address = $recipient->getAddressForChannel('push/one-signal', new Notification('subject')); var_dump($address); // array(1) { ["recipient_id"]=> string(2) "id" }
示例通知及地址使用
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; use Tobento\Service\Notifier\Symfony\MessageOptions; use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalOptions; class SampleNotification extends AbstractNotification implements Message\ToPush { /** * Returns the push message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\PushInterface */ public function toPush(RecipientInterface $recipient, string $channel): Message\PushInterface { if ($channel === 'push/one-signal') { $address = $recipient->getAddressForChannel('push/one-signal', $this); $options = new OneSignalOptions([ 'recipient_id' => $address['recipient_id'] ?? null, ]); return new (Message\Push('Push subject')) ->content('Push content') ->parameter(new MessageOptions($options)); } // for any other push channel: return new Message\Push( subject: 'Push subject', content: 'Push content', ); } }
存储通道
存储渠道将通知信息存储在配置的存储仓库中。
use Tobento\Service\Notifier\Storage; use Tobento\Service\Notifier\ChannelInterface; use Tobento\Service\Repository\RepositoryInterface; use Psr\Container\ContainerInterface; $channel = new Storage\Channel( name: 'storage/database', repository: $repository, // RepositoryInterface container: $container, // ContainerInterface ); var_dump($channel instanceof ChannelInterface); // bool(true)
查看存储服务以了解更多信息。
存储需要以下表列
存储仓库
您可以使用提供的StorageRepository::class
作为存储实现
您需要安装该服务
composer require tobento/service-repository-storage
use Tobento\Service\Notifier\Storage; use Tobento\Service\Notifier\ChannelInterface; use Tobento\Service\Repository\RepositoryInterface; use Tobento\Service\Storage\StorageInterface; use Psr\Container\ContainerInterface; $channel = new Storage\Channel( name: 'storage/database', repository: new StorageRepository( storage: $storage, // StorageInterface table: 'notifications', ), container: $container, // ContainerInterface );
查看存储服务 - 存储库以了解可用的存储库。
查看存储库存储服务以了解相关信息。
存储通知
发送存储通知有多种选择
使用抽象通知
简单地从AbstractNotification::class
扩展并实现ToStorage
接口。该接口要求一个toStorageHandler
方法,该方法已在AbstractNotification::class
上添加,定义了toStorage
方法作为消息处理器。您只需添加一个toStorage
方法,该方法将接收一个$recipient
实体,一个$channel
名称,您还可以请求容器解析(自动装配)的任何服务。
use Tobento\Service\Notifier\AbstractNotification; use Tobento\Service\Notifier\RecipientInterface; use Tobento\Service\Notifier\Message; class SampleNotification extends AbstractNotification implements Message\ToStorage { /** * Returns the storage message. * * @param RecipientInterface $recipient * @param string $channel The channel name. * @return Message\StorageInterface */ public function toStorage(RecipientInterface $recipient, string $channel): Message\StorageInterface { return new Message\Storage(data: [ 'order_id' => $this->order->id, ]); } }
使用 通知
use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Message; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', ); // with specific storage message: $notification = (new Notification()) ->addMessage('storage', new Message\Storage([ 'foo' => 'bar', ]));
存储接收者
通过存储通道发送通知时,通道将存储$recipient->getId()
和$recipient->getType()
值,您可以在以后使用这些值来获取通知
// channel will store on sending: $repository->create([ 'name' => $notification->getName(), 'recipient_id' => $recipient->getId(), 'recipient_type' => $recipient->getType(), 'data' => $message->getData(), 'read_at' => null, 'created_at' => null, ]);
访问存储通知
一旦通知被存储,您可以使用存储库从通道检索通知
$channel = $channels->get(name: 'storage/database'); $entities = $channel->repository()->findAll(where: [ 'recipient_id' => $userId, //'recipient_type' => 'user', ]);
通道
默认通道
use Tobento\Service\Notifier\Channels; use Tobento\Service\Notifier\ChannelsInterface; use Tobento\Service\Notifier\ChannelInterface; $channels = new Channels( $channel, // ChannelInterface $anotherChannel, // ChannelInterface ); var_dump($channels instanceof ChannelsInterface); // bool(true)
懒通道
LazyChannels::class
仅在需要时创建通道。
use Tobento\Service\Notifier\LazyQueues; use Tobento\Service\Notifier\ChannelsInterface; use Tobento\Service\Notifier\ChannelInterface; use Tobento\Service\Notifier\ChannelFactoryInterface; use Tobento\Service\Notifier\Symfony; use Psr\Container\ContainerInterface; $channels = new LazyChannels( container: $container, // ContainerInterface channels: [ // using a factory: 'sms' => [ // factory must implement ChannelFactoryInterface 'factory' => Symfony\ChannelFactory::class, 'config' => [ 'dsn' => 'vonage://KEY:SECRET@default?from=FROM', 'channel' => \Symfony\Component\Notifier\Channel\SmsChannel::class, ], ], // using a closure: 'mail' => static function (string $name, ContainerInterface $c): ChannelInterface { // create channel ... return $channel; }, // or you may sometimes just create the channel (not lazy): 'sms/null' => new NullChannel(name: 'sms'), ], ); var_dump($channels instanceof ChannelsInterface); // bool(true)
队列
您可以通过添加Queue::class
参数来排队通知
use Tobento\Service\Notifier\Notification; use Tobento\Service\Notifier\Parameter\Queue; $notification = (new Notification()) ->parameter(new 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, ));
要求
要支持排队通知,您需要将队列处理器传递给通知器。
考虑使用默认的队列处理器,请参阅队列服务
首先,安装队列服务
composer require tobento/service-queue
最后,将队列处理器传递给通知器
use Tobento\Service\Notifier\NotifierInterface; use Tobento\Service\Notifier\Notifier; use Tobento\Service\Notifier\ChannelsInterface; use Tobento\Service\Notifier\Channels; use Tobento\Service\Notifier\Queue\QueueHandler; use Tobento\Service\Queue\QueueInterface; $notifier = new Notifier( channels: new Channels(), // ChannelsInterface // set a queue handler: queueHandler: new QueueHandler( queue: $queue, // QueueInterface // you may define the default queue used if no specific is defined on the notification. queueName: 'mails', // null|string ), );
事件
如果您的通知器配置为支持,您可以监听以下事件。
当使用默认通知器时,只需传递一个事件分配器。