Passed
Push — master ( 1c6547...ceb2be )
by Sam
04:03
created

src/Socket/SocketSocket.php (1 issue)

1
<?php
2
3
4
namespace Pheanstalk\Socket;
5
6
use Pheanstalk\Contract\SocketInterface;
7
use Pheanstalk\Exception\ConnectionException;
8
use Pheanstalk\Exception\SocketException;
9
10
/**
11
 * A Socket implementation using the Sockets extension
12
 */
13
class SocketSocket implements SocketInterface
14
{
15
    /** @var resource */
16
    private $socket;
17
18 27
    public function __construct(
19
        string $host,
20
        int $port,
21
        int $connectTimeout
22
    ) {
23 27
        if (!extension_loaded('sockets')) {
24
            throw new \Exception('Sockets extension not found');
25
        }
26
27 27
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
28 27
        if ($this->socket === false) {
29
            $this->throwException();
30
        }
31
32
        $timeout = [
33 27
            'sec' => 0,
34
            'usec' => 100
35
        ];
36
37 27
        socket_set_option($this->socket, SOL_TCP, SO_KEEPALIVE, 1);
38 27
        socket_set_option($this->socket, SOL_SOCKET,SO_SNDTIMEO, $timeout);
39 27
        socket_set_option($this->socket, SOL_SOCKET,SO_RCVTIMEO, $timeout);
40
41 27
        $timeout = microtime(true) + $connectTimeout;
42 27
        $address = gethostbyname($host);
43
44 27
        while (microtime(true) < $timeout) {
45 27
            socket_clear_error($this->socket);
46 27
            if (@socket_connect($this->socket, $address, $port)) {
47 26
                return;
48
            };
49 1
            $error = socket_last_error($this->socket);
50 1
            if (!($error === SOCKET_EALREADY || $error === SOCKET_EINPROGRESS)) {
51 1
                break;
52
            }
53
        }
54
55 1
        throw new ConnectionException($error, socket_strerror($error));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $error does not seem to be defined for all execution paths leading up to this point.
Loading history...
56
    }
57
58
    /**
59
     * Writes data to the socket.
60
     *
61
     * @param string $data
62
     *
63
     * @return void
64
     */
65 25
    public function write(string $data): void
66
    {
67 25
        $this->checkClosed();
68 25
        while (!empty($data)) {
69 25
            $written = socket_write($this->socket, $data);
70 25
            if ($written === false) {
71
                $this->throwException();
72
            }
73 25
            $data = substr($data, $written);
74
        }
75
    }
76
77
    private function throwException()
78
    {
79
        $error = socket_last_error($this->socket);
80
        throw new SocketException(socket_strerror($error), $error);
81
    }
82
83 25
    private function checkClosed()
84
    {
85 25
        if (!isset($this->socket)) {
86
            throw new SocketException('The connection was closed');
87
        }
88
    }
89
90
    /**
91
     * Reads up to $length bytes from the socket.
92
     *
93
     * @return string
94
     */
95 24
    public function read(int $length): string
96
    {
97 24
        $this->checkClosed();
98
99 24
        $buffer = '';
100 24
        while (mb_strlen($buffer, '8BIT') < $length) {
101 24
            $result = socket_read($this->socket, $length - mb_strlen($buffer, '8BIT'));
102 24
            if ($result === false) {
103
                $this->throwException();
104
            }
105 24
            $buffer .= $result;
106
        }
107
108 24
        return $buffer;
109
    }
110
111 25
    public function getLine(): string
112
    {
113 25
        $this->checkClosed();
114
115 25
        $buffer = '';
116
        // Reading stops at \r or \n. In case it stopped at \r we must continue reading.
117 25
        while(substr($buffer, -1, 1) !== "\n") {
118 25
            $result = socket_read($this->socket, 1024, PHP_NORMAL_READ);
119 25
            if ($result === false) {
120
                $this->throwException();
121
            }
122 25
            $buffer .= $result;
123
        }
124
125
126
127 25
        return rtrim($buffer);
128
    }
129
130
    /**
131
     * Disconnect the socket; subsequent usage of the socket will fail.
132
     */
133 1
    public function disconnect(): void
134
    {
135 1
        $this->checkClosed();
136 1
        socket_close($this->socket);
137 1
        unset($this->socket);
138
    }
139
}