Broadcast::on()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace SfCod\SocketIoBundle\Service;
4
5
use Exception;
6
use HTMLPurifier;
7
use Psr\Log\LoggerInterface;
8
use SfCod\SocketIoBundle\Events\EventInterface;
9
use SfCod\SocketIoBundle\events\EventPolicyInterface;
10
use SfCod\SocketIoBundle\Events\EventPublisherInterface;
11
use SfCod\SocketIoBundle\events\EventRoomInterface;
12
use SfCod\SocketIoBundle\Events\EventSubscriberInterface;
13
use SfCod\SocketIoBundle\Exception\EventNotFoundException;
14
use SfCod\SocketIoBundle\Middleware\Process\ProcessMiddlewareInterface;
15
16
/**
17
 * Class Broadcast.
18
 *
19
 * @package SfCod\SocketIoBundle
20
 */
21
class Broadcast
22
{
23
    /**
24
     * @var array
25
     */
26
    protected static $channels = [];
27
    /**
28
     * @var ProcessMiddlewareInterface[]
29
     */
30
    protected $processMiddlewares = [];
31
    /**
32
     * @var RedisDriver
33
     */
34
    protected $redis;
35
    /**
36
     * @var LoggerInterface
37
     */
38
    protected $logger;
39
    /**
40
     * @var EventManager
41
     */
42
    protected $manager;
43
    /**
44
     * @var Process
45
     */
46
    protected $process;
47
48
    /**
49
     * Broadcast constructor.
50
     */
51
    public function __construct(RedisDriver $redis, EventManager $manager, LoggerInterface $logger, Process $process)
52
    {
53
        $this->redis = $redis;
54
        $this->logger = $logger;
55
        $this->manager = $manager;
56
        $this->process = $process;
57
    }
58
59
    /**
60
     * Subscribe to event from client.
61
     *
62
     * @return \Symfony\Component\Process\Process
63
     *
64
     * @throws Exception
65
     */
66
    public function on(string $event, array $data)
67
    {
68
        // Clear data
69
        array_walk_recursive($data, function (&$item, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
70
            $item = (new HtmlPurifier())->purify($item);
71
        });
72
73
        $this->logger->info(json_encode(['type' => 'on', 'name' => $event, 'data' => $data]));
74
75
        return $this->process->run($event, $data);
76
    }
77
78
    /**
79
     * Run process.
80
     */
81
    public function process(string $handler, array $data)
82
    {
83
        try {
84
            /** @var EventInterface|EventSubscriberInterface|EventPolicyInterface $eventHandler */
85
            $eventHandler = $this->manager->resolve($handler); //container->get(sprintf('socketio.%s', $handler));
86
87
            if (false === $eventHandler instanceof EventInterface) {
88
                throw new Exception('Event should implement EventInterface');
89
            }
90
91
            $eventHandler->setPayload($data);
92
93
            if (false === $eventHandler instanceof EventSubscriberInterface) {
94
                throw new Exception('Event should implement EventSubscriberInterface');
95
            }
96
97
            foreach ($this->processMiddlewares as $processMiddleware) {
98
                if (false === $processMiddleware($handler, $data)) {
99
                    $this->logger->info(json_encode(['type' => 'process_terminated', 'name' => $handler, 'data' => $data]));
100
101
                    return;
102
                }
103
            }
104
105
            if (true === $eventHandler instanceof EventPolicyInterface && false === $eventHandler->can($data)) {
106
                $this->logger->info(json_encode(['type' => 'process_policy_forbidden', 'name' => $handler, 'data' => $data]));
107
108
                return;
109
            }
110
111
            $this->logger->info(json_encode(['type' => 'process', 'name' => $handler, 'data' => $data]));
112
113
            $eventHandler->handle();
114
        } catch (EventNotFoundException $e) {
115
            $this->logger->info($e);
116
        } catch (Exception $e) {
117
            $this->logger->error($e);
118
        }
119
    }
120
121
    /**
122
     * Emit event to client.
123
     *
124
     * @throws Exception
125
     */
126
    public function emit(string $event, array $data)
127
    {
128
        $this->logger->info(json_encode(['type' => 'emit', 'name' => $event, 'data' => $data]));
129
130
        try {
131
            /** @var EventInterface|EventPublisherInterface|EventRoomInterface $eventHandler */
132
            $eventHandler = $this->manager->resolve($event); // container->get(sprintf('socketio.%s', $event));
133
134
            if (false === $eventHandler instanceof EventInterface) {
135
                throw new Exception('Event should implement EventInterface');
136
            }
137
138
            $eventHandler->setPayload($data);
139
140
            if (false === $eventHandler instanceof EventPublisherInterface) {
141
                throw new Exception('Event should implement EventPublisherInterface');
142
            }
143
144
            $data = $eventHandler->fire();
145
146
            if ($eventHandler instanceof EventRoomInterface) {
147
                $data['room'] = $eventHandler->room();
148
            }
149
150
            $eventHandlerClass = get_class($eventHandler);
151
            foreach ($eventHandlerClass::broadcastOn() as $channel) {
152
                $this->publish($this->channelName($channel), [
153
                    'name' => $eventHandlerClass::name(),
154
                    'data' => $data,
155
                ]);
156
            }
157
        } catch (EventNotFoundException $e) {
158
            $this->logger->info($e);
159
        } catch (Exception $e) {
160
            $this->logger->error($e);
161
        }
162
    }
163
164
    /**
165
     * Publish data to redis channel.
166
     */
167
    protected function publish(string $channel, array $data)
168
    {
169
        $this->redis->getClient(true)->publish($channel, json_encode($data));
170
    }
171
172
    /**
173
     * Prepare channel name.
174
     */
175
    protected function channelName(string $name): string
176
    {
177
        return $name . getenv('SOCKET_IO_NSP');
178
    }
179
180
    /**
181
     * Redis channels names.
182
     */
183
    public function channels(): array
184
    {
185
        if (empty(self::$channels)) {
186
            foreach ($this->manager->getList() as $eventHandlerClass) {
187
                self::$channels = array_merge(self::$channels, $eventHandlerClass::broadcastOn());
188
            }
189
            self::$channels = array_unique(self::$channels);
190
            self::$channels = array_map(function ($channel) {
191
                return $this->channelName($channel);
192
            }, self::$channels);
193
        }
194
195
        return self::$channels;
196
    }
197
198
    /**
199
     * @param ProcessMiddlewareInterface[] $processMiddlewares
200
     */
201
    public function setProcessMiddlewares($processMiddlewares)
202
    {
203
        $this->processMiddlewares = $processMiddlewares;
204
    }
205
}
206