Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Thrift Laravel Package

apache/thrift

Apache Thrift is a scalable cross-language services framework combining a code generation engine with a software stack to build efficient RPC clients and servers across languages such as C++, Java, Python, PHP, Ruby, Node.js, and more.

Deep Wiki
Context7

Getting Started

apache/thrift on Packagist is only the PHP runtime (transports, protocols, server loops). The thrift compiler is a separate binary — you need both, and their versions must match.

composer require apache/thrift
# compiler: pick ONE and pin it everywhere
brew install thrift                 # macOS
apt-get install thrift-compiler     # Debian/Ubuntu
docker run --rm -v "$PWD:/d" thrift:0.20 thrift -version

Minimal first use case — call an existing Thrift service (Cassandra, HBase, an internal Java/Go/Python service) from Laravel:

  1. Get the .thrift IDL from the service owner. Do not hand-write structs.
  2. Generate PHP into a dedicated, git-ignored or committed directory:
thrift --gen php:server,psr4,nsglobal=App\\Thrift -out storage/thrift service.thrift
  1. Register the output for autoloading (the psr4 generator option emits a Types.php map you can feed to Composer):
"autoload": { "psr-4": { "App\\Thrift\\": "storage/thrift/" } }
  1. Make one call in php artisan tinker before writing any app code:
$socket = new TSocket('127.0.0.1', 9090);
$socket->setRecvTimeout(2000);
$transport = new TFramedTransport(new TBufferedTransport($socket));
$protocol = new TBinaryProtocol($transport);
$client = new App\Thrift\MyServiceClient($protocol);
$transport->open();
$result = $client->ping();
$transport->close();

Where to look first: the generated *Client.php / *Processor.php (that is your API surface), then Thrift/Transport/ and Thrift/Protocol/ in vendor/apache/thrift/lib/php/lib/Thrift/ to understand which transport stack the server expects.

Implementation Patterns

Config + container binding. Never construct clients inline. One config block per remote service, one factory, one binding:

// config/thrift.php
'catalog' => [
    'host' => env('CATALOG_THRIFT_HOST'),
    'port' => (int) env('CATALOG_THRIFT_PORT', 9090),
    'transport' => 'framed',   // framed | buffered | http
    'protocol' => 'binary',    // binary | compact | json
    'timeout_ms' => 2000,
],

A thin client wrapper is the main integration pattern. It owns connect/close, translates exceptions, and gives you a seam for retries and metrics:

final class ThriftClientFactory
{
    public function make(array $cfg): TTransport
    {
        $socket = new TSocket($cfg['host'], $cfg['port']);
        $socket->setSendTimeout($cfg['timeout_ms']);
        $socket->setRecvTimeout($cfg['timeout_ms']);

        $transport = $cfg['transport'] === 'framed'
            ? new TFramedTransport(new TBufferedTransport($socket), 512 * 1024)
            : new TBufferedTransport($socket);

        return $transport;
    }
}

final class CatalogGateway
{
    public function __construct(private ThriftClientFactory $factory, private array $cfg) {}

    public function call(callable $fn)
    {
        $transport = $this->factory->make($this->cfg);
        try {
            $transport->open();
            $client = new CatalogClient($this->protocol($transport));

            return $fn($client);
        } catch (TTransportException $e) {
            report($e);
            throw new RemoteServiceUnavailable('catalog', previous: $e);
        } finally {
            $transport->close();   // always
        }
    }
}

One connection per request, scoped. Bind the gateway as scoped() (not singleton()); under PHP-FPM the process ends anyway, and a stale singleton socket is the #1 source of "TSocket: timed out reading" in Octane.

Transport stack must match the server exactly. Framed vs buffered and binary vs compact are not interchangeable — negotiate this with the service owner and put it in config, not in code. Cassandra, for instance, requires framed + binary.

Serialization without the network is a strong pattern for queue payloads and test fixtures:

$buf = new TMemoryBuffer();
$proto = new TBinaryProtocol($buf);
$obj->write($proto);
$bytes = $buf->getBuffer();   // store in Redis / pass to a Go consumer

