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

Laravel Singledb Tenancy Laravel Package

roberts/laravel-singledb-tenancy

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require roberts/laravel-singledb-tenancy
    

    Publish the config and migration:

    php artisan vendor:publish --provider="Roberts\SingleDbTenancy\SingleDbTenancyServiceProvider"
    php artisan migrate
    
  2. Configure Tenant Model: Extend Roberts\SingleDbTenancy\Contracts\Tenant in your Tenant model:

    use Roberts\SingleDbTenancy\Contracts\Tenant as TenantContract;
    
    class Tenant extends Model implements TenantContract
    {
        // Your tenant fields (e.g., `domain`, `subdomain`, `id`)
    }
    
  3. Define Tenant Resolution: In config/singledb-tenancy.php, set the resolution logic (e.g., subdomain):

    'resolution' => [
        'type' => 'subdomain',
        'column' => 'subdomain',
    ],
    
  4. First Use Case: Deploy a tenant-aware route (e.g., tenant.{subdomain}.example.com) and test with:

    php artisan route:list
    

    Verify tenant resolution via Tinker:

    use Roberts\SingleDbTenancy\Facades\Tenancy;
    Tenancy::resolve('test-tenant'); // Manually trigger resolution
    

Implementation Patterns

Core Workflows

  1. Tenant-Aware Routes: Use middleware to enforce tenancy:

    Route::middleware(['tenant'])->group(function () {
        Route::get('/', 'HomeController@index');
    });
    
    • Dynamic Subdomains: Leverage Laravel’s RouteServiceProvider to bind subdomains:
      Route::domain('{tenant}.app.test')->group(...);
      
  2. Model Scoping: Automatically scope queries to the current tenant:

    class User extends Model
    {
        public function scopeTenant($query)
        {
            return $query->where('tenant_id', Tenancy::tenant()->id);
        }
    }
    
    • Global Scoping: Enable via config:
      'scoping' => [
          'enabled' => true,
          'models' => [User::class, Post::class],
      ],
      
  3. Tenant Context: Access the current tenant anywhere:

    $tenant = Tenancy::tenant(); // Current tenant model
    $tenantId = Tenancy::id();   // Current tenant ID
    
    • View Helpers: Use @tenant in Blade:
      <h1>Welcome, {{ current_tenant()->name }}</h1>
      
  4. Middleware Integration:

    • Custom Resolution: Override resolution logic in a middleware:
      public function handle($request, Closure $next)
      {
          Tenancy::resolveByHeader('X-Tenant-ID');
          return $next($request);
      }
      
    • Fallback Tenancy: Use Tenancy::fallback() for public routes.
  5. API Tenancy: For APIs, resolve tenants via headers or tokens:

    Tenancy::resolveByHeader('X-Tenant-Token');
    

Integration Tips

  • Queue Jobs: Use Tenancy::runInContext() for tenant-specific jobs:
    Tenancy::runInContext($tenant, function () {
        SendWelcomeEmail::dispatch($user);
    });
    
  • Testing: Mock tenancy in tests:
    Tenancy::fake()->setTenant($fakeTenant);
    
  • Seeding: Use Tenancy::seedForTenant() to seed data per tenant.

Gotchas and Tips

Pitfalls

  1. Resolution Conflicts:

    • Issue: Multiple tenants resolve to the same subdomain/domain.
    • Fix: Implement custom resolution logic in app/Providers/TenancyServiceProvider:
      public function boot()
      {
          Tenancy::resolver(function ($request) {
              return Tenant::where('custom_field', $request->input('tenant_id'))->first();
          });
      }
      
  2. Query Performance:

    • Issue: Global scoping adds tenant_id to every query.
    • Fix: Disable for non-tenant models or use withoutGlobalScope(TenantScope::class).
  3. Middleware Order:

    • Issue: Tenancy middleware must run before other tenant-aware middleware.
    • Fix: Register in App\Http\Kernel above other middleware:
      protected $middlewareGroups = [
          'web' => [
              \Roberts\SingleDbTenancy\Http\Middleware\ResolveTenancy::class,
              // Other middleware...
          ],
      ];
      
  4. Database Locks:

    • Issue: Concurrent requests may cause race conditions during tenant resolution.
    • Fix: Use database transactions for critical operations:
      DB::transaction(function () {
          Tenancy::resolve($tenant);
          // Critical operations...
      });
      
  5. Caching:

    • Issue: Cached routes or views may not reflect tenant-specific data.
    • Fix: Invalidate cache per tenant or use tenant-aware cache keys:
      Cache::put("tenant:{$tenantId}:key", $value);
      

Debugging

  • Log Tenant Resolution: Enable debug mode in config:

    'debug' => env('TENANCY_DEBUG', false),
    

    Check logs for resolution events.

  • Tenant Context Leaks:

    • Symptom: Tenant-specific data leaks between requests.
    • Fix: Ensure Tenancy::forget() is called in middleware termination or use Tenancy::clear().
  • Migration Issues:

    • Symptom: Tenant-specific migrations fail.
    • Fix: Use Tenancy::seedForTenant() or run migrations in a tenant context:
      php artisan migrate --tenant=1
      

Extension Points

  1. Custom Tenant Models: Extend the Tenant model to add fields like plan_id or features:

    class Tenant extends Model implements TenantContract
    {
        protected $casts = [
            'features' => 'array',
        ];
    }
    
  2. Dynamic Tenant Fields: Override getTenantIdentifier() to use dynamic fields:

    public function getTenantIdentifier()
    {
        return $this->subdomain ?? $this->domain;
    }
    
  3. Webhook Tenancy: Resolve tenants via webhook payloads:

    Tenancy::resolver(function ($request) {
        return Tenant::where('webhook_secret', $request->secret)->first();
    });
    
  4. Multi-Factor Resolution: Combine domain + API key for resolution:

    Tenancy::resolver(function ($request) {
        return Tenant::where('domain', $request->domain)
            ->where('api_key', $request->header('X-API-KEY'))
            ->first();
    });
    
  5. Tenant-Specific Config: Load tenant-specific config (e.g., config/tenants/{tenant_id}.php):

    $tenantConfig = require config_path("tenants/{$tenantId}.php");
    
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.
daikazu/eloquent-salesforce-objects
unseen-codes/chat
romalytar/yammi-jobs-monitoring-laravel
kisame76/filament-db-table-state
nqxcode/laravel-lucene-search
dpfx/laravel-livewire-wizards
workos/workos-php-laravel
sofa/laravel-global-scope
nawasara/auth-primitives
adhocrat-io/arkhe-main
make-dev/orca-harpoon
itsemon245/lamet
baks-dev/dashboard
amoifr/pickle-panther-bundle
make-dev/orca
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle