Passed
Push — master ( 970cfc...ce25dd )
by Sam
04:36 queued 02:29
created

SocketSocket   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Test Coverage

Coverage 83.87%

Importance

Changes 4
Bugs 0 Features 1
Metric Value
wmc 19
eloc 54
dl 0
loc 126
ccs 52
cts 62
cp 0.8387
rs 10
c 4
b 0
f 1

7 Methods

Rating   Name   Duplication   Size   Complexity  
A checkClosed() 0 4 2
A throwException() 0 4 1
A getLine() 0 17 3
A read() 0 14 3
A write() 0 9 3
A disconnect() 0 5 1
B __construct() 0 39 6
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 34
    public function __construct(
19
        string $host,
20
        int $port,
21
        float $connectTimeout
22
    ) {
23 34
        if (!extension_loaded('sockets')) {
24
            throw new \Exception('Sockets extension not found');
25
        }
26
27 34
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
0 ignored issues
show
Documentation Bug introduced by
It seems like socket_create(Pheanstalk...anstalk\Socket\SOL_TCP) can also be of type Socket. However, the property $socket is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
28 34
        if ($this->socket === false) {
29
            $this->throwException();
30
        }
31
32 34
        $timeout = [
33 34
            'sec' => intval($connectTimeout),
34 34
            'usec' => ($connectTimeout - intval($connectTimeout)) * 1000000
35
        ];
36
37 34
        $sendTimeout = socket_get_option($this->socket, SOL_SOCKET, SO_SNDTIMEO);
38 34
        $receiveTimeout = socket_get_option($this->socket, SOL_SOCKET, SO_RCVTIMEO);
39 34
        socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1);
40 34
        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
41 34
        socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
42 34
        if (socket_set_block($this->socket) === false) {
43
            throw new ConnectionException(0, "Failed to set socket to blocking mode");
44
        }
45
46 34
        $addresses = gethostbynamel($host);
47 34
        if ($addresses === false) {
48 2
            throw new ConnectionException(0, "Could not resolve hostname $host");
49
        }
50 32
        if (@socket_connect($this->socket, $addresses[0], $port) === false) {
51 2
            $error = socket_last_error($this->socket);
52 2
            throw new ConnectionException($error, socket_strerror($error));
53
        };
54
55 30
        socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $sendTimeout);
56 30
        socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $receiveTimeout);
57 30
    }
58
59
    /**
60
     * Writes data to the socket.
61
     *
62
     * @param string $data
63
     *
64
     * @return void
65
     */
66 29
    public function write(string $data): void
67
    {
68 29
        $this->checkClosed();
69 29
        while (!empty($data)) {
70 29
            $written = socket_write($this->socket, $data);
71 29
            if ($written === false) {
72
                $this->throwException();
73
            }
74 29
            $data = substr($data, $written);
75
        }
76 29
    }
77
78
    private function throwException()
79
    {
80
        $error = socket_last_error($this->socket);
81
        throw new SocketException(socket_strerror($error), $error);
82
    }
83
84 29
    private function checkClosed()
85
    {
86 29
        if (!isset($this->socket)) {
87
            throw new SocketException('The connection was closed');
88
        }
89 29
    }
90
91
    /**
92
     * Reads up to $length bytes from the socket.
93
     *
94
     * @return string
95
     */
96 27
    public function read(int $length): string
97
    {
98 27
        $this->checkClosed();
99
100 27
        $buffer = '';
101 27
        while (mb_strlen($buffer, '8BIT') < $length) {
102 27
            $result = socket_read($this->socket, $length - mb_strlen($buffer, '8BIT'));
103 27
            if ($result === false) {
104
                $this->throwException();
105
            }
106 27
            $buffer .= $result;
107
        }
108
109 27
        return $buffer;
110
    }
111
112 29
    public function getLine(): string
113
    {
114 29
        $this->checkClosed();
115
116 29
        $buffer = '';
117
        // Reading stops at \r or \n. In case it stopped at \r we must continue reading.
118 29
        while (substr($buffer, -1, 1) !== "\n") {
119 29
            $result = socket_read($this->socket, 1024, PHP_NORMAL_READ);
120 29
            if ($result === false) {
121
                $this->throwException();
122
            }
123 29
            $buffer .= $result;
124
        }
125
126
127
128 29
        return rtrim($buffer);
129
    }
130
131
    /**
132
     * Disconnect the socket; subsequent usage of the socket will fail.
133
     */
134 2
    public function disconnect(): void
135
    {
136 2
        $this->checkClosed();
137 2
        socket_close($this->socket);
138 2
        unset($this->socket);
139 2
    }
140
}
141