Contract-first across languages. Keep the .thrift files in one shared repo (or a contracts/ directory), generate into every consumer in CI, and treat a .thrift change as a versioned API break. Commit generated PHP or generate in a Docker build stage — pick one, never both.

Server side, if you expose services. Thrift servers are long-running processes, not HTTP requests. Run TSimpleServer/TThreadedServer from php artisan thrift:serve under Supervisor, or (more commonly in Laravel) skip the PHP server entirely and let a Go/Java service own the endpoint. If you must serve over HTTP, use the generated processor plus TPhpStream::in()/out() inside a single route action — no framework middleware applies to those bytes.

Testing. Generate a stub implementation of the generated *If interface and unit-test your handlers directly; use TMemoryBuffer round-trips for serialization tests. For integration, point the gateway at a container in docker-compose and assert on error translation, not on wire behaviour.

Multiplexing. If the remote host serves several services on one port, wrap the protocol in TMultiplexedProtocol with the correct service name — the error if you forget is a generic TApplicationException: Unknown function.

Gotchas and Tips

  • Version skew is the top failure mode. The PHP runtime and the compiler that generated your code must be the same minor line. Symptoms are Bad version in readMessageBegin, garbage field IDs, or structs that read as empty. Pin the compiler in CI (thrift -version assertion) and pin apache/thrift to the same constraint.
  • strictRead/strictWrite mismatch. TBinaryProtocol defaults to strictWrite = true; old Java servers with strictRead = false will reject your calls. Only flip these when the counterparty forces it.
  • Framed vs buffered. A framed client against an unframed server hangs waiting for the length prefix; unframed against framed returns partial messages. Both look like timeouts.
  • Generated code is huge and noisy. Exclude storage/thrift (or wherever you generate) from PHPStan, Pint, and PHPCS, but do keep it in autoload. Use the psr4 generator option; the legacy ThriftClassLoader was removed in recent versions, so old tutorials will not work.
  • Required fields throw at write time, not at construction. TProtocolException: Required field ... is unset appears when you finally call the RPC — validate your DTO before the call if the payload is user-driven.
  • Optional fields and defaults. Generated properties are public and untyped-ish; isset($obj->field) is the only reliable "was it sent" check. Always check before reading to distinguish 0/'' from absent.
  • Field IDs are forever. Never renumber, never reuse a retired ID, prefer optional over required when adding fields. This is your only real schema-evolution tool — there is no reflection fallback.
  • No connection pooling in PHP. TSocket is blocking, single-threaded, and has no async client. Under PHP-FPM accept the per-request connect cost (~1ms on a LAN); with Octane, keep one client per worker and add a health-check call plus reconnect-on-TTransportException.
  • Timeouts are silent killers. Default socket timeouts are effectively infinite in some builds. Always set setSendTimeout/setRecvTimeout; wrap calls in try/finally so close() runs on exceptions and you don't leak FDs.
  • TLS is inconsistent in the PHP runtime. Don't assume TSSLSocket parity with other languages — front the service with stunnel/Envoy, or use THttpClient over an HTTPS base URL.
  • i64 and integers. PHP ints are 64-bit on modern platforms, but the JSON protocol represents i64 as a string; TCompactProtocol uses zigzag varints that will silently mis-decode if the runtime version differs. Watch for ID fields that come back as strings.
  • Exceptions are generated classes, not \Exception subtypes you recognize. Server-thrown IDL exceptions deserialize into their generated classes; transport/protocol faults throw TTransportException/TProtocolException; everything else arrives as TApplicationException. Map all three at the gateway boundary or you leak Thrift types into controllers.
  • Debugging: TMemoryBuffer + bin2hex the framed bytes, compare against a known-good client in another language, and check the server log for the function name. thrift --gen php output has enough structure that a dd() on the generated client shows exactly which args struct went out.
  • Extension point worth using: implement TTransport yourself to add tracing/metrics (bytes sent/received, latency) or to route through an existing pooled HTTP client. That's the supported seam — subclassing generated clients is not.
  • License is fine (Apache-2.0), but it is infrastructure, not a Laravel package: no facade, no config publish, no Blade, no queue integration. Budget for the plumbing.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource