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

Confluent Schema Registry Api Laravel Package

mateusjunges/confluent-schema-registry-api

A PHP library for consuming the Confluent Schema Registry REST API, offering low-level PSR-7 compliant requests plus high-level abstractions for easier developer experience. Includes async and sync APIs, caching support, and integration with Avro and Guzzle.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package (9.1.0+ now allows Guzzle 8, but still supports Guzzle 7):

    composer require flix-tech/confluent-schema-registry-api:^9.1 guzzlehttp/guzzle:^8.0
    

    If your Laravel app already pulls Guzzle 7 (Laravel 10/11/12 pin guzzlehttp/guzzle: ^7.8 via illuminate/http), do not force Guzzle 8 — just install the package and let Composer resolve the existing Guzzle 7:

    composer require flix-tech/confluent-schema-registry-api:^9.1
    composer why guzzlehttp/guzzle   # confirm which major you actually got
    

    Both majors are supported by the package; mixing is not.

  2. Configure the HTTP client (e.g. in config/services.php):

    'schema-registry' => [
        'url' => env('SCHEMA_REGISTRY_URL', 'http://localhost:8081'),
        'auth' => [
            'basic' => [
                'username' => env('SCHEMA_REGISTRY_USER'),
                'password' => env('SCHEMA_REGISTRY_PASSWORD'),
            ],
        ],
    ],
    
  3. Basic usage in a Laravel service:

    use FlixTech\SchemaRegistryApi\Registry\BlockingRegistry;
    use FlixTech\SchemaRegistryApi\Registry\PromisingRegistry;
    use GuzzleHttp\Client;
    use FlixTech\AvroPhp\AvroSchema;
    
    class SchemaService
    {
        protected BlockingRegistry $registry;
    
        public function __construct()
        {
            $client = new Client([
                'base_uri' => config('services.schema-registry.url'),
                'auth' => config('services.schema-registry.auth.basic'),
            ]);
    
            $this->registry = new BlockingRegistry(new PromisingRegistry($client));
        }
    
        public function registerSchema(string $subject, string $schemaJson): int
        {
            return $this->registry->register($subject, AvroSchema::parse($schemaJson));
        }
    }
    

Where to look first

  • BlockingRegistry — the class you will use 95% of the time in Laravel (synchronous controllers, commands, jobs).
  • PromisingRegistry — the underlying promise-based implementation; read this if you need async or want to understand error propagation.
  • CachedRegistry + CacheAdapterInterface — your performance lever in high-traffic apps.
  • Composer lockfile — after upgrading to 9.1.0, verify whether you landed on Guzzle 7 or 8 (see Gotchas).

First Use Case: Schema ID lookup in a Laravel request handler

Use the package to resolve/verify a schema before accepting a Kafka payload:

use FlixTech\SchemaRegistryApi\Registry\BlockingRegistry;
use FlixTech\AvroPhp\AvroSchema;

class KafkaWebhookController extends Controller
{
    public function __construct(private BlockingRegistry $registry) {}

    public function handleWebhook(Request $request)
    {
        $schema = AvroSchema::parse($request->input('schema'));
        $schemaId = $this->registry->schemaId('webhook-payload', $schema);

        // Proceed with the validated schema ID
    }
}

Implementation Patterns

1. Synchronous vs. Asynchronous Workflows

  • Synchronous (BlockingRegistry): the right default for Laravel routes, commands and queue jobs, where blocking is already the norm.
    $schemaId = $registry->register('user-profile', $schema);
    
  • Asynchronous (PromisingRegistry): useful only where you genuinely pipeline multiple registry calls (e.g. registering a batch of subjects in one job).
    $promise = $registry->register('user-profile', $schema);
    $promise->then(fn ($schemaId) => $schemaId);
    
    In practice, wrap async work in a queue job and call ->wait() once — Laravel has no event loop to resolve promises otherwise.

2. Caching Strategies

Reduce registry calls in high-traffic apps:

use FlixTech\SchemaRegistryApi\Registry\CachedRegistry;
use FlixTech\SchemaRegistryApi\Registry\Cache\DoctrineCacheAdapter;
use Doctrine\Common\Cache\ArrayCache;

