<?php
// 1. Primeiro, crie um middleware personalizado para detectar tanto revendedor quanto empresa
// app/Http/Middleware/InitializeTenancyByDomainAndPath.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Resolvers\PathTenantResolver;
use Stancl\Tenancy\Tenancy;
class InitializeTenancyByDomainAndPath
{
protected $tenancy;
public function __construct(Tenancy $tenancy)
{
$this->tenancy = $tenancy;
}
public function handle(Request $request, Closure $next)
{
$domain = $request->getHost();
$path = trim($request->getPathInfo(), '/');
// Primeiro, identifica o revendedor pelo subdomínio
$revendedor = $this->getRevendedorByDomain($domain);
if (!$revendedor) {
abort(404, 'Revendedor não encontrado');
}
// Se não há path ou é apenas o path base, usa o tenant do revendedor
if (empty($path) || $path === '/') {
$this->tenancy->initialize($revendedor);
return $next($request);
}
// Extrai o primeiro segmento do path
$pathSegments = explode('/', $path);
$empresaSlug = $pathSegments[0];
// Busca a empresa dentro do revendedor
$empresa = $this->getEmpresaBySlug($revendedor, $empresaSlug);
if (!$empresa) {
// Se não encontrar empresa, usa o tenant do revendedor
$this->tenancy->initialize($revendedor);
} else {
// Inicializa com o tenant da empresa
$this->tenancy->initialize($empresa);
}
return $next($request);
}
protected function getRevendedorByDomain($domain)
{
// Extrai o subdomínio (ex: agenda.gendu.com.br -> agenda)
$parts = explode('.', $domain);
$subdomain = $parts[0];
// Busca o revendedor pelo subdomínio
return \App\Models\Tenant::where('data->tipo', 'revendedor')
->where('data->subdomain', $subdomain)
->first();
}
protected function getEmpresaBySlug($revendedor, $slug)
{
// Busca a empresa que pertence ao revendedor
return \App\Models\Tenant::where('data->tipo', 'empresa')
->where('data->revendedor_id', $revendedor->id)
->where('data->slug', $slug)
->first();
}
}
// 2. Crie um modelo customizado para seus tenants
// app/Models/Tenant.php
namespace App\Models;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
class Tenant extends BaseTenant implements TenantWithDatabase
{
use HasDatabase, HasDomains;
protected $fillable = [
'id',
'data',
];
protected $casts = [
'data' => 'array',
];
public function isRevendedor()
{
return $this->data['tipo'] === 'revendedor';
}
public function isEmpresa()
{
return $this->data['tipo'] === 'empresa';
}
public function getRevendedor()
{
if ($this->isRevendedor()) {
return $this;
}
return static::find($this->data['revendedor_id']);
}
public function getEmpresas()
{
if ($this->isEmpresa()) {
return collect();
}
return static::where('data->revendedor_id', $this->id)->get();
}
}
// 3. Configure o arquivo de configuração
// config/tenancy.php
return [
'tenant_model' => \App\Models\Tenant::class,
'id_generator' => Stancl\Tenancy\UuidGenerator::class,
'domain_model' => \Stancl\Tenancy\Database\Models\Domain::class,
// Remova os middlewares padrão e use o customizado
'middleware' => [
// Comente ou remova estes:
// \Stancl\Tenancy\Middleware\InitializeTenancyByDomain::class,
// \Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain::class,
// Use o middleware customizado:
\App\Http\Middleware\InitializeTenancyByDomainAndPath::class,
],
'database' => [
'central_connection' => env('DB_CONNECTION', 'mysql'),
'template_tenant_connection' => null,
'prefix' => 'tenant',
'suffix' => '',
'managers' => [
'mysql' => \Stancl\Tenancy\Database\DatabaseManager::class,
],
],
'cache' => [
'tag_base' => 'tenant',
],
'filesystem' => [
'suffix_base' => 'tenant',
'disks' => [
'local',
'public',
],
],
'redis' => [
'prefix_base' => 'tenant',
],
'features' => [
\Stancl\Tenancy\Features\UserImpersonation::class,
\Stancl\Tenancy\Features\TelescopeTags::class,
\Stancl\Tenancy\Features\UniversalRoutes::class,
\Stancl\Tenancy\Features\TenantConfig::class,
\Stancl\Tenancy\Features\CrossDomainRedirect::class,
\Stancl\Tenancy\Features\ViteBundler::class,
],
];
// 4. Crie uma migration para ajustar a estrutura dos tenants
// database/migrations/xxxx_update_tenants_structure.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateTenantsStructure extends Migration
{
public function up()
{
Schema::table('tenants', function (Blueprint $table) {
// Adicione índices para melhorar performance
$table->index(['data->tipo']);
$table->index(['data->subdomain']);
$table->index(['data->slug']);
$table->index(['data->revendedor_id']);
});
}
public function down()
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropIndex(['data->tipo']);
$table->dropIndex(['data->subdomain']);
$table->dropIndex(['data->slug']);
$table->dropIndex(['data->revendedor_id']);
});
}
}
// 5. Helper para criar tenants
// app/Services/TenantService.php
namespace App\Services;
use App\Models\Tenant;
use Stancl\Tenancy\Database\Models\Domain;
class TenantService
{
public function createRevendedor($nome, $subdomain, $dados = [])
{
$tenant = Tenant::create([
'data' => array_merge([
'tipo' => 'revendedor',
'nome' => $nome,
'subdomain' => $subdomain,
], $dados)
]);
// Cria o domínio para o revendedor
$tenant->domains()->create([
'domain' => "{$subdomain}.seudominio.com.br"
]);
return $tenant;
}
public function createEmpresa($nome, $slug, $revendedorId, $dados = [])
{
$tenant = Tenant::create([
'data' => array_merge([
'tipo' => 'empresa',
'nome' => $nome,
'slug' => $slug,
'revendedor_id' => $revendedorId,
], $dados)
]);
return $tenant;
}
}
// 6. Route Service Provider personalizado
// app/Providers/TenantRouteServiceProvider.php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class TenantRouteServiceProvider extends ServiceProvider
{
public function boot()
{
$this->routes(function () {
// Rotas para revendedores (quando não há path)
Route::middleware([
'web',
InitializeTenancyByDomainAndPath::class,
PreventAccessFromCentralDomains::class,
])
->group(function () {
// Verifica se é revendedor
if (tenant() && tenant()->isRevendedor()) {
Route::group([], base_path('routes/revendedor.php'));
}
// Verifica se é empresa
if (tenant() && tenant()->isEmpresa()) {
Route::group([], base_path('routes/empresa.php'));
}
});
});
}
}
// 7. Exemplo de uso em um controller
// app/Http/Controllers/TenantController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TenantController extends Controller
{
public function index()
{
$tenant = tenant();
if ($tenant->isRevendedor()) {
return view('revendedor.dashboard', [
'revendedor' => $tenant,
'empresas' => $tenant->getEmpresas(),
]);
}
if ($tenant->isEmpresa()) {
return view('empresa.dashboard', [
'empresa' => $tenant,
'revendedor' => $tenant->getRevendedor(),
]);
}
abort(404);
}
}
// 8. Exemplo de criação de tenants
// database/seeders/TenantSeeder.php
namespace Database\Seeders;
use App\Services\TenantService;
use Illuminate\Database\Seeder;
class TenantSeeder extends Seeder
{
public function run()
{
$tenantService = new TenantService();
// Cria revendedor
$revendedor = $tenantService->createRevendedor(
'Gendu',
'agenda',
['email' => 'contato@gendu.com.br']
);
// Cria empresas do revendedor
$tenantService->createEmpresa(
'Salão da Maria',
'salao-maria',
$revendedor->id,
['email' => 'maria@salao.com']
);
$tenantService->createEmpresa(
'Barbearia do Zé',
'ze',
$revendedor->id,
['email' => 'ze@barbearia.com']
);
}
}