Completed
Branch 0.4-dev (54e67b)
by Evgenij
02:52
created

AbstractSocket::open()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3

Importance

Changes 18
Bugs 1 Features 3
Metric Value
c 18
b 1
f 3
dl 0
loc 28
ccs 17
cts 17
cp 1
rs 8.8571
cc 3
eloc 15
nc 2
nop 2
crap 3
1
<?php
2
/**
3
 * Async sockets
4
 *
5
 * @copyright Copyright (c) 2015-2016, 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
11
namespace AsyncSockets\Socket;
12
13
use AsyncSockets\Exception\ConnectionException;
14
use AsyncSockets\Frame\FramePickerInterface;
15
use AsyncSockets\Socket\Io\Context;
16
use AsyncSockets\Socket\Io\DisconnectedIo;
17
use AsyncSockets\Socket\Io\IoInterface;
18
19
/**
20
 * Class AbstractSocket
21
 */
22
abstract class AbstractSocket implements SocketInterface
23
{
24
    /**
25
     * Tcp socket type
26
     */
27
    const SOCKET_TYPE_TCP = 'tcp';
28
29
    /**
30
     * Udp socket type
31
     */
32
    const SOCKET_TYPE_UDP = 'udp';
33
34
    /**
35
     * Unix socket type
36
     */
37
    const SOCKET_TYPE_UNIX = 'unix';
38
39
    /**
40
     * Unix datagram socket type
41
     */
42
    const SOCKET_TYPE_UDG = 'udg';
43
44
    /**
45
     * Unknown type of socket
46
     */
47
    const SOCKET_TYPE_UNKNOWN = '';
48
49
    /**
50
     * This socket resource
51
     *
52
     * @var resource
53
     */
54
    private $resource;
55
56
    /**
57
     * I/O interface
58
     *
59
     * @var IoInterface
60
     */
61
    private $ioInterface;
62
63
    /**
64
     * Socket address
65
     *
66
     * @var string
67
     */
68
    private $remoteAddress;
69
70
    /**
71
     * Context for this socket
72
     *
73
     * @var Context
74
     */
75
    private $context;
76
77
    /**
78
     * AbstractSocket constructor.
79
     */
80 108
    public function __construct()
81
    {
82 108
        $this->setDisconnectedState();
83 108
        $this->context = new Context();
84 108
    }
85
86
    /**
87
     * Create certain socket resource
88
     *
89
     * @param string   $address Network address to open in form transport://path:port
90
     * @param resource $context Valid stream context created by function stream_context_create or null
91
     *
92
     * @return resource
93
     */
94
    abstract protected function createSocketResource($address, $context);
95
96
    /**
97
     * Create I/O interface for socket
98
     *
99
     * @param string $type Type of this socket, one of SOCKET_TYPE_* consts
100
     * @param string $address Address passed to open method
101
     *
102
     * @return IoInterface
103
     */
104
    abstract protected function createIoInterface($type, $address);
105
106
    /** {@inheritdoc} */
107 65
    public function open($address, $context = null)
108
    {
109 65
        $this->resource = $this->createSocketResource(
110 65
            $address,
111 65
            $context ?: stream_context_get_default()
112 65
        );
113
114 59
        $result = false;
115 59
        if (is_resource($this->resource)) {
116 59
            $result              = true;
117 59
            $this->remoteAddress = $address;
118
119
            // https://bugs.php.net/bug.php?id=51056
120 59
            stream_set_blocking($this->resource, 0);
121
122
            // https://bugs.php.net/bug.php?id=52602
123 59
            stream_set_timeout($this->resource, 0, 0);
124
125 59
            $this->ioInterface = $this->createIoInterface(
126 59
                $this->resolveSocketType(),
127
                $address
128 59
            );
129
130 55
            $this->context->reset();
131 55
        }
132
133 55
        return $result;
134
    }
135
136
    /** {@inheritdoc} */
137 12
    public function close()
138
    {
139 12
        if ($this->resource) {
140 8
            $this->setDisconnectedState();
141 8
            stream_socket_shutdown($this->resource, STREAM_SHUT_RDWR);
142 8
            fclose($this->resource);
143 8
            $this->resource      = null;
144 8
            $this->remoteAddress = null;
145 8
        }
146 12
    }
147
148
    /** {@inheritdoc} */
149 31
    public function read(FramePickerInterface $picker, $isOutOfBand = false)
150
    {
151
        try {
152 31
            return $this->ioInterface->read($picker, $this->context, $isOutOfBand);
153 10
        } catch (ConnectionException $e) {
154 5
            $this->setDisconnectedState();
155 5
            throw $e;
156
        }
157
    }
158
159
    /** {@inheritdoc} */
160 10
    public function write($data, $isOutOfBand = false)
161
    {
162
        try {
163 10
            return $this->ioInterface->write($data, $this->context, $isOutOfBand);
164 10
        } catch (ConnectionException $e) {
165 5
            $this->setDisconnectedState();
166 5
            throw $e;
167
        }
168
    }
169
170
    /** {@inheritdoc} */
171 41
    public function getStreamResource()
172
    {
173 41
        return $this->resource;
174
    }
175
176
    /**
177
     * Get current socket type
178
     *
179
     * @return string One of SOCKET_TYPE_* consts
180
     */
181 59
    private function resolveSocketType()
182
    {
183 59
        $info = stream_get_meta_data($this->resource);
184 59
        if (!isset($info['stream_type'])) {
185 4
            return self::SOCKET_TYPE_UNKNOWN;
186
        }
187
188 55
        $parts = explode('/', $info['stream_type']);
189
        $map   = [
190 55
            'tcp'  => self::SOCKET_TYPE_TCP,
191 55
            'udp'  => self::SOCKET_TYPE_UDP,
192 55
            'udg'  => self::SOCKET_TYPE_UDG,
193 55
            'unix' => self::SOCKET_TYPE_UNIX,
194 55
        ];
195
196 55
        $regexp = '#^('. implode('|', array_keys($map)) . ')_socket$#';
197 55
        foreach ($parts as $part) {
198 55
            if (preg_match($regexp, $part, $pockets)) {
199 41
                return $map[$pockets[1]];
200
            }
201 14
        }
202
203 14
        return self::SOCKET_TYPE_UNKNOWN;
204
    }
205
206
    /**
207
     * Set disconnected state for socket
208
     *
209
     * @return void
210
     */
211 108
    private function setDisconnectedState()
212
    {
213 108
        $this->ioInterface = new DisconnectedIo($this);
214 108
    }
215
216
    /**
217
     * @inheritDoc
218
     */
219
    public function __toString()
220
    {
221
        return $this->remoteAddress ?: '"closed socket"';
222
    }
223
}
224