Completed
Pull Request — master (#547)
by Alexandru
01:30
created

Channel::subscribe()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace BeyondCode\LaravelWebSockets\Channels;
4
5
use BeyondCode\LaravelWebSockets\Contracts\ChannelManager;
6
use BeyondCode\LaravelWebSockets\DashboardLogger;
7
use BeyondCode\LaravelWebSockets\Server\Exceptions\InvalidSignature;
8
use Illuminate\Support\Str;
9
use Ratchet\ConnectionInterface;
10
use stdClass;
11
12
class Channel
13
{
14
    /**
15
     * The channel name.
16
     *
17
     * @var string
18
     */
19
    protected $name;
20
21
    /**
22
     * The connections that got subscribed to this channel.
23
     *
24
     * @var array
25
     */
26
    protected $connections = [];
27
28
    /**
29
     * Create a new instance.
30
     *
31
     * @param  string  $name
32
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
33
     */
34
    public function __construct(string $name)
35
    {
36
        $this->name = $name;
37
        $this->channelManager = app(ChannelManager::class);
0 ignored issues
show
Bug introduced by
The property channelManager does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
38
    }
39
40
    /**
41
     * Get channel name.
42
     *
43
     * @return string
44
     */
45
    public function getName()
46
    {
47
        return $this->name;
48
    }
49
50
    /**
51
     * Get the list of subscribed connections.
52
     *
53
     * @return array
54
     */
55
    public function getConnections()
56
    {
57
        return $this->connections;
58
    }
59
60
    /**
61
     * Check if the channel has connections.
62
     *
63
     * @return bool
64
     */
65
    public function hasConnections(): bool
66
    {
67
        return count($this->getConnections()) > 0;
68
    }
69
70
    /**
71
     * Add a new connection to the channel.
72
     *
73
     * @see    https://pusher.com/docs/pusher_protocol#presence-channel-events
74
     * @param  \Ratchet\ConnectionInterface  $connection
75
     * @param  \stdClass  $payload
76
     * @return bool
77
     */
78
    public function subscribe(ConnectionInterface $connection, stdClass $payload): bool
79
    {
80
        $this->saveConnection($connection);
81
82
        $connection->send(json_encode([
83
            'event' => 'pusher_internal:subscription_succeeded',
84
            'channel' => $this->getName(),
85
        ]));
86
87
        DashboardLogger::log($connection->app->id, DashboardLogger::TYPE_SUBSCRIBED, [
0 ignored issues
show
Bug introduced by
Accessing app 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...
88
            'socketId' => $connection->socketId,
0 ignored issues
show
Bug introduced by
Accessing socketId 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...
89
            'channel' => $this->getName(),
90
        ]);
91
92
        return true;
93
    }
94
95
    /**
96
     * Unsubscribe connection from the channel.
97
     *
98
     * @param  \Ratchet\ConnectionInterface  $connection
99
     * @return bool
100
     */
101
    public function unsubscribe(ConnectionInterface $connection): bool
102
    {
103
        if (! $this->hasConnection($connection)) {
104
            return false;
105
        }
106
107
        unset($this->connections[$connection->socketId]);
108
109
        return true;
110
    }
111
112
    /**
113
     * Check if the given connection exists.
114
     *
115
     * @param  \Ratchet\ConnectionInterface  $connection
116
     * @return bool
117
     */
118
    public function hasConnection(ConnectionInterface $connection): bool
119
    {
120
        return isset($this->connections[$connection->socketId]);
0 ignored issues
show
Bug introduced by
Accessing socketId 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...
121
    }
122
123
    /**
124
     * Store the connection to the subscribers list.
125
     *
126
     * @param  \Ratchet\ConnectionInterface  $connection
127
     * @return void
128
     */
129
    public function saveConnection(ConnectionInterface $connection)
130
    {
131
        $this->connections[$connection->socketId] = $connection;
0 ignored issues
show
Bug introduced by
Accessing socketId 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...
132
    }
133
134
    /**
135
     * Broadcast a payload to the subscribed connections.
136
     *
137
     * @param  string|int  $appId
138
     * @param  \stdClass  $payload
139
     * @param  bool  $replicate
140
     * @return bool
141
     */
142
    public function broadcast($appId, stdClass $payload, bool $replicate = true): bool
143
    {
144
        collect($this->getConnections())
145
            ->each->send(json_encode($payload));
146
147
        if ($replicate) {
148
            $this->channelManager->broadcastAcrossServers($appId, null, $this->getName(), $payload);
149
        }
150
151
        return true;
152
    }
153
154
    /**
155
     * Broadcast a payload to the locally-subscribed connections.
156
     *
157
     * @param  string|int  $appId
158
     * @param  \stdClass  $payload
159
     * @return bool
160
     */
161
    public function broadcastLocally($appId, stdClass $payload): bool
162
    {
163
        return $this->broadcast($appId, $payload, false);
164
    }
165
166
    /**
167
     * Broadcast the payload, but exclude a specific socket id.
168
     *
169
     * @param  \stdClass  $payload
170
     * @param  string|null  $socketId
171
     * @param  string|int  $appId
172
     * @param  bool  $replicate
173
     * @return bool
174
     */
175
    public function broadcastToEveryoneExcept(stdClass $payload, ?string $socketId, $appId, bool $replicate = true)
176
    {
177
        if ($replicate) {
178
            $this->channelManager->broadcastAcrossServers($appId, $socketId, $this->getName(), $payload);
179
        }
180
181
        if (is_null($socketId)) {
182
            return $this->broadcast($appId, $payload, $replicate);
183
        }
184
185
        collect($this->getConnections())->each(function (ConnectionInterface $connection) use ($socketId, $payload) {
186
            if ($connection->socketId !== $socketId) {
0 ignored issues
show
Bug introduced by
Accessing socketId 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...
187
                $connection->send(json_encode($payload));
188
            }
189
        });
190
191
        return true;
192
    }
193
194
    /**
195
     * Broadcast the payload, but exclude a specific socket id.
196
     *
197
     * @param  \stdClass  $payload
198
     * @param  string|null  $socketId
199
     * @param  string|int  $appId
200
     * @return bool
201
     */
202
    public function broadcastLocallyToEveryoneExcept(stdClass $payload, ?string $socketId, $appId)
203
    {
204
        return $this->broadcastToEveryoneExcept(
205
            $payload, $socketId, $appId, false
206
        );
207
    }
208
209
    /**
210
     * Check if the signature for the payload is valid.
211
     *
212
     * @param  \Ratchet\ConnectionInterface  $connection
213
     * @param  \stdClass  $payload
214
     * @return void
215
     * @throws InvalidSignature
216
     */
217
    protected function verifySignature(ConnectionInterface $connection, stdClass $payload)
218
    {
219
        $signature = "{$connection->socketId}:{$this->getName()}";
0 ignored issues
show
Bug introduced by
Accessing socketId 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...
220
221
        if (isset($payload->channel_data)) {
222
            $signature .= ":{$payload->channel_data}";
223
        }
224
225
        if (! hash_equals(
226
            hash_hmac('sha256', $signature, $connection->app->secret),
0 ignored issues
show
Bug introduced by
Accessing app 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...
227
            Str::after($payload->auth, ':'))
228
        ) {
229
            throw new InvalidSignature;
230
        }
231
    }
232
}
233