1. Go to this page and download the library: Download glueful/tenancy library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// migration
$schema->createTable('projects', function ($table) {
$table->bigInteger('id')->primary()->autoIncrement();
$table->string('uuid', 12);
$table->string('tenant_uuid', 12); // <-- tenant ownership column
$table->string('slug', 255);
$table->string('name', 255);
$table->timestamp('created_at')->default('CURRENT_TIMESTAMP');
$table->unique('uuid');
$table->index('tenant_uuid');
// ⚠ per-tenant-unique business key MUST be COMPOSITE — see below
$table->unique(['tenant_uuid', 'slug']);
});
use Glueful\Database\ORM\Model;
use Glueful\Extensions\Tenancy\ORM\Concerns\BelongsToTenant;
final class Project extends Model
{
use BelongsToTenant;
protected string $table = 'projects';
protected array $fillable = ['slug', 'name'];
// tenant_uuid does NOT need to be in $fillable — it is force-stamped on create.
}
'tables' => ['projects'],
$table->unique(['tenant_uuid', 'slug']); // ✅ each tenant may use 'flagship'
// $table->unique('slug'); // ❌ tenant B can never reuse a slug tenant A took
$router->group(['middleware' => ['auth', 'tenant']], function ($router) {
$router->get('/projects', [ProjectController::class, 'index']);
});
// A central/optional route tolerates a missing tenant:
$router->get('/account', [AccountController::class, 'show'])->middleware(['auth', 'tenant:optional']);
// Inside a tenant request (context set by the `tenant` middleware):
Project::query($context)->get(); // only this tenant's projects
Project::create($context, ['slug' => 'app']); // tenant_uuid stamped automatically
use Glueful\Extensions\Tenancy\Query\TenantQuery;
$rows = TenantQuery::tenantTable($context, 'projects')->where('archived', false)->get();
use Glueful\Extensions\Tenancy\Bypass\Tenancy;
// Act AS a specific tenant: queries scope to it, writes stamp it, no bypass active.
// Accepts a Tenant, or a uuid/slug string (resolved + active-checked).
Tenancy::runAsTenant('acme', function () {
Project::create($context, ['slug' => 'q3']);
});
// System / no-tenant privileged maintenance (migrations, schedulers, cross-tenant admin):
Tenancy::runAsSystem(function () {
// runs unscoped, no active tenant
});
// Cross-tenant READ — scoped reads suspended so every tenant's rows are visible.
Tenancy::forAnyTenant(function () {
return Project::query($context)->get();
});
use Glueful\Queue\Job;
use Glueful\Extensions\Tenancy\Queue\PropagatesTenant;
final class SendInvoice extends Job
{
use PropagatesTenant;
public function __construct(array $data = [], ?ApplicationContext $context = null)
{
parent::__construct($data, $context);
$this->captureTenantContext($context); // runs inside the request
}
public function handle(): void
{
$this->runInTenantContext(function (): void {
// tenant-scoped work — DB guard / auto-injection now see the tenant
});
}
}
use Glueful\Console\BaseCommand;
use Glueful\Extensions\Tenancy\Console\Concerns\RunsInTenantContext;
final class BuildReports extends BaseCommand
{
use RunsInTenantContext;
protected function configure(): void
{
$this->setName('reports:build');
$this->configureTenantOption(); // adds --tenant
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return $this->runInTenantContext($input, function (): int {
// scoped to --tenant, or trusted SYSTEM context when the flag is omitted
return self::SUCCESS;
});
}
}
use Glueful\Extensions\Tenancy\Scheduling\ForEachTenant;
use Glueful\Extensions\Tenancy\Models\Tenant;
$result = ForEachTenant::run($context, function (Tenant $tenant): void {
// tenant-scoped maintenance for $tenant; inactive tenants are skipped
});
bash
php glueful migrate:run
bash
php glueful extensions:enable Tenancy
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.