AbstractClient   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 2
dl 0
loc 105
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 8 2
A connect() 0 11 4
A getPeerName() 0 9 3
A send() 0 7 2
A write() 0 14 3
1
<?php
2
3
namespace Helix\Socket;
4
5
use Throwable;
6
7
/**
8
 * Abstract client socket.
9
 */
10
abstract class AbstractClient extends AbstractSocket {
11
12
    /**
13
     * The peer's name as `<address>:<port>`,
14
     * or `<pid>:0` for Unix sockets,
15
     * or `?<id>` if a name can't be derived (e.g. the socket is closed).
16
     *
17
     * @see getPeerName()
18
     *
19
     * @return string
20
     */
21
    public function __toString () {
22
        try {
23
            return implode(':', $this->getPeerName());
24
        }
25
        catch (Throwable $e) {
26
            return "?{$this->resource}";
27
        }
28
    }
29
30
    /**
31
     * Connects the socket to a peer.
32
     * For datagram sockets this sets the target recipient.
33
     *
34
     * For non-blocking sockets, the `SOCKET_EINPROGRESS` and `SOCKET_EWOULDBLOCK` errors
35
     * are ignored and cleared, because they're expected.
36
     *
37
     * @see https://php.net/socket_connect
38
     *
39
     * @param string $address The remote network address, or local Unix file name.
40
     * @param int $port The remote port. Unix sockets ignore this entirely.
41
     * @return $this
42
     * @throws SocketError
43
     */
44
    public function connect (string $address, int $port = 0) {
45
        if (!@socket_connect($this->resource, $address, $port)) {
46
            // ignore expected errors for non-blocking connections
47
            $errno = SocketError::getLast($this->resource);
48
            if ($errno !== SOCKET_EINPROGRESS and $errno !== SOCKET_EWOULDBLOCK) {
49
                // $address or $port could be bad, or fallback to EINVAL
50
                throw new SocketError($errno, SOCKET_EINVAL);
51
            }
52
        }
53
        return $this;
54
    }
55
56
    /**
57
     * The peer's address and port, or Unix PID and port `0`.
58
     *
59
     * @see https://php.net/socket_getpeername
60
     *
61
     * @return array `[ 0 => address, 1 => port ]`
62
     * @throws SocketError
63
     */
64
    public function getPeerName (): array {
65
        if ($this->getDomain() === AF_UNIX) {
66
            return [$this->getOption(17), 0]; // SO_PEERCRED is not exposed by PHP
67
        }
68
        if (!@socket_getpeername($this->resource, $addr, $port)) {
69
            throw new SocketError($this->resource, SOCKET_EAFNOSUPPORT);
70
        }
71
        return [$addr, $port];
72
    }
73
74
    /**
75
     * Sends data to the remote peer.
76
     *
77
     * @see https://php.net/socket_send
78
     *
79
     * @param string $data
80
     * @param int $flags `MSG_*`
81
     * @return int Total bytes sent.
82
     * @throws SocketError
83
     */
84
    public function send (string $data, int $flags = 0): int {
85
        $count = @socket_send($this->resource, $data, strlen($data), $flags);
86
        if ($count === false) {
87
            throw new SocketError($this->resource); // reliable errno
88
        }
89
        return $count;
90
    }
91
92
    /**
93
     * Sends all data (forced blocking).
94
     *
95
     * @param string $data
96
     * @return $this
97
     * @throws SocketError `int` total bytes sent is set as the extra data.
98
     */
99
    public function write (string $data) {
100
        $length = strlen($data);
101
        $total = 0;
102
        while ($total < $length) {
103
            try {
104
                $total += $this->awaitWritable()->send(substr($data, $total));
105
            }
106
            catch (SocketError $e) {
107
                $e->setExtra($total);
108
                throw $e;
109
            }
110
        }
111
        return $this;
112
    }
113
114
}
115