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 Tenancy Bundle Laravel Package

mapeveri/multi-tenancy-bundle

A multi-tenancy bundle for PHP applications that simplifies tenant isolation, tenant-aware database connections, and request-based tenant resolution. It provides the building blocks to run multiple tenants from a single codebase with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first: this is not a Laravel package. Despite the name, mapeveri/multi-tenancy-bundle is a Symfony bundle ("type": "symfony-bundle" in composer.json). There is no service provider, no facade, no package auto-discovery, no config/multitenancy.php. On a Laravel app, composer require mapeveri/multi-tenancy-bundle will install inert code that the framework never boots. That is your first and most important finding.

Minimal steps for a Laravel dev:

  1. Do not add it to composer.json. The README carries an explicit "this repo is not maintained anymore" warning, 11 stars, 0 dependents, and no ecosystem to inherit fixes from. Treat it as reading material for a spike, cloned to a scratch directory.
  2. Clone and read, in this order:
    • the *Bundle class (entry point) — this is the Symfony analogue of a service provider,
    • the DI extension and Configuration class — this is the analogue of your config/*.php file,
    • the tenant resolver (subdomain / request header / config-driven),
    • the kernel.request listener or event subscriber that populates tenant context,
    • any Doctrine filter or connection-switching code.
  3. First "use case" for a Laravel developer: extract the pattern, not the code. The canonical flow is request arrives → resolver picks a tenant → context is stored → data access is scoped. That maps 1:1 onto a Laravel middleware + a scoped Eloquent trait, and that mapping is the entire practical value of this repository.

If you actually need multi-tenancy on Laravel today, evaluate stancl/tenancy or spatie/laravel-multitenancy instead — a Symfony bundle cannot be the answer to a Laravel question, maintained or not.

Implementation Patterns

Pattern A — Port the resolver/listener into middleware (the realistic day-to-day move).

The bundle resolves a tenant early in the request lifecycle and stores it scoped to the request. In Laravel that is a middleware registered before auth/session middleware:

  • Resolve the tenant from the subdomain ($request->getHost()) or a header, deterministically.
  • Bind it: app()->instance(Tenant::class, $tenant) and set it on a request-scoped TenantContext singleton.
  • Fail closed. If no tenant resolves on a tenant-scoped route, abort — do not fall back to a "default tenant." A silent default is the classic cross-tenant leak.
  • Forget the binding on terminate() (and especially under Octane, where the container persists across requests).

Pattern B — Port the Doctrine filter into an Eloquent global scope.

The bundle's isolation, where it exists, is likely a Doctrine SQL filter. The Laravel equivalent:

trait BelongsToTenant
{
    public static function bootBelongsToTenant(): void
    {
        static::addGlobalScope('tenant', fn ($q) => $q->where('tenant_id', app(TenantContext::class)->id()));
        static::creating(fn ($m) => $m->tenant_id ??= app(TenantContext::class)->id());
    }
}

Apply it to every tenant-owned model, plus an auto-fill on creating so writes cannot land under the wrong tenant. This mirrors the filter behavior at the layer where developers actually work.

Pattern C — Connection switching for database-per-tenant.

If you lift the bundle's connection-switching idea, do it in middleware:

  • Set config(['database.default' => "tenant_{$tenant->id}"]) and register the connection config at runtime.
  • DB::purge($connection) after switching, or you will serve one tenant's data from a stale pooled connection.
  • Run migrate per tenant as an explicit, tenant-parameterized command — never a global artisan migrate that silently hits the wrong database.

Pattern D — Re-bind context in every async boundary.

Tenant context does not survive process boundaries. For any job, scheduled command, or queued listener:

public function handle(): void
{
    app(TenantContext::class)->set(Tenant::find($this->tenantId));
    // ... do work
}

Carry tenantId in the job payload explicitly. This is the single most common way tenancy implementations leak in production: a job dispatched under tenant A executes in a worker that last served tenant B.

Integration tip: the bundle's "configurable isolation for different deployment models" is a design smell worth noting — isolation model is an architectural decision (shared schema + scoping vs. schema-per-tenant vs. database-per-tenant), not a runtime toggle. Pick one, write it down, and make the scoping mechanism uniform.

Gotchas and Tips

  • The framework mismatch is the whole story. No service provider, no facade, no config/ merge, no auto-discovery. Nothing will "just work" — and the bundle's Laravel-3-era naming (*Bundle) suggests conventions that no longer exist in any framework you're on.
  • Composer constraints will fight you. Expect PHP 5.x-era syntax and Symfony 2/3 constraints; installing it into a modern graph either fails resolution or drags in stale dependencies that fence you out of security updates elsewhere. Check composer.json constraints before any spike, not after.
  • Unmaintained + security boundary = asymmetric risk. Multi-tenancy is the line where one tenant must not read another's data. Zero dependents means no CVE tracking, no reference implementations, no Stack Overflow answers, and no upstream to escalate to when you find a leak.
  • Global scopes do not cover everything. Raw SQL (DB::select), withoutGlobalScopes(), native query builders, and relations defined without the scope all bypass the filter. Grep for these in code review; they are where leaks hide.
  • withoutGlobalScopes() in tests is a trap. Your isolation test must assert that tenant A's user gets 404/empty for tenant B's records — a test that passes with scoping disabled proves nothing.
  • Cache, session, queue, and filesystem all need tenant prefixes. A cache key of users:1 collides across tenants. Prefix with the tenant ID at every storage edge, not just the database.
  • Octane/long-running workers hold stale context. Bind in middleware, forget in terminate(), and consider a TenantContext::flush() in the worker's request-reset hook.
  • Migrations are the highest-blast-radius operation. A per-tenant migration command that resolves the wrong tenant silently corrupts data. Require an explicit tenant argument and log the resolved connection before every run.
  • Extension point that actually matters: if you're on a legacy Symfony app already using it, implement the bundle's resolver interface and register your own service in DI rather than editing vendor code — but plan the exit. For Laravel, the extension point is your own middleware, not this package.
  • Bottom line for day-to-day leverage: the honest answer is none as a dependency. Its value is a one-day design reference — read how someone else structured resolution, context propagation, and scoping, write down what you'd steal and what you'd avoid, then adopt a maintained library or build a thin internal layer you own with a named maintainer.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect