Completed
Push — feature/EVO-4790-configurable-... ( 3bdab3...7dcc3f )
by
unknown
09:22
created

onKernelResponse()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 45
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 7.0022

Importance

Changes 6
Bugs 3 Features 0
Metric Value
c 6
b 3
f 0
dl 0
loc 45
ccs 27
cts 28
cp 0.9643
rs 6.7272
cc 7
eloc 21
nc 7
nop 1
crap 7.0022
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 String gravitonSelfUrl FQDN or apialias to Graviton
57
     */
58
    private $gravitonSelfUrl;
59
60
    /**
61
     * @var ExtReferenceConverter ExtReferenceConverter
62
     */
63
    private $extRefConverter;
64
65
    /**
66
     * @var string classname of the EventWorker document
67
     */
68
    private $eventWorkerClassname;
69
70
    /**
71
     * @var string classname of the EventStatus document
72
     */
73
    private $eventStatusClassname;
74
75
    /**
76
     * @var string classname of the EventStatusStatus document
77
     */
78
    private $eventStatusStatusClassname;
79
80
    /**
81
     * @var string classname of the EventStatusEventResource document
82
     */
83
    private $eventStatusEventResourceClassname;
84
85
    /**
86
     * @var string route name of the /event/status route
87
     */
88
    private $eventStatusRouteName;
89
90
    /**
91
     * @var DocumentManager Document manager
92
     */
93
    private $documentManager;
94
95
    /**
96
     * @param ProducerInterface     $rabbitMqProducer                  RabbitMQ dependency
97
     * @param RouterInterface       $router                            Router dependency
98
     * @param RequestStack          $requestStack                      Request stack
99
     * @param DocumentManager       $documentManager                   Doctrine document manager
100
     * @param ExtReferenceConverter $extRefConverter                   instance of the ExtReferenceConverter service
101
     * @param QueueEvent            $queueEventDocument                queueevent document
102
     * @param array                 $eventMap                          eventmap
103
     * @param String                $gravitonSelfUrl                   gravitonSelfUrl FQDN or apialias to Graviton
104
     * @param string                $eventWorkerClassname              classname of the EventWorker document
105
     * @param string                $eventStatusClassname              classname of the EventStatus document
106
     * @param string                $eventStatusStatusClassname        classname of the EventStatusStatus document
107
     * @param string                $eventStatusEventResourceClassname classname of the E*S*E*Resource document
108
     * @param string                $eventStatusRouteName              name of the route to EventStatus
109
     */
110 2
    public function __construct(
111
        ProducerInterface $rabbitMqProducer,
112
        RouterInterface $router,
113
        RequestStack $requestStack,
114
        DocumentManager $documentManager,
115
        ExtReferenceConverter $extRefConverter,
116
        QueueEvent $queueEventDocument,
117
        array $eventMap,
118
        $gravitonSelfUrl,
119
        $eventWorkerClassname,
120
        $eventStatusClassname,
121
        $eventStatusStatusClassname,
122
        $eventStatusEventResourceClassname,
123
        $eventStatusRouteName
124
    ) {
125 2
        $this->rabbitMqProducer = $rabbitMqProducer;
126 2
        $this->router = $router;
127 2
        $this->request = $requestStack->getCurrentRequest();
128 2
        $this->documentManager = $documentManager;
129 2
        $this->extRefConverter = $extRefConverter;
130 2
        $this->queueEventDocument = $queueEventDocument;
131 2
        $this->eventMap = $eventMap;
132 2
        $this->gravitonSelfUrl = $gravitonSelfUrl;
133 2
        $this->eventWorkerClassname = $eventWorkerClassname;
134 2
        $this->eventStatusClassname = $eventStatusClassname;
135 2
        $this->eventStatusStatusClassname = $eventStatusStatusClassname;
136 2
        $this->eventStatusEventResourceClassname = $eventStatusEventResourceClassname;
137 2
        $this->eventStatusRouteName = $eventStatusRouteName;
138 2
    }
139
140
    /**
141
     * add a rel=eventStatus Link header to the response if necessary
142
     *
143
     * @param FilterResponseEvent $event response listener event
144
     *
145
     * @return void
146
     */
