PHPackages                             danplaton4/tenancy-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. danplaton4/tenancy-bundle

ActiveSymfony-bundle[Database &amp; ORM](/categories/database)

danplaton4/tenancy-bundle
=========================

Multi-tenant Symfony bundle - zero boilerplate, zero leaks.

v0.3.3(1w ago)15↓100%MITPHPPHP ^8.2CI failing

Since Apr 19Pushed 1w agoCompare

[ Source](https://github.com/danplaton4/tenancy-bundle)[ Packagist](https://packagist.org/packages/danplaton4/tenancy-bundle)[ Docs](https://github.com/danplaton4/tenancy-bundle)[ RSS](/packages/danplaton4-tenancy-bundle/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (2)Dependencies (24)Versions (9)Used By (0)

[![CI](https://github.com/danplaton4/tenancy-bundle/actions/workflows/ci.yml/badge.svg)](https://github.com/danplaton4/tenancy-bundle/actions/workflows/ci.yml)[![demo-smoke](https://github.com/danplaton4/tenancy-bundle/actions/workflows/demo-smoke.yml/badge.svg)](https://github.com/danplaton4/tenancy-bundle/actions/workflows/demo-smoke.yml)[![Latest Stable Version](https://camo.githubusercontent.com/1da88536bc3473f07862f67bce8f8a31ebd0f31ff59543c0b8ffb44d2cc86ace/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f64616e706c61746f6e342f74656e616e63792d62756e646c652e737667)](https://packagist.org/packages/danplaton4/tenancy-bundle)[![PHP Version](https://camo.githubusercontent.com/8c2d2e696865a2aff6fbaaae0a67c32f5d603c052030bd666c8edc52bb285bc9/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f64616e706c61746f6e342f74656e616e63792d62756e646c652e737667)](https://packagist.org/packages/danplaton4/tenancy-bundle)[![License](https://camo.githubusercontent.com/caf4d25d3edc55f86b876de61cd264f50da14cb8bd76abd9042dd9c9b019055d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f64616e706c61746f6e342f74656e616e63792d62756e646c652e737667)](https://packagist.org/packages/danplaton4/tenancy-bundle)[![codecov](https://camo.githubusercontent.com/d607ed3e29e307c8c3a16df585477b05625f229a7d96f0755a7e85dd21788714/68747470733a2f2f636f6465636f762e696f2f67682f64616e706c61746f6e342f74656e616e63792d62756e646c652f6272616e63682f6d61737465722f67726170682f62616467652e737667)](https://codecov.io/gh/danplaton4/tenancy-bundle)

Tenancy Bundle
==============

[](#tenancy-bundle)

> **Multi-tenancy for Symfony. Zero boilerplate, zero leaks.**

[Documentation](https://danplaton4.github.io/tenancy-bundle/) · [Runnable demo](examples/saas/README.md) · [Changelog](CHANGELOG.md) · [Roadmap](https://danplaton4.github.io/tenancy-bundle/roadmap/) · [Upgrade guide](UPGRADE.md)

---

Resolve a tenant once at the edge of the request — every Symfony subsystem reconfigures itself for the rest of the lifecycle.

- The DBAL connection switches to the tenant's database (or the Doctrine SQL filter scopes every query)
- Cache pools namespace by tenant
- The Mailer transport swaps to the tenant's SMTP/DSN
- Messenger envelopes stamp the active tenant and re-boot it on the consumer side
- Your code stays tenant-unaware:

```
// Controller — no $tenantId parameter, no manual filtering, no leaks
public function index(InvoiceRepository $repo): Response
{
    return $this->render('invoice/index.html.twig', [
        'invoices' => $repo->findAll(),  // automatically scoped to the active tenant
    ]);
}
```

That's it. The event-driven kernel extension does the rest.

Why this exists
---------------

[](#why-this-exists)

Laravel has [`stancl/tenancy`](https://github.com/stancl/tenancy). Symfony users have been writing their own glue for years — manual `$tenantId` parameters, leaked queries discovered in production, half-built abstractions that don't compose with Doctrine + Messenger + Cache + Mailer at the same time. This bundle treats tenancy as a first-class kernel extension, not a database switcher bolted on top.

Quality signals
---------------

[](#quality-signals)

- **PHPStan level 9** clean — no `@phpstan-ignore`, no `mixed` shortcuts
- **559 tests / 2,068 assertions** across the unit + integration suites
- **CI matrix:** PHP 8.2 / 8.3 / 8.4 × Symfony 7.4 / 8.0, plus `prefer-lowest`, "No Doctrine", and "No Messenger" guard builds
- **`demo-smoke` live-stack gate:** every push to `master` rebuilds the three-tenant FrankenPHP + Caddy + MariaDB demo and exercises tenant isolation end-to-end via `bin/smoke.sh` (~90s)
- **ASVS-L1 threat model** per phase, security gate before phase verification
- **Strict mode on by default** — a missing tenant on a `#[TenantAware]` entity is an exception, not silent data leakage

Install
-------

[](#install)

```
composer require danplaton4/tenancy-bundle
```

Register the bundle in `config/bundles.php`, then run `bin/console tenancy:init` to scaffold `config/packages/tenancy.yaml`.

> **One-shot setup:** `bin/console tenancy:install` handles both steps in a single command. It uses `nikic/php-parser` to AST-edit `config/bundles.php` safely — install as a dev dependency first: `composer require --dev nikic/php-parser`. Without it the command exits 1 with a clear error and prints the manual snippet to paste.

Configure (`config/packages/tenancy.yaml`):

```
tenancy:
    driver: database_per_tenant   # or shared_db
    database:
        enabled: true
```

Mark tenant-scoped entities (shared-DB mode only):

```
use Tenancy\Bundle\Attribute\TenantAware;

#[ORM\Entity]
#[TenantAware]
class Invoice { /* ... */ }
```

That's the minimum. See the [Documentation](https://danplaton4.github.io/tenancy-bundle/) for resolver options, custom bootstrappers, Messenger integration, testing, and the contributor guide.

Try the demo
------------

[](#try-the-demo)

A runnable three-tenant Symfony app lives under [`examples/saas/`](examples/saas/README.md):

```
git clone https://github.com/danplaton4/tenancy-bundle.git
cd tenancy-bundle/examples/saas
docker compose up -d --wait --build           # ~30s warm, ~110s cold
open http://acme.tenancy.localhost/           # or curl -H 'Host: acme.tenancy.localhost' http://localhost/
```

Three tenants (`acme`, `globex`, `initech`) + a landlord page, FrankenPHP + Caddy + MariaDB 11, with the Profiler tab and Mailpit always-up. If host ports 80 / 8025 are already taken on your machine, override:

```
PORT_HTTP=8081 PORT_MAILPIT_UI=8026 docker compose up -d --wait --build
BASE_PORT=8081 bash bin/smoke.sh              # DNS-independent isolation proof
```

See [`examples/saas/README.md`](examples/saas/README.md) for the full walkthrough — three-step fallback ladder (curl Host: → `/etc/hosts` → browser-native `*.localhost`), Mailpit + Profiler walkthroughs, CI gate details.

Features
--------

[](#features)

- **Database-per-tenant** — DBAL connection switches at runtime per tenant via `TenantDriverMiddleware`, no `wrapper_class` config required
- **Shared-database** — Doctrine SQL filter with `#[TenantAware]` attribute; zero manual query scoping; strict-mode by default
- **5 built-in resolvers** — Host (subdomain), Origin header (SPA-friendly, priority 25, allow-listed), `X-Tenant-ID` header, query param, CLI `--tenant` flag. Chain in any order via config; add your own.
- **Cache namespace isolation** — per-tenant cache pool prefixing, no cross-tenant bleed
- **Mailer bootstrapper** — per-tenant SMTP DSN + `From` + `Reply-To` headers, sync + async safe via `X-Transport` strategy
- **Messenger context propagation** — `TenantStamp` attached to every envelope, re-booted on consume; works under sync and async transports
- **Symfony Profiler tab** — "Tenancy" panel in the WDT showing slug, label, driver, resolver, bootstrappers, error state. Auto-registered when `kernel.debug=true`, compile-stripped in prod
- **CLI commands** — `tenancy:install` (one-shot setup), `tenancy:init` (scaffold config), `tenancy:migrate` (run migrations per tenant), `tenancy:run` (wrap any command with tenant context)
- **PHPUnit testing trait** — `InteractsWithTenancy` sets up a clean tenant DB/schema per test method, real SQLite, no mocks
- **Custom entity support** — extend `AbstractTenant` (MappedSuperclass) to add columns like `brandColor`, `plan`, `billingId` without breaking Doctrine inheritance

How it works
------------

[](#how-it-works)

The bundle hooks into the Symfony kernel via a `kernel.request` listener at priority 20 (above Security at 8, below Router at 32). A resolver chain identifies the tenant from the request. Once resolved, `BootstrapperChain` runs every registered bootstrapper to reconfigure its subsystem. On `kernel.terminate`, tenant context is cleared.

```
Request → Router → TenantContextOrchestrator (priority 20)
                         │
                   ResolverChain
                   (Host / Origin / Header / QueryParam / Console)
                         │
                   TenantResolved event
                         │
                   BootstrapperChain.boot()
                    ├─ DatabaseSwitchBootstrapper
                    ├─ DoctrineBootstrapper
                    ├─ CacheBootstrapper
                    └─ MailerBootstrapper
                         │
                   TenantBootstrapped event
                         │
                     Controller runs
                         │
                   kernel.terminate
                         │
                   TenantContextCleared event

```

Bootstrappers are Symfony services tagged with `tenancy.bootstrapper` — add your own by implementing `TenantBootstrapperInterface` and tagging the service. No bundle internals to modify. See the [Custom Bootstrapper guide](https://danplaton4.github.io/tenancy-bundle/contributor-guide/custom-bootstrapper/).

Comparison
----------

[](#comparison)

Featuredanplaton4/tenancy-bundlestancl/tenancy (Laravel)RamyHakam (Symfony)ManualDatabase-per-tenant✅✅✅DIYShared-DB (SQL filter)✅✅❌DIY`#[TenantAware]` attribute✅❌ (traits)❌❌Cache isolation✅✅❌❌Mailer per-tenant✅✅❌❌Messenger context propagation✅✅❌❌5 resolvers incl. Origin header✅✅Host onlyDIYCLI tenant context (`tenancy:run`)✅✅❌❌Strict mode (default ON)✅❌❌❌One-command setup (`tenancy:install`)✅N/A❌❌PHPUnit testing trait✅✅❌❌PHPStan level 9✅❌❌❌Symfony Profiler / WDT panel✅N/A❌❌Runnable demo + CI smoke gate✅✅❌❌Philosophy
----------

[](#philosophy)

A data leak across tenants is a security incident, not a config mistake — so strict mode is on by default. Opt out explicitly if you understand the trade-off.

The bundle is a kernel extension, not just a database switcher: every Symfony subsystem (database, cache, queue, mailer, filesystem) participates in the tenant lifecycle through the same event-driven bootstrapper model. Doctrine is treated as an optional dependency — every entry point is guarded by `class_exists` / `interface_exists`, so the bundle installs cleanly into a Symfony app that doesn't use Doctrine at all.

Requirements
------------

[](#requirements)

- PHP `^8.2`
- Symfony `^7.4` or `^8.0`
- Optional: `doctrine/orm ^3`, `doctrine/dbal ^4`, `doctrine/migrations`, `symfony/messenger`, `symfony/mailer`

Documentation
-------------

[](#documentation)

The full docs site is published from `docs/` to ****.

Highlights:

- [Getting started](https://danplaton4.github.io/tenancy-bundle/user-guide/getting-started/)
- [Database-per-tenant guide](https://danplaton4.github.io/tenancy-bundle/user-guide/database-per-tenant/)
- [Shared-DB guide](https://danplaton4.github.io/tenancy-bundle/user-guide/shared-db/)
- [Cache isolation](https://danplaton4.github.io/tenancy-bundle/user-guide/cache-isolation/)
- [Messenger integration](https://danplaton4.github.io/tenancy-bundle/user-guide/messenger/)
- [Origin-header resolver (SPA)](https://danplaton4.github.io/tenancy-bundle/user-guide/origin-header-resolver/)
- [Profiler tab](https://danplaton4.github.io/tenancy-bundle/user-guide/profiler-tab/)
- [Testing with `InteractsWithTenancy`](https://danplaton4.github.io/tenancy-bundle/user-guide/testing/)
- [Architecture (contributor guide)](https://danplaton4.github.io/tenancy-bundle/contributor-guide/architecture/)

Roadmap
-------

[](#roadmap)

See the [roadmap on the documentation site](https://danplaton4.github.io/tenancy-bundle/roadmap/) for what's shipping next and what's tracked-but-unscheduled. Open a [GitHub issue](https://github.com/danplaton4/tenancy-bundle/issues) if you want something prioritized — real demand is the single strongest input to the next milestone's scope.

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports, design discussions, and PRs are all welcome — the bundle is small enough that the first contributor read of the code can land a real change in a single session.

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE).

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance98

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

7

Last Release

11d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/89d0cfaf99daa8764ec48c0aea9470106bcfdf0e28897fae92f6df4e3a482492?d=identicon)[danplaton4](/maintainers/danplaton4)

---

Top Contributors

[![danplaton4](https://avatars.githubusercontent.com/u/31653824?v=4)](https://github.com/danplaton4 "danplaton4 (3 commits)")

---

Tags

bundledatabase-per-tenantdoctrinedoctrine-ormfrankenphpmailermulti-tenantmultitenancyphpsaassymfonysymfonybundledoctrinesaasmulti-tenantmultitenancytenancy

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/danplaton4-tenancy-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/danplaton4-tenancy-bundle/health.svg)](https://phpackages.com/packages/danplaton4-tenancy-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k17.5M370](/packages/easycorp-easyadmin-bundle)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M195](/packages/sulu-sulu)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.4M506](/packages/shopware-core)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9017.2k55](/packages/open-dxp-opendxp)[chameleon-system/chameleon-base

The Chameleon System core.

1027.9k4](/packages/chameleon-system-chameleon-base)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
