kitano/connection-bundle
Symfony bundle for managing multiple database connections. Configure named connections, switch between them at runtime, and centralize connection setup—useful for multi-tenant apps or services that need to talk to more than one database.
First, reconcile the mismatch: this is a Symfony bundle, not a Laravel package. There is no ConnectionServiceProvider, no config/connection.php to publish, no facade, and no composer require that resolves cleanly against Laravel 10/11's PHP 8.1+ floor. Kitano\ConnectionBundle ships Symfony Bundle/DI extension classes and Doctrine ORM mappings. For a Laravel team there are exactly two honest starting points:
If you take the reference path, budget a 2–3 day spike before writing anything:
Resources/config/services.yml to find the manager service (typically kitano.connection.manager). That service is the entire API surface — read it first.Connection entity mapping. It is the whole point of the package: a polymorphic adjacency edge — sourceType, sourceId, destinationType, destinationId, and a type/name discriminator on one table. That schema is the deliverable.composer.json. PHP/Symfony/Doctrine constraints from the Symfony 2.x era will frequently hard-conflict with modern stacks. Clear this gate in an hour or don't start.First use case for the ported version: you need a many-to-many relation to objects of other classes and don't want a join table per combination. "Tag this Post with these Tags", "User likes this Comment", "Attachment belongs to this Invoice" — one connections table, one service, done.
Model the edge explicitly, don't hide it behind a relation. The core schema for the Eloquent port:
Schema::create('connections', function (Blueprint $t) {
$t->id();
$t->string('source_type');
$t->unsignedBigInteger('source_id');
$t->string('target_type');
$t->unsignedBigInteger('target_id');
$t->string('type'); // 'follow', 'like', 'tagged-with' …
$t->json('meta')->nullable(); // direction, weight, timestamps, etc.
$t->timestamps();
$t->unique(['source_type','source_id','target_type','target_id','type'], 'conn_unique');
$t->index(['source_type','source_id','type'], 'conn_out');
$t->index(['target_type','target_id','type'], 'conn_in');
});
The unique index prevents duplicate parallel edges; the two composite indexes cover outbound and inbound adjacency lookups — the two query patterns you will actually issue.
Route every write through a service, never the model. A Laravel port wants a small ConnectionService (bound as a singleton in a provider) exposing:
$edges->connect($post, $tag, 'tagged-with', ['weight' => 3]);
$edges->disconnect($post, $tag, 'tagged-with');
$edges->targets($post, 'tagged-with'); // direct edges only
$edges->sources($user, 'follow');
Model-level create() calls on the edge table are how you end up with orphaned rows and duplicate edges — the service is the enforcement point.
Namespace your edge types religiously. A shared table with follow, like, parent-of, and tagged-with in the type column gets unreadable fast. Every read and write goes through the typed methods above; never let a bare where('type', ...) leak into a controller.
Traverse breadth-first with a visited set and a hard max-depth. If the ported code has a traversal method, verify it has both. Your own guard:
public function reachable($start, string $type, int $maxDepth = 3): Collection
{
$seen = collect([spl_object_id($start)]);
$frontier = collect([$start]);
$depth = 0;
while ($frontier->isNotEmpty() && $depth++ < $maxDepth) {
$next = $this->targetsForMany($frontier, $type);
$next = $next->reject(fn ($n) => $seen->contains(spl_object_id($n)));
$seen = $seen->merge($next->map(fn ($n) => spl_object_id($n)));
$frontier = $next;
}
return $seen;
}
Batch per hop, don't loop. targetsForMany() should issue a single whereIn keyed on (source_type, source_id) pairs — per-object targets() calls in a loop is the N+1 that makes adjacency tables look slow.
Use it as the substrate for feature after feature. Once the edge table exists and the service is in place, the tag/follow/like/related-product features stop requiring new migrations. This is the whole ROI argument for the pattern — it pays out on the fourth relationship, not the first.
Post leaves its edges in place. Add an application-level cascade on delete (or a model observer on every connectable class) and a scheduled integrity job. Instrument for orphan rows; they silently inflate traversal counts.targets() call. Cap per-node expansion and paginate wide nodes at the service layer, not the view.declare(strict_types=1), add a test suite + CI matrix (PHP 8.1+, Laravel 10/11), and verify the license before vendoring. No CVE feed means no patches arrive unprompted.composer.json on modern Symfony or Laravel. Constraint resolution alone will exceed the cost of writing the edge table — the abstraction is thinner than the integration tax.staudenmeir/laravel-adjacency-list), or a graph store.morphToMany and native join tables already solve the small case, are supported, and are free. The generic edge model earns its keep when relationship types keep appearing — not before.How can I help you explore Laravel packages today?