From a800170b489d9589a94e72c908007e934a39af29 Mon Sep 17 00:00:00 2001 From: eramitgupta Date: Thu, 28 May 2026 15:30:59 +0530 Subject: [PATCH] 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 00000000..673795d4 --- /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 00000000..aa7466ec --- /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.