Completed
Push — feature/EVO-4597-rabbitmq-hand... ( 4475a9 )
by
unknown
64:50
created

onKernelResponse()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 40
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 40
rs 6.7272
cc 7
eloc 19
nc 7
nop 1
1
<?php
2
/**
3
 * Response listener that adds an eventStatus to Link header if necessary, creates an EventStatus resource
4
 * and publishes the change to the queue
5
 */
6
7
namespace Graviton\RabbitMqBundle\Listener;
8
9
use Doctrine\ODM\MongoDB\DocumentManager;
10
use Graviton\DocumentBundle\Service\ExtReferenceConverter;
11
use Graviton\RabbitMqBundle\Document\QueueEvent;
12
use Graviton\RestBundle\HttpFoundation\LinkHeader;
13
use Graviton\RestBundle\HttpFoundation\LinkHeaderItem;
14
use Graviton\SecurityBundle\Authentication\Strategies\CookieFieldStrategy;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
17
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\RequestStack;
20
use Symfony\Component\Routing\RouterInterface;
21
22
/**
23
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
24
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
25
 * @link     http://swisscom.ch
26
 */
27
class EventStatusLinkResponseListener
28
{
29
30
    /**
31
     * @var ProducerInterface Producer for publishing messages.
32
     */
33
    private $rabbitMqProducer = null;
34
35
    /**
36
     * @var RouterInterface Router to generate resource URLs
37
     */
38
    private $router = null;
39
40
    /**
41
     * @var Request request
42
     */
43
    private $request;
44
45
    /**
46
     * @var QueueEvent queueevent document
47
     */
48
    private $queueEventDocument;
49
50
    /**
51
     * @var array
52
     */
53
    private $eventMap;
54
55
    /**
56
     * @var ExtReferenceConverter ExtReferenceConverter
57
     */
58
    private $extRefConverter;
59
60
    /**
61
     * @var string classname of the EventWorker document
62
     */
63
    private $eventWorkerClassname;
64
65
    /**
66
     * @var string classname of the EventStatus document
67
     */
68
    private $eventStatusClassname;
69
70
    /**
71
     * @var string classname of the EventStatusStatus document
72
     */
73
    private $eventStatusStatusClassname;
74
75
    /**
76
     * @var string classname of the EventStatusEventResource document
77
     */
78
    private $eventStatusEventResourceClassname;
79
80
    /**
81
     * @var string route name of the /event/status route
82
     */
83
    private $eventStatusRouteName;
84
85
    /**
86
     * @var DocumentManager Document manager
87
     */
88
    private $documentManager;
89
90
    /**
91
     * @param ProducerInterface     $rabbitMqProducer                  RabbitMQ dependency
92
     * @param RouterInterface       $router                            Router dependency
93
     * @param RequestStack          $requestStack                      Request stack
94
     * @param DocumentManager       $documentManager                   Doctrine document manager
95
     * @param ExtReferenceConverter $extRefConverter                   instance of the ExtReferenceConverter service
96
     * @param QueueEvent            $queueEventDocument                queueevent document
97
     * @param array                 $eventMap                          eventmap
98
     * @param string                $eventWorkerClassname              classname of the EventWorker document
99
     * @param string                $eventStatusClassname              classname of the EventStatus document
100
     * @param string                $eventStatusStatusClassname        classname of the EventStatusStatus document
101
     * @param string                $eventStatusEventResourceClassname classname of the E*S*E*Resource document
102
     * @param string                $eventStatusRouteName              name of the route to EventStatus
103
     */
104
    public function __construct(
105
        ProducerInterface $rabbitMqProducer,
106
        RouterInterface $router,
107
        RequestStack $requestStack,
108
        DocumentManager $documentManager,
109
        ExtReferenceConverter $extRefConverter,
110
        QueueEvent $queueEventDocument,
111
        array $eventMap,
112
        $eventWorkerClassname,
113
        $eventStatusClassname,
114
        $eventStatusStatusClassname,
115
        $eventStatusEventResourceClassname,
116
        $eventStatusRouteName
117
    ) {
118
        $this->rabbitMqProducer = $rabbitMqProducer;
119
        $this->router = $router;
120
        $this->request = $requestStack->getCurrentRequest();
121
        $this->documentManager = $documentManager;
122
        $this->extRefConverter = $extRefConverter;
123
        $this->queueEventDocument = $queueEventDocument;
124
        $this->eventMap = $eventMap;
125
        $this->eventWorkerClassname = $eventWorkerClassname;
126
        $this->eventStatusClassname = $eventStatusClassname;
127
        $this->eventStatusStatusClassname = $eventStatusStatusClassname;
128
        $this->eventStatusEventResourceClassname = $eventStatusEventResourceClassname;
129
        $this->eventStatusRouteName = $eventStatusRouteName;
130
    }
131
132
    /**
133
     * add a rel=eventStatus Link header to the response if necessary
134
     *
135
     * @param FilterResponseEvent $event response listener event
136
     *
137
     * @return void
138
     */
139
    public function onKernelResponse(FilterResponseEvent $event)
140
    {
141
        // exit if not master request or uninteresting method..
142
        if (!$event->isMasterRequest() || $this->isNotConcerningRequest()) {
143
            return;
144
        }
145
146
        // we can always safely call this, it doesn't need much resources.
147
        // only if we have subscribers, it will create more load as it persists an EventStatus
148
        $queueEvent = $this->createQueueEventObject();
149
150
        /** @var Response $response */
151
        $response = $event->getResponse();
152
153
        if (!empty($queueEvent->getStatusurl()) && !empty($queueEvent->getEvent())) {
154
            $linkHeader = LinkHeader::fromResponse($response);
155
            $linkHeader->add(
156
                new LinkHeaderItem(
157
                    $queueEvent->getStatusurl(),
158
                    array('rel' => 'eventStatus')
159
                )
160
            );
161
162
            $response->headers->set(
163
                'Link',
164
                (string) $linkHeader
165
            );
166
        }
167
168
        // let's send it to the queue if appropriate
169
        if (!empty($queueEvent->getEvent())) {
170
            $sQueueForEvent = $this->getRBMQueueNameForEvent($queueEvent->getEvent());
171
            if( strlen($sQueueForEvent) )
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
Coding Style introduced by
Expected 0 spaces before closing bracket; 1 found
Loading history...
172
            {
173
                // declare the Queue for the Event if its not there already declared
174
                $this->rabbitMqProducer->getChannel()->queue_declare($sQueueForEvent, false, true, false, false);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OldSound\RabbitMqBundle\RabbitMq\ProducerInterface as the method getChannel() does only exist in the following implementations of said interface: Graviton\RabbitMqBundle\Producer\Dummy, OldSound\RabbitMqBundle\RabbitMq\Producer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
175
                $this->rabbitMqProducer->publish( json_encode($queueEvent), $sQueueForEvent );
176
            }
177
        }
178
    }
179
180
    /**
181
     * @param String $sEventname the Eventname
0 ignored issues
show
Bug introduced by
There is no parameter named $sEventname. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
182
     *
183
     * @return String Queuename for the RabbitMQ
184
     * @Todo $aEventQueues must be fetched from the MongoDB!!!, Collection EventWorker.
185
     * This Collection contains the registered Worker-Ids and the events to them
186
     * Find out what's up with the generated Model of it (GravitonDyn/EventWorkerBundle) and get it!
187
     * This current code is not the solution!!!
188
     */
189
    private function getRBMQueueNameForEvent( $sInputEventname ) {
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces between opening bracket and argument "$sInputEventname"; 1 found
Loading history...
Coding Style introduced by
Expected 0 spaces between argument "$sInputEventname" and closing bracket; 1 found
Loading history...
190
        $sQueueName = '';
191
        $aEventQueues = array(
192
            'nios-printing' => array(
193
                'document.file.file.create',
194
                'document.printdocument.printdocument.create',
195
                'document.file.file.update',
196
                'document.printdocument.printdocument.update'
197
            ),
198
            'investment-worker' => array(
199
                'document.printinvestmentdocument.printinvestmentdocument.create',
200
                'document.printinvestmentdocument.printinvestmentdocument.update'
201
            ),
202
        );
203
        foreach($aEventQueues as $sEventQueueName => $aEventQueueDef) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FOREACH keyword; 0 found
Loading history...
204
            foreach( $aEventQueueDef as $sEventName ) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FOREACH keyword; 0 found
Loading history...
Coding Style introduced by
Space found after opening bracket of FOREACH loop
Loading history...
Coding Style introduced by
Space found before closing bracket of FOREACH loop
Loading history...
Coding Style introduced by
Expected 0 spaces before closing bracket; 1 found
Loading history...
205
                if( strstr($sEventName, $sInputEventname) ) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
Coding Style introduced by
Expected 0 spaces before closing bracket; 1 found
Loading history...
206
                    return $sEventQueueName;
207
                }
208
            }
