Completed
Push — develop ( 79789a...83eb4e )
by Bastian
11:23 queued 13s
created

EventStatusLinkResponseListener   B

Complexity

Total Complexity 22

Size/Duplication

Total Lines 326
Duplicated Lines 3.07 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 97.39%

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 22
c 2
b 1
f 1
lcom 1
cbo 16
dl 10
loc 326
ccs 112
cts 115
cp 0.9739
rs 8.4614

8 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 29 1
C onKernelResponse() 0 41 7
A isNotConcerningRequest() 0 4 1
A createQueueEventObject() 0 10 1
A generateRoutingKey() 0 20 4
B getStatusUrl() 0 46 4
B getSubscribedWorkerIds() 0 30 1
A getSecurityUsername() 10 10 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
21
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
22
use Symfony\Component\Security\Core\User\UserInterface;
23
use GravitonDyn\EventStatusBundle\Document\EventStatus;
24
25
/**
26
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
27
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
28
 * @link     http://swisscom.ch
29
 */
30
class EventStatusLinkResponseListener
31
{
32
33
    /**
34
     * @var ProducerInterface Producer for publishing messages.
35
     */
36
    private $rabbitMqProducer = null;
37
38
    /**
39
     * @var RouterInterface Router to generate resource URLs
40
     */
41
    private $router = null;
42
43
    /**
44
     * @var Request request
45
     */
46
    private $request;
47
48
    /**
49
     * @var QueueEvent queueevent document
50
     */
51
    private $queueEventDocument;
52
53
    /**
54
     * @var array
55
     */
56
    private $eventMap;
57
58
    /**
59
     * @var ExtReferenceConverter ExtReferenceConverter
60
     */
61
    private $extRefConverter;
62
63
    /**
64
     * @var string classname of the EventWorker document
65
     */
66
    private $eventWorkerClassname;
67
68
    /**
69
     * @var string classname of the EventStatus document
70
     */
71
    private $eventStatusClassname;
72
73
    /**
74
     * @var string classname of the EventStatusStatus document
75
     */
76
    private $eventStatusStatusClassname;
77
78
    /**
79
     * @var string classname of the EventStatusEventResource document
80
     */
81
    private $eventStatusEventResourceClassname;
82
83
    /**
84
     * @var string route name of the /event/status route
85
     */
86
    private $eventStatusRouteName;
87
88
    /**
89
     * @var DocumentManager Document manager
90
     */
91
    private $documentManager;
92
93
    /**
94
     * @var TokenStorage
95
     */
96
    protected $tokenStorage;
97
98
    /**
99
     * @param ProducerInterface     $rabbitMqProducer                  RabbitMQ dependency
100
     * @param RouterInterface       $router                            Router dependency
101
     * @param RequestStack          $requestStack                      Request stack
102
     * @param DocumentManager       $documentManager                   Doctrine document manager
103
     * @param ExtReferenceConverter $extRefConverter                   instance of the ExtReferenceConverter service
104
     * @param QueueEvent            $queueEventDocument                queueevent document
105
     * @param array                 $eventMap                          eventmap
106
     * @param string                $eventWorkerClassname              classname of the EventWorker document
107
     * @param string                $eventStatusClassname              classname of the EventStatus document
108
     * @param string                $eventStatusStatusClassname        classname of the EventStatusStatus document
109
     * @param string                $eventStatusEventResourceClassname classname of the E*S*E*Resource document
110
     * @param string                $eventStatusRouteName              name of the route to EventStatus
111
     * @param TokenStorage          $tokenStorage                      Security service
112
     */
113 2
    public function __construct(
114
        ProducerInterface $rabbitMqProducer,
115
        RouterInterface $router,
116
        RequestStack $requestStack,
117
        DocumentManager $documentManager,
118
        ExtReferenceConverter $extRefConverter,
119
        QueueEvent $queueEventDocument,
120
        array $eventMap,
121
        $eventWorkerClassname,
122
        $eventStatusClassname,
123
        $eventStatusStatusClassname,
124
        $eventStatusEventResourceClassname,
125
        $eventStatusRouteName,
126
        TokenStorage $tokenStorage
127
    ) {
128 2
        $this->rabbitMqProducer = $rabbitMqProducer;
129 2
        $this->router = $router;
130 2
        $this->request = $requestStack->getCurrentRequest();
131 2
        $this->documentManager = $documentManager;
132 2
        $this->extRefConverter = $extRefConverter;
133 2
        $this->queueEventDocument = $queueEventDocument;
134 2
        $this->eventMap = $eventMap;
135 2
        $this->eventWorkerClassname = $eventWorkerClassname;
136 2
        $this->eventStatusClassname = $eventStatusClassname;
137 2
        $this->eventStatusStatusClassname = $eventStatusStatusClassname;
138 2
        $this->eventStatusEventResourceClassname = $eventStatusEventResourceClassname;
139 2
        $this->eventStatusRouteName = $eventStatusRouteName;
140 2
        $this->tokenStorage = $tokenStorage;
141 2
    }
142
143
    /**
144
     * add a rel=eventStatus Link header to the response if necessary
145
     *
146
     * @param FilterResponseEvent $event response listener event
147
     *
148
     * @return void
149
     */
150 2
    public function onKernelResponse(FilterResponseEvent $event)
151
    {
152
        // exit if not master request or uninteresting method..
153 2
        if (!$event->isMasterRequest() || $this->isNotConcerningRequest()) {
154
            return;
155
        }
156
157
        // we can always safely call this, it doesn't need much resources.
158
        // only if we have subscribers, it will create more load as it persists an EventStatus
159 2
        $queueEvent = $this->createQueueEventObject();
160
161
        /**
162
         * @var Response $response
163
         */
164 2
        $response = $event->getResponse();
165
166 2
        if (!empty($queueEvent->getStatusurl()) && !empty($queueEvent->getEvent())) {
167 2
            $linkHeader = LinkHeader::fromResponse($response);
168 2
            $linkHeader->add(
169 2
                new LinkHeaderItem(
170 2
                    $queueEvent->getStatusurl(),
171 2
                    array('rel' => 'eventStatus')
172 1
                )
173 1
            );
174
175 2
            $response->headers->set(
176 2
                'Link',
177 1
                (string) $linkHeader
178 1
            );
179 1
        }
180
181
        // let's send it to the queue(s) if appropriate
182 2
        if (!empty($queueEvent->getEvent())) {
183 2
            $queuesForEvent = $this->getSubscribedWorkerIds($queueEvent);
184 2
            foreach ($queuesForEvent as $queueForEvent) {
185
                // declare the Queue for the Event if its not there already declared
186 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...
187 2
                $this->rabbitMqProducer->publish(json_encode($queueEvent), $queueForEvent);
188 1
            }
189 1
        }
190 2
    }
191
192
    /**
193
     * we only want to do something if we have a mapped event..
194
     *
195
     * @return boolean true if it should not concern us, false otherwise
196
     */
197 2
    private function isNotConcerningRequest()
198
    {
199 2
        return is_null($this->generateRoutingKey());
200
    }
201
202
    /**
203
     * Creates the structured object that will be sent to the queue (eventually..)
204
     *
205
     * @return QueueEvent event
206
     */
207 2
    private function createQueueEventObject()
208
    {
209 2
        $obj = clone $this->queueEventDocument;
210 2
        $obj->setEvent($this->generateRoutingKey());
211 2
        $obj->setDocumenturl($this->request->get('selfLink'));
212 2
        $obj->setStatusurl($this->getStatusUrl($obj));
213 2
        $obj->setCoreUserId($this->getSecurityUsername());
214
215 2
        return $obj;
216
    }
217
218
    /**
219
     * compose our routingKey. this will have the form of 'document.[bundle].[document].[event]'
220
     * rules:
221
     *  * always 4 parts divided by points.
222
     *  * in this context (doctrine/odm stuff) we prefix with 'document.'
223
     *
224
     * @return string routing key
225
     */
226 2
    private function generateRoutingKey()
227
    {
228 2
        $routeParts = explode('.', $this->request->get('_route'));
229 2
        $action = array_pop($routeParts);
230 2
        $baseRoute = implode('.', $routeParts);
231
232
        // find our route in the map
233 2
        $routingKey = null;
234
235 2
        foreach ($this->eventMap as $mapElement) {
236 2
            if ($mapElement['baseRoute'] == $baseRoute &&
237 2
                isset($mapElement['events'][$action])
238 1
            ) {
239 2
                $routingKey = $mapElement['events'][$action];
240 2
                break;
241
            }
242 1
        }
243
244 2
        return $routingKey;
245
    }
246
247
    /**
248
     * Creates a EventStatus object that gets persisted..
249
     *
250
     * @param QueueEvent $queueEvent queueEvent object
251
     *
252
     * @return string
253
     */
254 2
    private function getStatusUrl($queueEvent)
255
    {
256
        // this has to be checked after cause we should not call getSubscribedWorkerIds() if above is true
257 2
        $workerIds = $this->getSubscribedWorkerIds($queueEvent);
258 2
        if (empty($workerIds)) {
259
            return '';
260
        }
261
262
        // we have subscribers; create the EventStatus entry
263
        /** @var EventStatus $eventStatus **/
264 2
        $eventStatus = new $this->eventStatusClassname();
265 2
        $eventStatus->setCreatedate(new \DateTime());
266 2
        $eventStatus->setEventname($queueEvent->getEvent());
267
268
        // if available, transport the ref document to the eventStatus instance
269 2
        if (!empty($queueEvent->getDocumenturl())) {
270 2
            $eventStatusResource = new $this->eventStatusEventResourceClassname();
271 2
            $eventStatusResource->setRef($this->extRefConverter->getExtReference($queueEvent->getDocumenturl()));
272 2
            $eventStatus->setEventresource($eventStatusResource);
273 1
        }
274
275 2
        foreach ($workerIds as $workerId) {
276
            /** @var \GravitonDyn\EventStatusBundle\Document\EventStatusStatus $eventStatusStatus **/
277 2
            $eventStatusStatus = new $this->eventStatusStatusClassname();
278 2
            $eventStatusStatus->setWorkerid($workerId);
279 2
            $eventStatusStatus->setStatus('opened');
280 2
            $eventStatus->addStatus($eventStatusStatus);
281 1
        }
282
283
        // Set username to Event
284 2
        $eventStatus->setUserid($this->getSecurityUsername());
285
286 2
        $this->documentManager->persist($eventStatus);
287 2
        $this->documentManager->flush();
288
289
        // get the url..
290 2
        $url = $this->router->generate(
291 2
            $this->eventStatusRouteName,
292
            [
293 2
                'id' => $eventStatus->getId()
294 1
            ],
295 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...
296 1
        );
297
298 2
        return $url;
299
    }
300
301
    /**
302
     * Checks EventWorker for worker that are subscribed to our event and returns
303
     * their workerIds as array
304
305
     * @param QueueEvent $queueEvent queueEvent object
306
     *
307
     * @return array array of worker ids
308
     */
309 2
    private function getSubscribedWorkerIds(QueueEvent $queueEvent)
310
    {
311
        // compose our regex to match stars ;-)
312
        // results in = /((\*|document)+)\.((\*|dude)+)\.((\*|config)+)\.((\*|update)+)/
313 2
        $routingArgs = explode('.', $queueEvent->getEvent());
314
        $regex =
315
            '/'.
316 2
            implode(
317 2
                '\.',
318 1
                array_map(
319 2
                    function ($arg) {
320 2
                        return '((\*|'.$arg.')+)';
321 2
                    },
322
                    $routingArgs
323 1
                )
324 1
            ).
325 2
            '/';
326
327
        // look up workers by class name
328 2
        $qb = $this->documentManager->createQueryBuilder($this->eventWorkerClassname);
329
        $data = $qb
330 2
            ->select('id')
331 2
            ->field('subscription.event')
332 2
            ->equals(new \MongoRegex($regex))
333 2
            ->getQuery()
334 2
            ->execute()
335 2
            ->toArray();
336
337 2
        return array_keys($data);
338
    }
339
340
    /**
341
     * Security needs to be enabled to get
342
     *
343
     * @return String
344
     */
345 2 View Code Duplication
    private function getSecurityUsername()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
    {
347
        /** @var PreAuthenticatedToken $token */
348 2
        if (($token = $this->tokenStorage->getToken())
349 2
            && ($user = $token->getUser()) instanceof UserInterface ) {
350
            return $user->getUsername();
351
        }
352
353 2
        return '';
354
    }
355
}
356