|
2 | 2 |
|
3 | 3 | namespace Rcalicdan\FiberAsync\EventLoop\ValueObjects;
|
4 | 4 |
|
5 |
| -use Rcalicdan\FiberAsync\Promise\Interfaces\PromiseInterface; |
6 |
| -use Rcalicdan\FiberAsync\Socket\AsyncSocketOperations; |
7 |
| -use Rcalicdan\FiberAsync\Socket\Exceptions\SocketException; |
8 |
| - |
9 | 5 | /**
|
10 |
| - * Socket wrapper for asynchronous socket operations. |
| 6 | + * Socket value object representing socket connection data. |
11 | 7 | *
|
12 |
| - * This class provides a high-level interface for asynchronous socket operations |
13 |
| - * including reading, writing, and connection management. It wraps a socket resource |
14 |
| - * and delegates operations to the AsyncSocketOperations handler. |
| 8 | + * This is a pure value object that holds socket-related data without dependencies |
| 9 | + * or business logic. It's immutable and focused on data representation. |
15 | 10 | */
|
16 |
| -class Socket |
| 11 | +final class Socket |
17 | 12 | {
|
18 | 13 | /**
|
19 | 14 | * The underlying socket resource.
|
20 | 15 | *
|
21 |
| - * @var resource |
| 16 | + * @var resource|null |
22 | 17 | */
|
23 | 18 | private $resource;
|
24 | 19 |
|
25 | 20 | /**
|
26 |
| - * Handler for asynchronous socket operations. |
27 |
| - */ |
28 |
| - private AsyncSocketOperations $operations; |
29 |
| - |
30 |
| - /** |
31 |
| - * Whether the socket has been closed. |
| 21 | + * Whether the socket has been marked as closed. |
32 | 22 | */
|
33 |
| - private bool $isClosed = false; |
| 23 | + private bool $isClosed; |
34 | 24 |
|
35 | 25 | /**
|
36 |
| - * Default byte size for read operations. |
| 26 | + * Socket metadata (address, port, etc.) |
| 27 | + * |
| 28 | + * @var array<string, mixed> |
37 | 29 | */
|
38 |
| - private const DEFAULT_BYTE_SIZE = 8192; |
| 30 | + private array $metadata; |
39 | 31 |
|
40 | 32 | /**
|
41 |
| - * Creates a new Socket instance. |
| 33 | + * Creates a new Socket value object. |
42 | 34 | *
|
43 |
| - * @param resource $resource The socket resource |
44 |
| - * @param AsyncSocketOperations $operations Handler for async operations |
| 35 | + * @param resource|null $resource The socket resource |
| 36 | + * @param bool $isClosed Whether the socket is closed |
| 37 | + * @param array<string, mixed> $metadata Additional socket metadata |
45 | 38 | *
|
46 |
| - * @throws \TypeError If resource is not a valid resource type |
| 39 | + * @throws \TypeError If resource is not a valid resource type or null |
47 | 40 | */
|
48 |
| - public function __construct($resource, AsyncSocketOperations $operations) |
| 41 | + public function __construct($resource, bool $isClosed = false, array $metadata = []) |
49 | 42 | {
|
50 |
| - if (! is_resource($resource)) { |
51 |
| - throw new \TypeError('Expected resource, got '.gettype($resource)); |
| 43 | + if (!is_resource($resource) && $resource !== null) { |
| 44 | + throw new \TypeError('Expected resource or null, got ' . gettype($resource)); |
52 | 45 | }
|
53 | 46 |
|
54 | 47 | $this->resource = $resource;
|
55 |
| - $this->operations = $operations; |
56 |
| - stream_set_blocking($this->resource, false); |
| 48 | + $this->isClosed = $isClosed; |
| 49 | + $this->metadata = $metadata; |
57 | 50 | }
|
58 | 51 |
|
59 | 52 | /**
|
60 |
| - * Asynchronously reads data from the socket. |
| 53 | + * Gets the underlying socket resource. |
61 | 54 | *
|
62 |
| - * @param int|null $length Maximum number of bytes to read (default: 8192) |
63 |
| - * @param float|null $timeout Timeout in seconds (default: 10.0) |
64 |
| - * @return PromiseInterface<string> Promise that resolves with the read data |
| 55 | + * @return resource|null The socket resource |
| 56 | + */ |
| 57 | + public function getResource() |
| 58 | + { |
| 59 | + return $this->resource; |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Checks if the socket is marked as closed. |
65 | 64 | *
|
66 |
| - * @throws SocketException If the socket is closed |
| 65 | + * @return bool True if the socket is closed, false otherwise |
67 | 66 | */
|
68 |
| - public function read(?int $length = null, ?float $timeout = 10.0): PromiseInterface |
| 67 | + public function isClosed(): bool |
69 | 68 | {
|
70 |
| - if ($this->isClosed) { |
71 |
| - return $this->operations->getAsyncOps()->rejected(new SocketException('Socket is closed.')); |
72 |
| - } |
| 69 | + return $this->isClosed; |
| 70 | + } |
73 | 71 |
|
74 |
| - $readLength = $length ?? self::DEFAULT_BYTE_SIZE; |
| 72 | + /** |
| 73 | + * Gets socket metadata. |
| 74 | + * |
| 75 | + * @return array<string, mixed> The metadata array |
| 76 | + */ |
| 77 | + public function getMetadata(): array |
| 78 | + { |
| 79 | + return $this->metadata; |
| 80 | + } |
75 | 81 |
|
76 |
| - return $this->operations->read($this, $readLength, $timeout); |
| 82 | + /** |
| 83 | + * Gets a specific metadata value. |
| 84 | + * |
| 85 | + * @param string $key The metadata key |
| 86 | + * @param mixed $default Default value if key doesn't exist |
| 87 | + * @return mixed The metadata value or default |
| 88 | + */ |
| 89 | + public function getMetadataValue(string $key, mixed $default = null): mixed |
| 90 | + { |
| 91 | + return $this->metadata[$key] ?? $default; |
77 | 92 | }
|
78 | 93 |
|
79 | 94 | /**
|
80 |
| - * Asynchronously writes data to the socket. |
| 95 | + * Creates a new Socket instance with updated closed status. |
81 | 96 | *
|
82 |
| - * @param string $data The data to write |
83 |
| - * @param float|null $timeout Timeout in seconds (default: 10.0) |
84 |
| - * @return PromiseInterface<int> Promise that resolves with the number of bytes written |
| 97 | + * @param bool $isClosed The new closed status |
| 98 | + * @return self A new Socket instance |
| 99 | + */ |
| 100 | + public function withClosedStatus(bool $isClosed): self |
| 101 | + { |
| 102 | + return new self($this->resource, $isClosed, $this->metadata); |
| 103 | + } |
| 104 | + |
| 105 | + /** |
| 106 | + * Creates a new Socket instance with additional metadata. |
85 | 107 | *
|
86 |
| - * @throws SocketException If the socket is closed |
| 108 | + * @param array<string, mixed> $metadata The metadata to merge |
| 109 | + * @return self A new Socket instance |
87 | 110 | */
|
88 |
| - public function write(string $data, ?float $timeout = 10.0): PromiseInterface |
| 111 | + public function withMetadata(array $metadata): self |
89 | 112 | {
|
90 |
| - if ($this->isClosed) { |
91 |
| - return $this->operations->getAsyncOps()->rejected(new SocketException('Socket is closed.')); |
92 |
| - } |
| 113 | + return new self($this->resource, $this->isClosed, array_merge($this->metadata, $metadata)); |
| 114 | + } |
93 | 115 |
|
94 |
| - return $this->operations->write($this, $data, $timeout); |
| 116 | + /** |
| 117 | + * Creates a new Socket instance with a single metadata value. |
| 118 | + * |
| 119 | + * @param string $key The metadata key |
| 120 | + * @param mixed $value The metadata value |
| 121 | + * @return self A new Socket instance |
| 122 | + */ |
| 123 | + public function withMetadataValue(string $key, mixed $value): self |
| 124 | + { |
| 125 | + $newMetadata = $this->metadata; |
| 126 | + $newMetadata[$key] = $value; |
| 127 | + |
| 128 | + return new self($this->resource, $this->isClosed, $newMetadata); |
95 | 129 | }
|
96 | 130 |
|
97 | 131 | /**
|
98 |
| - * Closes the socket connection. |
| 132 | + * Checks if the socket resource is valid. |
99 | 133 | *
|
100 |
| - * This method ensures the socket is properly closed and cleaned up. |
101 |
| - * It's safe to call multiple times - subsequent calls will be ignored. |
| 134 | + * @return bool True if resource is valid, false otherwise |
102 | 135 | */
|
103 |
| - public function close(): void |
| 136 | + public function hasValidResource(): bool |
104 | 137 | {
|
105 |
| - if (! $this->isClosed) { |
106 |
| - $this->isClosed = true; |
107 |
| - $this->operations->close($this); |
108 |
| - } |
| 138 | + return is_resource($this->resource); |
109 | 139 | }
|
110 | 140 |
|
111 | 141 | /**
|
112 |
| - * Gets the underlying socket resource. |
| 142 | + * Gets the socket type from metadata. |
113 | 143 | *
|
114 |
| - * @return resource The socket resource |
| 144 | + * @return string|null The socket type or null if not set |
115 | 145 | */
|
116 |
| - public function getResource() |
| 146 | + public function getType(): ?string |
117 | 147 | {
|
118 |
| - return $this->resource; |
| 148 | + return $this->getMetadataValue('type'); |
119 | 149 | }
|
120 | 150 |
|
121 | 151 | /**
|
122 |
| - * Checks if the socket is closed. |
| 152 | + * Gets the socket address from metadata. |
123 | 153 | *
|
124 |
| - * @return bool True if the socket is closed, false otherwise |
| 154 | + * @return string|null The socket address or null if not set |
125 | 155 | */
|
126 |
| - public function isClosed(): bool |
| 156 | + public function getAddress(): ?string |
127 | 157 | {
|
128 |
| - return $this->isClosed; |
| 158 | + return $this->getMetadataValue('address'); |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Gets the socket port from metadata. |
| 163 | + * |
| 164 | + * @return int|null The socket port or null if not set |
| 165 | + */ |
| 166 | + public function getPort(): ?int |
| 167 | + { |
| 168 | + return $this->getMetadataValue('port'); |
| 169 | + } |
| 170 | + |
| 171 | + /** |
| 172 | + * String representation of the socket. |
| 173 | + * |
| 174 | + * @return string |
| 175 | + */ |
| 176 | + public function __toString(): string |
| 177 | + { |
| 178 | + $address = $this->getAddress() ?? 'unknown'; |
| 179 | + $port = $this->getPort() ?? 'unknown'; |
| 180 | + $status = $this->isClosed ? 'closed' : 'open'; |
| 181 | + |
| 182 | + return "Socket({$address}:{$port}, {$status})"; |
129 | 183 | }
|
130 | 184 |
|
131 | 185 | /**
|
132 |
| - * Destructor ensures the socket is properly closed. |
| 186 | + * Compare two Socket instances for equality. |
133 | 187 | *
|
134 |
| - * @return void |
| 188 | + * @param Socket $other The other socket to compare |
| 189 | + * @return bool True if sockets are equal, false otherwise |
135 | 190 | */
|
136 |
| - public function __destruct() |
| 191 | + public function equals(Socket $other): bool |
137 | 192 | {
|
138 |
| - $this->close(); |
| 193 | + return $this->resource === $other->resource |
| 194 | + && $this->isClosed === $other->isClosed |
| 195 | + && $this->metadata === $other->metadata; |
139 | 196 | }
|
140 | 197 | }
|
0 commit comments