├── .styleci.yml
├── BernardBundle.php
├── Collector
└── ProducerCollector.php
├── Command
└── DebugCommand.php
├── DependencyInjection
├── BernardExtension.php
├── Compiler
│ ├── NormalizerPass.php
│ └── ReceiverPass.php
└── Configuration.php
├── EventListener
└── SchemaListener.php
├── LICENSE
├── Normalizer
└── DefaultMessageNormalizer.php
├── README.md
├── Resources
├── config
│ ├── collector.xml
│ ├── commands.xml
│ └── services.xml
└── views
│ ├── Collector
│ └── producer.html.twig
│ └── Icon
│ └── bernard.svg
├── Tests
└── DependencyInjection
│ ├── BernardExtensionTest.php
│ ├── Compiler
│ ├── NormalizerPassTest.php
│ └── ReceiverPassTest.php
│ └── ConfigurationTest.php
└── composer.json
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: symfony
2 | risky: false
3 |
4 | disabled:
5 | - yoda_style
6 |
--------------------------------------------------------------------------------
/BernardBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new ReceiverPass())
17 | ->addCompilerPass(new NormalizerPass())
18 | ;
19 | }
20 |
21 | public function registerCommands(Application $application)
22 | {
23 | parent::registerCommands($application);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Collector/ProducerCollector.php:
--------------------------------------------------------------------------------
1 | data = [
17 | 'messageCount' => 0,
18 | 'messages' => [],
19 | ];
20 | }
21 |
22 | /**
23 | * Collects data for the given Request and Response.
24 | *
25 | * @param Request $request A Request instance
26 | * @param Response $response A Response instance
27 | * @param \Exception $exception An Exception instance
28 | */
29 | public function collect(Request $request, Response $response, \Exception $exception = null)
30 | {
31 | // We don't collect anything from the request data
32 | }
33 |
34 | /**
35 | * Returns the name of the collector.
36 | *
37 | * @return string The collector name
38 | */
39 | public function getName()
40 | {
41 | return 'bernard';
42 | }
43 |
44 | public function onBernardProduce(EnvelopeEvent $event)
45 | {
46 | $envelope = $event->getEnvelope();
47 |
48 | $this->data['messages'][(string) $event->getQueue()][] = [
49 | 'name' => $envelope->getName(),
50 | 'message' => $this->cloneVar($envelope->getMessage()),
51 | 'timestamp' => $envelope->getTimestamp(),
52 | 'class' => $envelope->getClass(),
53 | ];
54 |
55 | ++$this->data['messageCount'];
56 | }
57 |
58 | public static function getSubscribedEvents()
59 | {
60 | return array(
61 | BernardEvents::PRODUCE => 'onBernardProduce',
62 | );
63 | }
64 |
65 | public function getMessageCount()
66 | {
67 | return $this->data['messageCount'];
68 | }
69 |
70 | public function getDistinctQueueCount()
71 | {
72 | return count($this->data['messages']);
73 | }
74 |
75 | public function getAllMessages()
76 | {
77 | return $this->data['messages'];
78 | }
79 |
80 | /**
81 | * Resets this data collector to its initial state.
82 | */
83 | public function reset()
84 | {
85 | $this->data = [
86 | 'messageCount' => 0,
87 | 'messages' => [],
88 | ];
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Command/DebugCommand.php:
--------------------------------------------------------------------------------
1 | setName(null === $router ? 'bernard:debug' : $router);
29 |
30 | return;
31 | }
32 |
33 | $this->router = $router;
34 | }
35 |
36 | public function configure()
37 | {
38 | $this
39 | ->setName('bernard:debug')
40 | ->setDescription('Displays a table of receivers that are registered with "bernard.receiver" tag.')
41 | ;
42 | }
43 |
44 | protected function execute(InputInterface $input, OutputInterface $output)
45 | {
46 | if (null === $this->router) {
47 | $this->router = $this->getContainer()->get('bernard.router');
48 | }
49 |
50 | $r = new \ReflectionProperty($this->router, 'receivers');
51 | $r->setAccessible(true);
52 |
53 | $rows = [];
54 | foreach ($r->getValue($this->router) as $key => $val) {
55 | $rows[] = [$key, $val];
56 | }
57 |
58 | $table = new Table($output);
59 | $table
60 | ->setHeaders(['Message', 'Service'])
61 | ->addRows($rows)
62 | ->render()
63 | ;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/DependencyInjection/BernardExtension.php:
--------------------------------------------------------------------------------
1 | load('services.xml');
17 | $loader->load('commands.xml');
18 |
19 | switch ($config['driver']) {
20 | case 'doctrine':
21 | $this->registerDoctrineConfiguration($config['options'], $container);
22 |
23 | break;
24 |
25 | case 'file':
26 | $this->registerFlatFileConfiguration($config['options'], $container);
27 |
28 | break;
29 |
30 | case 'phpamqp':
31 | $this->registerPhpAmqpConfiguration($config['options'], $container);
32 |
33 | break;
34 |
35 | case 'phpredis':
36 | $this->registerPhpRedisConfiguration($config['options'], $container);
37 |
38 | break;
39 |
40 | case 'predis':
41 | $this->registerPredisConfiguration($config['options'], $container);
42 |
43 | break;
44 |
45 | case 'ironmq':
46 | $this->registerIronMQConfiguration($config['options'], $container);
47 |
48 | break;
49 |
50 | case 'sqs':
51 | $this->registerSqsConfiguration($config['options'], $container);
52 |
53 | break;
54 |
55 | case 'pheanstalk':
56 | $this->registerPheanstalkConfiguration($config['options'], $container);
57 |
58 | break;
59 | }
60 |
61 | if ($config['driver'] === 'custom') {
62 | $container->setAlias('bernard.driver', $config['options']['custom_service']);
63 | } else {
64 | $container->setAlias('bernard.driver', 'bernard.driver.'.$config['driver']);
65 | }
66 |
67 | $this->registerListeners($config['listeners'], $container);
68 |
69 | if ($container->getParameter('kernel.debug')) {
70 | $loader->load('collector.xml');
71 | }
72 | }
73 |
74 | private function registerDoctrineConfiguration($config, ContainerBuilder $container)
75 | {
76 | $container->setAlias('bernard.dbal.connection', 'doctrine.dbal.'.$config['connection'].'_connection');
77 | $container
78 | ->getDefinition('bernard.listener.doctrine_schema')
79 | ->addTag('doctrine.event_listener', [
80 | 'lazy' => true,
81 | 'event' => 'postGenerateSchema',
82 | 'connection' => $config['connection'],
83 | ]
84 | );
85 | }
86 |
87 | private function registerFlatFileConfiguration(array $config, ContainerBuilder $container)
88 | {
89 | $container->getDefinition('bernard.driver.file')->replaceArgument(0, $config['directory']);
90 | }
91 |
92 | private function registerPhpAmqpConfiguration(array $config, ContainerBuilder $container)
93 | {
94 | $container->getDefinition('bernard.driver.phpamqp')->replaceArgument(0, new Reference($config['phpamqp_service']))
95 | ->replaceArgument(1, $config['phpamqp_exchange'])
96 | ->replaceArgument(2, $config['phpamqp_default_message_parameters']);
97 | }
98 |
99 | private function registerPhpRedisConfiguration(array $config, ContainerBuilder $container)
100 | {
101 | $container->getDefinition('bernard.driver.phpredis')->replaceArgument(0, new Reference($config['phpredis_service']));
102 | }
103 |
104 | private function registerPredisConfiguration(array $config, ContainerBuilder $container)
105 | {
106 | $container->getDefinition('bernard.driver.predis')->replaceArgument(0, new Reference($config['predis_service']));
107 | }
108 |
109 | private function registerIronMQConfiguration(array $config, ContainerBuilder $container)
110 | {
111 | $container->getDefinition('bernard.driver.ironmq')->replaceArgument(0, new Reference($config['ironmq_service']));
112 | }
113 |
114 | private function registerSqsConfiguration(array $config, ContainerBuilder $container)
115 | {
116 | $container->getDefinition('bernard.driver.sqs')->replaceArgument(0, new Reference($config['sqs_service']))
117 | ->replaceArgument(1, $config['sqs_queue_map'])
118 | ->replaceArgument(2, $config['prefetch']);
119 | }
120 |
121 | private function registerPheanstalkConfiguration(array $config, ContainerBuilder $container)
122 | {
123 | $container->getDefinition('bernard.driver.pheanstalk')->replaceArgument(0, new Reference($config['pheanstalk_service']));
124 | }
125 |
126 | private function registerListeners(array $config, ContainerBuilder $container)
127 | {
128 | foreach ($config as $id => $params) {
129 | if (empty($params) || (is_array($params) && !$params['enabled'])) {
130 | $container->removeDefinition('bernard.listener.'.$id);
131 |
132 | continue;
133 | }
134 |
135 | // Enable listener.
136 | $listener = $container->getDefinition('bernard.listener.'.$id);
137 | $listener->addTag('kernel.event_subscriber');
138 |
139 | if ($id === 'logger') {
140 | $listener->replaceArgument(0, new Reference($params['service']));
141 | } elseif ($id === 'failure') {
142 | $listener->replaceArgument(1, $params['queue_name']);
143 | }
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/NormalizerPass.php:
--------------------------------------------------------------------------------
1 | findTaggedServiceIds('bernard.normalizer')));
16 |
17 | $container->getDefinition('bernard.normalizer')->replaceArgument(0, $normalizers);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/ReceiverPass.php:
--------------------------------------------------------------------------------
1 | findTaggedServiceIds('bernard.receiver') as $id => $tags) {
15 | foreach ($tags as $attrs) {
16 | if (!isset($attrs['message'])) {
17 | throw new \RuntimeException(sprintf('Each tag named "bernard.receiver" of service "%s" must have at "message" attribute that species the message name it is associated with.', $id));
18 | }
19 |
20 | $receivers[$attrs['message']] = $id;
21 | }
22 | }
23 |
24 | $container->getDefinition('bernard.router')->replaceArgument(1, $receivers);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
16 | } else {
17 | // BC layer for symfony/config 4.1 and older
18 | $root = $tree->root('bernard');
19 | }
20 |
21 | $root
22 | ->children()
23 | ->enumNode('driver')
24 | ->isRequired()
25 | ->cannotBeEmpty()
26 | ->values(['doctrine', 'file', 'phpamqp', 'phpredis', 'predis', 'ironmq', 'sqs', 'pheanstalk', 'custom'])
27 | ->end()
28 |
29 | ->arrayNode('options')
30 | ->addDefaultsIfNotSet()
31 | ->children()
32 | ->scalarNode('connection')->defaultValue('default')->end()
33 | ->scalarNode('directory')->defaultValue('%kernel.cache_dir%/bernard')->end()
34 | ->scalarNode('phpamqp_service')->defaultValue('old_sound_rabbit_mq.connection.default')->end()
35 | ->scalarNode('phpamqp_exchange')->defaultNull()->end()
36 | ->arrayNode('phpamqp_default_message_parameters')
37 | ->useAttributeAsKey('name')
38 | ->prototype('scalar')->end()
39 | ->end()
40 | ->scalarNode('phpredis_service')->defaultValue('snc_redis.bernard')->end()
41 | ->scalarNode('predis_service')->defaultValue('snc_redis.bernard')->end()
42 | ->scalarNode('ironmq_service')->defaultNull()->end()
43 | ->scalarNode('sqs_service')->defaultNull()->end()
44 | ->arrayNode('sqs_queue_map')
45 | ->useAttributeAsKey('name')
46 | ->prototype('scalar')->end()
47 | ->end()
48 | ->scalarNode('prefetch')->defaultNull()->end()
49 | ->scalarNode('pheanstalk_service')->defaultNull()->end()
50 | ->scalarNode('custom_service')->defaultNull()->end()
51 | ->end()
52 | ->end()
53 |
54 | ->arrayNode('listeners')
55 | ->addDefaultsIfNotSet()
56 | ->children()
57 | ->booleanNode('error_log')->defaultFalse()->end()
58 | ->arrayNode('logger')
59 | ->canBeEnabled()
60 | ->children()
61 | ->scalarNode('service')->defaultValue('logger')->end()
62 | ->end()
63 | ->beforeNormalization()
64 | ->ifString()
65 | ->then(function ($v) {
66 | return ['enabled' => true, 'service' => $v];
67 | })
68 | ->end()
69 | ->end()
70 | ->arrayNode('failure')
71 | ->canBeEnabled()
72 | ->children()
73 | ->scalarNode('queue_name')->defaultValue('failed')->end()
74 | ->end()
75 | ->beforeNormalization()
76 | ->ifString()
77 | ->then(function ($v) {
78 | return ['enabled' => true, 'queue_name' => $v];
79 | })
80 | ->end()
81 | ->end()
82 | ->end()
83 | ->end()
84 | ->end()
85 | ;
86 |
87 | $this
88 | ->validateDriver($root, 'file', 'directory')
89 | ->validateDriver($root, 'phpamqp', 'connection')
90 | ->validateDriver($root, 'phpamqp', 'phpamqp_exchange')
91 | ->validateDriver($root, 'phpredis', 'phpredis_service')
92 | ->validateDriver($root, 'predis', 'predis_service')
93 | ->validateDriver($root, 'ironmq', 'ironmq_service')
94 | ->validateDriver($root, 'sqs', 'sqs_service')
95 | ->validateDriver($root, 'pheanstalk', 'pheanstalk_service')
96 | ->validateDriver($root, 'custom', 'custom_service')
97 | ;
98 |
99 | return $tree;
100 | }
101 |
102 | /**
103 | * @param NodeDefinition $node
104 | * @param string $driver
105 | * @param string $option
106 | *
107 | * @return self
108 | */
109 | private function validateDriver(NodeDefinition $node, $driver, $option)
110 | {
111 | $node
112 | ->validate()
113 | ->ifTrue(function ($v) use ($driver, $option) {
114 | return $driver === $v['driver'] && empty($v['options'][$option]);
115 | })
116 | ->thenInvalid(sprintf('The "%s" option must be defined when using the "%s" driver.', $option, $driver))
117 | ->end()
118 | ;
119 |
120 | return $this;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/EventListener/SchemaListener.php:
--------------------------------------------------------------------------------
1 | getSchema());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Bernard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Normalizer/DefaultMessageNormalizer.php:
--------------------------------------------------------------------------------
1 | 1 and you may run into invisibility timeout problems with that
185 | ```
186 |
187 | ### Pheanstalk
188 |
189 | To use Pheanstalk (pda/pheanstalk), configure your driver like this:
190 |
191 | ``` yaml
192 | services:
193 | my.pheanstalk.connection:
194 | class: Pheanstalk\Pheanstalk
195 | arguments:
196 | - %some.parameter.containing.pheanstalk.host%
197 |
198 | bernard:
199 | driver: pheanstalk
200 | options:
201 | pheanstalk_service: my.pheanstalk.connection
202 | ```
203 |
--------------------------------------------------------------------------------
/Resources/config/collector.xml:
--------------------------------------------------------------------------------
1 |
2 |
# | 43 |Message | 44 |Produced at | 45 |
---|---|---|
{{ loop.index }} | 51 |52 | {% set context_id = 'context-' ~ queue ~ '-' ~ loop.index %} 53 | 54 | {{ message.class }} 55 | 56 | Show message 57 | 58 | 61 | | 62 |{{ message.timestamp|date('d/m/Y H:i:s') }} | 63 |
No messages were produced.
72 |