├── LICENSE ├── composer.json └── src ├── Admin └── MessageAdmin.php ├── Backend ├── AMQPBackend.php ├── AMQPBackendDispatcher.php ├── BackendHealthCheck.php ├── BackendInterface.php ├── MessageManagerBackend.php ├── MessageManagerBackendDispatcher.php ├── PostponeRuntimeBackend.php ├── QueueBackendDispatcher.php ├── QueueDispatcherInterface.php └── RuntimeBackend.php ├── Command ├── CleanupCommand.php ├── ConsumerHandlerCommand.php ├── CreateAndPublishCommand.php ├── ListHandlerCommand.php ├── ListQueuesCommand.php └── RestartCommand.php ├── Consumer ├── ConsumerEvent.php ├── ConsumerEventInterface.php ├── ConsumerInterface.php ├── ConsumerReturnInfo.php ├── LoggerConsumer.php ├── Metadata.php └── SwiftMailerConsumer.php ├── Controller ├── Api │ ├── Legacy │ │ └── MessageController.php │ └── MessageController.php └── MessageAdminController.php ├── DependencyInjection ├── Compiler │ └── NotificationCompilerPass.php ├── Configuration.php └── SonataNotificationExtension.php ├── Entity ├── BaseMessage.php └── MessageManager.php ├── Event ├── DoctrineBackendOptimizeListener.php ├── DoctrineOptimizeListener.php ├── IterateEvent.php └── IterationListener.php ├── Exception ├── BackendNotFoundException.php ├── HandlingException.php └── InvalidParameterException.php ├── Form └── Type │ └── MessageSerializationType.php ├── Iterator ├── AMQPMessageIterator.php ├── ErroneousMessageIterator.php ├── IteratorProxyMessageIterator.php ├── MessageIteratorInterface.php └── MessageManagerMessageIterator.php ├── Model ├── Message.php ├── MessageInterface.php └── MessageManagerInterface.php ├── Resources ├── config │ ├── admin.xml │ ├── api_controllers.xml │ ├── api_controllers_legacy.xml │ ├── api_form.xml │ ├── backend.xml │ ├── checkmonitor.xml │ ├── command.xml │ ├── consumer.xml │ ├── core.xml │ ├── core_legacy.xml │ ├── default_consumers.xml │ ├── doctrine │ │ └── BaseMessage.orm.xml │ ├── doctrine_orm.xml │ ├── event.xml │ ├── routing │ │ ├── api.xml │ │ └── api_nelmio_v3.xml │ ├── selector.xml │ ├── serializer │ │ └── Model.Message.xml │ └── validation.xml ├── meta │ └── LICENSE └── translations │ ├── SonataNotificationBundle.cs.xliff │ ├── SonataNotificationBundle.de.xliff │ ├── SonataNotificationBundle.en.xliff │ ├── SonataNotificationBundle.es.xliff │ ├── SonataNotificationBundle.fr.xliff │ ├── SonataNotificationBundle.it.xliff │ ├── SonataNotificationBundle.nl.xliff │ ├── SonataNotificationBundle.ru.xliff │ ├── SonataNotificationBundle.sk.xliff │ └── SonataNotificationBundle.sl.xliff ├── Selector └── ErroneousMessagesSelector.php └── SonataNotificationBundle.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 Thomas Rabaix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sonata-project/notification-bundle", 3 | "description": "Symfony SonataNotificationBundle", 4 | "license": "MIT", 5 | "type": "symfony-bundle", 6 | "abandoned": true, 7 | "keywords": [ 8 | "page", 9 | "sonata", 10 | "cms" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Thomas Rabaix", 15 | "email": "thomas.rabaix@sonata-project.org", 16 | "homepage": "https://sonata-project.org" 17 | }, 18 | { 19 | "name": "Sonata Community", 20 | "homepage": "https://github.com/sonata-project/SonataNotificationBundle/contributors" 21 | } 22 | ], 23 | "homepage": "https://docs.sonata-project.org/projects/SonataNotificationBundle", 24 | "require": { 25 | "php": "^7.3 || ^8.0", 26 | "doctrine/doctrine-bundle": "^1.12 || ^2.0", 27 | "doctrine/persistence": "^1.3 || ^2.0", 28 | "laminas/laminas-diagnostics": "^1.6", 29 | "sonata-project/datagrid-bundle": "^3.0", 30 | "sonata-project/doctrine-extensions": "^1.10.1", 31 | "sonata-project/form-extensions": "^0.1 || ^1.4", 32 | "symfony/config": "^4.4 || ^5.2", 33 | "symfony/console": "^4.4", 34 | "symfony/dependency-injection": "^4.4 || ^5.2", 35 | "symfony/doctrine-bridge": "^4.4", 36 | "symfony/event-dispatcher": "^4.4", 37 | "symfony/event-dispatcher-contracts": "^1.1", 38 | "symfony/form": "^4.4", 39 | "symfony/http-foundation": "^4.4 || ^5.2", 40 | "symfony/http-kernel": "^4.4", 41 | "symfony/security-core": "^4.4" 42 | }, 43 | "require-dev": { 44 | "enqueue/amqp-lib": "^0.8", 45 | "friendsofsymfony/rest-bundle": "^2.3 || ^3.0", 46 | "guzzlehttp/guzzle": "^3.8", 47 | "jms/serializer-bundle": "^2.0 || ^3.0", 48 | "liip/monitor-bundle": "^2.6", 49 | "matthiasnoback/symfony-config-test": "^4.2", 50 | "matthiasnoback/symfony-dependency-injection-test": "^4.1", 51 | "nelmio/api-doc-bundle": "^2.13.5 || ^3.6", 52 | "phpstan/phpstan": "^0.12.83", 53 | "phpunit/phpunit": "^9.5", 54 | "sonata-project/doctrine-orm-admin-bundle": "^3.19", 55 | "swiftmailer/swiftmailer": "^5.0 || ^6.0", 56 | "symfony/browser-kit": "^4.4 || ^5.2", 57 | "symfony/phpunit-bridge": "^5.2", 58 | "symfony/swiftmailer-bundle": "^3.4", 59 | "symfony/templating": "^4.4 || ^5.2", 60 | "symfony/yaml": "^4.4 || ^5.2", 61 | "vimeo/psalm": "^4.7" 62 | }, 63 | "conflict": { 64 | "friendsofsymfony/rest-bundle": "<2.3", 65 | "jms/serializer": "<0.13", 66 | "liip/monitor-bundle": "<1.0", 67 | "nelmio/api-doc-bundle": "<2.13.5 || >=4.0", 68 | "sonata-project/admin-bundle": "<3.1", 69 | "sonata-project/core-bundle": "<3.20", 70 | "sonata-project/doctrine-orm-admin-bundle": "<3.0" 71 | }, 72 | "suggest": { 73 | "friendsofsymfony/rest-bundle": "If you want to expose a ReST API.", 74 | "guzzlehttp/guzzle": "If you want to get a status report when using the rabbitMQ backend.", 75 | "liip/monitor-bundle": "Integrates the LiipMonitor library into Symfony", 76 | "nelmio/api-doc-bundle": "If you want to provide OAS documentation for the ReST API.", 77 | "queue-interop/amqp-interop": "Install any amqp-interop compatible transport if you want use RabbitMQ. For example enqueue/amqp-lib:^0.8", 78 | "sonata-project/admin-bundle": "To be able to use MessageAdmin.", 79 | "sonata-project/doctrine-orm-admin-bundle": "If you want to persist entities" 80 | }, 81 | "autoload": { 82 | "psr-4": { 83 | "Sonata\\NotificationBundle\\": "src/" 84 | } 85 | }, 86 | "autoload-dev": { 87 | "psr-4": { 88 | "Sonata\\NotificationBundle\\Tests\\": "tests/" 89 | } 90 | }, 91 | "config": { 92 | "sort-packages": true 93 | }, 94 | "extra": { 95 | "branch-alias": { 96 | "dev-master": "3.x-dev" 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Admin/MessageAdmin.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Admin; 15 | 16 | use Sonata\AdminBundle\Admin\AbstractAdmin; 17 | use Sonata\AdminBundle\Datagrid\DatagridMapper; 18 | use Sonata\AdminBundle\Datagrid\ListMapper; 19 | use Sonata\AdminBundle\Route\RouteCollection; 20 | use Sonata\AdminBundle\Show\ShowMapper; 21 | use Symfony\Component\Form\Extension\Core\Type\ChoiceType; 22 | 23 | /** 24 | * @phpstan-extends AbstractAdmin<\Sonata\NotificationBundle\Model\Message> 25 | * @final since sonata-project/notification-bundle 3.13 26 | */ 27 | class MessageAdmin extends AbstractAdmin 28 | { 29 | protected $classnameLabel = 'Message'; 30 | 31 | public function configureRoutes(RouteCollection $collection) 32 | { 33 | $collection 34 | ->remove('edit') 35 | ->remove('create') 36 | ->remove('history'); 37 | } 38 | 39 | public function getBatchActions() 40 | { 41 | $actions = []; 42 | $actions['publish'] = [ 43 | 'label' => $this->getLabelTranslatorStrategy()->getLabel('publish', 'batch', 'message'), 44 | 'translation_domain' => $this->getTranslationDomain(), 45 | 'ask_confirmation' => false, 46 | ]; 47 | 48 | $actions['cancelled'] = [ 49 | 'label' => $this->getLabelTranslatorStrategy()->getLabel('cancelled', 'batch', 'message'), 50 | 'translation_domain' => $this->getTranslationDomain(), 51 | 'ask_confirmation' => false, 52 | ]; 53 | 54 | return $actions; 55 | } 56 | 57 | protected function configureShowFields(ShowMapper $show) 58 | { 59 | $show 60 | ->add('id') 61 | ->add('type') 62 | ->add('createdAt') 63 | ->add('startedAt') 64 | ->add('completedAt') 65 | ->add('getStateName') 66 | ->add('body') 67 | ->add('restartCount'); 68 | } 69 | 70 | protected function configureListFields(ListMapper $list) 71 | { 72 | $list 73 | ->addIdentifier('id', null, ['route' => ['name' => 'show']]) 74 | ->add('type') 75 | ->add('createdAt') 76 | ->add('startedAt') 77 | ->add('completedAt') 78 | ->add('getStateName') 79 | ->add('restartCount'); 80 | } 81 | 82 | protected function configureDatagridFilters(DatagridMapper $filter) 83 | { 84 | $class = $this->getClass(); 85 | 86 | $filter 87 | ->add('type') 88 | ->add('state', null, [ 89 | 'field_type' => ChoiceType::class, 90 | 'field_options' => ['choices' => $class::getStateList()], 91 | ]); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Backend/AMQPBackend.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Interop\Amqp\AmqpConsumer; 17 | use Interop\Amqp\AmqpContext; 18 | use Interop\Amqp\AmqpMessage; 19 | use Interop\Amqp\AmqpQueue; 20 | use Interop\Amqp\AmqpTopic; 21 | use Interop\Amqp\Impl\AmqpBind; 22 | use Laminas\Diagnostics\Result\Failure; 23 | use Laminas\Diagnostics\Result\Success; 24 | use PhpAmqpLib\Channel\AMQPChannel; 25 | use Sonata\NotificationBundle\Consumer\ConsumerEvent; 26 | use Sonata\NotificationBundle\Exception\HandlingException; 27 | use Sonata\NotificationBundle\Iterator\AMQPMessageIterator; 28 | use Sonata\NotificationBundle\Model\Message; 29 | use Sonata\NotificationBundle\Model\MessageInterface; 30 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 31 | 32 | /** 33 | * Consumer side of the rabbitMQ backend. 34 | * 35 | * @final since sonata-project/notification-bundle 3.13 36 | */ 37 | class AMQPBackend implements BackendInterface 38 | { 39 | /** 40 | * @var AMQPBackendDispatcher 41 | */ 42 | protected $dispatcher = null; 43 | 44 | /** 45 | * @var string 46 | */ 47 | protected $exchange; 48 | 49 | /** 50 | * @var string 51 | */ 52 | protected $queue; 53 | 54 | /** 55 | * @var string 56 | */ 57 | protected $key; 58 | 59 | /** 60 | * @var string 61 | */ 62 | protected $recover; 63 | 64 | /** 65 | * @var string|null 66 | */ 67 | protected $deadLetterExchange; 68 | 69 | /** 70 | * @var string|null 71 | */ 72 | protected $deadLetterRoutingKey; 73 | 74 | /** 75 | * @var int|null 76 | */ 77 | protected $ttl; 78 | 79 | /** 80 | * @var int|null 81 | */ 82 | private $prefetchCount; 83 | 84 | /** 85 | * @var AmqpConsumer 86 | */ 87 | private $consumer; 88 | 89 | /** 90 | * @param string $exchange 91 | * @param string $queue 92 | * @param string $recover 93 | * @param string $key 94 | * @param string $deadLetterExchange 95 | * @param string $deadLetterRoutingKey 96 | * @param int|null $ttl 97 | */ 98 | public function __construct($exchange, $queue, $recover, $key, $deadLetterExchange = null, $deadLetterRoutingKey = null, $ttl = null, $prefetchCount = null) 99 | { 100 | $this->exchange = $exchange; 101 | $this->queue = $queue; 102 | $this->recover = $recover; 103 | $this->key = $key; 104 | $this->deadLetterExchange = $deadLetterExchange; 105 | $this->deadLetterRoutingKey = $deadLetterRoutingKey; 106 | $this->ttl = $ttl; 107 | $this->prefetchCount = $prefetchCount; 108 | } 109 | 110 | public function setDispatcher(AMQPBackendDispatcher $dispatcher) 111 | { 112 | $this->dispatcher = $dispatcher; 113 | } 114 | 115 | public function initialize() 116 | { 117 | $args = []; 118 | if (null !== $this->deadLetterExchange) { 119 | $args['x-dead-letter-exchange'] = $this->deadLetterExchange; 120 | 121 | if (null !== $this->deadLetterRoutingKey) { 122 | $args['x-dead-letter-routing-key'] = $this->deadLetterRoutingKey; 123 | } 124 | } 125 | 126 | if (null !== $this->ttl) { 127 | $args['x-message-ttl'] = $this->ttl; 128 | } 129 | 130 | $queue = $this->getContext()->createQueue($this->queue); 131 | $queue->addFlag(AmqpQueue::FLAG_DURABLE); 132 | $queue->setArguments($args); 133 | $this->getContext()->declareQueue($queue); 134 | 135 | $topic = $this->getContext()->createTopic($this->exchange); 136 | $topic->setType(AmqpTopic::TYPE_DIRECT); 137 | $topic->addFlag(AmqpTopic::FLAG_DURABLE); 138 | $this->getContext()->declareTopic($topic); 139 | 140 | $this->getContext()->bind(new AmqpBind($queue, $topic, $this->key)); 141 | 142 | if (null !== $this->deadLetterExchange && null === $this->deadLetterRoutingKey) { 143 | $deadLetterTopic = $this->getContext()->createTopic($this->deadLetterExchange); 144 | $deadLetterTopic->setType(AmqpTopic::TYPE_DIRECT); 145 | $deadLetterTopic->addFlag(AmqpTopic::FLAG_DURABLE); 146 | $this->getContext()->declareTopic($deadLetterTopic); 147 | 148 | $this->getContext()->bind(new AmqpBind($queue, $deadLetterTopic, $this->key)); 149 | } 150 | } 151 | 152 | public function publish(MessageInterface $message) 153 | { 154 | $body = json_encode([ 155 | 'type' => $message->getType(), 156 | 'body' => $message->getBody(), 157 | 'createdAt' => $message->getCreatedAt()->format('U'), 158 | 'state' => $message->getState(), 159 | ]); 160 | 161 | $amqpMessage = $this->getContext()->createMessage($body); 162 | $amqpMessage->setContentType('text/plain'); // application/json ? 163 | $amqpMessage->setTimestamp($message->getCreatedAt()->getTimestamp()); 164 | $amqpMessage->setDeliveryMode(AmqpMessage::DELIVERY_MODE_PERSISTENT); 165 | $amqpMessage->setRoutingKey($this->key); 166 | 167 | $topic = $this->getContext()->createTopic($this->exchange); 168 | 169 | $this->getContext()->createProducer()->send($topic, $amqpMessage); 170 | } 171 | 172 | public function create($type, array $body) 173 | { 174 | $message = new Message(); 175 | $message->setType($type); 176 | $message->setBody($body); 177 | $message->setState(MessageInterface::STATE_OPEN); 178 | 179 | return $message; 180 | } 181 | 182 | public function createAndPublish($type, array $body) 183 | { 184 | $this->publish($this->create($type, $body)); 185 | } 186 | 187 | public function getIterator() 188 | { 189 | $context = $this->getContext(); 190 | 191 | if (null !== $this->prefetchCount) { 192 | $context->setQos(null, $this->prefetchCount, false); 193 | } 194 | 195 | $this->consumer = $this->getContext()->createConsumer($this->getContext()->createQueue($this->queue)); 196 | $this->consumer->setConsumerTag('sonata_notification_'.uniqid()); 197 | 198 | if (!$context instanceof \Enqueue\AmqpLib\AmqpContext) { 199 | throw new \LogicException('The BC layer works only if enqueue/amqp-lib lib is being used.'); 200 | } 201 | 202 | return new AMQPMessageIterator($context->getLibChannel(), $this->consumer); 203 | } 204 | 205 | public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher) 206 | { 207 | $event = new ConsumerEvent($message); 208 | 209 | /** @var AmqpMessage $amqpMessage */ 210 | $amqpMessage = $message->getValue('interopMessage'); 211 | 212 | try { 213 | $dispatcher->dispatch($event, $message->getType()); 214 | 215 | $this->consumer->acknowledge($amqpMessage); 216 | 217 | $message->setCompletedAt(new \DateTime()); 218 | $message->setState(MessageInterface::STATE_DONE); 219 | } catch (HandlingException $e) { 220 | $message->setCompletedAt(new \DateTime()); 221 | $message->setState(MessageInterface::STATE_ERROR); 222 | 223 | $this->consumer->acknowledge($amqpMessage); 224 | 225 | throw new HandlingException('Error while handling a message', 0, $e); 226 | } catch (\Exception $e) { 227 | $message->setCompletedAt(new \DateTime()); 228 | $message->setState(MessageInterface::STATE_ERROR); 229 | 230 | $this->consumer->reject($amqpMessage, $this->recover); 231 | 232 | throw new HandlingException('Error while handling a message', 0, $e); 233 | } 234 | } 235 | 236 | public function getStatus() 237 | { 238 | try { 239 | $this->getContext(); 240 | } catch (\Exception $e) { 241 | return new Failure($e->getMessage()); 242 | } 243 | 244 | return new Success('Channel is running (RabbitMQ)'); 245 | } 246 | 247 | public function cleanup() 248 | { 249 | throw new \RuntimeException('Not implemented'); 250 | } 251 | 252 | /** 253 | * @deprecated since 3.2, will be removed in 4.x 254 | * 255 | * @return AMQPChannel 256 | */ 257 | protected function getChannel() 258 | { 259 | if (null === $this->dispatcher) { 260 | throw new \RuntimeException('Unable to retrieve AMQP channel without dispatcher.'); 261 | } 262 | 263 | return $this->dispatcher->getChannel(); 264 | } 265 | 266 | /** 267 | * @return AmqpContext 268 | */ 269 | private function getContext() 270 | { 271 | if (null === $this->dispatcher) { 272 | throw new \RuntimeException('Unable to retrieve AMQP context without dispatcher.'); 273 | } 274 | 275 | return $this->dispatcher->getContext(); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/Backend/AMQPBackendDispatcher.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Enqueue\AmqpTools\DelayStrategyAware; 17 | use Enqueue\AmqpTools\RabbitMqDlxDelayStrategy; 18 | use Guzzle\Http\Client as GuzzleClient; 19 | use Interop\Amqp\AmqpConnectionFactory; 20 | use Interop\Amqp\AmqpContext; 21 | use Laminas\Diagnostics\Result\Failure; 22 | use Laminas\Diagnostics\Result\Success; 23 | use PhpAmqpLib\Channel\AMQPChannel; 24 | use PhpAmqpLib\Connection\AMQPConnection; 25 | use Sonata\NotificationBundle\Exception\BackendNotFoundException; 26 | use Sonata\NotificationBundle\Model\MessageInterface; 27 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 28 | 29 | /** 30 | * Producer side of the rabbitmq backend. 31 | * 32 | * @final since sonata-project/notification-bundle 3.13 33 | */ 34 | class AMQPBackendDispatcher extends QueueBackendDispatcher 35 | { 36 | /** 37 | * @var array 38 | */ 39 | protected $settings; 40 | 41 | /** 42 | * @deprecated since 3.2, will be removed in 4.x 43 | * 44 | * @var AMQPChannel 45 | */ 46 | protected $channel; 47 | 48 | /** 49 | * @deprecated since 3.2, will be removed in 4.x 50 | * 51 | * @var AMQPConnection 52 | */ 53 | protected $connection; 54 | 55 | protected $backendsInitialized = false; 56 | 57 | /** 58 | * @var AmqpConnectionFactory 59 | */ 60 | private $connectionFactory; 61 | 62 | /** 63 | * @var AmqpContext 64 | */ 65 | private $context; 66 | 67 | /** 68 | * @param string $defaultQueue 69 | */ 70 | public function __construct(array $settings, array $queues, $defaultQueue, array $backends) 71 | { 72 | parent::__construct($queues, $defaultQueue, $backends); 73 | 74 | $this->settings = $settings; 75 | } 76 | 77 | /** 78 | * @deprecated since 3.2, will be removed in 4.x 79 | * 80 | * @return AMQPChannel 81 | */ 82 | public function getChannel() 83 | { 84 | @trigger_error(sprintf('The method %s is deprecated since version 3.3 and will be removed in 4.0. Use %s::getContext() instead.', __METHOD__, __CLASS__), \E_USER_DEPRECATED); 85 | 86 | if (!$this->channel) { 87 | if (!$this->context instanceof \Enqueue\AmqpLib\AmqpContext) { 88 | throw new \LogicException('The BC layer works only if enqueue/amqp-lib lib is being used.'); 89 | } 90 | 91 | // load context 92 | $this->getContext(); 93 | 94 | /** @var \Enqueue\AmqpLib\AmqpContext $context */ 95 | $context = $this->getContext(); 96 | 97 | $this->channel = $context->getLibChannel(); 98 | $this->connection = $this->channel->getConnection(); 99 | } 100 | 101 | return $this->channel; 102 | } 103 | 104 | /** 105 | * @return AmqpContext 106 | */ 107 | final public function getContext() 108 | { 109 | if (!$this->context) { 110 | if (!\array_key_exists('factory_class', $this->settings)) { 111 | throw new \LogicException('The factory_class option is missing though it is required.'); 112 | } 113 | $factoryClass = $this->settings['factory_class']; 114 | if ( 115 | !class_exists($factoryClass) || 116 | !(new \ReflectionClass($factoryClass))->implementsInterface(AmqpConnectionFactory::class) 117 | ) { 118 | throw new \LogicException(sprintf( 119 | 'The factory_class option "%s" has to be valid class that implements "%s"', 120 | $factoryClass, 121 | AmqpConnectionFactory::class 122 | )); 123 | } 124 | 125 | /* @var AmqpConnectionFactory $factory */ 126 | $this->connectionFactory = $factory = new $factoryClass([ 127 | 'host' => $this->settings['host'], 128 | 'port' => $this->settings['port'], 129 | 'user' => $this->settings['user'], 130 | 'pass' => $this->settings['pass'], 131 | 'vhost' => $this->settings['vhost'], 132 | ]); 133 | 134 | if ($factory instanceof DelayStrategyAware) { 135 | $factory->setDelayStrategy(new RabbitMqDlxDelayStrategy()); 136 | } 137 | 138 | $this->context = $factory->createContext(); 139 | 140 | register_shutdown_function([$this, 'shutdown']); 141 | } 142 | 143 | return $this->context; 144 | } 145 | 146 | public function getBackend($type) 147 | { 148 | if (!$this->backendsInitialized) { 149 | foreach ($this->backends as $backend) { 150 | $backend['backend']->initialize(); 151 | } 152 | $this->backendsInitialized = true; 153 | } 154 | 155 | $default = null; 156 | 157 | if (0 === \count($this->queues)) { 158 | foreach ($this->backends as $backend) { 159 | if ('default' === $backend['type']) { 160 | return $backend['backend']; 161 | } 162 | } 163 | } 164 | 165 | foreach ($this->backends as $backend) { 166 | if ('all' === $type && '' === $backend['type']) { 167 | return $backend['backend']; 168 | } 169 | 170 | if ($backend['type'] === $type) { 171 | return $backend['backend']; 172 | } 173 | 174 | if ($backend['type'] === $this->defaultQueue) { 175 | $default = $backend['backend']; 176 | } 177 | } 178 | 179 | if (null === $default) { 180 | throw new BackendNotFoundException('Could not find a message backend for the type '.$type); 181 | } 182 | 183 | return $default; 184 | } 185 | 186 | public function getIterator() 187 | { 188 | throw new \RuntimeException( 189 | 'You need to use a specific rabbitmq backend supporting the selected queue to run a consumer.' 190 | ); 191 | } 192 | 193 | public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher) 194 | { 195 | throw new \RuntimeException( 196 | 'You need to use a specific rabbitmq backend supporting the selected queue to run a consumer.' 197 | ); 198 | } 199 | 200 | public function getStatus() 201 | { 202 | try { 203 | $this->getContext(); 204 | $output = $this->getApiQueueStatus(); 205 | $checked = 0; 206 | $missingConsumers = []; 207 | 208 | foreach ($this->queues as $queue) { 209 | foreach ($output as $q) { 210 | if ($q['name'] === $queue['queue']) { 211 | ++$checked; 212 | if (0 === $q['consumers']) { 213 | $missingConsumers[] = $queue['queue']; 214 | } 215 | } 216 | } 217 | } 218 | 219 | if ($checked !== \count($this->queues)) { 220 | return new Failure( 221 | 'Not all queues for the available notification types registered in the rabbitmq broker. ' 222 | .'Are the consumer commands running?' 223 | ); 224 | } 225 | 226 | if (\count($missingConsumers) > 0) { 227 | return new Failure( 228 | 'There are no rabbitmq consumers running for the queues: '.implode(', ', $missingConsumers) 229 | ); 230 | } 231 | } catch (\Exception $e) { 232 | return new Failure($e->getMessage()); 233 | } 234 | 235 | return new Success('Channel is running (RabbitMQ) and consumers for all queues available.'); 236 | } 237 | 238 | public function cleanup() 239 | { 240 | throw new \RuntimeException( 241 | 'You need to use a specific rabbitmq backend supporting the selected queue to run a consumer.' 242 | ); 243 | } 244 | 245 | public function shutdown() 246 | { 247 | if ($this->context) { 248 | $this->context->close(); 249 | } 250 | } 251 | 252 | public function initialize() 253 | { 254 | } 255 | 256 | /** 257 | * Calls the rabbitmq management api /api//queues endpoint to list the available queues. 258 | * 259 | * @see http://hg.rabbitmq.com/rabbitmq-management/raw-file/3646dee55e02/priv/www-api/help.html 260 | * 261 | * @return array 262 | */ 263 | protected function getApiQueueStatus() 264 | { 265 | if (!class_exists(GuzzleClient::class)) { 266 | throw new \RuntimeException( 267 | 'The guzzle http client library is required to run rabbitmq health checks. ' 268 | .'Make sure to add guzzlehttp/guzzle to your composer.json.' 269 | ); 270 | } 271 | 272 | $client = new GuzzleClient(); 273 | $client->setConfig(['curl.options' => [\CURLOPT_CONNECTTIMEOUT_MS => 3000]]); 274 | $request = $client->get(sprintf('%s/queues', $this->settings['console_url'])); 275 | $request->setAuth($this->settings['user'], $this->settings['pass']); 276 | 277 | return json_decode($request->send()->getBody(true), true); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/Backend/BackendHealthCheck.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Laminas\Diagnostics\Check\AbstractCheck; 17 | 18 | /** 19 | * @final since sonata-project/notification-bundle 3.13 20 | */ 21 | class BackendHealthCheck extends AbstractCheck 22 | { 23 | /** 24 | * @var BackendInterface 25 | */ 26 | protected $backend; 27 | 28 | public function __construct(BackendInterface $backend) 29 | { 30 | $this->backend = $backend; 31 | } 32 | 33 | public function check() 34 | { 35 | return $this->backend->getStatus(); 36 | } 37 | 38 | public function getName() 39 | { 40 | return 'Sonata Notification Default Backend'; 41 | } 42 | 43 | public function getGroup() 44 | { 45 | return 'sonata'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Backend/BackendInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Laminas\Diagnostics\Result\ResultInterface; 17 | use Sonata\NotificationBundle\Iterator\MessageIteratorInterface; 18 | use Sonata\NotificationBundle\Model\MessageInterface; 19 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 20 | 21 | interface BackendInterface 22 | { 23 | public function publish(MessageInterface $message); 24 | 25 | /** 26 | * @param string $type 27 | * 28 | * @return MessageInterface 29 | */ 30 | public function create($type, array $body); 31 | 32 | /** 33 | * @param string $type 34 | */ 35 | public function createAndPublish($type, array $body); 36 | 37 | /** 38 | * @return MessageIteratorInterface 39 | */ 40 | public function getIterator(); 41 | 42 | /** 43 | * Initialize. 44 | * 45 | * @return void 46 | */ 47 | public function initialize(); 48 | 49 | /** 50 | * @return mixed 51 | */ 52 | public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher); 53 | 54 | /** 55 | * @return ResultInterface 56 | */ 57 | public function getStatus(); 58 | 59 | /** 60 | * Clean up messages. 61 | * 62 | * @return void 63 | */ 64 | public function cleanup(); 65 | } 66 | -------------------------------------------------------------------------------- /src/Backend/MessageManagerBackend.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Laminas\Diagnostics\Result\Failure; 17 | use Laminas\Diagnostics\Result\Success; 18 | use Laminas\Diagnostics\Result\Warning; 19 | use Sonata\NotificationBundle\Consumer\ConsumerEvent; 20 | use Sonata\NotificationBundle\Exception\HandlingException; 21 | use Sonata\NotificationBundle\Iterator\MessageManagerMessageIterator; 22 | use Sonata\NotificationBundle\Model\MessageInterface; 23 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 24 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 25 | 26 | /** 27 | * @final since sonata-project/notification-bundle 3.13 28 | */ 29 | class MessageManagerBackend implements BackendInterface 30 | { 31 | /** 32 | * @var MessageManagerInterface 33 | */ 34 | protected $messageManager; 35 | 36 | /** 37 | * @var array 38 | */ 39 | protected $checkLevel; 40 | 41 | /** 42 | * @var int 43 | */ 44 | protected $pause; 45 | 46 | /** 47 | * @var int 48 | */ 49 | protected $maxAge; 50 | 51 | /** 52 | * @var MessageManagerBackendDispatcher|null 53 | */ 54 | protected $dispatcher = null; 55 | 56 | /** 57 | * @var array 58 | */ 59 | protected $types; 60 | 61 | /** 62 | * @var int 63 | */ 64 | protected $batchSize; 65 | 66 | /** 67 | * @param int $pause 68 | * @param int $maxAge 69 | * @param int $batchSize 70 | */ 71 | public function __construct(MessageManagerInterface $messageManager, array $checkLevel, $pause = 500000, $maxAge = 86400, $batchSize = 10, array $types = []) 72 | { 73 | $this->messageManager = $messageManager; 74 | $this->checkLevel = $checkLevel; 75 | $this->pause = $pause; 76 | $this->maxAge = $maxAge; 77 | $this->batchSize = $batchSize; 78 | $this->types = $types; 79 | } 80 | 81 | /** 82 | * @param array $types 83 | */ 84 | public function setTypes($types) 85 | { 86 | $this->types = $types; 87 | } 88 | 89 | public function publish(MessageInterface $message) 90 | { 91 | $this->messageManager->save($message); 92 | 93 | return $message; 94 | } 95 | 96 | public function create($type, array $body) 97 | { 98 | $message = $this->messageManager->create(); 99 | $message->setType($type); 100 | $message->setBody($body); 101 | $message->setState(MessageInterface::STATE_OPEN); 102 | 103 | return $message; 104 | } 105 | 106 | public function createAndPublish($type, array $body) 107 | { 108 | return $this->publish($this->create($type, $body)); 109 | } 110 | 111 | public function getIterator() 112 | { 113 | return new MessageManagerMessageIterator($this->messageManager, $this->types, $this->pause, $this->batchSize); 114 | } 115 | 116 | public function initialize() 117 | { 118 | } 119 | 120 | public function setDispatcher(MessageManagerBackendDispatcher $dispatcher) 121 | { 122 | $this->dispatcher = $dispatcher; 123 | } 124 | 125 | public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher) 126 | { 127 | $event = new ConsumerEvent($message); 128 | 129 | try { 130 | $message->setStartedAt(new \DateTime()); 131 | $message->setState(MessageInterface::STATE_IN_PROGRESS); 132 | $this->messageManager->save($message); 133 | 134 | $dispatcher->dispatch($event, $message->getType()); 135 | 136 | $message->setCompletedAt(new \DateTime()); 137 | $message->setState(MessageInterface::STATE_DONE); 138 | $this->messageManager->save($message); 139 | 140 | return $event->getReturnInfo(); 141 | } catch (\Exception $e) { 142 | $message->setCompletedAt(new \DateTime()); 143 | $message->setState(MessageInterface::STATE_ERROR); 144 | 145 | $this->messageManager->save($message); 146 | 147 | throw new HandlingException('Error while handling a message', 0, $e); 148 | } 149 | } 150 | 151 | public function getStatus() 152 | { 153 | try { 154 | $states = $this->messageManager->countStates(); 155 | } catch (\Exception $e) { 156 | return new Failure(sprintf('Unable to retrieve message information - %s (Database)', $e->getMessage())); 157 | } 158 | 159 | if ($states[MessageInterface::STATE_IN_PROGRESS] > $this->checkLevel[MessageInterface::STATE_IN_PROGRESS]) { 160 | return new Failure('Too many messages processed at the same time (Database)'); 161 | } 162 | 163 | if ($states[MessageInterface::STATE_ERROR] > $this->checkLevel[MessageInterface::STATE_ERROR]) { 164 | return new Failure('Too many errors (Database)'); 165 | } 166 | 167 | if ($states[MessageInterface::STATE_OPEN] > $this->checkLevel[MessageInterface::STATE_OPEN]) { 168 | return new Warning('Too many messages waiting to be processed (Database)'); 169 | } 170 | 171 | if ($states[MessageInterface::STATE_DONE] > $this->checkLevel[MessageInterface::STATE_DONE]) { 172 | return new Warning('Too many processed messages, please clean the database (Database)'); 173 | } 174 | 175 | return new Success('Ok (Database)'); 176 | } 177 | 178 | public function cleanup() 179 | { 180 | $this->messageManager->cleanup($this->maxAge); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Backend/MessageManagerBackendDispatcher.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Laminas\Diagnostics\Result\Success; 17 | use Sonata\NotificationBundle\Model\MessageInterface; 18 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 19 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 20 | 21 | /** 22 | * Producer side of the doctrine backend. 23 | * 24 | * @final since sonata-project/notification-bundle 3.13 25 | */ 26 | class MessageManagerBackendDispatcher extends QueueBackendDispatcher 27 | { 28 | /** 29 | * @var array 30 | */ 31 | protected $dedicatedTypes = []; 32 | 33 | /** 34 | * @var BackendInterface 35 | */ 36 | protected $default; 37 | 38 | /** 39 | * NEXT_MAJOR: Remove $messageManager parameter. 40 | * 41 | * @param MessageManagerInterface $messageManager Only used in compiler pass 42 | * @param string $defaultQueue 43 | */ 44 | public function __construct(MessageManagerInterface $messageManager, array $queues, $defaultQueue, array $backends) 45 | { 46 | parent::__construct($queues, $defaultQueue, $backends); 47 | 48 | foreach ($this->queues as $queue) { 49 | if (true === $queue['default']) { 50 | continue; 51 | } 52 | 53 | $this->dedicatedTypes = array_merge($this->dedicatedTypes, $queue['types']); 54 | } 55 | 56 | foreach ($this->backends as $backend) { 57 | if (empty($backend['types'])) { 58 | $this->default = $backend['backend']; 59 | } 60 | } 61 | } 62 | 63 | public function getBackend($type) 64 | { 65 | $default = null; 66 | 67 | if (!$type) { 68 | return $this->getDefaultBackend(); 69 | } 70 | 71 | foreach ($this->backends as $backend) { 72 | if (\in_array($type, $backend['types'], true)) { 73 | return $backend['backend']; 74 | } 75 | } 76 | 77 | return $this->getDefaultBackend(); 78 | } 79 | 80 | public function getIterator() 81 | { 82 | throw new \RuntimeException('You need to use a specific doctrine backend supporting the selected queue to run a consumer.'); 83 | } 84 | 85 | public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher) 86 | { 87 | throw new \RuntimeException('You need to use a specific doctrine backend supporting the selected queue to run a consumer.'); 88 | } 89 | 90 | public function getStatus() 91 | { 92 | return new Success('Channel is running (Database) and consumers for all queues available.'); 93 | } 94 | 95 | public function cleanup() 96 | { 97 | throw new \RuntimeException('You need to use a specific doctrine backend supporting the selected queue to run a consumer.'); 98 | } 99 | 100 | public function initialize() 101 | { 102 | } 103 | 104 | /** 105 | * @return BackendInterface 106 | */ 107 | protected function getDefaultBackend() 108 | { 109 | $types = []; 110 | 111 | if (!empty($this->dedicatedTypes)) { 112 | $types = [ 113 | 'exclude' => $this->dedicatedTypes, 114 | ]; 115 | } 116 | 117 | $this->default->setTypes($types); 118 | 119 | return $this->default; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Backend/PostponeRuntimeBackend.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Laminas\Diagnostics\Result\Success; 17 | use Sonata\NotificationBundle\Iterator\IteratorProxyMessageIterator; 18 | use Sonata\NotificationBundle\Model\MessageInterface; 19 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 20 | 21 | /** 22 | * This backend postpones the handling of messages to a registered event. 23 | * 24 | * It's based on the asynchronous event dispatcher: 25 | * 26 | * @see https://gist.github.com/3852361 27 | * 28 | * @author Toni Uebernickel 29 | * 30 | * @final since sonata-project/notification-bundle 3.13 31 | */ 32 | class PostponeRuntimeBackend extends RuntimeBackend 33 | { 34 | /** 35 | * @var MessageInterface[] 36 | */ 37 | protected $messages = []; 38 | 39 | /** 40 | * If set to true, you have to fire an event the onEvent method is subscribed to manually! 41 | * 42 | * @var bool 43 | */ 44 | protected $postponeOnCli = false; 45 | 46 | /** 47 | * @param bool $postponeOnCli Whether to postpone the messages on the CLI, too 48 | */ 49 | public function __construct(EventDispatcherInterface $dispatcher, $postponeOnCli = false) 50 | { 51 | parent::__construct($dispatcher); 52 | 53 | $this->postponeOnCli = $postponeOnCli; 54 | } 55 | 56 | public function publish(MessageInterface $message) 57 | { 58 | // if the message is generated from the cli the message is handled 59 | // directly as there is no kernel.terminate in cli 60 | if (!$this->postponeOnCli && $this->isCommandLineInterface()) { 61 | $this->handle($message, $this->dispatcher); 62 | 63 | return; 64 | } 65 | 66 | $this->messages[] = $message; 67 | } 68 | 69 | /** 70 | * Listen on any event and handle the messages. 71 | * 72 | * Actually, an event is not necessary, you can call this method manually, to. 73 | * The event is not processed in any way. 74 | */ 75 | public function onEvent() 76 | { 77 | while (!empty($this->messages)) { 78 | $message = array_shift($this->messages); 79 | 80 | $this->handle($message, $this->dispatcher); 81 | } 82 | } 83 | 84 | public function getIterator() 85 | { 86 | return new IteratorProxyMessageIterator(new \ArrayIterator($this->messages)); 87 | } 88 | 89 | public function getStatus() 90 | { 91 | return new Success('Postpone runtime backend', 'Ok (Postpone Runtime)'); 92 | } 93 | 94 | /** 95 | * Check whether this Backend is run on the CLI. 96 | * 97 | * @return bool 98 | */ 99 | protected function isCommandLineInterface() 100 | { 101 | return 'cli' === \PHP_SAPI; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Backend/QueueBackendDispatcher.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Sonata\NotificationBundle\Model\MessageInterface; 17 | 18 | /** 19 | * Base class for queue backent dispatchers. 20 | * 21 | * @author Kevin Nedelec 22 | */ 23 | abstract class QueueBackendDispatcher implements QueueDispatcherInterface, BackendInterface 24 | { 25 | /** 26 | * @var array 27 | */ 28 | protected $queues; 29 | 30 | /** 31 | * @var string 32 | */ 33 | protected $defaultQueue; 34 | 35 | /** 36 | * @var BackendInterface[] 37 | */ 38 | protected $backends; 39 | 40 | /** 41 | * @param string $defaultQueue 42 | * @param BackendInterface[] $backends 43 | */ 44 | public function __construct(array $queues, $defaultQueue, array $backends) 45 | { 46 | $this->queues = $queues; 47 | $this->backends = $backends; 48 | $this->defaultQueue = $defaultQueue; 49 | 50 | foreach ($this->backends as $backend) { 51 | $backend['backend']->setDispatcher($this); 52 | } 53 | } 54 | 55 | public function publish(MessageInterface $message) 56 | { 57 | $this->getBackend($message->getType())->publish($message); 58 | } 59 | 60 | public function create($type, array $body) 61 | { 62 | return $this->getBackend($type)->create($type, $body); 63 | } 64 | 65 | public function createAndPublish($type, array $body) 66 | { 67 | $this->getBackend($type)->createAndPublish($type, $body); 68 | } 69 | 70 | public function getQueues() 71 | { 72 | return $this->queues; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Backend/QueueDispatcherInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Sonata\NotificationBundle\Exception\BackendNotFoundException; 17 | 18 | /** 19 | * A QueueDispatcherInterface acts as a router for different 20 | * queue types. 21 | * 22 | * @see AMQPBackendDispatcher for an example implementation 23 | */ 24 | interface QueueDispatcherInterface 25 | { 26 | /** 27 | * Get a backend by message type. 28 | * 29 | * NEXT_MAJOR: Change signature to getBackend(?string $type = null); 30 | * 31 | * @param string|null $type 32 | * 33 | * @throws BackendNotFoundException 34 | * 35 | * @return BackendInterface 36 | */ 37 | public function getBackend($type); 38 | 39 | /** 40 | * Get all registered queues. 41 | * 42 | * @return array 43 | */ 44 | public function getQueues(); 45 | } 46 | -------------------------------------------------------------------------------- /src/Backend/RuntimeBackend.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Backend; 15 | 16 | use Laminas\Diagnostics\Result\Success; 17 | use Sonata\NotificationBundle\Consumer\ConsumerEvent; 18 | use Sonata\NotificationBundle\Exception\HandlingException; 19 | use Sonata\NotificationBundle\Model\Message; 20 | use Sonata\NotificationBundle\Model\MessageInterface; 21 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 22 | 23 | class RuntimeBackend implements BackendInterface 24 | { 25 | /** 26 | * @var EventDispatcherInterface 27 | */ 28 | protected $dispatcher; 29 | 30 | public function __construct(EventDispatcherInterface $dispatcher) 31 | { 32 | $this->dispatcher = $dispatcher; 33 | } 34 | 35 | public function publish(MessageInterface $message) 36 | { 37 | $this->handle($message, $this->dispatcher); 38 | 39 | return $message; 40 | } 41 | 42 | public function create($type, array $body) 43 | { 44 | $message = new Message(); 45 | $message->setType($type); 46 | $message->setBody($body); 47 | $message->setState(MessageInterface::STATE_OPEN); 48 | 49 | return $message; 50 | } 51 | 52 | public function createAndPublish($type, array $body) 53 | { 54 | return $this->publish($this->create($type, $body)); 55 | } 56 | 57 | public function getIterator() 58 | { 59 | return new \EmptyIterator(); 60 | } 61 | 62 | public function initialize() 63 | { 64 | } 65 | 66 | public function cleanup() 67 | { 68 | } 69 | 70 | public function handle(MessageInterface $message, EventDispatcherInterface $dispatcher) 71 | { 72 | $event = new ConsumerEvent($message); 73 | 74 | try { 75 | $dispatcher->dispatch($event, $message->getType()); 76 | 77 | $message->setCompletedAt(new \DateTime()); 78 | $message->setState(MessageInterface::STATE_DONE); 79 | } catch (\Exception $e) { 80 | $message->setCompletedAt(new \DateTime()); 81 | $message->setState(MessageInterface::STATE_ERROR); 82 | 83 | throw new HandlingException('Error while handling a message: '.$e->getMessage(), 0, $e); 84 | } 85 | } 86 | 87 | public function getStatus() 88 | { 89 | return new Success('Runtime backend health check', 'Ok (Runtime)'); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Command/CleanupCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Command; 15 | 16 | use Sonata\NotificationBundle\Backend\BackendInterface; 17 | use Sonata\NotificationBundle\Backend\QueueDispatcherInterface; 18 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 19 | use Symfony\Component\Console\Input\InputInterface; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | 22 | /** 23 | * @final since sonata-project/notification-bundle 3.13 24 | */ 25 | class CleanupCommand extends ContainerAwareCommand 26 | { 27 | public function configure() 28 | { 29 | $this->setName('sonata:notification:cleanup'); 30 | $this->setDescription('Clean up backend message'); 31 | } 32 | 33 | public function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | $output->write('Starting ... '); 36 | 37 | $this->getBackend()->cleanup(); 38 | 39 | $output->writeln('done!'); 40 | 41 | return 0; 42 | } 43 | 44 | /** 45 | * @return BackendInterface 46 | */ 47 | private function getBackend() 48 | { 49 | $backend = $this->getContainer()->get('sonata.notification.backend'); 50 | 51 | if ($backend instanceof QueueDispatcherInterface) { 52 | return $backend->getBackend(null); 53 | } 54 | 55 | return $backend; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Command/ConsumerHandlerCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Command; 15 | 16 | use Sonata\NotificationBundle\Backend\BackendInterface; 17 | use Sonata\NotificationBundle\Backend\QueueDispatcherInterface; 18 | use Sonata\NotificationBundle\Consumer\ConsumerInterface; 19 | use Sonata\NotificationBundle\Event\IterateEvent; 20 | use Sonata\NotificationBundle\Exception\HandlingException; 21 | use Sonata\NotificationBundle\Model\MessageInterface; 22 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 23 | use Symfony\Component\Console\Input\InputInterface; 24 | use Symfony\Component\Console\Input\InputOption; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; 27 | 28 | /** 29 | * @final since sonata-project/notification-bundle 3.13 30 | */ 31 | class ConsumerHandlerCommand extends ContainerAwareCommand 32 | { 33 | public function configure() 34 | { 35 | $this->setName('sonata:notification:start'); 36 | $this->setDescription('Listen for incoming messages'); 37 | $this->addOption('iteration', 'i', InputOption::VALUE_OPTIONAL, 'Only run n iterations before exiting', false); 38 | $this->addOption('type', null, InputOption::VALUE_OPTIONAL, 'Use a specific backed based on a message type, "all" with doctrine backend will handle all notifications no matter their type', null); 39 | $this->addOption('show-details', 'd', InputOption::VALUE_OPTIONAL, 'Show consumers return details', true); 40 | } 41 | 42 | public function execute(InputInterface $input, OutputInterface $output) 43 | { 44 | $startDate = new \DateTime(); 45 | 46 | $output->writeln(sprintf('[%s] Checking listeners', $startDate->format('r'))); 47 | foreach ($this->getNotificationDispatcher()->getListeners() as $type => $listeners) { 48 | $output->writeln(sprintf(' - %s', $type)); 49 | foreach ($listeners as $listener) { 50 | if (!$listener[0] instanceof ConsumerInterface) { 51 | throw new \RuntimeException(sprintf( 52 | 'The registered service does not implement the ConsumerInterface (class=%s', 53 | \get_class($listener[0]) 54 | )); 55 | } 56 | 57 | $output->writeln(sprintf(' > %s', \get_class($listener[0]))); 58 | } 59 | } 60 | 61 | $type = $input->getOption('type'); 62 | $showDetails = $input->getOption('show-details'); 63 | 64 | $output->write(sprintf('[%s] Retrieving backend ...', $startDate->format('r'))); 65 | $backend = $this->getBackend($type); 66 | 67 | $output->writeln(''); 68 | $output->write(sprintf('[%s] Initialize backend ...', $startDate->format('r'))); 69 | 70 | // initialize the backend 71 | $backend->initialize(); 72 | 73 | $output->writeln(' done!'); 74 | 75 | if (null === $type) { 76 | $output->writeln(sprintf( 77 | '[%s] Starting the backend handler - %s', 78 | $startDate->format('r'), 79 | \get_class($backend) 80 | )); 81 | } else { 82 | $output->writeln(sprintf( 83 | '[%s] Starting the backend handler - %s (type: %s)', 84 | $startDate->format('r'), 85 | \get_class($backend), 86 | $type 87 | )); 88 | } 89 | 90 | $startMemoryUsage = memory_get_usage(true); 91 | $i = 0; 92 | $iterator = $backend->getIterator(); 93 | foreach ($iterator as $message) { 94 | ++$i; 95 | 96 | if (!$message instanceof MessageInterface) { 97 | throw new \RuntimeException('The iterator must return a MessageInterface instance'); 98 | } 99 | 100 | if (!$message->getType()) { 101 | $output->write('Skipping : no type defined '); 102 | 103 | continue; 104 | } 105 | 106 | $date = new \DateTime(); 107 | $output->write(sprintf('[%s] %s #%s: ', $date->format('r'), $message->getType(), $i)); 108 | $memoryUsage = memory_get_usage(true); 109 | 110 | try { 111 | $start = microtime(true); 112 | $returnInfos = $backend->handle($message, $this->getNotificationDispatcher()); 113 | 114 | $currentMemory = memory_get_usage(true); 115 | 116 | $output->writeln(sprintf( 117 | 'OK! - %0.04fs, %ss, %s, %s - %s = %s, %0.02f%%', 118 | microtime(true) - $start, 119 | $date->format('U') - $message->getCreatedAt()->format('U'), 120 | $this->formatMemory($currentMemory - $memoryUsage), 121 | $this->formatMemory($currentMemory), 122 | $this->formatMemory($startMemoryUsage), 123 | $this->formatMemory($currentMemory - $startMemoryUsage), 124 | ($currentMemory - $startMemoryUsage) / $startMemoryUsage * 100 125 | )); 126 | 127 | if ($showDetails && null !== $returnInfos) { 128 | $output->writeln($returnInfos->getReturnMessage()); 129 | } 130 | } catch (HandlingException $e) { 131 | $output->writeln(sprintf('KO! - %s', $e->getPrevious()->getMessage())); 132 | } catch (\Exception $e) { 133 | $output->writeln(sprintf('KO! - %s', $e->getMessage())); 134 | } 135 | 136 | $this->getEventDispatcher()->dispatch( 137 | new IterateEvent($iterator, $backend, $message), 138 | IterateEvent::EVENT_NAME 139 | ); 140 | 141 | if ($input->getOption('iteration') && $i >= (int) $input->getOption('iteration')) { 142 | $output->writeln('End of iteration cycle'); 143 | 144 | return 0; 145 | } 146 | } 147 | 148 | return 0; 149 | } 150 | 151 | /** 152 | * @param string $type 153 | * @param object $backend 154 | * 155 | * @throws \RuntimeException 156 | */ 157 | protected function throwTypeNotFoundException($type, $backend) 158 | { 159 | throw new \RuntimeException( 160 | "The requested backend for the type '".$type." 'does not exist. \nMake sure the backend '". 161 | \get_class($backend)."' \nsupports multiple queues and the routing_key is defined. (Currently rabbitmq only)" 162 | ); 163 | } 164 | 165 | /** 166 | * @param $memory 167 | * 168 | * @return string 169 | */ 170 | private function formatMemory($memory) 171 | { 172 | if ($memory < 1024) { 173 | return $memory.'b'; 174 | } elseif ($memory < 1048576) { 175 | return round($memory / 1024, 2).'Kb'; 176 | } 177 | 178 | return round($memory / 1048576, 2).'Mb'; 179 | } 180 | 181 | /** 182 | * @param string $type 183 | * 184 | * @return BackendInterface 185 | */ 186 | private function getBackend($type = null) 187 | { 188 | $backend = $this->getContainer()->get('sonata.notification.backend'); 189 | 190 | if ($type && !\array_key_exists($type, $this->getNotificationDispatcher()->getListeners())) { 191 | throw new \RuntimeException(sprintf('The type `%s` does not exist, available types: %s', $type, implode(', ', array_keys($this->getNotificationDispatcher()->getListeners())))); 192 | } 193 | 194 | if (null !== $type && !$backend instanceof QueueDispatcherInterface) { 195 | throw new \RuntimeException(sprintf( 196 | 'Unable to use the provided type %s with a non QueueDispatcherInterface backend', 197 | $type 198 | )); 199 | } 200 | 201 | if ($backend instanceof QueueDispatcherInterface) { 202 | return $backend->getBackend($type); 203 | } 204 | 205 | return $backend; 206 | } 207 | 208 | /** 209 | * @return EventDispatcherInterface 210 | */ 211 | private function getNotificationDispatcher() 212 | { 213 | return $this->getContainer()->get('sonata.notification.dispatcher'); 214 | } 215 | 216 | /** 217 | * @return EventDispatcherInterface 218 | */ 219 | private function getEventDispatcher() 220 | { 221 | return $this->getContainer()->get('event_dispatcher'); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Command/CreateAndPublishCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Command; 15 | 16 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 17 | use Symfony\Component\Console\Input\InputArgument; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | use Symfony\Component\Console\Output\OutputInterface; 20 | 21 | /** 22 | * @final since sonata-project/notification-bundle 3.13 23 | */ 24 | class CreateAndPublishCommand extends ContainerAwareCommand 25 | { 26 | public function configure() 27 | { 28 | $this 29 | ->setName('sonata:notification:create-and-publish') 30 | ->addArgument('type', InputArgument::REQUIRED, 'Type of the notification') 31 | ->addArgument('body', InputArgument::REQUIRED, 'Body of the notification (json)'); 32 | } 33 | 34 | public function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $type = $input->getArgument('type'); 37 | $body = json_decode($input->getArgument('body'), true); 38 | 39 | if (null === $body) { 40 | throw new \InvalidArgumentException('Body does not contain valid json.'); 41 | } 42 | 43 | $this->getContainer() 44 | ->get('sonata.notification.backend') 45 | ->createAndPublish($type, $body); 46 | 47 | $output->writeln('Done !'); 48 | 49 | return 0; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Command/ListHandlerCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Command; 15 | 16 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | use Symfony\Component\Console\Output\OutputInterface; 19 | 20 | /** 21 | * @final since sonata-project/notification-bundle 3.13 22 | */ 23 | class ListHandlerCommand extends ContainerAwareCommand 24 | { 25 | public function configure() 26 | { 27 | $this->setName('sonata:notification:list-handler'); 28 | $this->setDescription('List all consumers available'); 29 | } 30 | 31 | public function execute(InputInterface $input, OutputInterface $output) 32 | { 33 | $output->writeln('List of consumers available'); 34 | foreach ($this->getMetadata() as $type => $ids) { 35 | foreach ($ids as $id) { 36 | $output->writeln(sprintf('%s - %s', $type, $id)); 37 | } 38 | } 39 | 40 | $output->writeln(' done!'); 41 | 42 | return 0; 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | private function getMetadata() 49 | { 50 | return $this->getContainer()->get('sonata.notification.consumer.metadata')->getInformations(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/ListQueuesCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Command; 15 | 16 | use Sonata\NotificationBundle\Backend\QueueDispatcherInterface; 17 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | use Symfony\Component\Console\Output\OutputInterface; 20 | 21 | /** 22 | * @final since sonata-project/notification-bundle 3.13 23 | */ 24 | class ListQueuesCommand extends ContainerAwareCommand 25 | { 26 | public function configure() 27 | { 28 | $this->setName('sonata:notification:list-queues'); 29 | $this->setDescription('List all queues available'); 30 | } 31 | 32 | public function execute(InputInterface $input, OutputInterface $output) 33 | { 34 | $backend = $this->getContainer()->get('sonata.notification.backend'); 35 | 36 | if (!$backend instanceof QueueDispatcherInterface) { 37 | $output->writeln( 38 | 'The backend class '.\get_class($backend).' does not provide multiple queues.' 39 | ); 40 | 41 | return 0; 42 | } 43 | 44 | $output->writeln('List of queues available'); 45 | foreach ($backend->getQueues() as $queue) { 46 | $output->writeln(sprintf( 47 | 'queue: %s - routing_key: %s', 48 | $queue['queue'], 49 | $queue['routing_key'] 50 | )); 51 | } 52 | 53 | return 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Command/RestartCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Command; 15 | 16 | use Sonata\NotificationBundle\Backend\BackendInterface; 17 | use Sonata\NotificationBundle\Event\IterateEvent; 18 | use Sonata\NotificationBundle\Iterator\ErroneousMessageIterator; 19 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 20 | use Sonata\NotificationBundle\Selector\ErroneousMessagesSelector; 21 | use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 22 | use Symfony\Component\Console\Input\InputInterface; 23 | use Symfony\Component\Console\Input\InputOption; 24 | use Symfony\Component\Console\Output\OutputInterface; 25 | use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; 26 | 27 | /** 28 | * @final since sonata-project/notification-bundle 3.13 29 | */ 30 | class RestartCommand extends ContainerAwareCommand 31 | { 32 | public function configure() 33 | { 34 | $this->setName('sonata:notification:restart'); 35 | $this->setDescription('Restart messages with erroneous statuses, only for doctrine backends'); 36 | $this->addOption('type', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'List of messages types to restart (separate multiple types with a space)'); 37 | $this->addOption('max-attempts', null, InputOption::VALUE_REQUIRED, 'Maximum number of attempts', 6); 38 | $this->addOption('attempt-delay', null, InputOption::VALUE_OPTIONAL, 'Min seconds between two attempts', 10); 39 | $this->addOption('pulling', null, InputOption::VALUE_NONE, 'Run the command as an infinite pulling loop'); 40 | $this->addOption('pause', null, InputOption::VALUE_OPTIONAL, 'Seconds between each data pull (used only when pulling option is set)', 500000); 41 | $this->addOption('batch-size', null, InputOption::VALUE_OPTIONAL, 'Number of message to process on each pull (used only when pulling option is set)', 10); 42 | } 43 | 44 | public function execute(InputInterface $input, OutputInterface $output) 45 | { 46 | $output->writeln('Starting... '); 47 | 48 | if (!is_numeric($input->getOption('max-attempts'))) { 49 | throw new \Exception('Option "max-attempts" is invalid (integer value needed).'); 50 | } 51 | 52 | $pullMode = $input->getOption('pulling'); 53 | $manager = $this->getMessageManager(); 54 | 55 | if ($pullMode) { 56 | $messages = new ErroneousMessageIterator( 57 | $manager, 58 | $input->getOption('type'), 59 | $input->getOption('pause'), 60 | $input->getOption('batch-size'), 61 | $input->getOption('max-attempts'), 62 | $input->getOption('attempt-delay') 63 | ); 64 | } else { 65 | $messages = $this->getErroneousMessageSelector()->getMessages( 66 | $input->getOption('type'), 67 | $input->getOption('max-attempts') 68 | ); 69 | 70 | /* 71 | * Check messages count only for not pulling mode 72 | * to avoid PHP warning message 73 | * since ErroneousMessageIterator does not implement Countable. 74 | */ 75 | if (0 === \count($messages)) { 76 | $output->writeln('Nothing to restart, bye.'); 77 | 78 | return 0; 79 | } 80 | } 81 | 82 | /** @var EventDispatcherInterface $eventDispatcher */ 83 | $eventDispatcher = $this->getContainer()->get('event_dispatcher'); 84 | 85 | foreach ($messages as $message) { 86 | $id = $message->getId(); 87 | 88 | $newMessage = $manager->restart($message); 89 | 90 | $this->getBackend()->publish($newMessage); 91 | 92 | $output->writeln(sprintf( 93 | 'Reset Message %s #%d, new id %d. Attempt #%d', 94 | $newMessage->getType(), 95 | $id, 96 | $newMessage->getId(), 97 | $newMessage->getRestartCount() 98 | )); 99 | 100 | if ($pullMode) { 101 | $eventDispatcher->dispatch(new IterateEvent($messages, null, $newMessage), IterateEvent::EVENT_NAME); 102 | } 103 | } 104 | 105 | $output->writeln('Done!'); 106 | 107 | return 0; 108 | } 109 | 110 | /** 111 | * Return the erroneous message selector service. 112 | * 113 | * @return ErroneousMessagesSelector 114 | */ 115 | protected function getErroneousMessageSelector() 116 | { 117 | return $this->getContainer()->get('sonata.notification.erroneous_messages_selector'); 118 | } 119 | 120 | /** 121 | * @return MessageManagerInterface 122 | */ 123 | protected function getMessageManager() 124 | { 125 | return $this->getContainer()->get('sonata.notification.manager.message'); 126 | } 127 | 128 | /** 129 | * @return BackendInterface 130 | */ 131 | protected function getBackend() 132 | { 133 | return $this->getContainer()->get('sonata.notification.backend'); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Consumer/ConsumerEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | use Sonata\NotificationBundle\Model\MessageInterface; 17 | use Symfony\Contracts\EventDispatcher\Event; 18 | 19 | /** 20 | * @final since sonata-project/notification-bundle 3.13 21 | */ 22 | class ConsumerEvent extends Event implements ConsumerEventInterface 23 | { 24 | /** 25 | * @var MessageInterface 26 | */ 27 | protected $message; 28 | 29 | /** 30 | * @var ConsumerReturnInfo 31 | */ 32 | protected $returnInfo; 33 | 34 | public function __construct(MessageInterface $message) 35 | { 36 | $this->message = $message; 37 | } 38 | 39 | public function getMessage() 40 | { 41 | return $this->message; 42 | } 43 | 44 | /** 45 | * @param ConsumerReturnInfo $returnInfo 46 | */ 47 | public function setReturnInfo($returnInfo) 48 | { 49 | $this->returnInfo = $returnInfo; 50 | } 51 | 52 | /** 53 | * @return ConsumerReturnInfo 54 | */ 55 | public function getReturnInfo() 56 | { 57 | return $this->returnInfo; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Consumer/ConsumerEventInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | use Sonata\NotificationBundle\Model\MessageInterface; 17 | 18 | interface ConsumerEventInterface 19 | { 20 | /** 21 | * @return MessageInterface 22 | */ 23 | public function getMessage(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Consumer/ConsumerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | interface ConsumerInterface 17 | { 18 | /** 19 | * Process a ConsumerEvent. 20 | */ 21 | public function process(ConsumerEvent $event); 22 | } 23 | -------------------------------------------------------------------------------- /src/Consumer/ConsumerReturnInfo.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | /** 17 | * Return informations for comsumers. 18 | * 19 | * @author Kevin Nedelec 20 | * 21 | * @final since sonata-project/notification-bundle 3.13 22 | */ 23 | class ConsumerReturnInfo 24 | { 25 | /** 26 | * @var string 27 | */ 28 | protected $returnMessage; 29 | 30 | /** 31 | * @param string $returnMessage 32 | */ 33 | public function setReturnMessage($returnMessage) 34 | { 35 | $this->returnMessage = $returnMessage; 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getReturnMessage() 42 | { 43 | return $this->returnMessage; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Consumer/LoggerConsumer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | use Psr\Log\LoggerInterface; 17 | use Sonata\NotificationBundle\Exception\InvalidParameterException; 18 | 19 | /** 20 | * @final since sonata-project/notification-bundle 3.13 21 | */ 22 | class LoggerConsumer implements ConsumerInterface 23 | { 24 | /** 25 | * @var LoggerInterface 26 | */ 27 | protected $logger; 28 | 29 | /** 30 | * @var string[] 31 | */ 32 | protected $types = [ 33 | 'emerg' => 'emergency', 34 | 'alert' => 'alert', 35 | 'crit' => 'critical', 36 | 'err' => 'error', 37 | 'warn' => 'warning', 38 | 'notice' => 'notice', 39 | 'info' => 'info', 40 | 'debug' => 'debug', 41 | ]; 42 | 43 | public function __construct(LoggerInterface $logger) 44 | { 45 | $this->logger = $logger; 46 | } 47 | 48 | public function process(ConsumerEvent $event) 49 | { 50 | $message = $event->getMessage(); 51 | 52 | if (!\array_key_exists($message->getValue('level'), $this->types)) { 53 | throw new InvalidParameterException(); 54 | } 55 | 56 | $level = $this->types[$message->getValue('level')]; 57 | 58 | $this->logger->{$level}($message->getValue('message')); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Consumer/Metadata.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | /** 17 | * @final since sonata-project/notification-bundle 3.13 18 | */ 19 | class Metadata 20 | { 21 | /** 22 | * @var array 23 | */ 24 | protected $informations; 25 | 26 | public function __construct(array $informations = []) 27 | { 28 | $this->informations = $informations; 29 | } 30 | 31 | /** 32 | * @return array 33 | */ 34 | public function getInformations() 35 | { 36 | return $this->informations; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Consumer/SwiftMailerConsumer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Consumer; 15 | 16 | use Sonata\NotificationBundle\Model\MessageInterface; 17 | 18 | /** 19 | * @final since sonata-project/notification-bundle 3.13 20 | */ 21 | class SwiftMailerConsumer implements ConsumerInterface 22 | { 23 | /** 24 | * @var \Swift_Mailer 25 | */ 26 | protected $mailer; 27 | 28 | public function __construct(\Swift_Mailer $mailer) 29 | { 30 | $this->mailer = $mailer; 31 | } 32 | 33 | public function process(ConsumerEvent $event) 34 | { 35 | if (!$this->mailer->getTransport()->isStarted()) { 36 | $this->mailer->getTransport()->start(); 37 | } 38 | 39 | $exception = false; 40 | 41 | try { 42 | $this->sendEmail($event->getMessage()); 43 | } catch (\Exception $e) { 44 | $exception = $e; 45 | } 46 | 47 | $this->mailer->getTransport()->stop(); 48 | 49 | if ($exception) { 50 | throw $exception; 51 | } 52 | } 53 | 54 | private function sendEmail(MessageInterface $message) 55 | { 56 | $mail = $this->mailer->createMessage() 57 | ->setSubject($message->getValue('subject')) 58 | ->setFrom([$message->getValue(['from', 'email']) => $message->getValue(['from', 'name'])]) 59 | ->setTo($message->getValue('to')); 60 | 61 | if ($replyTo = $message->getValue('replyTo')) { 62 | $mail->setReplyTo($replyTo); 63 | } 64 | if ($returnPath = $message->getValue('returnPath')) { 65 | $mail->setReturnPath($returnPath); 66 | } 67 | 68 | if ($cc = $message->getValue('cc')) { 69 | $mail->setCc($cc); 70 | } 71 | 72 | if ($bcc = $message->getValue('bcc')) { 73 | $mail->setBcc($bcc); 74 | } 75 | 76 | if ($text = $message->getValue(['message', 'text'])) { 77 | $mail->addPart($text, 'text/plain'); 78 | } 79 | 80 | if ($html = $message->getValue(['message', 'html'])) { 81 | $mail->addPart($html, 'text/html'); 82 | } 83 | 84 | if ($attachment = $message->getValue(['attachment', 'file'])) { 85 | $attachmentName = $message->getValue(['attachment', 'name']); 86 | 87 | $mail->attach(new \Swift_Attachment($attachment, $attachmentName)); 88 | } 89 | 90 | $this->mailer->send($mail); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Controller/Api/Legacy/MessageController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Controller\Api\Legacy; 15 | 16 | use FOS\RestBundle\Controller\Annotations as Rest; 17 | use FOS\RestBundle\Request\ParamFetcherInterface; 18 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; 19 | use Sonata\DatagridBundle\Pager\PagerInterface; 20 | use Sonata\NotificationBundle\Form\Type\MessageSerializationType; 21 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 22 | use Symfony\Component\Form\FormFactoryInterface; 23 | use Symfony\Component\Form\FormInterface; 24 | use Symfony\Component\HttpFoundation\Request; 25 | 26 | /** 27 | * @author Hugo Briand 28 | */ 29 | class MessageController 30 | { 31 | /** 32 | * @var MessageManagerInterface 33 | */ 34 | protected $messageManager; 35 | 36 | /** 37 | * @var FormFactoryInterface 38 | */ 39 | protected $formFactory; 40 | 41 | public function __construct(MessageManagerInterface $messageManager, FormFactoryInterface $formFactory) 42 | { 43 | $this->messageManager = $messageManager; 44 | $this->formFactory = $formFactory; 45 | } 46 | 47 | /** 48 | * Retrieves the list of messages (paginated). 49 | * 50 | * @ApiDoc( 51 | * resource=true, 52 | * output={"class"="Sonata\DatagridBundle\Pager\PagerInterface", "groups"={"sonata_api_read"}} 53 | * ) 54 | * 55 | * @Rest\QueryParam(name="page", requirements="\d+", default="1", description="Page for message list pagination") 56 | * @Rest\QueryParam(name="count", requirements="\d+", default="10", description="Number of messages per page") 57 | * @Rest\QueryParam(name="type", nullable=true, description="Message type filter") 58 | * @Rest\QueryParam(name="state", requirements="\d+", strict=true, nullable=true, description="Message status filter") 59 | * @Rest\QueryParam(name="orderBy", map=true, requirements="ASC|DESC", nullable=true, strict=true, description="Query groups order by clause (key is field, value is direction)") 60 | * 61 | * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) 62 | * 63 | * @return PagerInterface 64 | */ 65 | public function getMessagesAction(ParamFetcherInterface $paramFetcher) 66 | { 67 | $supportedCriteria = [ 68 | 'state' => '', 69 | 'type' => '', 70 | ]; 71 | 72 | $page = $paramFetcher->get('page'); 73 | $limit = $paramFetcher->get('count'); 74 | $sort = $paramFetcher->get('orderBy'); 75 | $criteria = array_intersect_key($paramFetcher->all(), $supportedCriteria); 76 | 77 | $criteria = array_filter($criteria, static function ($value): bool { 78 | return null !== $value; 79 | }); 80 | 81 | if (!$sort) { 82 | $sort = []; 83 | } elseif (!\is_array($sort)) { 84 | $sort = [$sort => 'asc']; 85 | } 86 | 87 | return $this->getMessageManager()->getPager($criteria, $page, $limit, $sort); 88 | } 89 | 90 | /** 91 | * Adds a message. 92 | * 93 | * @ApiDoc( 94 | * input={"class"="sonata_notification_api_form_message", "name"="", "groups"={"sonata_api_write"}}, 95 | * output={"class"="Sonata\NotificationBundle\Model\Message", "groups"={"sonata_api_read"}}, 96 | * statusCodes={ 97 | * 200="Returned when successful", 98 | * 400="Returned when an error has occurred while message creation" 99 | * } 100 | * ) 101 | * 102 | * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) 103 | * 104 | * @param Request $request A Symfony request 105 | * 106 | * @return FormInterface 107 | */ 108 | public function postMessageAction(Request $request) 109 | { 110 | $message = null; 111 | 112 | $form = $this->formFactory->createNamed('', MessageSerializationType::class, $message, [ 113 | 'csrf_protection' => false, 114 | ]); 115 | 116 | $form->handleRequest($request); 117 | 118 | if ($form->isSubmitted() && $form->isValid()) { 119 | $message = $form->getData(); 120 | $this->messageManager->save($message); 121 | 122 | return $message; 123 | } 124 | 125 | return $form; 126 | } 127 | 128 | /** 129 | * @return MessageManagerInterface 130 | */ 131 | protected function getMessageManager() 132 | { 133 | return $this->messageManager; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Controller/Api/MessageController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Controller\Api; 15 | 16 | use FOS\RestBundle\Controller\Annotations as Rest; 17 | use FOS\RestBundle\Request\ParamFetcherInterface; 18 | use Nelmio\ApiDocBundle\Annotation\Model; 19 | use Nelmio\ApiDocBundle\Annotation\Operation; 20 | use Sonata\DatagridBundle\Pager\PagerInterface; 21 | use Sonata\NotificationBundle\Form\Type\MessageSerializationType; 22 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 23 | use Swagger\Annotations as SWG; 24 | use Symfony\Component\Form\FormFactoryInterface; 25 | use Symfony\Component\Form\FormInterface; 26 | use Symfony\Component\HttpFoundation\Request; 27 | 28 | /** 29 | * @author Hugo Briand 30 | * 31 | * @final since sonata-project/notification-bundle 3.13 32 | */ 33 | class MessageController 34 | { 35 | /** 36 | * @var MessageManagerInterface 37 | */ 38 | protected $messageManager; 39 | 40 | /** 41 | * @var FormFactoryInterface 42 | */ 43 | protected $formFactory; 44 | 45 | public function __construct(MessageManagerInterface $messageManager, FormFactoryInterface $formFactory) 46 | { 47 | $this->messageManager = $messageManager; 48 | $this->formFactory = $formFactory; 49 | } 50 | 51 | /** 52 | * Retrieves the list of messages (paginated). 53 | * 54 | * @Operation( 55 | * tags={"/api/notification/messages"}, 56 | * summary="Retrieves the list of messages (paginated).", 57 | * @SWG\Parameter( 58 | * name="page", 59 | * in="query", 60 | * description="Page for message list pagination", 61 | * required=false, 62 | * type="string" 63 | * ), 64 | * @SWG\Parameter( 65 | * name="count", 66 | * in="query", 67 | * description="Number of messages per page", 68 | * required=false, 69 | * type="string" 70 | * ), 71 | * @SWG\Parameter( 72 | * name="type", 73 | * in="query", 74 | * description="Message type filter", 75 | * required=false, 76 | * type="string" 77 | * ), 78 | * @SWG\Parameter( 79 | * name="state", 80 | * in="query", 81 | * description="Message status filter", 82 | * required=false, 83 | * type="string" 84 | * ), 85 | * @SWG\Parameter( 86 | * name="orderBy", 87 | * in="query", 88 | * description="Query groups order by clause (key is field, value is direction)", 89 | * required=false, 90 | * type="string" 91 | * ), 92 | * @SWG\Response( 93 | * response="200", 94 | * description="Returned when successful", 95 | * @SWG\Schema(ref=@Model(type="Sonata\DatagridBundle\Pager\PagerInterface")) 96 | * ) 97 | * ) 98 | * 99 | * @Rest\QueryParam(name="page", requirements="\d+", default="1", description="Page for message list pagination") 100 | * @Rest\QueryParam(name="count", requirements="\d+", default="10", description="Number of messages per page") 101 | * @Rest\QueryParam(name="type", nullable=true, description="Message type filter") 102 | * @Rest\QueryParam(name="state", requirements="\d+", strict=true, nullable=true, description="Message status filter") 103 | * @Rest\QueryParam(name="orderBy", map=true, requirements="ASC|DESC", nullable=true, strict=true, description="Query groups order by clause (key is field, value is direction)") 104 | * 105 | * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) 106 | * 107 | * @return PagerInterface 108 | */ 109 | public function getMessagesAction(ParamFetcherInterface $paramFetcher) 110 | { 111 | $supportedCriteria = [ 112 | 'state' => '', 113 | 'type' => '', 114 | ]; 115 | 116 | $page = $paramFetcher->get('page'); 117 | $limit = $paramFetcher->get('count'); 118 | $sort = $paramFetcher->get('orderBy'); 119 | $criteria = array_intersect_key($paramFetcher->all(), $supportedCriteria); 120 | 121 | $criteria = array_filter($criteria, static function ($value): bool { 122 | return null !== $value; 123 | }); 124 | 125 | if (!$sort) { 126 | $sort = []; 127 | } elseif (!\is_array($sort)) { 128 | $sort = [$sort => 'asc']; 129 | } 130 | 131 | return $this->getMessageManager()->getPager($criteria, $page, $limit, $sort); 132 | } 133 | 134 | /** 135 | * Adds a message. 136 | * 137 | * @Operation( 138 | * tags={"/api/notification/messages"}, 139 | * summary="Adds a message.", 140 | * @SWG\Response( 141 | * response="200", 142 | * description="Returned when successful", 143 | * @SWG\Schema(ref=@Model(type="Sonata\NotificationBundle\Model\Message")) 144 | * ), 145 | * @SWG\Response( 146 | * response="400", 147 | * description="Returned when an error has occurred while message creation" 148 | * ) 149 | * ) 150 | * 151 | * @Rest\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) 152 | * 153 | * @return FormInterface 154 | */ 155 | public function postMessageAction(Request $request) 156 | { 157 | $message = null; 158 | 159 | $form = $this->formFactory->createNamed('', MessageSerializationType::class, $message, [ 160 | 'csrf_protection' => false, 161 | ]); 162 | 163 | $form->handleRequest($request); 164 | 165 | if ($form->isSubmitted() && $form->isValid()) { 166 | $message = $form->getData(); 167 | $this->messageManager->save($message); 168 | 169 | return $message; 170 | } 171 | 172 | return $form; 173 | } 174 | 175 | /** 176 | * @return MessageManagerInterface 177 | */ 178 | protected function getMessageManager() 179 | { 180 | return $this->messageManager; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Controller/MessageAdminController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Controller; 15 | 16 | use Sonata\AdminBundle\Controller\CRUDController; 17 | use Sonata\AdminBundle\Datagrid\ProxyQueryInterface; 18 | use Sonata\NotificationBundle\Backend\BackendInterface; 19 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 20 | use Symfony\Component\HttpFoundation\RedirectResponse; 21 | use Symfony\Component\Security\Core\Exception\AccessDeniedException; 22 | 23 | /** 24 | * @final since sonata-project/notification-bundle 3.13 25 | */ 26 | class MessageAdminController extends CRUDController 27 | { 28 | /** 29 | * @throws AccessDeniedException 30 | * 31 | * @return RedirectResponse 32 | */ 33 | public function batchActionPublish(ProxyQueryInterface $query) 34 | { 35 | if (false === $this->admin->isGranted('EDIT')) { 36 | throw new AccessDeniedException(); 37 | } 38 | 39 | foreach ($query->execute() as $message) { 40 | $message = $this->getMessageManager()->restart($message); 41 | 42 | $this->getBackend()->publish($message); 43 | } 44 | 45 | return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters())); 46 | } 47 | 48 | /** 49 | * @throws AccessDeniedException 50 | * 51 | * @return RedirectResponse 52 | */ 53 | public function batchActionCancelled(ProxyQueryInterface $query) 54 | { 55 | if (false === $this->admin->isGranted('EDIT')) { 56 | throw new AccessDeniedException(); 57 | } 58 | 59 | foreach ($query->execute() as $message) { 60 | $this->getMessageManager()->cancel($message); 61 | } 62 | 63 | return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters())); 64 | } 65 | 66 | /** 67 | * @return MessageManagerInterface 68 | */ 69 | protected function getMessageManager() 70 | { 71 | return $this->get('sonata.notification.manager.message'); 72 | } 73 | 74 | /** 75 | * @return BackendInterface 76 | */ 77 | protected function getBackend() 78 | { 79 | return $this->get('sonata.notification.backend'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/NotificationCompilerPass.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\DependencyInjection\Compiler; 15 | 16 | use Sonata\NotificationBundle\Event\IterateEvent; 17 | use Sonata\NotificationBundle\Event\IterationListener; 18 | use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; 19 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\DependencyInjection\Exception\RuntimeException; 22 | use Symfony\Component\DependencyInjection\Reference; 23 | 24 | /** 25 | * @final since sonata-project/notification-bundle 3.13 26 | * 27 | * @internal since sonata-project/notification-bundle 4.0 28 | */ 29 | class NotificationCompilerPass implements CompilerPassInterface 30 | { 31 | public function process(ContainerBuilder $container) 32 | { 33 | if (!$container->hasDefinition('sonata.notification.dispatcher')) { 34 | return; 35 | } 36 | 37 | $definition = $container->getDefinition('sonata.notification.dispatcher'); 38 | 39 | $informations = []; 40 | 41 | foreach ($container->findTaggedServiceIds('sonata.notification.consumer') as $id => $events) { 42 | $container->getDefinition($id)->setPublic(true); 43 | 44 | foreach ($events as $event) { 45 | $priority = $event['priority'] ?? 0; 46 | 47 | if (!isset($event['type'])) { 48 | throw new \InvalidArgumentException(sprintf( 49 | 'Service "%s" must define the "type" attribute on "sonata.notification" tags.', 50 | $id 51 | )); 52 | } 53 | 54 | if (!isset($informations[$event['type']])) { 55 | $informations[$event['type']] = []; 56 | } 57 | 58 | $informations[$event['type']][] = $id; 59 | 60 | $definition->addMethodCall( 61 | 'addListener', 62 | [ 63 | $event['type'], 64 | [new ServiceClosureArgument(new Reference($id)), 'process'], 65 | $priority, 66 | ] 67 | ); 68 | } 69 | } 70 | 71 | $container->getDefinition('sonata.notification.consumer.metadata')->replaceArgument(0, $informations); 72 | 73 | if ($container->getParameter('sonata.notification.event.iteration_listeners')) { 74 | $ids = $container->getParameter('sonata.notification.event.iteration_listeners'); 75 | 76 | foreach ($ids as $serviceId) { 77 | $definition = $container->getDefinition($serviceId); 78 | 79 | $class = new \ReflectionClass($definition->getClass()); 80 | if (!$class->implementsInterface(IterationListener::class)) { 81 | throw new RuntimeException( 82 | 'Iteration listeners must implement Sonata\NotificationBundle\Event\IterationListener' 83 | ); 84 | } 85 | 86 | $definition->addTag( 87 | 'kernel.event_listener', 88 | ['event' => IterateEvent::EVENT_NAME, 'method' => 'iterate'] 89 | ); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\DependencyInjection; 15 | 16 | use Enqueue\AmqpLib\AmqpConnectionFactory; 17 | use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; 18 | use Symfony\Component\Config\Definition\Builder\NodeDefinition; 19 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 20 | use Symfony\Component\Config\Definition\ConfigurationInterface; 21 | 22 | /** 23 | * @final since sonata-project/notification-bundle 3.13 24 | */ 25 | class Configuration implements ConfigurationInterface 26 | { 27 | public function getConfigTreeBuilder() 28 | { 29 | $treeBuilder = new TreeBuilder('sonata_notification'); 30 | 31 | $rootNode = $treeBuilder->getRootNode()->children(); 32 | 33 | $backendInfo = <<<'EOF' 34 | Other backends you can use: 35 | 36 | sonata.notification.backend.postpone 37 | sonata.notification.backend.doctrine 38 | sonata.notification.backend.rabbitmq 39 | EOF; 40 | 41 | $iterationListenersInfo = <<scalarNode('backend') 50 | ->info($backendInfo) 51 | ->defaultValue('sonata.notification.backend.runtime') 52 | ->end() 53 | ->append($this->getQueueNode()) 54 | ->arrayNode('backends') 55 | ->children() 56 | ->arrayNode('doctrine') 57 | ->children() 58 | ->scalarNode('message_manager') 59 | ->defaultValue('sonata.notification.manager.message.default') 60 | ->end() 61 | ->scalarNode('max_age') 62 | ->info('The max age in seconds') 63 | ->defaultValue(86400) 64 | ->end() 65 | ->scalarNode('pause') 66 | ->info('The delay in microseconds') 67 | ->defaultValue(500000) 68 | ->end() 69 | ->scalarNode('batch_size') 70 | ->info('The number of items on each iteration') 71 | ->defaultValue(10) 72 | ->end() 73 | ->arrayNode('states') 74 | ->info('Raising errors level') 75 | ->addDefaultsIfNotSet() 76 | ->children() 77 | ->integerNode('in_progress') 78 | ->defaultValue(10) 79 | ->end() 80 | ->integerNode('error') 81 | ->defaultValue(20) 82 | ->end() 83 | ->integerNode('open') 84 | ->defaultValue(100) 85 | ->end() 86 | ->integerNode('done') 87 | ->defaultValue(10000) 88 | ->end() 89 | ->end() 90 | ->end() 91 | ->end() 92 | ->end() 93 | ->arrayNode('rabbitmq') 94 | ->children() 95 | ->scalarNode('exchange') 96 | ->cannotBeEmpty() 97 | ->isRequired() 98 | ->end() 99 | ->arrayNode('connection') 100 | ->addDefaultsIfNotSet() 101 | ->children() 102 | ->scalarNode('host') 103 | ->defaultValue('localhost') 104 | ->end() 105 | ->scalarNode('port') 106 | ->defaultValue(5672) 107 | ->end() 108 | ->scalarNode('user') 109 | ->defaultValue('guest') 110 | ->end() 111 | ->scalarNode('pass') 112 | ->defaultValue('guest') 113 | ->end() 114 | ->scalarNode('vhost') 115 | ->defaultValue('guest') 116 | ->end() 117 | ->scalarNode('console_url') 118 | ->defaultValue('http://localhost:55672/api') 119 | ->end() 120 | ->scalarNode('factory_class') 121 | ->cannotBeEmpty() 122 | ->defaultValue(AmqpConnectionFactory::class) 123 | ->info('This option defines an AMQP connection factory to be used to establish a connection with RabbitMQ.') 124 | ->end() 125 | ->end() 126 | ->end() 127 | ->end() 128 | ->end() 129 | ->end() 130 | ->end() 131 | ->arrayNode('consumers') 132 | ->addDefaultsIfNotSet() 133 | ->children() 134 | ->booleanNode('register_default') 135 | ->info('If set to true, SwiftMailerConsumer and LoggerConsumer will be registered as services') 136 | ->defaultTrue() 137 | ->end() 138 | ->end() 139 | ->end() 140 | ->arrayNode('iteration_listeners') 141 | ->info($iterationListenersInfo) 142 | ->defaultValue([]) 143 | ->prototype('scalar')->end() 144 | ->end() 145 | ->arrayNode('class') 146 | ->addDefaultsIfNotSet() 147 | ->children() 148 | ->scalarNode('message') 149 | ->defaultValue('App\\Entity\\Message') 150 | ->end() 151 | ->end() 152 | ->end() 153 | ->arrayNode('admin') 154 | ->addDefaultsIfNotSet() 155 | ->children() 156 | ->booleanNode('enabled') 157 | ->defaultTrue() 158 | ->end() 159 | ->arrayNode('message') 160 | ->addDefaultsIfNotSet() 161 | ->children() 162 | ->scalarNode('class') 163 | ->cannotBeEmpty() 164 | ->defaultValue('Sonata\\NotificationBundle\\Admin\\MessageAdmin') 165 | ->end() 166 | ->scalarNode('controller') 167 | ->cannotBeEmpty() 168 | ->defaultValue('SonataNotificationBundle:MessageAdmin') 169 | ->end() 170 | ->scalarNode('translation') 171 | ->cannotBeEmpty() 172 | ->defaultValue('SonataNotificationBundle') 173 | ->end() 174 | ->end() 175 | ->end() 176 | ->end() 177 | ->end(); 178 | 179 | return $treeBuilder; 180 | } 181 | 182 | /** 183 | * @return ArrayNodeDefinition|NodeDefinition 184 | */ 185 | protected function getQueueNode() 186 | { 187 | $treeBuilder = new TreeBuilder('queues'); 188 | 189 | $node = $treeBuilder->getRootNode(); 190 | 191 | $queuesInfo = <<<'EOF' 192 | Example for using RabbitMQ 193 | - { queue: myQueue, recover: true, default: false, routing_key: the_routing_key, dead_letter_exchange: 'my.dead.letter.exchange' } 194 | - { queue: catchall, default: true } 195 | 196 | Example for using Doctrine 197 | - { queue: sonata_page, types: [sonata.page.create_snapshot, sonata.page.create_snapshots] } 198 | - { queue: catchall, default: true } 199 | EOF; 200 | 201 | $routingKeyInfo = <<<'EOF' 202 | Only used by RabbitMQ 203 | 204 | Direct exchange with routing_key 205 | EOF; 206 | 207 | $recoverInfo = <<<'EOF' 208 | Only used by RabbitMQ 209 | 210 | If set to true, the consumer will respond with a `basic.recover` when an exception occurs, 211 | otherwise it will not respond at all and the message will be unacknowledged 212 | EOF; 213 | 214 | $deadLetterExchangeInfo = <<<'EOF' 215 | Only used by RabbitMQ 216 | 217 | If is set, failed messages will be rejected and sent to this exchange 218 | EOF; 219 | 220 | $deadLetterRoutingKeyInfo = <<<'EOF' 221 | Only used by RabbitMQ 222 | 223 | If set, failed messages will be routed to the queue using this key by dead-letter-exchange, 224 | otherwise it will be requeued to the original queue if `dead-letter-exchange` is set. 225 | 226 | If set, the queue must be configured with this key as `routing_key`. 227 | EOF; 228 | 229 | $ttlInfo = <<<'EOF' 230 | Only used by RabbitMQ 231 | 232 | Defines the per-queue message time-to-live (milliseconds) 233 | EOF; 234 | 235 | $prefetchCountInfo = <<<'EOF' 236 | Only used by RabbitMQ 237 | 238 | Defines the number of messages which will be delivered to the customer at a time. 239 | EOF; 240 | 241 | $typesInfo = <<<'EOF' 242 | Only used by Doctrine 243 | 244 | Defines types handled by the message backend 245 | EOF; 246 | 247 | $connectionNode = $node 248 | ->info($queuesInfo) 249 | ->requiresAtLeastOneElement() 250 | ->prototype('array'); 251 | 252 | $connectionNode 253 | ->children() 254 | ->scalarNode('queue') 255 | ->info('The name of the queue') 256 | ->cannotBeEmpty() 257 | ->isRequired() 258 | ->end() 259 | ->booleanNode('default') 260 | ->info('Set the name of the default queue') 261 | ->defaultValue(false) 262 | ->end() 263 | 264 | // RabbitMQ configuration 265 | ->scalarNode('routing_key') 266 | ->info($routingKeyInfo) 267 | ->defaultValue('') 268 | ->end() 269 | ->booleanNode('recover') 270 | ->info($recoverInfo) 271 | ->defaultValue(false) 272 | ->end() 273 | ->scalarNode('dead_letter_exchange') 274 | ->info($deadLetterExchangeInfo) 275 | ->defaultValue(null) 276 | ->end() 277 | ->scalarNode('dead_letter_routing_key') 278 | ->info($deadLetterRoutingKeyInfo) 279 | ->defaultValue(null) 280 | ->end() 281 | ->integerNode('ttl') 282 | ->info($ttlInfo) 283 | ->min(0) 284 | ->defaultValue(null) 285 | ->end() 286 | ->integerNode('prefetch_count') 287 | ->info($prefetchCountInfo) 288 | ->min(0) 289 | ->max(65535) 290 | ->defaultValue(null) 291 | ->end() 292 | 293 | // Database configuration (Doctrine) 294 | ->arrayNode('types') 295 | ->info($typesInfo) 296 | ->defaultValue([]) 297 | ->prototype('scalar')->end() 298 | ->end() 299 | ->end(); 300 | 301 | return $node; 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/Entity/BaseMessage.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Entity; 15 | 16 | use Sonata\NotificationBundle\Model\Message; 17 | 18 | class BaseMessage extends Message 19 | { 20 | /** 21 | * @var int 22 | */ 23 | protected $id; 24 | 25 | /** 26 | * Override clone in order to avoid duplicating entries in Doctrine. 27 | */ 28 | public function __clone() 29 | { 30 | parent::__clone(); 31 | 32 | $this->id = null; 33 | } 34 | 35 | /** 36 | * Get id. 37 | * 38 | * @return int $id 39 | */ 40 | public function getId() 41 | { 42 | return $this->id; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Entity/MessageManager.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Entity; 15 | 16 | use Doctrine\ORM\QueryBuilder; 17 | use Sonata\DatagridBundle\Pager\Doctrine\Pager; 18 | use Sonata\DatagridBundle\Pager\PagerInterface; 19 | use Sonata\Doctrine\Entity\BaseEntityManager; 20 | use Sonata\NotificationBundle\Model\MessageInterface; 21 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 22 | 23 | /** 24 | * @final since sonata-project/notification-bundle 3.13 25 | */ 26 | class MessageManager extends BaseEntityManager implements MessageManagerInterface 27 | { 28 | public function save($entity, $andFlush = true) 29 | { 30 | //Hack for ConsumerHandlerCommand->optimize() 31 | if ($entity->getId() && !$this->getEntityManager()->getUnitOfWork()->isInIdentityMap($entity)) { 32 | $entity = $this->getEntityManager()->getUnitOfWork()->merge($entity); 33 | } 34 | 35 | parent::save($entity, $andFlush); 36 | } 37 | 38 | public function findByTypes(array $types, $state, $batchSize) 39 | { 40 | $params = []; 41 | $query = $this->prepareStateQuery($state, $types, $batchSize, $params); 42 | 43 | $query->setParameters($params); 44 | 45 | return $query->getQuery()->execute(); 46 | } 47 | 48 | public function findByAttempts(array $types, $state, $batchSize, $maxAttempts = null, $attemptDelay = 10) 49 | { 50 | $params = []; 51 | $query = $this->prepareStateQuery($state, $types, $batchSize, $params); 52 | 53 | if ($maxAttempts) { 54 | $query 55 | ->andWhere('m.restartCount < :maxAttempts') 56 | ->andWhere('m.updatedAt < :delayDate'); 57 | 58 | $params['maxAttempts'] = $maxAttempts; 59 | $now = new \DateTime(); 60 | $params['delayDate'] = $now->add(\DateInterval::createFromDateString(($attemptDelay * -1).' second')); 61 | } 62 | 63 | $query->setParameters($params); 64 | 65 | return $query->getQuery()->execute(); 66 | } 67 | 68 | public function countStates() 69 | { 70 | $tableName = $this->getEntityManager()->getClassMetadata($this->class)->table['name']; 71 | 72 | $stm = $this->getConnection()->query( 73 | sprintf('SELECT state, count(state) as cnt FROM %s GROUP BY state', $tableName) 74 | ); 75 | 76 | $states = [ 77 | MessageInterface::STATE_DONE => 0, 78 | MessageInterface::STATE_ERROR => 0, 79 | MessageInterface::STATE_IN_PROGRESS => 0, 80 | MessageInterface::STATE_OPEN => 0, 81 | ]; 82 | 83 | while ($data = $stm->fetch()) { 84 | $states[$data['state']] = $data['cnt']; 85 | } 86 | 87 | return $states; 88 | } 89 | 90 | public function cleanup($maxAge) 91 | { 92 | $tableName = $this->getEntityManager()->getClassMetadata($this->class)->table['name']; 93 | 94 | $date = new \DateTime('now'); 95 | $date->sub(new \DateInterval(sprintf('PT%sS', $maxAge))); 96 | 97 | $qb = $this->getRepository()->createQueryBuilder('message') 98 | ->delete() 99 | ->where('message.state = :state') 100 | ->andWhere('message.completedAt < :date') 101 | ->setParameter('state', MessageInterface::STATE_DONE) 102 | ->setParameter('date', $date); 103 | 104 | $qb->getQuery()->execute(); 105 | } 106 | 107 | public function cancel(MessageInterface $message, $force = false) 108 | { 109 | if (($message->isRunning() || $message->isError()) && !$force) { 110 | return; 111 | } 112 | 113 | $message->setState(MessageInterface::STATE_CANCELLED); 114 | 115 | $this->save($message); 116 | } 117 | 118 | public function restart(MessageInterface $message) 119 | { 120 | if ($message->isOpen() || $message->isRunning() || $message->isCancelled()) { 121 | return; 122 | } 123 | 124 | $this->cancel($message, true); 125 | 126 | $newMessage = clone $message; 127 | $newMessage->setRestartCount($message->getRestartCount() + 1); 128 | $newMessage->setType($message->getType()); 129 | 130 | return $newMessage; 131 | } 132 | 133 | public function getPager(array $criteria, int $page, int $limit = 10, array $sort = []): PagerInterface 134 | { 135 | $query = $this->getRepository() 136 | ->createQueryBuilder('m') 137 | ->select('m'); 138 | 139 | $fields = $this->getEntityManager()->getClassMetadata($this->class)->getFieldNames(); 140 | foreach ($sort as $field => $direction) { 141 | if (!\in_array($field, $fields, true)) { 142 | throw new \RuntimeException(sprintf("Invalid sort field '%s' in '%s' class", $field, $this->class)); 143 | } 144 | } 145 | if (0 === \count($sort)) { 146 | $sort = ['type' => 'ASC']; 147 | } 148 | foreach ($sort as $field => $direction) { 149 | $query->orderBy(sprintf('m.%s', $field), strtoupper($direction)); 150 | } 151 | 152 | $parameters = []; 153 | 154 | if (isset($criteria['type'])) { 155 | $query->andWhere('m.type = :type'); 156 | $parameters['type'] = $criteria['type']; 157 | } 158 | 159 | if (isset($criteria['state'])) { 160 | $query->andWhere('m.state = :state'); 161 | $parameters['state'] = $criteria['state']; 162 | } 163 | 164 | $query->setParameters($parameters); 165 | 166 | return Pager::create($query, $limit, $page); 167 | } 168 | 169 | /** 170 | * @param int $state 171 | * @param array $types 172 | * @param int $batchSize 173 | * @param array $parameters 174 | * 175 | * @return QueryBuilder 176 | */ 177 | protected function prepareStateQuery($state, $types, $batchSize, &$parameters) 178 | { 179 | $query = $this->getRepository() 180 | ->createQueryBuilder('m') 181 | ->where('m.state = :state') 182 | ->orderBy('m.createdAt'); 183 | 184 | $parameters['state'] = $state; 185 | 186 | if (\count($types) > 0) { 187 | if (\array_key_exists('exclude', $types) || \array_key_exists('include', $types)) { 188 | if (\array_key_exists('exclude', $types)) { 189 | $query->andWhere('m.type NOT IN (:exclude)'); 190 | $parameters['exclude'] = $types['exclude']; 191 | } 192 | 193 | if (\array_key_exists('include', $types)) { 194 | $query->andWhere('m.type IN (:include)'); 195 | $parameters['include'] = $types['include']; 196 | } 197 | } else { // BC 198 | $query->andWhere('m.type IN (:types)'); 199 | $parameters['types'] = $types; 200 | } 201 | } 202 | 203 | $query->setMaxResults($batchSize); 204 | 205 | return $query; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/Event/DoctrineBackendOptimizeListener.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Event; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\Registry; 17 | use Doctrine\Persistence\ManagerRegistry; 18 | 19 | /** 20 | * Doctrine context optimizer 21 | * Used with doctrine backend to clear context taking care of the batch iterations. 22 | * 23 | * @author Kevin Nedelec 24 | * 25 | * @final since sonata-project/notification-bundle 3.13 26 | */ 27 | class DoctrineBackendOptimizeListener implements IterationListener 28 | { 29 | /** 30 | * @var Registry 31 | */ 32 | protected $doctrine; 33 | 34 | public function __construct(ManagerRegistry $doctrine) 35 | { 36 | $this->doctrine = $doctrine; 37 | } 38 | 39 | public function iterate(IterateEvent $event) 40 | { 41 | if (!method_exists($event->getIterator(), 'isBufferEmpty')) { 42 | throw new \LogicException('You can\'t use DoctrineOptimizeListener with this iterator'); 43 | } 44 | 45 | if ($event->getIterator()->isBufferEmpty()) { 46 | $this->doctrine->getManager()->getUnitOfWork()->clear(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Event/DoctrineOptimizeListener.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Event; 15 | 16 | use Doctrine\Persistence\ManagerRegistry; 17 | 18 | /** 19 | * Doctrine context optimizer 20 | * Used to clear the doctrine context on each command iteration. 21 | * Do not use with doctrine backend, use DoctrineBackendOptimizeListener instead. 22 | * 23 | * @author Kevin Nedelec 24 | * 25 | * @final since sonata-project/notification-bundle 3.13 26 | */ 27 | class DoctrineOptimizeListener implements IterationListener 28 | { 29 | /** 30 | * @var ManagerRegistry 31 | */ 32 | protected $doctrine; 33 | 34 | public function __construct(ManagerRegistry $doctrine) 35 | { 36 | $this->doctrine = $doctrine; 37 | } 38 | 39 | public function iterate(IterateEvent $event) 40 | { 41 | foreach ($this->doctrine->getManagers() as $name => $manager) { 42 | if (!$manager->isOpen()) { 43 | throw new \RuntimeException(sprintf('The doctrine manager: %s is closed', $name)); 44 | } 45 | 46 | $manager->getUnitOfWork()->clear(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Event/IterateEvent.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Event; 15 | 16 | use Sonata\NotificationBundle\Backend\BackendInterface; 17 | use Sonata\NotificationBundle\Iterator\MessageIteratorInterface; 18 | use Sonata\NotificationBundle\Model\MessageInterface; 19 | use Symfony\Contracts\EventDispatcher\Event; 20 | 21 | /** 22 | * Event for ConsumerHandlerCommand iterations event. 23 | * 24 | * @author Kevin Nedelec 25 | * 26 | * @final since sonata-project/notification-bundle 3.13 27 | */ 28 | class IterateEvent extends Event 29 | { 30 | public const EVENT_NAME = 'sonata.notification.event.message_iterate_event'; 31 | 32 | /** 33 | * @var MessageIteratorInterface 34 | */ 35 | protected $iterator; 36 | 37 | /** 38 | * @var BackendInterface 39 | */ 40 | protected $backend; 41 | 42 | /** 43 | * @var MessageInterface 44 | */ 45 | protected $message; 46 | 47 | /** 48 | * @param BackendInterface $backend 49 | * @param MessageInterface $message 50 | */ 51 | public function __construct(MessageIteratorInterface $iterator, ?BackendInterface $backend = null, ?MessageInterface $message = null) 52 | { 53 | $this->iterator = $iterator; 54 | $this->backend = $backend; 55 | $this->message = $message; 56 | } 57 | 58 | /** 59 | * @return BackendInterface 60 | */ 61 | public function getBackend() 62 | { 63 | return $this->backend; 64 | } 65 | 66 | /** 67 | * @return MessageIteratorInterface 68 | */ 69 | public function getIterator() 70 | { 71 | return $this->iterator; 72 | } 73 | 74 | /** 75 | * @return MessageInterface 76 | */ 77 | public function getMessage() 78 | { 79 | return $this->message; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Event/IterationListener.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Event; 15 | 16 | /** 17 | * Listener for ConsumerHandlerCommand iterations event. 18 | * 19 | * @author Kevin Nedelec 20 | */ 21 | interface IterationListener 22 | { 23 | /** 24 | * @return void 25 | */ 26 | public function iterate(IterateEvent $event); 27 | } 28 | -------------------------------------------------------------------------------- /src/Exception/BackendNotFoundException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Exception; 15 | 16 | /** 17 | * @final since sonata-project/notification-bundle 3.13 18 | */ 19 | class BackendNotFoundException extends \RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/HandlingException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Exception; 15 | 16 | /** 17 | * @final since sonata-project/notification-bundle 3.13 18 | */ 19 | class HandlingException extends \RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/InvalidParameterException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Exception; 15 | 16 | use Sonata\CoreBundle\Exception\InvalidParameterException as CoreBundleException; 17 | 18 | if (class_exists(CoreBundleException::class)) { 19 | class InvalidParameterException extends CoreBundleException 20 | { 21 | } 22 | } else { 23 | /** 24 | * @final since sonata-project/notification-bundle 3.13 25 | */ 26 | class InvalidParameterException extends \RuntimeException 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Form/Type/MessageSerializationType.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Form\Type; 15 | 16 | use Sonata\Form\Type\BaseDoctrineORMSerializationType; 17 | 18 | /** 19 | * @final since sonata-project/notification-bundle 3.13 20 | */ 21 | class MessageSerializationType extends BaseDoctrineORMSerializationType 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Iterator/AMQPMessageIterator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Iterator; 15 | 16 | use Interop\Amqp\AmqpConsumer; 17 | use PhpAmqpLib\Channel\AMQPChannel; 18 | use PhpAmqpLib\Message\AMQPMessage; 19 | use Sonata\NotificationBundle\Model\Message; 20 | 21 | /** 22 | * @final since sonata-project/notification-bundle 3.13 23 | */ 24 | class AMQPMessageIterator implements MessageIteratorInterface 25 | { 26 | /** 27 | * @deprecated since 3.2, will be removed in 4.x 28 | * 29 | * @var AMQPChannel 30 | */ 31 | protected $channel; 32 | 33 | /** 34 | * @var mixed 35 | */ 36 | protected $message; 37 | 38 | /** 39 | * @deprecated since 3.2, will be removed in 4.x 40 | * 41 | * @var AMQPMessage 42 | */ 43 | protected $AMQMessage; 44 | 45 | /** 46 | * @deprecated since 3.2, will be removed in 4.x 47 | * 48 | * @var string 49 | */ 50 | protected $queue; 51 | 52 | /** 53 | * @var int 54 | */ 55 | protected $counter; 56 | 57 | /** 58 | * @var \Interop\Amqp\AmqpMessage 59 | */ 60 | private $interopMessage; 61 | 62 | /** 63 | * @var int 64 | */ 65 | private $timeout; 66 | 67 | /** 68 | * @var AmqpConsumer 69 | */ 70 | private $consumer; 71 | 72 | /** 73 | * @var bool 74 | */ 75 | private $isValid; 76 | 77 | public function __construct(AMQPChannel $channel, AmqpConsumer $consumer) 78 | { 79 | $this->consumer = $consumer; 80 | $this->counter = 0; 81 | $this->timeout = 0; 82 | $this->isValid = true; 83 | 84 | $this->channel = $channel; 85 | $this->queue = $consumer->getQueue()->getQueueName(); 86 | } 87 | 88 | public function current() 89 | { 90 | return $this->message; 91 | } 92 | 93 | public function next() 94 | { 95 | $this->isValid = false; 96 | 97 | if ($amqpMessage = $this->consumer->receive($this->timeout)) { 98 | $this->AMQMessage = $this->convertToAmqpLibMessage($amqpMessage); 99 | 100 | $data = json_decode($amqpMessage->getBody(), true); 101 | $data['body']['interopMessage'] = $amqpMessage; 102 | 103 | // @deprecated 104 | $data['body']['AMQMessage'] = $this->AMQMessage; 105 | 106 | $message = new Message(); 107 | $message->setBody($data['body']); 108 | $message->setType($data['type']); 109 | $message->setState($data['state']); 110 | $this->message = $message; 111 | 112 | ++$this->counter; 113 | $this->isValid = true; 114 | } 115 | } 116 | 117 | public function key() 118 | { 119 | $this->counter; 120 | } 121 | 122 | public function valid() 123 | { 124 | return $this->isValid; 125 | } 126 | 127 | public function rewind() 128 | { 129 | $this->isValid = true; 130 | $this->next(); 131 | } 132 | 133 | /** 134 | * @deprecated since 3.2, will be removed in 4.x 135 | * 136 | * @return AMQPMessage 137 | */ 138 | private function convertToAmqpLibMessage(\Interop\Amqp\AmqpMessage $amqpMessage) 139 | { 140 | $amqpLibProperties = $amqpMessage->getHeaders(); 141 | $amqpLibProperties['application_headers'] = $amqpMessage->getProperties(); 142 | 143 | $amqpLibMessage = new AMQPMessage($amqpMessage->getBody(), $amqpLibProperties); 144 | $amqpLibMessage->delivery_info = [ 145 | 'consumer_tag' => $this->consumer->getConsumerTag(), 146 | 'delivery_tag' => $amqpMessage->getDeliveryTag(), 147 | 'redelivered' => $amqpMessage->isRedelivered(), 148 | 'routing_key' => $amqpMessage->getRoutingKey(), 149 | 'channel' => $this->channel, 150 | ]; 151 | 152 | return $amqpLibMessage; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Iterator/ErroneousMessageIterator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Iterator; 15 | 16 | use Sonata\NotificationBundle\Model\MessageInterface; 17 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 18 | 19 | /** 20 | * @final since sonata-project/notification-bundle 3.13 21 | */ 22 | class ErroneousMessageIterator extends MessageManagerMessageIterator 23 | { 24 | /** 25 | * @var int 26 | */ 27 | protected $maxAttempts; 28 | 29 | /** 30 | * @var int 31 | */ 32 | protected $attemptDelay; 33 | 34 | /** 35 | * @param array $types 36 | * @param int $pause 37 | * @param int $batchSize 38 | * @param int $maxAttempts 39 | * @param int $attemptDelay 40 | */ 41 | public function __construct(MessageManagerInterface $messageManager, $types = [], $pause = 500000, $batchSize = 10, $maxAttempts = 5, $attemptDelay = 10) 42 | { 43 | parent::__construct($messageManager, $types, $pause, $batchSize); 44 | 45 | $this->maxAttempts = $maxAttempts; 46 | $this->attemptDelay = $attemptDelay; 47 | } 48 | 49 | /** 50 | * Find messages in error. 51 | * 52 | * @param array $types 53 | * 54 | * @return mixed 55 | */ 56 | protected function findNextMessages($types) 57 | { 58 | return $this->messageManager->findByAttempts( 59 | $this->types, 60 | MessageInterface::STATE_ERROR, 61 | $this->batchSize, 62 | $this->maxAttempts, 63 | $this->attemptDelay 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Iterator/IteratorProxyMessageIterator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Iterator; 15 | 16 | /** 17 | * @author Toni Uebernickel 18 | * 19 | * @final since sonata-project/notification-bundle 3.13 20 | */ 21 | class IteratorProxyMessageIterator implements MessageIteratorInterface 22 | { 23 | /** 24 | * @var \Iterator 25 | */ 26 | protected $iterator; 27 | 28 | public function __construct(\Iterator $iterator) 29 | { 30 | $this->iterator = $iterator; 31 | } 32 | 33 | public function current() 34 | { 35 | return $this->iterator->current(); 36 | } 37 | 38 | public function next() 39 | { 40 | $this->iterator->next(); 41 | } 42 | 43 | public function key() 44 | { 45 | return $this->iterator->key(); 46 | } 47 | 48 | public function valid() 49 | { 50 | return $this->iterator->valid(); 51 | } 52 | 53 | public function rewind() 54 | { 55 | $this->iterator->rewind(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Iterator/MessageIteratorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Iterator; 15 | 16 | interface MessageIteratorInterface extends \Iterator 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Iterator/MessageManagerMessageIterator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Iterator; 15 | 16 | use Sonata\NotificationBundle\Model\MessageInterface; 17 | use Sonata\NotificationBundle\Model\MessageManagerInterface; 18 | 19 | class MessageManagerMessageIterator implements MessageIteratorInterface 20 | { 21 | /** 22 | * @var MessageManagerInterface 23 | */ 24 | protected $messageManager; 25 | 26 | /** 27 | * @var int 28 | */ 29 | protected $counter; 30 | 31 | /** 32 | * @var mixed 33 | */ 34 | protected $current; 35 | 36 | /** 37 | * @var array 38 | */ 39 | protected $types; 40 | 41 | /** 42 | * @var int 43 | */ 44 | protected $batchSize; 45 | 46 | /** 47 | * @var array 48 | */ 49 | protected $buffer = []; 50 | 51 | /** 52 | * @var int 53 | */ 54 | protected $pause; 55 | 56 | /** 57 | * @param array $types 58 | * @param int $pause 59 | * @param int $batchSize 60 | */ 61 | public function __construct(MessageManagerInterface $messageManager, $types = [], $pause = 500000, $batchSize = 10) 62 | { 63 | $this->messageManager = $messageManager; 64 | $this->counter = 0; 65 | $this->pause = $pause; 66 | $this->types = $types; 67 | $this->batchSize = $batchSize; 68 | } 69 | 70 | public function current() 71 | { 72 | return $this->current; 73 | } 74 | 75 | public function next() 76 | { 77 | $this->setCurrent(); 78 | ++$this->counter; 79 | } 80 | 81 | public function key() 82 | { 83 | return $this->counter; 84 | } 85 | 86 | public function valid() 87 | { 88 | return true; 89 | } 90 | 91 | public function rewind() 92 | { 93 | $this->setCurrent(); 94 | } 95 | 96 | /** 97 | * Return true if the internal buffer is empty. 98 | * 99 | * @return bool 100 | */ 101 | public function isBufferEmpty() 102 | { 103 | return 0 === \count($this->buffer); 104 | } 105 | 106 | /** 107 | * Assign current pointer a message. 108 | */ 109 | protected function setCurrent() 110 | { 111 | if (0 === \count($this->buffer)) { 112 | $this->bufferize($this->types); 113 | } 114 | 115 | $this->current = array_pop($this->buffer); 116 | } 117 | 118 | /** 119 | * Fill the inner messages buffer. 120 | * 121 | * @param array $types 122 | */ 123 | protected function bufferize($types = []) 124 | { 125 | while (true) { 126 | $this->buffer = $this->findNextMessages($types); 127 | 128 | if (\count($this->buffer) > 0) { 129 | break; 130 | } 131 | 132 | usleep($this->pause); 133 | } 134 | } 135 | 136 | /** 137 | * Find open messages. 138 | * 139 | * @param array $types 140 | * 141 | * @return mixed 142 | */ 143 | protected function findNextMessages($types) 144 | { 145 | return $this->messageManager->findByTypes($types, MessageInterface::STATE_OPEN, $this->batchSize); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Model/Message.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Model; 15 | 16 | class Message implements MessageInterface 17 | { 18 | /** 19 | * @var string 20 | */ 21 | protected $type; 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected $body; 27 | 28 | /** 29 | * @var int 30 | */ 31 | protected $state; 32 | 33 | /** 34 | * @var int 35 | */ 36 | protected $restartCount = 0; 37 | 38 | /** 39 | * @var string 40 | */ 41 | protected $group; 42 | 43 | /** 44 | * @var \DateTime 45 | */ 46 | protected $createdAt; 47 | 48 | /** 49 | * @var \DateTime 50 | */ 51 | protected $updatedAt; 52 | 53 | /** 54 | * @var \DateTime 55 | */ 56 | protected $startedAt; 57 | 58 | /** 59 | * @var \DateTime 60 | */ 61 | protected $completedAt; 62 | 63 | public function __construct() 64 | { 65 | $this->createdAt = new \DateTime(); 66 | $this->updatedAt = new \DateTime(); 67 | $this->state = self::STATE_OPEN; 68 | } 69 | 70 | public function __clone() 71 | { 72 | $this->state = self::STATE_OPEN; 73 | $this->startedAt = null; 74 | $this->completedAt = null; 75 | 76 | $this->createdAt = new \DateTime(); 77 | $this->updatedAt = new \DateTime(); 78 | } 79 | 80 | public function setBody(array $body) 81 | { 82 | $this->body = $body; 83 | } 84 | 85 | public function getBody() 86 | { 87 | return $this->body; 88 | } 89 | 90 | public function getValue($names, $default = null) 91 | { 92 | if (!\is_array($names)) { 93 | $names = [$names]; 94 | } 95 | 96 | $body = $this->getBody(); 97 | foreach ($names as $name) { 98 | if (!isset($body[$name])) { 99 | return $default; 100 | } 101 | 102 | $body = $body[$name]; 103 | } 104 | 105 | return $body; 106 | } 107 | 108 | public function setCompletedAt(?\DateTime $completedAt = null) 109 | { 110 | $this->completedAt = $completedAt; 111 | } 112 | 113 | public function getCompletedAt() 114 | { 115 | return $this->completedAt; 116 | } 117 | 118 | public function setCreatedAt(?\DateTime $createdAt = null) 119 | { 120 | $this->createdAt = $createdAt; 121 | } 122 | 123 | public function getCreatedAt() 124 | { 125 | return $this->createdAt; 126 | } 127 | 128 | public function setGroup($group) 129 | { 130 | $this->group = $group; 131 | } 132 | 133 | public function getGroup() 134 | { 135 | return $this->group; 136 | } 137 | 138 | public function setType($type) 139 | { 140 | $this->type = $type; 141 | } 142 | 143 | public function getType() 144 | { 145 | return $this->type; 146 | } 147 | 148 | public function setState($state) 149 | { 150 | $this->state = $state; 151 | } 152 | 153 | public function getState() 154 | { 155 | return $this->state; 156 | } 157 | 158 | public function setRestartCount($restartCount) 159 | { 160 | $this->restartCount = $restartCount; 161 | } 162 | 163 | public function getRestartCount() 164 | { 165 | return $this->restartCount; 166 | } 167 | 168 | public function setUpdatedAt(?\DateTime $updatedAt = null) 169 | { 170 | $this->updatedAt = $updatedAt; 171 | } 172 | 173 | public function getUpdatedAt() 174 | { 175 | return $this->updatedAt; 176 | } 177 | 178 | /** 179 | * @return string[] 180 | */ 181 | public static function getStateList() 182 | { 183 | return [ 184 | self::STATE_OPEN => 'open', 185 | self::STATE_IN_PROGRESS => 'in_progress', 186 | self::STATE_DONE => 'done', 187 | self::STATE_ERROR => 'error', 188 | self::STATE_CANCELLED => 'cancelled', 189 | ]; 190 | } 191 | 192 | public function setStartedAt(?\DateTime $startedAt = null) 193 | { 194 | $this->startedAt = $startedAt; 195 | } 196 | 197 | public function getStartedAt() 198 | { 199 | return $this->startedAt; 200 | } 201 | 202 | public function getStateName() 203 | { 204 | $list = self::getStateList(); 205 | 206 | return isset($list[$this->getState()]) ? $list[$this->getState()] : ''; 207 | } 208 | 209 | public function isRunning() 210 | { 211 | return self::STATE_IN_PROGRESS === $this->state; 212 | } 213 | 214 | public function isCancelled() 215 | { 216 | return self::STATE_CANCELLED === $this->state; 217 | } 218 | 219 | public function isError() 220 | { 221 | return self::STATE_ERROR === $this->state; 222 | } 223 | 224 | public function isOpen() 225 | { 226 | return self::STATE_OPEN === $this->state; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/Model/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Model; 15 | 16 | interface MessageInterface 17 | { 18 | public const STATE_OPEN = 0; 19 | public const STATE_IN_PROGRESS = 1; 20 | public const STATE_DONE = 2; 21 | public const STATE_ERROR = -1; 22 | public const STATE_CANCELLED = -2; 23 | 24 | /** 25 | * @return void 26 | */ 27 | public function setBody(array $body); 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function getBody(); 33 | 34 | /** 35 | * @param array|string $names 36 | * @param mixed $default 37 | * 38 | * @return mixed 39 | */ 40 | public function getValue($names, $default = null); 41 | 42 | /** 43 | * @param \DateTime $completedAt 44 | */ 45 | public function setCompletedAt(?\DateTime $completedAt = null); 46 | 47 | /** 48 | * @return \DateTime 49 | */ 50 | public function getCompletedAt(); 51 | 52 | /** 53 | * @param \DateTime $createdAt 54 | */ 55 | public function setCreatedAt(?\DateTime $createdAt = null); 56 | 57 | /** 58 | * @return \DateTime 59 | */ 60 | public function getCreatedAt(); 61 | 62 | /** 63 | * @param string $group 64 | */ 65 | public function setGroup($group); 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getGroup(); 71 | 72 | /** 73 | * @param string $type 74 | */ 75 | public function setType($type); 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function getType(); 81 | 82 | /** 83 | * @param int $state 84 | */ 85 | public function setState($state); 86 | 87 | /** 88 | * @return int 89 | */ 90 | public function getState(); 91 | 92 | /** 93 | * @param int $restartCount 94 | */ 95 | public function setRestartCount($restartCount); 96 | 97 | /** 98 | * @return int 99 | */ 100 | public function getRestartCount(); 101 | 102 | /** 103 | * @param \DateTime $updatedAt 104 | */ 105 | public function setUpdatedAt(?\DateTime $updatedAt = null); 106 | 107 | /** 108 | * @return \DateTime 109 | */ 110 | public function getUpdatedAt(); 111 | 112 | /** 113 | * @param \DateTime $startedAt 114 | */ 115 | public function setStartedAt(?\DateTime $startedAt = null); 116 | 117 | /** 118 | * @return \DateTime 119 | */ 120 | public function getStartedAt(); 121 | 122 | /** 123 | * @return string 124 | */ 125 | public function getStateName(); 126 | 127 | /** 128 | * @return bool 129 | */ 130 | public function isRunning(); 131 | 132 | /** 133 | * @return bool 134 | */ 135 | public function isCancelled(); 136 | 137 | /** 138 | * @return bool 139 | */ 140 | public function isError(); 141 | 142 | /** 143 | * @return bool 144 | */ 145 | public function isOpen(); 146 | } 147 | -------------------------------------------------------------------------------- /src/Model/MessageManagerInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Model; 15 | 16 | use Sonata\DatagridBundle\Pager\PageableInterface; 17 | use Sonata\Doctrine\Model\ManagerInterface; 18 | 19 | interface MessageManagerInterface extends ManagerInterface, PageableInterface 20 | { 21 | /** 22 | * @return array 23 | */ 24 | public function countStates(); 25 | 26 | /** 27 | * @param int $maxAge 28 | */ 29 | public function cleanup($maxAge); 30 | 31 | /** 32 | * Cancels a given Message. 33 | */ 34 | public function cancel(MessageInterface $message); 35 | 36 | /** 37 | * Restarts a given message (cancels it and returns a new one, ready for publication). 38 | * 39 | * @return MessageInterface $message 40 | */ 41 | public function restart(MessageInterface $message); 42 | 43 | /** 44 | * @param int $state 45 | * @param int $batchSize 46 | * 47 | * @return MessageInterface[] 48 | */ 49 | public function findByTypes(array $types, $state, $batchSize); 50 | 51 | /** 52 | * @param int $state 53 | * @param int $batchSize 54 | * @param int|null $maxAttempts 55 | * @param int $attemptDelay 56 | * 57 | * @return mixed 58 | */ 59 | public function findByAttempts(array $types, $state, $batchSize, $maxAttempts = null, $attemptDelay = 10); 60 | } 61 | -------------------------------------------------------------------------------- /src/Resources/config/admin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sonata.notification.admin.message.entity% 8 | %sonata.notification.admin.message.controller% 9 | 10 | %sonata.notification.admin.message.translation_domain% 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Resources/config/api_controllers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Resources/config/api_controllers_legacy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Resources/config/api_form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | sonata_notification_api_form_message 9 | %sonata.notification.admin.message.entity% 10 | sonata_api_write 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resources/config/backend.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Resources/config/checkmonitor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/command.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Resources/config/consumer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Resources/config/core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Resources/config/core_legacy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/default_consumers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine/BaseMessage.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine_orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %sonata.notification.message.class% 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Resources/config/event.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/config/routing/api.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | json|xml|html 5 | 6 | 7 | json|xml|html 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/routing/api_nelmio_v3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | json|xml|html 5 | 6 | 7 | json|xml|html 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %sonata.notification.message.class% 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/serializer/Model.Message.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resources/config/validation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2013 thomas.rabaix@sonata-project.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.cs.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Oznámení 8 | 9 | 10 | notifications 11 | Oznámení 12 | 13 | 14 | breadcrumb.link_message_list 15 | Zprávy 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Typ 24 | 25 | 26 | list.label_created_at 27 | Vytvořeno 28 | 29 | 30 | list.label_started_at 31 | Zahájené 32 | 33 | 34 | list.label_completed_at 35 | Dokončené 36 | 37 | 38 | list.label_get_state_name 39 | Stav 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Typ 48 | 49 | 50 | show.label_created_at 51 | Vytvořeno 52 | 53 | 54 | show.label_started_at 55 | Zahájené 56 | 57 | 58 | show.label_completed_at 59 | Dokončené 60 | 61 | 62 | show.label_get_state_name 63 | Stav 64 | 65 | 66 | breadcrumb.link_message_show 67 | Zpráva 68 | 69 | 70 | show.label_body 71 | Náklad 72 | 73 | 74 | filter.label_type 75 | Typ 76 | 77 | 78 | filter.label_state 79 | Stav 80 | 81 | 82 | batch.message_publish 83 | Publikování zpráv 84 | 85 | 86 | batch.message_cancelled 87 | Zrušení zpráv 88 | 89 | 90 | list.label_restart_count 91 | Znovu # 92 | 93 | 94 | show.label_restart_count 95 | Znovu # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.de.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Systemmeldung 8 | 9 | 10 | notifications 11 | Systemmeldungen 12 | 13 | 14 | breadcrumb.link_message_list 15 | Systemmeldungen 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Typ 24 | 25 | 26 | list.label_created_at 27 | Erstellt am 28 | 29 | 30 | list.label_started_at 31 | Gestartet am 32 | 33 | 34 | list.label_completed_at 35 | Fertig am 36 | 37 | 38 | list.label_get_state_name 39 | Status 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Typ 48 | 49 | 50 | show.label_created_at 51 | Erstellt am 52 | 53 | 54 | show.label_started_at 55 | Gestartet am 56 | 57 | 58 | show.label_completed_at 59 | Fertig am 60 | 61 | 62 | show.label_get_state_name 63 | Status 64 | 65 | 66 | breadcrumb.link_message_show 67 | Systemmeldung 68 | 69 | 70 | show.label_body 71 | Auslastung 72 | 73 | 74 | filter.label_type 75 | Type 76 | 77 | 78 | filter.label_state 79 | Status 80 | 81 | 82 | batch.message_publish 83 | Öffentlich 84 | 85 | 86 | batch.message_cancelled 87 | Abbruchmeldung 88 | 89 | 90 | list.label_restart_count 91 | Neustarts # 92 | 93 | 94 | show.label_restart_count 95 | Neustarts # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.en.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Notification 8 | 9 | 10 | notifications 11 | Notifications 12 | 13 | 14 | breadcrumb.link_message_list 15 | Messages 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Type 24 | 25 | 26 | list.label_created_at 27 | Created At 28 | 29 | 30 | list.label_started_at 31 | Started At 32 | 33 | 34 | list.label_completed_at 35 | Completed At 36 | 37 | 38 | list.label_get_state_name 39 | State 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Type 48 | 49 | 50 | show.label_created_at 51 | Created At 52 | 53 | 54 | show.label_started_at 55 | Started At 56 | 57 | 58 | show.label_completed_at 59 | Completed At 60 | 61 | 62 | show.label_get_state_name 63 | State 64 | 65 | 66 | breadcrumb.link_message_show 67 | Message 68 | 69 | 70 | show.label_body 71 | Payload 72 | 73 | 74 | filter.label_type 75 | Type 76 | 77 | 78 | filter.label_state 79 | State 80 | 81 | 82 | batch.message_publish 83 | Publish messages 84 | 85 | 86 | batch.message_cancelled 87 | Cancel messages 88 | 89 | 90 | list.label_restart_count 91 | Restart # 92 | 93 | 94 | show.label_restart_count 95 | Restart # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.es.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Notification 8 | 9 | 10 | notifications 11 | Notifications 12 | 13 | 14 | breadcrumb.link_message_list 15 | Mensajes 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Tipo 24 | 25 | 26 | list.label_created_at 27 | Creado el 28 | 29 | 30 | list.label_started_at 31 | Iniciado el 32 | 33 | 34 | list.label_completed_at 35 | Completado el 36 | 37 | 38 | list.label_get_state_name 39 | Estado 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Tipo 48 | 49 | 50 | show.label_created_at 51 | Creado el 52 | 53 | 54 | show.label_started_at 55 | Iniciado el 56 | 57 | 58 | show.label_completed_at 59 | Completado el 60 | 61 | 62 | show.label_get_state_name 63 | Estado 64 | 65 | 66 | breadcrumb.link_message_show 67 | Mensaje 68 | 69 | 70 | show.label_body 71 | Carga 72 | 73 | 74 | filter.label_type 75 | Tipo 76 | 77 | 78 | filter.label_state 79 | Estado 80 | 81 | 82 | batch.message_publish 83 | Mensajes publicados 84 | 85 | 86 | batch.message_cancelled 87 | Mensajes Cancelados 88 | 89 | 90 | list.label_restart_count 91 | Reinicio # 92 | 93 | 94 | show.label_restart_count 95 | Reinicio # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.fr.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Notification 8 | 9 | 10 | notifications 11 | Notifications 12 | 13 | 14 | breadcrumb.link_message_list 15 | Messages 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Type 24 | 25 | 26 | list.label_created_at 27 | Date de création 28 | 29 | 30 | list.label_started_at 31 | Date de début 32 | 33 | 34 | list.label_completed_at 35 | Date de fin 36 | 37 | 38 | list.label_get_state_name 39 | Etat 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Type 48 | 49 | 50 | show.label_created_at 51 | Date de création 52 | 53 | 54 | show.label_started_at 55 | Date de début 56 | 57 | 58 | show.label_completed_at 59 | Date de fin 60 | 61 | 62 | show.label_get_state_name 63 | Etat 64 | 65 | 66 | breadcrumb.link_message_show 67 | Message 68 | 69 | 70 | show.label_body 71 | Payload 72 | 73 | 74 | filter.label_type 75 | Type 76 | 77 | 78 | filter.label_state 79 | Etat 80 | 81 | 82 | batch.message_publish 83 | Publier les messages 84 | 85 | 86 | batch.message_cancelled 87 | Annuler les messages 88 | 89 | 90 | list.label_restart_count 91 | Restart # 92 | 93 | 94 | show.label_restart_count 95 | Restart # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.it.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Notification 8 | 9 | 10 | notifications 11 | Notifiche 12 | 13 | 14 | breadcrumb.link_message_list 15 | Messaggi 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Tipo 24 | 25 | 26 | list.label_created_at 27 | Creato il 28 | 29 | 30 | list.label_started_at 31 | Avviato il 32 | 33 | 34 | list.label_completed_at 35 | Completato il 36 | 37 | 38 | list.label_get_state_name 39 | Stato 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Tipo 48 | 49 | 50 | show.label_created_at 51 | Creato il 52 | 53 | 54 | show.label_started_at 55 | Avviato il 56 | 57 | 58 | show.label_completed_at 59 | Completato il 60 | 61 | 62 | show.label_get_state_name 63 | Stato 64 | 65 | 66 | breadcrumb.link_message_show 67 | Messaggio 68 | 69 | 70 | show.label_body 71 | Corpo 72 | 73 | 74 | filter.label_type 75 | Tipo 76 | 77 | 78 | filter.label_state 79 | Stato 80 | 81 | 82 | batch.message_publish 83 | Pubblica 84 | 85 | 86 | batch.message_cancelled 87 | Elimina 88 | 89 | 90 | list.label_restart_count 91 | Riavvii # 92 | 93 | 94 | show.label_restart_count 95 | Riavvii # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.nl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Systeem berichten 8 | 9 | 10 | notifications 11 | Systeem berichten 12 | 13 | 14 | breadcrumb.link_message_list 15 | Systeem berichten 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Type 24 | 25 | 26 | list.label_created_at 27 | Aangemaakt op 28 | 29 | 30 | list.label_started_at 31 | Gestart op 32 | 33 | 34 | list.label_completed_at 35 | Afgerond op 36 | 37 | 38 | list.label_get_state_name 39 | Status 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Type 48 | 49 | 50 | show.label_created_at 51 | Aangemaakt op 52 | 53 | 54 | show.label_started_at 55 | Gestart op 56 | 57 | 58 | show.label_completed_at 59 | Afgerond op 60 | 61 | 62 | show.label_get_state_name 63 | Status 64 | 65 | 66 | breadcrumb.link_message_show 67 | Systeem bericht 68 | 69 | 70 | show.label_body 71 | Inhoud 72 | 73 | 74 | filter.label_type 75 | Type 76 | 77 | 78 | filter.label_state 79 | Status 80 | 81 | 82 | batch.message_publish 83 | Berichten publiceren 84 | 85 | 86 | batch.message_cancelled 87 | Berichten annuleren 88 | 89 | 90 | list.label_restart_count 91 | Herstart # 92 | 93 | 94 | show.label_restart_count 95 | Herstart # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.ru.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Уведомление 8 | 9 | 10 | notifications 11 | Уведомления 12 | 13 | 14 | breadcrumb.link_message_list 15 | Сообщения 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Тип 24 | 25 | 26 | list.label_created_at 27 | Время создания 28 | 29 | 30 | list.label_started_at 31 | Время начала 32 | 33 | 34 | list.label_completed_at 35 | Время завершения 36 | 37 | 38 | list.label_get_state_name 39 | Состояние 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Тип 48 | 49 | 50 | show.label_created_at 51 | Время создания 52 | 53 | 54 | show.label_started_at 55 | Время начала 56 | 57 | 58 | show.label_completed_at 59 | Время завершения 60 | 61 | 62 | show.label_get_state_name 63 | Состояние 64 | 65 | 66 | breadcrumb.link_message_show 67 | Сообщение 68 | 69 | 70 | show.label_body 71 | Нагрузка 72 | 73 | 74 | filter.label_type 75 | Тип 76 | 77 | 78 | filter.label_state 79 | Состояние 80 | 81 | 82 | batch.message_publish 83 | Опубликовать сообщения 84 | 85 | 86 | batch.message_cancelled 87 | Отменить сообщения 88 | 89 | 90 | list.label_restart_count 91 | Перезапуск # 92 | 93 | 94 | show.label_restart_count 95 | Перезапуск # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.sk.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Oznámenie 8 | 9 | 10 | notifications 11 | Oznámenia 12 | 13 | 14 | breadcrumb.link_message_list 15 | Správy 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Typ 24 | 25 | 26 | list.label_created_at 27 | Vytvorené 28 | 29 | 30 | list.label_started_at 31 | Začaté 32 | 33 | 34 | list.label_completed_at 35 | Dokončené 36 | 37 | 38 | list.label_get_state_name 39 | Stav 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Typ 48 | 49 | 50 | show.label_created_at 51 | Vytvorené 52 | 53 | 54 | show.label_started_at 55 | Začaté 56 | 57 | 58 | show.label_completed_at 59 | Dokončené 60 | 61 | 62 | show.label_get_state_name 63 | Stav 64 | 65 | 66 | breadcrumb.link_message_show 67 | Správa 68 | 69 | 70 | show.label_body 71 | Náklad 72 | 73 | 74 | filter.label_type 75 | Typ 76 | 77 | 78 | filter.label_state 79 | Stav 80 | 81 | 82 | batch.message_publish 83 | Publikovanie správ 84 | 85 | 86 | batch.message_cancelled 87 | Zrušenie správ 88 | 89 | 90 | list.label_restart_count 91 | Znova # 92 | 93 | 94 | show.label_restart_count 95 | Znova # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Resources/translations/SonataNotificationBundle.sl.xliff: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sonata_notification 7 | Obvestilo 8 | 9 | 10 | notifications 11 | Obvestila 12 | 13 | 14 | breadcrumb.link_message_list 15 | Sporočia 16 | 17 | 18 | list.label_id 19 | Id 20 | 21 | 22 | list.label_type 23 | Tip 24 | 25 | 26 | list.label_created_at 27 | Ustvarjeno 28 | 29 | 30 | list.label_started_at 31 | Pričeto 32 | 33 | 34 | list.label_completed_at 35 | Zaključeno 36 | 37 | 38 | list.label_get_state_name 39 | Stanje 40 | 41 | 42 | show.label_id 43 | Id 44 | 45 | 46 | show.label_type 47 | Tip 48 | 49 | 50 | show.label_created_at 51 | Ustvarjeno 52 | 53 | 54 | show.label_started_at 55 | Pričeto 56 | 57 | 58 | show.label_completed_at 59 | Zaključeno 60 | 61 | 62 | show.label_get_state_name 63 | Stanje 64 | 65 | 66 | breadcrumb.link_message_show 67 | Sporočilo 68 | 69 | 70 | show.label_body 71 | Tovor 72 | 73 | 74 | filter.label_type 75 | Tip 76 | 77 | 78 | filter.label_state 79 | Stanje 80 | 81 | 82 | batch.message_publish 83 | Objavna sporočila 84 | 85 | 86 | batch.message_cancelled 87 | Preklicna sporočila 88 | 89 | 90 | list.label_restart_count 91 | Znova # 92 | 93 | 94 | show.label_restart_count 95 | Znova # 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/Selector/ErroneousMessagesSelector.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle\Selector; 15 | 16 | use Doctrine\Persistence\ManagerRegistry; 17 | use Sonata\NotificationBundle\Model\MessageInterface; 18 | 19 | /** 20 | * @final since sonata-project/notification-bundle 3.13 21 | */ 22 | class ErroneousMessagesSelector 23 | { 24 | /** 25 | * @var string 26 | */ 27 | protected $class; 28 | 29 | /** 30 | * @var ManagerRegistry 31 | */ 32 | protected $registry; 33 | 34 | /** 35 | * @param string $class 36 | */ 37 | public function __construct(ManagerRegistry $registry, $class) 38 | { 39 | $this->registry = $registry; 40 | $this->class = $class; 41 | } 42 | 43 | /** 44 | * Retrieve messages with given type(s) and restrict to max attempts count. 45 | * 46 | * @param int $maxAttempts 47 | * 48 | * @return array 49 | */ 50 | public function getMessages(array $types, $maxAttempts = 5) 51 | { 52 | $query = $this->registry->getManagerForClass($this->class)->getRepository($this->class) 53 | ->createQueryBuilder('m') 54 | ->where('m.state = :erroneousState') 55 | ->andWhere('m.restartCount < :maxAttempts'); 56 | 57 | $parameters = [ 58 | 'erroneousState' => MessageInterface::STATE_ERROR, 59 | 'maxAttempts' => $maxAttempts, 60 | ]; 61 | 62 | if (\count($types) > 0) { 63 | $query->andWhere('m.type IN (:types)'); 64 | $parameters['types'] = $types; 65 | } 66 | 67 | $query->setParameters($parameters); 68 | 69 | return $query->getQuery()->execute(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/SonataNotificationBundle.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Sonata\NotificationBundle; 15 | 16 | use Sonata\CoreBundle\Form\FormHelper; 17 | use Sonata\NotificationBundle\DependencyInjection\Compiler\NotificationCompilerPass; 18 | use Sonata\NotificationBundle\Form\Type\MessageSerializationType; 19 | use Symfony\Component\DependencyInjection\ContainerBuilder; 20 | use Symfony\Component\HttpKernel\Bundle\Bundle; 21 | 22 | /** 23 | * @final since sonata-project/notification-bundle 3.13 24 | */ 25 | class SonataNotificationBundle extends Bundle 26 | { 27 | public function build(ContainerBuilder $container) 28 | { 29 | $container->addCompilerPass(new NotificationCompilerPass()); 30 | 31 | $this->registerFormMapping(); 32 | } 33 | 34 | public function boot() 35 | { 36 | if (!\defined('AMQP_DEBUG')) { 37 | // define('AMQP_DEBUG', $this->container->getParameter('kernel.debug')); 38 | \define('AMQP_DEBUG', false); 39 | } 40 | 41 | $this->registerFormMapping(); 42 | } 43 | 44 | /** 45 | * Register form mapping information. 46 | * 47 | * NEXT_MAJOR: remove this method 48 | */ 49 | public function registerFormMapping() 50 | { 51 | if (class_exists(FormHelper::class)) { 52 | FormHelper::registerFormTypeMapping([ 53 | 'sonata_notification_api_form_message' => MessageSerializationType::class, 54 | ]); 55 | } 56 | } 57 | } 58 | --------------------------------------------------------------------------------