modera / notification-bundle
提供存储/检索通知的功能。
Requires
- php: >=5.6
- doctrine/doctrine-bundle: ^1.4|^2.0
- doctrine/orm: ^2.4
- sergeil/expander-bundle: ^1.0
- symfony/framework-bundle: ~2.8|^3.0|^4.0|^5.0
Requires (Dev)
- modera/foundation-bundle: ^3.0|^4.0
- phake/phake: ^2.0
- phpunit/phpunit: ^5.5
- sergeil/aux-bundle: ^1.0
- symfony/security: ^3.0
README
提供存储/检索通知的功能。
安装
将以下依赖项添加到您的 composer.json 文件中
"modera/notification-bundle": "dev-master"
如果此扩展与 Modera Foundation 一起使用,则不需要额外的配置。如果您使用的是普通的 Symfony,则需要配置 Doctrine ORM 并指定您正在使用的 Symfony\Component\Security\Core\User\UserInterface
的实现。例如,如果您有一个实现 UserInterface 的实体 Acme/AppBundle/Entity/User,则可以将以下内容添加到 config.yml
文件中,以便 ModeraNotificationBundle 扩展能够正常工作
doctrine:
orm:
resolve_target_entities:
Symfony\Component\Security\Core\User\UserInterface: Acme/AppBundle/Entity/User
此外,别忘了更新您的 AppKernel 类,并将 ModeraNotificationBundle 添加到其中
new \Modera\NotificationBundle\ModeraNotificationBundle(),
最后,更新数据库
app/console doctrine:schema:update --force
文档
该扩展提供了一个可扩展的架构,具有简单的 API,开发人员可以使用它将用户通知支持集成到自己的应用程序中。通过使用所谓的“通道”来实现可扩展性,这些通道用于整合通过特定介质传递通知所需的逻辑——例如,一个通道可能负责通过发送短信或甚至使用推送通知到移动设备来传递文本片段。该扩展提供了一套非常直观的 API,开发人员可以使用它来创建自己的通道。
当您想将传递通知的支持添加到应用程序时,首先需要使用通知中心,它由 Modera\NotificationBundle\Dispatching\NotificationCenter 类的实例表示。通常,您不会手动创建其实例,而是使用 modera_notification.dispatching.notification_center 依赖注入服务。例如,要从控制器获取通知服务,您可以这样做
public function indexAction()
{
$notificationCenter = $this->container->get('modera_notification.dispatching.notification_center');
}
简而言之,通知中心提供了一组高级 API,您可以在配置和从应用程序代码中发送通知时与之交互。您可以使用通知中心实现的操作包括
- 为特定通道、用户发送通知并监控其传递状态
- 查询通知
- 更改通知状态(将其标记为已读)
发送通知
如前所述,为了发送通知,您需要使用通知中心并依赖其简单且高级的 API。发送通知时,您可以配置几个方面
- 通知应通过哪些通道传递
- 谁应接收通知
- 您想与通知关联的哪些可选元数据
当您想发送通知时,首先需要创建所谓的“通知构建器”,这是一个对象,它提供了一组方法,您可以使用这些方法调整上述列表中提到的内容。这是您发送第一条通知需要做的事情
$user = $em->getRepository(User::class)->findOneBy(array('username' => 'foo_user'));
$notificationCenter->createNotificationBuilder('hello world', 'test-group');
->addRecipient($user)
->dispatch()
;
当你调用底层的 createNotificationBuilder 时,通知中心将创建一个与给定通知中心关联的 Modera\NotificationBundle\Dispatching\NotificationBuilder 实例,该实例提供流畅的接口方法,你可以通过链式调用配置你的通知。方法的第一参数包含关于此通知的文本,第二个参数是一个组名(请参考 NotificationBuilder 类属性以查看“group”参数的完整描述)。在这种情况下,一旦创建了通知构建器的实例,我们发送通知的最后一件事就是指定谁应该接收它,为此你可以使用 addRecipient 或 setRecipients 方法,调用它们时需要提供我们在本指南“安装”部分中提到的 UserInterface 的实现。配置好通知后,你可以调用 dispatch 方法,通知将尝试通过所有已注册的渠道发送,因为我们没有使用接受通道 ID 列表的 setChannels 方法,也没有在调用 dispatch 方法时指定它们。以下是一个示例,说明如何在指定要使用的通道的情况下发送通知
$notificationCenter->createNotificationBuilder('hello world', 'test-group');
->setRecipients([$bob, $jane])
->dispatch(['push', 'email'])
;
更细粒度地控制通知发送
到目前为止,我们已经查看了一组非常简单的发送通知时可以使用的函数,但有时你可能想更细粒度地控制通知的发送方式,它包含哪些可选元信息,以及发送后是否在传输过程中出现任何问题。以下是一个更高级的示例
$notificationCenter->createNotificationBuilder('hello world', 'test-group');
->setMetaProperty('sent_by', 'vasya@example.org')
->setContextProperty('sms_api_key', 12345)
->throwExceptionWhenChannelNotFound()
->setRecipients([$bob, $jane])
->dispatch(['push', 'sms', 'email'])
;
有时在通知被发送到用户后,用户想要对其执行某些操作,这时需要将一些附加的规范化信息存储在通知中,以便处理逻辑可以更好地了解在用户确认通知时应采取哪些行动。为此,你可以使用两种方法:setMeta 和 setMetaProperty。第一个方法允许你覆盖特定通知构建器中存储的所有元信息,而第二个方法 setMetaProperty 可以用来指定一个元属性。
我们调用链中的下一个方法是 setContextProperty。虽然元信息与通知内容本身一起存储在持久存储中,但上下文属性只会在这个特定的请求-响应服务器周期中存在。通常,你可能想使用上下文属性来调整特定通知通道的配置属性,这些属性会影响通知的发送方式,但一旦通知被发送,这些详细信息就可以被丢弃。
最后一个方法是 throwExceptionWhenChannelNotFound,可以用来指示通知中心如果找不到某些指定的通道,则必须抛出异常 - 默认行为相反,如果找不到一些通道,它们将被简单地忽略。一旦调用了 throwExceptionWhenChannelNotFound,你可以使用 suppressChannelNotFoundException 来将其恢复到默认行为。
控制通知分发状态
我们还没有讨论的是,你可以找出通知是否已成功发送,或者哪些渠道可能失败以及错误是什么。当你使用 NotificationBuilder 上的 dispatch 方法发送通知时,会返回一个 Modera\NotificationBundle\Dispatching\DeliveryReport 实例,你可以使用它来获取分发状态。以下是一个示例,说明你可以使用的一些方法
$report = $notificationCenter->createNotificationBuilder('hello world', 'test-group');
// ...
->dispatch()
;
if ($report->isSuccessful()) {
echo "Everything's good, it seems all channel managed to deliver a notification.";
} else {
foreach ($report->getFailedDeliveries() as $info) {
echo sprintf("%s: failed to deliver a notification, error: ", $info['channel']->getId(), $info['error']);
echo $info['meta'] ? print_r($info['meta'], true) : 'No meta-information provided';
echo "\n";
}
}
除了 isSuccessful 和 getFailedDeliveries 方法外,还有一些其他方法可以使用
- isFailed() - 是 isSuccessful 的对立面,如果任何渠道被认为无法发送通知,则返回 TRUE
- getSuccessfulDeliveries() - 使用此方法获取能够成功发送通知的渠道列表。此方法返回数组的每个元素都是一个包含三个键的数组:channel(ChannelInterface实例)、message(由渠道指定的可选消息)、meta(用于应用程序逻辑以更好地理解分发过程的非规范化元数据)
有关所有可用方法的完整列表,请参阅DeliveryReport类。
命令行
Bundle提供了几个可能在开发和生产环境中都很有用的命令行命令。
清理旧通知
每次分发新通知时,数据库中都会创建一些记录。如果您有大量用户的大量部署,您的通知数据库最终可能会增长,性能可能会开始下降。为了解决这个问题,Bundle提供了modera:notification:clean-up控制台命令,您可以使用它来清理已标记为已读的通知。
app/console modera:notification:clean-up
分发通知
在开发过程中,有时您可能想要模拟发送通知,而不必手动编写所需的源代码。如果是这种情况,我们为您提供了覆盖方案,Bundle提供了modera:notification:send-notification命令,您可以使用它从控制台环境发送通知。以下是一个示例
app/console modera:notification:send-notification "Hello world" modera_backend_chat_notification_bridge "*"
您需要传递的最小配置参数才能分发通知,让我们更仔细地看看它们
- "Hello world" - 如您可能已经猜到的,它包含此通知的内容
- "modera_backend_chat_notification_bridge" - 一个组名,此值用于将类似的通知分组在一起,有关详细信息,请参阅NotificationBuilder::$group
- "*" - 第三个参数接受应接收给定通知的用户ID列表,在此情况下,“*”表示将向数据库中找到的所有用户发送通知。用户ID列表必须用逗号分隔,例如 - 1,2,6
此外,还有另外两个我们尚未展示的参数,您可以指定用于分发通知的渠道,以及您可以指定与给定通知一起存储的元键。以下是一个示例,说明如何指定仅使用"email"、"sms"渠道分发通知,并包含"sender"元键
app/console modera:notification:send-notification "Hello world" modera_backend_chat_notification_bridge "*" --channels="email,sms" --meta="sender=john.doe@example.org"
您可以指定任意多的--meta配置选项,此选项的内容是一个用等号(=)分隔的字符串,其中左侧是参数名称,右侧是其值。
架构
该组件具有非常简单直接的架构,由三个部分组成——通知中心、通道和通知对象。第一个您已经在之前的章节中见过,提醒一下,通知类由Modera\NotificationBundle\Dispatching\NotificationCenter表示,这是您在应用程序逻辑中添加通知发送支持时将使用的API所在的类。第二个我们略微提到的是“通道”,它由实现Modera\NotificationBundle\Dispatching\ChannelInterface接口的类表示。简而言之,当通知中心涉及发送通知时,它只负责减轻开发人员希望通过通道发送通知之间的协作,并将通知数据存储在数据库中,而通过不同媒介发送通知的繁重工作则由通道负责处理。第三个组件是通知对象本身,它由Modera\NotificationBundle\Model\NotificationInterface表示,该接口提供了一个非常通用且不依赖于持久化技术的API,用于读取通知详情,下节中描述的fetchBy和fetchOneBy方法返回该接口的实例。
通知中心
到目前为止,我们只使用了NotificationCenter的一个方法createNotificationCenter,但实际上还有其他几个方法可以用来查询和修改通知。
-
fetchBy、fetchOneBy——这两个方法用于从数据库中查询通知,两个方法都接受所谓的“数组查询”,实际上是一种非常简化的语法,用于通过简单的关联数组构建查询。请参阅方法API文档以查看配置属性的完整列表。这些方法返回NotificationInterface的实例。示例
$notifications = $notificationCenter->fetchBy(array('group' => 'test', 'status' => NotificationInterface::STATUS_NOT_READ));
-
changeStatus——此方法可用于更改通知的状态(例如,将某些标记为已读)。方法接受两个参数——通知必须具有的新状态以及描述哪些通知应该更新的“数组查询”。示例
$notificationCenter->changeStatus(NotificationInterface::STATUS_READ, array('group' => 'test'));
通道 & 创建通道
如前所述,通道是负责执行繁重工作并最终通过特定媒介发送通知的部分。了解通道工作方式的最佳方式是创建一个,在本节中,我们将创建一个非常简单的通道,该通道将发送的通知写入Monolog日志。
如果您已经阅读了之前的章节,那么您可能还记得,通道由ChannelInterface表示,该接口只包含少量方法,通常您不会直接实现此接口,而是扩展Modera\NotificationBundle\Dispatching\AbstractChannel,这将为您实现一些方法,但让我们仍花点时间描述接口的方法都做了什么。
- getId——必须返回一个唯一ID,可以用于识别您的通道,您将使用此方法指定通知应通过哪些通道发送,例如使用NotificationBuilder的dispatch方法。
- getAliases——这是一个可选方法,您可能希望用来为您的通道提供其他名称。例如,虽然您的getId方法可能返回“backend”,但getAliases方法也可能返回类似“backend.title”或“backend.desktop*”的内容。此功能在您的通道可能有“子通道”时很有用。
- dispatch(NotificationBuilder $builder, DeliveryReport $report) - 这个方法应包含负责实际发送通知的逻辑,换句话说,这里就是实际进行大量工作的地方。该方法接受两个参数 - 一个通知构建器,这是开发者用来指定通知如何发送的,你可以从构建器中提取所需详细信息来配置通知在通过通道发送之前应该如何传输,第二个参数 - $report,这是一个由 NotificationBuilder::dispatch() 方法内部创建的对象,你可以使用这个对象来报告你的通道是否成功发送了消息或存在问题,开发者之后可以使用这个对象来获取关于发送进度的反馈。
现在你已经知道了通道的方法,让我们编写我们的自定义通道,这是我们的 Monolog 通道的实现(所有注释都故意省略了)
namespace Modera\NotificationBundle\Channels;
use Modera\NotificationBundle\Dispatching\AbstractChannel;
use Modera\NotificationBundle\Dispatching\DeliveryReport;
use Modera\NotificationBundle\Dispatching\NotificationBuilder;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class MonologChannel extends AbstractChannel
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function getId()
{
return 'monolog';
}
public function dispatch(NotificationBuilder $builder, DeliveryReport $report)
{
$usernames = [];
foreach ($builder->getRecipients() as $user) {
/* @var UserInterface $user */
$usernames[] = $user->getUsername();
}
try {
$message = sprintf(
'Notification with contents "%s" dispatched for %d users: %s.',
$builder->getMessage(),
count($usernames),
implode(', ', $usernames)
);
$this->logger->info($message, $builder->getMeta());
$report->markDelivered($this);
} catch (\Exception $e) {
$report->markFailed($this, $e->getMessage());
}
}
}
通道的实现相当直观,我只想让你注意一点,那就是我们如何根据通道是否成功调用 $report 的 markDelivered 和 markFailed 方法。一旦通道创建完成,我们还需要完成一个步骤,使我们的通知中心看到有新的通道可用。为此,我们需要向 modera_notification.channels 扩展点创建一个贡献
namespace Modera\NotificationBundle\Contributions;
use Modera\NotificationBundle\Dispatching\ChannelInterface;
use Sli\ExpanderBundle\Ext\ContributorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Modera\NotificationBundle\Channels\MonologChannel;
class ChannelsProvider implements ContributorInterface
{
private $container;
private $channels;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getItems(): array
{
if (!$this->channels) {
$this->channels = [
new MonologChannel($this->container->get('logger'))
];
}
return $this->channels;
}
}
并在依赖注入容器中注册我们的贡献者类
<service id="modera_notification.contributions.channels_provider"
class="Modera\NotificationBundle\Contributions\ChannelsProvider">
<argument type="service" id="service_container" />
<tag name="modera_notification.channels_provider" />
</service>
到目前为止,如果你尝试发送新的通知,通知通道应该能够发现我们的 Monolog 通道,并使用它来发送通知,你可以在控制台中运行类似以下命令来尝试
echo "" > app/logs/dev.log && app/console modera:notification:send-notification "hello test" test_group "*"
然后检查 app/logs/dev.log 文件的内容。
许可
此包受 MIT 许可证保护。在包中查看完整的许可证:Resources/meta/LICENSE