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

friendsofphp/consul-php-sdk

A thin PHP wrapper around the Consul HTTP API, covering agent, catalog, health, kv, session, and txn services. It returns ConsulResponse objects, throws typed exceptions, and includes helpers for locks and semaphores.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and verify the version floor first. friendsofphp/consul-php-sdk 5.4 requires PHP ≥ 8.2, symfony/http-client ≥ 6.4, and psr/log ≥ 2. On Laravel 11/12 that's a clean install; on Laravel 9 you'll be fighting the dependency graph, so plan the platform upgrade and the SDK adoption as one ticket.

composer require friendsofphp/consul-php-sdk

# Local dev agent (matches what the SDK's own tests expect)
docker run -d --name=dev-consul -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul

Where to look first, in order:

  1. src/Services/ — every Consul API surface is one class (KV, Agent, Catalog, Health, Session, Txn, plus the 5.4 additions: ACL, Connect, Operator, Snapshot, Status, PreparedQuery, Peering, Coordinate, DiscoveryChain, Event, Config). The method signatures are the documentation.
  2. src/Client.php — the DSN/option handling and the CONSUL_HTTP_TOKEN / CONSUL_HTTP_ADDR environment fallbacks.
  3. tests/ — these are the real usage examples; the README is deliberately thin.
  4. changelog/5.4.0.md — the release that went from six services to the full CE API and whitelisted all documented options. If you used this package (or its SensioLabs\Consul ancestor) before, read this before assuming old behavior.

First use case — pull config out of KV in a service provider. It's the lowest-risk, highest-value entry point and needs no Consul-side plumbing:

// app/Providers/ConsulServiceProvider.php
use Consul\Client;
use Consul\ClientInterface;
use Consul\Services\KV;

public function register(): void
{
    $this->app->singleton(ClientInterface::class, fn () => new Client(
        config('consul.dsn'),          // e.g. http://consul.service.consul:8500
    ));

    $this->app->bind(KV::class, fn ($app) => new KV($app->make(ClientInterface::class)));
}

public function boot(): void
{
    if (! config('consul.enabled')) {
        return;
    }

    try {
        $raw = $this->app->make(KV::class)
            ->get('config/' . config('app.env') . '/features', ['raw' => true])
            ->getBody();

        config(['features' => json_decode($raw, true, flags: JSON_THROW_ON_ERROR)]);
    } catch (\Throwable $e) {
        report($e); // degrade to local defaults, never fail boot
    }
}

Note there is no package service provider — no auto-discovery, no config/consul.php, no facade. You write that binding yourself; it's ~20 lines and the first thing to do on day one. There is no consul.php config to publish, so config/consul.php is yours to define (dsn, token, dc, enabled).

Implementation Patterns

Wrap the client, never touch it from controllers. The SDK is framework-agnostic and stateless, so a single container binding plus a thin domain wrapper keeps Consul concepts (keys, sessions, indexes) out of your HTTP layer.

Service registration — let the local agent do it. Consul's Agent endpoints are agent-local: calling registerService() against a remote server registers the agent's node, not your app, and your health checks then probe the wrong host. In containers, run a Consul agent sidecar per pod and register against 127.0.0.1:8500, or register through Consul's HTTP API from the node itself. Typical boot hook:

$agent = new Agent($client);

$agent->registerService([
    'ID'      => $serviceId = gethostname() . '-' . getmypid(),
    'Name'    => 'billing-api',
    'Port'    => (int) config('app.port', 80),
    'Tags'    => ['php', 'v' . config('app.version')],
    'Check'   => [
        'TTL'      => '30s',
        'DeregisterCriticalServiceAfter' => '90m',
    ],
]);

// in a scheduled command, every 15s
$agent->checkPass($serviceId);
// on SIGTERM / Octane stop
$agent->deregisterService($serviceId);

TTL checks are usually easier than HTTP checks for PHP because you don't need Consul to reach into the container. Fail the check from your exception handler when the app is degraded (checkFail($id)), and let DeregisterCriticalServiceAfter handle crashed pods.

Blocking queries — the correct way to do live config. Consul's KV long-polling (index + wait) is the killer feature for runtime config, and 5.4 whitelists those options instead of forcing you to hand-build query strings:

$index = cache()->get('consul.kv.index', '0');

$response = $kv->get('config/prod/features', [
    'index' => $index,
    'wait'  => '5m',
    'consistent' => true,
]);
// $response->getHeaders() / raw body carries the new X-Consul-Index

