Completed
Push — master ( 10de85...76baec )
by Kacper
03:21
created

XmlStream::setParser()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 16
nc 1
nop 1
dl 0
loc 24
rs 8.9713
c 1
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\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
    /**
82
     * XmlStream constructor.
83
     *
84
     * Xml Stream acts like stream wrapper, that uses $transport stream to communicate with server.
85
     *
86
     * @param XmlParser             $parser    XmlParser instance used for converting XML to objects
87
     * @param DuplexStreamInterface $transport Stream used as the transport
88
     */
89
    public function __construct(XmlParser $parser, DuplexStreamInterface $transport = null)
90
    {
91
        parent::__construct($transport);
92
        $this->setParser($parser);
93
94
        $this->on('close', function () { $this->_isOpened = false; });
95
    }
96
97
    public function setParser(XmlParser $parser)
98
    {
99
        $this->_parser = $parser;
100
101
        $this->on('element', function (Error $element) {
102
            $this->handleError($element);
103
        }, with\instance(Error::class));
104
105
        $this->_parser->on('parse.begin', function (XmlElement $stream) {
106
            $this->_inbound = $stream;
107
            $this->emit('stream.open', [ $stream ]);
108
        }, with\argument(1, with\equals(0)));
109
110
        $this->_parser->on('parse.end', function (XmlElement $stream) {
111
            $this->emit('stream.close', [ $stream ]);
112
            $this->_inbound = null;
113
        }, with\argument(1, with\equals(0)));
114
115
        $this->_parser->on('element', function (...$arguments) {
116
            $this->emit('element', $arguments);
117
        });
118
119
        $this->on('data', [$this->_parser, 'parse']);
120
    }
121
122
    /**
123
     * Writes data to stream
124
     *
125
     * @param  string $data Data to write
126
     * @return bool
127
     */
128
    public function write($data)
129
    {
130
        if($data instanceof XmlElement) {
131
            $this->_outbound->append($data);
132
        }
133
134
        $this->emit('send.'.($data instanceof XmlElement ? 'element' : 'text'), [ $data ]);
135
136
        return parent::write($data);
137
    }
138
139
    /**
140
     * Starts new stream with specified attributes
141
     *
142
     * @param array $attributes Stream attributes
143
     */
144
    public function start(array $attributes = [])
145
    {
146
        $this->_parser->reset();
147
148
        $this->write('<?xml version="1.0" encoding="utf-8"?>');
149
        $this->_outbound = new Stream(['attributes' => $attributes, 'xmlns' => 'jabber:client']);
150
151
        $this->write(preg_replace('~\s+/>$~', '>', $this->_outbound));
152
        $this->_isOpened = true;
153
    }
154
155
    /**
156
     * Gently closes stream
157
     */
158
    public function close()
159
    {
160
        if ($this->isOpened()) {
161
            $this->write('</stream:stream>');
162
            $this->_isOpened = false;
163
        }
164
165
        parent::close();
166
    }
167
168
    /**
169
     * Checks if stream is opened
170
     *
171
     * @return bool
172
     */
173
    public function isOpened()
174
    {
175
        return $this->_isOpened;
176
    }
177
178
    public function __get($name)
179
    {
180
        return $this->_inbound->getAttribute($name === 'lang' ? 'xml:lang' : $name);
181
    }
182
183
    public function __set($name, $value)
184
    {
185
        throw new ReadOnlyException('Stream attributes are read-only.');
186
    }
187
188
    public function __isset($name)
189
    {
190
        return $this->_inbound->hasAttribute($name);
191
    }
192
193
    private function handleError(Error $element)
194
    {
195
        if ($this->emit('stream.error', [ $element ])) {
196
            throw new StreamErrorException($element);
197
        }
198
199
        return false;
200
    }
201
}
202