PHPackages                             rasuvaeff/yii3-tenancy - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. rasuvaeff/yii3-tenancy

ActiveLibrary[Framework](/categories/framework)

rasuvaeff/yii3-tenancy
======================

Multi-tenancy core for Yii3: tenant resolution, request context, and scoping primitives

v1.1.1(3w ago)02201BSD-3-ClausePHPPHP 8.3 - 8.5CI passing

Since Jul 4Pushed 1mo agoCompare

[ Source](https://github.com/rasuvaeff/yii3-tenancy)[ Packagist](https://packagist.org/packages/rasuvaeff/yii3-tenancy)[ Docs](https://github.com/rasuvaeff/yii3-tenancy)[ RSS](/packages/rasuvaeff-yii3-tenancy/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (34)Versions (4)Used By (1)

rasuvaeff/yii3-tenancy
======================

[](#rasuvaeffyii3-tenancy)

[![Stable Version](https://camo.githubusercontent.com/13c3a801b8a900256c79ad24b1997efbcae7cbaada7621084339a38ff29254ec/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7261737576616566662f796969332d74656e616e63793f6c6162656c3d737461626c6526736f72745f73656d7665723d31)](https://packagist.org/packages/rasuvaeff/yii3-tenancy)[![Total Downloads](https://camo.githubusercontent.com/5f3f2fee6b1a0ee272a73d7c77936c7a1bc3cec197dc8302557004a00ccfc7db/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7261737576616566662f796969332d74656e616e6379)](https://packagist.org/packages/rasuvaeff/yii3-tenancy)[![Build](https://camo.githubusercontent.com/03f3438fec28ba9595dd51e471ef13a4760952a2c2f58f13dc802c14b398402b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f7261737576616566662f796969332d74656e616e63792f6275696c642e796d6c3f6272616e63683d6d6173746572)](https://github.com/rasuvaeff/yii3-tenancy/actions)[![Static analysis](https://camo.githubusercontent.com/711a50c966510f46d51168cc15fbf272087fab4b65968b67572bc156fe35451b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f7261737576616566662f796969332d74656e616e63792f7374617469632d616e616c797369732e796d6c3f6272616e63683d6d6173746572266c6162656c3d737461746963253230616e616c79736973)](https://github.com/rasuvaeff/yii3-tenancy/actions)[![Psalm level](https://camo.githubusercontent.com/96f0376e917393dc04f5e50ef34fa74f202272a3100aa70c66ece3282f6a2e36/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7073616c6d2d6c6576656c253230312d3134314634383f6c6f676f3d7073616c6d266c6f676f436f6c6f723d7768697465)](https://github.com/rasuvaeff/yii3-tenancy/blob/master/psalm.xml)[![PHP](https://camo.githubusercontent.com/ee719dbb29765bf00b2f04185d6badd8eddb1401820ae67414f92e1434f9325c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f7261737576616566662f796969332d74656e616e63792f706870)](https://packagist.org/packages/rasuvaeff/yii3-tenancy)[![License](https://camo.githubusercontent.com/f7d3c956d9f8506db5f0d61deec9106a475ddde5244b027811da9ba1b10afc81/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7261737576616566662f796969332d74656e616e6379)](LICENSE.md)

Multi-tenancy core for Yii3: tenant resolution from the request (header/subdomain/path), a request-scoped `CurrentTenant` context, and scoping primitives. Deliberately **no ORM auto-scoping magic** — explicit primitives and recipes instead.

> **Using an AI coding assistant?** [llms.txt](llms.txt) contains a compact API reference you can share with the model. Contributors: see [AGENTS.md](AGENTS.md).

Requirements
------------

[](#requirements)

RequirementVersionPHP8.3 – 8.5PSR-7 / PSR-15 / PSR-17 / PSR-16any implementationInstallation
------------

[](#installation)

```
composer require rasuvaeff/yii3-tenancy
```

For persistent tenant storage add the DB backend (planned: `rasuvaeff/yii3-tenancy-db`) or bind your own `TenantProvider`.

Usage
-----

[](#usage)

### Resolution middleware

[](#resolution-middleware)

```
use Rasuvaeff\Yii3Tenancy\ConfigTenantProvider;
use Rasuvaeff\Yii3Tenancy\HeaderTenantResolver;
use Rasuvaeff\Yii3Tenancy\RequestCurrentTenant;
use Rasuvaeff\Yii3Tenancy\TenantResolutionMiddleware;

$middleware = new TenantResolutionMiddleware(
    resolver: new HeaderTenantResolver(),                 // X-Tenant-Id
    provider: new ConfigTenantProvider([
        'acme' => ['name' => 'Acme Inc', 'attributes' => ['plan' => 'pro']],
    ]),
    currentTenant: $requestCurrentTenant,                 // shared RequestCurrentTenant
    responseFactory: $psr17Factory,
);
```

Place it in the middleware pipeline **before** authentication — the tenant usually determines the user store. On success the tenant is published twice:

- `CurrentTenant` service (constructor-inject it anywhere);
- `Tenant::class` request attribute.

Unresolved/unknown key → `404`; suspended tenant → `403`. Both are policies (`TenantPolicy::Reject` | `TenantPolicy::PassThrough`).

### Resolvers

[](#resolvers)

ResolverSourceExample`HeaderTenantResolver``X-Tenant-Id` header (configurable)`X-Tenant-Id: acme``SubdomainTenantResolver`first label under a configured base domain`acme.example.com``PathTenantResolver`first segment after a configured prefix`/t/acme/dashboard``CompositeTenantResolver`chain, first non-null winsheader, then subdomainEvery resolver validates the extracted key against `Tenant::isValidId()`(`/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/`) and returns `null` on mismatch — keys taken from requests are untrusted input. Nested subdomains (`a.b.example.com`) and lookalike hosts (`acmeexample.com`) resolve to `null`.

### Reading the current tenant

[](#reading-the-current-tenant)

```
use Rasuvaeff\Yii3Tenancy\CurrentTenant;

final readonly class InvoiceService
{
    public function __construct(private CurrentTenant $currentTenant) {}

    public function create(): void
    {
        $tenantId = $this->currentTenant->get()->id;   // throws if unresolved
        $plan = $this->currentTenant->get()->attributes['plan'] ?? 'free';
    }
}
```

For console/test contexts where one process handles several tenants use `RequestCurrentTenant::override()`.

### Tenant-scoped cache

[](#tenant-scoped-cache)

```
use Rasuvaeff\Yii3Tenancy\TenantScopedCache;

$cache = new TenantScopedCache($psr16Cache, $currentTenant);
$cache->set('report', $data);   // stored as "t.acme.report"
```

> `clear()` delegates to the inner cache and wipes **all** tenants — PSR-16 has no prefix-scoped clear. Do not call it in tenant-scoped code paths.

### DI configuration (Yii3)

[](#di-configuration-yii3)

Ships `config/di.php` + `config/params.php` via `config-plugin`. The core binds `CurrentTenant`, the resolvers, and the middleware. **`TenantProvider`is deliberately not bound** — exactly one source binds it: a backend package or your application:

```
// config/common/di/tenancy.php
use Rasuvaeff\Yii3Tenancy\ConfigTenantProvider;
use Rasuvaeff\Yii3Tenancy\TenantProvider;

return [
    TenantProvider::class => static fn (): TenantProvider => new ConfigTenantProvider([
        'acme' => ['name' => 'Acme Inc'],
    ]),
];
```

Override params as needed:

```
// config/params.php
return [
    'rasuvaeff/yii3-tenancy' => [
        'header' => 'X-Tenant-Id',
        'base_domain' => 'example.com',   // required by SubdomainTenantResolver
        'path_prefix' => '/t',
        'resolvers' => [
            \Rasuvaeff\Yii3Tenancy\HeaderTenantResolver::class,
            \Rasuvaeff\Yii3Tenancy\SubdomainTenantResolver::class,
        ],
        'unresolved_policy' => 'reject',      // or 'passthrough'
        'suspended_policy' => 'reject',
    ],
];
```

### Recipes: wiring into the rasuvaeff/\* ecosystem

[](#recipes-wiring-into-the-rasuvaeff-ecosystem)

```
// feature flags: tenant-aware FlagContext
FlagContext::class => static fn (CurrentTenant $t): FlagContext =>
    new FlagContext(tenantId: $t->find()?->id),

// clickhouse-toolkit: mandatory tenant filter
$builder->withMandatoryFilter(column: 'tenant_id', value: $currentTenant->get()->id);

// settings / feature flags: tenant-isolated cache layer
CacheInterface::class => static fn (CacheInterface $inner, CurrentTenant $t): CacheInterface =>
    new TenantScopedCache($inner, $t),
```

Components
----------

[](#components)

### `Tenant`

[](#tenant)

PropertyTypeDescription`id``string`validated: `/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/``name``string`optional display name`status``TenantStatus``Active` (default) / `Suspended``attributes``array`free-form tenant metadata### `CurrentTenant` / `RequestCurrentTenant`

[](#currenttenant--requestcurrenttenant)

Readers depend on the `CurrentTenant` interface (`get()`, `find()`, `isResolved()`); the middleware depends on the concrete `RequestCurrentTenant`(`set()` once per request, `override()` for console/tests).

### `TenantResolutionMiddleware`

[](#tenantresolutionmiddleware)

ParameterTypeDefaultDescription`resolver``TenantResolver`—key extraction`provider``TenantProvider`—key → `Tenant` lookup`currentTenant``RequestCurrentTenant`—publication target`responseFactory``ResponseFactoryInterface`—builds 404/403`unresolvedPolicy``TenantPolicy``Reject`unresolved/unknown key`suspendedPolicy``TenantPolicy``Reject`suspended tenantSecurity
--------

[](#security)

- Tenant keys extracted from requests are **untrusted input** — every resolver validates against a strict whitelist pattern before lookup.
- Subdomain resolution matches only against the configured base domain, never the raw `Host` value alone; nested labels are rejected.
- There is no implicit "default tenant" fallback — unresolved requests are rejected unless you explicitly opt into `passthrough`.
- The package performs no I/O, SQL, or shell access itself.

Examples
--------

[](#examples)

See [examples/](examples/) for a runnable script.

ScriptShowsNeeds server?[`resolve-tenant.php`](examples/resolve-tenant.php)Resolution, request attribute, 404/403 policiesnoDevelopment
-----------

[](#development)

No PHP/Composer on the host — run in Docker via the `composer:2` image:

```
docker run --rm -v "$PWD":/app -w /app composer:2 composer build
```

Or with Make: `make build`, `make cs-fix`, `make psalm`, `make test`.

License
-------

[](#license)

BSD-3-Clause. See [LICENSE.md](LICENSE.md).

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance92

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~10 days

Total

3

Last Release

27d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b0812d5572a7041dfe36e222d295b2e6dc55833a605350fcde58a51a5965ed30?d=identicon)[rasuvaeff](/maintainers/rasuvaeff)

---

Top Contributors

[![rasuvaeff](https://avatars.githubusercontent.com/u/1352718?v=4)](https://github.com/rasuvaeff "rasuvaeff (1 commits)")

---

Tags

middlewaremulti-tenancyphppsr-15tenancytenantyii3middlewarepsr-15tenantmulti-tenancyyii3tenancy

###  Code Quality

Static AnalysisPsalm, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/rasuvaeff-yii3-tenancy/health.svg)

```
[![Health](https://phpackages.com/badges/rasuvaeff-yii3-tenancy/health.svg)](https://phpackages.com/packages/rasuvaeff-yii3-tenancy)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.9k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

86337.5k](/packages/flow-php-flow)[typo3/cms-core

TYPO3 CMS Core

3313.6M5.6k](/packages/typo3-cms-core)[spiral/framework

Spiral, High-Performance PHP/Go Framework

2.1k2.3M72](/packages/spiral-framework)[thecodingmachine/graphqlite

Write your GraphQL queries in simple to write controllers (using webonyx/graphql-php).

5753.4M51](/packages/thecodingmachine-graphqlite)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
