Skip to content

Zhortein/multi-tenant-bundle

Repository files navigation

Zhortein Multi-Tenant Bundle

A comprehensive Symfony 7+ bundle for building multi-tenant applications with PostgreSQL 16 support.

PHP Version Symfony Version PostgreSQL Version

Features

  • 🏒 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

Installation

Install the bundle via Composer:

composer require zhortein/multi-tenant-bundle

Enable the bundle in your config/bundles.php:

<?php

return [
    // ...
    Zhortein\MultiTenantBundle\ZhorteinMultiTenantBundle::class => ['all' => true],
];

Quick Start

1. Create Your Tenant Entity

<?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
}

2. Configure the Bundle

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

3. Create Tenant-Aware Entities

<?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
}

4. Use in Controllers

<?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,
        ]);
    }
}

πŸ“š Documentation

πŸš€ Getting Started

πŸ—οΈ Core Concepts

πŸ”§ Service Integration

  • Mailer - Tenant-aware email with templated support
  • Messenger - Tenant-aware queues with automatic context propagation
  • Storage - File storage isolation

πŸ—„οΈ Database Management

πŸ› οΈ Development Tools

πŸ“– Examples

Testing with the Bundle

The bundle includes a comprehensive Test Kit to make testing multi-tenant applications easy and reliable:

Test Kit Features

  • 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

Quick Example

<?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);
            });
        });
    }
}

Running Tests

# 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-coverage

See the Testing Documentation for complete Test Kit usage.

Code Quality

# 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

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for your changes
  4. Ensure all tests pass and code meets quality standards
  5. Submit a pull request

See CONTRIBUTING.md for detailed guidelines.

License

This bundle is released under the MIT License. See the LICENSE file for details.

Support

Changelog

See CHANGELOG.md for version history and upgrade instructions.

About

Symfony bundle for multi-tenant applications

Resources

License

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors