From 4175ac07bdab87ccf0421df0f1c40d8e2ee06dbe Mon Sep 17 00:00:00 2001 From: lukinovec Date: Tue, 6 Jun 2023 15:46:50 +0200 Subject: [PATCH] Rework model discovery --- src/Tenancy.php | 47 ++++++++++++++++++++++++++++++--------- tests/PostgresRLSTest.php | 3 +++ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Tenancy.php b/src/Tenancy.php index db9e1e2b..6cf8b801 100644 --- a/src/Tenancy.php +++ b/src/Tenancy.php @@ -5,14 +5,17 @@ declare(strict_types=1); namespace Stancl\Tenancy; use Closure; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Traits\Macroable; -use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Contracts\Tenant; +use Symfony\Component\Finder\Finder; +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Traits\Macroable; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Collection; +use Stancl\Tenancy\Contracts\TenancyBootstrapper; use Stancl\Tenancy\Database\Concerns\BelongsToPrimaryModel; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByIdException; +use Symfony\Component\Finder\SplFileInfo; class Tenancy { @@ -29,6 +32,10 @@ class Tenancy /** Is tenancy fully initialized? */ public bool $initialized = false; // todo document the difference between $tenant being set and $initialized being true (e.g. end of initialize() method) + public static array $modelDirectories = ['App/Models']; + + public static Closure|null $modelDiscoveryOverride = null; + /** Initialize tenancy for the passed tenant. */ public function initialize(Tenant|int|string $tenant): void { @@ -218,17 +225,35 @@ class Tenancy return config('tenancy.identification.default_middleware', Middleware\InitializeTenancyByDomain::class); } - public static function getModels(): array + public static function getModels(): Collection { - $tables = array_map(fn ($table) => $table->tablename, Schema::getAllTables()); - $models = array_map(fn (string $table) => static::getModelFromTable($table), $tables); + if (static::$modelDiscoveryOverride) { + return (static::$modelDiscoveryOverride)(); + } - return array_filter($models); + $modelFiles = Finder::create()->files()->name('*.php')->in(static::$modelDirectories)->depth('== 0'); + + $classes = collect($modelFiles)->map(function (SplFileInfo $file) { + $fileContents = str($file->getContents()); + $class = $fileContents->after('class ')->before("\n")->explode(' ')->first(); + + if ($fileContents->contains('namespace ')) { + try { + return new ($fileContents->after('namespace ')->before(';')->toString() . '\\' . $class); + } catch (\Throwable $th) { + // Skip non-instantiable classes – we only care about models, and those are instantiable + } + } + + return null; + })->filter(); + + return $classes->filter(fn ($class) => in_array(Model::class, class_parents($class))); } - public static function getTenantModels(): array + public static function getTenantModels(): Collection { - return array_filter(static::getModels(), fn (Model $model) => tenancy()->modelBelongsToTenant($model) || tenancy()->modelBelongsToTenantIndirectly($model)); + return static::getModels()->filter(fn (Model $model) => tenancy()->modelBelongsToTenant($model) || tenancy()->modelBelongsToTenantIndirectly($model)); } protected static function getModelFromTable(string $table): Model|null diff --git a/tests/PostgresRLSTest.php b/tests/PostgresRLSTest.php index 9f72c06a..2d0cfa30 100644 --- a/tests/PostgresRLSTest.php +++ b/tests/PostgresRLSTest.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids; use Stancl\Tenancy\Jobs\CreatePostgresUserForTenant; use Stancl\Tenancy\Listeners\RevertToCentralContext; use Stancl\Tenancy\Bootstrappers\Integrations\PostgresRLSBootstrapper; +use Stancl\Tenancy\Tenancy; beforeEach(function () { DB::purge($centralConnection = config('tenancy.database.central_connection')); @@ -27,6 +28,8 @@ beforeEach(function () { Event::listen(TenancyInitialized::class, BootstrapTenancy::class); Event::listen(TenancyEnded::class, RevertToCentralContext::class); + Tenancy::$modelDirectories = [__DIR__ . '/Etc']; + // Turn RLS scoping on config(['tenancy.database.rls' => true]); config(['tenancy.bootstrappers' => [PostgresRLSBootstrapper::class]]);