misaf/laravel-tenancy-support

Core interfaces and abstractions for Misaf packages

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/misaf/laravel-tenancy-support

v1.0.0 2026-02-16 02:39 UTC

This package is auto-updated.

Last update: 2026-02-16 02:42:34 UTC


README

Shared tenancy contracts and Eloquent helpers for tenant-aware packages.

Requirements

  • PHP 8.2+
  • Laravel application runtime (for container + Eloquent usage)

Installation

composer require misaf/laravel-tenancy-support

Contracts

TenantResolver

namespace Misaf\TenancySupport\Contracts;

interface TenantResolver
{
    public function getTenantId(): int|string|null;
}

TeamResolver

namespace Misaf\TenancySupport\Contracts;

interface TeamResolver
{
    public function getTeamId(): int|string|null;
}

TenantAccessResolver (optional)

Use this when your resolver can authorize elevated, all-tenant access (for example, super admin).

namespace Misaf\TenancySupport\Contracts;

interface TenantAccessResolver
{
    public function canAccessAllTenants(): bool;
}

TeamAccessResolver (optional)

Use this when your resolver can authorize elevated, all-team access.

namespace Misaf\TenancySupport\Contracts;

interface TeamAccessResolver
{
    public function canAccessAllTeams(): bool;
}

Resolver Example

namespace App\Tenancy;

use Misaf\TenancySupport\Contracts\TeamAccessResolver;
use Misaf\TenancySupport\Contracts\TeamResolver;
use Misaf\TenancySupport\Contracts\TenantAccessResolver;
use Misaf\TenancySupport\Contracts\TenantResolver;

final class RequestTenantResolver implements TenantResolver, TenantAccessResolver, TeamResolver, TeamAccessResolver
{
    public function getTenantId(): int|string|null
    {
        return auth()->user()?->tenant_id;
    }

    public function getTeamId(): int|string|null
    {
        return auth()->id();
    }

    public function canAccessAllTenants(): bool
    {
        return (bool) auth()->user()?->is_super_admin;
    }

    public function canAccessAllTeams(): bool
    {
        return (bool) auth()->user()?->is_super_admin;
    }
}

If TeamResolver is not bound, team scope falls back to auth()->id().

Bind it in your service provider:

use App\Tenancy\RequestTenantResolver;
use Misaf\TenancySupport\Contracts\TeamResolver;
use Misaf\TenancySupport\Contracts\TenantResolver;

public function register(): void
{
    $this->app->scoped(TenantResolver::class, RequestTenantResolver::class);
    $this->app->scoped(TeamResolver::class, RequestTenantResolver::class);
}

Model Usage

use Illuminate\Database\Eloquent\Model;
use Misaf\TenancySupport\Concerns\BelongsToTenant;

final class Invoice extends Model
{
    use BelongsToTenant;
}

Team scope usage:

use Illuminate\Database\Eloquent\Model;
use Misaf\TenancySupport\Concerns\BelongsToTeam;

final class Task extends Model
{
    use BelongsToTeam;
}

Behavior:

  • Normal queries are tenant-scoped by tenant_id.
  • If tenant is not resolved, reads fail-closed (no rows).
  • On create, tenant_id is auto-filled from current tenant when available.
  • Team-scoped models apply the same behavior using team_id.

Super Admin: Explicit All-Tenant Queries

Elevated access is opt-in per operation:

use App\Models\Invoice;
use Misaf\TenancySupport\Support\CurrentTenant;

$allInvoices = CurrentTenant::withAllTenants(
    fn () => Invoice::query()->latest()->get()
);

withAllTenants(...) throws when current context is not authorized by TenantAccessResolver::canAccessAllTenants().

Team equivalent:

use App\Models\Task;
use Misaf\TenancySupport\Support\CurrentTeam;

$allTeamTasks = CurrentTeam::withAllTeams(
    fn () => Task::query()->latest()->get()
);

License

MIT. See LICENSE.