These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 | $accessToken = $conn->WebSocket->request->getQuery()->get('access_token'); |
||
55 | |||
56 | if (null === $accessToken) { |
||
57 | return null; |
||
58 | } |
||
59 | |||
60 | $authenticationManager = $this->sandstoneApplication['security.authentication_manager']; |
||
61 | |||
62 | $authenticatedToken = $authenticationManager->authenticate(new OAuth2Token($accessToken)); |
||
63 | $user = $authenticatedToken->getUser(); |
||
64 | $isUser = $user instanceof UserInterface; |
||
65 | |||
66 | if (!$isUser) { |
||
67 | throw new \Exception('User not found.'); |
||
68 | } |
||
69 | |||
70 | return $user; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * {@InheritDoc} |
||
75 | */ |
||
76 | public function onOpen(ConnectionInterface $conn) |
||
77 | { |
||
78 | $this->dispatch(ConnectionEvent::ON_OPEN, new ConnectionEvent($conn)); |
||
79 | |||
80 | $this->logger->info('Connection event', ['event' => 'open']); |
||
81 | $this->logger->info('Authentication...'); |
||
82 | |||
83 | try { |
||
84 | $user = $this->authenticateUser($conn); |
||
85 | if (null === $user) { |
||
86 | $this->logger->info('Anonymous connection'); |
||
87 | } else { |
||
88 | $this->dispatch(ConnectionEvent::ON_AUTHENTICATION, new WebsocketAuthenticationEvent($conn, $user)); |
||
89 | $this->logger->info('User logged.', ['username' => $user->getUsername()]); |
||
90 | } |
||
91 | } catch (\Exception $e) { |
||
92 | $this->logger->notice('Failed authentication', ['error_message' => $e->getMessage()]); |
||
93 | $conn->send(json_encode('Could not authenticate client, closing connection.')); |
||
94 | $conn->close(); |
||
95 | |||
96 | return; |
||
97 | } |
||
98 | |||
99 | $conn->user = $user; |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * {@InheritDoc} |
||
104 | */ |
||
105 | private function getTopic($topicPath) |
||
106 | { |
||
107 | if (!isset($this->topics[$topicPath])) { |
||
108 | $this->topics[$topicPath] = $this->loadTopic($topicPath); |
||
109 | } |
||
110 | |||
111 | return $this->topics[$topicPath]; |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * @param string $topicPath |
||
116 | * |
||
117 | * @return Topic |
||
118 | */ |
||
119 | private function loadTopic($topicPath) |
||
120 | { |
||
121 | $topic = $this->sandstoneApplication['sandstone.websocket.router']->loadTopic($topicPath); |
||
122 | |||
123 | if (!$this->sandstoneApplication->offsetExists('serializer')) { |
||
124 | throw new \RuntimeException('A serializer must be registered'); |
||
125 | } |
||
126 | |||
127 | $topic->setNormalizer($this->sandstoneApplication['serializer']); |
||
128 | |||
129 | if ($topic instanceof EventSubscriberInterface) { |
||
130 | $this->sandstoneApplication['dispatcher']->addSubscriber($topic); |
||
131 | } |
||
132 | |||
133 | // debug purpose only. Sometimes I use the wrong namespace (JMS one), and it's hard to debug. |
||
134 | if ($topic instanceof WrongEventSubscriberInterface) { |
||
135 | throw new \LogicException( |
||
136 | get_class($topic).' seems to implements the wrong EventSubscriberInterface. '. |
||
137 | 'Use the Symfony one, not the JMS one.' |
||
138 | ); |
||
139 | } |
||
140 | |||
141 | return $topic; |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * {@InheritDoc} |
||
146 | */ |
||
147 | View Code Duplication | public function onSubscribe(ConnectionInterface $conn, $topic) |
|
0 ignored issues
–
show
|
|||
148 | { |
||
149 | $this->dispatch(ConnectionEvent::ON_SUBSCRIBE, new WampEvent($conn, $topic)); |
||
150 | |||
151 | $this->logger->info('Topic event', ['event' => 'subscribe', 'topic' => $topic]); |
||
152 | |||
153 | $this->getTopic($topic)->onSubscribe($conn, $topic); |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * {@InheritDoc} |
||
158 | */ |
||
159 | public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) |
||
160 | { |
||
161 | $this->dispatch(ConnectionEvent::ON_PUBLISH, new PublishEvent($conn, $topic, $event, $exclude, $eligible)); |
||
162 | |||
163 | $this->logger->info('Topic event', ['event' => 'publish', 'topic' => $topic]); |
||
164 | |||
165 | $this->getTopic($topic)->onPublish($conn, $topic, $event); |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * {@InheritDoc} |
||
170 | */ |
||
171 | View Code Duplication | public function onUnSubscribe(ConnectionInterface $conn, $topic) |
|
0 ignored issues
–
show
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...
|
|||
172 | { |
||
173 | $this->dispatch(ConnectionEvent::ON_UNSUBSCRIBE, new WampEvent($conn, $topic)); |
||
174 | |||
175 | $this->logger->info('Topic event', ['event' => 'unsubscribe', 'topic' => $topic]); |
||
176 | |||
177 | $this->getTopic($topic)->onUnSubscribe($conn, $topic); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * {@InheritDoc} |
||
182 | */ |
||
183 | View Code Duplication | public function onClose(ConnectionInterface $conn) |
|
0 ignored issues
–
show
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...
|
|||
184 | { |
||
185 | $this->dispatch(ConnectionEvent::ON_CLOSE, new ConnectionEvent($conn)); |
||
186 | |||
187 | $this->logger->info('Connection event', ['event' => 'close']); |
||
188 | |||
189 | foreach ($this->topics as $topic) { |
||
190 | if ($topic->has($conn)) { |
||
191 | $topic->onUnSubscribe($conn, $topic); |
||
192 | } |
||
193 | } |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * {@InheritDoc} |
||
198 | */ |
||
199 | public function onCall(ConnectionInterface $conn, $id, $topic, array $params) |
||
200 | { |
||
201 | $this->dispatch(ConnectionEvent::ON_RPC, new RPCEvent($conn, $topic, $id, $params)); |
||
202 | |||
203 | $this->logger->info('Topic event', ['event' => 'call', 'topic' => $topic]); |
||
204 | } |
||
205 | |||
206 | /** |
||
207 | * {@InheritDoc} |
||
208 | */ |
||
209 | public function onError(ConnectionInterface $conn, \Exception $e) |
||
210 | { |
||
211 | $this->dispatch(ConnectionEvent::ON_ERROR, new ConnectionErrorEvent($conn, $e)); |
||
212 | |||
213 | $this->logger->info('Connection event', ['event' => 'error', 'message' => $e->getMessage()]); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Dispatch a ConnectionEvent to SandstoneApplication dispatcher. |
||
218 | * |
||
219 | * @param string $eventName |
||
220 | * @param ConnectionEvent $event |
||
221 | */ |
||
222 | private function dispatch($eventName, ConnectionEvent $event) |
||
223 | { |
||
224 | $this->sandstoneApplication['dispatcher']->dispatch($eventName, $event); |
||
225 | } |
||
226 | } |
||
227 |
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.