Sprout for Laravel

website·Indexed 29 days ago

https://sproutforlaravel.com/

A multitenancy package for Laravel 1.x that provides tools for tenant identification, resolution, and configuration. It includes features such as Eloquent Tenant Providers, tenant routes, service overrides, and identity resolvers to help developers implement multitenancy within their Laravel applications.

Tokens
7.7K
Snippets
40
Records
53
Agent score
49%

What's inside Sprout

  1. Implement multitenancy in Laravel using Sprout

    Sprout is a multitenancy package for Laravel that allows developers to implement tenant-specific logic. Key features include:

    • Tenant Identification: Out-of-the-box support for identifying tenants via subdomains, URL paths, sessions, HTTP headers, and cookies. Custom identity resolvers can be written to extend this functionality.
    • Eloquent Integration: Seamlessly integrates with Eloquent models, supporting both tenant models and models that belong to one or more tenants.
    • Service Overrides: Provides tenant-specific authentication, filesystem disks, cache stores, cookies, and sessions without overriding the global application configuration.
    • Database Architecture: Uses a shared database and shared schema approach by default, where tenant data is isolated using database foreign keys.
  2. Understand Sprout Service Overrides

    Service overrides in Sprout are similar to service providers but are tied to the lifecycle of a tenant. They are triggered when the current tenant changes, allowing the application to set up for a new tenant and clean up after the previous one. Some overrides are 'bootable', meaning they perform actions once Laravel has booted, regardless of whether a tenant is currently identified.
  3. Understand Multitenanted Context in Sprout

    Certain Sprout features, such as the CurrentTenancy and CurrentTenant attributes, require a multitenanted context to function. If accessed outside this context, they may throw an exception. Sprout considers the following scenarios as multitenanted contexts:

    1. Tenant Routes: Any logic executing after a tenant has been successfully identified for a route.
    2. Jobs: When processing a job, provided the job service override is enabled.
  4. Implement Tenant Children in Eloquent Models

    Tenant children are models that belong to a tenant. Sprout automates scoping and association for these models when a multitenanted context is active.

    1. Use Sprout\Database\Eloquent\Concern\BelongsToTenant for models belonging to a single tenant.
    2. Use Sprout\Database\Eloquent\Concern\BelongsToManyTenants for models belonging to multiple tenants.
    3. Mark the relationship method with the #[TenantRelation] attribute.

    Effects when a tenant is active:

    • Read queries are automatically scoped to the current tenant.
    • New models are automatically associated with the current tenant upon saving.
    use Sprout\Attributes\TenantRelation;
    use Sprout\Database\Eloquent\Concern\BelongsToTenant;
    
    class Post extends Model
    {
        use BelongsToTenant;
    
        #[TenantRelation]
        public function blog(): BelongsTo
        {
            return $this->belongsTo(Blog::class);
        }
    }
  5. Generate tenant-aware URLs

    When a tenant is already identified via a parameter-based resolver, the standard Laravel route() helper automatically populates parameters. To generate a tenant URL from outside a multitenanted context (where no tenant is currently active), use the Sprout facade or the sprout() helper method.
    use Sprout\Facades\Sprout;
    
    // Using the Facade
    Sprout::route('my.route.name', $tenant);
    
    // Using the helper
    sprout()->route('my.route.name', $tenant);
  6. Install Sprout in a Laravel application

    Install Sprout using Composer. The package supports Laravel's auto-discovery, but can be manually registered in bootstrap/providers.php before the AppServiceProvider if necessary. After installation, publish the configuration files using the artisan command.
    composer require sprout/sprout
    php artisan vendor:publish --provider="Sprout\SproutServiceProvider"
  7. Create an Eloquent Tenant Model

    For Eloquent-based tenants, use the Sprout\Database\Eloquent\Concerns\IsTenant trait. This trait provides default implementations for the Tenant interface. By default, it assumes the tenant identifier is stored in a column named identifier and uses the model's primary key as the tenant key. To use a different column for the identifier, override the getTenantIdentifierName method.
  8. Register multitenanted routes using subdomains

    To identify tenants via subdomains, set the TENANTED_DOMAIN environment variable to your main domain. Use the Route::tenanted() macro in routes/web.php to wrap routes that should be scoped to a tenant.

    Note: If using subdomains, wrap non-tenanted routes in a route group using the main application domain to prevent them from being accessible under tenant subdomains.

    TENANTED_DOMAIN=localhost
    Route::tenanted(function () {
        Route::get('/', function () {
            return view('welcome');
        });
    });
  9. Define tenant routes using the Route::tenanted macro

    The preferred way to define routes that require a tenant is using the Route::tenanted router macro. This approach ensures that identity resolvers capable of using route parameters are configured correctly, avoiding issues with URL generation and non-tenanted routes. You can optionally specify a resolver and a tenancy name.
    // Use default resolver and tenancy
    Route::tenanted(function () {
        // Tenant Routes
    });
    
    // Default tenancy, manual resolver
    Route::tenanted(function () {
        // Tenant Routes
    }, 'subdomain');
    
    // Default resolver, manual tenancy
    Route::tenanted(function () {
        // Tenant Routes
    }, null, 'tenants');
    
    // Manual resolver, manual tenancy
    Route::tenanted(function () {
        // Tenant Routes
    }, 'subdomain', 'tenants');
  10. Bypass Tenant Restrictions for Child Models

    To perform queries or operations that ignore automatic tenant scoping and ownership validation, use the ignoreTenantRestrictions() and resetTenantRestrictions() methods, or the withoutTenantRestrictions() callback helper. Use these with caution to avoid leaking data between tenants.
    // Option 1: Manual toggle
    Post::ignoreTenantRestrictions();
    $posts = Post::all();
    Post::resetTenantRestrictions();
    
    // Option 2: Callback wrapper
    $posts = Post::withoutTenantRestrictions(function () {
        return Post::all();
    });
  11. Scope child models to a tenant

    To make a model (e.g., Post) a child of a tenant, use the Sprout\Database\Eloquent\Concerns\BelongsToTenant trait and mark the tenant relationship method with the #[TenantRelation] attribute. This ensures that queries are automatically scoped to the current tenant and new records are automatically associated with the current tenant.
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Database\Eloquent\Relations\BelongsTo;
    use Sprout\Attributes\TenantRelation;
    use Sprout\Database\Eloquent\Concerns\BelongsToTenant;
    
    class Post extends Model
    {
        use BelongsToTenant;
    
        #[TenantRelation]
        public function blog(): BelongsTo
        {
            return $this->belongsTo(Blog::class);
        }
    }
  12. Create Optional Tenant Children

    For models that can belong to a tenant but may also be global (belong to no tenant), implement the Sprout\Database\Eloquent\Contracts\OptionalTenant marker interface.

    Behavior for Optional Tenants:

    • The tenant relation is NOT automatically populated.
    • No exception is thrown during hydration if the model has no tenant.
    • Read queries are automatically scoped to the current tenant AND null (global records).
    • You must manually set the tenant relation when associating the model with a tenant.
    use Sprout\Attributes\TenantRelation;
    use Sprout\Database\Eloquent\Concern\BelongsToTenant;
    use Sprout\Database\Eloquent\Contracts\OptionalTenant;
    
    class Category extends Model implements OptionalTenant
    {
        use BelongsToTenant;
    
        #[TenantRelation]
        public function blog(): BelongsTo
        {
            return $this->belongsTo(Blog::class);
        }
    }