209
        }
210
        return $sQueueName;
211
    }
212
213
    /**
214
     * we only want to do something if we have a mapped event..
215
     *
216
     * @return boolean true if it should not concern us, false otherwise
217
     */
218
    private function isNotConcerningRequest()
219
    {
220
        return is_null($this->generateRoutingKey());
221
    }
222
223
    /**
224
     * Creates the structured object that will be sent to the queue (eventually..)
225
     *
226
     * @return QueueEvent event
227
     */
228
    private function createQueueEventObject()
229
    {
230
        $obj = clone $this->queueEventDocument;
231
        $obj->setEvent($this->generateRoutingKey());
232
        $obj->setDocumenturl($this->request->get('selfLink'));
233
        $obj->setStatusurl($this->getStatusUrl($obj));
234
        $obj->setCoreUserId($this->getCoreUserId());
235
236
        return $obj;
237
    }
238
239
    /**
240
     * compose our routingKey. this will have the form of 'document.[bundle].[document].[event]'
241
     * rules:
242
     *  * always 4 parts divided by points.
243
     *  * in this context (doctrine/odm stuff) we prefix with 'document.'
244
     *
245
     * @return string routing key
246
     */
247
    private function generateRoutingKey()
248
    {
249
        $routeParts = explode('.', $this->request->get('_route'));
250
        $action = array_pop($routeParts);
251
        $baseRoute = implode('.', $routeParts);
252
253
        // find our route in the map
254
        $routingKey = null;
255
256
        foreach ($this->eventMap as $mapElement) {
257
            if ($mapElement['baseRoute'] == $baseRoute &&
258
                isset($mapElement['events'][$action])
259
            ) {
260
                $routingKey = $mapElement['events'][$action];
261
                break;
262
            }
263
        }
264
265
        return $routingKey;
266
    }