$cachedRegistry = new CachedRegistry(
    $promisingRegistry,
    new DoctrineCacheAdapter(new ArrayCache())
);

$schemaId = $cachedRegistry->register('user-profile', $schema);

In Laravel you almost always want a shared store (Redis/APCu) rather than ArrayCache, which is per-process and therefore per-request.

3. Integration with Laravel’s Service Container

Bind the registry once in AppServiceProvider:

public function register(): void
{
    $this->app->singleton(BlockingRegistry::class, function () {
        $client = new Client([
            'base_uri' => config('services.schema-registry.url'),
            'auth' => config('services.schema-registry.auth.basic'),
        ]);

        return new BlockingRegistry(new PromisingRegistry($client));
    });
}

Then inject it anywhere:

public function __construct(BlockingRegistry $registry) { /* ... */ }

4. Schema Management in Migrations / Deploy Steps

public function up(): void
{
    $schema = AvroSchema::parse(file_get_contents(database_path('schemas/user.avsc')));
    $schemaId = app(BlockingRegistry::class)->register('user', $schema);

    Schema::create('users', function (Blueprint $table) use ($schemaId) {
        $table->id();
        $table->unsignedInteger('schema_id')->default($schemaId);
    });
}

Prefer an artisan command or deployment step over migrations — migrations should not depend on an external service being reachable.

5. Error Handling

use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException;

try {
    $schemaId = $this->registry->register('user-profile', $schema);
} catch (SchemaRegistryException $e) {
    report($e);

    throw new \RuntimeException('Schema registration failed', 0, $e);
}

Catch at the boundary (job/controller), not deep inside services — registry exceptions are infrastructure failures, not domain errors.

6. Low-Level API for Custom Endpoints

use FlixTech\SchemaRegistryApi\Requests\Functions;

$request  = Functions::deleteSchema($client, 'subject', 1);
$response = $client->send($request);

This is how you reach endpoints the high-level registries do not expose (deletes, compatibility checks, mode switches).


Gotchas and Tips

Pitfalls

  1. Guzzle 8 vs Laravel's HTTP client (new in 9.1.0)

    • 9.1.0 adds Guzzle 8 support (guzzlehttp/guzzle:^8, guzzlehttp/promises:^3, guzzlehttp/psr7:^3) while keeping Guzzle 7 compatibility.
    • Laravel 10/11/12 themselves require guzzlehttp/guzzle:^7.8, so a standard Laravel app cannot upgrade to Guzzle 8 without conflicting with the framework. The package supports both, so this is not a blocker — just don't add guzzlehttp/guzzle:^8 to require in a Laravel project.
    • Practical read: this release is mainly relevant if you (a) use the package outside Laravel, (b) run a future Laravel version that moves to Guzzle 8, or (c) have another dependency forcing Guzzle 8. Verify with composer why guzzlehttp/guzzle and composer why guzzlehttp/psr7 after upgrading.
    • Lock the majors you test against in composer.json ("guzzlehttp/guzzle": "^7.8") so a future transitive resolution doesn't silently swap HTTP stacks under you.
  2. PSR-7 3 / Promises 3 type changes

    • The new stack uses stricter return types. If you wrote custom StreamInterface/ResponseInterface fakes for tests, or a custom PromiseInterface implementation, they must be updated to the v3 signatures. Prefer GuzzleHttp\Psr7\HttpFactory and GuzzleHttp\Promise\Create::promiseFor() over hand-rolled implementations.
    • Mock handlers built with MockHandler / HandlerStack are generally source-compatible, but re-run your schema-registry test suite after the bump.
  3. Schema Registry Compatibility

    • Match the Confluent Schema Registry version to the API endpoints the package targets; test against your production version.
    • The package assumes Avro (via flix-tech/avro-php). Protobuf/JSON Schema require pre-processing or extension.
  4. Caching Quirks

    • No TTL/invalidation by default: CachedRegistry will happily serve stale IDs after a schema update. Use a TTL cache or a custom adapter.
    • Hash collisions: default md5 hashing is fine in practice but configurable:
      $cachedRegistry = new CachedRegistry($promisingRegistry, $cache, 'sha1');
      
  5. Asynchronous Edge Cases

    • Rejections must be handled explicitly; an unhandled rejection inside ->then() will surface as a fatal error at ->wait().
      $promise
          ->then(fn ($schemaId) => $schemaId)
          ->otherwise(function (SchemaRegistryException $e) {
              report($e);
              throw $e;
          })
          ->wait();
      
    • Unresolved promises leak memory — always terminate with ->wait() in a bounded context (a job), never in a long-running worker loop without resolution.
    • With Promises 3, wait() return types are stricter; don't rely on loose truthiness of the resolved value.
  6. Authentication

    • The package enforces no auth scheme; configure Guzzle:
      'auth' => ['user', 'pass'],                                  // Basic
      // 'headers' => ['Authorization' => 'Bearer ' . $token],    // Bearer
      
    • For Confluent Cloud, the API key/secret pair goes in Basic auth — the same shape as above.
  7. Performance & Rate Limiting

    • Handle 429s at the job level with retry/backoff ($job->release($seconds)) rather than in the exception handler.
    • Batch registrations manually; there is no bulk endpoint wrapper.

