PHPackages                             azaharizaman/nexus-tenant - 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. azaharizaman/nexus-tenant

ActiveLibrary[Framework](/categories/framework)

azaharizaman/nexus-tenant
=========================

Framework-agnostic multi-tenancy context and isolation engine for the Nexus ERP system

v0.1.0-alpha1(1mo ago)022MITPHPPHP ^8.3

Since May 5Pushed 1mo agoCompare

[ Source](https://github.com/azaharizaman/nexus-tenant)[ Packagist](https://packagist.org/packages/azaharizaman/nexus-tenant)[ RSS](/packages/azaharizaman-nexus-tenant/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (3)Versions (2)Used By (2)

Nexus\\Tenant
=============

[](#nexustenant)

[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](https://opensource.org/licenses/MIT)[![PHP Version](https://camo.githubusercontent.com/c8d8dad6beb757a2b8acba331d16140813699543b88a37af0a81f20bd35f61de/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e332532422d626c7565)](https://php.net)

Framework-agnostic multi-tenancy context and isolation engine for the Nexus ERP system.

Overview
--------

[](#overview)

The **Nexus\\Tenant** package provides the core business logic and contracts necessary to manage multi-tenancy context, session, and administrative lifecycle. It serves as the central engine for identifying and validating the current tenant across the entire ERP system.

Key Features
------------

[](#key-features)

- **Framework-Agnostic**: Pure PHP with no Laravel dependencies
- **Context Management**: Set and retrieve active tenant across request/process lifecycle
- **Tenant Identification**: Support for multiple strategies (domain, subdomain, header, token, path)
- **Lifecycle Management**: Create, activate, suspend, reactivate, archive, and delete tenants
- **Impersonation**: Secure support staff impersonation with audit trails
- **Multi-Database Support**: Single database with tenant\_id or separate databases per tenant
- **Enterprise Features**: Parent-child relationships, quotas, rate limiting, feature flags
- **Event-Driven**: Lifecycle events for integration with other packages
- **Caching**: Optimized performance with cache abstraction

Architecture
------------

[](#architecture)

This package follows the **"Logic in Packages, Implementation in Applications"** pattern:

- **Package Layer** (`packages/Tenant/`): Framework-agnostic business logic and interfaces
- **Application Layer** (`apps/Atomy/`): Laravel-specific implementation (models, migrations, repositories)

### What This Package Provides

[](#what-this-package-provides)

- `TenantContextManager`: Core service for setting/retrieving current tenant context
- `TenantLifecycleService`: Business logic for tenant CRUD and state management
- `TenantImpersonationService`: Secure impersonation with validation and logging
- `TenantResolverService`: Identifies tenant from request (domain, subdomain, header, etc.)
- **Contracts**: All external dependencies defined via interfaces
- **Events**: Framework-agnostic events for lifecycle changes
- **Exceptions**: Domain-specific exceptions for error handling
- **Value Objects**: Immutable objects for tenant status, identification strategy, and settings

### What the Application Must Implement

[](#what-the-application-must-implement)

The consuming application (`Nexus\Atomy`) must provide:

1. **Data Isolation**: Global Scope for automatic `WHERE tenant_id = X` clauses
2. **Persistence**: Migrations and Eloquent models implementing package interfaces
3. **Cache Integration**: Concrete implementation of `CacheRepositoryInterface`
4. **Context Propagation**: Middleware and queue job handling for tenant context
5. **Multi-Database Strategy**: Database connection switching (if using separate databases)

Installation
------------

[](#installation)

```
composer require azaharizaman/nexus-tenant:"*@dev"
```

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

[](#requirements)

- PHP 8.3 or higher
- PSR-3 Logger implementation (optional)

Basic Usage
-----------

[](#basic-usage)

### 1. Setting Tenant Context

[](#1-setting-tenant-context)

```
use Nexus\Tenant\Services\TenantContextManager;

$contextManager = app(TenantContextManager::class);

// Set tenant by ID
$contextManager->setTenant('01HQRS...');

// Get current tenant ID
$tenantId = $contextManager->getCurrentTenantId(); // '01HQRS...'

// Check if tenant is set
if ($contextManager->hasTenant()) {
    // Tenant context is active
}

// Clear context
$contextManager->clearTenant();
```

### 2. Tenant Lifecycle Management

[](#2-tenant-lifecycle-management)

```
use Nexus\Tenant\Services\TenantLifecycleService;

$lifecycle = app(TenantLifecycleService::class);

// Create new tenant
$tenant = $lifecycle->createTenant(
    code: 'ACME',
    name: 'Acme Corporation',
    email: 'admin@acme.com',
    domain: 'acme.example.com'
);

// Activate tenant
$lifecycle->activateTenant($tenant->getId());

// Suspend tenant (reversible)
$lifecycle->suspendTenant($tenant->getId(), reason: 'Payment overdue');

// Reactivate
$lifecycle->reactivateTenant($tenant->getId());

// Archive (soft delete)
$lifecycle->archiveTenant($tenant->getId());

// Permanently delete (hard delete after retention period)
$lifecycle->deleteTenant($tenant->getId());
```

### 3. Tenant Impersonation

[](#3-tenant-impersonation)

```
use Nexus\Tenant\Services\TenantImpersonationService;

$impersonation = app(TenantImpersonationService::class);

// Start impersonation (support staff accessing tenant)
$impersonation->impersonate(
    tenantId: '01HQRS...',
    originalUserId: 'admin-123',
    reason: 'Customer support ticket #4567'
);

// Check if impersonating
if ($impersonation->isImpersonating()) {
    $tenantId = $impersonation->getImpersonatedTenantId();
    $originalUser = $impersonation->getOriginalUserId();
}

// Stop impersonation
$impersonation->stopImpersonation();
```

### 4. Tenant Resolution

[](#4-tenant-resolution)

```
use Nexus\Tenant\Services\TenantResolverService;

$resolver = app(TenantResolverService::class);

// Resolve from domain
$tenantId = $resolver->resolveFromDomain('acme.example.com');

// Resolve from subdomain
$tenantId = $resolver->resolveFromSubdomain('acme.myapp.com');

// Resolve from header
$tenantId = $resolver->resolveFromHeader($_SERVER, 'X-Tenant-ID');

// Resolve from path
$tenantId = $resolver->resolveFromPath('/tenant/acme/dashboard');
```

Contracts
---------

[](#contracts)

All external dependencies are defined via interfaces that must be implemented by the application:

### TenantInterface

[](#tenantinterface)

Defines the data structure for a tenant entity.

```
interface TenantInterface
{
    public function getId(): string;
    public function getCode(): string;
    public function getName(): string;
    public function getStatus(): string;
    public function getDomain(): ?string;
    // ... (20+ methods)
}
```

### TenantRepositoryInterface

[](#tenantrepositoryinterface)

Defines persistence operations for tenants.

```
interface TenantRepositoryInterface
{
    public function findById(string $id): ?TenantInterface;
    public function findByCode(string $code): ?TenantInterface;
    public function findByDomain(string $domain): ?TenantInterface;
    public function create(array $data): TenantInterface;
    public function update(string $id, array $data): TenantInterface;
    public function delete(string $id): bool;
    // ... (15+ methods)
}
```

### CacheRepositoryInterface

[](#cacherepositoryinterface)

Defines caching operations for tenant data.

```
interface CacheRepositoryInterface
{
    public function get(string $key): mixed;
    public function set(string $key, mixed $value, ?int $ttl = null): bool;
    public function forget(string $key): bool;
    public function flush(): bool;
}
```

### TenantContextInterface

[](#tenantcontextinterface)

Defines the tenant context contract for global access.

```
interface TenantContextInterface
{
    public function setTenant(string $tenantId): void;
    public function getCurrentTenantId(): ?string;
    public function hasTenant(): bool;
    public function clearTenant(): void;
}
```

Events
------

[](#events)

The package emits framework-agnostic events for lifecycle changes:

- `TenantCreatedEvent`: Fired when a new tenant is created
- `TenantActivatedEvent`: Fired when a tenant is activated
- `TenantSuspendedEvent`: Fired when a tenant is suspended
- `TenantReactivatedEvent`: Fired when a suspended tenant is reactivated
- `TenantArchivedEvent`: Fired when a tenant is archived (soft deleted)
- `TenantDeletedEvent`: Fired when a tenant is permanently deleted
- `TenantUpdatedEvent`: Fired when tenant metadata is updated
- `ImpersonationStartedEvent`: Fired when impersonation begins
- `ImpersonationEndedEvent`: Fired when impersonation stops

Value Objects
-------------

[](#value-objects)

### TenantStatus

[](#tenantstatus)

Immutable enum representing tenant status:

```
use Nexus\Tenant\Enums\TenantStatus;

$status = TenantStatus::pending();    // Tenant created, not yet active
$status = TenantStatus::active();     // Tenant active and operational
$status = TenantStatus::suspended();  // Temporarily suspended (reversible)
$status = TenantStatus::archived();   // Soft deleted
$status = TenantStatus::trial();      // Trial period
```

### IdentificationStrategy

[](#identificationstrategy)

Defines how tenants are identified:

```
use Nexus\Tenant\Enums\IdentificationStrategy;

$strategy = IdentificationStrategy::domain();      // acme.example.com
$strategy = IdentificationStrategy::subdomain();   // acme.myapp.com
$strategy = IdentificationStrategy::header();      // X-Tenant-ID header
$strategy = IdentificationStrategy::path();        // /tenant/acme
$strategy = IdentificationStrategy::token();       // API token embedded tenant
```

### TenantSettings

[](#tenantsettings)

Immutable settings object for tenant-specific configuration:

```
use Nexus\Tenant\ValueObjects\TenantSettings;

$settings = new TenantSettings(
    timezone: 'Asia/Kuala_Lumpur',
    locale: 'en_MY',
    currency: 'MYR',
    dateFormat: 'd/m/Y',
    timeFormat: 'H:i',
    metadata: ['custom_field' => 'value']
);
```

Exception Handling
------------------

[](#exception-handling)

The package provides specific exceptions for error scenarios:

```
use Nexus\Tenant\Exceptions\TenantNotFoundException;
use Nexus\Tenant\Exceptions\InvalidTenantStatusException;
use Nexus\Tenant\Exceptions\TenantSuspendedException;
use Nexus\Tenant\Exceptions\TenantContextNotSetException;
use Nexus\Tenant\Exceptions\InvalidIdentificationStrategyException;
use Nexus\Tenant\Exceptions\ImpersonationNotAllowedException;
use Nexus\Tenant\Exceptions\DuplicateTenantCodeException;
use Nexus\Tenant\Exceptions\DuplicateTenantDomainException;

try {
    $tenant = $repository->findById('invalid-id');
} catch (TenantNotFoundException $e) {
    // Handle not found
}
```

Testing
-------

[](#testing)

```
# Run package tests (unit tests, no database)
cd packages/Tenant
composer test

# Run with coverage
composer test -- --coverage-html coverage/
```

Queue Context Propagation
-------------------------

[](#queue-context-propagation)

**Critical**: Queued jobs run in a separate process and do not automatically inherit the tenant context from the dispatching request. The Nexus architecture provides a **middleware pattern** to preserve tenant context across job dispatches.

### Architecture Overview

[](#architecture-overview)

```
┌─────────────────┐
│  HTTP Request   │
│  (Tenant Set)   │
└────────┬────────┘
         │ Dispatch Job
         ▼
┌─────────────────────────┐
│  Job Serialization      │
│  ✓ Captures tenant_id   │ ← TenantAwareJob Trait
└────────┬────────────────┘
         │ Push to Queue
         ▼
┌─────────────────────────┐
│  Queue Worker Process   │
│  ✗ No tenant context    │
└────────┬────────────────┘
         │ Process Job
         ▼
┌─────────────────────────┐
│  SetTenantContext       │
│  ✓ Restores tenant_id   │ ← Middleware
└────────┬────────────────┘
         │
         ▼
┌─────────────────────────┐
│  Job Execution          │
│  ✓ Tenant context set   │
└────────┬────────────────┘
         │ Complete
         ▼
┌─────────────────────────┐
│  Context Cleanup        │
│  ✓ Clears tenant_id     │ ← Middleware (finally block)
└─────────────────────────┘

```

### Implementation Pattern

[](#implementation-pattern)

#### 1. Use the TenantAwareJob Trait

[](#1-use-the-tenantawarejob-trait)

For any job that needs tenant context, use the `TenantAwareJob` trait provided by the application layer:

```