267
268
    /**
269
     * Creates a EventStatus object that gets persisted..
270
     *
271
     * @param QueueEvent $queueEvent queueEvent object
272
     *
273
     * @return string
274
     */
275
    private function getStatusUrl($queueEvent)
276
    {
277
        // this has to be checked after cause we should not call getSubscribedWorkerIds() if above is true
278
        $workerIds = $this->getSubscribedWorkerIds($queueEvent);
279
        if (empty($workerIds)) {
280
            return '';
281
        }
282
283
        // we have subscribers; create the EventStatus entry
284
        /** @var \GravitonDyn\EventStatusBundle\Document\EventStatus $eventStatus **/
285
        $eventStatus = new $this->eventStatusClassname();
286
        $eventStatus->setCreatedate(new \DateTime());
287
        $eventStatus->setEventname($queueEvent->getEvent());
288
289
        // if available, transport the ref document to the eventStatus instance
290
        if (!empty($queueEvent->getDocumenturl())) {
291
            $eventStatusResource = new $this->eventStatusEventResourceClassname();
292
            $eventStatusResource->setRef($this->extRefConverter->getExtReference($queueEvent->getDocumenturl()));
293
            $eventStatus->setEventresource($eventStatusResource);
294
        }
295
296
        foreach ($workerIds as $workerId) {
297
            /** @var \GravitonDyn\EventStatusBundle\Document\EventStatusStatus $eventStatusStatus **/
298
            $eventStatusStatus = new $this->eventStatusStatusClassname();
299
            $eventStatusStatus->setWorkerid($workerId);
300
            $eventStatusStatus->setStatus('opened');
301
            $eventStatus->addStatus($eventStatusStatus);
302
        }
303
304
        $this->documentManager->persist($eventStatus);
305
        $this->documentManager->flush();
306
307
        // get the url..
308
        $url = $this->router->generate(
309
            $this->eventStatusRouteName,
310
            [
311
                'id' => $eventStatus->getId()
312
            ],
313
            true
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
314
        );
315
316
        return $url;
317
    }
318
319
    /**
320
     * Checks EventWorker for worker that are subscribed to our event and returns
321
     * their workerIds as array
322
323
     * @param QueueEvent $queueEvent queueEvent object
324
     *
325
     * @return array array of worker ids
326
     */
327
    private function getSubscribedWorkerIds(QueueEvent $queueEvent)
328
    {
329
        // compose our regex to match stars ;-)
330
        // results in = /((\*|document)+)\.((\*|dude)+)\.((\*|config)+)\.((\*|update)+)/
331
        $routingArgs = explode('.', $queueEvent->getEvent());
332
        $regex =
333
            '/'.
334
            implode(
335
                '\.',
336
                array_map(
337
                    function ($arg) {
338
                        return '((\*|'.$arg.')+)';
339
                    },
340
                    $routingArgs
341
                )
342
            ).
343
            '/';
344
345
        // look up workers by class name
346
        $qb = $this->documentManager->createQueryBuilder($this->eventWorkerClassname);
347
        $data = $qb
348
            ->select('id')
349
            ->field('subscription.event')
350
            ->equals(new \MongoRegex($regex))
351
            ->getQuery()
352
            ->execute()
353
            ->toArray();
354
355
        return array_keys($data);
356
    }
357
358
    /**
359
     * Find current request attribute to User Core Id
360
     *
361
     * @return string
362
     */
363
    public function getCoreUserId()
364
    {
365
        $attributes = $this->request->attributes;
366
        if (!$attributes) {
367
            return '';
368
        }
369
        $value = $attributes->get(CookieFieldStrategy::CONFIGURATION_PARAMETER_CORE_ID);
370
        if ($value) {
371
            return (string) $value;
372
        }
373
        return '';
374
    }
375
}
376