Completed
Push — master ( a3140b...10de85 )
by Kacper
04:29
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
        $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
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...
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
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...
272
        }
273
    }
274
275
    public function getFeatures()
276
    {
277
        return $this->_features;
278
    }
279
}
280