1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 11:34:03 +00:00

phpstan, global_cache, resolver improvements, InitializationHelpers trait

This commit is contained in:
Samuel Štancl 2022-09-29 02:47:13 +02:00
parent fd65cf1754
commit 87212e5390
35 changed files with 170 additions and 231 deletions

View file

@ -10,6 +10,7 @@ parameters:
universalObjectCratesClasses:
- Illuminate\Routing\Route
- Illuminate\Database\Eloquent\Model
ignoreErrors:
-
@ -20,6 +21,14 @@ parameters:
message: '#invalid type Laravel\\Telescope\\IncomingEntry#'
paths:
- src/Features/TelescopeTags.php
-
message: '#Parameter \#1 \$key of method Illuminate\\Contracts\\Cache\\Repository::put\(\) expects string#'
paths:
- src/helpers.php
-
message: '#PHPDoc tag \@param has invalid value \(dynamic#'
paths:
- src/helpers.php
checkMissingIterableValueType: false
treatPhpDocTypesAsCertain: false

View file

@ -8,24 +8,11 @@ use Illuminate\Console\Command;
class Install extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tenancy:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install stancl/tenancy.';
/**
* Execute the console command.
*/
public function handle()
public function handle(): void
{
$this->comment('Installing stancl/tenancy...');
$this->callSilent('vendor:publish', [

View file

@ -15,30 +15,15 @@ class Link extends Command
{
use HasATenantsOption;
/**
* The console command signature.
*
* @var string
*/
protected $signature = 'tenants:link
{--tenants=* : The tenant(s) to run the command for. Default: all}
{--relative : Create the symbolic link using relative paths}
{--force : Recreate existing symbolic links}
{--remove : Remove symbolic links}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create or remove tenant symbolic links.';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
public function handle(): void
{
$tenants = $this->getTenants();

View file

@ -7,7 +7,6 @@ namespace Stancl\Tenancy\Commands;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Console\Migrations\MigrateCommand;
use Illuminate\Database\Migrations\Migrator;
use Stancl\Tenancy\Concerns\DealsWithMigrations;
use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
use Stancl\Tenancy\Concerns\HasATenantsOption;
use Stancl\Tenancy\Events\DatabaseMigrated;
@ -15,7 +14,7 @@ use Stancl\Tenancy\Events\MigratingDatabase;
class Migrate extends MigrateCommand
{
use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
use HasATenantsOption, ExtendsLaravelCommand;
protected $description = 'Run migrations for tenant(s)';
@ -31,10 +30,7 @@ class Migrate extends MigrateCommand
$this->specifyParameters();
}
/**
* Execute the console command.
*/
public function handle()
public function handle(): int
{
foreach (config('tenancy.migration_parameters') as $parameter => $value) {
if (! $this->input->hasParameterOption($parameter)) {
@ -43,10 +39,10 @@ class Migrate extends MigrateCommand
}
if (! $this->confirmToProceed()) {
return;
return 1;
}
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
tenancy()->runForMultiple($this->getTenants(), function ($tenant) {
$this->line("Tenant: {$tenant->getTenantKey()}");
event(new MigratingDatabase($tenant));
@ -56,5 +52,7 @@ class Migrate extends MigrateCommand
event(new DatabaseMigrated($tenant));
});
return 0;
}
}

View file

@ -5,19 +5,13 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Commands;
use Illuminate\Console\Command;
use Stancl\Tenancy\Concerns\DealsWithMigrations;
use Stancl\Tenancy\Concerns\HasATenantsOption;
use Symfony\Component\Console\Input\InputOption;
final class MigrateFresh extends Command
{
use HasATenantsOption, DealsWithMigrations;
use HasATenantsOption;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Drop all tables and re-run all migrations for tenant(s)';
public function __construct()
@ -29,12 +23,9 @@ final class MigrateFresh extends Command
$this->setName('tenants:migrate-fresh');
}
/**
* Execute the console command.
*/
public function handle()
public function handle(): void
{
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
tenancy()->runForMultiple($this->getTenants(), function ($tenant) {
$this->info('Dropping tables.');
$this->call('db:wipe', array_filter([
'--database' => 'tenant',

View file

@ -6,7 +6,6 @@ namespace Stancl\Tenancy\Commands;
use Illuminate\Database\Console\Migrations\RollbackCommand;
use Illuminate\Database\Migrations\Migrator;
use Stancl\Tenancy\Concerns\DealsWithMigrations;
use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
use Stancl\Tenancy\Concerns\HasATenantsOption;
use Stancl\Tenancy\Events\DatabaseRolledBack;
@ -14,25 +13,10 @@ use Stancl\Tenancy\Events\RollingBackDatabase;
class Rollback extends RollbackCommand
{
use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
use HasATenantsOption, ExtendsLaravelCommand;
protected static function getTenantCommandName(): string
{
return 'tenants:rollback';
}
/**
* The console command description.
*
* @var string
*/
protected $description = 'Rollback migrations for tenant(s).';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(Migrator $migrator)
{
parent::__construct($migrator);
@ -40,10 +24,7 @@ class Rollback extends RollbackCommand
$this->specifyTenantSignature();
}
/**
* Execute the console command.
*/
public function handle()
public function handle(): int
{
foreach (config('tenancy.migration_parameters') as $parameter => $value) {
if (! $this->input->hasParameterOption($parameter)) {
@ -52,7 +33,7 @@ class Rollback extends RollbackCommand
}
if (! $this->confirmToProceed()) {
return;
return 1;
}
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
@ -65,5 +46,12 @@ class Rollback extends RollbackCommand
event(new DatabaseRolledBack($tenant));
});
return 0;
}
protected static function getTenantCommandName(): string
{
return 'tenants:rollback';
}
}

View file

@ -11,27 +11,14 @@ use Symfony\Component\Console\Output\ConsoleOutput;
class Run extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run a command for tenant(s)';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tenants:run {commandname : The artisan command.}
{--tenants=* : The tenant(s) to run the command for. Default: all}';
/**
* Execute the console command.
*/
public function handle()
public function handle(): void
{
$argvInput = $this->ArgvInput();
$argvInput = $this->argvInput();
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) use ($argvInput) {
$this->line("Tenant: {$tenant->getTenantKey()}");
@ -41,10 +28,7 @@ class Run extends Command
});
}
/**
* Get command as ArgvInput instance.
*/
protected function ArgvInput(): ArgvInput
protected function argvInput(): ArgvInput
{
// Convert string command to array
$subCommand = explode(' ', $this->argument('commandname'));

View file

@ -14,29 +14,16 @@ class Seed extends SeedCommand
{
use HasATenantsOption;
/**
* The console command description.
*
* @var string
*/
protected $description = 'Seed tenant database(s).';
protected $name = 'tenants:seed';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct(ConnectionResolverInterface $resolver)
{
parent::__construct($resolver);
}
/**
* Execute the console command.
*/
public function handle()
public function handle(): int
{
foreach (config('tenancy.seeder_parameters') as $parameter => $value) {
if (! $this->input->hasParameterOption($parameter)) {
@ -45,7 +32,7 @@ class Seed extends SeedCommand
}
if (! $this->confirmToProceed()) {
return;
return 1;
}
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
@ -58,5 +45,7 @@ class Seed extends SeedCommand
event(new DatabaseSeeded($tenant));
});
return 0;
}
}

View file

@ -9,24 +9,11 @@ use Stancl\Tenancy\Contracts\Tenant;
class TenantList extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tenants:list';
/**
* The console command description.
*
* @var string
*/
protected $description = 'List tenants.';
/**
* Execute the console command.
*/
public function handle()
public function handle(): void
{
$this->info('Listing all tenants.');
tenancy()

View file

@ -6,12 +6,12 @@ namespace Stancl\Tenancy\Concerns;
trait DealsWithMigrations
{
protected function getMigrationPaths()
protected function getMigrationPaths(): array
{
if ($this->input->hasOption('path') && $this->input->getOption('path')) {
return parent::getMigrationPaths();
}
return database_path('migrations/tenant');
return [database_path('migrations/tenant')];
}
}

View file

@ -4,10 +4,12 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Contracts;
use Illuminate\Database\Eloquent\Model;
interface UniqueIdentifierGenerator
{
/**
* Generate a unique identifier.
* Generate a unique identifier for a model.
*/
public static function generate($resource): string;
public static function generate(Model $model): string;
}

View file

@ -10,6 +10,8 @@ use Stancl\Tenancy\Contracts\Domain;
/**
* @property-read Domain[]|\Illuminate\Database\Eloquent\Collection $domains
* @mixin \Illuminate\Database\Eloquent\Model
* @mixin \Stancl\Tenancy\Contracts\Tenant
*/
trait HasDomains
{

View file

@ -0,0 +1,19 @@
<?php
namespace Stancl\Tenancy\Database\Concerns;
/**
* @mixin \Stancl\Tenancy\Contracts\Tenant
*/
trait InitializationHelpers
{
public function enter(): void
{
tenancy()->initialize($this);
}
public function leave(): void
{
tenancy()->end();
}
}

View file

@ -26,6 +26,7 @@ class Tenant extends Model implements Contracts\Tenant
Concerns\HasDataColumn,
Concerns\HasInternalKeys,
Concerns\TenantRun,
Concerns\InitializationHelpers,
Concerns\InvalidatesResolverCache;
protected $table = 'tenants';

View file

@ -8,11 +8,7 @@ use Stancl\Tenancy\Tenancy;
abstract class TenancyEvent
{
/** @var Tenancy */
public $tenancy;
public function __construct(Tenancy $tenancy)
{
$this->tenancy = $tenancy;
}
public function __construct(
public Tenancy $tenancy,
) {}
}

View file

@ -4,20 +4,18 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
use Stancl\Tenancy\Contracts\TenantResolver;
use Stancl\Tenancy\Tenancy;
/**
* @property Tenancy $tenancy
* @property TenantResolver $resolver
*/
abstract class IdentificationMiddleware
{
/** @var callable */
public static $onFail;
/** @var Tenancy */
protected $tenancy;
/** @var TenantResolver */
protected $resolver;
public static ?Closure $onFail = null;
public function initializeTenancy($request, $next, ...$resolverArguments)
{

View file

@ -5,32 +5,21 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Stancl\Tenancy\Resolvers\DomainTenantResolver;
use Stancl\Tenancy\Tenancy;
class InitializeTenancyByDomain extends IdentificationMiddleware
{
/** @var callable|null */
public static $onFail;
public static ?Closure $onFail = null;
/** @var Tenancy */
protected $tenancy;
public function __construct(
protected Tenancy $tenancy,
protected DomainTenantResolver $resolver,
) {}
/** @var DomainTenantResolver */
protected $resolver;
public function __construct(Tenancy $tenancy, DomainTenantResolver $resolver)
{
$this->tenancy = $tenancy;
$this->resolver = $resolver;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
*/
public function handle($request, Closure $next)
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
return $this->initializeTenancy(
$request,

View file

@ -5,16 +5,13 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class InitializeTenancyByDomainOrSubdomain
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
*/
public function handle($request, Closure $next)
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if ($this->isSubdomain($request->getHost())) {
return app(InitializeTenancyBySubdomain::class)->handle($request, $next);

View file

@ -16,22 +16,15 @@ use Stancl\Tenancy\Tenancy;
class InitializeTenancyByPath extends IdentificationMiddleware
{
/** @var callable|null */
public static $onFail;
public static ?Closure $onFail = null;
/** @var Tenancy */
protected $tenancy;
public function __construct(
protected Tenancy $tenancy,
protected PathTenantResolver $resolver,
) {}
/** @var PathTenantResolver */
protected $resolver;
public function __construct(Tenancy $tenancy, PathTenantResolver $resolver)
{
$this->tenancy = $tenancy;
$this->resolver = $resolver;
}
public function handle(Request $request, Closure $next)
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
/** @var Route $route */
$route = $request->route();

View file

@ -11,33 +11,17 @@ use Stancl\Tenancy\Tenancy;
class InitializeTenancyByRequestData extends IdentificationMiddleware
{
/** @var string|null */
public static $header = 'X-Tenant';
public static string $header = 'X-Tenant';
public static string $queryParameter = 'tenant';
public static ?Closure $onFail = null;
/** @var string|null */
public static $queryParameter = 'tenant';
public function __construct(
protected Tenancy $tenancy,
protected RequestDataTenantResolver $resolver,
) {}
/** @var callable|null */
public static $onFail;
/** @var Tenancy */
protected $tenancy;
/** @var TenantResolver */
protected $resolver;
public function __construct(Tenancy $tenancy, RequestDataTenantResolver $resolver)
{
$this->tenancy = $tenancy;
$this->resolver = $resolver;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
*/
public function handle($request, Closure $next)
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if ($request->method() !== 'OPTIONS') {
return $this->initializeTenancy($request, $next, $this->getPayload($request));

View file

@ -6,6 +6,7 @@ namespace Stancl\Tenancy\Middleware;
use Closure;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Stancl\Tenancy\Exceptions\NotASubdomainException;
@ -21,15 +22,10 @@ class InitializeTenancyBySubdomain extends InitializeTenancyByDomain
*/
public static $subdomainIndex = 0;
/** @var callable|null */
public static $onFail;
public static ?Closure $onFail = null;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
*/
public function handle($request, Closure $next)
/** @return Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
$subdomain = $this->makeSubdomain($request->getHost());

View file

@ -11,12 +11,11 @@ class PreventAccessFromCentralDomains
{
/**
* Set this property if you want to customize the on-fail behavior.
*
* @var callable|null
*/
public static $abortRequest;
public static ?Closure $abortRequest;
public function handle(Request $request, Closure $next)
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if (in_array($request->getHost(), config('tenancy.central_domains'))) {
$abortRequest = static::$abortRequest ?? function () {

View file

@ -12,7 +12,8 @@ class ScopeSessions
{
public static $tenantIdKey = '_tenant_id';
public function handle(Request $request, Closure $next)
/** @return \Illuminate\Http\Response|mixed */
public function handle(Request $request, Closure $next): mixed
{
if (! tenancy()->initialized) {
throw new TenancyNotInitializedException('Tenancy needs to be initialized before the session scoping middleware is executed');

View file

@ -65,7 +65,7 @@ abstract class CachedTenantResolver implements TenantResolver
abstract public function resolveWithoutCache(mixed ...$args): Tenant;
public function resolved(Tenant $tenant, ...$args): void
public function resolved(Tenant $tenant, mixed ...$args): void
{
}

View file

@ -24,7 +24,6 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
{
$domain = $args[0];
/** @var Tenant|null $tenant */
$tenant = config('tenancy.tenant_model')::query()
->whereHas('domains', fn (Builder $query) => $query->where('domain', $domain))
->with('domains')
@ -39,7 +38,7 @@ class DomainTenantResolver extends Contracts\CachedTenantResolver
throw new TenantCouldNotBeIdentifiedOnDomainException($args[0]);
}
public function resolved(Tenant $tenant, ...$args): void
public function resolved(Tenant $tenant, mixed ...$args): void
{
$this->setCurrentDomain($tenant, $args[0]);
}

View file

@ -23,7 +23,7 @@ class PathTenantResolver extends Contracts\CachedTenantResolver
/** @var Route $route */
$route = $args[0];
if ($id = $route->parameter(static::$tenantParameterName)) {
if ($id = (string) $route->parameter(static::$tenantParameterName)) {
$route->forgetParameter(static::$tenantParameterName);
if ($tenant = tenancy()->find($id)) {
@ -37,7 +37,7 @@ class PathTenantResolver extends Contracts\CachedTenantResolver
public function getArgsForTenant(Tenant $tenant): array
{
return [
[$tenant->id],
[$tenant->getTenantKey()],
];
}
}

View file

@ -17,7 +17,7 @@ class RequestDataTenantResolver extends Contracts\CachedTenantResolver
public function resolveWithoutCache(mixed ...$args): Tenant
{
$payload = $args[0];
$payload = (string) $args[0];
if ($payload && $tenant = tenancy()->find($payload)) {
return $tenant;
@ -29,7 +29,7 @@ class RequestDataTenantResolver extends Contracts\CachedTenantResolver
public function getArgsForTenant(Tenant $tenant): array
{
return [
[$tenant->id],
[$tenant->getTenantKey()],
];
}
}

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy;
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
@ -11,7 +12,7 @@ use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
class UUIDGenerator implements UniqueIdentifierGenerator
{
public static function generate($resource): string
public static function generate(Model $model): string
{
return Uuid::uuid4()->toString();
}

View file

@ -2,6 +2,7 @@
declare(strict_types=1);
use Stancl\Tenancy\CacheManager;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Tenancy;
@ -35,6 +36,7 @@ if (! function_exists('tenant')) {
if (! function_exists('tenant_asset')) {
// todo docblock
// todo add an option to generate paths respecting the ASSET_URL
function tenant_asset(string|null $asset): string
{
return route('stancl.tenancy.asset', ['path' => $asset]);
@ -42,17 +44,43 @@ if (! function_exists('tenant_asset')) {
}
if (! function_exists('global_asset')) {
function global_asset(string $asset) // todo types, also inside the globalUrl implementation
function global_asset(string $asset): string
{
return app('globalUrl')->asset($asset);
}
}
if (! function_exists('global_cache')) {
function global_cache()
/**
* Get / set the specified cache value in the global cache store.
*
* If an array is passed, we'll assume you want to put to the cache.
*
* @param dynamic key|key,default|data,expiration|null
* @return mixed|\Illuminate\Cache\CacheManager
*
* @throws \InvalidArgumentException
*/
function global_cache(): mixed
{
$arguments = func_get_args();
if (empty($arguments)) {
return app('globalCache');
}
if (is_string($arguments[0])) {
return app('globalCache')->get(...$arguments);
}
if (!is_array($arguments[0])) {
throw new InvalidArgumentException(
'When setting a value in the cache, you must pass an array of key / value pairs.'
);
}
return app('globalCache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1] ?? null);
}
}
if (! function_exists('tenant_route')) {

View file

@ -81,7 +81,7 @@ test('tenant can be identified by domain', function () {
test('onfail logic can be customized', function () {
InitializeTenancyByDomain::$onFail = function () {
return 'foo';
return response('foo');
};
pest()

View file

@ -9,6 +9,9 @@ use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Database\Models;
/**
* @method static static create(array $attributes = [])
*/
class Tenant extends Models\Tenant implements TenantWithDatabase
{
use HasDatabase, HasDomains;

View file

@ -50,3 +50,17 @@ test('global cache manager stores data in global cache', function () {
expect(cache('def'))->toBe('ghi');
});
test('the global_cache helper supports the same syntax as the cache helper', function () {
$tenant = Tenant::create();
$tenant->enter();
expect(cache('foo'))->toBe(null); // tenant cache is empty
global_cache(['foo' => 'bar']);
expect(global_cache('foo'))->toBe('bar');
global_cache()->set('foo', 'baz');
expect(global_cache()->get('foo'))->toBe('baz');
expect(cache('foo'))->toBe(null); // tenant cache is not affected
});

View file

@ -71,7 +71,7 @@ test('exception is thrown when tenant cannot be identified by path', function ()
test('onfail logic can be customized', function () {
InitializeTenancyByPath::$onFail = function () {
return 'foo';
return response('foo');
};
pest()

View file

@ -37,7 +37,6 @@ test('header identification works', function () {
});
test('query parameter identification works', function () {
InitializeTenancyByRequestData::$header = null;
InitializeTenancyByRequestData::$queryParameter = 'tenant';
$tenant = Tenant::create();

View file

@ -44,7 +44,7 @@ test('tenant can be identified by subdomain', function () {
test('onfail logic can be customized', function () {
InitializeTenancyBySubdomain::$onFail = function () {
return 'foo';
return response('foo');
};
pest()