Completed
Push — master ( 4703e1...97bad7 )
by
unknown
11:22
created

generateRoutingKey()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 13
cts 13
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 3
nop 0
crap 4
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 Symfony\Component\HttpFoundation\Response;
15
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
16
use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\RequestStack;
19
use Symfony\Component\Routing\RouterInterface;
20
use Graviton\SecurityBundle\Service\SecurityUtils;
21
use GravitonDyn\EventStatusBundle\Document\EventStatus;
22
23
/**
24
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
25
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
26
 * @link     http://swisscom.ch
27
 */
28
class EventStatusLinkResponseListener
29
{
30
31
    /**
32
     * @var ProducerInterface Producer for publishing messages.
33
     */
34
    private $rabbitMqProducer = null;
35
36
    /**
37
     * @var RouterInterface Router to generate resource URLs
38
     */
39
    private $router = null;
40
41
    /**
42
     * @var Request request
43
     */
44
    private $request;
45
46
    /**
47
     * @var QueueEvent queue event document
48
     */
49
    private $queueEventDocument;
50
51
    /**
52
     * @var array
53
     */
54
    private $eventMap;
55
56
    /**
57
     * @var ExtReferenceConverter ExtReferenceConverter
58
     */
59
    private $extRefConverter;
60
61
    /**
62
     * @var string classname of the EventWorker document
63
     */
64
    private $eventWorkerClassname;
65
66
    /**
67
     * @var string classname of the EventStatus document
68
     */
69
    private $eventStatusClassname;
70
71
    /**
72
     * @var string classname of the EventStatusStatus document
73
     */
74
    private $eventStatusStatusClassname;
75
76
    /**
77
     * @var string classname of the EventStatusEventResource document
78
     */
79
    private $eventStatusEventResourceClassname;
80
81
    /**
82
     * @var string route name of the /event/status route
83
     */
84
    private $eventStatusRouteName;
85
86
    /**
87
     * @var DocumentManager Document manager
88
     */
89
    private $documentManager;
90
91
    /**
92
     * @var SecurityUtils
93
     */
94
    protected $securityUtils;
95
96
    /**
97
     * @param ProducerInterface     $rabbitMqProducer                  RabbitMQ dependency
98
     * @param RouterInterface       $router                            Router dependency
99
     * @param RequestStack          $requestStack                      Request stack
100
     * @param DocumentManager       $documentManager                   Doctrine document manager
101
     * @param ExtReferenceConverter $extRefConverter                   instance of the ExtReferenceConverter service
102
     * @param QueueEvent            $queueEventDocument                queueevent document
103
     * @param array                 $eventMap                          eventmap
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
     * @param SecurityUtils         $securityUtils                     Security utils service
110
     */
111 2
    public function __construct(
112
        ProducerInterface $rabbitMqProducer,
113
        RouterInterface $router,
114
        RequestStack $requestStack,
115
        DocumentManager $documentManager,
116
        ExtReferenceConverter $extRefConverter,
117
        QueueEvent $queueEventDocument,
118
        array $eventMap,
119
        $eventWorkerClassname,
120
        $eventStatusClassname,
121
        $eventStatusStatusClassname,
122
        $eventStatusEventResourceClassname,
123
        $eventStatusRouteName,
124
        SecurityUtils $securityUtils
125
    ) {
126 2
        $this->rabbitMqProducer = $rabbitMqProducer;
127 2
        $this->router = $router;
128 2
        $this->request = $requestStack->getCurrentRequest();
129 2
        $this->documentManager = $documentManager;
130 2
        $this->extRefConverter = $extRefConverter;
131 2
        $this->queueEventDocument = $queueEventDocument;
132 2
        $this->eventMap = $eventMap;
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
        $this->securityUtils = $securityUtils;
139 2
    }
140
141
    /**
142
     * add a rel=eventStatus Link header to the response if necessary
143
     *
144
     * @param FilterResponseEvent $event response listener event
145
     *
146
     * @return void
147
     */
148 2
    public function onKernelResponse(FilterResponseEvent $event)
149
    {
150
        /**
151
         * @var Response $response
152
         */
153 2
        $response = $event->getResponse();
154
155
        // exit if not master request, uninteresting method or an error occurred
156 2
        if (!$event->isMasterRequest() || $this->isNotConcerningRequest() || !$response->isSuccessful()) {
157
            return;
158
        }
159
160
        // we can always safely call this, it doesn't need much resources.
161
        // only if we have subscribers, it will create more load as it persists an EventStatus
162 2
        $queueEvent = $this->createQueueEventObject();
163
164 2
        if (!empty($queueEvent->getStatusurl()) && !empty($queueEvent->getEvent())) {
165 2
            $linkHeader = LinkHeader::fromResponse($response);
166 2
            $linkHeader->add(
167 2
                new LinkHeaderItem(
168 2
                    $queueEvent->getStatusurl(),
169 2
                    array('rel' => 'eventStatus')
170 1
                )
171 1
            );
172
173 2
            $response->headers->set(
174 2
                'Link',
175 1
                (string) $linkHeader
176 1
            );
177 1
        }
178
179
        // let's send it to the queue(s) if appropriate
180 2
        if (!empty($queueEvent->getEvent())) {
181 2
            $queuesForEvent = $this->getSubscribedWorkerIds($queueEvent);
182 2
            foreach ($queuesForEvent as $queueForEvent) {
183
                // declare the Queue for the Event if its not there already declared
184 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, Graviton\RabbitMqBundle\Producer\JobProducer, 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...
185 2
                $this->rabbitMqProducer->publish(json_encode($queueEvent), $queueForEvent);
186 1
            }
187 1
        }
188 2
    }
189
190
    /**
191
     * we only want to do something if we have a mapped event..
192
     *
193
     * @return boolean true if it should not concern us, false otherwise
194
     */
195 2
    private function isNotConcerningRequest()
196
    {
197 2
        return is_null($this->generateRoutingKey());
198
    }
199
200
    /**
201
     * Creates the structured object that will be sent to the queue (eventually..)
202
     *
203
     * @return QueueEvent event
204
     */
205 2
    private function createQueueEventObject()
206
    {
207 2
        $obj = clone $this->queueEventDocument;
208 2
        $obj->setEvent($this->generateRoutingKey());
209 2
        $obj->setDocumenturl($this->request->get('selfLink'));
210 2
        $obj->setStatusurl($this->getStatusUrl($obj));
211 2
        $obj->setCoreUserId($this->getSecurityUsername());
212
213 2
        return $obj;
214
    }
215
216
    /**
217
     * compose our routingKey. this will have the form of 'document.[bundle].[document].[event]'
218
     * rules:
219
     *  * always 4 parts divided by points.
220
     *  * in this context (doctrine/odm stuff) we prefix with 'document.'
221
     *
222
     * @return string routing key
223
     */
224 2
    private function generateRoutingKey()
225
    {
226 2
        $routeParts = explode('.', $this->request->get('_route'));
227 2
        $action = array_pop($routeParts);
228 2
        $baseRoute = implode('.', $routeParts);
229
230
        // find our route in the map
231 2
        $routingKey = null;
232
233 2
        foreach ($this->eventMap as $mapElement) {
234 2
            if ($mapElement['baseRoute'] == $baseRoute &&
235 2
                isset($mapElement['events'][$action])
236 1
            ) {
237 2
                $routingKey = $mapElement['events'][$action];
238 2
                break;
239
            }
240 1
        }
241
242 2
        return $routingKey;
243
    }
244
245
    /**
246
     * Creates a EventStatus object that gets persisted..
247
     *
248
     * @param QueueEvent $queueEvent queueEvent object
249
     *
250
     * @return string
251
     */
252 2
    private function getStatusUrl($queueEvent)
253
    {
254
        // this has to be checked after cause we should not call getSubscribedWorkerIds() if above is true
255 2
        $workerIds = $this->getSubscribedWorkerIds($queueEvent);
256 2
        if (empty($workerIds)) {
257
            return '';
258
        }
259
260
        // we have subscribers; create the EventStatus entry
261
        /** @var EventStatus $eventStatus **/
262 2
        $eventStatus = new $this->eventStatusClassname();
263 2
        $eventStatus->setCreatedate(new \DateTime());
264 2
        $eventStatus->setEventname($queueEvent->getEvent());
265
266
        // if available, transport the ref document to the eventStatus instance
267 2
        if (!empty($queueEvent->getDocumenturl())) {
268 2
            $eventStatusResource = new $this->eventStatusEventResourceClassname();
269 2
            $eventStatusResource->setRef($this->extRefConverter->getExtReference($queueEvent->getDocumenturl()));
270 2
            $eventStatus->setEventresource($eventStatusResource);
271 1
        }
272
273 2
        foreach ($workerIds as $workerId) {
274
            /** @var \GravitonDyn\EventStatusBundle\Document\EventStatusStatus $eventStatusStatus **/
275 2
            $eventStatusStatus = new $this->eventStatusStatusClassname();
276 2
            $eventStatusStatus->setWorkerid($workerId);
277 2
            $eventStatusStatus->setStatus('opened');
278 2
            $eventStatus->addStatus($eventStatusStatus);
279 1
        }
280
281
        // Set username to Event
282 2
        $eventStatus->setUserid($this->getSecurityUsername());
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
     * Security needs to be enabled to get
340
     *
341
     * @return String
342
     */
343 2
    private function getSecurityUsername()
344
    {
345 2
        if ($this->securityUtils->isSecurityUser()) {
346
            return $this->securityUtils->getSecurityUsername();
347
        }
348
349 2
        return '';
350
    }
351
}
352