XmlStream   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 168
ccs 14
cts 14
cp 1
rs 10
c 0
b 0
f 0
wmc 16
lcom 1
cbo 9

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
B setParser() 0 24 2
A write() 0 10 3
A start() 0 11 1
A restart() 0 5 1
A close() 0 9 2
A isOpened() 0 4 1
A __get() 0 4 1
A __set() 0 4 1
A __isset() 0 4 1
A handleError() 0 8 2
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\Xml;
17
18
use Kadet\Xmpp\Exception\Protocol\StreamErrorException;
19
use Kadet\Xmpp\Exception\ReadOnlyException;
20
use Kadet\Xmpp\Stream\Error;
21
use Kadet\Xmpp\Stream\Stream;
22
use Kadet\Xmpp\Utils\BetterEmitter;
23
use Kadet\Xmpp\Utils\Logging;
24
use Kadet\Xmpp\Utils\StreamDecorator;
25
use React\Stream\DuplexStreamInterface;
26
use Kadet\Xmpp\Utils\filter as with;
27
28
/**
29
 * Class XmlStream
30
 *
31
 * @package Kadet\Xmpp\Xml
32
 *
33
 * @event element(XmlElement $element)
34
 * @event stream.error(
35
 * @event stream.open
36
 * @event stream.close
37
 * @event send.element
38
 * @event send.text
39
 *
40
 * @property-read string $id
41
 * @property-read string $from
42
 * @property-read string $to
43
 * @property-read string $version
44
 * @property-read string $lang
45
 */
46
class XmlStream extends StreamDecorator // implements BetterEmitterInterface // Some php cancer
47
{
48
    use BetterEmitter, Logging;
49
50
    /** XML namespace of stream */
51
    const NAMESPACE_URI = 'http://etherx.jabber.org/streams';
52
53
    /**
54
     * XmlParser reference
55
     *
56
     * @var XmlParser
57
     */
58
    protected $_parser;
59
60
    /**
61
     * @var bool
62
     *
63
     * @see XmlStream::isOpened
64
     */
65
    private $_isOpened = false;
66
67
    /**
68
     * Inbound Stream root element
69
     *
70
     * @var XmlElement
71
     */
72
    private $_inbound;
73
74
    /**
75
     * Outbound Stream root element
76
     *
77
     * @var XmlElement
78
     */
79
    private $_outbound;
80
81
    private $_attributes = [];
82
83
    /**
84
     * XmlStream constructor.
85
     *
86
     * Xml Stream acts like stream wrapper, that uses $transport stream to communicate with server.
87
     *
88
     * @param XmlParser             $parser    XmlParser instance used for converting XML to objects
89
     * @param DuplexStreamInterface $transport Stream used as the transport
90
     */
91 9
    public function __construct(XmlParser $parser, DuplexStreamInterface $transport = null)
92
    {
93 9
        parent::__construct($transport);
94 9
        $this->setParser($parser);
95
96
        $this->on('close', function () { $this->_isOpened = false; });
97
        $this->on('element', function (Error $element) {
98
            $this->handleError($element);
99 9
        }, with\instance(Error::class));
100 9
    }
101
102 8
    public function setParser(XmlParser $parser)
103
    {
104 8
        if($this->_parser) {
105
            $this->removeListener('data', [ $this->_parser, 'parse' ]);
106
        }
107
108 8
        $this->_parser = $parser;
109
110
        $this->_parser->on('parse.begin', function (XmlElement $stream) {
111
            $this->_inbound = $stream;
112
            $this->emit('stream.open', [ $stream ]);
113 8
        }, with\argument(1, with\equals(0)));
114
115
        $this->_parser->on('parse.end', function (XmlElement $stream) {
116
            $this->emit('stream.close', [ $stream ]);
117
            $this->_inbound = null;
118 8
        }, with\argument(1, with\equals(0)));
119
120 8
        $this->_parser->on('element', function (...$arguments) {
121
            $this->emit('element', $arguments);
122 8
        });
123
124 8
        $this->on('data', [ $this->_parser, 'parse' ]);
125 8
    }
126
127
    /**
128
     * Writes data to stream
129
     *
130
     * @param  string $data Data to write
131
     * @return bool
132
     */
133
    public function write($data)
134
    {
135
        if($data instanceof XmlElement) {
136
            $this->_outbound->append($data);
137
        }
138
139
        $this->emit('send.'.($data instanceof XmlElement ? 'element' : 'text'), [ $data ]);
140
141
        return parent::write($data);
142
    }
143
144
    /**
145
     * Starts new stream with specified attributes
146
     *
147
     * @param array  $attributes Stream attributes
148
     */
149
    public function start(array $attributes = [])
150
    {
151
        $this->_parser->reset();
152
        $this->_attributes = $attributes;
153
154
        $this->write('<?xml version="1.0" encoding="utf-8"?>');
155
        $this->_outbound = new Stream($attributes);
156
157
        $this->write(preg_replace('~\s+/>$~', '>', $this->_outbound));
158
        $this->_isOpened = true;
159
    }
160
161
    public function restart()
162
    {
163
        $this->getLogger()->debug('Restarting stream', $this->_attributes);
164
        $this->start($this->_attributes);
165
    }
166
167
    /**
168
     * Gently closes stream
169
     */
170
    public function close()
171
    {
172
        if ($this->isOpened()) {
173
            $this->write('</stream:stream>');
174
            $this->_isOpened = false;
175
        }
176
177
        parent::close();
178
    }
179
180
    /**
181
     * Checks if stream is opened
182
     *
183
     * @return bool
184
     */
185
    public function isOpened()
186
    {
187
        return $this->_isOpened;
188
    }
189
190
    public function __get($name)
191
    {
192
        return $this->_inbound->$name;
193
    }
194
195
    public function __set($name, $value)
196
    {
197
        throw new ReadOnlyException('Stream attributes are read-only.');
198
    }
199
200
    public function __isset($name)
201
    {
202
        return $this->_inbound->hasAttribute($name);
203
    }
204
205
    private function handleError(Error $element)
206
    {
207
        if ($this->emit('stream.error', [ $element ])) {
208
            throw new StreamErrorException($element);
209
        }
210
211
        return false;
212
    }
213
}
214