tobento / service-notifier
Notifier interface for PHP applications.
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
Notifier interface for PHP applications using Symfony Notifier as default implementation.
Table of Contents
- Getting started
- Documentation
- Credits
Getting started
Add the latest version of the notifier service project running this command.
composer require tobento/service-notifier
Requirements
- PHP 8.0 or greater
Highlights
- Framework-agnostic, will work with any project
- Decoupled design
Documentation
Basic Usage
Creating And Sending Notifications
Once you have created the notifier you can create and send notifications:
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); } }
Check out the Notifications section to learn more about the available notifications you can create or you might create your own notification class fitting your application.
Check out the Recipients section to learn more about the available recipients you can create or you might create your own recipient class fitting your application.
Notifier
Create Notifier
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)
Check out the Channels section to learn more about the available channels.
Notifications
Notification
The Notification::class
may be used to create simple notification messages supporting all channels.
use Tobento\Service\Notifier\Notification; $notification = new Notification( subject: 'New Invoice', content: 'You got a new invoice for 15 EUR.', channels: ['mail', 'sms'], );
If you want to support custom channels you may consider creating a custom notification!
Available methods
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');
In addition, you may add messages for specific channels:
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', ));
Abstract Notification
Use the AbstractNotification::class
if you want to create specific messages for each channel.
Simply extend from the AbstractNotification::class
and add the message interfaces with its method you want to support.
Furthermore, any to
methods such as toSms
will receive a $recipient
entity, the $channel
name and you may request any service being resolved (autowired) by the container.
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), ); } }
Recipients
Recipient
The Recipient::class
may be used to create a recipient supporting all channels.
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 );
User Recipient
The UserRecipient::class
may be used if you have installed the User Service.
use Tobento\Service\Notifier\UserRecipient; use Tobento\Service\User\UserInterface; $recipient = new UserRecipient( user: $user, // UserInterface channels: [], );
Channel
Mail Channel
The mail channel uses the Mail Service to send notifications.
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)
Mail Notification
To send mail notifications you have multiple options:
Using the Abstract Notification
Simply extend from the AbstractNotification::class
and implement the ToMail
interface. The interface requires a toMailHandler
method which is already added on the AbstractNotification::class
defining the toMail
method as the message handler. You will just need to add the toMail
method which will receive a $recipient
entity, the $channel
name and you may request any service being resolved (autowired) by the container.
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>'); } }
If you do not have defined a default from address, you will need to set it on each message:
use Tobento\Service\Mail\Message; $message = (new Message()) ->from('from@example.com');
Using the Notification
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>') );
Check out the Mail Message section to learn more about mail messages.
Mail Recipient
When sending notifications via the mail channel, the channel will call the getAddressForChannel
method on the recipient entity to get the email address when no were defined on the mail message.
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)
Mail Channel Factory
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)
Sms Channel
Use the ChannelAdapter::class
to create a SMS channel using the Symfony SMS Channel:
You will need to install any chat service you would like composer require symfony/vonage-notifier
e.g.
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 Notification
To send SMS notifications you have multiple options:
Using the Abstract Notification
Simply extend from the AbstractNotification::class
and implement the ToSms
interface. The interface requires a toSmsHandler
method which is already added on the AbstractNotification::class
defining the toSms
method as the message handler. You will just need to add the toSms
method which will receive a $recipient
entity, the $channel
name and you may request any service being resolved (autowired) by the container.
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'), ); } }
Using the Notification
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 Recipient
When sending notifications via the sms channel, the channel will call the getAddressForChannel
method on the recipient entity to get the sms address when no specific were defined.
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)
Chat Channel
Use the ChannelAdapter::class
to create a chat channel using the Symfony Chat Channel:
You will need to install any chat service you would like composer require symfony/slack-notifier
e.g.
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)
Chat Notification
To send chat notifications you have multiple options:
Using the Abstract Notification
Simply extend from the AbstractNotification::class
and implement the ToChat
interface. The interface requires a toChatHandler
method which is already added on the AbstractNotification::class
defining the toChat
method as the message handler. You will just need to add the toChat
method which will receive a $recipient
entity, the $channel
name and you may request any service being resolved (autowired) by the container.
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', ); } }
Using the Notification
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', ));
Chat Recipient
When sending notifications via the chat channel, you may add a specific channel address with parameters for later usage.
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" }
Sample notification with address usage
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', ); } }
Push Channel
Use the ChannelAdapter::class
to create a push channel using the Symfony Push Channel:
You will need to install any push service you would like composer require symfony/one-signal-notifier
e.g.
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)
Push Notification
To send push notifications you have multiple options:
Using the Abstract Notification
Simply extend from the AbstractNotification::class
and implement the ToPush
interface. The interface requires a toPushHandler
method which is already added on the AbstractNotification::class
defining the toPush
method as the message handler. You will just need to add the toPush
method which will receive a $recipient
entity, the $channel
name and you may request any service being resolved (autowired) by the container.
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', ); } }
Using the Notification
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', ));
Push Recipient
When sending notifications via the push channel, you may add a specific channel address with parameters for later usage.
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" }
Sample notification with address usage
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', ); } }
Storage Channel
The storage channel stores the notification information in the configured storage repository.
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)
Check out the Repository Service to learn more about it.
The storage needs to have the following table columns:
Storage Repository
You may use the provided StorageRepository::class
as repository implementation:
You will need to install the service:
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 );
Check out the Storage Service - Storages for the available storages.
Check out the Repository Storage Service to learn more about it in general.
Storage Notification
To send Storage notifications you have multiple options:
Using the Abstract Notification
Simply extend from the AbstractNotification::class
and implement the ToStorage
interface. The interface requires a toStorageHandler
method which is already added on the AbstractNotification::class
defining the toStorage
method as the message handler. You will just need to add the toStorage
method which will receive a $recipient
entity, the $channel
name and you may request any service being resolved (autowired) by the container.
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, ]); } }
Using the Notification
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', ]));
Storage Recipient
When sending notifications via the storage channel, the channel will store the $recipient->getId()
and $recipient->getType()
values which you can later use to fetch notifications:
// 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, ]);
Accessing Storage Notifications
Once notifications are stored, you can retrieve the notifications using the repository from the channel:
$channel = $channels->get(name: 'storage/database'); $entities = $channel->repository()->findAll(where: [ 'recipient_id' => $userId, //'recipient_type' => 'user', ]);
Channels
Default Channels
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)
Lazy Channels
The LazyChannels::class
creates the channels only on demand.
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
You may queue your notification by just adding the Queue::class
parameter:
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, ));
Requirements
To support queuing notifications you will need to pass a queue handler to the notifier.
Consider using the default queue handler using the Queue Service:
First, install the queue service:
composer require tobento/service-queue
Finally, pass the queue handler to the notifier:
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 ), );
Events
You may listen to the following events if your notifier is configured to support it.
When using the default notifier just pass an event dispatcher.