Weave Code
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

Multitenant Bundle Laravel Package

cvele/multitenant-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require cvele/multitenant-bundle
    

    Enable the bundle in config/bundles.php:

    return [
        // ...
        Cvele\MultiTenantBundle\CveleMultiTenantBundle::class => ['all' => true],
    ];
    
  2. Configure Tenant Resolution Define a tenant resolver service in config/services.yaml:

    services:
        App\Service\TenantResolver:
            arguments:
                $tenantParam: 'tenant'  # URL parameter name
    

    Register it as the default resolver in config/packages/cvele_multitenant.yaml:

    cvele_multitenant:
        resolver: App\Service\TenantResolver
    
  3. First Use Case: Tenant-Aware Entity Add the TenantAwareInterface and TenantAwareTrait to an entity:

    use Cvele\MultiTenantBundle\Model\TenantAwareInterface;
    use Cvele\MultiTenantBundle\Model\TenantAwareTrait;
    
    class Product implements TenantAwareInterface
    {
        use TenantAwareTrait;
        // ...
    }
    

    The bundle will now automatically filter queries and set the tenant ID.


Implementation Patterns

Core Workflows

  1. Tenant Resolution

    • URL Parameter: Pass ?tenant=acme to switch tenants dynamically.
    • Service Method: Use tenant() helper in controllers:
      $tenant = $this->get('cvele_multitenant.tenant');
      
    • Middleware: Resolve tenants early in the request lifecycle (e.g., via Symfony’s ON_KERNEL_REQUEST).
  2. Entity Integration

    • Automatic Filtering: Queries on TenantAware entities auto-apply WHERE tenant_id = ?.
    • Manual Overrides: Disable filtering for specific queries:
      $queryBuilder->andWhere('...')->disableTenantFiltering();
      
    • Bulk Operations: Use TenantAwareTrait::setTenant() for explicit tenant assignment:
      $product->setTenant($tenant); // Overrides automatic resolution
      
  3. Multi-Tenant Relationships

    • Shared vs. Isolated:
      • Shared Entities: Use TenantAwareTrait for tenant-scoped data (e.g., Product).
      • Global Entities: Exclude from filtering (e.g., User if using FOSUserBundle).
    • Polymorphic Tenancy: Extend TenantAwareTrait for custom tenant resolution logic.
  4. API/CLI Context

    • APIs: Pass tenant via headers (X-Tenant-ID) or subdomains (acme.example.com).
    • CLI: Set tenant manually in commands:
      $this->tenantResolver->setTenant($tenantId);
      

Gotchas and Tips

Pitfalls

  1. Query Performance

    • N+1 Problem: Eager-load relationships to avoid repeated tenant filtering:
      $products = $repo->findBy([], ['tenant' => 'ASC']);
      
    • Indexing: Ensure tenant_id is indexed in the database.
  2. Caching Quirks

    • Cache Invalidation: Tenant-scoped caches must include tenant ID in keys:
      $cache->get("tenant:{$tenantId}:products", ...);
      
    • OPcache: Clear OPcache after changing entity mappings.
  3. Migration Risks

    • Schema Changes: Add tenant_id as a nullable column initially to avoid downtime:
      ALTER TABLE products ADD tenant_id INT NULL;
      
    • Data Migration: Use a script to backfill tenant_id for existing records.
  4. Symfony Version Mismatch

    • Deprecation Warnings: The bundle targets Symfony 2.4+. Use a wrapper or fork for Symfony 5+:
      // Example: Override resolver for Symfony 5
      $resolver = new TenantResolver($requestStack, 'tenant');
      $this->tenantResolver = $resolver;
      

Debugging Tips

  1. Tenant Resolution Logs Enable debug mode to trace tenant resolution:

    cvele_multitenant:
        debug: true
    

    Check logs for Tenant resolved to: {id} entries.

  2. Query Debugging Use Doctrine’s query logging to verify filtering:

    doctrine:
        dbal:
            logging: true
            profiling: true
    
  3. Common Errors

    • Missing Tenant: Handle TenantNotFoundException in controllers:
      try {
          $tenant = $this->tenantResolver->resolve();
      } catch (TenantNotFoundException $e) {
          return new JsonResponse(['error' => 'Invalid tenant'], 400);
      }
      
    • Circular References: Avoid bidirectional relationships where both sides are TenantAware.

Extension Points

  1. Custom Resolvers Implement Cvele\MultiTenantBundle\Resolver\TenantResolverInterface for:

    • Subdomain-based tenancy (acme.example.com).
    • JWT-based tenant extraction.
  2. Dynamic Tenant Switching Override the TenantAwareTrait to support runtime tenant changes:

    class DynamicTenantAwareTrait {
        public function setTenant(TenantInterface $tenant, bool $persist = true) {
            $this->tenant = $tenant;
            if ($persist) $this->markAsDirty('tenant_id');
        }
    }
    
  3. Non-Database Tenants Use the TenantInterface to support external tenant sources (e.g., Redis):

    class RedisTenant implements TenantInterface {
        public function getId() { /* ... */ }
        public function getName() { /* ... */ }
    }
    
  4. Testing Mock the tenant resolver in PHPUnit:

    $tenant = $this->createMock(TenantInterface::class);
    $tenant->method('getId')->willReturn(1);
    $this->tenantResolver->setTenant($tenant);
    
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.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui