Skip to content

Commit 5ccdc2c

Browse files
committed
[Notifier] added the component
1 parent 5e48c39 commit 5ccdc2c

File tree

107 files changed

+4598
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+4598
-0
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"symfony/messenger": "self.version",
7070
"symfony/mime": "self.version",
7171
"symfony/monolog-bridge": "self.version",
72+
"symfony/notifier": "self.version",
7273
"symfony/options-resolver": "self.version",
7374
"symfony/postmark-mailer": "self.version",
7475
"symfony/process": "self.version",
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Monolog\Handler;
13+
14+
use Monolog\Handler\AbstractHandler;
15+
use Monolog\Logger;
16+
use Symfony\Component\Notifier\Notification\Notification;
17+
use Symfony\Component\Notifier\Notifier;
18+
use Symfony\Component\Notifier\NotifierInterface;
19+
20+
/**
21+
* Uses Notifier as a log handler.
22+
*
23+
* @author Fabien Potencier <fabien@symfony.com>
24+
*/
25+
class NotifierHandler extends AbstractHandler
26+
{
27+
private $notifier;
28+
29+
public function __construct(NotifierInterface $notifier, int $level = Logger::ERROR, bool $bubble = true)
30+
{
31+
$this->notifier = $notifier;
32+
33+
parent::__construct($level < Logger::ERROR ? Logger::ERROR : $level, $bubble);
34+
}
35+
36+
public function handle(array $record): bool
37+
{
38+
if (!$this->isHandling($record)) {
39+
return false;
40+
}
41+
42+
$this->notify([$record]);
43+
44+
return !$this->bubble;
45+
}
46+
47+
public function handleBatch(array $records): void
48+
{
49+
if ($records = array_filter($records, [$this, 'isHandling'])) {
50+
$this->notify($records);
51+
}
52+
}
53+
54+
private function notify(array $records): void
55+
{
56+
$record = $this->getHighestRecord($records);
57+
if (($record['context']['exception'] ?? null) instanceof \Throwable) {
58+
$notification = Notification::fromThrowable($record['context']['exception']);
59+
} else {
60+
$notification = new Notification($record['message']);
61+
}
62+
63+
$notification->importanceFromLogLevelName(Logger::getLevelName($record['level']));
64+
65+
$this->notifier->send($notification, ...$this->notifier->getAdminRecipients());
66+
}
67+
68+
private function getHighestRecord(array $records)
69+
{
70+
$highestRecord = null;
71+
foreach ($records as $record) {
72+
if (null === $highestRecord || $highestRecord['level'] < $record['level']) {
73+
$highestRecord = $record;
74+
}
75+
}
76+
77+
return $highestRecord;
78+
}
79+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Lock\Store\SemaphoreStore;
2828
use Symfony\Component\Mailer\Mailer;
2929
use Symfony\Component\Messenger\MessageBusInterface;
30+
use Symfony\Component\Notifier\Notifier;
3031
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
3132
use Symfony\Component\Serializer\Serializer;
3233
use Symfony\Component\Translation\Translator;
@@ -114,6 +115,7 @@ public function getConfigTreeBuilder()
114115
$this->addRobotsIndexSection($rootNode);
115116
$this->addHttpClientSection($rootNode);
116117
$this->addMailerSection($rootNode);
118+
$this->addNotifierSection($rootNode);
117119

118120
return $treeBuilder;
119121
}
@@ -1475,4 +1477,50 @@ private function addMailerSection(ArrayNodeDefinition $rootNode)
14751477
->end()
14761478
;
14771479
}
1480+
1481+
private function addNotifierSection(ArrayNodeDefinition $rootNode)
1482+
{
1483+
$rootNode
1484+
->children()
1485+
->arrayNode('notifier')
1486+
->info('Notifier configuration')
1487+
->{!class_exists(FullStack::class) && class_exists(Notifier::class) ? 'canBeDisabled' : 'canBeEnabled'}()
1488+
->fixXmlConfig('chatter_transport')
1489+
->children()
1490+
->arrayNode('chatter_transports')
1491+
->useAttributeAsKey('name')
1492+
->prototype('scalar')->end()
1493+
->end()
1494+
->end()
1495+
->fixXmlConfig('texter_transport')
1496+
->children()
1497+
->arrayNode('texter_transports')
1498+
->useAttributeAsKey('name')
1499+
->prototype('scalar')->end()
1500+
->end()
1501+
->end()
1502+
->children()
1503+
->arrayNode('channel_policy')
1504+
->useAttributeAsKey('name')
1505+
->prototype('array')
1506+
->beforeNormalization()->ifString()->then(function (string $v) { return [$v]; })->end()
1507+
->prototype('scalar')->end()
1508+
->end()
1509+
->end()
1510+
->end()
1511+
->fixXmlConfig('admin_recipient')
1512+
->children()
1513+
->arrayNode('admin_recipients')
1514+
->prototype('array')
1515+
->children()
1516+
->scalarNode('email')->cannotBeEmpty()->end()
1517+
->scalarNode('phone')->defaultNull()->end()
1518+
->end()
1519+
->end()
1520+
->end()
1521+
->end()
1522+
->end()
1523+
->end()
1524+
;
1525+
}
14781526
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@
8989
use Symfony\Component\Messenger\Transport\TransportInterface;
9090
use Symfony\Component\Mime\MimeTypeGuesserInterface;
9191
use Symfony\Component\Mime\MimeTypes;
92+
use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory;
93+
use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory;
94+
use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory;
95+
use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory;
96+
use Symfony\Component\Notifier\Notifier;
97+
use Symfony\Component\Notifier\Recipient\AdminRecipient;
9298
use Symfony\Component\PropertyAccess\PropertyAccessor;
9399
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
94100
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
@@ -297,6 +303,10 @@ public function load(array $configs, ContainerBuilder $container)
297303
$this->registerMailerConfiguration($config['mailer'], $container, $loader);
298304
}
299305