Never do this inside an HTTP request. A blocking query parks a PHP-FPM/Octane worker for up to wait. Run the watcher in php artisan consul:watch under Supervisor, write to Cache::put(...), and let web workers read from cache. Also raise the HTTP client timeout above your wait value, or Symfony HttpClient will abort the request before Consul answers.

Locking and semaphores — use the helpers, not raw sessions. LockHandler, MultiLockHandler, and MultiSemaphore exist so you don't hand-roll acquire/release semantics. They replace withoutOverlapping() for multi-host schedulers:

$lock = new LockHandler('reporting:daily', 60, new Session($client), $kv);

if ($lock->lock()) {
    try {
        // the job body
    } finally {
        $lock->release();
    }
}

For rate limiting across hosts, MultiSemaphore with new Resource('outbound-api', 5, 30) gives you a distributed concurrency limit — genuinely useful for third-party API quotas that Redis-based limiters can't enforce across regions.

Discovery for outbound calls. Catalog::service('user-api', ['tag' => ['primary'], 'passing' => true]) returns node addresses; combine with a cached lookup and retry to avoid a Consul round-trip on every outbound request. Use Health::service() when you need only passing instances.

Atomic multi-key writes via Txn::create([...]) — use it when a config change touches several keys and partial application would be visible to watchers.

Testing. ClientInterface exists (added in 5.1) precisely so you can substitute it. Bind a fake or a Symfony MockHttpClient-backed client in tests instead of requiring a live agent; keep one integration test suite that runs against the Docker agent so you catch real API-shape drift.

$this->app->instance(ClientInterface::class, new FakeConsulClient());

Operational tooling belongs in Artisan commands: Snapshot for a nightly consul:backup (and a rehearsed restore command), Operator/Status/Coordinate for a health dashboard, ACL for provisioning tokens in CI, Connect intentions if you're moving to a mesh.

Gotchas and Tips

  • json() vs getBody(). Every call returns a Consul\ConsulResponse. json() decodes the body; for ['raw' => true] KV reads and for Snapshot payloads you want getBody() or you'll get garbage. An empty 200 body can decode to null — guard before indexing.
  • 4xx throws; 404 is not "missing". A missing KV key is a ClientException, not null. Wrap reads in try/catch or accept that a typo in a key path becomes a runtime exception in production.
  • Typo'd option keys fail silently. 5.4 whitelists documented options — an unrecognized key is dropped rather than serialized into the query string. ['recurs' => true] will quietly do nothing. Keep the Consul API docs open and assert behavior in tests.
  • Multi-valued options are arrays. ['tag' => ['v1', 'primary']] — not a comma string. Same for filters.
  • Token handling and config:cache. The client reads CONSUL_HTTP_TOKEN directly from the environment; Laravel's env() only works before config caching, and getenv() may be unpopulated depending on your Dotenv adapter. Pass the token explicitly through your consul.php config ('token' => config('consul.token')) so php artisan config:cache doesn't silently drop auth.
  • dc is not universal. 5.4 allows dc on all endpoints not local to the agent — passing dc to Agent::self() or Agent::checks() is meaningless. Agent-local vs. catalog-wide is the single most common conceptual mix-up with Consul.
  • Don't block boot on Consul. A Consul outage must degrade to local config, not take down every pod on restart. Wrap registration and config reads in try/catch with a short timeout and a cached fallback.
  • Session TTL vs. job duration. A LockHandler with a 60-second TTL loses the lock mid-job if the job runs 90 seconds — and the SDK will happily report success on release. Set the TTL above your p99 job duration, and always release() in a finally.
  • Octane. A singleton Client holding a persistent Symfony HttpClient is fine, but the connection pool and any per-request state persist across requests. Never run a blocking query or sleep-style lock wait under Octane — it stalls the worker for every queued request.
  • Exceptions naming. The README says ServeException; the actual class is Consul\Exception\ServerException. Catch the real class.
  • Namespace migration. Everything moved from SensioLabs\Consul\* to Consul\* in 5.0. A grep-and-replace of SensioLabs\\Consul is the entire upgrade; the Factory class is gone (construct services directly), and request bodies must be arrays now, not JSON strings.
  • Extension point that pays off: decorate ClientInterface. Add a logging/metrics decorator that records endpoint, duration, and status code before delegating. Consul failures are otherwise invisible until a lock silently stops being exclusive.
  • simple-phpunit is legacy. 5.4 moved the suite to PHPUnit 11+. If you're copying test patterns from an older blog post, they won't run.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels