From a800170b489d9589a94e72c908007e934a39af29 Mon Sep 17 00:00:00 2001 From: eramitgupta Date: Thu, 28 May 2026 15:30:59 +0530 Subject: [PATCH 1/5] Add comprehensive tenancy package documentation to boost resources --- resources/boost/skills/tenancy/SKILL.md | 191 ++++++++++ .../skills/tenancy/references/package.md | 339 ++++++++++++++++++ 2 files changed, 530 insertions(+) create mode 100644 resources/boost/skills/tenancy/SKILL.md create mode 100644 resources/boost/skills/tenancy/references/package.md diff --git a/resources/boost/skills/tenancy/SKILL.md b/resources/boost/skills/tenancy/SKILL.md new file mode 100644 index 000000000..673795d4b --- /dev/null +++ b/resources/boost/skills/tenancy/SKILL.md @@ -0,0 +1,191 @@ +--- +name: tenancy +description: "Activate when the user is building or debugging multi-tenant Laravel behavior with stancl/tenancy. Use for tenancy:install, tenant identification middleware, central and tenant routes, tenant model and domain model setup, multi-database or single-database tenancy, tenant-aware bootstrappers for database/cache/filesystem/queue/session/Redis, tenant context switching with tenancy()->initialize() or tenant()->run(), tenant migrations and seeders, tenant asset routes, pending tenants, resource syncing, user impersonation, RLS, Vite bundling, or testing tenant-aware behavior." +license: MIT +metadata: + author: laravel +--- + +# Tenancy For Laravel + +Use this skill when a Laravel task involves `stancl/tenancy`. + +## Documentation + +Use `search-docs` first when it is available for Laravel integration patterns. For package-specific behavior, inspect: + +- `src/TenancyServiceProvider.php` +- `src/Tenancy.php` +- `assets/config.php` +- `assets/routes.php` +- `src/Middleware/*` +- `src/Bootstrappers/*` +- `src/Commands/*` +- `src/Database/Models/*` +- `src/Resolvers/*` +- `src/Features/*` +- `references/package.md` + +Load `references/package.md` when the task needs package-specific detail beyond the core workflow in this file. + +## Package Surface + +The package auto-discovers: + +- Service provider: `Stancl\Tenancy\TenancyServiceProvider` +- Facades: `Tenancy` and `GlobalCache` + +The package also publishes: + +- `config/tenancy.php` +- `routes/tenant.php` +- `app/Providers/TenancyServiceProvider.php` +- tenant, domain, impersonation, and resource-syncing migrations + +## Installation And Setup + +Install the package with Composer: + +```bash +composer require stancl/tenancy +``` + +Prefer the package installer over manual publishing: + +```bash +php artisan tenancy:install --no-interaction +``` + +That command publishes the config, routes, provider, core migrations, and creates `database/migrations/tenant`. + +## Core Working Pattern + +1. Install the package and inspect `config/tenancy.php`. +2. Decide the tenant identification strategy first: domain, subdomain, domain-or-subdomain, path, request data, or origin header. +3. Keep central and tenant routes explicit. Use the package middleware and route modes instead of ad hoc request checks. +4. Choose the minimum bootstrapper set that matches the app's infrastructure. +5. For data isolation, decide between multi-database tenancy, single-database tenancy, or PostgreSQL RLS before writing application models. +6. Test both central and tenant contexts. + +## Tenant Identification + +The default identification middleware is `InitializeTenancyByDomain`. + +Available identification middleware: + +- `InitializeTenancyByDomain` +- `InitializeTenancyBySubdomain` +- `InitializeTenancyByDomainOrSubdomain` +- `InitializeTenancyByPath` +- `InitializeTenancyByRequestData` +- `InitializeTenancyByOriginHeader` + +Use `PreventAccessFromUnwantedDomains` only with the domain-oriented identification middleware recognized by the package config. + +For path identification, the package uses `PathTenantResolver` and a route parameter name configured in `tenancy.identification.resolvers`. + +## Tenant Context + +Common patterns: + +```php +tenancy()->initialize($tenant); + +$tenant->run(function () { + // Code in tenant context. +}); + +tenancy()->central(function () { + // Code in central context. +}); +``` + +Prefer the package context helpers instead of manually mutating connections, cache prefixes, filesystem roots, or config. + +## Bootstrappers + +Default bootstrappers cover: + +- tenant database connection switching +- cache scoping +- filesystem scoping +- queue scoping +- database session support + +Optional bootstrappers exist for: + +- Redis scoping +- database-backed cache scoping +- tenant config injection +- URL, root URL, and asset generation +- mail config +- broadcasting config and channel prefixing +- Fortify and Scout integration +- PostgreSQL RLS + +If tenant state appears partially applied, inspect the active bootstrappers in `config/tenancy.php` before changing application code. + +## Routes And Assets + +The package registers route middleware groups for `clone`, `universal`, `tenant`, and `central`. + +When tenancy routes are enabled, it registers tenant asset routes from `assets/routes.php`. For path-based identification, asset routes can include the tenant parameter prefix. + +Keep tenant routes in `routes/tenant.php` when the app uses the published route stub. + +## Data Model Guidance + +The default tenant model is `Stancl\Tenancy\Database\Models\Tenant`. + +Related package models: + +- `Stancl\Tenancy\Database\Models\Domain` +- `Stancl\Tenancy\Database\Models\ImpersonationToken` + +Use package traits and helpers before inventing your own tenant key, domain, or tenant-run abstractions. + +When changing tenant identity generation, use a class implementing `UniqueIdentifierGenerator` or set the generator to `null` only if the application intentionally uses auto-incrementing IDs. + +## Commands + +Common package commands: + +- `tenancy:install` +- `tenants:migrate` +- `tenants:rollback` +- `tenants:seed` +- `tenants:run` +- `tenant:tinker` +- `tenants:list` +- `tenants:down` +- `tenants:up` +- `tenants:link` + +Use the package commands for tenant-aware migration, seeding, maintenance, and per-tenant execution instead of custom loops. + +## Features + +Optional features include: + +- `UserImpersonation` +- `TelescopeTags` +- `CrossDomainRedirect` +- `ViteBundler` +- `DisallowSqliteAttach` +- `TenantConfig` + +Enable features through `tenancy.features` and check each feature's class before assuming it changes bootstrapping behavior. + +## Testing + +Test both successful identification and failure behavior. + +For tenancy-aware tests, verify: + +- the request resolves the correct tenant +- central routes stay central +- tenant routes reject central access when expected +- tenant context affects database, cache, filesystem, queue, and URL behavior as intended +- tenant artisan commands run against the expected tenants + +If a behavior depends on package internals, inspect `tests/*` in the package or load `references/package.md` before adding application-level workarounds. diff --git a/resources/boost/skills/tenancy/references/package.md b/resources/boost/skills/tenancy/references/package.md new file mode 100644 index 000000000..aa7466ec1 --- /dev/null +++ b/resources/boost/skills/tenancy/references/package.md @@ -0,0 +1,339 @@ +# Tenancy Package Reference + +This reference is for package-specific details that do not need to live in `SKILL.md`. + +## Main Entry Points + +- `src/TenancyServiceProvider.php` +- `src/Tenancy.php` +- `assets/config.php` +- `assets/routes.php` +- `src/helpers.php` + +## Published Files + +The package publishes: + +- `config/tenancy.php` +- `routes/tenant.php` +- `app/Providers/TenancyServiceProvider.php` +- `database/migrations/2019_09_15_000010_create_tenants_table.php` +- `database/migrations/2019_09_15_000020_create_domains_table.php` +- impersonation migrations +- resource syncing migrations + +`tenancy:install` also creates `database/migrations/tenant`. + +## Service Provider Behavior + +`TenancyServiceProvider`: + +- merges `assets/config.php` into `tenancy` +- binds `Stancl\Tenancy\Database\DatabaseManager` as a singleton +- binds `Stancl\Tenancy\Tenancy` as a singleton +- binds the current tenant to the `Stancl\Tenancy\Contracts\Tenant` contract +- binds the current domain to the `Stancl\Tenancy\Contracts\Domain` contract +- registers configured bootstrappers as singletons +- binds the configured unique ID generator to `UniqueIdentifierGenerator` +- registers package commands +- publishes config, routes, provider, and migrations +- loads package asset routes when `tenancy.routes` is enabled +- boots configured features through `tenancy()->bootstrapFeatures()` +- registers middleware groups: `clone`, `universal`, `tenant`, `central` + +## Core Runtime API + +`Stancl\Tenancy\Tenancy` exposes: + +- `initialize(Tenant|int|string $tenant): void` +- `run(Tenant $tenant, Closure $callback): mixed` +- `central(Closure $callback): mixed` +- `end(): void` +- `reinitialize(): void` +- `bootstrapFeatures(): void` +- `getBootstrappers(): array` +- `find(int|string $id, ?string $column = null, bool $withRelations = false)` + +Use these runtime methods instead of rolling your own context switch logic. + +## Default Models + +From `assets/config.php`: + +- `tenancy.models.tenant` => `Stancl\Tenancy\Database\Models\Tenant` +- `tenancy.models.domain` => `Stancl\Tenancy\Database\Models\Domain` +- `tenancy.models.impersonation_token` => `Stancl\Tenancy\Database\Models\ImpersonationToken` + +The default tenant key relation column is `tenant_id`. + +## Tenant ID Generators + +Supported generators exposed in config: + +- `UUIDGenerator` +- `ULIDGenerator` +- `UUIDv7Generator` +- `RandomHexGenerator` +- `RandomIntGenerator` +- `RandomStringGenerator` + +Set `tenancy.models.id_generator` to `null` only when the app intentionally uses auto-incrementing tenant IDs. + +## Identification Middleware + +Available middleware: + +- `InitializeTenancyByDomain` +- `InitializeTenancyBySubdomain` +- `InitializeTenancyByDomainOrSubdomain` +- `InitializeTenancyByPath` +- `InitializeTenancyByRequestData` +- `InitializeTenancyByOriginHeader` +- `PreventAccessFromUnwantedDomains` +- `CheckTenantForMaintenanceMode` +- `ScopeSessions` + +All package identification middleware inherit package failure handling through `IdentificationMiddleware::initializeTenancy()`. Failed identification throws a package exception unless an `onFail` callback is registered. + +## Resolvers + +Resolvers configured in `tenancy.identification.resolvers`: + +- `DomainTenantResolver` +- `PathTenantResolver` +- `RequestDataTenantResolver` + +Resolver config includes: + +- cache enablement +- cache TTL +- cache store +- path tenant parameter name +- route name prefix +- request header, cookie, and query parameter names +- custom tenant model lookup column + +## Default Bootstrappers + +Enabled by default: + +- `DatabaseTenancyBootstrapper` +- `CacheTenancyBootstrapper` +- `FilesystemTenancyBootstrapper` +- `QueueTenancyBootstrapper` +- `DatabaseSessionBootstrapper` + +Optional bootstrappers: + +- `CacheTagsBootstrapper` +- `DatabaseCacheBootstrapper` +- `RedisTenancyBootstrapper` +- `TenantConfigBootstrapper` +- `RootUrlBootstrapper` +- `UrlGeneratorBootstrapper` +- `MailConfigBootstrapper` +- `BroadcastingConfigBootstrapper` +- `BroadcastChannelPrefixBootstrapper` +- `Bootstrappers\Integrations\FortifyRouteBootstrapper` +- `Bootstrappers\Integrations\ScoutPrefixBootstrapper` +- `PostgresRLSBootstrapper` +- `PersistentQueueTenancyBootstrapper` + +## Bootstrapper Semantics + +- `DatabaseTenancyBootstrapper` + switches the active database connection into tenant context and reverts back to the central connection. +- `CacheTenancyBootstrapper` + scopes supported cache stores by prefix and can also scope cache-backed sessions. +- `CacheTagsBootstrapper` + is the older tag-based cache isolation approach and is less complete than prefix-based cache scoping. +- `DatabaseCacheBootstrapper` + scopes cache by moving database-backed cache stores onto the tenant connection instead of using prefixes. +- `FilesystemTenancyBootstrapper` + suffixes `storage_path()`, rewrites configured local disk roots, can scope file cache and file sessions, and can enable tenant-aware asset URLs. +- `QueueTenancyBootstrapper` + injects `tenant_id` into queued job payloads and re-initializes tenancy around queue job execution. +- `PersistentQueueTenancyBootstrapper` + is the queue bootstrapper variant for cases where worker processes should stay tenant-aware across jobs. +- `DatabaseSessionBootstrapper` + makes the database session driver use the tenant connection. +- `RedisTenancyBootstrapper` + sets Redis connection prefixes for configured direct Redis connections. +- `TenantConfigBootstrapper` + maps tenant attributes into arbitrary config keys during tenancy. +- `RootUrlBootstrapper` + overrides `app.url` and the URL generator root URL, primarily for CLI URL generation in tenant context. +- `UrlGeneratorBootstrapper` + swaps in `TenancyUrlGenerator` so route names and tenant parameters are generated correctly for path or query-string identification. +- `MailConfigBootstrapper` + maps tenant attributes into mail configuration at runtime. +- `BroadcastingConfigBootstrapper` + maps tenant-specific broadcaster credentials into broadcasting config and swaps in a tenancy-aware broadcast manager. +- `BroadcastChannelPrefixBootstrapper` + prefixes actual broadcast channel names with the tenant key for supported broadcasters. +- `Bootstrappers\Integrations\FortifyRouteBootstrapper` + rewrites Fortify redirect targets so tenant auth flows can land on tenant routes. +- `Bootstrappers\Integrations\ScoutPrefixBootstrapper` + sets `scout.prefix` to the tenant key. +- `PostgresRLSBootstrapper` + swaps the tenant connection to the configured PostgreSQL RLS user and session variable model. + +## Database Isolation Options + +The config supports: + +- separate tenant databases +- PostgreSQL schema isolation +- optional permission-controlled database managers +- PostgreSQL RLS + +Database manager mappings exist for: + +- `sqlite` +- `mysql` +- `mariadb` +- `pgsql` +- `sqlsrv` + +`tenancy.database.drop_tenant_databases_on_migrate_fresh` controls whether `migrate:fresh` also drops tenant databases through the package override. + +## Cache, Filesystem, Queue, And Session Scoping + +Important config sections: + +- `tenancy.cache` +- `tenancy.filesystem` +- `tenancy.redis` +- `tenancy.migration_parameters` +- `tenancy.seeder_parameters` + +Notable filesystem behavior: + +- local disks can have tenant-specific root overrides +- `Storage::disk()->url()` can be overridden with tenant-aware public names +- `storage_path()` can be suffixed per tenant +- file cache and file sessions can be scoped +- `asset()` tenancy can be enabled, but may affect packages that assume global assets + +## Routes + +`assets/routes.php` registers: + +- `/tenancy/assets/{path?}` named `stancl.tenancy.asset` +- `/{tenant}/tenancy/assets/{path?}` named `tenant.stancl.tenancy.asset` for path identification, behind the `tenant` middleware + +The package route mode enum is `Stancl\Tenancy\Enums\RouteMode`, with central as the default route mode in config. + +The service provider also registers empty middleware groups named: + +- `clone` +- `universal` +- `tenant` +- `central` + +These route modes and middleware groups are part of the package routing model and should be preferred over ad hoc tenant-versus-central route branching. + +## Optional Features + +Feature classes shipped in `src/Features`: + +- `CrossDomainRedirect` +- `DisallowSqliteAttach` +- `TelescopeTags` +- `TenantConfig` +- `UserImpersonation` +- `ViteBundler` + +Features bootstrap independently from tenant initialization and are intended to be enabled through `tenancy.features`. + +Feature behavior: + +- `CrossDomainRedirect` + adds a `RedirectResponse::domain(string $domain)` macro that swaps the redirect host without rebuilding the whole URL. +- `DisallowSqliteAttach` + blocks SQLite `ATTACH` usage by registering an authorizer on SQLite PDO connections, using a native authorizer on PHP 8.5+ and a loadable extension fallback on older runtimes. +- `TelescopeTags` + adds a `tenant:{tenantKey}` Telescope tag when tenancy is initialized. +- `TenantConfig` + maps tenant attributes into config values using event listeners. This feature is deprecated in favor of `TenantConfigBootstrapper`. +- `UserImpersonation` + adds a `tenancy()->impersonate()` macro, stores impersonation tokens in the configured impersonation token model, validates token TTL and tenant match, logs the user in with the configured guard, and exposes `isImpersonating()` plus `stopImpersonating()`. +- `ViteBundler` + configures Vite asset path generation to use `global_asset()` so asset URLs stay central rather than tenant-scoped. + +## Pending Tenants + +Pending tenant support is configured under `tenancy.pending`. + +Important behavior: + +- `include_in_queries` controls whether pending tenants are included in normal tenant queries. +- `count` controls the maintained size of the pending-tenant pool. +- the package ships dedicated commands for creating and clearing pending tenants. + +If a task touches pre-provisioned tenant pools, inspect the pending-tenant commands and model scopes before implementing custom provisioning logic. + +## Commands + +Commands registered by `TenancyServiceProvider`: + +- `tenancy:install` +- `tenants:up` +- `tenants:run` +- `tenants:down` +- `tenants:link` +- `tenants:seed` +- `tenant:tinker` +- `tenants:migrate` +- `tenants:rollback` +- `tenants:list` +- `tenants:dump` +- `tenants:migrate-fresh` +- `tenants:pending-clear` +- `tenants:pending-create` +- `tenants:purge-impersonation-tokens` +- `tenants:create-user-with-rls-policies` + +Prefer these commands over hand-written loops for tenant maintenance tasks. + +Command notes: + +- `tenancy:install` + publishes config, routes, service provider, and core migrations, then creates `database/migrations/tenant`. +- `tenants:migrate` + applies `tenancy.migration_parameters`, supports concurrent execution, and can continue with `--skip-failing`. +- `tenants:rollback` + rolls back tenant migrations. +- `tenants:migrate-fresh` + rebuilds tenant schema from scratch. +- `tenants:dump` + dumps a tenant schema and defaults the dump path from `tenancy.migration_parameters.--schema-path`. +- `tenants:seed` + uses `tenancy.seeder_parameters`. +- `tenants:run` + runs arbitrary artisan commands against tenant context. +- `tenant:tinker` + opens Tinker in a selected tenant context and supports searching by tenant key or domain. +- `tenants:link` + manages tenant storage symlinks used by tenant-aware public disk URLs. +- `tenants:down` / `tenants:up` + toggle tenant maintenance mode. +- `tenants:pending-create` / `tenants:pending-clear` + manage the pending-tenant pool. +- `tenants:purge-impersonation-tokens` + removes expired impersonation tokens. +- `tenants:rls` + creates the shared RLS user and row-level-security policies for tenant-related tables. + +## Related Subsystems + +The package also includes: + +- `src/Events/*` for tenancy lifecycle, tenant, domain, database, storage, and pending-tenant events +- `src/Listeners/*` for bootstrapping and reverting context +- `src/Jobs/*` for database and storage lifecycle jobs +- `src/ResourceSyncing/*` for central-to-tenant resource syncing +- `src/RLS/*` for PostgreSQL row-level security support +- `src/Actions/*` for route cloning and storage symlink helpers + +When a task touches one of these areas, inspect the relevant namespace before inventing a parallel abstraction in app code. From c94c0cd932ec0619ecb2020b59e07105697ced8d Mon Sep 17 00:00:00 2001 From: eramitgupta Date: Thu, 28 May 2026 15:37:40 +0530 Subject: [PATCH 2/5] Update tenant command name for clarity --- resources/boost/skills/tenancy/references/package.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/boost/skills/tenancy/references/package.md b/resources/boost/skills/tenancy/references/package.md index aa7466ec1..9b00b92ab 100644 --- a/resources/boost/skills/tenancy/references/package.md +++ b/resources/boost/skills/tenancy/references/package.md @@ -292,7 +292,7 @@ Commands registered by `TenancyServiceProvider`: - `tenants:pending-clear` - `tenants:pending-create` - `tenants:purge-impersonation-tokens` -- `tenants:create-user-with-rls-policies` +- `tenants:rls` Prefer these commands over hand-written loops for tenant maintenance tasks. From fde2bf0cf4f2d2c102b72e358ef510b67fe611d9 Mon Sep 17 00:00:00 2001 From: eramitgupta Date: Tue, 2 Jun 2026 12:33:02 +0530 Subject: [PATCH 3/5] Add detailed tenancy references and guidelines to boost resources --- resources/boost/guidelines/core.blade.php | 901 ++++++++++++++++++ resources/boost/skills/tenancy/SKILL.md | 25 +- .../tenancy/references/bootstrappers.md | 42 + .../tenancy/references/configuration.md | 34 + .../skills/tenancy/references/context-api.md | 45 + .../tenancy/references/database-tenancy.md | 36 + .../skills/tenancy/references/features.md | 30 + .../references/filesystem-cache-queue.md | 41 + .../tenancy/references/identification.md | 43 + .../tenancy/references/impersonation.md | 36 + .../skills/tenancy/references/installation.md | 73 ++ .../skills/tenancy/references/integrations.md | 44 + .../tenancy/references/lifecycle-jobs.md | 46 + .../tenancy/references/migrations-commands.md | 56 ++ .../tenancy/references/models-domains.md | 42 + .../skills/tenancy/references/package.md | 24 + .../tenancy/references/pending-tenants.md | 33 + .../tenancy/references/resource-syncing.md | 32 + .../boost/skills/tenancy/references/rls.md | 33 + .../tenancy/references/routing-assets.md | 49 + .../skills/tenancy/references/testing.md | 43 + 21 files changed, 1707 insertions(+), 1 deletion(-) create mode 100644 resources/boost/guidelines/core.blade.php create mode 100644 resources/boost/skills/tenancy/references/bootstrappers.md create mode 100644 resources/boost/skills/tenancy/references/configuration.md create mode 100644 resources/boost/skills/tenancy/references/context-api.md create mode 100644 resources/boost/skills/tenancy/references/database-tenancy.md create mode 100644 resources/boost/skills/tenancy/references/features.md create mode 100644 resources/boost/skills/tenancy/references/filesystem-cache-queue.md create mode 100644 resources/boost/skills/tenancy/references/identification.md create mode 100644 resources/boost/skills/tenancy/references/impersonation.md create mode 100644 resources/boost/skills/tenancy/references/installation.md create mode 100644 resources/boost/skills/tenancy/references/integrations.md create mode 100644 resources/boost/skills/tenancy/references/lifecycle-jobs.md create mode 100644 resources/boost/skills/tenancy/references/migrations-commands.md create mode 100644 resources/boost/skills/tenancy/references/models-domains.md create mode 100644 resources/boost/skills/tenancy/references/pending-tenants.md create mode 100644 resources/boost/skills/tenancy/references/resource-syncing.md create mode 100644 resources/boost/skills/tenancy/references/rls.md create mode 100644 resources/boost/skills/tenancy/references/routing-assets.md create mode 100644 resources/boost/skills/tenancy/references/testing.md diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php new file mode 100644 index 000000000..878494ba7 --- /dev/null +++ b/resources/boost/guidelines/core.blade.php @@ -0,0 +1,901 @@ + +=== core rules === + +# Stancl Tenancy Guidelines + +These guidelines are for Laravel applications using `stancl/tenancy`. They are based on this package's source, installer, config, stubs, routes, migrations, service provider, commands, bootstrappers, middleware, models, and tests. + +## Package Context + +- Composer package: `stancl/tenancy` +- Purpose: automatic multi-tenancy for Laravel applications +- Service provider: `Stancl\Tenancy\TenancyServiceProvider` +- Facades: `Tenancy`, `GlobalCache` +- Core singleton: `Stancl\Tenancy\Tenancy` +- Database manager singleton: `Stancl\Tenancy\Database\DatabaseManager` +- Current tenant contract binding: `Stancl\Tenancy\Contracts\Tenant` +- Current domain contract binding: `Stancl\Tenancy\Contracts\Domain` +- Default tenant model: `Stancl\Tenancy\Database\Models\Tenant` +- Default domain model: `Stancl\Tenancy\Database\Models\Domain` +- Default impersonation token model: `Stancl\Tenancy\Database\Models\ImpersonationToken` +- Default tenant key relation column: `tenant_id` +- Reserved tenant database connection name: `tenant` +- Default central connection config: `tenancy.database.central_connection`, usually `env('DB_CONNECTION', 'central')` + +## Source Files To Inspect + +Before changing tenancy behavior, inspect the relevant local package files. Do not guess package behavior from memory. + +- `README.md` +- `composer.json` +- `src/TenancyServiceProvider.php` +- `src/Tenancy.php` +- `src/helpers.php` +- `assets/config.php` +- `assets/routes.php` +- `assets/tenant_routes.stub.php` +- `assets/TenancyServiceProvider.stub.php` +- `assets/migrations/*` +- `assets/impersonation-migrations/*` +- `assets/resource-syncing-migrations/*` +- `src/Middleware/*` +- `src/Resolvers/*` +- `src/Bootstrappers/*` +- `src/Commands/*` +- `src/Database/Models/*` +- `src/Database/Concerns/*` +- `src/Database/TenantDatabaseManagers/*` +- `src/Features/*` +- `src/Jobs/*` +- `resources/boost/skills/tenancy/references/package.md` +- `tests/*` for expected behavior + +Use Laravel documentation for framework-level behavior such as service providers, vendor publishing, routes, middleware, migrations, queues, cache, filesystem, database, and testing. + +## Installation Steps + +Follow every installation step. Do not skip setup files or migrations. + +1. Install the package with Composer. + +```bash +composer require stancl/tenancy +``` + +2. Run the package installer. + +```bash +php artisan tenancy:install --no-interaction +``` + +3. Confirm the installer published the config. + +```text +config/tenancy.php +``` + +4. Confirm the installer published tenant routes. + +```text +routes/tenant.php +``` + +5. Confirm the installer published the application tenancy service provider. + +```text +app/Providers/TenancyServiceProvider.php +``` + +6. Confirm the installer published central migrations. + +```text +database/migrations/2019_09_15_000010_create_tenants_table.php +database/migrations/2019_09_15_000020_create_domains_table.php +``` + +7. Confirm the installer created the tenant migration directory. + +```text +database/migrations/tenant +``` + +8. Review and adjust `config/tenancy.php` before running migrations. Decide identification, bootstrappers, database isolation, central domains, route mode, and tenant migration parameters first. + +9. Run central migrations for the main application database. + +```bash +php artisan migrate +``` + +10. Add tenant-specific migrations under `database/migrations/tenant`. + +11. Create tenants using the configured tenant model and attach domains when using domain, subdomain, or domain-or-subdomain identification. + +12. Run tenant migrations after tenants exist. + +```bash +php artisan tenants:migrate +``` + +13. Seed tenant databases only when needed. + +```bash +php artisan tenants:seed +``` + +14. If tenant-aware local public storage URLs are enabled, create tenant symlinks. + +```bash +php artisan tenants:link +``` + +15. Test central routes and tenant routes separately before shipping. + +## Manual Publish Commands + +Prefer `tenancy:install`. Use manual publishing only when intentionally publishing a specific group. + +```bash +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=config +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=routes +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=providers +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=migrations +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=impersonation-migrations +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=resource-syncing-migrations +``` + +## Published Files + +The package publishes these files and directories: + +- `assets/config.php` to `config/tenancy.php` +- `assets/tenant_routes.stub.php` to `routes/tenant.php` +- `assets/TenancyServiceProvider.stub.php` to `app/Providers/TenancyServiceProvider.php` +- `assets/migrations/2019_09_15_000010_create_tenants_table.php` to central migrations +- `assets/migrations/2019_09_15_000020_create_domains_table.php` to central migrations +- `assets/impersonation-migrations/2020_05_15_000010_create_tenant_user_impersonation_tokens_table.php` when user impersonation is used +- `assets/resource-syncing-migrations/2020_05_11_000002_create_tenant_resources_table.php` when resource syncing is used +- `database/migrations/tenant` is created by `tenancy:install` for tenant migrations + +## Installer Behavior + +`php artisan tenancy:install` performs these package-defined steps: + +- Publishes config using tag `config` +- Publishes routes using tag `routes` +- Publishes provider using tag `providers` +- Publishes tenant and domain migrations using tag `migrations` +- Creates `database/migrations/tenant` +- Skips existing files and warns instead of overwriting them +- Shows an interactive GitHub support prompt unless `--no-interaction` is used + +Use `--no-interaction` in automation and agent workflows. + +## Core Config Checklist + +Always review these `config/tenancy.php` sections before implementation: + +- `models.tenant` +- `models.domain` +- `models.impersonation_token` +- `models.tenant_key_column` +- `models.id_generator` +- `identification.central_domains` +- `identification.default_middleware` +- `identification.middleware` +- `identification.domain_identification_middleware` +- `identification.path_identification_middleware` +- `identification.resolvers` +- `bootstrappers` +- `database.central_connection` +- `database.template_tenant_connection` +- `database.tenant_host_connection_name` +- `database.prefix` +- `database.suffix` +- `database.managers` +- `database.drop_tenant_databases_on_migrate_fresh` +- `rls.manager` +- `rls.user.username` +- `rls.user.password` +- `rls.session_variable_name` +- `cache.prefix` +- `cache.stores` +- `cache.scope_sessions` +- `cache.tag_base` +- `filesystem.suffix_base` +- `filesystem.disks` +- `filesystem.root_override` +- `filesystem.url_override` +- `filesystem.scope_cache` +- `filesystem.scope_sessions` +- `filesystem.suffix_storage_path` +- `filesystem.asset_helper_override` +- `redis.prefix` +- `redis.prefixed_connections` +- `features` +- `routes` +- `default_route_mode` +- `pending.include_in_queries` +- `pending.count` +- `migration_parameters` +- `seeder_parameters` + +## Tenant Identification + +Decide the identification strategy before writing routes, middleware, model logic, URLs, tests, or tenant creation flows. + +Built-in identification middleware: + +- `Stancl\Tenancy\Middleware\InitializeTenancyByDomain` +- `Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain` +- `Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain` +- `Stancl\Tenancy\Middleware\InitializeTenancyByPath` +- `Stancl\Tenancy\Middleware\InitializeTenancyByRequestData` +- `Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader` + +Related middleware: + +- `Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains` +- `Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode` +- `Stancl\Tenancy\Middleware\ScopeSessions` + +Rules: + +- Configure `identification.central_domains` correctly for domain and subdomain identification. +- Use `PreventAccessFromUnwantedDomains` only with domain-oriented identification middleware listed in `identification.domain_identification_middleware`. +- For path identification, check `PathTenantResolver::tenantParameterName()` and `identification.resolvers[PathTenantResolver::class]` before changing route parameters. +- For request data identification, configure header, cookie, and query parameter names in `RequestDataTenantResolver` config. +- For origin header identification, verify trusted frontend/origin behavior and failure handling. +- Failed identification throws package exceptions unless an `onFail` callback is registered. +- If custom middleware is added, also add it to the matching config array when the package needs to recognize its category. + +## Tenant Resolvers + +Built-in resolvers: + +- `Stancl\Tenancy\Resolvers\DomainTenantResolver` +- `Stancl\Tenancy\Resolvers\PathTenantResolver` +- `Stancl\Tenancy\Resolvers\RequestDataTenantResolver` + +Resolver guidance: + +- Enable resolver cache only deliberately and invalidate it when domain or tenant lookup data changes. +- Use `cache_ttl` and `cache_store` when resolver caching is enabled. +- For path resolver custom binding fields, configure `allowed_extra_model_columns`. +- For request data resolver, set unused identification channels to `null`. +- Use `tenant_model_column` when lookup should use a custom tenant column instead of the tenant key. + +## Route Setup + +The published `routes/tenant.php` stub uses: + +```php +Route::middleware([ + 'web', + Middleware\InitializeTenancyByDomain::class, + Middleware\PreventAccessFromUnwantedDomains::class, + Middleware\ScopeSessions::class, +])->group(function () { + // Tenant routes... +}); +``` + +The published application `TenancyServiceProvider` loads tenant routes like this: + +- Waits until the app is booted +- Checks `base_path('routes/tenant.php')` +- Applies middleware group `tenant` +- Uses `static::$controllerNamespace` +- Can clone routes as tenant routes through `CloneRoutesAsTenant` + +The package service provider registers empty route middleware groups: + +- `clone` +- `universal` +- `tenant` +- `central` + +Route mode rules: + +- `tenancy.default_route_mode` defaults to `RouteMode::CENTRAL`. +- Override default route mode by applying `central`, `tenant`, or `universal` middleware. +- Keep central and tenant routes explicit. +- Use `routes/tenant.php` for tenant application routes when the published stub is used. +- Use `universal` only for routes that intentionally work in both contexts. +- Do not use ad hoc request host checks when package middleware/route modes cover the behavior. + +## Tenant Asset Routes + +When `tenancy.routes` is true, the package loads `assets/routes.php` and registers: + +- `/tenancy/assets/{path?}` named `stancl.tenancy.asset` +- `/{tenant}/tenancy/assets/{path?}` named `tenant.stancl.tenancy.asset` for path identification, behind `tenant` middleware + +Guidance: + +- Disable `tenancy.routes` only if using external storage or a custom asset controller. +- If `filesystem.url_override` is used for local disks, run `php artisan tenants:link`. +- For global assets, use global asset helpers when `filesystem.asset_helper_override` is enabled. +- Prefer explicit `tenant_asset()` calls for tenant-specific assets when global packages call `asset()` internally. + +## Tenant Context API + +Use package APIs for context switching. Do not manually mutate Laravel globals. + +```php +tenancy()->initialize($tenant); + +tenancy()->end(); + +tenancy()->reinitialize(); + +$tenant->run(function () { + // Code runs in tenant context. +}); + +tenancy()->central(function () { + // Code runs in central context and then safely reverts. +}); +``` + +Important runtime behavior: + +- `initialize()` accepts a tenant model, tenant ID, or tenant key string. +- `initialize()` ends the previous tenant context before switching to a different tenant. +- `run()` is atomic and reverts to the previous tenant or central context in `finally`. +- `central()` is atomic and restores the previous tenant context when finished. +- `reinitialize()` is useful when tenant attributes used by bootstrappers changed during a request. +- `bootstrapFeatures()` is idempotent for features already bootstrapped, but feature bootstrapping is irreversible. +- `tenancy()->find($id, $column = null, $withRelations = false)` uses the configured tenant model. +- `Tenancy::tenantKeyColumn()` reads `tenancy.models.tenant_key_column` and defaults to `tenant_id`. + +## Helpers And Facades + +Use package helpers and facades where appropriate: + +- `tenancy()` for the `Tenancy` singleton +- `tenant()` for current tenant access and tenant attribute lookup +- `central()` for central-context execution +- `globalCache()` or `GlobalCache` for cache that should remain central +- `global_asset()` when asset helper tenancy is enabled and the asset should remain global +- `tenant_asset()` for tenant-specific local assets +- `Tenancy` facade for the tenancy manager +- `GlobalCache` facade for central cache access + +## Bootstrappers + +Default bootstrappers in `assets/config.php`: + +- `DatabaseTenancyBootstrapper` +- `CacheTenancyBootstrapper` +- `FilesystemTenancyBootstrapper` +- `QueueTenancyBootstrapper` +- `DatabaseSessionBootstrapper` + +Optional bootstrappers: + +- `CacheTagsBootstrapper` +- `DatabaseCacheBootstrapper` +- `RedisTenancyBootstrapper` +- `TenantConfigBootstrapper` +- `RootUrlBootstrapper` +- `UrlGeneratorBootstrapper` +- `MailConfigBootstrapper` +- `BroadcastingConfigBootstrapper` +- `BroadcastChannelPrefixBootstrapper` +- `Bootstrappers\Integrations\FortifyRouteBootstrapper` +- `Bootstrappers\Integrations\ScoutPrefixBootstrapper` +- `PostgresRLSBootstrapper` +- `PersistentQueueTenancyBootstrapper` + +Bootstrapper rules: + +- Configure bootstrappers before writing application workarounds. +- Do not manually change DB connections, cache prefixes, filesystem roots, queue payloads, Redis prefixes, URL roots, or mail/broadcasting config when a bootstrapper owns it. +- `DatabaseCacheBootstrapper` must run after `DatabaseTenancyBootstrapper`. +- `RedisTenancyBootstrapper` needs phpredis and is for direct Redis calls, not normal cache-only Redis usage. +- `TenantConfigBootstrapper` should be preferred over the deprecated `TenantConfig` feature. +- `RootUrlBootstrapper` affects CLI URL generation in tenant context. +- `UrlGeneratorBootstrapper` is important for path/query-string route generation. +- If tenant state appears partially applied, inspect `tenancy()->getBootstrappers()` and `tenancy.bootstrappers`. + +## Database Tenancy + +Database config supports: + +- separate tenant databases +- PostgreSQL schema isolation +- permission-controlled tenant database users +- SQLite tenant database management +- MySQL/MariaDB tenant database management +- PostgreSQL tenant database management +- SQL Server tenant database management +- PostgreSQL RLS for single-database tenancy + +Rules: + +- `tenant` is a reserved dynamic connection name; do not use it as the template tenant connection name. +- Use `database.template_tenant_connection` for the tenant connection template. +- Use `database.tenant_host_connection_name` for temporary creation/deletion connection behavior. +- Tenant database names are generated as `prefix + tenant_id + suffix`. +- Use permission-controlled managers only when tenant-specific DB users are required. +- For PostgreSQL schemas, swap the pgsql manager to a schema manager instead of a database manager. +- `database.drop_tenant_databases_on_migrate_fresh` controls package behavior for `migrate:fresh` through the package override. + +## Central Migrations + +The base central migrations create: + +- `tenants` table with string primary `id`, timestamps, and nullable JSON `data` +- `domains` table with integer `id`, unique `domain`, tenant key column, timestamps, and foreign key to `tenants.id` + +Guidance: + +- Add custom tenant columns to the `tenants` migration before running it. +- If using auto-increment tenant IDs, set `models.id_generator` to `null` and update the tenants migration primary key accordingly. +- Keep domain values unique and lowercase behavior in mind; the default domain model converts domains to lowercase. +- The domains migration uses `Tenancy::tenantKeyColumn()` for the tenant foreign key. + +## Tenant Migrations And Seeders + +Tenant migrations are configured by `tenancy.migration_parameters`: + +- `--force` defaults to true +- `--path` defaults to `database/migrations/tenant` +- `--schema-path` defaults to `database/schema/tenant-schema.dump` +- `--realpath` defaults to true + +Tenant seeders are configured by `tenancy.seeder_parameters`: + +- `--class` defaults to `Database\Seeders\DatabaseSeeder` + +Rules: + +- Put tenant database migrations in `database/migrations/tenant` by default. +- Use `php artisan tenants:migrate` for tenant migrations. +- Use `php artisan tenants:rollback` for tenant rollback. +- Use `php artisan tenants:migrate-fresh` for tenant migrate fresh behavior. +- Use `php artisan tenants:seed` for tenant seeders. +- Do not run normal Laravel migrations expecting them to apply to tenant databases. +- Review `migration_parameters` and `seeder_parameters` before changing command calls. +- Use `--tenants=*` options when only specific tenants should be affected. +- Use `--skip-failing` deliberately when tenant migration failures should not stop the whole command. +- Use `--processes` only after confirming database and application code are safe for concurrent tenant operations. + +## Tenant Models + +Default tenant model traits include: + +- `VirtualColumn` +- `CentralConnection` +- `GeneratesIds` +- `HasInternalKeys` +- `TenantRun` +- `InitializationHelpers` +- `InvalidatesResolverCache` + +Default tenant model behavior: + +- table: `tenants` +- primary key: `id` +- guarded: empty array +- dispatches creating, created, saving, saved, updating, updated, deleting, deleted tenant events +- `Tenant::current()` returns current tenant +- `Tenant::currentOrFail()` throws if tenancy is not initialized +- tenant collection class: `Stancl\Tenancy\Database\TenantCollection` + +Guidance: + +- Use the configured tenant model from `config('tenancy.models.tenant')`. +- If replacing the tenant model, implement `Stancl\Tenancy\Contracts\Tenant`. +- Preserve package traits unless there is a specific tested reason to replace them. +- Use tenant model events and the published application service provider's pipelines for provisioning. + +## Domain Models + +Default domain model traits include: + +- `CentralConnection` +- `EnsuresDomainIsNotOccupied` +- `ConvertsDomainsToLowercase` +- `InvalidatesTenantsResolverCache` + +Default domain model behavior: + +- guarded: empty array +- belongs to the configured tenant model using `Tenancy::tenantKeyColumn()` +- dispatches creating, created, saving, saved, updating, updated, deleting, deleted domain events + +Guidance: + +- Use domains for domain/subdomain/domain-or-subdomain identification. +- Do not bypass domain uniqueness checks. +- Ensure domain cache invalidates when domain or tenant lookup data changes. + +## Single-Database Tenancy + +For single-database tenancy, use package traits and scopes instead of hand-written tenant filters. + +Relevant concerns: + +- `BelongsToTenant` +- `FillsCurrentTenant` +- `TenantConnection` +- `CentralConnection` +- `TenantScope` +- `HasScopedValidationRules` +- `RLSModel` when PostgreSQL RLS is used + +Rules: + +- Apply tenant scoping consistently to tenant-owned models. +- Make tenant-owned models fill the current tenant key automatically where appropriate. +- Test central resources and tenant resources separately. +- For PostgreSQL RLS, configure `rls.user`, `rls.manager`, and `rls.session_variable_name`, then use package RLS commands/policies. + +## Resource Syncing + +Resource syncing assets include: + +- migration: `tenant_resources` +- events in `Stancl\Tenancy\ResourceSyncing\Events` +- listeners in `Stancl\Tenancy\ResourceSyncing\Listeners` +- traits/classes such as `ResourceSyncing`, `SyncMaster`, `Syncable`, `TenantPivot`, `TenantMorphPivot` + +Guidance: + +- Publish `resource-syncing-migrations` before using resource syncing. +- Keep central resource and tenant resource lifecycles explicit. +- Use package events/listeners from the published provider instead of custom sync loops. +- If soft-deleted synced resources are needed, configure the listener query scope in the application `TenancyServiceProvider` as shown in the stub. + +## User Impersonation + +User impersonation uses: + +- feature: `Stancl\Tenancy\Features\UserImpersonation` +- model: `Stancl\Tenancy\Database\Models\ImpersonationToken` +- migration: `tenant_user_impersonation_tokens` +- command: `tenants:purge-impersonation-tokens` + +Guidance: + +- Publish `impersonation-migrations` before enabling impersonation. +- Enable the `UserImpersonation` feature in `tenancy.features`. +- Run central migrations after publishing the impersonation migration. +- Purge expired tokens with `php artisan tenants:purge-impersonation-tokens`. +- Verify guard, redirect URL, remember flag, tenant match, and token TTL in tests. + +## Pending Tenants + +Pending tenant config: + +- `pending.include_in_queries` +- `pending.count`, defaulting to `TENANCY_PENDING_COUNT` or 5 + +Commands: + +- `php artisan tenants:pending-create` +- `php artisan tenants:pending-create --count=10` +- `php artisan tenants:pending-clear` +- `php artisan tenants:pending-clear --older-than-days=7` +- `php artisan tenants:pending-clear --older-than-hours=12` + +Rules: + +- If `pending.include_in_queries` is false, pending tenants are excluded from tenant queries and tenant commands. +- Use `withPending()`, `withoutPending()`, and `onlyPending()` intentionally when querying pending tenants. +- Do not assume pending tenants are included in migrations or seeds when config excludes them. + +## Tenant Lifecycle And Jobs + +The published application `TenancyServiceProvider` wires lifecycle events to job pipelines. + +Default `TenantCreated` pipeline: + +- `CreateDatabase` +- `MigrateDatabase` +- optional `SeedDatabase` +- optional `CreateStorageSymlinks` +- custom provisioning jobs + +Default deleting/deleted tenant pipelines: + +- `DeleteDomains` during `DeletingTenant` +- optional `DeleteTenantStorage` +- optional `RemoveStorageSymlinks` +- `DeleteDatabase` during `TenantDeleted` +- optional resource-syncing cleanup + +Rules: + +- Add tenant provisioning logic to the event pipeline rather than scattering it through controllers. +- Decide whether pipelines should be queued using `shouldBeQueued()`. +- Keep database creation, migration, seeding, storage, and domain deletion order explicit. +- Test tenant creation and deletion side effects. + +## Filesystem, Storage, And Assets + +Filesystem config controls: + +- tenant storage suffix base +- scoped disks +- local root overrides +- URL overrides +- file cache scoping +- file session scoping +- `storage_path()` suffixing +- `asset()` helper override + +Rules: + +- Keep `suffix_storage_path` enabled for local disk tenancy unless using external storage like S3 and the app is tested without it. +- Add local disks to both `filesystem.disks` and `filesystem.root_override` when root override is needed. +- Use `tenants:link` when `filesystem.url_override` maps local public disks. +- Use `tenants:link --remove` when removing tenant symlinks. +- Use `tenants:link --relative` only when relative symlinks are required by deployment. +- Use `tenants:link --force` when recreating existing symlinks deliberately. +- Be careful with `asset_helper_override`; packages that call `asset()` may unexpectedly become tenant-aware. + +## Cache, Global Cache, Sessions, And Redis + +Cache rules: + +- `CacheTenancyBootstrapper` scopes cache by changing `cache.prefix`. +- `CacheTagsBootstrapper` scopes using tags and is an alternative pattern. +- `DatabaseCacheBootstrapper` scopes database cache by tenant DB connection and must run after database tenancy. +- Use `GlobalCache`/`globalCache()` for cache that must remain central. +- If session driver is cache-based, `cache.scope_sessions` may add the session store to prefixed stores. + +Filesystem session rules: + +- `filesystem.scope_sessions` scopes file sessions under tenant storage. +- Use `ScopeSessions` middleware on tenant routes when session scoping is required. + +Redis rules: + +- `RedisTenancyBootstrapper` is for direct Redis facade/injected Redis usage. +- phpredis is required for Redis tenancy. +- Redis cache alone usually does not need `RedisTenancyBootstrapper`; cache scoping covers cache usage. + +## Queue Behavior + +Queue bootstrappers: + +- `QueueTenancyBootstrapper` +- `PersistentQueueTenancyBootstrapper` + +Rules: + +- Use the queue bootstrapper for tenant-aware queued jobs. +- Do not manually inject tenant IDs into every job if the package bootstrapper already handles payload/context. +- Test queued jobs from central context and tenant context. +- Use the persistent queue bootstrapper only when worker processes intentionally stay tenant-aware across jobs. + +## URL, Routes, Mail, Broadcasting, Fortify, Scout + +Use optional bootstrappers for integration-specific runtime config: + +- `RootUrlBootstrapper` for tenant root URL in CLI/context URL generation +- `UrlGeneratorBootstrapper` for tenant-aware route generation and tenant parameters +- `MailConfigBootstrapper` for tenant mail configuration +- `BroadcastingConfigBootstrapper` for tenant broadcaster credentials +- `BroadcastChannelPrefixBootstrapper` for tenant-prefixed broadcast channel names +- `FortifyRouteBootstrapper` for tenant Fortify route/redirect behavior +- `ScoutPrefixBootstrapper` for tenant-specific Scout prefixes + +Rules: + +- Configure these bootstrappers instead of writing custom service-provider mutations. +- Use the `overrideUrlInTenantContext()` hook in the published application provider for CLI root URL customization. +- For Livewire v3, follow the provider stub pattern to make the Livewire update route universal when needed. + +## Optional Features + +Features are bootstrapped independently from tenant initialization and are enabled via `tenancy.features`. + +Available features: + +- `Stancl\Tenancy\Features\UserImpersonation` +- `Stancl\Tenancy\Features\TelescopeTags` +- `Stancl\Tenancy\Features\CrossDomainRedirect` +- `Stancl\Tenancy\Features\ViteBundler` +- `Stancl\Tenancy\Features\DisallowSqliteAttach` +- `Stancl\Tenancy\Features\TenantConfig` + +Rules: + +- Inspect the feature class before assuming it affects tenant initialization. +- Prefer `TenantConfigBootstrapper` over deprecated `TenantConfig` feature for mapping tenant attributes into config. +- `DisallowSqliteAttach` protects SQLite use by blocking `ATTACH`; verify PHP/version-specific behavior in tests. +- `CrossDomainRedirect` adds redirect domain behavior. +- `TelescopeTags` adds tenant tags when tenancy is initialized. +- `ViteBundler` affects tenant-aware bundling behavior. + +## Artisan Commands + +Use package commands instead of ad hoc loops. + +Installation: + +```bash +php artisan tenancy:install --no-interaction +``` + +Tenant migrations and seeds: + +```bash +php artisan tenants:migrate +php artisan tenants:migrate --tenants=tenant-id +php artisan tenants:migrate --skip-failing +php artisan tenants:rollback +php artisan tenants:migrate-fresh +php artisan tenants:seed +``` + +Run commands in tenant context: + +```bash +php artisan tenants:run cache:clear +php artisan tenants:run "your:command" --tenants=tenant-id +php artisan tenant:tinker +``` + +Tenant maintenance: + +```bash +php artisan tenants:down +php artisan tenants:down --redirect=/maintenance --retry=60 --refresh=60 --secret=secret --status=503 +php artisan tenants:up +``` + +Tenant storage symlinks: + +```bash +php artisan tenants:link +php artisan tenants:link --tenants=tenant-id +php artisan tenants:link --relative +php artisan tenants:link --force +php artisan tenants:link --remove +``` + +Pending tenants: + +```bash +php artisan tenants:pending-create +php artisan tenants:pending-create --count=10 +php artisan tenants:pending-clear +php artisan tenants:pending-clear --older-than-days=7 +php artisan tenants:pending-clear --older-than-hours=12 +``` + +Other package commands: + +```bash +php artisan tenants:list +php artisan tenant:dump +php artisan tenants:purge-impersonation-tokens +php artisan tenants:rls +php artisan tenants:rls --force +``` + +Command rules: + +- Use `php artisan list` or `php artisan help ` to confirm available options in the installed app. +- Use `--tenants=*` when affecting only selected tenants. +- Use tenant commands for tenant databases, not central Laravel migration commands. +- Use `tenants:run` for existing Artisan commands that need tenant context. + +## Maintenance Mode + +Maintenance commands: + +- `tenants:down` +- `tenants:up` + +Related middleware: + +- `CheckTenantForMaintenanceMode` + +Rules: + +- Use tenant maintenance mode when only tenant apps should be unavailable. +- Test bypass secret, redirect, retry, refresh, and status code behavior when configured. +- Do not confuse tenant maintenance with Laravel global maintenance mode. + +## PostgreSQL RLS + +RLS config and classes: + +- `rls.manager` +- `rls.user.username` +- `rls.user.password` +- `rls.session_variable_name` +- `PostgresRLSBootstrapper` +- `RLSModel` +- `TableRLSManager` +- `TraitRLSManager` +- `tenants:rls` + +Rules: + +- Use PostgreSQL and single-database tenancy for RLS. +- Set a namespaced session variable name such as `my.current_tenant`. +- Configure one tenant database user used for all tenants, not one user per tenant. +- Run `php artisan tenants:rls` to create policies/user. +- Use `--force` only when policies should be recreated even if they already exist. +- Test policy coverage on every tenant-owned table. + +## Testing Guidelines + +Every tenancy behavior change must be tested programmatically. + +Test at minimum: + +- installation artifacts exist when testing installer behavior +- central routes remain central +- tenant routes initialize tenancy +- wrong central/tenant access is rejected +- domain, subdomain, path, request-data, or origin-header identification resolves the expected tenant +- identification failure throws or handles the expected package exception +- current tenant is available through `tenant()`, facade, and contract binding when expected +- database connection switches and reverts +- cache keys are scoped or global as intended +- filesystem roots, URLs, and storage paths are scoped as intended +- queues reinitialize tenant context around jobs +- sessions are scoped when configured +- URL generation uses tenant route names and parameters correctly +- tenant migrations, rollbacks, and seeds affect the expected tenants +- tenant creation pipelines create DBs, migrate DBs, seed DBs, create storage, and attach domains as configured +- tenant deletion pipelines delete domains, database, storage, symlinks, and resource mappings as configured +- optional features behave only when enabled +- central context is restored after `run()` and `central()` callbacks + +Use existing package tests as examples: + +- `tests/AutomaticModeTest.php` +- `tests/ManualModeTest.php` +- `tests/RouteMiddlewareTest.php` +- `tests/PathIdentificationTest.php` +- `tests/RequestDataIdentificationTest.php` +- `tests/OriginHeaderIdentificationTest.php` +- `tests/TenantAssetTest.php` +- `tests/CommandsTest.php` +- `tests/QueueTest.php` +- `tests/SingleDatabaseTenancyTest.php` +- `tests/RLS/*` +- `tests/ResourceSyncingTest.php` +- `tests/TenantUserImpersonationTest.php` + +## Implementation Rules + +- Decide identification first. +- Decide database isolation before creating application models. +- Decide bootstrappers before writing workaround code. +- Keep central and tenant routes explicit. +- Use package commands for tenant-aware operations. +- Use package context APIs instead of manual context mutation. +- Use package models, contracts, traits, events, and jobs before adding custom abstractions. +- Keep provisioning in the application `TenancyServiceProvider` event pipelines. +- Review config before changing application code. +- Test both central and tenant behavior for every change. + +## Common Pitfalls + +- Running normal `php artisan migrate` and expecting tenant DBs to migrate +- Forgetting to run `php artisan tenants:migrate` after tenants exist +- Skipping `routes/tenant.php` or the application `TenancyServiceProvider` +- Using the reserved connection name `tenant` as a template connection +- Mixing central and tenant routes without route modes or middleware +- Using `PreventAccessFromUnwantedDomains` with unsupported non-domain identification +- Forgetting `central_domains` for domain/subdomain apps +- Manually changing DB/cache/filesystem/queue/session/url state instead of using bootstrappers +- Enabling `asset_helper_override` without checking third-party packages that call `asset()` +- Enabling resolver caching without invalidation coverage +- Forgetting to publish impersonation or resource-syncing migrations before enabling those features +- Assuming pending tenants are included when `pending.include_in_queries` excludes them +- Using auto-increment tenant IDs without considering enumeration risk +- Adding tenant-owned models in single-database tenancy without tenant scoping +- Not verifying central context after tenant context work + + diff --git a/resources/boost/skills/tenancy/SKILL.md b/resources/boost/skills/tenancy/SKILL.md index 673795d4b..b60bcdbb7 100644 --- a/resources/boost/skills/tenancy/SKILL.md +++ b/resources/boost/skills/tenancy/SKILL.md @@ -3,7 +3,7 @@ name: tenancy description: "Activate when the user is building or debugging multi-tenant Laravel behavior with stancl/tenancy. Use for tenancy:install, tenant identification middleware, central and tenant routes, tenant model and domain model setup, multi-database or single-database tenancy, tenant-aware bootstrappers for database/cache/filesystem/queue/session/Redis, tenant context switching with tenancy()->initialize() or tenant()->run(), tenant migrations and seeders, tenant asset routes, pending tenants, resource syncing, user impersonation, RLS, Vite bundling, or testing tenant-aware behavior." license: MIT metadata: - author: laravel + author: Samuel Ć tancl --- # Tenancy For Laravel @@ -28,6 +28,29 @@ Use `search-docs` first when it is available for Laravel integration patterns. F Load `references/package.md` when the task needs package-specific detail beyond the core workflow in this file. +## Feature References + +Load focused references when the task matches a specific package area: + +- `references/installation.md` for install, publishing, and setup checks +- `references/configuration.md` for `config/tenancy.php` sections +- `references/identification.md` for middleware and resolvers +- `references/routing-assets.md` for tenant routes, route modes, cloned routes, and asset routes +- `references/context-api.md` for `tenancy()`, `tenant()`, `run()`, and `central()` behavior +- `references/bootstrappers.md` for tenant-aware Laravel service scoping +- `references/database-tenancy.md` for database isolation and tenant database managers +- `references/migrations-commands.md` for tenant Artisan commands +- `references/models-domains.md` for tenant/domain models and single-database traits +- `references/filesystem-cache-queue.md` for storage, cache, sessions, Redis, and queues +- `references/lifecycle-jobs.md` for events, provisioning, and cleanup pipelines +- `references/resource-syncing.md` for synced central and tenant resources +- `references/impersonation.md` for tenant user impersonation +- `references/pending-tenants.md` for pending tenant pools +- `references/rls.md` for PostgreSQL row-level security +- `references/features.md` for optional package features +- `references/integrations.md` for URL, mail, broadcasting, Fortify, Scout, Livewire, Telescope, and Vite +- `references/testing.md` for test coverage guidance + ## Package Surface The package auto-discovers: diff --git a/resources/boost/skills/tenancy/references/bootstrappers.md b/resources/boost/skills/tenancy/references/bootstrappers.md new file mode 100644 index 000000000..0333b8227 --- /dev/null +++ b/resources/boost/skills/tenancy/references/bootstrappers.md @@ -0,0 +1,42 @@ +# Bootstrappers Reference + +Use this when tenant context should affect Laravel services. + +## Source Files + +- `src/Bootstrappers/*` +- `assets/config.php` + +## Defaults + +- `DatabaseTenancyBootstrapper` +- `CacheTenancyBootstrapper` +- `FilesystemTenancyBootstrapper` +- `QueueTenancyBootstrapper` +- `DatabaseSessionBootstrapper` + +## Optional Bootstrappers + +- `CacheTagsBootstrapper` +- `DatabaseCacheBootstrapper` +- `RedisTenancyBootstrapper` +- `TenantConfigBootstrapper` +- `RootUrlBootstrapper` +- `UrlGeneratorBootstrapper` +- `MailConfigBootstrapper` +- `BroadcastingConfigBootstrapper` +- `BroadcastChannelPrefixBootstrapper` +- `FortifyRouteBootstrapper` +- `ScoutPrefixBootstrapper` +- `PostgresRLSBootstrapper` +- `PersistentQueueTenancyBootstrapper` + +## Rules + +- Configure bootstrappers before writing app-level workarounds. +- `DatabaseCacheBootstrapper` must run after `DatabaseTenancyBootstrapper`. +- `RedisTenancyBootstrapper` needs phpredis and is for direct Redis calls. +- Prefer `TenantConfigBootstrapper` over deprecated `TenantConfig` feature. +- Use `RootUrlBootstrapper` for CLI URL root behavior. +- Use `UrlGeneratorBootstrapper` for tenant-aware route generation. +- Inspect `tenancy()->getBootstrappers()` when context looks partially applied. diff --git a/resources/boost/skills/tenancy/references/configuration.md b/resources/boost/skills/tenancy/references/configuration.md new file mode 100644 index 000000000..99deb1aa0 --- /dev/null +++ b/resources/boost/skills/tenancy/references/configuration.md @@ -0,0 +1,34 @@ +# Configuration Reference + +Use this when changing `config/tenancy.php`. + +## Source Files + +- `assets/config.php` +- `src/TenancyServiceProvider.php` + +## Must-Review Sections + +- `models`: tenant, domain, impersonation token, tenant key column, ID generator. +- `identification`: central domains, default middleware, resolver settings. +- `bootstrappers`: runtime Laravel feature scoping. +- `database`: central connection, template tenant connection, DB managers, prefixes. +- `rls`: PostgreSQL RLS manager, user, and session variable. +- `cache`: prefix, stores, session scoping, tag base. +- `filesystem`: disks, root overrides, URL overrides, storage suffixing, asset override. +- `redis`: direct Redis connection prefixing. +- `features`: optional package features. +- `routes`: package asset route registration toggle. +- `default_route_mode`: central, tenant, or universal default route behavior. +- `pending`: pending tenant query inclusion and pool count. +- `migration_parameters`: tenant migration defaults. +- `seeder_parameters`: tenant seeder defaults. + +## Rules + +- Decide identification, database isolation, and bootstrappers before writing app code. +- Never use `tenant` as the template tenant connection name; it is reserved by the package. +- Set `models.id_generator` to `null` only when using auto-increment tenant IDs intentionally. +- Keep resolver caching disabled until invalidation behavior is tested. +- Keep `database.drop_tenant_databases_on_migrate_fresh` false unless local/dev destructive behavior is intended. +- Enable `filesystem.asset_helper_override` only after checking third-party package asset calls. diff --git a/resources/boost/skills/tenancy/references/context-api.md b/resources/boost/skills/tenancy/references/context-api.md new file mode 100644 index 000000000..d07bcc2fb --- /dev/null +++ b/resources/boost/skills/tenancy/references/context-api.md @@ -0,0 +1,45 @@ +# Context API Reference + +Use this when switching between tenant and central contexts. + +## Source Files + +- `src/Tenancy.php` +- `src/helpers.php` +- `src/Database/Concerns/TenantRun.php` +- `src/Database/Concerns/InitializationHelpers.php` + +## Core API + +```php +tenancy()->initialize($tenant); +tenancy()->end(); +tenancy()->reinitialize(); +tenancy()->central(fn () => null); +$tenant->run(fn () => null); +``` + +## Behavior + +- `initialize()` accepts a tenant model, ID, or string key. +- Switching tenants ends the previous context first. +- `run()` restores the previous tenant or central context in `finally`. +- `central()` temporarily ends tenancy and restores prior tenant context. +- `reinitialize()` re-runs bootstrappers for the current tenant. +- `bootstrapFeatures()` is idempotent per feature, but feature bootstrapping is irreversible. +- `find()` resolves tenants through the configured tenant model. + +## Helpers + +- `tenancy()` returns the tenancy singleton. +- `tenant()` returns current tenant or tenant attribute. +- `central()` executes a callback in central context. +- `globalCache()` resolves central cache. +- `tenant_asset()` returns tenant asset URLs. +- `global_asset()` returns global asset URLs. + +## Rules + +- Do not manually mutate DB connections, cache prefixes, filesystem roots, queue payloads, sessions, or URL roots. +- Use atomic `run()` and `central()` when context must be restored safely. +- Test context restoration after exceptions. diff --git a/resources/boost/skills/tenancy/references/database-tenancy.md b/resources/boost/skills/tenancy/references/database-tenancy.md new file mode 100644 index 000000000..13b3c680f --- /dev/null +++ b/resources/boost/skills/tenancy/references/database-tenancy.md @@ -0,0 +1,36 @@ +# Database Tenancy Reference + +Use this when changing tenant database isolation or managers. + +## Source Files + +- `src/Database/DatabaseManager.php` +- `src/Database/DatabaseConfig.php` +- `src/Database/TenantDatabaseManagers/*` +- `src/Bootstrappers/DatabaseTenancyBootstrapper.php` +- `assets/config.php` + +## Supported Isolation + +- Separate tenant databases. +- PostgreSQL schema isolation. +- Permission-controlled tenant database users. +- PostgreSQL RLS for single-database tenancy. + +## Managers + +- SQLite: `SQLiteDatabaseManager` +- MySQL/MariaDB: `MySQLDatabaseManager` +- PostgreSQL: `PostgreSQLDatabaseManager` +- SQL Server: `MicrosoftSQLDatabaseManager` +- Permission-controlled variants for MySQL, PostgreSQL, SQL Server. +- PostgreSQL schema managers for schema isolation. + +## Rules + +- `tenant` is a reserved dynamic connection name. +- Use `database.template_tenant_connection` for the tenant connection template. +- Use `database.tenant_host_connection_name` for database creation/deletion host connection. +- Tenant DB names are `prefix + tenant_id + suffix`. +- Use schema managers only when PostgreSQL schema isolation is intended. +- Test creation, migration, rollback, deletion, and connection restoration. diff --git a/resources/boost/skills/tenancy/references/features.md b/resources/boost/skills/tenancy/references/features.md new file mode 100644 index 000000000..06c70a7c2 --- /dev/null +++ b/resources/boost/skills/tenancy/references/features.md @@ -0,0 +1,30 @@ +# Optional Features Reference + +Use this when enabling or debugging classes in `tenancy.features`. + +## Source Files + +- `src/Features/*` +- `src/Tenancy.php` + +## Features + +- `UserImpersonation` +- `TelescopeTags` +- `CrossDomainRedirect` +- `ViteBundler` +- `DisallowSqliteAttach` +- `TenantConfig` + +## Behavior + +- Features are bootstrapped independently from tenant initialization. +- `tenancy()->bootstrapFeatures()` is idempotent per feature. +- Feature bootstrapping is irreversible during the request lifecycle. +- `TenantConfig` is deprecated in favor of `TenantConfigBootstrapper`. + +## Rules + +- Inspect the feature class before assuming behavior. +- Enable only the needed features. +- Test each enabled feature in central and tenant contexts where relevant. diff --git a/resources/boost/skills/tenancy/references/filesystem-cache-queue.md b/resources/boost/skills/tenancy/references/filesystem-cache-queue.md new file mode 100644 index 000000000..ae688932a --- /dev/null +++ b/resources/boost/skills/tenancy/references/filesystem-cache-queue.md @@ -0,0 +1,41 @@ +# Filesystem Cache Queue Reference + +Use this when tenant context affects storage, cache, sessions, Redis, or queues. + +## Source Files + +- `src/Bootstrappers/FilesystemTenancyBootstrapper.php` +- `src/Bootstrappers/CacheTenancyBootstrapper.php` +- `src/Bootstrappers/CacheTagsBootstrapper.php` +- `src/Bootstrappers/DatabaseCacheBootstrapper.php` +- `src/Bootstrappers/RedisTenancyBootstrapper.php` +- `src/Bootstrappers/QueueTenancyBootstrapper.php` +- `src/Bootstrappers/PersistentQueueTenancyBootstrapper.php` +- `src/Bootstrappers/DatabaseSessionBootstrapper.php` +- `src/Middleware/ScopeSessions.php` + +## Filesystem + +- `filesystem.disks` controls scoped disks. +- `filesystem.root_override` rewrites local disk roots. +- `filesystem.url_override` enables tenant-aware local public URLs. +- `filesystem.suffix_storage_path` controls `storage_path()` suffixing. +- `filesystem.asset_helper_override` makes `asset()` tenant-aware. + +## Cache And Redis + +- Cache tenancy prefixes configured stores through `cache.prefix`. +- Global central cache is available through `GlobalCache`/`globalCache()`. +- Redis tenancy is for direct Redis usage and requires phpredis. + +## Queue + +- Queue bootstrapper carries tenant context into queued jobs. +- Persistent queue bootstrapper is for workers intentionally staying tenant-aware. + +## Rules + +- Run `php artisan tenants:link` when tenant public local storage URLs are enabled. +- Be careful with `asset_helper_override`; third-party package assets may become tenant-aware. +- Scope sessions through config and `ScopeSessions` middleware where required. +- Test queued jobs in central and tenant contexts. diff --git a/resources/boost/skills/tenancy/references/identification.md b/resources/boost/skills/tenancy/references/identification.md new file mode 100644 index 000000000..787c944e5 --- /dev/null +++ b/resources/boost/skills/tenancy/references/identification.md @@ -0,0 +1,43 @@ +# Tenant Identification Reference + +Use this when resolving tenants from requests. + +## Source Files + +- `src/Middleware/IdentificationMiddleware.php` +- `src/Middleware/InitializeTenancyByDomain.php` +- `src/Middleware/InitializeTenancyBySubdomain.php` +- `src/Middleware/InitializeTenancyByDomainOrSubdomain.php` +- `src/Middleware/InitializeTenancyByPath.php` +- `src/Middleware/InitializeTenancyByRequestData.php` +- `src/Middleware/InitializeTenancyByOriginHeader.php` +- `src/Middleware/PreventAccessFromUnwantedDomains.php` +- `src/Middleware/ScopeSessions.php` +- `src/Resolvers/*` + +## Middleware + +- `InitializeTenancyByDomain` +- `InitializeTenancyBySubdomain` +- `InitializeTenancyByDomainOrSubdomain` +- `InitializeTenancyByPath` +- `InitializeTenancyByRequestData` +- `InitializeTenancyByOriginHeader` +- `PreventAccessFromUnwantedDomains` +- `CheckTenantForMaintenanceMode` +- `ScopeSessions` + +## Resolver Config + +- `DomainTenantResolver`: cache, TTL, cache store. +- `PathTenantResolver`: tenant route parameter, route name prefix, tenant model column, allowed extra columns, cache. +- `RequestDataTenantResolver`: header, cookie, query parameter, tenant model column, cache. + +## Rules + +- Configure `identification.central_domains` for domain/subdomain strategies. +- Use `PreventAccessFromUnwantedDomains` only with configured domain identification middleware. +- For path identification, confirm the tenant parameter name before defining URLs. +- For request-data identification, set unused channels to `null`. +- If custom middleware is introduced, add it to the appropriate config category. +- Test success and failure identification paths. diff --git a/resources/boost/skills/tenancy/references/impersonation.md b/resources/boost/skills/tenancy/references/impersonation.md new file mode 100644 index 000000000..9d6f70bc9 --- /dev/null +++ b/resources/boost/skills/tenancy/references/impersonation.md @@ -0,0 +1,36 @@ +# User Impersonation Reference + +Use this when implementing tenant user impersonation. + +## Source Files + +- `src/Features/UserImpersonation.php` +- `src/Database/Models/ImpersonationToken.php` +- `assets/impersonation-migrations/*` + +## Setup + +```bash +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=impersonation-migrations +php artisan migrate +``` + +Enable feature: + +```php +'features' => [ + Stancl\Tenancy\Features\UserImpersonation::class, +], +``` + +## Command + +```bash +php artisan tenants:purge-impersonation-tokens +``` + +## Rules + +- Verify tenant match before logging in impersonated users. +- Test guard, redirect URL, remember flag, token TTL, and invalid token behavior. +- Purge expired tokens routinely. diff --git a/resources/boost/skills/tenancy/references/installation.md b/resources/boost/skills/tenancy/references/installation.md new file mode 100644 index 000000000..1165fdb15 --- /dev/null +++ b/resources/boost/skills/tenancy/references/installation.md @@ -0,0 +1,73 @@ +# Installation Reference + +Use this when installing or auditing `stancl/tenancy` setup. + +## Source Files + +- `src/Commands/Install.php` +- `src/TenancyServiceProvider.php` +- `assets/config.php` +- `assets/tenant_routes.stub.php` +- `assets/TenancyServiceProvider.stub.php` +- `assets/migrations/*` + +## Required Steps + +1. Install the package. + +```bash +composer require stancl/tenancy +``` + +2. Run the installer non-interactively. + +```bash +php artisan tenancy:install --no-interaction +``` + +3. Confirm these files exist: + +- `config/tenancy.php` +- `routes/tenant.php` +- `app/Providers/TenancyServiceProvider.php` +- `database/migrations/2019_09_15_000010_create_tenants_table.php` +- `database/migrations/2019_09_15_000020_create_domains_table.php` +- `database/migrations/tenant` + +4. Review `config/tenancy.php` before running migrations. + +5. Run central migrations. + +```bash +php artisan migrate +``` + +6. Add tenant migrations to `database/migrations/tenant`. + +7. Create tenants and domains according to the identification strategy. + +8. Run tenant migrations. + +```bash +php artisan tenants:migrate +``` + +## Manual Publish Commands + +Prefer `tenancy:install`. Use these only for targeted publishing: + +```bash +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=config +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=routes +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=providers +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=migrations +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=impersonation-migrations +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=resource-syncing-migrations +``` + +## Installer Behavior + +- Publishes config, routes, provider, and base migrations. +- Creates `database/migrations/tenant`. +- Skips files that already exist and warns instead of overwriting. +- Shows an interactive support prompt unless `--no-interaction` is used. diff --git a/resources/boost/skills/tenancy/references/integrations.md b/resources/boost/skills/tenancy/references/integrations.md new file mode 100644 index 000000000..89a31cbfd --- /dev/null +++ b/resources/boost/skills/tenancy/references/integrations.md @@ -0,0 +1,44 @@ +# Integrations Reference + +Use this when tenancy integrates with URL generation, mail, broadcasting, Fortify, Scout, Livewire, Telescope, or Vite. + +## Source Files + +- `src/Bootstrappers/RootUrlBootstrapper.php` +- `src/Bootstrappers/UrlGeneratorBootstrapper.php` +- `src/Bootstrappers/MailConfigBootstrapper.php` +- `src/Bootstrappers/BroadcastingConfigBootstrapper.php` +- `src/Bootstrappers/BroadcastChannelPrefixBootstrapper.php` +- `src/Bootstrappers/Integrations/FortifyRouteBootstrapper.php` +- `src/Bootstrappers/Integrations/ScoutPrefixBootstrapper.php` +- `src/Features/TelescopeTags.php` +- `src/Features/ViteBundler.php` +- `assets/TenancyServiceProvider.stub.php` + +## Bootstrappers + +- `RootUrlBootstrapper`: tenant root URL for CLI/context URL generation. +- `UrlGeneratorBootstrapper`: tenant-aware route names and tenant parameters. +- `MailConfigBootstrapper`: tenant-specific mail config. +- `BroadcastingConfigBootstrapper`: tenant broadcaster config and manager. +- `BroadcastChannelPrefixBootstrapper`: tenant-prefixed broadcast channel names. +- `FortifyRouteBootstrapper`: tenant auth route/redirect integration. +- `ScoutPrefixBootstrapper`: tenant-specific Scout prefix. + +## Features + +- `TelescopeTags`: adds tenant tags when tenancy is initialized. +- `ViteBundler`: tenant-aware bundling behavior. + +## Stub Hooks + +- `overrideUrlInTenantContext()` shows how to set `RootUrlBootstrapper::$rootUrlOverride`. +- The Livewire v3 comment shows how to make the Livewire update route universal. +- `cloneRoutes()` shows the package route-cloning integration point. + +## Rules + +- Prefer bootstrappers over ad hoc service-provider config mutation. +- Test generated URLs in HTTP and CLI contexts. +- Test broadcast channel names and tenant-specific broadcaster credentials. +- Make third-party package routes universal or cloned only when intentionally accessible in tenant context. diff --git a/resources/boost/skills/tenancy/references/lifecycle-jobs.md b/resources/boost/skills/tenancy/references/lifecycle-jobs.md new file mode 100644 index 000000000..f87159823 --- /dev/null +++ b/resources/boost/skills/tenancy/references/lifecycle-jobs.md @@ -0,0 +1,46 @@ +# Lifecycle Jobs Reference + +Use this when provisioning or deleting tenant resources through events and job pipelines. + +## Source Files + +- `assets/TenancyServiceProvider.stub.php` +- `src/Events/*` +- `src/Jobs/*` +- `src/Listeners/*` +- `src/Database/Models/Tenant.php` +- `src/Database/Models/Domain.php` + +## Tenant Created Pipeline + +The published application provider wires `TenantCreated` to a `JobPipeline` containing: + +- `CreateDatabase` +- `MigrateDatabase` +- optional `SeedDatabase` +- optional `CreateStorageSymlinks` +- custom provisioning jobs + +## Tenant Deletion Pipelines + +The stub wires: + +- `DeletingTenant` to `DeleteDomains`, optional `DeleteTenantStorage`, optional `RemoveStorageSymlinks`. +- `TenantDeleted` to `DeleteDatabase`, optional resource-syncing cleanup. + +## Event Groups + +- Tenant lifecycle events. +- Domain lifecycle events. +- Database lifecycle events. +- Tenancy initialization/end/bootstrap events. +- Pending tenant events. +- Resource syncing events. +- Storage symlink events. + +## Rules + +- Put provisioning and cleanup in event pipelines rather than controllers. +- Keep database, migration, seeding, storage, domain, and sync cleanup order explicit. +- Decide whether pipelines should be queued using `shouldBeQueued()`. +- Test tenant creation and deletion side effects. diff --git a/resources/boost/skills/tenancy/references/migrations-commands.md b/resources/boost/skills/tenancy/references/migrations-commands.md new file mode 100644 index 000000000..7df8c4656 --- /dev/null +++ b/resources/boost/skills/tenancy/references/migrations-commands.md @@ -0,0 +1,56 @@ +# Migrations And Commands Reference + +Use this for tenant-aware Artisan operations. + +## Source Files + +- `src/Commands/*` +- `src/Concerns/HasTenantOptions.php` +- `src/Concerns/ExtendsLaravelCommand.php` +- `src/Concerns/DealsWithMigrations.php` +- `assets/config.php` + +## Tenant Migrations + +Defaults from `tenancy.migration_parameters`: + +- `--force` true +- `--path` `database/migrations/tenant` +- `--schema-path` `database/schema/tenant-schema.dump` +- `--realpath` true + +Commands: + +```bash +php artisan tenants:migrate +php artisan tenants:migrate --tenants=tenant-id +php artisan tenants:migrate --skip-failing +php artisan tenants:rollback +php artisan tenants:migrate-fresh +php artisan tenants:seed +``` + +## Tenant Operations + +```bash +php artisan tenants:run cache:clear +php artisan tenant:tinker +php artisan tenants:list +php artisan tenant:dump +``` + +## Maintenance And Storage + +```bash +php artisan tenants:down +php artisan tenants:up +php artisan tenants:link +php artisan tenants:link --remove +``` + +## Rules + +- Use tenant commands for tenant DBs; normal `migrate` is central. +- Use `--tenants=*` to scope commands to selected tenants. +- Use `--skip-failing` only when failures should not stop execution. +- Use concurrent process options only after verifying tenant operations are safe in parallel. diff --git a/resources/boost/skills/tenancy/references/models-domains.md b/resources/boost/skills/tenancy/references/models-domains.md new file mode 100644 index 000000000..eec8fbdf3 --- /dev/null +++ b/resources/boost/skills/tenancy/references/models-domains.md @@ -0,0 +1,42 @@ +# Models And Domains Reference + +Use this when changing tenant/domain models or tenant-owned models. + +## Source Files + +- `src/Database/Models/Tenant.php` +- `src/Database/Models/Domain.php` +- `src/Contracts/Tenant.php` +- `src/Contracts/Domain.php` +- `src/Database/Concerns/*` +- `assets/migrations/*` + +## Default Tenant Model + +- Table: `tenants` +- Primary key: `id` +- Uses `VirtualColumn`, `CentralConnection`, `GeneratesIds`, `HasInternalKeys`, `TenantRun`, `InitializationHelpers`, `InvalidatesResolverCache`. +- Dispatches tenant lifecycle events. + +## Default Domain Model + +- Unique `domain` column. +- Belongs to configured tenant model using `Tenancy::tenantKeyColumn()`. +- Uses `CentralConnection`, `EnsuresDomainIsNotOccupied`, `ConvertsDomainsToLowercase`, `InvalidatesTenantsResolverCache`. + +## Single-Database Traits + +- `BelongsToTenant` +- `FillsCurrentTenant` +- `TenantConnection` +- `CentralConnection` +- `HasScopedValidationRules` +- `RLSModel` + +## Rules + +- Custom tenant models must implement `Stancl\Tenancy\Contracts\Tenant`. +- Custom domain models must implement `Stancl\Tenancy\Contracts\Domain`. +- Preserve resolver cache invalidation behavior when replacing models. +- Use tenant scoping traits consistently for single-database tenancy. +- If auto-increment tenant IDs are used, update config and migrations together. diff --git a/resources/boost/skills/tenancy/references/package.md b/resources/boost/skills/tenancy/references/package.md index 9b00b92ab..755036837 100644 --- a/resources/boost/skills/tenancy/references/package.md +++ b/resources/boost/skills/tenancy/references/package.md @@ -2,6 +2,30 @@ This reference is for package-specific details that do not need to live in `SKILL.md`. + +## Focused References + +Load these smaller references for topic-specific work: + +- `installation.md` for install, publishing, and setup checks +- `configuration.md` for `config/tenancy.php` sections +- `identification.md` for middleware and resolvers +- `routing-assets.md` for tenant routes, route modes, cloned routes, and asset routes +- `context-api.md` for `tenancy()`, `tenant()`, `run()`, and `central()` behavior +- `bootstrappers.md` for tenant-aware Laravel service scoping +- `database-tenancy.md` for database isolation and tenant database managers +- `migrations-commands.md` for tenant Artisan commands +- `models-domains.md` for tenant/domain models and single-database traits +- `filesystem-cache-queue.md` for storage, cache, sessions, Redis, and queues +- `lifecycle-jobs.md` for events, provisioning, and cleanup pipelines +- `resource-syncing.md` for synced central and tenant resources +- `impersonation.md` for tenant user impersonation +- `pending-tenants.md` for pending tenant pools +- `rls.md` for PostgreSQL row-level security +- `features.md` for optional package features +- `integrations.md` for URL, mail, broadcasting, Fortify, Scout, Livewire, Telescope, and Vite +- `testing.md` for test coverage guidance + ## Main Entry Points - `src/TenancyServiceProvider.php` diff --git a/resources/boost/skills/tenancy/references/pending-tenants.md b/resources/boost/skills/tenancy/references/pending-tenants.md new file mode 100644 index 000000000..28c2ca1af --- /dev/null +++ b/resources/boost/skills/tenancy/references/pending-tenants.md @@ -0,0 +1,33 @@ +# Pending Tenants Reference + +Use this when maintaining a pool of prepared tenants. + +## Source Files + +- `src/Commands/CreatePendingTenants.php` +- `src/Commands/ClearPendingTenants.php` +- `src/Database/Concerns/HasPending.php` +- `src/Database/Concerns/PendingScope.php` +- `src/Jobs/CreatePendingTenants.php` +- `src/Jobs/ClearPendingTenants.php` + +## Config + +- `pending.include_in_queries` +- `pending.count`, defaulting to `TENANCY_PENDING_COUNT` or 5 + +## Commands + +```bash +php artisan tenants:pending-create +php artisan tenants:pending-create --count=10 +php artisan tenants:pending-clear +php artisan tenants:pending-clear --older-than-days=7 +php artisan tenants:pending-clear --older-than-hours=12 +``` + +## Rules + +- When `include_in_queries` is false, pending tenants are excluded from tenant queries and tenant commands. +- Use `withPending()`, `withoutPending()`, and `onlyPending()` intentionally. +- Test command behavior with and without pending tenants included in queries. diff --git a/resources/boost/skills/tenancy/references/resource-syncing.md b/resources/boost/skills/tenancy/references/resource-syncing.md new file mode 100644 index 000000000..fba5c2175 --- /dev/null +++ b/resources/boost/skills/tenancy/references/resource-syncing.md @@ -0,0 +1,32 @@ +# Resource Syncing Reference + +Use this when syncing central resources into tenant contexts. + +## Source Files + +- `src/ResourceSyncing/*` +- `assets/resource-syncing-migrations/*` +- `assets/TenancyServiceProvider.stub.php` + +## Setup + +Publish resource syncing migration: + +```bash +php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=resource-syncing-migrations +php artisan migrate +``` + +## Main Pieces + +- `tenant_resources` table. +- `ResourceSyncing` classes and listeners. +- `SyncMaster`, `Syncable`, `TenantPivot`, `TenantMorphPivot`. +- Events such as `SyncedResourceSaved`, `SyncedResourceDeleted`, `CentralResourceAttachedToTenant`. + +## Rules + +- Use package events/listeners instead of custom tenant loops. +- Keep central and tenant resource lifecycles explicit. +- Configure soft-delete query behavior in the application `TenancyServiceProvider` when needed. +- Test create, update, delete, restore, attach, and detach behavior. diff --git a/resources/boost/skills/tenancy/references/rls.md b/resources/boost/skills/tenancy/references/rls.md new file mode 100644 index 000000000..64eef412c --- /dev/null +++ b/resources/boost/skills/tenancy/references/rls.md @@ -0,0 +1,33 @@ +# PostgreSQL RLS Reference + +Use this when implementing single-database PostgreSQL row-level security. + +## Source Files + +- `src/Bootstrappers/PostgresRLSBootstrapper.php` +- `src/RLS/*` +- `src/Database/Concerns/RLSModel.php` +- `src/Commands/CreateUserWithRLSPolicies.php` +- `tests/RLS/*` + +## Config + +- `rls.manager` +- `rls.user.username` +- `rls.user.password` +- `rls.session_variable_name` +- `PostgresRLSBootstrapper` in `bootstrappers` + +## Command + +```bash +php artisan tenants:rls +php artisan tenants:rls --force +``` + +## Rules + +- Use PostgreSQL and single-database tenancy. +- Session variable name must be namespaced, for example `my.current_tenant`. +- RLS user is one tenant database user for all tenants, not one user per tenant. +- Test policies on every tenant-owned table. diff --git a/resources/boost/skills/tenancy/references/routing-assets.md b/resources/boost/skills/tenancy/references/routing-assets.md new file mode 100644 index 000000000..45b29074b --- /dev/null +++ b/resources/boost/skills/tenancy/references/routing-assets.md @@ -0,0 +1,49 @@ +# Routing And Assets Reference + +Use this when working with tenant routes, route modes, cloned routes, or tenant assets. + +## Source Files + +- `assets/tenant_routes.stub.php` +- `assets/TenancyServiceProvider.stub.php` +- `assets/routes.php` +- `src/Actions/CloneRoutesAsTenant.php` +- `src/Enums/RouteMode.php` +- `src/Controllers/TenantAssetController.php` + +## Published Tenant Routes + +The stub groups tenant routes with: + +- `web` +- `InitializeTenancyByDomain` +- `PreventAccessFromUnwantedDomains` +- `ScopeSessions` + +The application `TenancyServiceProvider` loads `routes/tenant.php` under the `tenant` middleware group. + +## Route Modes + +The package registers these middleware groups: + +- `clone` +- `universal` +- `tenant` +- `central` + +`tenancy.default_route_mode` defaults to central. Override per route using route mode middleware. + +## Asset Routes + +When `tenancy.routes` is true, the package registers: + +- `/tenancy/assets/{path?}` named `stancl.tenancy.asset` +- `/{tenant}/tenancy/assets/{path?}` named `tenant.stancl.tenancy.asset` for path identification + +## Rules + +- Keep central and tenant routes explicit. +- Use `routes/tenant.php` for tenant application routes when using the stub. +- Use `universal` only for routes intended to work in both contexts. +- Use `CloneRoutesAsTenant` for package route integration instead of manually duplicating route definitions. +- Disable package routes only if using external storage or a custom asset controller. diff --git a/resources/boost/skills/tenancy/references/testing.md b/resources/boost/skills/tenancy/references/testing.md new file mode 100644 index 000000000..6a72cb169 --- /dev/null +++ b/resources/boost/skills/tenancy/references/testing.md @@ -0,0 +1,43 @@ +# Testing Reference + +Use this when adding or reviewing tenancy behavior tests. + +## Source Files + +- `tests/TestCase.php` +- `tests/Pest.php` +- `tests/*` + +## High-Value Test Areas + +- Installation and published files. +- Central route access. +- Tenant route access. +- Domain, subdomain, path, request data, and origin header identification. +- Identification failures. +- Tenant context API restoration after success and exceptions. +- Database connection switching and reverting. +- Cache, Redis, filesystem, session, queue, URL, mail, and broadcasting scoping. +- Tenant migrations, rollbacks, seeds, and tenant command options. +- Tenant lifecycle jobs and event pipelines. +- Resource syncing. +- User impersonation. +- Pending tenants. +- RLS policies. +- Optional features. + +## Useful Existing Tests + +- `tests/AutomaticModeTest.php` +- `tests/ManualModeTest.php` +- `tests/RouteMiddlewareTest.php` +- `tests/PathIdentificationTest.php` +- `tests/RequestDataIdentificationTest.php` +- `tests/OriginHeaderIdentificationTest.php` +- `tests/TenantAssetTest.php` +- `tests/CommandsTest.php` +- `tests/QueueTest.php` +- `tests/SingleDatabaseTenancyTest.php` +- `tests/RLS/*` +- `tests/ResourceSyncingTest.php` +- `tests/TenantUserImpersonationTest.php` From 882eaef8b440a6f52abf06633e36ee1697e5ffab Mon Sep 17 00:00:00 2001 From: eramitgupta Date: Tue, 2 Jun 2026 12:38:17 +0530 Subject: [PATCH 4/5] Remove detailed tenancy references from boost resources for better clarity and maintainability --- resources/boost/skills/{tenancy => laravel-tenancy}/SKILL.md | 0 .../{tenancy => laravel-tenancy}/references/bootstrappers.md | 0 .../{tenancy => laravel-tenancy}/references/configuration.md | 0 .../skills/{tenancy => laravel-tenancy}/references/context-api.md | 0 .../{tenancy => laravel-tenancy}/references/database-tenancy.md | 0 .../skills/{tenancy => laravel-tenancy}/references/features.md | 0 .../references/filesystem-cache-queue.md | 0 .../{tenancy => laravel-tenancy}/references/identification.md | 0 .../{tenancy => laravel-tenancy}/references/impersonation.md | 0 .../{tenancy => laravel-tenancy}/references/installation.md | 0 .../{tenancy => laravel-tenancy}/references/integrations.md | 0 .../{tenancy => laravel-tenancy}/references/lifecycle-jobs.md | 0 .../references/migrations-commands.md | 0 .../{tenancy => laravel-tenancy}/references/models-domains.md | 0 .../skills/{tenancy => laravel-tenancy}/references/package.md | 0 .../{tenancy => laravel-tenancy}/references/pending-tenants.md | 0 .../{tenancy => laravel-tenancy}/references/resource-syncing.md | 0 .../boost/skills/{tenancy => laravel-tenancy}/references/rls.md | 0 .../{tenancy => laravel-tenancy}/references/routing-assets.md | 0 .../skills/{tenancy => laravel-tenancy}/references/testing.md | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename resources/boost/skills/{tenancy => laravel-tenancy}/SKILL.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/bootstrappers.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/configuration.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/context-api.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/database-tenancy.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/features.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/filesystem-cache-queue.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/identification.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/impersonation.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/installation.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/integrations.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/lifecycle-jobs.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/migrations-commands.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/models-domains.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/package.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/pending-tenants.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/resource-syncing.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/rls.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/routing-assets.md (100%) rename resources/boost/skills/{tenancy => laravel-tenancy}/references/testing.md (100%) diff --git a/resources/boost/skills/tenancy/SKILL.md b/resources/boost/skills/laravel-tenancy/SKILL.md similarity index 100% rename from resources/boost/skills/tenancy/SKILL.md rename to resources/boost/skills/laravel-tenancy/SKILL.md diff --git a/resources/boost/skills/tenancy/references/bootstrappers.md b/resources/boost/skills/laravel-tenancy/references/bootstrappers.md similarity index 100% rename from resources/boost/skills/tenancy/references/bootstrappers.md rename to resources/boost/skills/laravel-tenancy/references/bootstrappers.md diff --git a/resources/boost/skills/tenancy/references/configuration.md b/resources/boost/skills/laravel-tenancy/references/configuration.md similarity index 100% rename from resources/boost/skills/tenancy/references/configuration.md rename to resources/boost/skills/laravel-tenancy/references/configuration.md diff --git a/resources/boost/skills/tenancy/references/context-api.md b/resources/boost/skills/laravel-tenancy/references/context-api.md similarity index 100% rename from resources/boost/skills/tenancy/references/context-api.md rename to resources/boost/skills/laravel-tenancy/references/context-api.md diff --git a/resources/boost/skills/tenancy/references/database-tenancy.md b/resources/boost/skills/laravel-tenancy/references/database-tenancy.md similarity index 100% rename from resources/boost/skills/tenancy/references/database-tenancy.md rename to resources/boost/skills/laravel-tenancy/references/database-tenancy.md diff --git a/resources/boost/skills/tenancy/references/features.md b/resources/boost/skills/laravel-tenancy/references/features.md similarity index 100% rename from resources/boost/skills/tenancy/references/features.md rename to resources/boost/skills/laravel-tenancy/references/features.md diff --git a/resources/boost/skills/tenancy/references/filesystem-cache-queue.md b/resources/boost/skills/laravel-tenancy/references/filesystem-cache-queue.md similarity index 100% rename from resources/boost/skills/tenancy/references/filesystem-cache-queue.md rename to resources/boost/skills/laravel-tenancy/references/filesystem-cache-queue.md diff --git a/resources/boost/skills/tenancy/references/identification.md b/resources/boost/skills/laravel-tenancy/references/identification.md similarity index 100% rename from resources/boost/skills/tenancy/references/identification.md rename to resources/boost/skills/laravel-tenancy/references/identification.md diff --git a/resources/boost/skills/tenancy/references/impersonation.md b/resources/boost/skills/laravel-tenancy/references/impersonation.md similarity index 100% rename from resources/boost/skills/tenancy/references/impersonation.md rename to resources/boost/skills/laravel-tenancy/references/impersonation.md diff --git a/resources/boost/skills/tenancy/references/installation.md b/resources/boost/skills/laravel-tenancy/references/installation.md similarity index 100% rename from resources/boost/skills/tenancy/references/installation.md rename to resources/boost/skills/laravel-tenancy/references/installation.md diff --git a/resources/boost/skills/tenancy/references/integrations.md b/resources/boost/skills/laravel-tenancy/references/integrations.md similarity index 100% rename from resources/boost/skills/tenancy/references/integrations.md rename to resources/boost/skills/laravel-tenancy/references/integrations.md diff --git a/resources/boost/skills/tenancy/references/lifecycle-jobs.md b/resources/boost/skills/laravel-tenancy/references/lifecycle-jobs.md similarity index 100% rename from resources/boost/skills/tenancy/references/lifecycle-jobs.md rename to resources/boost/skills/laravel-tenancy/references/lifecycle-jobs.md diff --git a/resources/boost/skills/tenancy/references/migrations-commands.md b/resources/boost/skills/laravel-tenancy/references/migrations-commands.md similarity index 100% rename from resources/boost/skills/tenancy/references/migrations-commands.md rename to resources/boost/skills/laravel-tenancy/references/migrations-commands.md diff --git a/resources/boost/skills/tenancy/references/models-domains.md b/resources/boost/skills/laravel-tenancy/references/models-domains.md similarity index 100% rename from resources/boost/skills/tenancy/references/models-domains.md rename to resources/boost/skills/laravel-tenancy/references/models-domains.md diff --git a/resources/boost/skills/tenancy/references/package.md b/resources/boost/skills/laravel-tenancy/references/package.md similarity index 100% rename from resources/boost/skills/tenancy/references/package.md rename to resources/boost/skills/laravel-tenancy/references/package.md diff --git a/resources/boost/skills/tenancy/references/pending-tenants.md b/resources/boost/skills/laravel-tenancy/references/pending-tenants.md similarity index 100% rename from resources/boost/skills/tenancy/references/pending-tenants.md rename to resources/boost/skills/laravel-tenancy/references/pending-tenants.md diff --git a/resources/boost/skills/tenancy/references/resource-syncing.md b/resources/boost/skills/laravel-tenancy/references/resource-syncing.md similarity index 100% rename from resources/boost/skills/tenancy/references/resource-syncing.md rename to resources/boost/skills/laravel-tenancy/references/resource-syncing.md diff --git a/resources/boost/skills/tenancy/references/rls.md b/resources/boost/skills/laravel-tenancy/references/rls.md similarity index 100% rename from resources/boost/skills/tenancy/references/rls.md rename to resources/boost/skills/laravel-tenancy/references/rls.md diff --git a/resources/boost/skills/tenancy/references/routing-assets.md b/resources/boost/skills/laravel-tenancy/references/routing-assets.md similarity index 100% rename from resources/boost/skills/tenancy/references/routing-assets.md rename to resources/boost/skills/laravel-tenancy/references/routing-assets.md diff --git a/resources/boost/skills/tenancy/references/testing.md b/resources/boost/skills/laravel-tenancy/references/testing.md similarity index 100% rename from resources/boost/skills/tenancy/references/testing.md rename to resources/boost/skills/laravel-tenancy/references/testing.md From 656bcda8c262ad40ccaced4856c51604e858d2b4 Mon Sep 17 00:00:00 2001 From: eramitgupta Date: Tue, 2 Jun 2026 12:50:21 +0530 Subject: [PATCH 5/5] Simplify tenancy boost resources by replacing detailed guidelines with focused references --- resources/boost/guidelines/core.blade.php | 934 +++--------------- .../boost/skills/laravel-tenancy/SKILL.md | 10 +- .../references/installation.md | 14 +- .../references/migrations-commands.md | 2 +- 4 files changed, 126 insertions(+), 834 deletions(-) diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php index 878494ba7..104aa6850 100644 --- a/resources/boost/guidelines/core.blade.php +++ b/resources/boost/guidelines/core.blade.php @@ -3,899 +3,191 @@ # Stancl Tenancy Guidelines -These guidelines are for Laravel applications using `stancl/tenancy`. They are based on this package's source, installer, config, stubs, routes, migrations, service provider, commands, bootstrappers, middleware, models, and tests. +These guidelines apply to Laravel applications using `stancl/tenancy`. Keep this file concise for Laravel Boost. For detailed package behavior, load the focused references in `resources/boost/skills/laravel-tenancy/references`. ## Package Context - Composer package: `stancl/tenancy` -- Purpose: automatic multi-tenancy for Laravel applications - Service provider: `Stancl\Tenancy\TenancyServiceProvider` -- Facades: `Tenancy`, `GlobalCache` -- Core singleton: `Stancl\Tenancy\Tenancy` -- Database manager singleton: `Stancl\Tenancy\Database\DatabaseManager` -- Current tenant contract binding: `Stancl\Tenancy\Contracts\Tenant` -- Current domain contract binding: `Stancl\Tenancy\Contracts\Domain` +- Facades: `Tenancy` and `GlobalCache` +- Core manager: `Stancl\Tenancy\Tenancy` - Default tenant model: `Stancl\Tenancy\Database\Models\Tenant` - Default domain model: `Stancl\Tenancy\Database\Models\Domain` -- Default impersonation token model: `Stancl\Tenancy\Database\Models\ImpersonationToken` -- Default tenant key relation column: `tenant_id` -- Reserved tenant database connection name: `tenant` -- Default central connection config: `tenancy.database.central_connection`, usually `env('DB_CONNECTION', 'central')` - -## Source Files To Inspect - -Before changing tenancy behavior, inspect the relevant local package files. Do not guess package behavior from memory. - -- `README.md` -- `composer.json` -- `src/TenancyServiceProvider.php` -- `src/Tenancy.php` -- `src/helpers.php` -- `assets/config.php` -- `assets/routes.php` -- `assets/tenant_routes.stub.php` -- `assets/TenancyServiceProvider.stub.php` -- `assets/migrations/*` -- `assets/impersonation-migrations/*` -- `assets/resource-syncing-migrations/*` -- `src/Middleware/*` -- `src/Resolvers/*` -- `src/Bootstrappers/*` -- `src/Commands/*` -- `src/Database/Models/*` -- `src/Database/Concerns/*` -- `src/Database/TenantDatabaseManagers/*` -- `src/Features/*` -- `src/Jobs/*` -- `resources/boost/skills/tenancy/references/package.md` -- `tests/*` for expected behavior - -Use Laravel documentation for framework-level behavior such as service providers, vendor publishing, routes, middleware, migrations, queues, cache, filesystem, database, and testing. - -## Installation Steps - -Follow every installation step. Do not skip setup files or migrations. - -1. Install the package with Composer. +- Default tenant key column: `tenant_id` +- Reserved dynamic tenant connection name: `tenant` + +## Focused References + +Load the focused reference matching the task before implementing changes: + +- `resources/boost/skills/laravel-tenancy/references/installation.md` for install and publishing +- `resources/boost/skills/laravel-tenancy/references/configuration.md` for `config/tenancy.php` +- `resources/boost/skills/laravel-tenancy/references/identification.md` for middleware and resolvers +- `resources/boost/skills/laravel-tenancy/references/routing-assets.md` for routes, route modes, cloned routes, and tenant assets +- `resources/boost/skills/laravel-tenancy/references/context-api.md` for `tenancy()`, `tenant()`, `run()`, and `central()` +- `resources/boost/skills/laravel-tenancy/references/bootstrappers.md` for tenant-aware Laravel service scoping +- `resources/boost/skills/laravel-tenancy/references/database-tenancy.md` for database isolation and database managers +- `resources/boost/skills/laravel-tenancy/references/migrations-commands.md` for tenant Artisan commands +- `resources/boost/skills/laravel-tenancy/references/models-domains.md` for tenant/domain models and single-database traits +- `resources/boost/skills/laravel-tenancy/references/filesystem-cache-queue.md` for storage, cache, sessions, Redis, and queues +- `resources/boost/skills/laravel-tenancy/references/lifecycle-jobs.md` for tenant event pipelines and provisioning +- `resources/boost/skills/laravel-tenancy/references/resource-syncing.md` for synced central and tenant resources +- `resources/boost/skills/laravel-tenancy/references/impersonation.md` for tenant user impersonation +- `resources/boost/skills/laravel-tenancy/references/pending-tenants.md` for pending tenant pools +- `resources/boost/skills/laravel-tenancy/references/rls.md` for PostgreSQL row-level security +- `resources/boost/skills/laravel-tenancy/references/features.md` for optional package features +- `resources/boost/skills/laravel-tenancy/references/integrations.md` for URL, mail, broadcasting, Fortify, Scout, Livewire, Telescope, and Vite +- `resources/boost/skills/laravel-tenancy/references/testing.md` for test coverage guidance + +## Installation Rules + +Use the package installer unless intentionally publishing one tag: ```bash composer require stancl/tenancy -``` - -2. Run the package installer. - -```bash php artisan tenancy:install --no-interaction -``` - -3. Confirm the installer published the config. - -```text -config/tenancy.php -``` - -4. Confirm the installer published tenant routes. - -```text -routes/tenant.php -``` - -5. Confirm the installer published the application tenancy service provider. - -```text -app/Providers/TenancyServiceProvider.php -``` - -6. Confirm the installer published central migrations. - -```text -database/migrations/2019_09_15_000010_create_tenants_table.php -database/migrations/2019_09_15_000020_create_domains_table.php -``` - -7. Confirm the installer created the tenant migration directory. - -```text -database/migrations/tenant -``` - -8. Review and adjust `config/tenancy.php` before running migrations. Decide identification, bootstrappers, database isolation, central domains, route mode, and tenant migration parameters first. - -9. Run central migrations for the main application database. - -```bash php artisan migrate +php artisan tenants:migrate ``` -10. Add tenant-specific migrations under `database/migrations/tenant`. +The installer publishes: -11. Create tenants using the configured tenant model and attach domains when using domain, subdomain, or domain-or-subdomain identification. +- `config/tenancy.php` +- `routes/tenant.php` +- `app/Providers/TenancyServiceProvider.php` +- central tenant and domain migrations +- `database/migrations/tenant` -12. Run tenant migrations after tenants exist. +Review `config/tenancy.php` before running migrations. Decide identification, central domains, route mode, bootstrappers, database isolation, and tenant migration path before writing application code. -```bash -php artisan tenants:migrate -``` +## Core Workflow -13. Seed tenant databases only when needed. +- Decide tenant identification first: domain, subdomain, domain-or-subdomain, path, request data, or origin header. +- Keep central, tenant, and universal routes explicit with package middleware and route modes. +- Choose database isolation early: separate tenant databases, PostgreSQL schemas, single-database tenancy, or PostgreSQL RLS. +- Configure bootstrappers before writing application workarounds. +- Put tenant migrations in `database/migrations/tenant` unless `tenancy.migration_parameters` changes the path. +- Use package context APIs instead of manually mutating framework state. +- Test central and tenant contexts for every tenancy-sensitive change. -```bash -php artisan tenants:seed -``` +## Tenant Context Rules -14. If tenant-aware local public storage URLs are enabled, create tenant symlinks. +Use package APIs: -```bash -php artisan tenants:link +```php +tenancy()->initialize($tenant); +$tenant->run(fn () => null); +tenancy()->central(fn () => null); +tenancy()->end(); ``` -15. Test central routes and tenant routes separately before shipping. - -## Manual Publish Commands +Do not manually change database connections, cache prefixes, filesystem roots, queue payloads, session stores, URL roots, mail config, or broadcasting config when a package bootstrapper owns that concern. -Prefer `tenancy:install`. Use manual publishing only when intentionally publishing a specific group. +## Identification Rules -```bash -php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=config -php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=routes -php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=providers -php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=migrations -php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=impersonation-migrations -php artisan vendor:publish --provider="Stancl\Tenancy\TenancyServiceProvider" --tag=resource-syncing-migrations -``` +Built-in tenant identification middleware: -## Published Files - -The package publishes these files and directories: - -- `assets/config.php` to `config/tenancy.php` -- `assets/tenant_routes.stub.php` to `routes/tenant.php` -- `assets/TenancyServiceProvider.stub.php` to `app/Providers/TenancyServiceProvider.php` -- `assets/migrations/2019_09_15_000010_create_tenants_table.php` to central migrations -- `assets/migrations/2019_09_15_000020_create_domains_table.php` to central migrations -- `assets/impersonation-migrations/2020_05_15_000010_create_tenant_user_impersonation_tokens_table.php` when user impersonation is used -- `assets/resource-syncing-migrations/2020_05_11_000002_create_tenant_resources_table.php` when resource syncing is used -- `database/migrations/tenant` is created by `tenancy:install` for tenant migrations - -## Installer Behavior - -`php artisan tenancy:install` performs these package-defined steps: - -- Publishes config using tag `config` -- Publishes routes using tag `routes` -- Publishes provider using tag `providers` -- Publishes tenant and domain migrations using tag `migrations` -- Creates `database/migrations/tenant` -- Skips existing files and warns instead of overwriting them -- Shows an interactive GitHub support prompt unless `--no-interaction` is used - -Use `--no-interaction` in automation and agent workflows. - -## Core Config Checklist - -Always review these `config/tenancy.php` sections before implementation: - -- `models.tenant` -- `models.domain` -- `models.impersonation_token` -- `models.tenant_key_column` -- `models.id_generator` -- `identification.central_domains` -- `identification.default_middleware` -- `identification.middleware` -- `identification.domain_identification_middleware` -- `identification.path_identification_middleware` -- `identification.resolvers` -- `bootstrappers` -- `database.central_connection` -- `database.template_tenant_connection` -- `database.tenant_host_connection_name` -- `database.prefix` -- `database.suffix` -- `database.managers` -- `database.drop_tenant_databases_on_migrate_fresh` -- `rls.manager` -- `rls.user.username` -- `rls.user.password` -- `rls.session_variable_name` -- `cache.prefix` -- `cache.stores` -- `cache.scope_sessions` -- `cache.tag_base` -- `filesystem.suffix_base` -- `filesystem.disks` -- `filesystem.root_override` -- `filesystem.url_override` -- `filesystem.scope_cache` -- `filesystem.scope_sessions` -- `filesystem.suffix_storage_path` -- `filesystem.asset_helper_override` -- `redis.prefix` -- `redis.prefixed_connections` -- `features` -- `routes` -- `default_route_mode` -- `pending.include_in_queries` -- `pending.count` -- `migration_parameters` -- `seeder_parameters` - -## Tenant Identification - -Decide the identification strategy before writing routes, middleware, model logic, URLs, tests, or tenant creation flows. - -Built-in identification middleware: - -- `Stancl\Tenancy\Middleware\InitializeTenancyByDomain` -- `Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain` -- `Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain` -- `Stancl\Tenancy\Middleware\InitializeTenancyByPath` -- `Stancl\Tenancy\Middleware\InitializeTenancyByRequestData` -- `Stancl\Tenancy\Middleware\InitializeTenancyByOriginHeader` - -Related middleware: - -- `Stancl\Tenancy\Middleware\PreventAccessFromUnwantedDomains` -- `Stancl\Tenancy\Middleware\CheckTenantForMaintenanceMode` -- `Stancl\Tenancy\Middleware\ScopeSessions` - -Rules: - -- Configure `identification.central_domains` correctly for domain and subdomain identification. -- Use `PreventAccessFromUnwantedDomains` only with domain-oriented identification middleware listed in `identification.domain_identification_middleware`. -- For path identification, check `PathTenantResolver::tenantParameterName()` and `identification.resolvers[PathTenantResolver::class]` before changing route parameters. -- For request data identification, configure header, cookie, and query parameter names in `RequestDataTenantResolver` config. -- For origin header identification, verify trusted frontend/origin behavior and failure handling. -- Failed identification throws package exceptions unless an `onFail` callback is registered. -- If custom middleware is added, also add it to the matching config array when the package needs to recognize its category. - -## Tenant Resolvers - -Built-in resolvers: - -- `Stancl\Tenancy\Resolvers\DomainTenantResolver` -- `Stancl\Tenancy\Resolvers\PathTenantResolver` -- `Stancl\Tenancy\Resolvers\RequestDataTenantResolver` - -Resolver guidance: - -- Enable resolver cache only deliberately and invalidate it when domain or tenant lookup data changes. -- Use `cache_ttl` and `cache_store` when resolver caching is enabled. -- For path resolver custom binding fields, configure `allowed_extra_model_columns`. -- For request data resolver, set unused identification channels to `null`. -- Use `tenant_model_column` when lookup should use a custom tenant column instead of the tenant key. - -## Route Setup - -The published `routes/tenant.php` stub uses: +- `InitializeTenancyByDomain` +- `InitializeTenancyBySubdomain` +- `InitializeTenancyByDomainOrSubdomain` +- `InitializeTenancyByPath` +- `InitializeTenancyByRequestData` +- `InitializeTenancyByOriginHeader` -```php -Route::middleware([ - 'web', - Middleware\InitializeTenancyByDomain::class, - Middleware\PreventAccessFromUnwantedDomains::class, - Middleware\ScopeSessions::class, -])->group(function () { - // Tenant routes... -}); -``` +Use `PreventAccessFromUnwantedDomains` only with domain-oriented identification middleware recognized by `tenancy.identification.domain_identification_middleware`. -The published application `TenancyServiceProvider` loads tenant routes like this: +For path identification, check `PathTenantResolver` config before changing route parameters. For request-data identification, configure header, cookie, and query parameter names explicitly. -- Waits until the app is booted -- Checks `base_path('routes/tenant.php')` -- Applies middleware group `tenant` -- Uses `static::$controllerNamespace` -- Can clone routes as tenant routes through `CloneRoutesAsTenant` +## Routing Rules -The package service provider registers empty route middleware groups: +The package registers route middleware groups: - `clone` - `universal` - `tenant` - `central` -Route mode rules: - -- `tenancy.default_route_mode` defaults to `RouteMode::CENTRAL`. -- Override default route mode by applying `central`, `tenant`, or `universal` middleware. -- Keep central and tenant routes explicit. -- Use `routes/tenant.php` for tenant application routes when the published stub is used. -- Use `universal` only for routes that intentionally work in both contexts. -- Do not use ad hoc request host checks when package middleware/route modes cover the behavior. - -## Tenant Asset Routes - -When `tenancy.routes` is true, the package loads `assets/routes.php` and registers: - -- `/tenancy/assets/{path?}` named `stancl.tenancy.asset` -- `/{tenant}/tenancy/assets/{path?}` named `tenant.stancl.tenancy.asset` for path identification, behind `tenant` middleware - -Guidance: +Use `routes/tenant.php` for tenant application routes when using the published stub. Use `CloneRoutesAsTenant` for package route integration instead of manually duplicating package routes. -- Disable `tenancy.routes` only if using external storage or a custom asset controller. -- If `filesystem.url_override` is used for local disks, run `php artisan tenants:link`. -- For global assets, use global asset helpers when `filesystem.asset_helper_override` is enabled. -- Prefer explicit `tenant_asset()` calls for tenant-specific assets when global packages call `asset()` internally. +When `tenancy.routes` is true, tenant asset routes are loaded from `assets/routes.php`. If `filesystem.url_override` is enabled for local public disks, run: -## Tenant Context API - -Use package APIs for context switching. Do not manually mutate Laravel globals. - -```php -tenancy()->initialize($tenant); - -tenancy()->end(); - -tenancy()->reinitialize(); - -$tenant->run(function () { - // Code runs in tenant context. -}); - -tenancy()->central(function () { - // Code runs in central context and then safely reverts. -}); +```bash +php artisan tenants:link ``` -Important runtime behavior: - -- `initialize()` accepts a tenant model, tenant ID, or tenant key string. -- `initialize()` ends the previous tenant context before switching to a different tenant. -- `run()` is atomic and reverts to the previous tenant or central context in `finally`. -- `central()` is atomic and restores the previous tenant context when finished. -- `reinitialize()` is useful when tenant attributes used by bootstrappers changed during a request. -- `bootstrapFeatures()` is idempotent for features already bootstrapped, but feature bootstrapping is irreversible. -- `tenancy()->find($id, $column = null, $withRelations = false)` uses the configured tenant model. -- `Tenancy::tenantKeyColumn()` reads `tenancy.models.tenant_key_column` and defaults to `tenant_id`. - -## Helpers And Facades - -Use package helpers and facades where appropriate: - -- `tenancy()` for the `Tenancy` singleton -- `tenant()` for current tenant access and tenant attribute lookup -- `central()` for central-context execution -- `globalCache()` or `GlobalCache` for cache that should remain central -- `global_asset()` when asset helper tenancy is enabled and the asset should remain global -- `tenant_asset()` for tenant-specific local assets -- `Tenancy` facade for the tenancy manager -- `GlobalCache` facade for central cache access - -## Bootstrappers - -Default bootstrappers in `assets/config.php`: - -- `DatabaseTenancyBootstrapper` -- `CacheTenancyBootstrapper` -- `FilesystemTenancyBootstrapper` -- `QueueTenancyBootstrapper` -- `DatabaseSessionBootstrapper` - -Optional bootstrappers: - -- `CacheTagsBootstrapper` -- `DatabaseCacheBootstrapper` -- `RedisTenancyBootstrapper` -- `TenantConfigBootstrapper` -- `RootUrlBootstrapper` -- `UrlGeneratorBootstrapper` -- `MailConfigBootstrapper` -- `BroadcastingConfigBootstrapper` -- `BroadcastChannelPrefixBootstrapper` -- `Bootstrappers\Integrations\FortifyRouteBootstrapper` -- `Bootstrappers\Integrations\ScoutPrefixBootstrapper` -- `PostgresRLSBootstrapper` -- `PersistentQueueTenancyBootstrapper` - -Bootstrapper rules: - -- Configure bootstrappers before writing application workarounds. -- Do not manually change DB connections, cache prefixes, filesystem roots, queue payloads, Redis prefixes, URL roots, or mail/broadcasting config when a bootstrapper owns it. -- `DatabaseCacheBootstrapper` must run after `DatabaseTenancyBootstrapper`. -- `RedisTenancyBootstrapper` needs phpredis and is for direct Redis calls, not normal cache-only Redis usage. -- `TenantConfigBootstrapper` should be preferred over the deprecated `TenantConfig` feature. -- `RootUrlBootstrapper` affects CLI URL generation in tenant context. -- `UrlGeneratorBootstrapper` is important for path/query-string route generation. -- If tenant state appears partially applied, inspect `tenancy()->getBootstrappers()` and `tenancy.bootstrappers`. - -## Database Tenancy - -Database config supports: - -- separate tenant databases -- PostgreSQL schema isolation -- permission-controlled tenant database users -- SQLite tenant database management -- MySQL/MariaDB tenant database management -- PostgreSQL tenant database management -- SQL Server tenant database management -- PostgreSQL RLS for single-database tenancy - -Rules: - -- `tenant` is a reserved dynamic connection name; do not use it as the template tenant connection name. -- Use `database.template_tenant_connection` for the tenant connection template. -- Use `database.tenant_host_connection_name` for temporary creation/deletion connection behavior. -- Tenant database names are generated as `prefix + tenant_id + suffix`. -- Use permission-controlled managers only when tenant-specific DB users are required. -- For PostgreSQL schemas, swap the pgsql manager to a schema manager instead of a database manager. -- `database.drop_tenant_databases_on_migrate_fresh` controls package behavior for `migrate:fresh` through the package override. - -## Central Migrations - -The base central migrations create: - -- `tenants` table with string primary `id`, timestamps, and nullable JSON `data` -- `domains` table with integer `id`, unique `domain`, tenant key column, timestamps, and foreign key to `tenants.id` - -Guidance: - -- Add custom tenant columns to the `tenants` migration before running it. -- If using auto-increment tenant IDs, set `models.id_generator` to `null` and update the tenants migration primary key accordingly. -- Keep domain values unique and lowercase behavior in mind; the default domain model converts domains to lowercase. -- The domains migration uses `Tenancy::tenantKeyColumn()` for the tenant foreign key. - -## Tenant Migrations And Seeders - -Tenant migrations are configured by `tenancy.migration_parameters`: - -- `--force` defaults to true -- `--path` defaults to `database/migrations/tenant` -- `--schema-path` defaults to `database/schema/tenant-schema.dump` -- `--realpath` defaults to true - -Tenant seeders are configured by `tenancy.seeder_parameters`: - -- `--class` defaults to `Database\Seeders\DatabaseSeeder` - -Rules: - -- Put tenant database migrations in `database/migrations/tenant` by default. -- Use `php artisan tenants:migrate` for tenant migrations. -- Use `php artisan tenants:rollback` for tenant rollback. -- Use `php artisan tenants:migrate-fresh` for tenant migrate fresh behavior. -- Use `php artisan tenants:seed` for tenant seeders. -- Do not run normal Laravel migrations expecting them to apply to tenant databases. -- Review `migration_parameters` and `seeder_parameters` before changing command calls. -- Use `--tenants=*` options when only specific tenants should be affected. -- Use `--skip-failing` deliberately when tenant migration failures should not stop the whole command. -- Use `--processes` only after confirming database and application code are safe for concurrent tenant operations. - -## Tenant Models - -Default tenant model traits include: - -- `VirtualColumn` -- `CentralConnection` -- `GeneratesIds` -- `HasInternalKeys` -- `TenantRun` -- `InitializationHelpers` -- `InvalidatesResolverCache` - -Default tenant model behavior: - -- table: `tenants` -- primary key: `id` -- guarded: empty array -- dispatches creating, created, saving, saved, updating, updated, deleting, deleted tenant events -- `Tenant::current()` returns current tenant -- `Tenant::currentOrFail()` throws if tenancy is not initialized -- tenant collection class: `Stancl\Tenancy\Database\TenantCollection` - -Guidance: - -- Use the configured tenant model from `config('tenancy.models.tenant')`. -- If replacing the tenant model, implement `Stancl\Tenancy\Contracts\Tenant`. -- Preserve package traits unless there is a specific tested reason to replace them. -- Use tenant model events and the published application service provider's pipelines for provisioning. - -## Domain Models - -Default domain model traits include: - -- `CentralConnection` -- `EnsuresDomainIsNotOccupied` -- `ConvertsDomainsToLowercase` -- `InvalidatesTenantsResolverCache` - -Default domain model behavior: - -- guarded: empty array -- belongs to the configured tenant model using `Tenancy::tenantKeyColumn()` -- dispatches creating, created, saving, saved, updating, updated, deleting, deleted domain events - -Guidance: - -- Use domains for domain/subdomain/domain-or-subdomain identification. -- Do not bypass domain uniqueness checks. -- Ensure domain cache invalidates when domain or tenant lookup data changes. - -## Single-Database Tenancy - -For single-database tenancy, use package traits and scopes instead of hand-written tenant filters. +## Database And Model Rules -Relevant concerns: +- Never use `tenant` as a template tenant connection name. +- Add tenant-specific migrations to `database/migrations/tenant`. +- Use tenant commands for tenant databases, not normal Laravel migration commands. +- If using auto-increment tenant IDs, set `models.id_generator` to `null` and update the tenants migration together. +- Custom tenant models must implement `Stancl\Tenancy\Contracts\Tenant`. +- Custom domain models must implement `Stancl\Tenancy\Contracts\Domain`. +- Use package traits for single-database tenancy instead of hand-written tenant filters. -- `BelongsToTenant` -- `FillsCurrentTenant` -- `TenantConnection` -- `CentralConnection` -- `TenantScope` -- `HasScopedValidationRules` -- `RLSModel` when PostgreSQL RLS is used +## Command Rules -Rules: - -- Apply tenant scoping consistently to tenant-owned models. -- Make tenant-owned models fill the current tenant key automatically where appropriate. -- Test central resources and tenant resources separately. -- For PostgreSQL RLS, configure `rls.user`, `rls.manager`, and `rls.session_variable_name`, then use package RLS commands/policies. - -## Resource Syncing - -Resource syncing assets include: - -- migration: `tenant_resources` -- events in `Stancl\Tenancy\ResourceSyncing\Events` -- listeners in `Stancl\Tenancy\ResourceSyncing\Listeners` -- traits/classes such as `ResourceSyncing`, `SyncMaster`, `Syncable`, `TenantPivot`, `TenantMorphPivot` - -Guidance: - -- Publish `resource-syncing-migrations` before using resource syncing. -- Keep central resource and tenant resource lifecycles explicit. -- Use package events/listeners from the published provider instead of custom sync loops. -- If soft-deleted synced resources are needed, configure the listener query scope in the application `TenancyServiceProvider` as shown in the stub. - -## User Impersonation - -User impersonation uses: - -- feature: `Stancl\Tenancy\Features\UserImpersonation` -- model: `Stancl\Tenancy\Database\Models\ImpersonationToken` -- migration: `tenant_user_impersonation_tokens` -- command: `tenants:purge-impersonation-tokens` - -Guidance: - -- Publish `impersonation-migrations` before enabling impersonation. -- Enable the `UserImpersonation` feature in `tenancy.features`. -- Run central migrations after publishing the impersonation migration. -- Purge expired tokens with `php artisan tenants:purge-impersonation-tokens`. -- Verify guard, redirect URL, remember flag, tenant match, and token TTL in tests. - -## Pending Tenants - -Pending tenant config: - -- `pending.include_in_queries` -- `pending.count`, defaulting to `TENANCY_PENDING_COUNT` or 5 - -Commands: - -- `php artisan tenants:pending-create` -- `php artisan tenants:pending-create --count=10` -- `php artisan tenants:pending-clear` -- `php artisan tenants:pending-clear --older-than-days=7` -- `php artisan tenants:pending-clear --older-than-hours=12` - -Rules: - -- If `pending.include_in_queries` is false, pending tenants are excluded from tenant queries and tenant commands. -- Use `withPending()`, `withoutPending()`, and `onlyPending()` intentionally when querying pending tenants. -- Do not assume pending tenants are included in migrations or seeds when config excludes them. - -## Tenant Lifecycle And Jobs - -The published application `TenancyServiceProvider` wires lifecycle events to job pipelines. - -Default `TenantCreated` pipeline: - -- `CreateDatabase` -- `MigrateDatabase` -- optional `SeedDatabase` -- optional `CreateStorageSymlinks` -- custom provisioning jobs - -Default deleting/deleted tenant pipelines: - -- `DeleteDomains` during `DeletingTenant` -- optional `DeleteTenantStorage` -- optional `RemoveStorageSymlinks` -- `DeleteDatabase` during `TenantDeleted` -- optional resource-syncing cleanup - -Rules: - -- Add tenant provisioning logic to the event pipeline rather than scattering it through controllers. -- Decide whether pipelines should be queued using `shouldBeQueued()`. -- Keep database creation, migration, seeding, storage, and domain deletion order explicit. -- Test tenant creation and deletion side effects. - -## Filesystem, Storage, And Assets - -Filesystem config controls: - -- tenant storage suffix base -- scoped disks -- local root overrides -- URL overrides -- file cache scoping -- file session scoping -- `storage_path()` suffixing -- `asset()` helper override - -Rules: - -- Keep `suffix_storage_path` enabled for local disk tenancy unless using external storage like S3 and the app is tested without it. -- Add local disks to both `filesystem.disks` and `filesystem.root_override` when root override is needed. -- Use `tenants:link` when `filesystem.url_override` maps local public disks. -- Use `tenants:link --remove` when removing tenant symlinks. -- Use `tenants:link --relative` only when relative symlinks are required by deployment. -- Use `tenants:link --force` when recreating existing symlinks deliberately. -- Be careful with `asset_helper_override`; packages that call `asset()` may unexpectedly become tenant-aware. - -## Cache, Global Cache, Sessions, And Redis - -Cache rules: - -- `CacheTenancyBootstrapper` scopes cache by changing `cache.prefix`. -- `CacheTagsBootstrapper` scopes using tags and is an alternative pattern. -- `DatabaseCacheBootstrapper` scopes database cache by tenant DB connection and must run after database tenancy. -- Use `GlobalCache`/`globalCache()` for cache that must remain central. -- If session driver is cache-based, `cache.scope_sessions` may add the session store to prefixed stores. - -Filesystem session rules: - -- `filesystem.scope_sessions` scopes file sessions under tenant storage. -- Use `ScopeSessions` middleware on tenant routes when session scoping is required. - -Redis rules: - -- `RedisTenancyBootstrapper` is for direct Redis facade/injected Redis usage. -- phpredis is required for Redis tenancy. -- Redis cache alone usually does not need `RedisTenancyBootstrapper`; cache scoping covers cache usage. - -## Queue Behavior - -Queue bootstrappers: - -- `QueueTenancyBootstrapper` -- `PersistentQueueTenancyBootstrapper` - -Rules: - -- Use the queue bootstrapper for tenant-aware queued jobs. -- Do not manually inject tenant IDs into every job if the package bootstrapper already handles payload/context. -- Test queued jobs from central context and tenant context. -- Use the persistent queue bootstrapper only when worker processes intentionally stay tenant-aware across jobs. - -## URL, Routes, Mail, Broadcasting, Fortify, Scout - -Use optional bootstrappers for integration-specific runtime config: - -- `RootUrlBootstrapper` for tenant root URL in CLI/context URL generation -- `UrlGeneratorBootstrapper` for tenant-aware route generation and tenant parameters -- `MailConfigBootstrapper` for tenant mail configuration -- `BroadcastingConfigBootstrapper` for tenant broadcaster credentials -- `BroadcastChannelPrefixBootstrapper` for tenant-prefixed broadcast channel names -- `FortifyRouteBootstrapper` for tenant Fortify route/redirect behavior -- `ScoutPrefixBootstrapper` for tenant-specific Scout prefixes - -Rules: - -- Configure these bootstrappers instead of writing custom service-provider mutations. -- Use the `overrideUrlInTenantContext()` hook in the published application provider for CLI root URL customization. -- For Livewire v3, follow the provider stub pattern to make the Livewire update route universal when needed. - -## Optional Features - -Features are bootstrapped independently from tenant initialization and are enabled via `tenancy.features`. - -Available features: - -- `Stancl\Tenancy\Features\UserImpersonation` -- `Stancl\Tenancy\Features\TelescopeTags` -- `Stancl\Tenancy\Features\CrossDomainRedirect` -- `Stancl\Tenancy\Features\ViteBundler` -- `Stancl\Tenancy\Features\DisallowSqliteAttach` -- `Stancl\Tenancy\Features\TenantConfig` - -Rules: - -- Inspect the feature class before assuming it affects tenant initialization. -- Prefer `TenantConfigBootstrapper` over deprecated `TenantConfig` feature for mapping tenant attributes into config. -- `DisallowSqliteAttach` protects SQLite use by blocking `ATTACH`; verify PHP/version-specific behavior in tests. -- `CrossDomainRedirect` adds redirect domain behavior. -- `TelescopeTags` adds tenant tags when tenancy is initialized. -- `ViteBundler` affects tenant-aware bundling behavior. - -## Artisan Commands - -Use package commands instead of ad hoc loops. - -Installation: - -```bash -php artisan tenancy:install --no-interaction -``` - -Tenant migrations and seeds: +Use package commands for tenant-aware operations: ```bash php artisan tenants:migrate -php artisan tenants:migrate --tenants=tenant-id -php artisan tenants:migrate --skip-failing php artisan tenants:rollback php artisan tenants:migrate-fresh php artisan tenants:seed -``` - -Run commands in tenant context: - -```bash php artisan tenants:run cache:clear -php artisan tenants:run "your:command" --tenants=tenant-id php artisan tenant:tinker -``` - -Tenant maintenance: - -```bash +php artisan tenants:dump +php artisan tenants:list php artisan tenants:down -php artisan tenants:down --redirect=/maintenance --retry=60 --refresh=60 --secret=secret --status=503 php artisan tenants:up -``` - -Tenant storage symlinks: - -```bash php artisan tenants:link -php artisan tenants:link --tenants=tenant-id -php artisan tenants:link --relative -php artisan tenants:link --force -php artisan tenants:link --remove -``` - -Pending tenants: - -```bash php artisan tenants:pending-create -php artisan tenants:pending-create --count=10 php artisan tenants:pending-clear -php artisan tenants:pending-clear --older-than-days=7 -php artisan tenants:pending-clear --older-than-hours=12 -``` - -Other package commands: - -```bash -php artisan tenants:list -php artisan tenant:dump php artisan tenants:purge-impersonation-tokens php artisan tenants:rls -php artisan tenants:rls --force ``` -Command rules: - -- Use `php artisan list` or `php artisan help ` to confirm available options in the installed app. -- Use `--tenants=*` when affecting only selected tenants. -- Use tenant commands for tenant databases, not central Laravel migration commands. -- Use `tenants:run` for existing Artisan commands that need tenant context. - -## Maintenance Mode - -Maintenance commands: - -- `tenants:down` -- `tenants:up` - -Related middleware: - -- `CheckTenantForMaintenanceMode` - -Rules: - -- Use tenant maintenance mode when only tenant apps should be unavailable. -- Test bypass secret, redirect, retry, refresh, and status code behavior when configured. -- Do not confuse tenant maintenance with Laravel global maintenance mode. - -## PostgreSQL RLS +Use `php artisan help ` to confirm options in the installed app. Use `--tenants=*` when only selected tenants should be affected. -RLS config and classes: +## Feature Rules -- `rls.manager` -- `rls.user.username` -- `rls.user.password` -- `rls.session_variable_name` -- `PostgresRLSBootstrapper` -- `RLSModel` -- `TableRLSManager` -- `TraitRLSManager` -- `tenants:rls` +Enable optional features through `tenancy.features` only when needed: -Rules: +- `UserImpersonation` +- `TelescopeTags` +- `CrossDomainRedirect` +- `ViteBundler` +- `DisallowSqliteAttach` +- `TenantConfig` -- Use PostgreSQL and single-database tenancy for RLS. -- Set a namespaced session variable name such as `my.current_tenant`. -- Configure one tenant database user used for all tenants, not one user per tenant. -- Run `php artisan tenants:rls` to create policies/user. -- Use `--force` only when policies should be recreated even if they already exist. -- Test policy coverage on every tenant-owned table. +Prefer `TenantConfigBootstrapper` over the deprecated `TenantConfig` feature for mapping tenant attributes into config. Publish and migrate impersonation/resource-syncing migrations before enabling those workflows. -## Testing Guidelines +## Testing Rules -Every tenancy behavior change must be tested programmatically. +Every tenancy behavior change needs tests covering the relevant central and tenant contexts. -Test at minimum: +Verify the task-specific behavior: -- installation artifacts exist when testing installer behavior -- central routes remain central +- tenant identification success and failure +- central routes stay central - tenant routes initialize tenancy -- wrong central/tenant access is rejected -- domain, subdomain, path, request-data, or origin-header identification resolves the expected tenant -- identification failure throws or handles the expected package exception -- current tenant is available through `tenant()`, facade, and contract binding when expected -- database connection switches and reverts -- cache keys are scoped or global as intended -- filesystem roots, URLs, and storage paths are scoped as intended -- queues reinitialize tenant context around jobs -- sessions are scoped when configured -- URL generation uses tenant route names and parameters correctly -- tenant migrations, rollbacks, and seeds affect the expected tenants -- tenant creation pipelines create DBs, migrate DBs, seed DBs, create storage, and attach domains as configured -- tenant deletion pipelines delete domains, database, storage, symlinks, and resource mappings as configured -- optional features behave only when enabled -- central context is restored after `run()` and `central()` callbacks - -Use existing package tests as examples: - -- `tests/AutomaticModeTest.php` -- `tests/ManualModeTest.php` -- `tests/RouteMiddlewareTest.php` -- `tests/PathIdentificationTest.php` -- `tests/RequestDataIdentificationTest.php` -- `tests/OriginHeaderIdentificationTest.php` -- `tests/TenantAssetTest.php` -- `tests/CommandsTest.php` -- `tests/QueueTest.php` -- `tests/SingleDatabaseTenancyTest.php` -- `tests/RLS/*` -- `tests/ResourceSyncingTest.php` -- `tests/TenantUserImpersonationTest.php` - -## Implementation Rules - -- Decide identification first. -- Decide database isolation before creating application models. -- Decide bootstrappers before writing workaround code. -- Keep central and tenant routes explicit. -- Use package commands for tenant-aware operations. -- Use package context APIs instead of manual context mutation. -- Use package models, contracts, traits, events, and jobs before adding custom abstractions. -- Keep provisioning in the application `TenancyServiceProvider` event pipelines. -- Review config before changing application code. -- Test both central and tenant behavior for every change. +- database, cache, filesystem, queue, session, URL, mail, or broadcasting scoping +- tenant migrations, seeds, and command options +- tenant lifecycle jobs and event pipelines +- optional features only when enabled +- context restoration after `run()` and `central()` callbacks ## Common Pitfalls -- Running normal `php artisan migrate` and expecting tenant DBs to migrate -- Forgetting to run `php artisan tenants:migrate` after tenants exist -- Skipping `routes/tenant.php` or the application `TenancyServiceProvider` -- Using the reserved connection name `tenant` as a template connection -- Mixing central and tenant routes without route modes or middleware -- Using `PreventAccessFromUnwantedDomains` with unsupported non-domain identification -- Forgetting `central_domains` for domain/subdomain apps -- Manually changing DB/cache/filesystem/queue/session/url state instead of using bootstrappers -- Enabling `asset_helper_override` without checking third-party packages that call `asset()` +- Running normal `php artisan migrate` and expecting tenant databases to migrate +- Forgetting `php artisan tenants:migrate` after tenants exist +- Mixing central and tenant routes without route mode decisions +- Using `PreventAccessFromUnwantedDomains` with non-domain identification +- Forgetting `identification.central_domains` for domain/subdomain apps +- Manually changing framework state instead of using bootstrappers - Enabling resolver caching without invalidation coverage -- Forgetting to publish impersonation or resource-syncing migrations before enabling those features -- Assuming pending tenants are included when `pending.include_in_queries` excludes them -- Using auto-increment tenant IDs without considering enumeration risk -- Adding tenant-owned models in single-database tenancy without tenant scoping -- Not verifying central context after tenant context work +- Enabling `filesystem.asset_helper_override` without checking third-party asset calls +- Using single-database tenancy without consistent tenant scoping +- Skipping central-context tests after adding tenant behavior diff --git a/resources/boost/skills/laravel-tenancy/SKILL.md b/resources/boost/skills/laravel-tenancy/SKILL.md index b60bcdbb7..edb8e8017 100644 --- a/resources/boost/skills/laravel-tenancy/SKILL.md +++ b/resources/boost/skills/laravel-tenancy/SKILL.md @@ -84,11 +84,11 @@ That command publishes the config, routes, provider, core migrations, and create ## Core Working Pattern 1. Install the package and inspect `config/tenancy.php`. -2. Decide the tenant identification strategy first: domain, subdomain, domain-or-subdomain, path, request data, or origin header. -3. Keep central and tenant routes explicit. Use the package middleware and route modes instead of ad hoc request checks. -4. Choose the minimum bootstrapper set that matches the app's infrastructure. -5. For data isolation, decide between multi-database tenancy, single-database tenancy, or PostgreSQL RLS before writing application models. -6. Test both central and tenant contexts. +1. Decide the tenant identification strategy first: domain, subdomain, domain-or-subdomain, path, request data, or origin header. +1. Keep central and tenant routes explicit. Use the package middleware and route modes instead of ad hoc request checks. +1. Choose the minimum bootstrapper set that matches the app's infrastructure. +1. For data isolation, decide between multi-database tenancy, single-database tenancy, or PostgreSQL RLS before writing application models. +1. Test both central and tenant contexts. ## Tenant Identification diff --git a/resources/boost/skills/laravel-tenancy/references/installation.md b/resources/boost/skills/laravel-tenancy/references/installation.md index 1165fdb15..cce40ec7d 100644 --- a/resources/boost/skills/laravel-tenancy/references/installation.md +++ b/resources/boost/skills/laravel-tenancy/references/installation.md @@ -19,13 +19,13 @@ Use this when installing or auditing `stancl/tenancy` setup. composer require stancl/tenancy ``` -2. Run the installer non-interactively. +1. Run the installer non-interactively. ```bash php artisan tenancy:install --no-interaction ``` -3. Confirm these files exist: +1. Confirm these files exist: - `config/tenancy.php` - `routes/tenant.php` @@ -34,19 +34,19 @@ php artisan tenancy:install --no-interaction - `database/migrations/2019_09_15_000020_create_domains_table.php` - `database/migrations/tenant` -4. Review `config/tenancy.php` before running migrations. +1. Review `config/tenancy.php` before running migrations. -5. Run central migrations. +1. Run central migrations. ```bash php artisan migrate ``` -6. Add tenant migrations to `database/migrations/tenant`. +1. Add tenant migrations to `database/migrations/tenant`. -7. Create tenants and domains according to the identification strategy. +1. Create tenants and domains according to the identification strategy. -8. Run tenant migrations. +1. Run tenant migrations. ```bash php artisan tenants:migrate diff --git a/resources/boost/skills/laravel-tenancy/references/migrations-commands.md b/resources/boost/skills/laravel-tenancy/references/migrations-commands.md index 7df8c4656..73bbfa177 100644 --- a/resources/boost/skills/laravel-tenancy/references/migrations-commands.md +++ b/resources/boost/skills/laravel-tenancy/references/migrations-commands.md @@ -36,7 +36,7 @@ php artisan tenants:seed php artisan tenants:run cache:clear php artisan tenant:tinker php artisan tenants:list -php artisan tenant:dump +php artisan tenants:dump ``` ## Maintenance And Storage