Completed
Push — master ( 5eff09...92586b )
by Kacper
03:19
created

XmppClient::getLanguage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
194
    private function handleConnect($stream)
195
    {
196
        $this->exchangeStream($stream);
197
198
        $this->getLogger()->info("Connected to {$this->_jid->domain}");
199
        $this->start([
200
            'from' => (string)$this->_jid,
201
            'to'   => $this->_jid->domain
202
        ]);
203
    }
204
205
    /**
206
     * @param $connector
207
     */
208
    protected function setConnector($connector)
209
    {
210
        if ($connector instanceof LoopInterface) {
211
            $this->_connector = new Connector\TcpXmppConnector($this->_jid->domain, $connector);
212
        } elseif ($connector instanceof Connector) {
213
            $this->_connector = $connector;
214
        } else {
215
            throw new InvalidArgumentException(sprintf(
216
                '$connector must be either %s or %s instance, %s given.',
217
                LoopInterface::class, Connector::class, \Kadet\Xmpp\Utils\helper\typeof($connector)
218
            ));
219
        }
220
221
        $this->_connector->on('connect', function ($stream) {
222
            $this->handleConnect($stream);
223
        });
224
    }
225
226
    public function register(ClientModuleInterface $module, $alias = true)
227
    {
228
        $module->setClient($this);
229
        if ($alias === true) {
230
            $this->_container->set(get_class($module), $module);
231
            $aliases = array_merge(class_implements($module), array_slice(class_parents($module), 1));
232
            foreach ($aliases as $alias) {
233
                if (!$this->has($alias)) {
234
                    $this->_container->set($alias, $module);
235
                }
236
            }
237
        } else {
238
            $this->_container->set($alias === true ? get_class($module) : $alias, $module);
0 ignored issues
show
Bug introduced by
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...
239
        }
240
    }
241
242
    protected function getContainer() : ContainerInterface
243
    {
244
        return $this->_container;
245
    }
246
247
    protected function setContainer(Container $container)
248
    {
249
        $this->_container = $container;
250
    }
251
252
    public function getLanguage(): string
253
    {
254
        return $this->_lang;
255
    }
256
257
    public function setLanguage(string $language)
258
    {
259
        $this->_lang = $language;
260
    }
261
262
    public function setPassword(string $password)
263
    {
264
        $this->get(Authenticator::class)->setPassword($password);
265
    }
266
267
    public function setModules(array $modules) {
268
        foreach ($modules as $name => $module) {
269
            $this->register($module, is_string($name) ? $name : true);
0 ignored issues
show
Bug introduced by
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...
270
        }
271
    }
272
273
    public function getFeatures()
274
    {
275
        return $this->_features;
276
    }
277
}
278