apajo/symfony-multi-tenancy-bundle

Symfony multi tenancy bundle

0.4.10 2024-10-02 02:05 UTC

README

Description

There are many packages that provide multi tenancy in Symfony but all of them focus on the database. This package's goal is to provide a way to manage any kind of configuration in your system.

This bundle aims to provide multi tenancy on a higher level. It provides a way to dynamically change the system configuration based on the tenant (database, media provider, mailer etc).

It also bundles a way to manage migrations for each tenant.

The package's development is in early stages, any feedback is welcome.

Requirements

Installation

composer require apajo/symfony-multi-tenancy-bundle

Configuration

doctrine.yml

You need 2 connections and entity_managers:

doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        url: '%env(DEFAULT_DATABASE_URL)%'
        driver: pdo_mysql
        charset: utf8
        server_version: '8'

      tenant:
        url: '%env(TENANT_DATABASE_URL)%'
        driver:   pdo_mysql
        charset:  utf8
        server_version: '8'

  orm:
    default_entity_manager: default
    auto_generate_proxy_classes: true
    
    entity_managers:
      default:
        connection: default
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware

      tenant:
        connection: tenant
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        mappings:

In this case thay are named default and tenant but you can name them as you wish.

NB! Third party packages may require the default connection to be present so you might want to keep the default name.

Connection and entity manager default are common for all the individual tenants. Connection and entity manager tenant are specific for the tenant.

doctrine_migrations.yml

doctrine_migrations:
  migrations_paths:
    'App\Migrations\Default': '%kernel.project_dir%/migrations/default'
    'App\Migrations\Tenant': '%kernel.project_dir%/migrations/tenant'

Configuration

apajo_multi_tenancy:
  adapters: # Adapters dynamically change the system configuration for selected tenant
    - aPajo\MultiTenancyBundle\Adapter\Database\DatabaseAdapter
    - aPajo\MultiTenancyBundle\Adapter\Filesystem\FilesystemAdapter
    - aPajo\MultiTenancyBundle\Adapter\Mailer\MailerAdapter
  
  tenant:                                   # Tenant (entity) configuration
    class: App\Entity\Tenant                # Must implement TenantInterface
    identifier: key                         # Identifier column name (must be unique field)
    entity_manager: default                 # Tenant entity manager name
    resolvers:                              # Resolvers resolve the tenant based on the request
      - aPajo\MultiTenancyBundle\Service\Resolver\HostBasedResolver 
      
  migrations: # Migration configuration
    namespace: App\Migrations\Tenant
    entity_manager: tenant

Adapters

Adapters are responsible for dynamic configuration changes based on tenant table values at runtime.

For more on (built-in) adapters see Adapters directory

Resolvers

Resolvers are responsible for resolving current tenant.

For more on (built-in) resolvers see Resolvers directory

Database migrations

Create migration (or diff)

php bin/console tenants:migrations:diff

Migrate tenants

php bin/console tenants:migrations:migrate:all

Migrate a single tenant

# TODO: Implement
# php bin/console tenants:migrations:migrate [tenant_id]

Examples

Switch/select tenant

use aPajo\MultiTenancyBundle\Service\EnvironmentProvider;
use aPajo\MultiTenancyBundle\Entity\TenantInterface;
use aPajo\MultiTenancyBundle\Event\TenantSelectEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface

class Tenant implements TenantInterface {
    // ...
}

class MyTenantService {
  public function __construct (
    private EnvironmentProvider $environmentProvider,
    private EventDispatcherInterface $dispatcher,
  ) {
  }
  
  
  /**
   * Use the EnvironmentProvider to select a different tenant
   */
  public function select () {
    $tenant = new Tenant();
    
    $environmentProvider->select($tenant);
    // Now the system is configured based on the tenant
  }

  /**
   * You can also dispatch an event to select a new tenant
   */
  public function alternativeSelect () {
      $tenant = new Tenant();
      
      $event = new TenantSelectEvent($tenant);
      $this->dispatcher->dispatch($event);
  }
}

Iterate over all tenant environments

use aPajo\MultiTenancyBundle\Service\EnvironmentProvider;
use aPajo\MultiTenancyBundle\Entity\TenantInterface;

class MyTenantService {
  public function __construct (
    private EnvironmentProvider $environmentProvider,
  ) {
  
    $environmentProvider->forAll(function (TenantInterface $tenant) {
      // Each iteration will have tenant specific configuration/environment
    });
    
  }

}

Issues

Feel free to report an issue under GitHub Issues

Known Issues

  • Resetting to default/initial tenant does not work

Contributing

Feel free to contribute

Thanks to

This bundle is inspired by the RamyHakam / multi_tenancy_bundle