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

Consul Php Sdk Laravel Package

sensiolabs/consul-php-sdk

A thin PHP wrapper around the Consul HTTP API, covering agent, catalog, health, kv, session and txn services. Methods return a ConsulResponse and throw exceptions on 4xx/5xx errors, with handy lock and semaphore helpers for distributed coordination.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the maintained package, not the one in the title. The project was renamed when it moved to the FriendsOfPHP org: the Composer name is friendsofphp/consul-php-sdk and the namespace changed from SensioLabs\Consul to Consul. If you run composer require sensiolabs/consul-php-sdk you get the abandoned v4 line.

composer require friendsofphp/consul-php-sdk   # v5.4+ requires PHP >= 8.2, symfony/http-client >= 6.4
docker run -d --name=dev-consul -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul

Where to look first: Consul\Client (the HTTP plumbing and base URI/token handling), then src/Services/* — one class per Consul API area (KV, Session, Agent, Catalog, Health, Txn, plus v5.4's ACL, Config, Connect, Operator, Snapshot, Status, PreparedQuery, DiscoveryChain, Peering, Event, Coordinate). Skim the release/v5.4.0.md notes; they explain the option whitelisting and the CONSUL_HTTP_TOKEN behaviour you'll rely on.

First use case — register the app and read config from KV. Bind one configured client in a service provider so nothing else in the app constructs services ad hoc:

// config/services.php
'consul' => [
    'base_uri' => env('CONSUL_HTTP_ADDR', 'http://127.0.0.1:8500'),
    'token'    => env('CONSUL_HTTP_TOKEN'),
],

// app/Providers/ConsulServiceProvider.php
public function register(): void
{
    $this->app->singleton(Consul\ClientInterface::class, fn () => new Consul\Client(
        config('services.consul.base_uri'),
        config('services.consul.token'),
    ));
    $this->app->bind(Consul\Services\KV::class, fn ($app) => new Consul\Services\KV($app->make(Consul\ClientInterface::class)));
    $this->app->bind(Consul\Services\Agent::class, fn ($app) => new Consul\Services\Agent($app->make(Consul\ClientInterface::class)));
}

Then the three-line smoke test from the README, wrapped so failures are visible:

$kv = app(Consul\Services\KV::class);
$kv->put('myapp/config/feature_flags', json_encode(['new_checkout' => true]));
$flags = json_decode($kv->get('myapp/config/feature_flags', ['raw' => true])->getBody(), true);

Keep a docker compose service for Consul in your local stack and point CONSUL_HTTP_ADDR at it — otherwise every developer needs a native Consul install just to boot the app.

Implementation Patterns

Wrap the SDK behind your own repository class. Every Consul\Services\* call returns Consul\ConsulResponse and throws on 4xx/5xx, so leaking it into controllers/jobs couples your domain to Consul's wire format. A thin ConsulConfigRepository with get()/put()/watch() methods makes it swappable for a config()-backed fake in tests — there is no built-in HTTP fake.

Service registration in a bootstrapper, heartbeat in the scheduler. Register on deploy/boot with Agent::registerService(), then pass a TTL check from a scheduled command:

// AppServiceProvider::boot() — or a dedicated `consul:register` artisan command run by your entrypoint
$agent->registerService([
    'ID'      => $serviceId = config('app.name').'-'.gethostname().'-'.getmypid(),
    'Name'    => config('app.name'),
    'Address' => gethostbyname(gethostname()),
    'Port'    => (int) config('app.port'),
    'Check'   => ['TTL' => '30s', 'DeregisterCriticalServiceAfter' => '90s'],
]);

// app/Console/Kernel.php / routes/console.php
$schedule->call(fn () => app(Consul\Services\Agent::class)->passCheck($serviceId))
    ->everyFifteenSeconds();

Prefer TTL checks over script checks for PHP containers — there's no supervisor inside the container to answer an HTTP /health probe reliably during deploy churn.

Discovery: query Health, not Catalog. Health::service('billing', ['passing' => true, 'tag' => ['v1']]) gives you only instances whose checks pass. Cache the result (30–60s) in Laravel's cache — Consul lookups on every outbound request add a full HTTP round trip to every call path.

Blocking queries belong in a daemon, not a request. For push-style config propagation, run an artisan command under Supervisor that loops with the returned X-Consul-Index:

$index = 0;
while (true) {
    $res = $kv->get('myapp/config', ['index' => $index, 'wait' => '5m', 'raw' => true]);
    $index = $res->getHeaders()['X-Consul-Index'][0] ?? $index;
    Cache::forever('consul.config', $res->getBody());
}

Set the Symfony HTTP client timeout well above wait (e.g. 330s), otherwise the transport blows up before Consul returns. This is also why blocking queries must never run inside an FPM/Octane request — they pin a worker for up to five minutes.

Distributed locks for cross-host mutual exclusion. Laravel's withoutOverlapping() uses your cache store; it will not stop two pods on different hosts. Use LockHandler / MultiLockHandler around scheduled jobs, always in a try/finally, and always check the boolean return rather than catching:

$lock = new Consul\Helper\MultiLockHandler($resources, 60, app(Session::class), app(KV::class), 'myapp/locks/');
if (! $lock->lock()) { return; }
try { $this->doWork(); } finally { $lock->release(); }

MultiSemaphore + Consul\Helper\Resource(name, limit, weight) is the right tool for capping concurrent queue workers across a fleet (Resource('importer', 3, 1) = max 3 concurrent).

Use Txn for atomic multi-key writes. Read-modify-write on separate put() calls is not atomic; a config rollout touching several keys can be observed half-applied. Consul\Services\Txn::create([...]) gives you check-and-set semantics in one request.

Consistency options are now available and worth setting deliberately. ['consistent' => true] on Catalog/Health reads after a write you just made, ['stale' => true] for high-throughput reads where a few seconds of lag is fine. And pass ['dc' => 'eu-west-1'] on any non-agent endpoint — v5.4 accepts it everywhere, which finally removes bespoke URL plumbing for cross-DC reads.

Pin the client to Consul\ClientInterface in constructor injection. Since 5.1 the SDK accepts the interface, so tests can pass an in-memory stub of the client rather than a real socket.

Gotchas and Tips

  • The 4xx exception class in the README is wrong. You'll see ClientException for 4xx (correct) and the README's ServeException for 5xx — the actual class is Consul\Exception\ServerException. Catch both, and catch Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface separately: connection refused / DNS failures are not wrapped in a Consul exception, so catch (ClientException|ServerException $e) alone will not make your code resilient to an agent outage.
  • json() throws on non-JSON bodies. KV values are arbitrary strings. Use ->getBody() for raw payloads and only call ->json() on endpoints you know return JSON.
  • Lock contention is not an exception. KV::put(..., ['acquire' => $sessionId]) returns false in the JSON body when another node holds the lock. Check the value; don't wrap in try/catch.
  • Sessions expire; locks can vanish mid-job. A Consul session has a TTL and will be invalidated if the node dies or the heartbeat stops. Design locked jobs to be idempotent and re-check that the lock is still held before the final commit. LockHandler handles renewal for you, but only while the process lives.
  • config:cache will break your token if you read env() at runtime. Map CONSUL_HTTP_TOKEN into config/services.php and read it through config(). The SDK also reads the env var itself when no token is passed — pick one mechanism and be consistent, or you'll get "works on my machine" auth behaviour between local (env file) and production (injected secret).
  • Option keys are whitelisted in v5.4. Unknown or misspelled option names (e.g. 'tag' vs 'tags', 'index' casing) may simply be dropped rather than erroring. When a blocking query spins hot or a filter appears to do nothing, dump the built query string from the client — the silent-drop failure mode is the single most confusing thing about this SDK.
  • Multi-valued options need arrays: ['tag' => ['v1', 'primary']], not a comma-joined string.
  • Don't store secrets in KV. Consul KV is not encrypted at rest and is readable to anyone with a token. Keep credentials in Vault/secret manager; use KV for non-sensitive dynamic config only.
  • Don't put KV on a hot path. Every get() is a network round trip; reading config per-request in a middleware is a latency regression. Read once into the Laravel config repository at boot, then refresh via the blocking-query daemon.
  • Registration address pitfalls. 127.0.0.1 registered from inside a container is unreachable from other nodes. Resolve the container's routable IP or use CONSUL_SERVICE_ADDR explicitly, and always set DeregisterCriticalServiceAfter so dead pods don't linger in the catalog for days.
  • Extension point: if you need retries/circuit breaking, wrap at the Consul\ClientInterface boundary or a repository layer. The SDK's Client is deliberately thin and exposes no retry hook — don't fork it, decorate it.
  • Testing: vendor/bin/simple-phpunit (the SDK's own suite) needs an agent at CONSUL_HTTP_ADDR; for your app's tests, stub the repository or ClientInterface bindings — spinning up Consul per test is slow and racy.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings