Completed
Push — master ( a3140b...10de85 )
by Kacper
04:29
created

XmppClient.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Nucleus - XMPP Library for PHP
4
 *
5
 * Copyright (C) 2016, Some rights reserved.
6
 *
7
 * @author Kacper "Kadet" Donat <[email protected]>
8
 *
9
 * Contact with author:
10
 * Xmpp: [email protected]
11
 * E-mail: [email protected]
12
 *
13
 * From Kadet with love.
14
 */
15
16
namespace Kadet\Xmpp;
17
18
use DI\Container;
19
use DI\ContainerBuilder;
20
use Interop\Container\ContainerInterface;
21
use Kadet\Xmpp\Exception\InvalidArgumentException;
22
use Kadet\Xmpp\Module\Authenticator;
23
use Kadet\Xmpp\Module\Binding;
24
use Kadet\Xmpp\Module\ClientModule;
25
use Kadet\Xmpp\Module\ClientModuleInterface;
26
use Kadet\Xmpp\Module\SaslAuthenticator;
27
use Kadet\Xmpp\Module\TlsEnabler;
28
use Kadet\Xmpp\Network\Connector;
29
use Kadet\Xmpp\Stream\Features;
30
use Kadet\Xmpp\Utils\Accessors;
31
use Kadet\Xmpp\Utils\filter as with;
32
use Kadet\Xmpp\Utils\ServiceManager;
33
use Kadet\Xmpp\Xml\XmlElementFactory;
34
use Kadet\Xmpp\Xml\XmlParser;
35
use Kadet\Xmpp\Xml\XmlStream;
36
use React\EventLoop\LoopInterface;
37
38
/**
39
 * Class XmppClient
40
 * @package Kadet\Xmpp
41
 *
42
 * @property-read Jid                $jid       Client's jid (Jabber Identifier) address.
43
 * @property-read Features           $features  Features provided by that stream
44
 * @property-read ContainerInterface $container Dependency container used for module management.
45
 * @property-read string             $language  Stream language (reflects xml:language attribute)
46
 *
47
 * @property-write Connector         $connector
48
 */
49
class XmppClient extends XmlStream implements ContainerInterface
50
{
51
    use ServiceManager, Accessors;
52
53
    private $_attributes = [];
54
    private $_lang;
55
56
    /**
57
     * Connector used to instantiate stream connection to server.
58
     *
59
     * @var Connector
60
     */
61
    protected $_connector;
62
63
    /**
64
     * Client's jid (Jabber Identifier) address.
65
     *
66
     * @var Jid
67
     */
68
    protected $_jid;
69
70
    /**
71
     * Dependency container used as service manager.
72
     *
73
     * @var Container
74
     */
75
    protected $_container;
76
77
    /**
78
     * Features provided by that stream
79
     *
80
     * @var Features
81
     */
82
    protected $_features;
83
84
    /**
85
     * XmppClient constructor.
86
     * @param Jid              $jid
87
     * @param array            $options {
88
     *     @var XmlParser          $parser          Parser used for interpreting streams.
89
     *     @var ClientModule[]     $modules         Additional modules registered when creating client.
90
     *     @var string             $language        Stream language (reflects xml:language attribute)
91
     *     @var ContainerInterface $container       Dependency container used for module management.
92
     *     @var bool               $default-modules Load default modules or not
93
     * }
94
     */
95
    public function __construct(Jid $jid, array $options = [])
96
    {
97
        $options = array_replace([
98
            'parser'    => new XmlParser(new XmlElementFactory()),
99
            'language'  => 'en',
100
            'container' => ContainerBuilder::buildDevContainer(),
101
            'connector' => $options['connector'] ?? new Connector\TcpXmppConnector($jid->domain, $options['loop']),
102
103
            'modules'         => [],
104
            'default-modules' => true,
105
        ], $options);
106
        $options['jid'] = $jid;
107
108
        parent::__construct($options['parser'], null);
109
        $this->_parser->factory->load(require __DIR__ . '/XmlElementLookup.php');
110
111
        $this->applyOptions($options);
112
113
        $this->_connector->on('connect', function (...$arguments) {
114
            return $this->emit('connect', $arguments);
115
        });
116
117
        $this->on('element', function (Features $element) {
118
            $this->_features = $element;
119
            $this->emit('features', [$element]);
120
        }, Features::class);
121
122
        $this->connect();
123
    }
124
125
    public function applyOptions(array $options)
126
    {
127
        unset($options['parser']); // don't need that
128
        $options = \Kadet\Xmpp\Utils\helper\rearrange($options, [
129
            'container' => 6,
130
            'jid'       => 5,
131
            'connector' => 4,
132
            'modules'   => 3,
133
            'password'  => -1
134
        ]);
135
136
        if($options['default-modules']) {
137
            $options['modules'] = array_merge([
138
                TlsEnabler::class    => new TlsEnabler(),
139
                Binding::class       => new Binding(),
140
                Authenticator::class => new SaslAuthenticator()
141
            ], $options['modules']);
142
        }
143
144
        foreach ($options as $name => $value) {
145
            $this->$name = $value;
146
        }
147
    }
148
149
    public function start(array $attributes = [])
150
    {
151
        $this->_attributes = $attributes;
152
153
        parent::start(array_merge([
154
            'xmlns'    => 'jabber:client',
155
            'version'  => '1.0',
156
            'xml:lang' => $this->_lang
157
        ], $attributes));
158
    }
159
160
    public function restart()
161
    {
162
        $this->getLogger()->debug('Restarting stream', $this->_attributes);
163
        $this->start($this->_attributes);
164
    }
165
166
    public function connect()
167
    {
168
        $this->getLogger()->debug("Connecting to {$this->_jid->domain}");
169
170
        $this->_connector->connect();
171
    }
172
173
    public function setResource(string $resource)
174
    {
175
        $this->_jid = new Jid($this->_jid->domain, $this->_jid->local, $resource);
176
    }
177
178
    public function getJid()
179
    {
180
        return $this->_jid;
181
    }
182
183
    protected function setJid(Jid $jid)
184
    {
185
        $this->_jid = $jid;
186
    }
187
188
    public function bind($jid)
189
    {
190
        $this->jid = new Jid($jid);
191
        $this->emit('bind', [$jid]);
192
193
        $this->emit('ready', []);
194
    }
195
196
    private function handleConnect($stream)
197
    {
198
        $this->exchangeStream($stream);
199
200
        $this->getLogger()->info("Connected to {$this->_jid->domain}");
201
        $this->start([
202
            'from' => (string)$this->_jid,
203
            'to'   => $this->_jid->domain
204
        ]);
205
    }
206
207
    /**
208
     * @param $connector
209
     */
210
    protected function setConnector($connector)
211
    {
212
        if ($connector instanceof LoopInterface) {
213
            $this->_connector = new Connector\TcpXmppConnector($this->_jid->domain, $connector);
214
        } elseif ($connector instanceof Connector) {
215
            $this->_connector = $connector;
216
        } else {
217
            throw new InvalidArgumentException(sprintf(
218
                '$connector must be either %s or %s instance, %s given.',
219
                LoopInterface::class, Connector::class, \Kadet\Xmpp\Utils\helper\typeof($connector)
220
            ));
221
        }
222
223
        $this->_connector->on('connect', function ($stream) {
224
            $this->handleConnect($stream);
225
        });
226
    }
227
228
    public function register(ClientModuleInterface $module, $alias = true)
229
    {
230
        $module->setClient($this);
231
        if ($alias === true) {
232
            $this->_container->set(get_class($module), $module);
233
            $aliases = array_merge(class_implements($module), array_slice(class_parents($module), 1));
234
            foreach ($aliases as $alias) {
235
                if (!$this->has($alias)) {
236
                    $this->_container->set($alias, $module);
237
                }
238
            }
239
        } else {
240
            $this->_container->set($alias === true ? get_class($module) : $alias, $module);
0 ignored issues
show
It seems like $alias === true ? get_class($module) : $alias can also be of type boolean; however, DI\Container::set() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
241
        }
242
    }
243
244
    protected function getContainer() : ContainerInterface
245
    {
246
        return $this->_container;
247
    }
248
249
    protected function setContainer(Container $container)
250
    {
251
        $this->_container = $container;
252
    }
253
254
    public function getLanguage(): string
255
    {
256
        return $this->_lang;
257
    }
258
259
    public function setLanguage(string $language)
260
    {
261
        $this->_lang = $language;
262
    }
263
264
    public function setPassword(string $password)
265
    {
266
        $this->get(Authenticator::class)->setPassword($password);
267
    }
268
269
    public function setModules(array $modules) {
270
        foreach ($modules as $name => $module) {
271
            $this->register($module, is_string($name) ? $name : true);
0 ignored issues
show
It seems like is_string($name) ? $name : true can also be of type string; however, Kadet\Xmpp\XmppClient::register() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
272
        }
273
    }
274
275
    public function getFeatures()
276
    {
277
        return $this->_features;
278
    }
279
}
280