yakeing/php_fsockopen
yakeing/php_fsockopen is a lightweight PHP wrapper around fsockopen for low-level socket and HTTP connections. It provides a simple way to open sockets, send requests, and read responses without cURL, making it useful for small integrations or environments where cURL isn't available.
composer require yakeing/php_fsockopen
There is nothing to register. This is a plain PHP socket wrapper — no service provider, no facade, no config file, no published assets. After composer require, the only thing Laravel does is autoload the class.
Where to look first
vendor/yakeing/php_fsockopen/README.md — it is short and contains the only canonical usage example. Treat it as the API contract; the method names below are representative, so confirm against src/ before writing production code.vendor/yakeing/php_fsockopen/src/ — usually one or two classes. Read the whole thing (it is small) so you know exactly how connect, write/send, read, and close behave, especially return values.composer show yakeing/php_fsockopen — confirm the installed version and PHP constraint.First use case
The realistic day-to-day use is talking to something that is not HTTP: an internal TCP service, a legacy SMTP/IMAP/LDAP handshake, a local Unix-socket daemon, or a line-oriented text protocol. It is a replacement for hand-rolling fsockopen(), not for Laravel's HTTP client.
A good first experiment is an Artisan command that does a one-shot request/response:
// app/Console/Commands/PingDaemon.php
$socket = new \Yakeing\Fsockopen\Fsockopen();
try {
$socket->connect('tcp://127.0.0.1', 9000, 2); // connect timeout in seconds
$socket->write("PING\r\n");
$reply = $socket->read();
$this->info(trim($reply));
} finally {
$socket->close();
}
If that works, wrap it behind an interface (see below) and never instantiate the package class anywhere else.
Wrap it in a small adapter and bind it to the container. The package has no Laravel integration, so you create one. Define an interface so the socket can be faked in tests, and resolve config from config/services.php rather than hard-coding hosts.
// config/services.php
'daemon' => [
'dsn' => env('DAEMON_DSN', 'tcp://127.0.0.1:9000'),
'timeout' => (int) env('DAEMON_TIMEOUT', 2),
],
// app/Services/DaemonClient.php
class DaemonClient implements DaemonClientContract
{
public function __construct(private array $config) {}
public function request(string $payload): string
{
$socket = new \Yakeing\Fsockopen\Fsockopen();
try {
[$scheme, $rest] = explode('://', $this->config['dsn'], 2);
[$host, $port] = array_pad(explode(':', $rest, 2), 2, null);
$socket->connect($this->config['dsn'], $port, $this->config['timeout']);
$socket->write($payload);
return $socket->read();
} finally {
$socket->close();
}
}
}
Pattern: one connection per operation. In a web request or a queued job, open, transact, close. Do not memoize the socket in a singleton — under Octane or a long-running queue worker the resource will be stale and you will get silent read timeouts.
Pattern: read in a loop until you have what you need. read() typically returns whatever is currently in the buffer. For anything larger than a line, loop with a terminator or length check and enforce a total deadline:
$buffer = '';
$deadline = microtime(true) + 5.0;
while (microtime(true) < $deadline) {
$chunk = $socket->read();
if ($chunk === false || $chunk === '') {
break; // EOF or timeout — check stream metadata if the wrapper exposes it
}
$buffer .= $chunk;
if (str_contains($buffer, "\r\n\r\n")) {
break;
}
}
Pattern: push fire-and-forget traffic to the queue. For logging/metrics-style sends (UDP or a Unix datagram socket to a local collector), dispatch a job with dispatch()->afterResponse() or use Bus::batch so a slow or dead daemon never blocks the HTTP response.
Pattern: UDP and Unix sockets use the same class, different DSN. udp://host:port for datagram metrics, unix:///var/run/service.sock (or udg:// for Unix datagrams) for local daemons. This is genuinely the reason to reach for the package over raw fsockopen calls scattered through the codebase — one call shape, protocol in the DSN.
Pattern: scheduled health check. A scheduled command that connects and closes is a cheap liveness probe. Record the result with Log::warning or a metric; do not let a flapping daemon page anyone.
Testing. Fake at the adapter level: bind DaemonClientContract to a stub in the container and assert on the payload. Do not try to unit-test the package class itself; integration-test the adapter against a real local socket only in CI where you control the process.
php -l on every file under src/ on your target PHP version. Pre-PHP 8 code of this era often uses removed constructs (curly-brace string offsets $s{0}, dynamic properties without #[AllowDynamicProperties], etc.). If it errors on PHP 8.2/8.3, fork it or replace it.connect() only covers establishing the connection. Once connected, a read() on a silent peer blocks. If the wrapper exposes the stream resource or a timeout setter, call stream_set_timeout(); otherwise run the call inside a job with a retryUntil/timeout so a hung worker is killed.false vs '' vs partial data. read() returning '' can mean "nothing yet", "EOF", or "timed out". If the wrapper does not surface stream_get_meta_data(), you cannot distinguish them reliably — this is the single biggest reason to keep the adapter thin and add your own deadline loop.fwrite can write fewer bytes than requested. If write()/send() does not loop internally, verify the return value and re-send the remainder. For binary protocols this matters; for line protocols it usually does not.trim() protocol data. Binary payloads and length-prefixed frames break under trim, mb_*, or json_encode without care. Use strlen, raw bytes, and an explicit framing rule.tcp://[::1]:9000, not tcp://::1:9000.ssl:// / tls:// wrappers work only if the wrapper passes stream context options through. Verify certificate verification settings before trusting it; there is no Laravel-level verify => false knob.RateLimited or backoff(). Do not retry a partial write on a non-idempotent protocol.fsockopen emits PHP warnings and returns false. If the package suppresses them with @, your try/catch will not fire — check the return value explicitly and convert to an exception yourself.vendor/, those modified files must be made available under MPL. Prefer a fork or your own wrapper over in-place vendor edits.nc -vz host port to test reachability outside PHP, tcpdump -i any -A port 9000 to see the actual bytes, and strace -f -e trace=network php artisan your:command when you suspect a blocking call. Log the DSN and the time-to-first-byte per request — that tells you far more than a stack trace.How can I help you explore Laravel packages today?