Application::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
namespace Eole\Sandstone\Websocket;
4
5
use Psr\Log\LoggerAwareTrait;
6
use JMS\Serializer\EventDispatcher\EventSubscriberInterface as WrongEventSubscriberInterface;
7
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8
use Symfony\Component\Security\Core\User\UserInterface;
9
use Ratchet\ConnectionInterface;
10
use Ratchet\Wamp\WampServerInterface;
11
use Eole\Sandstone\Logger\EchoLogger;
12
use Eole\Sandstone\OAuth2\Security\Authentication\Token\OAuth2Token;
13
use Eole\Sandstone\Websocket\Event\ConnectionEvent;
14
use Eole\Sandstone\Websocket\Event\WebsocketAuthenticationEvent;
15
use Eole\Sandstone\Websocket\Event\ConnectionErrorEvent;
16
use Eole\Sandstone\Websocket\Event\WampEvent;
17
use Eole\Sandstone\Websocket\Event\PublishEvent;
18
use Eole\Sandstone\Websocket\Event\RPCEvent;
19
use Eole\Sandstone\Application as SandstoneApplication;
20
21
final class Application implements WampServerInterface
22
{
23
    use LoggerAwareTrait;
24
25
    /**
26
     * @var SandstoneApplication
27
     */
28
    private $sandstoneApplication;
29
30
    /**
31
     * @var Topic[]
32
     */
33
    private $topics;
34
35
    /**
36
     * @param SandstoneApplication $sandstoneApplication
37
     */
38
    public function __construct(SandstoneApplication $sandstoneApplication)
39
    {
40
        $this->sandstoneApplication = $sandstoneApplication;
41
        $this->topics = array();
42
        $this->logger = new EchoLogger();
43
    }
44
45
    /**
46
     * @param ConnectionInterface $conn
47
     *
48
     * @return UserInterface|null
49
     *
50
     * @throws \Exception
51
     */
52
    private function authenticateUser(ConnectionInterface $conn)
53
    {
54
        $queryParameters = [];
55
        parse_str($conn->httpRequest->getUri()->getQuery(), $queryParameters);
0 ignored issues
show
Bug introduced by
Accessing httpRequest on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
56
57
        if (!isset($queryParameters['access_token'])) {
58
            return null;
59
        }
60
61
        $accessToken = $queryParameters['access_token'];
62
        $authenticationManager = $this->sandstoneApplication['security.authentication_manager'];
63
64
        $authenticatedToken = $authenticationManager->authenticate(new OAuth2Token($accessToken));
65
        $user = $authenticatedToken->getUser();
66
        $isUser = $user instanceof UserInterface;
67
68
        if (!$isUser) {
69
            throw new \Exception('User not found.');
70
        }
71
72
        return $user;
73
    }
74
75
    /**
76
     * {@InheritDoc}
77
     */
78
    public function onOpen(ConnectionInterface $conn)
79
    {
80
        $this->dispatch(ConnectionEvent::ON_OPEN, new ConnectionEvent($conn));
81
82
        $this->logger->info('Connection event', ['event' => 'open']);
83
        $this->logger->info('Authentication...');
84
85
        try {
86
            $user = $this->authenticateUser($conn);
87
            if (null === $user) {
88
                $this->logger->info('Anonymous connection');
89
            } else {
90
                $this->dispatch(ConnectionEvent::ON_AUTHENTICATION, new WebsocketAuthenticationEvent($conn, $user));
91
                $this->logger->info('User logged.', ['username' => $user->getUsername()]);
92
            }
93
        } catch (\Exception $e) {
94
            $this->logger->notice('Failed authentication', ['error_message' => $e->getMessage()]);
95
            $conn->send(json_encode('Could not authenticate client, closing connection.'));
96
            $conn->close();
97
98
            return;
99
        }
100
101
        $conn->user = $user;
0 ignored issues
show
Bug introduced by
Accessing user on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
102
    }
103
104
    /**
105
     * {@InheritDoc}
106
     */
107
    private function getTopic($topicPath)
108
    {
109
        if (!isset($this->topics[$topicPath])) {
110
            $this->topics[$topicPath] = $this->loadTopic($topicPath);
111
        }
112
113
        return $this->topics[$topicPath];
114
    }
115
116
    /**
117
     * @param string $topicPath
118
     *
119
     * @return Topic
120
     */
121
    private function loadTopic($topicPath)
122
    {
123
        $topic = $this->sandstoneApplication['sandstone.websocket.router']->loadTopic($topicPath);
124
125
        if (!$this->sandstoneApplication->offsetExists('serializer')) {
126
            throw new \RuntimeException('A serializer must be registered');
127
        }
128
129
        $topic->setNormalizer($this->sandstoneApplication['serializer']);
130
131
        if ($topic instanceof EventSubscriberInterface) {
132
            $this->sandstoneApplication['dispatcher']->addSubscriber($topic);
133
        }
134
135
        // debug purpose only. Sometimes I use the wrong namespace (JMS one), and it's hard to debug.
136
        if ($topic instanceof WrongEventSubscriberInterface) {
137
            throw new \LogicException(
138
                get_class($topic).' seems to implements the wrong EventSubscriberInterface. '.
139
                'Use the Symfony one, not the JMS one.'
140
            );
141
        }
142
143
        return $topic;
144
    }
145
146
    /**
147
     * {@InheritDoc}
148
     */
149 View Code Duplication
    public function onSubscribe(ConnectionInterface $conn, $topic)
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...
150
    {
151
        $this->dispatch(ConnectionEvent::ON_SUBSCRIBE, new WampEvent($conn, $topic));
152
153
        $this->logger->info('Topic event', ['event' => 'subscribe', 'topic' => $topic]);
154
155
        $this->getTopic($topic)->onSubscribe($conn, $topic);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $topic defined by parameter $topic on line 149 can also be of type object<Ratchet\Wamp\Topic>; however, Eole\Sandstone\Websocket\Topic::onSubscribe() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
156
    }
157
158
    /**
159
     * {@InheritDoc}
160
     */
161
    public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible)
