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

Cassandra Bundle Laravel Package

m6web/cassandra-bundle

Symfony bundle for integrating Cassandra into your app, with configuration, service wiring, and profiling tools to manage connections and inspect Cassandra queries from within Symfony.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is not a Laravel package. m6web/cassandra-bundle is a Symfony 2/3-era bundle that wires datastax/php-driver (the ext-cassandra PHP extension) into the container. You will not composer require it into a Laravel app and get working services — its M6WebCassandraBundle, DependencyInjection/Configuration.php, and service definitions assume Symfony's kernel and DI conventions. For a Laravel developer the realistic "getting started" is: read it as a reference design, port the ~200 lines of wiring it does, and write your own service provider. Treat it as documentation, not a dependency.

Step 1 — get the extension working before you touch any PHP glue. Everything hinges on ext-cassandra, which wraps the DataStax C++ driver:

pecl install cassandra       # needs libuv, openssl, and the C++ driver toolchain
php -m | grep cassandra

If this fails, none of the bundle's code (or any port of it) matters. The most common first-day failure is a container/boot error like Class "Cassandra\Cluster" not found because the extension isn't loaded.

Step 2 — look at exactly two files in the repo:

  • DependencyInjection/Configuration.php — the authoritative list of every cluster option the bundle supports: contactpoints, port, keyspace, timeout, connect_timeout, consistency, retry_policy, load_balancing, protocol_version, username/password, default_page_size. This is your checklist for what a real Cassandra config needs.
  • Cassandra/Connection.php (and the session factory around it) — how the bundle wraps a Cassandra\Session and exposes query() / prepared statements. This is the interface you'll re-implement.

Step 3 — mirror it in Laravel. Create config/cassandra.php (one array per cluster, matching the bundle's YAML shape) and a CassandraServiceProvider that binds a lazily-created singleton per cluster:

// config/cassandra.php
return [
    'default' => 'default',
    'clusters' => [
        'default' => [
            'contactpoints'   => explode(',', env('CASSANDRA_HOSTS', '127.0.0.1')),
            'port'            => (int) env('CASSANDRA_PORT', 9042),
            'keyspace'        => env('CASSANDRA_KEYSPACE', 'app'),
            'timeout'         => 5,
            'connect_timeout' => 5,
            'consistency'     => 'quorum',   // LOCAL_QUORUM in prod multi-DC
            'protocol_version'=> 4,
            'username'        => env('CASSANDRA_USER'),
            'password'        => env('CASSANDRA_PASS'),
        ],
    ],
];
// app/Providers/CassandraServiceProvider.php
public function register(): void
{
    $this->app->singleton(CassandraManager::class, fn ($app) =>
        new CassandraManager($app['config']->get('cassandra'))
    );

    $this->app->bind(CassandraConnection::class, fn ($app) =>
        $app->make(CassandraManager::class)->connection()
    );
}

First use case — one write, one read, through a repository:

final class UserRepository
{
    public function __construct(private CassandraConnection $cassandra) {}

    public function touch(string $userId): void
    {
        $this->cassandra->execute(
            'INSERT INTO users (id, last_seen) VALUES (?, toTimestamp(now()))',
            [$userId]
        );
    }

    public function find(string $userId): ?array
    {
        $rows = $this->cassandra->execute(
            'SELECT id, last_seen FROM users WHERE id = ?',
            [$userId]
        );
        return $rows[0] ?? null;   // map to a DTO, don't leak Cassandra\Row
    }
}

The keyspace must already exist — neither the bundle nor your port creates schema. Do it with cqlsh or a dedicated artisan command before the app connects.

Implementation Patterns

Repository wrapper, not raw session in controllers. The single most important pattern the bundle implies (but doesn't enforce) is: keep all CQL in a per-table-family repository and inject a small connection wrapper. The Symfony coupling stays in one service definition; in Laravel the equivalent is one binding in a service provider and repositories that accept an interface. This is what survives the underlying driver going EOL — your CQL surface is small and swappable.

Port the bundle's cluster/session model, not its YAML. Recreate the shape: a CassandraManager that holds cluster definitions and lazily builds a Cassandra\Session per cluster name, caching it. The bundle's value is exactly this lifecycle — build session on first use, keep it for the request lifecycle, configure consistency/retry at the cluster level. Do not create a Session per query.

final class CassandraManager
{
    private array $sessions = [];

    public function __construct(private array $config) {}

    public function connection(?string $cluster = null): CassandraConnection
    {
        $name = $cluster ?? $this->config['default'];
        if (!isset($this->sessions[$name])) {
            $cfg = $this->config['clusters'][$name];
            $cluster = Cassandra::cluster()
                ->withContactPoints(...$cfg['contactpoints'])
                ->withPort($cfg['port'])
                ->withDefaultConsistency(constant('Cassandra::CONSISTENCY_' . strtoupper($cfg['consistency'])))
                ->withConnectTimeout($cfg['connect_timeout'])
                ->build();
            $this->sessions[$name] = new CassandraConnection(
                $cluster->connect($cfg['keyspace'])
            );
        }
        return $this->sessions[$name];
    }
}

Reuse prepared statements; use ExecutionOptions for per-query overrides. The bundle's config sets defaults on the session, but real workloads need per-query consistency (e.g. ONE for hot reads, LOCAL_QUORUM for writes). That means prepared statements plus an options object — this is where the bundle's defaults stop and your application takes over:

$stmt = $session->prepare('SELECT * FROM events WHERE user_id = ? AND ts > ?');
$opts = new Cassandra\ExecutionOptions([
    'arguments'    => [$userId, $since],
    'consistency'  => Cassandra::CONSISTENCY_ONE,
    'page_size'    => 500,
]);
$rows = $session->execute($stmt, $opts);

Multiple clusters → inject the manager, not a connection. The bundle supports multiple lazily-created sessions; mirror that exactly. Have CassandraManager::connection('analytics') and bind a named alias CassandraConnection::class to the default, plus analytics via a tagged or when() binding. Do not autowire different connection types by name — Laravel has no equivalent to Symfony's service IDs, so use interfaces (AnalyticsConnection) or explicit calls in the repository constructor.

Mirror the bundle's profiler with a query listener or Telescope. The bundle ships a Symfony data collector to observe Cassandra queries — that's the one "nice to have" you'll actually miss. In Laravel, wrap CassandraConnection::execute() to fire a log entry (query, bound args count, duration, consistency) and optionally a Telescope-like listener. Never log raw bound values in prod (PII).

Testing strategy. Because the extension is heavy, define CassandraConnectionInterface with execute(string $cql, array $args): array and bind a fake for unit tests. Integration-test against a real single-node cluster (docker cassandra:3.11 or scylladb) — mocking Cassandra's query semantics is more trouble than it's worth.

Schema as code. Since neither the bundle nor the driver manages DDL, add an artisan command cassandra:migrate that runs versioned .cql files from database/cassandra/. This is the piece every Cassandra integration needs and no framework bundles for you.

Port a legacy Symfony app by wrapping, not rewriting. If you're migrating a Symfony app that uses this bundle, the safe move is to keep the bundle's Connection semantics (same method names, same config keys) behind a Laravel CassandraConnection class. Repositories then compile against either side unchanged.

Gotchas and Tips

  • ext-cassandra is a native extension, not a Composer package. It can't be installed on shared hosting or many serverless platforms (Laravel Vapor included). CI needs libuv/openssl and the C++ driver. Build it into your base Docker image and version-pin it.
  • Archived and pre-PHP 8. The bundle is untouched since March 2020 and the DataStax PHP driver underneath is EOL. If your runtime is PHP 8+, expect to patch what you port — and accept that no one upstream will fix driver bugs. This is the single biggest reason not to vendor the bundle itself; port the patterns instead.
  • No autowiring in Symfony — and no equivalent aliasing in Laravel. The bundle's service IDs (cassandra.<cluster>_session) vary between releases, which is why you bin/console debug:container cassandra on the Symfony side. In Laravel, always bind explicit interfaces; never rely on container naming conventions for Cassandra.
  • Config keys are a moving target. Configuration.php is the only reliable reference for accepted keys. consistency is a string like quorum; in code you'll need Cassandra::CONSISTENCY_QUORUM constants. protocol_version must match your cluster (4 for Cassandra 3.x, 5 for 4.x) — a mismatch causes opaque handshake failures, not a helpful error.
  • Exceptions are not Laravel-shaped. The driver throws Cassandra\Exception\TimeoutException, UnavailableException, InvalidQueryException, AlreadyExistsException, etc. Map them in your wrapper — at minimum translate TimeoutException/UnavailableException into a retryable RuntimeException and let InvalidQueryException bubble up untouched (it means a bug). Silently swallowing them is how you get mystery data loss.
  • Session vs. request lifecycle. A Cassandra\Session is long-lived and internally pooled. Creating one per query will exhaust connections under load; creating one per request loses warmup but is safe. Bind as a singleton in the container and never new Cassandra\Cluster() in a controller.
  • Keyspace and schema are your problem. The bundle never creates tables. Missing keyspace produces a Keyspace not found error deep in the connect call — check cqlsh -e "DESCRIBE KEYSPACES" before debugging anything else.
  • Consistency is a per-statement decision. Session defaults from YAML apply only to statements without ExecutionOptions. Teams routinely set quorum globally and then wonder why one read is slow — that read probably should be ONE or LOCAL_ONE. Bake consistency into repository methods, not into config.
  • Pagination requires paging state. There's no paginate() helper. You must thread Cassandra\ExecutionOptions::$pagingState across requests (store it in the session or the client). The bundle's default_page_size only sets a default; it doesn't paginate for you.
  • Don't leak Cassandra\Row / Cassandra\Collections into domain code. Convert to plain arrays or DTOs inside the repository. This is what lets you replace the underlying driver later — which, given the EOL status, is a when, not an if.
  • ALLOW FILTERING will bite you. It works in dev and destroys production. The bundle gives you a query surface with zero guardrails; add a static analysis rule or code-review checklist banning it outside known-partition queries.
  • Profiler/data collector overhead. If you replicate the bundle's profiler, gate it behind app.debug. Per-query logging on a high-write Cassandra workload is measurable.
  • Retry policy config is easy to misread. retry_policy in the bundle is passed to the cluster builder, not the session; changing it requires rebuilding the session. Since sessions are singletons, a config change means restart, not a request-scope tweak.
  • Extension point: fork, don't depend. If you genuinely need this bundle (a frozen Symfony 2 monolith), vendor it into vendor/ or a packages/ path, pin the PHP version, assign an owner, and file a removal ticket. Zero dependents and archived status mean no community answers when something breaks — you own every failure.
  • Debugging connection failures: check in this order — php -m | grep cassandra (extension), cqlsh -u ... (cluster reachability and auth), DESCRIBE KEYSPACES (keyspace exists), protocol_version match, then your code. Almost every "bundle is broken" incident is one of the first three.
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.
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
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin