A comprehensive Symfony 7+ bundle for building multi-tenant applications with PostgreSQL 16 support.
- π’ Multiple Tenant Resolution Strategies: Subdomain, path-based, header-based, domain-based, DNS TXT, hybrid, or custom resolvers
- ποΈ Database Strategies: Shared database with filtering or separate databases per tenant
- β‘ Performance Optimized: Built-in caching for tenant settings and configurations
- π§ Doctrine Integration: Automatic tenant filtering with Doctrine ORM
- π§ Tenant-Aware Services: Mailer with automatic tenant propagation, Messenger with context preservation, and file storage integration
- π― Event-Driven: Database switching events and automatic tenant context resolution
- π οΈ Advanced Commands: Schema management, migrations, and fixtures for tenants
- π§ͺ Comprehensive Test Kit: First-class testing utilities to prove tenant isolation works
- π RLS Integration: PostgreSQL Row-Level Security for defense-in-depth
- π PHPStan Level Max: Static analysis at maximum level
Install the bundle via Composer:
composer require zhortein/multi-tenant-bundleEnable the bundle in your config/bundles.php:
<?php
return [
// ...
Zhortein\MultiTenantBundle\ZhorteinMultiTenantBundle::class => ['all' => true],
];<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zhortein\MultiTenantBundle\Entity\TenantInterface;
#[ORM\Entity]
#[ORM\Table(name: 'tenants')]
class Tenant implements TenantInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255, unique: true)]
private string $slug;
#[ORM\Column(type: 'string', length: 255)]
private string $name;
// Implement TenantInterface methods...
public function getId(): ?int
{
return $this->id;
}
public function getSlug(): string
{
return $this->slug;
}
public function setSlug(string $slug): void
{
$this->slug = $slug;
}
// ... other methods
}Create config/packages/zhortein_multi_tenant.yaml:
zhortein_multi_tenant:
tenant_entity: 'App\Entity\Tenant'
resolver:
type: 'subdomain'
options:
base_domain: 'example.com'
database:
strategy: 'shared_db'
enable_filter: true<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zhortein\MultiTenantBundle\Attribute\AsTenantAware;
use Zhortein\MultiTenantBundle\Entity\TenantAwareEntityTrait;
#[ORM\Entity]
#[AsTenantAware]
class Product
{
use TenantAwareEntityTrait;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private string $name;
// ... other properties and methods
}<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Zhortein\MultiTenantBundle\Context\TenantContextInterface;
class DashboardController extends AbstractController
{
public function index(TenantContextInterface $tenantContext): Response
{
$tenant = $tenantContext->getTenant();
// All database queries are automatically filtered by tenant
$products = $this->entityManager
->getRepository(Product::class)
->findAll(); // Only returns current tenant's products
return $this->render('dashboard/index.html.twig', [
'tenant' => $tenant,
'products' => $products,
]);
}
}- Installation & Setup - Complete installation guide
- Configuration Reference - All configuration options
- Database Strategies - Shared DB vs Multi-DB
- Tenant Context - Tenant resolution and access
- Tenant Resolution - Resolution strategies
- DNS TXT Resolver - DNS-based tenant resolution
- Domain Resolvers - Domain and hybrid resolvers
- Doctrine Tenant Filter - Automatic filtering
- Tenant Settings - Configuration system
- Mailer - Tenant-aware email with templated support
- Messenger - Tenant-aware queues with automatic context propagation
- Storage - File storage isolation
- Migrations - Database migrations
- Fixtures - Test data loading
- CLI Commands - Console commands
- Testing - Testing strategies and Test Kit
- FAQ - Common questions
- Basic Usage - Code examples
- Mailer Examples - Email templates and configuration
- Messenger Examples - Message routing and handling
- Service Integration - Practical implementations
The bundle includes a comprehensive Test Kit to make testing multi-tenant applications easy and reliable:
- WithTenantTrait: Execute code within specific tenant contexts
- TestData: Lightweight builders for tenant-aware test entities
- Base Test Classes: Pre-configured for HTTP, CLI, and Messenger testing
- RLS Isolation Tests: Prove PostgreSQL Row-Level Security works as defense-in-depth
<?php
use Zhortein\MultiTenantBundle\Tests\Toolkit\TenantWebTestCase;
class ProductControllerTest extends TenantWebTestCase
{
public function testTenantIsolation(): void
{
// Seed test data
$this->getTestData()->seedProducts('tenant-a', 2);
$this->getTestData()->seedProducts('tenant-b', 1);
// Test tenant A sees only its data
$this->withTenant('tenant-a', function () {
$products = $this->repository->findAll();
$this->assertCount(2, $products);
});
// Test RLS isolation (critical test)
$this->withTenant('tenant-a', function () {
$this->withoutDoctrineTenantFilter(function () {
$products = $this->repository->findAll();
// Should still see only 2 products due to RLS
$this->assertCount(2, $products);
});
});
}
}# Run all tests
make test
# Run unit tests only
make test-unit
# Run integration tests only
make test-integration
# Run Test Kit RLS isolation tests
vendor/bin/phpunit tests/Integration/RlsIsolationTest.php
# Run with coverage
make test-coverageSee the Testing Documentation for complete Test Kit usage.
# PHPStan at maximum level
make phpstan
# PHP-CS-Fixer code style check
make csfixer-check
# Fix code style
make csfixer
# Run all quality checks
make dev-check- Fork the repository
- Create a feature branch
- Write tests for your changes
- Ensure all tests pass and code meets quality standards
- Submit a pull request
See CONTRIBUTING.md for detailed guidelines.
This bundle is released under the MIT License. See the LICENSE file for details.
- Documentation: Complete documentation
- Issues: GitHub Issues
- Discussions: GitHub Discussions
See CHANGELOG.md for version history and upgrade instructions.