## File: README.md

<h1>[Tenancy for Laravel &mdash; stancl/tenancy](https://tenancyforlaravel.com)</h1>

### *Automatic multi-tenancy for your Laravel app.*

You won't have to change a thing in your application's code.

- :heavy_check_mark: No model traits to change database connection
- :heavy_check_mark: No replacing of Laravel classes (`Cache`, `Storage`, ...) with tenancy-aware classes
- :heavy_check_mark: Built-in tenant identification based on hostname (including second level domains)

### [Documentation](https://v4.tenancyforlaravel.com)

Documentation can be found here: https://v4.tenancyforlaravel.com

### [Need help?](https://github.com/stancl/tenancy/blob/3.x/SUPPORT.md)

### Credits

- Package created by [Samuel Štancl](https://twitter.com/samuelstancl)
- Design help & current logo by [Florian Karsten](https://floriankarsten.com/)

---

## File: CLAUDE.md

# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

You're working on version 4, which is the unreleased `master` branch with no tags at the moment.

## Development Commands

### Testing
- `composer test` - Run tests without coverage using Docker
- `./test tests/TestFile.php` - Run an entire test file
- `./t 'test name'` - Run a specific test
- You can append `-v` to get a full stack trace if a test fails due to an exception

### Code Quality
- `composer phpstan` - Run PHPStan static analysis (level 8)
- `composer cs` - Fix code style using PHP CS Fixer

### Docker Development
- `composer docker-up` - Start Docker environment
- `composer docker-down` - Stop Docker environment
- `composer docker-restart` - Restart Docker environment

## Architecture Overview

**Tenancy for Laravel** is a multi-tenancy package that automatically handles tenant isolation without requiring changes to application code.

### Core Components

**Central Classes:**
- `Tenancy` - Main orchestrator class managing tenant context and lifecycle
- `TenancyServiceProvider` (NOT the stub) - Registers services, commands, and bootstrappers
- `Tenant` (model) - Represents individual tenants with domains and databases
- `Domain` (model) - Maps domains/subdomains to tenants

**Tenant Identification:**
- **Resolvers** (`src/Resolvers/`) - Identify tenants by domain, path, or request data - this data comes from middleware
- **Middleware** (`src/Middleware/`) - Middleware that calls resolvers and tries to initialize tenancy based on information from a request
- **Cached resolvers** - Cached wrapper around resolvers to avoid querying the central database

**Tenancy Bootstrappers (`src/Bootstrappers/`):**
- `DatabaseTenancyBootstrapper` - Switches database connections
- `CacheTenancyBootstrapper` - Isolates cache by tenant
- `FilesystemTenancyBootstrapper` - Manages tenant-specific storage
- `QueueTenancyBootstrapper` - Ensures queued jobs run in correct tenant context
- `RedisTenancyBootstrapper` - Prefixes Redis keys by tenant

**Database Management:**
- **DatabaseManager** - Creates/deletes tenant databases and users
- **TenantDatabaseManagers** - Database-specific implementations (MySQL, PostgreSQL, SQLite, SQL Server)
- **Row Level Security (RLS)** - PostgreSQL-based tenant isolation using policies

**Advanced Features:**
- **Resource Syncing** - Sync central models to tenant databases
- **User Impersonation** - Admin access to tenant contexts
- **Cross-domain redirects** - Handle multi-domain tenant setups
- **Telescope integration** - Tag entries by tenant

### Key Patterns

**Tenant Context Management:**
```php
tenancy()->initialize($tenant);           // Switch to tenant
tenancy()->run($tenant, $callback);      // Atomic tenant execution
tenancy()->runForMultiple($tenants, $callback); // Batch operations
tenancy()->central($callback);           // Run in central context
```

**Tenant Identification Flow:**
1. Middleware identifies tenant from request (domain/subdomain/path)
2. Resolver fetches tenant model from identification data
3. Tenancy initializes and bootstrappers configure tenant context
4. Application runs with tenant-specific database/cache/storage

**Route Middleware Groups:**
All of these work as flags, i.e. middleware groups that are empty arrays with a purely semantic use.
- `tenant` - Routes requiring tenant context
- `central` - Routes for central/admin functionality
- `universal` - Routes working in both contexts
- `clone` - Tells route cloning logic to clone the route

### Early Identification

**Early identification** ensures tenancy is initialized before controller instantiation, which is critical for certain scenarios.

**When needed:**
- Controllers using constructor dependency injection
- Integration with packages that inject dependencies in constructors

**The Problem:**
Laravel executes controller constructors and route model binding before route-level middleware runs, causing services to use central context instead of tenant context.

**Solutions:**
1. **Avoid Constructor Injection** - Use method injection instead
2. **Laravel's Native Solution** - Use controllers that implement `HasMiddleware` interface
3. **Kernel Identification** - Add middleware to HTTP Kernel's global stack:

```php
// In HttpKernel.php
protected $middleware = [
    \Stancl\Tenancy\Middleware\InitializeTenancyByDomain::class,
    // other middleware...
];
```

Note you also need to flag the route with the `'tenant'` middleware if default route mode (set in config) isn't set to TENANT.

**Benefits:**
- Constructor dependency injection receives tenant-aware services
- Seamless integration with existing Laravel applications

### Testing Environment

Tests use Docker with MySQL/PostgreSQL/Redis. The `./test` script runs Pest tests inside containers with proper database isolation.

`./t 'test name'` is equivalent to `./test --filter 'test name'`

**Key test patterns:**
- Database preparation and cleanup between tests
- Multi-database scenarios (central + tenant databases)
- Middleware and identification testing
- Resource syncing validation

#### Debugging tests

If you're developing some feature and you encounter `SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry` errors, it's likely that some PHP errors were thrown in past test runs and prevented the test cleanup from running properly.

To fix this, simply delete the database memory by shutting down containers and starting them again: `docker compose restart mysql`.

Same thing for `SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared`. Just run `docker compose restart mysql`.

### Configuration

Central config in `config/tenancy.php` controls:
- Tenant/domain model classes
- Database connection settings
- Enabled bootstrappers and features
- Identification middleware and resolvers
- Cache and storage prefixes

## Subagents

Subagents may work in separate checkouts to make concurrent changes: jj workspaces under `.workspaces/`, or git
worktrees (e.g. under `.claude/worktrees/`). If your working directory is one of these, you're in a separate
checkout, and Docker Compose treats it as a separate project. `./test` won't reach the main checkout's
containers, and those containers mount the main checkout, so they wouldn't test your changes anyway.

Decide whether you need to run tests yourself:

- If you're confident in your changes, don't start containers. Say which tests should be run, and the parent
  agent runs them after integrating your work.
- If the work would benefit from running tests several times, start your own Compose project. It uses about
  2 GB of RAM while idle. The `test` image is built for your project on first start; when the build cache is
  warm this takes seconds and practically no disk space, but after a cache prune it's a full rebuild that
  takes a few minutes.

To set up and use your own project:

1. If your directory name doesn't already start with `tenancy-`, give the project a unique name by writing
   it to `.env` (gitignored), so it applies to every `docker compose` and `./test` call:
   `echo "COMPOSE_PROJECT_NAME=tenancy-$(basename "$PWD")" > .env`
2. `docker compose up -d`
3. `docker compose exec test composer install` (`vendor/` isn't shared between checkouts)
4. Run tests as described above. The full suite takes about 2 minutes, so run only the relevant tests unless
   the change is likely to affect many of them.
5. When you're done, including after failures: `docker compose down -v --rmi local`. This removes the
   containers, their database volumes, and the image your project built.

When creating a workspace for a subagent, the parent agent should name its directory `tenancy-<something-unique>`
(e.g. `.workspaces/tenancy-foo`). The directory name is the default Compose project name, so this prevents
collisions with other repos' workspaces even if the subagent doesn't set `COMPOSE_PROJECT_NAME`.

---

## File: composer.json

{
    "name": "stancl/tenancy",
    "description": "Automatic multi-tenancy for your Laravel application.",
    "keywords": [
        "laravel",
        "multi-tenancy",
        "multitenancy",
        "multi-database",
        "tenancy"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "Samuel Štancl",
            "email": "samuel@archte.ch"
        }
    ],
    "require": {
        "php": "^8.4",
        "ext-json": "*",
        "illuminate/support": "^12.0|^13.0",
        "laravel/tinker": "^2.0|^3.0",
        "ramsey/uuid": "^4.7.3",
        "stancl/jobpipeline": "2.0.0-rc7",
        "stancl/virtualcolumn": "^1.5.0",
        "spatie/invade": "*",
        "laravel/prompts": "0.*"
    },
    "require-dev": {
        "laravel/framework": "^13.0",
        "orchestra/testbench": "^10.0|^11.0",
        "league/flysystem-aws-s3-v3": "^3.12.2",
        "doctrine/dbal": "^3.6.0",
        "spatie/valuestore": "^1.2.5",
        "pestphp/pest": "^4.0",
        "larastan/larastan": "^3.0",
        "league/flysystem-path-prefixing": "^3.0",
        "friendsofphp/php-cs-fixer": "^3.95"
    },
    "autoload": {
        "psr-4": {
            "Stancl\\Tenancy\\": "src/"
        },
        "files": [
            "src/helpers.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "Stancl\\Tenancy\\Tests\\": "tests/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "Stancl\\Tenancy\\TenancyServiceProvider"
            ],
            "aliases": {
                "Tenancy": "Stancl\\Tenancy\\Facades\\Tenancy",
                "GlobalCache": "Stancl\\Tenancy\\Facades\\GlobalCache"
            }
        }
    },
    "scripts": {
        "docker-up": "docker compose up -d",
        "docker-down": "docker compose down",
        "docker-restart": "docker compose down && docker compose up -d",
        "docker-rebuild": [
            "Composer\\Config::disableProcessTimeout",
            "docker compose up -d --no-deps --build"
        ],
        "docker-rebuild-with-xdebug": [
            "Composer\\Config::disableProcessTimeout",
            "XDEBUG_ENABLED=true docker compose up -d --no-deps --build"
        ],
        "docker-m1": "ln -s docker-compose-m1.override.yml docker-compose.override.yml",
        "php85-patch": [
            "php -r '$file=\"vendor/orchestra/testbench-core/laravel/config/database.php\"; file_put_contents($file, str_replace(\"PDO::MYSQL_ATTR_SSL_CA\", \"Pdo\\\\Mysql::ATTR_SSL_CA\", file_get_contents($file)));'"
        ],
        "testbench-unlink": "rm ./vendor/orchestra/testbench-core/laravel/vendor",
        "testbench-link": "ln -s /var/www/html/vendor ./vendor/orchestra/testbench-core/laravel/vendor",
        "testbench-repair": "mkdir -p ./vendor/orchestra/testbench-core/laravel/storage/framework/sessions && mkdir -p ./vendor/orchestra/testbench-core/laravel/storage/framework/views && mkdir -p ./vendor/orchestra/testbench-core/laravel/storage/framework/cache",
        "coverage": "open coverage/phpunit/html/index.html",
        "phpstan": "vendor/bin/phpstan --memory-limit=256M",
        "phpstan-pro": "vendor/bin/phpstan --memory-limit=256M --pro",
        "cs": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php",
        "test": [
            "Composer\\Config::disableProcessTimeout",
            "./test --no-coverage"
        ],
        "test-full": [
            "Composer\\Config::disableProcessTimeout",
            "./test"
        ],
        "act": [
            "Composer\\Config::disableProcessTimeout",
            "act -j tests --matrix 'laravel:^11.0'"
        ],
        "act-input": [
            "Composer\\Config::disableProcessTimeout",
            "act -j tests --matrix 'laravel:^11.0' --input"
        ]
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "config": {
        "allow-plugins": {
            "pestphp/pest-plugin": true
        }
    }
}

---

## File: CONTRIBUTING.md

# Contributing

## Code style

php-cs-fixer will fix code style violations in your pull requests.

To run it locally, use `composer cs`.

## Running tests

Run `composer docker-up` to start the containers. Then run `composer test` to run the tests.

If you need to pass additional flags to phpunit, use `composer test --`, e.g. `composer test -- --filter="foo"`. Alternatively, you can use `./test --filter="foo"`

If you want to run a specific test (or test file), you can also use `./t 'name of the test'`. This is equivalent to `./test --no-coverage --filter 'name of the test'` (`--no-coverage` speeds up the execution time).

When you're done testing, run `composer docker-down` to shut down the containers.

### Debugging tests

If you're developing some feature and you encounter `SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry` errors, it's likely that some PHP errors were thrown in past test runs and prevented the test cleanup from running properly.

To fix this, simply delete the database memory by shutting down containers and starting them again: `composer docker-down && composer docker-up`.

Same thing for `SQLSTATE[HY000]: General error: 1615 Prepared statement needs to be re-prepared`.

### Docker on Apple Silicon

Run `composer docker-m1` to symlink `docker-compose-m1.override.yml` to `docker-compose.override.yml`. This will reconfigure a few services in the docker compose config to be compatible with M1.

2025 note: By now only MSSQL doesn't have good M1 support. The override also started being a bit problematic, having issues with starts, often requiring multiple starts. This often makes the original image in docker-compose more stable, even if it's amd64-only. With Rosetta enabled, you should be able to use it without issues.

### Coverage reports

To run tests and generate coverage reports, use `composer test-full`.

To view the coverage reports in your browser, use `composer coverage` (works on macOS; on other operating systems you can manually open `coverage/phpunit/html/index.html` in your browser).

### Rebuilding containers

If you need to rebuild the container for any reason (e.g. a change in `Dockerfile`), you can use `composer docker-rebuild`.

## PHPStan

Use `composer phpstan` to run our phpstan suite.

## PhpStorm

Create `.env` with `PROJECT_PATH=/full/path/to/this/directory`. Configure a Docker-based interpreter for tests (with exec, not run).

If you want to use XDebug, use `composer docker-rebuild-with-xdebug`.

## PHP 8.5

To use PHP 8.5 during development, run:
- `PHP_VERSION=8.5.0 composer docker-rebuild` to build the `test` container with PHP 8.5
- `composer php85-patch` to get rid of some deprecation errors coming from `config/database.php` from within testbench-core

---

## File: docker-compose-m1.override.yml

services:
  mssql:
    image: mcr.microsoft.com/azure-sql-edge

---

## File: docker-compose.yml

services:
    test:
        build:
            context: .
            args:
                PHP_VERSION: ${PHP_VERSION:-8.4}
                XDEBUG_ENABLED: ${XDEBUG_ENABLED:-false}
        depends_on:
            mysql:
                condition: service_healthy
            mysql2:
                condition: service_healthy
            postgres:
                condition: service_healthy
            redis:
                condition: service_healthy
            mssql:
               condition: service_healthy
            memcached:
                condition: service_healthy
            dynamodb:
                condition: service_healthy
        volumes:
            - .:${PROJECT_PATH:-$PWD}:cached
        working_dir: ${PROJECT_PATH:-$PWD}
        environment:
            DOCKER: 1
            DB_PASSWORD: password
            DB_USERNAME: root
            DB_DATABASE: main
            TENANCY_TEST_REDIS_HOST: redis
            TENANCY_TEST_MYSQL_HOST: mysql
            TENANCY_TEST_PGSQL_HOST: postgres
            TENANCY_TEST_SQLSRV_HOST: mssql
            TENANCY_TEST_SQLSRV_USERNAME: sa
            TENANCY_TEST_SQLSRV_PASSWORD: P@ssword
        extra_hosts:
            - "host.docker.internal:host-gateway"
        stdin_open: true
        tty: true
    mysql:
        image: mysql:8
        environment:
            MYSQL_ROOT_PASSWORD: password
            MYSQL_DATABASE: main
            MYSQL_USER: user # redundant
            MYSQL_PASSWORD: password
        healthcheck:
            test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
            timeout: 10s
            retries: 10
        tmpfs:
            - /var/lib/mysql
    mysql2:
        image: mysql:8
        environment:
            MYSQL_ROOT_PASSWORD: password
            MYSQL_DATABASE: main
            MYSQL_USER: user # redundant
            MYSQL_PASSWORD: password
        healthcheck:
            test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
            timeout: 10s
            retries: 10
        tmpfs:
            - /var/lib/mysql
    postgres:
        image: postgres:16
        environment:
            POSTGRES_PASSWORD: password
            POSTGRES_USER: root # superuser name
            POSTGRES_DB: main
        healthcheck:
            test: ["CMD-SHELL", "pg_isready -U postgres"]
            interval: 10s
            timeout: 5s
            retries: 5
        tmpfs:
            - /var/lib/postgresql/data
    mssql:
        image: mcr.microsoft.com/mssql/server:2022-latest
        environment:
            ACCEPT_EULA: "Y"
            SA_PASSWORD: "P@ssword" # must be the same as TENANCY_TEST_SQLSRV_PASSWORD
        healthcheck: # https://github.com/Microsoft/mssql-docker/issues/133#issuecomment-1995615432
            test: timeout 2 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/1433'
            interval: 10s
            timeout: 10s
            retries: 10
    redis:
        image: redis:alpine
        healthcheck:
            test: ["CMD", "redis-cli", "ping"]
            interval: 1s
            timeout: 3s
            retries: 20
    memcached:
        image: memcached:alpine
        healthcheck:
            test: ["CMD-SHELL", "echo version | nc localhost 11211 | grep -q VERSION"]
            interval: 1s
            timeout: 3s
            retries: 20
    dynamodb:
        image: amazon/dynamodb-local:latest
        healthcheck:
            test: ["CMD-SHELL", "cat < /dev/null > /dev/tcp/127.0.0.1/8000"]
            interval: 1s
            timeout: 3s
            retries: 20

---

## File: DONATIONS.md

# Donations

Any donations will be greatly appreciated and help ensure that the package is developed and maintained in the future.

To donate, see the donation page on the website: [https://tenancyforlaravel.com/donate](https://tenancyforlaravel.com/donate).

---

## File: INTERNAL.md

# Internal development notes

## Updating the docker image used by the GH action

1. Login in to Docker Hub: `docker login -u archtechx`
1. Shut down containers: `composer docker-down`
1. Build the image: `DOCKER_DEFAULT_PLATFORM=linux/amd64 docker compose build --no-cache`
1. Start containers again, using the amd64 image for the `test` service: `composer docker-up`
1. Verify that tests pass on the new image: `composer test`
1. Tag a new image: `docker tag tenancy-test archtechx/tenancy:latest`
1. Push the image: `docker push archtechx/tenancy:latest`
1. Optional: Rebuild the image again locally for arm64: `composer docker-rebuild`

## Debugging GitHub Actions

The `ci.yml` workflow includes support for [act](https://github.com/nektos/act).

To run all tests using act, run `composer act`. To run only certain tests using act, use `composer act-input "FILTER='some test name'"` or `composer act -- --input "FILTER='some test name'"`.

Helpful note: GHA doesn't mount the project at /var/www/html like the docker compose setup does. This can be observed in act where the inner container's filesystem structure will match the host.

Also, for debugging act you can just add a job that does `sleep 1h` and then `docker ps` + `docker exec -it  bash`.

---

## File: SUPPORT.md

# Get Support

If you need help with implementing the package, you can join our community [Discord server](https://archte.ch/discord) and ask in `#help`.

If you're interested in paid consulting from the maintainer, see the [contact page](https://tenancyforlaravel.com/contact/) on our website.

---

## File: doctum/composer.json

{
    "require": {
        "code-lts/doctum": "v5.5.x-dev"
    },
    "scripts": {
        "doctum": "./vendor/bin/doctum.php update doctum.php --output-format=github --force --ignore-parse-errors --no-ansi --no-progress -v"
    }
}