162
    {
163
        $this->dispatch(ConnectionEvent::ON_PUBLISH, new PublishEvent($conn, $topic, $event, $exclude, $eligible));
164
165
        $this->logger->info('Topic event', ['event' => 'publish', 'topic' => $topic]);
166
167
        $this->getTopic($topic)->onPublish($conn, $topic, $event);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $topic defined by parameter $topic on line 161 can also be of type object<Ratchet\Wamp\Topic>; however, Eole\Sandstone\Websocket\Topic::onPublish() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
168
    }
169
170
    /**
171
     * {@InheritDoc}
172
     */
173 View Code Duplication
    public function onUnSubscribe(ConnectionInterface $conn, $topic)
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...
174
    {
175
        $this->dispatch(ConnectionEvent::ON_UNSUBSCRIBE, new WampEvent($conn, $topic));
176
177
        $this->logger->info('Topic event', ['event' => 'unsubscribe', 'topic' => $topic]);
178
179
        $this->getTopic($topic)->onUnSubscribe($conn, $topic);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $topic defined by parameter $topic on line 173 can also be of type object<Ratchet\Wamp\Topic>; however, Eole\Sandstone\Websocket\Topic::onUnSubscribe() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
180
    }
181
182
    /**
183
     * {@InheritDoc}
184
     */
185 View Code Duplication
    public function onClose(ConnectionInterface $conn)
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...
186
    {
187
        $this->dispatch(ConnectionEvent::ON_CLOSE, new ConnectionEvent($conn));
188
189
        $this->logger->info('Connection event', ['event' => 'close']);
190
191
        foreach ($this->topics as $topic) {
192
            if ($topic->has($conn)) {
193
                $topic->onUnSubscribe($conn, $topic);
0 ignored issues
show
Compatibility introduced by
$conn of type object<Ratchet\ConnectionInterface> is not a sub-type of object<Ratchet\Wamp\WampConnection>. It seems like you assume a concrete implementation of the interface Ratchet\ConnectionInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
194
            }
195
        }
196
    }
197
198
    /**
199
     * {@InheritDoc}
200
     */
201
    public function onCall(ConnectionInterface $conn, $id, $topic, array $params)
202
    {
203
        $this->dispatch(ConnectionEvent::ON_RPC, new RPCEvent($conn, $topic, $id, $params));
204
205
        $this->logger->info('Topic event', ['event' => 'call', 'topic' => $topic]);
206
    }
207
208
    /**
209
     * {@InheritDoc}
210
     */
211
    public function onError(ConnectionInterface $conn, \Exception $e)
212
    {
213
        $this->dispatch(ConnectionEvent::ON_ERROR, new ConnectionErrorEvent($conn, $e));
214
215
        $this->logger->info('Connection event', ['event' => 'error', 'message' => $e->getMessage()]);
216
    }
217
218
    /**
219
     * Dispatch a ConnectionEvent to SandstoneApplication dispatcher.
220
     *
221
     * @param string $eventName
222
     * @param ConnectionEvent $event
223
     */
224
    private function dispatch($eventName, ConnectionEvent $event)
225
    {
226
        $this->sandstoneApplication['dispatcher']->dispatch($eventName, $event);
227
    }
228
}
229