Completed
Push — master ( 02147a...ed637b )
by Evgenij
04:37
created

AbstractClientIo::handleUnreadData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * Async sockets
4
 *
5
 * @copyright Copyright (c) 2015, Efimov Evgenij <[email protected]>
6
 *
7
 * This source file is subject to the MIT license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
namespace AsyncSockets\Socket\Io;
11
12
use AsyncSockets\Exception\ConnectionException;
13
use AsyncSockets\Exception\FrameSocketException;
14
use AsyncSockets\Exception\NetworkSocketException;
15
use AsyncSockets\Frame\FramePickerInterface;
16
use AsyncSockets\Frame\NullFramePicker;
17
use AsyncSockets\Frame\PartialFrame;
18
19
/**
20
 * Class AbstractClientIo
21
 */
22
abstract class AbstractClientIo extends AbstractIo
23
{
24
    /**
25
     * Disconnected state
26
     */
27
    const STATE_DISCONNECTED = 0;
28
29
    /**
30
     * Connected state
31
     */
32
    const STATE_CONNECTED = 1;
33
34
    /**
35
     * Unhandled portion of data at the end of frame
36
     *
37
     * @var string
38
     */
39
    private $unhandledData = '';
40
41
    /**
42
     * Socket state
43
     *
44
     * @var int
45
     */
46
    private $state = self::STATE_DISCONNECTED;
47
48
    /**
49
     * Amount of attempts to write data before treat request as failed
50
     *
51
     * @var int
52
     */
53
    private $writeAttempts = self::IO_ATTEMPTS;
54
55
    /**
56
     * Read raw data from network into given picker
57
     *
58
     * @param FramePickerInterface $picker Frame picker
59
     *
60
     * @return string Data after end of frame
61
     */
62
    abstract protected function readRawDataIntoPicker(FramePickerInterface $picker);
63
64
    /**
65
     * Write data to socket
66
     *
67
     * @param string $data Data to write
68
     *
69
     * @return int Number of written bytes
70
     */
71
    abstract protected function writeRawData($data);
72
73
    /**
74
     * Check whether given socket resource is connected
75
     *
76
     * @return bool
77
     */
78
    abstract protected function isConnected();
79
80
    /**
81
     * Return true, if transfer is finished, false otherwise
82
     *
83
     * @return bool
84
     */
85
    abstract protected function isEndOfTransfer();
86
87
    /**
88
     * Return true if frame can be collected in nearest future, false otherwise
89
     *
90
     * @return bool
91
     */
92
    abstract protected function canReachFrame();
93
94
    /** {@inheritdoc} */
95 41
    final public function read(FramePickerInterface $picker)
96
    {
97 41
        $this->setConnectedState();
98 35
        $isEndOfFrameReached = $this->handleUnreadData($picker);
99 35
        if (!$isEndOfFrameReached) {
100 35
            $this->unhandledData = $this->readRawDataIntoPicker($picker);
101
102 34
            $isEndOfTransfer     = $this->isEndOfTransfer();
103 34
            $isEndOfFrameReached = $this->isEndOfFrameReached($picker, $isEndOfTransfer);
104 34
            if (!$isEndOfFrameReached && !$this->canReachFrame()) {
105 5
                throw new FrameSocketException($picker, $this->socket, 'Failed to receive desired frame.');
106
            }
107 30
        }
108
109 30
        $frame = $picker->createFrame();
110 30
        if (!$isEndOfFrameReached) {
111 4
            $frame = new PartialFrame($frame);
112 4
        }
113
114 30
        return $frame;
115
    }
116
117
    /** {@inheritdoc} */
118 21
    final public function write($data)
119
    {
120 21
        $this->setConnectedState();
121
122 13
        $result              = $this->writeRawData($data);
123 6
        $this->writeAttempts = $result > 0 ? self::IO_ATTEMPTS : $this->writeAttempts - 1;
124
125 6
        $this->throwNetworkSocketExceptionIf(
126 6
            !$this->writeAttempts && $result !== strlen($data),
127
            'Failed to send data.'
128 6
        );
129
130 6
        return $result;
131
    }
132
133
    /**
134
     * Verify, that we are in connected state
135
     *
136
     * @return void
137
     * @throws ConnectionException
138
     */
139 62
    private function setConnectedState()
140
    {
141 62
        $resource = $this->socket->getStreamResource();
142 62
        if (!is_resource($resource)) {
143 12
            $message = $this->state === self::STATE_CONNECTED ?
144 12
                'Connection was unexpectedly closed.' :
145 12
                'Can not start io operation on uninitialized socket.';
146 12
            throw new ConnectionException($this->socket, $message);
147
        }
148
149 50
        if ($this->state !== self::STATE_CONNECTED) {
150 50
            if (!$this->isConnected()) {
151 2
                throw new ConnectionException($this->socket, 'Connection refused.');
152
            }
153
154 48
            $this->state = self::STATE_CONNECTED;
155 48
        }
156 48
    }
157
158
    /**
159
     * Checks that we are in connected state
160
     *
161
     * @param string $message Message to pass in exception
162
     *
163
     * @return void
164
     * @throws NetworkSocketException
165
     */
166 2
    final protected function throwExceptionIfNotConnected($message)
167
    {
168 2
        $this->throwNetworkSocketExceptionIf(
169 2
            !$this->isConnected(),
170
            $message
171 2
        );
172 1
    }
173
174
    /**
175
     * Checks whether all FramePicker data is read
176
     *
177
     * @param FramePickerInterface $picker Frame object to check
178
     * @param bool                 $isTransferFinished Flag whether network transfer is finished
179
     *
180
     * @return bool
181
     */
182 35
    protected function isEndOfFrameReached(FramePickerInterface $picker, $isTransferFinished)
183
    {
184
        return
185 35
            (!($picker instanceof NullFramePicker) && $picker->isEof()) ||
186 35
            ($picker instanceof NullFramePicker && $isTransferFinished);
187
    }
188
189
    /**
190
     * Read unhandled data if there is something from the previous operation
191
     *
192
     * @param FramePickerInterface $picker Frame picker to use
193
     *
194
     * @return bool Flag whether it is the end of the frame
195
     */
196 35
    private function handleUnreadData(FramePickerInterface $picker)
197
    {
198 35
        if ($this->unhandledData) {
199 6
            $this->unhandledData = $picker->pickUpData($this->unhandledData);
200 6
        }
201
202 35
        return $this->isEndOfFrameReached($picker, false);
203
    }
204
}
205