306+
if ($this->isConfigEnabled($container, $config['notifier'])) {
307+
$this->registerNotifierConfiguration($config['notifier'], $container, $loader);
308+
}
309+
300310
$propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']);
301311
$this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled);
302312
$this->registerEsiConfiguration($config['esi'], $container, $loader);
@@ -1848,6 +1858,67 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co
18481858
$envelopeListener->setArgument(1, $recipients);
18491859
}
18501860

1861+
private function registerNotifierConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
1862+
{
1863+
if (!class_exists(Notifier::class)) {
1864+
throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".');
1865+
}
1866+
1867+
$loader->load('notifier.xml');
1868+
$loader->load('notifier_transports.xml');
1869+
1870+
if ($config['chatter_transports']) {
1871+
$container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']);
1872+
} else {
1873+
$container->removeDefinition('chatter');
1874+
}
1875+
if ($config['texter_transports']) {
1876+
$container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']);
1877+
} else {
1878+
$container->removeDefinition('texter');
1879+
}
1880+
1881+
if ($this->mailerConfigEnabled) {
1882+
$sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0);
1883+
$container->getDefinition('notifier.email_channel')->setArgument(2, $sender);
1884+
} else {
1885+
$container->removeDefinition('notifier.email_channel');
1886+
}
1887+
1888+
if (!$this->messengerConfigEnabled) {
1889+
$container->removeDefinition('notifier.failed_message_listener');
1890+
} else {
1891+
// as we have a bus, the channels don't need the transports
1892+
$container->getDefinition('notifier.chat_channel')->setArgument(0, null);
1893+
$container->getDefinition('notifier.email_channel')->setArgument(0, null);
1894+
$container->getDefinition('notifier.sms_channel')->setArgument(0, null);
1895+
}
1896+
1897+
$container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']);
1898+
1899+
$classToServices = [
1900+
SlackTransportFactory::class => 'notifier.transport_factory.slack',
1901+
TelegramTransportFactory::class => 'notifier.transport_factory.telegram',
1902+
NexmoTransportFactory::class => 'notifier.transport_factory.nexmo',
1903+
TwilioTransportFactory::class => 'notifier.transport_factory.twilio',
1904+
];
1905+
1906+
foreach ($classToServices as $class => $service) {
1907+
if (!class_exists($class)) {
1908+
$container->removeDefinition($service);
1909+
}
1910+
}
1911+
1912+
if (isset($config['admin_recipients'])) {
1913+
$notifier = $container->getDefinition('notifier');
1914+
foreach ($config['admin_recipients'] as $i => $recipient) {
1915+
$id = 'notifier.admin_recipient.'.$i;
1916+
$container->setDefinition($id, new Definition(AdminRecipient::class, [$recipient['email'], $recipient['phone']]));
1917+
$notifier->addMethodCall('addAdminRecipient', [new Reference($id)]);
1918+
}
1919+
}
1920+
}
1921+
18511922
/**
18521923
* {@inheritdoc}
18531924
*/
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="notifier" class="Symfony\Component\Notifier\Notifier">
9+
<argument type="tagged_locator" tag="notifier.channel" index-by="channel" />
10+
<argument type="service" id="notifier.channel_policy" on-invalid="ignore" />
11+
</service>
12+
<service id="Symfony\Component\Notifier\NotifierInterface" alias="notifier" />
13+
14+
<service id="notifier.channel_policy" class="Symfony\Component\Notifier\Channel\ChannelPolicy">
15+
<argument type="collection" /> <!-- policy -->
16+
</service>
17+
18+
<service id="notifier.browser_channel" class="Symfony\Component\Notifier\Channel\BrowserChannel">
19+
<argument type="service" id="request_stack" />
20+
<tag name="notifier.channel" channel="browser" />
21+
</service>
22+
23+
<service id="notifier.chat_channel" class="Symfony\Component\Notifier\Channel\ChatChannel">
24+
<argument type="service" id="chatter.transports" />
25+
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
26+
<tag name="notifier.channel" channel="chat" />
27+
</service>
28+
29+
<service id="notifier.sms_channel" class="Symfony\Component\Notifier\Channel\SmsChannel">
30+
<argument type="service" id="texter.transports" />
31+
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
32+
<tag name="notifier.channel" channel="sms" />
33+
</service>
34+
35+
<service id="notifier.email_channel" class="Symfony\Component\Notifier\Channel\EmailChannel">
36+
<argument type="service" id="mailer.transports" />
37+
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
38+
<tag name="notifier.channel" channel="email" />
39+
</service>
40+
41+
<service id="notifier.monolog_handler" class="Symfony\Bridge\Monolog\Handler\NotifierHandler">
42+
<argument type="service" id="notifier" />
43+
</service>
44+
45+
<service id="notifier.failed_message_listener" class="Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener">
46+
<argument type="service" id="notifier" />
47+
<tag name="kernel.event_subscriber" />
48+
</service>
49+
50+
<!-- chatter -->
51+
<service id="chatter" class="Symfony\Component\Notifier\Chatter">
52+
<argument type="service" id="chatter.transports" />
53+
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
54+
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
55+
</service>
56+
<service id="Symfony\Component\Notifier\ChatterInterface" alias="chatter" />
57+
58+
<service id="chatter.transports" class="Symfony\Component\Notifier\Transport\Transports">
59+
<factory service="chatter.transport_factory" method="fromStrings" />
60+
<argument type="collection" /> <!-- transports -->
61+
</service>
62+
63+
<service id="chatter.transport_factory" class="Symfony\Component\Notifier\Transport">
64+
<argument type="tagged_iterator" tag="chatter.transport_factory" />
65+
</service>
66+
67+
<service id="chatter.messenger.chat_handler" class="Symfony\Component\Notifier\Messenger\MessageHandler">
68+
<argument type="service" id="chatter.transports" />
69+
<tag name="messenger.message_handler" handles="Symfony\Component\Notifier\Message\ChatMessage" />
70+
</service>
71+
72+
<!-- texter -->
73+
<service id="texter" class="Symfony\Component\Notifier\Texter">
74+
<argument type="service" id="texter.transports" />
75+
<argument type="service" id="messenger.default_bus" on-invalid="ignore" />
76+
<argument type="service" id="event_dispatcher" on-invalid="ignore" />
77+
</service>
78+
<service id="Symfony\Component\Notifier\TexterInterface" alias="texter" />
79+
80+
<service id="texter.transports" class="Symfony\Component\Notifier\Transport\Transports">
81+
<factory service="texter.transport_factory" method="fromStrings" />
82+
<argument type="collection" /> <!-- transports -->
83+
</service>
84+
85+
<service id="texter.transport_factory" class="Symfony\Component\Notifier\Transport">
86+
<argument type="tagged_iterator" tag="texter.transport_factory" />
87+
</service>
88+
89+
<service id="texter.messenger.sms_handler" class="Symfony\Component\Notifier\Messenger\MessageHandler">
90+
<argument type="service" id="texter.transports" />
91+
<tag name="messenger.message_handler" handles="Symfony\Component\Notifier\Message\SmsMessage" />
92+
</service>
93+
</services>
94+
</container>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="notifier.transport_factory.abstract" class="Symfony\Component\Notifier\Transport\AbstractTransportFactory" abstract="true">
9+
<argument type="service" id="event_dispatcher" />
10+
<argument type="service" id="http_client" on-invalid="ignore" />
11+
</service>
12+
13+
<service id="notifier.transport_factory.slack" class="Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory" parent="notifier.transport_factory.abstract">
14+
<tag name="chatter.transport_factory" />
15+
</service>
16+
17+
<service id="notifier.transport_factory.telegram" class="Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory" parent="notifier.transport_factory.abstract">
18+
<tag name="chatter.transport_factory" />
19+
</service>
20+
21+
<service id="notifier.transport_factory.nexmo" class="Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory" parent="notifier.transport_factory.abstract">
22+
<tag name="texter.transport_factory" />
23+
</service>
24+
25+
<service id="notifier.transport_factory.twilio" class="Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory" parent="notifier.transport_factory.abstract">
26+
<tag name="texter.transport_factory" />
27+
</service>
28+
29+
<service id="notifier.transport_factory.null" class="Symfony\Component\Notifier\Transport\NullTransportFactory" parent="notifier.transport_factory.abstract">
30+
<tag name="notifier.transport_factory" />
31+
</service>
32+
</services>
33+
</container>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Lock\Store\SemaphoreStore;
2222
use Symfony\Component\Mailer\Mailer;
2323
use Symfony\Component\Messenger\MessageBusInterface;
24+
use Symfony\Component\Notifier\Notifier;
2425

2526
class ConfigurationTest extends TestCase
2627
{
@@ -411,6 +412,13 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
411412
'transports' => [],
412413
'enabled' => !class_exists(FullStack::class) && class_exists(Mailer::class),
413414
],
415+
'notifier' => [
416+
'enabled' => !class_exists(FullStack::class) && class_exists(Notifier::class),
417+
'chatter_transports' => [],
418+
'texter_transports' => [],
419+
'channel_policy' => [],
420+
'admin_recipients' => [],
421+
],
414422
'error_controller' => 'error_controller',
415423
];
416424
}

0 commit comments

Comments
 (0)