Skip to content

Commit

Permalink
[Notifier] added the component
Browse files Browse the repository at this point in the history
  • Loading branch information
fabpot committed Oct 1, 2019
1 parent 5e48c39 commit 084e9bc
Show file tree
Hide file tree
Showing 107 changed files with 4,557 additions and 0 deletions.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -69,6 +69,7 @@
"symfony/messenger": "self.version",
"symfony/mime": "self.version",
"symfony/monolog-bridge": "self.version",
"symfony/notifier": "self.version",
"symfony/options-resolver": "self.version",
"symfony/postmark-mailer": "self.version",
"symfony/process": "self.version",
Expand Down
75 changes: 75 additions & 0 deletions src/Symfony/Bridge/Monolog/Handler/NotifierHandler.php
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Monolog\Handler;

use Monolog\Handler\AbstractHandler;
use Monolog\Logger;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\Notifier\NotifierInterface;

/**
* Uses Notifier as a log handler.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NotifierHandler extends AbstractHandler
{
private $notifier;

public function __construct(NotifierInterface $notifier, int $level = Logger::ERROR, bool $bubble = true)
{
$this->notifier = $notifier;

parent::__construct($level < Logger::ERROR ? Logger::ERROR : $level, $bubble);
}

public function handle(array $record): bool
{
if (!$this->isHandling($record)) {
return false;
}

$this->notify([$record]);

return !$this->bubble;
}

public function handleBatch(array $records): void
{
if ($records = array_filter($records, [$this, 'isHandling'])) {
$this->notify($records);
}
}

private function notify(array $records): void
{
$record = max(array_column($records, 'level'));
if (($record['context']['exception'] ?? null) instanceof \Throwable) {
$notification = Notification::fromThrowable($record['context']['exception']);
} else {
$notification = new Notification($record['message']);
}

$notification->importanceFromLogLevelName(Logger::getLevelName($record['level']));

try {
$this->notifier->send($notification, ...Notifier::getAdminReceivers());
} catch (\Throwable $e) {
$message = $e->getMessage();
while ($e = $e->getPrevious()) {
$message .= ' > '.$e->getMessage();
}
error_log($message);
}
}
}
Expand Up @@ -27,6 +27,7 @@
use Symfony\Component\Lock\Store\SemaphoreStore;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Translation\Translator;
Expand Down Expand Up @@ -114,6 +115,7 @@ public function getConfigTreeBuilder()
$this->addRobotsIndexSection($rootNode);
$this->addHttpClientSection($rootNode);
$this->addMailerSection($rootNode);
$this->addNotifierSection($rootNode);

return $treeBuilder;
}
Expand Down Expand Up @@ -1475,4 +1477,50 @@ private function addMailerSection(ArrayNodeDefinition $rootNode)
->end()
;
}

private function addNotifierSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('notifier')
->info('Notifier configuration')
->{!class_exists(FullStack::class) && class_exists(Notifier::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->fixXmlConfig('chatter_transport')
->children()
->arrayNode('chatter_transports')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->fixXmlConfig('texter_transport')
->children()
->arrayNode('texter_transports')
->useAttributeAsKey('name')
->prototype('scalar')->end()
->end()
->end()
->children()
->arrayNode('channel_policy')
->useAttributeAsKey('name')
->prototype('array')
->beforeNormalization()->ifString()->then(function (string $v) { return [$v]; })->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->fixXmlConfig('admin_receiver')
->children()
->arrayNode('admin_receivers')
->prototype('array')
->children()
->scalarNode('email')->cannotBeEmpty()->end()
->scalarNode('phone')->defaultNull()->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
}
Expand Up @@ -89,6 +89,12 @@
use Symfony\Component\Messenger\Transport\TransportInterface;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory;
use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory;
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\Notifier\Receiver\AdminReceiver;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
Expand Down Expand Up @@ -297,6 +303,10 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerMailerConfiguration($config['mailer'], $container, $loader);
}

if ($this->isConfigEnabled($container, $config['notifier'])) {
$this->registerNotifierConfiguration($config['notifier'], $container, $loader);
}

$propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']);
$this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled);
$this->registerEsiConfiguration($config['esi'], $container, $loader);
Expand Down Expand Up @@ -1848,6 +1858,62 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
$envelopeListener->setArgument(1, $recipients);
}

private function registerNotifierConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
if (!class_exists(Notifier::class)) {
throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".');
}

$loader->load('notifier.xml');
$loader->load('notifier_transports.xml');

if ($config['chatter_transports']) {
$container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']);
} else {
$container->removeDefinition('chatter');
}
if ($config['texter_transports']) {
$container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']);
} else {
$container->removeDefinition('texter');
}

if ($this->mailerConfigEnabled) {
$sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0);
$container->getDefinition('notifier.email_channel')->setArgument(1, $sender);
} else {
$container->removeDefinition('notifier.email_channel');
}

if (!$this->messengerConfigEnabled) {
$container->removeDefinition('notifier.failed_message_listener');
}

$container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']);

$classToServices = [
SlackTransportFactory::class => 'notifier.transport_factory.slack',
TelegramTransportFactory::class => 'notifier.transport_factory.telegram',
NexmoTransportFactory::class => 'notifier.transport_factory.nexmo',
TwilioTransportFactory::class => 'notifier.transport_factory.twilio',
];

foreach ($classToServices as $class => $service) {
if (!class_exists($class)) {
$container->removeDefinition($service);
}
}

if (isset($config['admin_receivers'])) {
$notifier = $container->getDefinition('notifier');
foreach ($config['admin_receivers'] as $i => $receiver) {
$id = 'notifier.admin_receiver.'.$i;
$container->setDefinition($id, new Definition(AdminReceiver::class, [$receiver['email'], $receiver['phone']]));
$notifier->addMethodCall('addAdminReceiver', [new Reference($id)]);
}
}
}

/**
* {@inheritdoc}
*/
Expand Down
92 changes: 92 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.xml
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="notifier" class="Symfony\Component\Notifier\Notifier">
<argument type="tagged_locator" tag="notifier.channel" index-by="channel" />
<argument type="service" id="notifier.channel_policy" on-invalid="ignore" />
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
</service>
<service id="Symfony\Component\Notifier\NotifierInterface" alias="notifier" />

<service id="notifier.channel_policy" class="Symfony\Component\Notifier\Channel\ChannelPolicy">
<argument type="collection" /> <!-- policy -->
</service>

<service id="notifier.browser_channel" class="Symfony\Component\Notifier\Channel\BrowserChannel">
<argument type="service" id="request_stack" />
<tag name="notifier.channel" channel="browser" />
</service>

<service id="notifier.chat_channel" class="Symfony\Component\Notifier\Channel\ChatChannel">
<argument type="service" id="chatter.transports" />
<tag name="notifier.channel" channel="chat" />
</service>

<service id="notifier.sms_channel" class="Symfony\Component\Notifier\Channel\SmsChannel">
<argument type="service" id="texter.transports" />
<tag name="notifier.channel" channel="sms" />
</service>

<service id="notifier.email_channel" class="Symfony\Component\Notifier\Channel\EmailChannel">
<argument type="service" id="mailer.transports" />
<tag name="notifier.channel" channel="email" />
</service>

<service id="notifier.monolog_handler" class="Symfony\Bridge\Monolog\Handler\NotifierHandler">
<argument type="service" id="notifier" />
</service>

<service id="notifier.failed_message_listener" class="Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener">
<argument type="service" id="notifier" />
<tag name="kernel.event_subscriber" />
</service>

<!-- chatter -->
<service id="chatter" class="Symfony\Component\Notifier\Chatter">
<argument type="service" id="chatter.transports" />
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
</service>
<service id="Symfony\Component\Notifier\ChatterInterface" alias="chatter" />

<service id="chatter.transports" class="Symfony\Component\Notifier\Transport\Transports">
<factory service="chatter.transport_factory" method="fromStrings" />
<argument type="collection" /> <!-- transports -->
</service>

<service id="chatter.transport_factory" class="Symfony\Component\Notifier\Transport">
<argument type="tagged_iterator" tag="chatter.transport_factory" />
</service>

<service id="chatter.messenger.chat_handler" class="Symfony\Component\Notifier\Messenger\MessageHandler">
<argument type="service" id="chatter.transports" />
<tag name="messenger.message_handler" handles="Symfony\Component\Notifier\Message\ChatMessage" />
</service>

<!-- texter -->
<service id="texter" class="Symfony\Component\Notifier\Texter">
<argument type="service" id="texter.transports" />
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
</service>
<service id="Symfony\Component\Notifier\TexterInterface" alias="texter" />

<service id="texter.transports" class="Symfony\Component\Notifier\Transport\Transports">
<factory service="texter.transport_factory" method="fromStrings" />
<argument type="collection" /> <!-- transports -->
</service>

<service id="texter.transport_factory" class="Symfony\Component\Notifier\Transport">
<argument type="tagged_iterator" tag="texter.transport_factory" />
</service>

<service id="texter.messenger.sms_handler" class="Symfony\Component\Notifier\Messenger\MessageHandler">
<argument type="service" id="texter.transports" />
<tag name="messenger.message_handler" handles="Symfony\Component\Notifier\Message\SmsMessage" />
</service>
</services>
</container>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="notifier.transport_factory.abstract" class="Symfony\Component\Notifier\Transport\AbstractTransportFactory" abstract="true">
<argument type="service" id="event_dispatcher" />
<argument type="service" id="http_client" on-invalid="ignore" />
</service>

<service id="notifier.transport_factory.slack" class="Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory" parent="notifier.transport_factory.abstract">
<tag name="chatter.transport_factory" />
</service>

<service id="notifier.transport_factory.telegram" class="Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory" parent="notifier.transport_factory.abstract">
<tag name="chatter.transport_factory" />
</service>

<service id="notifier.transport_factory.nexmo" class="Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory" parent="notifier.transport_factory.abstract">
<tag name="texter.transport_factory" />
</service>

<service id="notifier.transport_factory.twilio" class="Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory" parent="notifier.transport_factory.abstract">
<tag name="texter.transport_factory" />
</service>

<service id="notifier.transport_factory.null" class="Symfony\Component\Notifier\Transport\NullTransportFactory" parent="notifier.transport_factory.abstract">
<tag name="notifier.transport_factory" />
</service>
</services>
</container>
Expand Up @@ -21,6 +21,7 @@
use Symfony\Component\Lock\Store\SemaphoreStore;
use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Notifier\Notifier;

class ConfigurationTest extends TestCase
{
Expand Down Expand Up @@ -411,6 +412,13 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
'transports' => [],
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
],
'notifier' => [
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),
'chatter_transports' => [],
'texter_transports' => [],
'channel_policy' => [],
'admin_receivers' => [],
],
'error_controller' => 'error_controller',
];
}
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Notifier/.gitattributes
@@ -0,0 +1,2 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore
3 changes: 3 additions & 0 deletions src/Symfony/Component/Notifier/.gitignore
@@ -0,0 +1,3 @@
composer.lock
phpunit.xml
vendor/
2 changes: 2 additions & 0 deletions src/Symfony/Component/Notifier/Bridge/Nexmo/.gitattributes
@@ -0,0 +1,2 @@
/Tests export-ignore
/phpunit.xml.dist export-ignore

0 comments on commit 084e9bc

Please sign in to comment.