1. Go to this page and download the library: Download tobento/app-notification library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
tobento / app-notification example snippets
use Tobento\App\AppFactory;
use Tobento\App\Notification\Channel\ChannelsInterface;
use Tobento\App\Notification\Newsletter\SubscriberRepositoryInterface;
use Tobento\App\Notification\NotificationRepositoryInterface;
use Tobento\App\Notification\NotificationTypesInterface;
// Create the app
$app = new AppFactory()->createApp();
// Add directories:
$app->dirs()
->dir(realpath(__DIR__.'/../'), 'root')
->dir(realpath(__DIR__.'/../app/'), 'app')
->dir($app->dir('app').'config', 'config', group: 'config')
->dir($app->dir('root').'public', 'public')
->dir($app->dir('root').'vendor', 'vendor');
// Adding boots
$app->boot(\Tobento\App\Notification\Boot\Notification::class);
$app->booting();
// Implemented interfaces:
$channels = $app->get(ChannelsInterface::class);
$subscriberRepository = $app->get(SubscriberRepositoryInterface::class);
$notificationRepository = $app->get(NotificationRepositoryInterface::class);
$notificationTypes = $app->get(NotificationTypesInterface::class);
// Run the app
$app->run();
'features' => [
new Feature\Notifications(
// A menu name to show the notification link or null if none.
menu: 'main',
menuLabel: 'Notifications',
// A menu parent name (e.g. 'system') or null if none.
menuParent: null,
// you may disable the ACL while testing for instance,
// otherwise only users with the right permissions can access the page.
withAcl: false,
),
],
namespace Tobento\App\Notification\Type;
use Psr\Container\ContainerInterface;
use Tobento\App\Notification\NotificationEntityInterface;
use Tobento\App\Notification\NotificationTypeInterface;
use Tobento\App\Notification\ReportAwareInterface;
use Tobento\Service\Notifier\NotificationInterface;
class NewsletterNotification implements NotificationTypeInterface, ReportAwareInterface
{
// ...
/**
* Returns true if a preview notification exists, otherwise false.
*
* @return bool
*/
public function hasPreviewNotification(): bool
{
return true;
}
/**
* Create a preview notification.
*
* @param ContainerInterface $container
* @param NotificationEntityInterface $entity
* @return null|NotificationInterface
*/
public function createPreviewNotification(
ContainerInterface $container,
NotificationEntityInterface $entity,
): null|NotificationInterface {
return $this->createNotification(container: $container, entity: $entity);
}
}
namespace Tobento\App\Notification\Type;
use Tobento\App\AppInterface;
use Tobento\App\Card\Cards;
use Tobento\App\Notification\NotificationEntityInterface;
use Tobento\App\Notification\NotificationTypeInterface;
use Tobento\App\Notification\ReportAwareInterface;
class NewsletterNotification implements NotificationTypeInterface, ReportAwareInterface
{
// ...
/**
* Configure report cards.
*
* @param AppInterface $app
* @param NotificationEntityInterface $entity
* @return CardsInterface
*/
public function configureReportCards(AppInterface $app, NotificationEntityInterface $entity): CardsInterface
{
$cards = new Cards(container: $app->container());
// adding cards...
return $cards;
}
}
use Tobento\App\Notification\Card\ReportCards;
$app->on(ReportCards::class, static function(ReportCards $cards) use ($app): void {
// you may add cards only for specific notfication types:
if ($cards->notificationEntity()->type() === 'newsletter') {
$cards->add(
name: 'orders',
card: $app->make(CustomOrdersCard::class, ['entity' => $cards->notificationEntity(), 'priority' => 100]),
);
}
});
use Tobento\App\AppInterface;
use Tobento\App\Card\Cards;
use Tobento\App\Notification\NotificationEntityInterface;
use Tobento\App\Notification\Type\NewsletterNotification;
class CustomizedNewsletterNotification extends NewsletterNotification
{
/**
* Configure report cards.
*
* @param AppInterface $app
* @param NotificationEntityInterface $entity
* @return CardsInterface
*/
public function configureReportCards(AppInterface $app, NotificationEntityInterface $entity): CardsInterface
{
$cards = new Cards(container: $app->container());
// adding cards...
return $cards;
}
}
'interfaces' => [
NotificationTypesInterface::class =>
static function(Channel\ChannelsInterface $channels): NotificationTypesInterface {
return new NotificationTypes(
new CustomizedNewsletterNotification(
// Filter the channels you want to support:
channels: $channels->only('mail', 'sms'),
// You may disable tracking options:
trackingOptions: false, // true default
// You may change the block editor:
blockEditorName: 'mail', // default
),
);
},
],
use Psr\Container\ContainerInterface;
use Tobento\App\Notification\Placeholder\Placeholder;
use Tobento\App\Notification\Placeholder\Placeholders;
use Tobento\App\Notification\Placeholder\PlaceholdersInterface;
use Tobento\App\Notification\Type\AbstractCustomNotification;
use Tobento\App\User\Web\Notification\ResetPassword;
use Tobento\App\Notification\NotificationEntityInterface;
use Tobento\Service\Notifier\NotificationInterface;
class ResetPasswordNotificationType extends AbstractCustomNotification
{
/**
* Returns the name.
*
* @return string
*/
public function name(): string
{
return 'reset.password';
}
/**
* Returns the title.
*
* @return string
*/
public function title(): string
{
return 'Reset Password';
}
/**
* Returns the notification name which to replace or null if not supported.
*
* @return null|string
*/
public function replacesNotification(): null|string
{
return ResetPassword::class;
}
/**
* Returns true if a preview notification exists, otherwise false.
*
* @return bool
*/
public function hasPreviewNotification(): bool
{
return false;
}
/**
* Create a preview notification.
*
* @param ContainerInterface $container
* @param NotificationEntityInterface $entity
* @return null|NotificationInterface
*/
public function createPreviewNotification(ContainerInterface $container, NotificationEntityInterface $entity): null|NotificationInterface
{
// you may create a notification for preview.
// See file: \Tobento\App\Notification\Type\ResetPasswordNotificationType::class
return null;
}
/**
* Returns the configured placeholders.
*
* @return PlaceholdersInterface
*/
public function configurePlaceholders(): PlaceholdersInterface
{
return new Placeholders(
new Placeholder(
key: 'url',
value: fn (ResetPassword $notification): string => $notification->url(),
description: The url where users can reset theirs password.
),
new Placeholder(
key: 'expires.in.seconds',
value: fn (ResetPassword $notification): int => $notification->token()->expiresAt()->getTimestamp() - time(),
),
);
}
}
'interfaces' => [
NotificationTypesInterface::class =>
static function(Channel\ChannelsInterface $channels): NotificationTypesInterface {
return new NotificationTypes(
new ResetPasswordNotificationType(
// Filter the channels you want to support:
channels: $channels->only('mail', 'sms'),
// Define the supported app ids:
supportedAppIds: ['frontend'],
// You may customize the executions the user can select:
allowedExecutions: ['send', 'queue', 'skip'], // default
// You may customize the default execution of the notification:
defaultExecution: 'send',
// You may customize the queues the user can select:
allowedQueueNames: ['file'], // default
// You may disable tracking options:
trackingOptions: false, // true default
// You may change the block editor:
blockEditorName: 'mail', // default
),
);
},
],
'interfaces' => [
NotificationTypesInterface::class =>
static function(
Channel\ChannelsInterface $channels,
Newsletter\TopicsInterface $topics,
): NotificationTypesInterface {
return new NotificationTypes(
new Type\UsersNotification(
// Filter the channels you want to support:
channels: $channels, //->only('mail'),
// You may disable tracking options:
trackingOptions: false, // true default
// You may change the block editor:
blockEditorName: 'mail', // default
// You may change the name:
name: 'customers', // 'users' is default
// You may change the title:
title: 'Customers',
// You may change the user repository with a custom one:
userRepository: CustomerRepositoryInterface::class,
),
);
},
],
use Tobento\Service\Schedule\Task;
use Butschster\CronExpression\Generator;
use Butschster\CronExpression\Parts\Hours\BetweenHours;
use Butschster\CronExpression\Parts\Minutes\EveryMinute;
$schedule->task(
new Task\CommandTask(
command: 'notifications:send',
// lets queue 100 active user notifications at a time to the queue file.
input: [
'--interval' => 100,
'--queue' => 'file',
'--type' => ['users', 'customers'], // depending on the type name
'--status' => 'active',
],
)
// schedule task:
->cron(
Generator::create()
->set(new EveryMinute(1))
->set(new BetweenHours(8, 16)
)
);
use Tobento\App\Notification\Task\SendNotificationRegistry;
'registries' => [
'newsletter.send' => new SendNotificationRegistry(
name: 'Send User Notifications',
// You may customize the statuses the user can select to send only:
allowedStatuses: ['active'], // default
// You may customize the notification types the user can select to send:
allowedTypes: ['users', 'customers'],
// You may customize the max. interval the user can to send:
maxAllowedIntervalToSend: 20, // default
// You may customize the max. interval the user can to queue:
maxAllowedIntervalToQueue: 20000, // default
// You may customize the queues the user can select:
allowedQueueNames: ['file'], // default
// You may add task parameters to be always processed:
parameters: [
new \Tobento\Service\Schedule\Parameter\WithoutOverlapping(),
],
// Define the supported apps where the task can be run:
supportedAppIds: ['root', 'backend'],
),
]
'registries' => [
'user.notifications.send' => new Registry\CommandTask(
name: 'Queues 100 user notifications at a time',
command: 'notifications:send',
input: [
'--interval' => 100,
'--queue' => 'file',
'--type' => ['users', 'customers'],
],
// You may add task parameters to be always processed:
parameters: [
new \Tobento\Service\Schedule\Parameter\WithoutOverlapping(),
],
// Define the supported apps where the task can be run:
supportedAppIds: ['root', 'backend'],
),
]
'interfaces' => [
NotificationTypesInterface::class =>
static function(
Channel\ChannelsInterface $channels,
Newsletter\TopicsInterface $topics,
): NotificationTypesInterface {
return new NotificationTypes(
new Type\NewsletterNotification(
// Filter the channels you want to support:
channels: $channels->only('mail', 'sms'),
// You may define the topics the newsletter covers to choose from.
// You may change its titles:
topics: $topics->withTitle('products.new', 'New Products'),
// Or null if no topics at all:
topics: null,
// You may disable tracking options:
trackingOptions: false, // true default
// You may change the block editor:
blockEditorName: 'mail', // default
// You may change the days the unsubscribe link will expire:
unsubscribeLinkExpiresInDays: 30, // default
),
);
},
],
use Tobento\Service\Schedule\Task;
use Butschster\CronExpression\Generator;
use Butschster\CronExpression\Parts\Hours\BetweenHours;
use Butschster\CronExpression\Parts\Minutes\EveryMinute;
$schedule->task(
new Task\CommandTask(
command: 'notifications:send',
// lets queue 100 active newsletter notifications at a time to the queue file.
input: [
'--interval' => 100,
'--queue' => 'file',
'--type' => ['newsletter'],
'--status' => 'active',
],
)
// schedule task:
->cron(
Generator::create()
->set(new EveryMinute(1))
->set(new BetweenHours(8, 16)
)
);
use Tobento\App\Notification\Task\SendNotificationRegistry;
'registries' => [
'newsletter.send' => new SendNotificationRegistry(
name: 'Send Newsletters',
// You may customize the statuses the user can select to send only:
allowedStatuses: ['active'], // default
// You may customize the notification types the user can select to send:
allowedTypes: ['newsletter'], // default
// You may customize the max. interval the user can to send:
maxAllowedIntervalToSend: 20, // default
// You may customize the max. interval the user can to queue:
maxAllowedIntervalToQueue: 20000, // default
// You may customize the queues the user can select:
allowedQueueNames: ['file'], // default
// You may add task parameters to be always processed:
parameters: [
new \Tobento\Service\Schedule\Parameter\WithoutOverlapping(),
],
// Define the supported apps where the task can be run:
supportedAppIds: ['root', 'backend'],
),
]
'registries' => [
'newsletter.send' => new Registry\CommandTask(
name: 'Queues 100 Newsletters at a time',
command: 'notifications:send',
input: [
'--interval' => 100,
'--queue' => 'file',
'--type' => ['newsletter'],
],
// You may add task parameters to be always processed:
parameters: [
new \Tobento\Service\Schedule\Parameter\WithoutOverlapping(),
],
// Define the supported apps where the task can be run:
supportedAppIds: ['root', 'backend'],
),
]
'features' => [
new Feature\NewsletterSubscribers(
// A menu name to show the newsletter subscribers link or null if none.
menu: 'main',
menuLabel: 'Newsletter Subscribers',
// A menu parent name (e.g. 'system') or null if none.
menuParent: null,
// you may disable the ACL while testing for instance,
// otherwise only users with the right permissions can access the page.
withAcl: false,
),
],
use Tobento\App\Notification\Newsletter\SubscriberRepositoryInterface;
use Tobento\App\User\UserInterface;
use Tobento\Service\User\AddressInterface;
class SomeService
{
public function demoWithUser(SubscriberRepositoryInterface $subscriberRepository): void
{
// Subscribe user returning the subscribed subscriber or null.
// If already subscribed it updates subscription:
$subscriber = $subscriberRepository->subscribeUser($user); // UserInterface
// You may add additional attributes:
$subscriber = $subscriberRepository->subscribeUser(
user: $user,
attributes: ['status' => 'active'], // unconfirmed is default status
);
// Unsubscribe user returning the unsubscribed subscriber or null if none unsubscribed:
$unsubscribedSubscriber = $subscriberRepository->unsubscribeUser($user); // UserInterface
// You may check if the given user has already been subscribed returning a boolean:
$subscribed = $subscriberRepository->hasSubscribedUser($user); // UserInterface
}
public function demoWithAddress(SubscriberRepositoryInterface $subscriberRepository): void
{
// Subscribe address returning the subscribed subscriber or null.
// If already subscribed it updates subscription:
$subscriber = $subscriberRepository->subscribeAddress($address); // AddressInterface
// You may add additional attributes:
$subscriber = $subscriberRepository->subscribeAddress(
address: $address,
attributes: ['status' => 'active'], // unconfirmed is default status
);
// Unsubscribe address returning the unsubscribed subscriber or null if none unsubscribed:
$unsubscribedSubscriber = $subscriberRepository->unsubscribeAddress($address); // AddressInterface
// You may check if the given address has already been subscribed returning a boolean:
$subscribed = $subscriberRepository->hasSubscribedAddress($address); // AddressInterface
}
}
'features' => [
new Feature\SubscribeNewsletter(
// The view to render.
view: 'newsletter/subscribe',
// A menu name to show the subscribe link or null if none.
menu: 'footer',
menuLabel: 'Newsletter',
// A menu parent name (e.g. 'information') or null if none.
menuParent: null,
// The message shown after successful subscription:
successMessage = 'Thank you for subscribing to our newsletter - we are excited to have you with us!',
// When the channelVerification parameter is set to true you may consider changing the message to something like:
// successMessage = 'You are almost there! We have sent a confirmation email - just click the link to complete your subscription.',
// The message shown when the email or phone already exists.
alreadySubscribedMessage: 'You have already subscribed to our newsletter.',
// The route to redirect to after successful subscription.
successRedirectRoute: 'home',
// The subscriber status after a successful subscription.
subscriberStatus: 'unconfirmed',
// It is not recommended to set the status to 'active' as it may be a spammed email.
// Change the status manually at the subscriber web interface or
// use channel verification instead.
// If true, routes are being localized.
localizeRoute: false,
// The channels to support.
supportedChannels: ['email', 'smartphone'],
// When true, a notification email and/or sms depending on the supported channels defined
// will be sent to verify the channel(s).
channelVerification: true,
// The expiration in day after the channel verfication url expires.
channelVerificationUrlExpiresInDays: 5,
// When the channelVerification parameter is set to true,
// this status will be used after all channels defined have been verified,
// otherwise the status of the subscriberStatus parameter is used.
subscriberStatusForVerifiedChannels: 'active',
// The message shown after a successful channel confirmation.
confirmSuccessMessage: 'Your newsletter subscription has been successfully confirmed.',
// The message shown after a failed channel confirmation.
confirmFailedMessage: 'Newsletter subscription confirmation failed.',
// When true, only the store route will be available.
onlySubscribeStoreRoute: false,
// you may disable the ACL while testing for instance,
// otherwise only users with the right permissions can access the page.
withAcl: false,
),
],
use Tobento\App\Notification\Newsletter;
'interfaces' => [
Newsletter\TopicsInterface::class =>
static function(): Newsletter\TopicsInterface {
return new Newsletter\Topics([
'products.new' => trans('Inform me about new products.'),
'articles.new' => trans('Inform me about new articles.'),
]);
},
],
use Tobento\App\Spam\Factory;
'detectors' => [
'newsletter.subscribe' => new Factory\Composite(
new Factory\Honeypot(inputName: 'hp'),
new Factory\MinTimePassed(inputName: 'mtp', milliseconds: 1000),
),
]
use Tobento\App\AppInterface;
use Tobento\App\Notification\Feature\SubscribeNewsletter;
use Tobento\App\RateLimiter\Middleware\RateLimitRequests;
use Tobento\App\RateLimiter\Symfony\Registry\SlidingWindow;
use Tobento\App\Spam\Middleware\ProtectAgainstSpam;
use Tobento\Service\Routing\RouterInterface;
class CustomSubscribeNewsletter extends SubscribeNewsletter
{
protected function configureRoutes(RouterInterface $router, AppInterface $app): void
{
$router->getRoute(name: 'newsletter.subscribe.store')->middleware([
RateLimitRequests::class,
'registry' => new SlidingWindow(limit: 6, interval: '5 Minutes', id: 'newsletter.subscribe'),
'redirectRoute' => 'newsletter.subscribe',
'message' => 'Too many attempts. Please retry after :seconds seconds.',
//'messageLevel' => 'error',
], [
ProtectAgainstSpam::class,
'detector' => 'newsletter.subscribe',
]);
}
}
use Tobento\App\Notification\Event;
'features' => [
new Feature\UnsubscribeNewsletter(
// You may change the success message:
successMessage: 'Your newsletter subscription has been successfully cancelled.',
// You may change the failed message:
successMessage: 'Your newsletter subscription has already been cancelled.',
// You may change the redirect route:
redirectRoute: 'home',
// If true, routes are being localized.
localizeRoute: false,
),
],
use Tobento\Service\Dater\Dater;
use Tobento\Service\Routing\RouterInterface;
final class SomeService
{
public function __construct(
private readonly RouterInterface $router,
) {}
public function generateUnsubscribeLink(int|string $subscriberId): string
{
retrun (string) $this->router
->url('newsletter.unsubscribe', ['id' => $subscriberId])
->sign(new Dater()->addDays(3));
// with locale:
retrun (string) $this->router
->url('newsletter.unsubscribe', ['id' => $subscriberId, 'locale' => 'de'])
->sign(new Dater()->addDays(3));
}
}
'features' => [
new Feature\Tracking(
// you may customize the failed message:
failedMessage: 'The link you used is either expired or invalid, so you\'ve been redirected to this page.',
// you may change the redirect route which will be used if
// tracking token expired or is not found e.g.
failedRedirectRoute: 'home',
// you may change the logging:
logTrackingTokenExpiredException: true, // default
logTrackingTokenExceptions: true, // default
// you may change the route prefix:
routePrefix: 'nft', // default
),
],
use Tobento\App\Notification\Tracking;
use Tobento\Service\Database\DatabasesInterface;
'interfaces' => [
Tracking\EventRepositoryInterface::class =>
static function(
DatabasesInterface $databases,
Tracking\ClickRepositoryInterface $clickRepository,
): Tracking\EventRepositoryInterface {
return new Tracking\EventStorageRepository(
storage: $databases->get('mysql-storage')->storage()->new(),
table: 'notification_tracking_events',
clickRepository: $clickRepository,
);
},
],
use Tobento\App\Notification\Tracking\EventRepositoryInterface;
use Tobento\Service\Session\SessionInterface;
final class NotificationReportOrderCompleted
{
public function __construct(
private readonly SessionInterface $session,
private readonly EventRepositoryInterface $eventRepository,
) {}
public function subscribe(Event\OrderCompleted $event): void
{
if ($clickId = $this->session->get('notification_tracking_click_id')) {
$this->eventRepository->createFromClickId(
clickId: $clickId,
name: 'orders',
meta: ['order_id' => $event->order()->id()],
);
}
}
}
use Tobento\App\Notification\Channel;
'interfaces' => [
Channel\ChannelsInterface::class =>
static function(): Channel\ChannelsInterface {
return new Channel\Channels(
new Channel\Chat(
// Define the notifier channel name:
name: 'chat-slack', // default
// Specify a title:
name: 'Slack', // default
),
);
},
],
use Tobento\App\Notification\Channel;
'interfaces' => [
Channel\ChannelsInterface::class =>
static function(): Channel\ChannelsInterface {
return new Channel\Channels(
new Channel\Push(
// Define the notifier channel name:
name: 'push', // default
// Specify a title:
name: 'Push', // default
),
);
},
],
use Tobento\App\Notification\Placeholder\Placeholder;
use Tobento\App\Notification\Placeholder\Placeholders;
$placeholders = new Placeholders(
new Placeholder(
key: 'date',
value: fn () => date("F j, Y, g:i a"),
),
);
use Tobento\App\Notification\NotificationEntityInterface;
use Tobento\App\Notification\Placeholder\Placeholder;
use Tobento\Service\Notifier\NotificationInterface;
use Tobento\Service\Notifier\RecipientInterface;
use Tobento\Service\User\AddressInterface;
$placeholder = new Placeholder(
// define a key:
key: 'recipient.locale',
// define a value:
value: 'de',
// or using a closure:
value: fn (
NotificationEntityInterface $entity,
RecipientInterface $recipient,
AddressInterface $address,
null|NotificationInterface $notification,
// ... any other parameters are being resolved by autowiring
) => $recipient->getLocale(),
// you set if the placeholder is active or not using a boolean:
active: false,
// or using a closure with autowired parameters:
active: fn (): bool => false,
// you may define a fallback value:
fallbackValue: 'en',
// you may define a description:
description: 'Locale ...',
),
use Tobento\App\Notification\Placeholder\GreetingPlaceholder;
$placeholder = new GreetingPlaceholder();
$placeholder = new GreetingPlaceholder(
// you may customize the key:
key: 'greeting', // default
),
'registries' => [
'purge.tracking.tokens' => new Registry\CommandTask(
name: 'Deletes tracking tokens 30 days after the expiration date.',
command: 'notifications:purge-tracking-tokens',
input: [
'--days' => 30,
],
// Define the supported apps where the task can be run:
supportedAppIds: ['root', 'backend'],
),
]
use Tobento\App\Crud\ActionProcessor;
use Tobento\App\Crud\ActionProcessorInterface;
use Tobento\App\Notification\Action\PreviewNotificationAction;
use Tobento\App\Notification\Action\PreviewNotificationChannelAction;
use Tobento\App\Notification\Controller\SendNotificationController;
use Tobento\Service\Language\AreaLanguagesInterface;
// Get the languages you support for resources:
$areaLanguages = $app->get(AreaLanguagesInterface::class);
$languages = $areaLanguages->get('resources'); // or whatever name you have configured
// Configure:
$app->set(ActionProcessorInterface::class, ActionProcessor::class)->with(['languages' => $languages]);
$app->set(PreviewNotificationAction::class)->with(['languages' => $languages]);
$app->set(PreviewNotificationChannelAction::class)->with(['languages' => $languages]);
$app->set(SendNotificationController::class)->with(['languages' => $languages]);
app/config/notification.php
app/config/spam.php
app/config/notifier.php
app/config/notifier.php
app/config/notifier.php
app/config/notifier.php
app/config/notifier.php
php ap notifications:send
php ap notifications:purge-tracking-tokens
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.