Debugging Tips

  1. Guzzle logging middleware (API differs slightly between Guzzle 7 Middleware::tap and Guzzle 8 — check the docs for your installed major):

    $stack = HandlerStack::create();
    $stack->push(Middleware::tap(function ($request) {
        Log::debug('Schema Registry Request', [
            'method' => $request->getMethod(),
            'uri'    => (string) $request->getUri(),
            'body'   => (string) $request->getBody(),
        ]);
    }));
    
    $client = new Client([
        'base_uri'  => config('services.schema-registry.url'),
        'handler'   => $stack,
    ]);
    

    Note: (string) $request->getBody() consumes the stream — rewind it ($request->getBody()->rewind()) before the request is sent if you see empty request bodies.

  2. Health check route:

    Route::get('/schema-registry/health', function () {
        $client = new Client(['base_uri' => config('services.schema-registry.url')]);
        $client->get('/subjects');
    
        return response()->json(['status' => 'healthy']);
    });
    
  3. Version skew after upgrade: if you see Class "GuzzleHttp\Promise\..." not found or signature-mismatch errors after bumping to 9.1.0, run composer update guzzlehttp/* --with-dependencies — a stale promises 2.x alongside psr7 3.x is the usual culprit.

Extension Points

  1. Custom cache adapters — implement CacheAdapterInterface for Redis/Memcached or to add TTL + invalidation:

    use FlixTech\SchemaRegistryApi\Registry\Cache\CacheAdapterInterface;
    use Psr\SimpleCache\CacheInterface;
    
    final class PsrCacheAdapter implements CacheAdapterInterface
    {
        public function __construct(private CacheInterface $cache) {}
    
        public function get(string $key)
        {
            return $this->cache->get($key);
        }
    
        public function put(string $key, $value): void
        {
            $this->cache->set($key, $value, 3600);
        }
    
        public function has(string $key): bool
        {
            return $this->cache->has($key);
        }
    }
    

    Bind it to a Laravel cache store (Cache::store('redis')) so cache invalidation is shared across queue workers.

  2. Custom hash strategy — pass a callable/algorithm name to CachedRegistry when md5 key derivation doesn't fit.

  3. Decorating the registry — wrap BlockingRegistry in your own class implementing the same registry interface to add metrics, logging, or circuit breaking without touching call sites:

    $this->app->singleton(BlockingRegistry::class, fn () => new MetricsRegistry(
        new BlockingRegistry(new PromisingRegistry($client))
    ));
    
  4. Custom Guzzle handlers — inject a HandlerStack (retry middleware, tracing, mock handler in tests) straight into the Client you hand to PromisingRegistry; the package never constructs its own client, so this is the cleanest seam for cross-cutting HTTP concerns.

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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk