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

Multi Tenant Bundle Laravel Package

syndicatetheory/multi-tenant-bundle

A Symfony bundle for multi-tenant PHP applications, providing tenant-aware configuration and infrastructure to isolate data and serve multiple customers from one codebase, simplifying SaaS architecture.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the orientation check. Despite the framing, this is not a Laravel package. It is a Symfony2 bundle (PHP 5.x era, Symfony 2.x), marked [IN DEVELOPMENT], with 13 stars, zero dependents, and a score of 0.225. You cannot composer require it into a Laravel app and expect anything to work — the service container, event dispatcher, Doctrine integration, and Resources/config/*.yml conventions are all Symfony. Treat this repository as a design reference for multi-tenancy, not a dependency.

Where to look first, in this order:

  1. composer.json — confirm the Symfony/PHP constraints. If it pins symfony/symfony: ~2.x, that branch is EOL and unpatched; nothing here belongs in production.
  2. README.md — expect thin or absent docs given the star count and in-development banner. Do not budget time for it to answer your questions.
  3. DependencyInjection/*Extension.php and Resources/config/services.yml — this is where the bundle's real configuration surface lives (tenant resolution strategy, connection parameters, storage paths). This tells you what knobs the author actually thought about.
  4. EventListener/ or EventSubscriber/ — the request-time hook where a tenant is resolved and applied. This is the single most portable idea in the repo.
  5. Doctrine connection handling — look for a custom Connection wrapper, a wrapper_class config, or an event listener on kernel.request that swaps connection parameters. That is the mechanism you are really evaluating.

The first use case to test in Laravel: database-per-tenant with hostname resolution. Wire a middleware that reads $request->getHost(), looks up the tenant, and rewrites config(['database.connections.tenant' => [...]]) before calling DB::purge('tenant') and DB::reconnect('tenant'). If you can get that working with two local hosts, you have reproduced ~80% of what this bundle is trying to do.

Implementation Patterns

Resolution at the edge, application below. The bundle's model is a request listener that identifies a tenant before anything else runs. In Laravel that maps to middleware registered in the web group before StartSession, Authenticate, and anything touching the database. If tenancy resolution runs after auth, your user lookup hits the wrong database.

Connection switching, not connection duplication. The Symfony pattern is to mutate connection parameters and let Doctrine rebuild. The Laravel equivalent is configuration mutation plus purge/reconnect — never DB::setDefaultConnection() alone, because pooled connections already open will keep pointing at the old tenant. Order matters: DB::purge($name) → reconfigure → DB::reconnect($name) → DB::setDefaultConnection($name).

Per-tenant namespacing for everything stateful. Beyond the database, the bundle concerns itself with cache and filesystem paths. Mirror that deliberately: Storage::disk('tenant') with a root of storage/tenants/{id}, cache prefixes per tenant, and a session cookie scoped to the tenant's domain. Any subsystem that holds global state and isn't namespaced is where your first cross-tenant leak will appear.

Console and queue symmetry. Anything the web request does, the CLI must do too. Port the pattern to an artisan command that iterates tenants, sets context, runs a callback, and tears down — php artisan tenants:run migrate. Queue workers need a job middleware that re-establishes tenant context from a tenant ID carried in the job payload, not from a global, because workers are long-lived.

Shared-schema vs. database-per-tenant is a fork in the road, not a detail. If the bundle only supports separate databases, know that up front. Shared-schema with a tenant_id column and a global Eloquent scope is a fundamentally different implementation with different failure modes (a forgotten scope fails open; a wrong connection fails closed). Don't start down one path assuming you can convert later cheaply.

Integration tip: vendor the ideas, not the code. Extract the resolution and connection-swap logic into your own app/Tenancy/ namespace and own it. Given zero dependents and an in-development banner, you will be maintaining every line you pull in regardless.

Gotchas and Tips

  • The [IN DEVELOPMENT] label plus 0 dependents is the headline. No downstream users means no one has hit the edge cases for you, and no issue reports to learn from. There is no realistic path to upstream support. This is a read-only artifact.
  • Symfony2 EOL. Even as a reference, its idioms (YAML service definitions, compiler passes, services.yml parameters) will look foreign to anyone on Laravel or Symfony 6+. Read for architecture, not for style.
  • The connection-cache trap is the #1 debugging time sink. Symptom: tenant resolves correctly, config() shows the right database name, but queries still hit the wrong place. Cause: an already-resolved connection object. Always purge before reconfiguring, and log DB::getDefaultConnection() and DB::connection()->getDatabaseName() at the top of every tenant-scoped request while you're building.
  • Long-running processes hold tenant context. In tests, worker loops, or any daemon, tenant state set in one iteration leaks into the next unless explicitly reset. Build the teardown at the same time as the setup — a finally block, not an afterthought.
  • Caching gotchas compound. Cache tags (the obvious isolation mechanism) are unsupported by the file and database cache drivers. If you're on file cache in development, isolation failures won't surface until you deploy to Redis. Test isolation on the production cache driver.
  • Don't trust the config defaults. With no docs and no users, defaults were chosen by one person for one project. Read Configuration.php (if present) and the DI extension to see what is actually configurable versus hardcoded, before designing anything around it.
  • Extension points to note, then reimplement: Symfony compiler passes and bundle config are the author's injection points. In Laravel the analogue is a service provider that publishes a config/tenancy.php and registers singletons; that is the seam you want in your port, because it's where you'll later swap resolution strategies.
  • Verdict for a day-to-day Laravel workflow: allocate a half-day to reading the resolution and connection-swap logic, take notes, and build it yourself. Do not add this to composer.json under any circumstances.
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