PHP code example of fomvasss / laravel-notify-templates
1. Go to this page and download the library: Download fomvasss/laravel-notify-templates 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/ */
fomvasss / laravel-notify-templates example snippets
return [
'tables' => [
'notify_templates' => 'notify_templates',
'notify_role_subscriptions' => 'notify_role_subscriptions',
],
// All delivery channels available in the project.
// Used as UI listing and as fallback when typeDefinition()['channels'] is empty.
// Include only channels actually wired up in the app.
'channels' => ['mail', 'telegram', 'sms', 'database', 'broadcast'],
// Fallback channels when subscription has no channels configured,
// or when via() resolves to nothing entirely
'default_channels' => ['mail'],
// Tenant ID: null (single-tenant), or a callable that returns the tenant ID string.
// Used automatically by NotifyTemplatesManager (resolveTemplate/resolveChannels/resolveDelay,
// and therefore BaseNotify) whenever no explicit $tenantId is passed — set $this->tenantId in
// a concrete Notify class to override it per-instance.
'tenant_id' => null,
// 'tenant_id' => fn() => app('domain')->getId(),
// Directories to scan for BaseNotify subclasses on boot (auto-discovery)
'discover' => [
app_path('Notifications'),
],
// Optional: pre-register notify types via config
'types' => [],
// Override models in the project (e.g. to add translatable support)
'models' => [
'notify_template' => \Fomvasss\NotifyTemplates\Models\NotifyTemplate::class,
],
];
public static function typeDefinition(): array
{
return [
'key' => 'UserOtp',
// ...
'user_configurable' => false,
// Only these roles can have a subscription/template for this type. An OTP code always goes to the
// user who is logging in, so a template for any other role would never be used
'allowed_roles' => ['client'],
// Sent directly ($user->notify()) in response to the user's own action, not through a role resolver.
// Combined with user_configurable = false, a disabled subscription falls back to default_channels
// and the mail goes out anyway, so the admin UI shows the "active" toggle locked instead
'always_sent' => true,
];
}
// Admin matrix: hide cells for roles the type doesn't allow
$allowed = NotifyTemplates::getType($key)['allowed_roles'] ?? null;
$roleAllowed = $allowed === null || in_array($role, $allowed, true);
// Subscription toggle: refuse to disable what can't be disabled
abort_if(NotifyTemplates::getType($key)['always_sent'] ?? false, 422, 'This type is always sent');
NotifyTemplates::getTypes(); // all types
NotifyTemplates::getTypes('order'); // filtered by group
NotifyTemplates::getType('OrderOrdered');
use Fomvasss\NotifyTemplates\Traits\HasNotifySettings;
class User extends Authenticatable
{
use HasNotifySettings;
protected $casts = [
'notify_channels' => 'array',
];
}
public function getNotifyChannels(): array
{
return $this->channels ?? [];
}
if (!$this->manager()->isNotifyEnabled($this->getNotifyKey(), $notifiable)) {
return [];
}
$override = $this->manager()->resolveNotifyUserChannels($this->getNotifyKey(), $notifiable); // null = no override
use Fomvasss\NotifyTemplates\Contracts\NotifyRoleResolverInterface;
use Fomvasss\NotifyTemplates\Models\NotifyRoleSubscription;
class AppNotifyRoleResolver implements NotifyRoleResolverInterface
{
// Roles you actually trust to receive a broadcast — typically your internal staff role(s).
// Whitelist, not blacklist: any role not listed here (your "customer"/"tenant" role, and any
// future role you add and forget to review) defaults to personal-only delivery. Without this,
// a subscription row created with its default personal_only=false — e.g. the first time
// someone opens the admin UI for a brand-new notify type, via a lazy firstOrNew() — silently
// broadcasts to *every* holder of that role the first time the event fires. For a role with
// many unrelated accounts (customers, tenants) that means leaking one person's event (an
// invite link, a generated password, ...) to everyone else on the platform.
private const BROADCAST_SAFE_ROLES = ['admin'];
public function resolveUsersForNotify(string $notifyKey, mixed $context = null): array
{
$tenantId = config('notify-templates.tenant_id');
$tenantId = is_callable($tenantId) ? $tenantId() : $tenantId;
$subscriptions = NotifyRoleSubscription::query()
->active()
->forNotify($notifyKey)
->forTenant($tenantId)
->get();
$result = [];
foreach ($subscriptions as $sub) {
$forcePersonal = !in_array($sub->role_key, self::BROADCAST_SAFE_ROLES, true);
if (($sub->personal_only || $forcePersonal) && $context?->user) {
$result[$sub->role_key] = collect([$context->user]);
} else {
$result[$sub->role_key] = User::role($sub->role_key)
->where('status', User::STATUS_ACTIVE)
->get();
}
}
return $result;
}
}
// send only via mail, regardless of subscription settings
$user->notify((new UserOtpNotify(roleKey: 'client', code: $code))->only(['mail']));
// send via all resolved channels except sms
$user->notify((new OrderOrderedNotify(roleKey: 'client'))->except(['sms']));
namespace App\Notifications;
use Fomvasss\NotifyTemplates\Notifications\BaseNotify;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Str;
use NotificationChannels\Telegram\TelegramMessage;
abstract class BaseNotification extends BaseNotify
{
// 1. Custom channels: map a subscription channel slug to a channel name / class-string,
// or null to skip (no route). This is the ONLY method to touch — the opt-out gate,
// user channel preferences, subscription resolution, the user_configurable fallback
// and only()/except() all keep applying to these channels automatically.
protected function mapChannel(string $channel, mixed $notifiable): ?string
{
return match ($channel) {
'telegram' => $notifiable->routeNotificationForTelegram() ? 'telegram' : null,
'sms' => $notifiable->phone ? TurboSmsChannel::class : null,
default => parent::mapChannel($channel, $notifiable),
};
}
// 2. A to{Channel}() per added channel. getMessengerBody() resolves the 'messenger'
// template slot (falling back to 'mail') and runs prepareText() on it.
// Messengers have hard message limits — trim and strip HTML per channel.
public function toTelegram(mixed $notifiable): TelegramMessage
{
return TelegramMessage::create()
->options(['parse_mode' => 'HTML'])
->line($this->getMessengerBody($notifiable));
}
public function toTurboSms(mixed $notifiable): string
{
return Str::limit(strip_tags($this->getMessengerBody($notifiable)), 660);
}
// 3. Token replacement — applied to subject/body of every channel
// (example uses fomvasss/laravel-str-tokens; any templating works)
protected function prepareText(string $text, mixed $notifiable): string
{
return \StrToken::setEntity($notifiable)->setText($text)->replace();
}
// 4. Optional: custom mail view instead of the default ->line()
public function toMail(mixed $notifiable): MailMessage
{
$template = $this->resolveTemplate('mail');
return (new MailMessage())
->subject($this->prepareText($template?->subject ?: $this->getSubjectDefault(), $notifiable))
->view('mails.plain', ['body' => $this->prepareText($template?->body ?: $this->getBodyDefault(), $notifiable)]);
}
}
use Fomvasss\NotifyTemplates\Contracts\NotifyRoleResolverInterface;
use Fomvasss\NotifyTemplates\Facades\NotifyTemplates;
use Illuminate\Support\Facades\Notification;
class OrderOrderedListener
{
public function __construct(protected NotifyRoleResolverInterface $resolver) {}
public function handle(OrderOrdered $event): void
{
$order = $event->order->fresh();
foreach ($this->resolver->resolveUsersForNotify('OrderOrdered', $order) as $roleKey => $users) {
$delay = NotifyTemplates::resolveDelay('OrderOrdered', $roleKey, $order->domain_id);
Notification::send(
$users,
(new OrderOrderedNotify($order, $roleKey))->delay($delay),
);
}
}
}
// Type registry
NotifyTemplates::discoverIn(string $path): void
NotifyTemplates::registerType(array $type): void
NotifyTemplates::registerTypes(array $types): void
NotifyTemplates::getTypes(?string $group = null): array
NotifyTemplates::getType(string $key): ?array
// Channels supported by a notify type (from typeDefinition or config fallback)
NotifyTemplates::getTypeChannels(string $notifyKey): array
// Template resolution (8-level fallback chain)
NotifyTemplates::resolveTemplate(string $notifyKey, string $channel, ?string $roleKey, ?string $tenantId): ?NotifyTemplate
// Delivery channels: subscription channels intersected with user preferences (user can opt out, not add)
NotifyTemplates::resolveChannels(string $notifyKey, string $roleKey, ?string $tenantId, array $userChannels = []): array
// Messenger buttons, raw: template options.buttons → buttons_by_role[role] → buttons
NotifyTemplates::resolveButtons(string $notifyKey, ?string $roleKey, ?string $tenantId, string $channel = 'messenger', ?array $type = null): array
NotifyTemplates::resolveButtonsColumns(string $notifyKey, ?string $roleKey, ?string $tenantId, string $channel = 'messenger', ?array $type = null): int
// Button text for a locale: plain string as is, locale map → locale → fallback_locale → first filled
NotifyTemplates::localizeButtonText(string|array $text, ?string $locale = null): string
// Delay in seconds (options.delay in DB is stored in minutes)
NotifyTemplates::resolveDelay(string $notifyKey, string $roleKey, ?string $tenantId): int
// Delivery report from a provider; status only moves forward
NotifyTemplates::updateDelivery(string $channel, string $externalId, string $status, array $payload = []): bool
use Fomvasss\NotifyTemplates\Contracts\ExternalIdResolverInterface;
class TurboSmsIdResolver implements ExternalIdResolverInterface
{
public function resolve(mixed $response): ?string
{
return $response['response_result'][0]['message_id'] ?? null;
}
}
// app/Models/NotifyTemplate.php
namespace App\Models;
use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
use Astrotomic\Translatable\Translatable;
use Fomvasss\NotifyTemplates\Models\NotifyTemplate as BaseNotifyTemplate;
class NotifyTemplate extends BaseNotifyTemplate implements TranslatableContract
{
use Translatable;
public array $translatable = ['subject', 'body'];
}
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Authenticatable implements HasLocalePreference
{
public function preferredLocale(): string
{
return $this->locale ?? config('app.locale');
}
}