147 2
    public function onKernelResponse(FilterResponseEvent $event)
148
    {
149
        // exit if not master request or uninteresting method..
150 2
        if (!$event->isMasterRequest() || $this->isNotConcerningRequest()) {
151
            return;
152
        }
153
154
        // we can always safely call this, it doesn't need much resources.
155
        // only if we have subscribers, it will create more load as it persists an EventStatus
156 2
        $queueEvent = $this->createQueueEventObject();
157
158
        /**
159
         * @var Response $response
160
         */
161 2
        $response = $event->getResponse();
162
163 2
        if (!empty($queueEvent->getStatusurl()) && !empty($queueEvent->getEvent())) {
164 2
            $linkHeader = LinkHeader::fromResponse($response);
165 2
            $linkHeader->add(
166 2
                new LinkHeaderItem(
167 2
                    $queueEvent->getStatusurl(),
168 2
                    array('rel' => 'eventStatus')
169 1
                )
170 1
            );
171
172 2
            $response->headers->set(
173 2
                'Link',
174 1
                (string) $linkHeader
175 1
            );
176 1
        }
177
178
        // let's send it to the queue(s) if appropriate
179 2
        if (!empty($queueEvent->getEvent())) {
180 2
            $queuesForEvent = $this->getSubscribedWorkerIds($queueEvent);
181
            // overwrite $queueEvent documentUrl & statusUrl with the FQDN or apialias injected from the settings.
182
            // Do it here, so just the $queueEvent sent to the WorkerBase is affected
183 2
            $queueEvent->setDocumenturl($this->gravitonSelfUrl.parse_url($queueEvent->getDocumenturl(), PHP_URL_PATH));
184 2
            $queueEvent->setStatusurl($this->gravitonSelfUrl.parse_url($queueEvent->getStatusurl(), PHP_URL_PATH));
185 2
            foreach ($queuesForEvent as $queueForEvent) {
186
                // declare the Queue for the Event if its not there already declared
187 2
                $this->rabbitMqProducer->getChannel()->queue_declare($queueForEvent, 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...
188 2
                $this->rabbitMqProducer->publish(json_encode($queueEvent), $queueForEvent);
189 1
            }
190 1
        }
191 2
    }
192
193
    /**
194
     * we only want to do something if we have a mapped event..
195
     *
196
     * @return boolean true if it should not concern us, false otherwise
197
     */
198 2
    private function isNotConcerningRequest()
199
    {
200 2
        return is_null($this->generateRoutingKey());
201
    }
202
203
    /**
204
     * Creates the structured object that will be sent to the queue (eventually..)
205
     *
206
     * @return QueueEvent event
207
     */
208 2
    private function createQueueEventObject()
209
    {
210 2
        $obj = clone $this->queueEventDocument;
211 2
        $obj->setEvent($this->generateRoutingKey());
212 2
        $obj->setDocumenturl($this->request->get('selfLink'));
213 2
        $obj->setStatusurl($this->getStatusUrl($obj));
214 2
        $obj->setCoreUserId($this->getCoreUserId());
215
216 2
        return $obj;
217
    }
218
219
    /**
220
     * compose our routingKey. this will have the form of 'document.[bundle].[document].[event]'
221
     * rules:
222
     *  * always 4 parts divided by points.
223
     *  * in this context (doctrine/odm stuff) we prefix with 'document.'
224
     *
225
     * @return string routing key
226
     */
227 2
    private function generateRoutingKey()
228
    {
229 2
        $routeParts = explode('.', $this->request->get('_route'));
230 2
        $action = array_pop($routeParts);
231 2
        $baseRoute = implode('.', $routeParts);
232
233
        // find our route in the map
234 2
        $routingKey = null;
235
236 2
        foreach ($this->eventMap as $mapElement) {
237 2
            if ($mapElement['baseRoute'] == $baseRoute &&
238 2
                isset($mapElement['events'][$action])
239 1
            ) {
240 2
                $routingKey = $mapElement['events'][$action];
241 2
                break;
242
            }
243 1
        }
244
245 2
        return $routingKey;
246
    }
247
248
    /**
249
     * Creates a EventStatus object that gets persisted..
250
     *
251
     * @param QueueEvent $queueEvent queueEvent object
252
     *
253
     * @return string
254
     */
255 2
    private function getStatusUrl($queueEvent)
256
    {
257
        // this has to be checked after cause we should not call getSubscribedWorkerIds() if above is true
258 2
        $workerIds = $this->getSubscribedWorkerIds($queueEvent);
259 2
        if (empty($workerIds)) {
260
            return '';
261
        }
262
263
        // we have subscribers; create the EventStatus entry
264
        /** @var \GravitonDyn\EventStatusBundle\Document\EventStatus $eventStatus **/
265 2
        $eventStatus = new $this->eventStatusClassname();
266 2
        $eventStatus->setCreatedate(new \DateTime());
267 2
        $eventStatus->setEventname($queueEvent->getEvent());
268
269
        // if available, transport the ref document to the eventStatus instance
270 2
        if (!empty($queueEvent->getDocumenturl())) {
271 2
            $eventStatusResource = new $this->eventStatusEventResourceClassname();
272 2
            $eventStatusResource->setRef($this->extRefConverter->getExtReference($queueEvent->getDocumenturl()));
273 2
            $eventStatus->setEventresource($eventStatusResource);
274 1
        }
275
276 2
        foreach ($workerIds as $workerId) {
277
            /** @var \GravitonDyn\EventStatusBundle\Document\EventStatusStatus $eventStatusStatus **/
278 2
            $eventStatusStatus = new $this->eventStatusStatusClassname();
279 2
            $eventStatusStatus->setWorkerid($workerId);
280 2
            $eventStatusStatus->setStatus('opened');
281 2
            $eventStatus->addStatus($eventStatusStatus);
282 1
        }
283
284 2
        $this->documentManager->persist($eventStatus);
285 2
        $this->documentManager->flush();
286
287
        // get the url..
288 2
        $url = $this->router->generate(
289 2
            $this->eventStatusRouteName,
290
            [
291 2
                'id' => $eventStatus->getId()
292 1
            ],
293 1
            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...
294 1
        );
295
296 2
        return $url;
297
    }
298
299
    /**
300
     * Checks EventWorker for worker that are subscribed to our event and returns
301
     * their workerIds as array
302
303
     * @param QueueEvent $queueEvent queueEvent object
304
     *
305
     * @return array array of worker ids
306
     */
307 2
    private function getSubscribedWorkerIds(QueueEvent $queueEvent)
308
    {
309
        // compose our regex to match stars ;-)
310
        // results in = /((\*|document)+)\.((\*|dude)+)\.((\*|config)+)\.((\*|update)+)/
311 2
        $routingArgs = explode('.', $queueEvent->getEvent());
312
        $regex =
313
            '/'.
314 2
            implode(
315 2
                '\.',
316 1
                array_map(
317 2
                    function ($arg) {
318 2
                        return '((\*|'.$arg.')+)';
319 2
                    },
320
                    $routingArgs
321 1
                )
322 1
            ).
323 2
            '/';
324
325
        // look up workers by class name
326 2
        $qb = $this->documentManager->createQueryBuilder($this->eventWorkerClassname);
327
        $data = $qb
328 2
            ->select('id')
329 2
            ->field('subscription.event')
330 2
            ->equals(new \MongoRegex($regex))
331 2
            ->getQuery()
332 2
            ->execute()
333 2
            ->toArray();
334
335 2
        return array_keys($data);
336
    }
337
338
    /**
339
     * Find current request attribute to User Core Id
340
     *
341
     * @return string
342
     */
343 2
    public function getCoreUserId()
344
    {
345 2
        $attributes = $this->request->attributes;
346 2
        if (!$attributes) {
347 2
            return '';
348
        }
349
        $value = $attributes->get(CookieFieldStrategy::CONFIGURATION_PARAMETER_CORE_ID);
350
        if ($value) {
351
            return (string) $value;
352
        }
353
        return '';
354
    }
355
}
356