mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 12:24:04 +00:00
misc improvements - stronger types, exception refactor
This commit is contained in:
parent
ddc7cf49c3
commit
55d0a9ab87
34 changed files with 179 additions and 209 deletions
|
|
@ -5,12 +5,6 @@ ARG PHP_VERSION=8.1
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source=https://github.com/stancl/tenancy \
|
|
||||||
org.opencontainers.image.vendor="Samuel Štancl" \
|
|
||||||
org.opencontainers.image.licenses="MIT" \
|
|
||||||
org.opencontainers.image.title="PHP ${PHP_VERSION} with modules for laravel support" \
|
|
||||||
org.opencontainers.image.description="PHP ${PHP_VERSION} with a set of php/os packages suitable for running Laravel apps"
|
|
||||||
|
|
||||||
# our default timezone and langauge
|
# our default timezone and langauge
|
||||||
ENV TZ=Europe/London
|
ENV TZ=Europe/London
|
||||||
ENV LANG=en_GB.UTF-8
|
ENV LANG=en_GB.UTF-8
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,16 @@ namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
use Stancl\Tenancy\DatabaseConfig;
|
use Stancl\Tenancy\DatabaseConfig;
|
||||||
|
|
||||||
|
// todo possibly move to Database namespace, along with other classes
|
||||||
|
|
||||||
interface ManagesDatabaseUsers extends TenantDatabaseManager
|
interface ManagesDatabaseUsers extends TenantDatabaseManager
|
||||||
{
|
{
|
||||||
|
/** Create a database user. */
|
||||||
public function createUser(DatabaseConfig $databaseConfig): bool;
|
public function createUser(DatabaseConfig $databaseConfig): bool;
|
||||||
|
|
||||||
|
/** Delete a database user. */
|
||||||
public function deleteUser(DatabaseConfig $databaseConfig): bool;
|
public function deleteUser(DatabaseConfig $databaseConfig): bool;
|
||||||
|
|
||||||
|
/** Does a database user exist? */
|
||||||
public function userExists(string $username): bool;
|
public function userExists(string $username): bool;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ namespace Stancl\Tenancy\Contracts;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
|
// todo move all resource syncing-related things to a separate namespace?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read Tenant[]|Collection $tenants
|
* @property-read Tenant[]|Collection $tenants
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Contracts;
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see \Stancl\Tenancy\Database\Models\Tenant
|
* @see \Stancl\Tenancy\Database\Models\Tenant
|
||||||
*
|
*
|
||||||
* @method __call(string $method, array $parameters) IDE support. This will be a model.
|
|
||||||
* @method static __callStatic(string $method, array $parameters) IDE support. This will be a model.
|
|
||||||
* @mixin \Illuminate\Database\Eloquent\Model
|
* @mixin \Illuminate\Database\Eloquent\Model
|
||||||
*/
|
*/
|
||||||
interface Tenant
|
interface Tenant
|
||||||
|
|
@ -17,14 +17,14 @@ interface Tenant
|
||||||
public function getTenantKeyName(): string;
|
public function getTenantKeyName(): string;
|
||||||
|
|
||||||
/** Get the value of the key used for identifying the tenant. */
|
/** Get the value of the key used for identifying the tenant. */
|
||||||
public function getTenantKey();
|
public function getTenantKey(): int|string;
|
||||||
|
|
||||||
/** Get the value of an internal key. */
|
/** Get the value of an internal key. */
|
||||||
public function getInternal(string $key);
|
public function getInternal(string $key): mixed;
|
||||||
|
|
||||||
/** Set the value of an internal key. */
|
/** Set the value of an internal key. */
|
||||||
public function setInternal(string $key, $value);
|
public function setInternal(string $key, mixed $value): static;
|
||||||
|
|
||||||
/** Run a callback in this tenant's environment. */
|
/** Run a callback in this tenant's environment. */
|
||||||
public function run(callable $callback);
|
public function run(Closure $callback): mixed;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,50 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Contracts;
|
namespace Stancl\Tenancy\Contracts;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Facade\IgnitionContracts\BaseSolution;
|
||||||
|
use Facade\IgnitionContracts\ProvidesSolution;
|
||||||
|
use Facade\IgnitionContracts\Solution;
|
||||||
|
|
||||||
abstract class TenantCouldNotBeIdentifiedException extends Exception
|
abstract class TenantCouldNotBeIdentifiedException extends Exception implements ProvidesSolution
|
||||||
{
|
{
|
||||||
|
/** Default solution title. */
|
||||||
|
protected string $solutionTitle = 'Tenant could not be identified';
|
||||||
|
|
||||||
|
/** Default solution description. */
|
||||||
|
protected string $solutionDescription = 'Are you sure this tenant exists?';
|
||||||
|
|
||||||
|
/** Set the message. */
|
||||||
|
protected function tenantCouldNotBeIdentified(string $how): static
|
||||||
|
{
|
||||||
|
$this->message = "Tenant could not be identified " . $how;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the solution title. */
|
||||||
|
protected function title(string $solutionTitle): static
|
||||||
|
{
|
||||||
|
$this->solutionTitle = $solutionTitle;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the solution description. */
|
||||||
|
protected function description(string $solutionDescription): static
|
||||||
|
{
|
||||||
|
$this->solutionDescription = $solutionDescription;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the Ignition description. */
|
||||||
|
public function getSolution(): Solution
|
||||||
|
{
|
||||||
|
return BaseSolution::create($this->solutionTitle)
|
||||||
|
->setSolutionDescription($this->solutionDescription)
|
||||||
|
->setDocumentationLinks([
|
||||||
|
'Tenants' => 'https://tenancyforlaravel.com/docs/v3/tenants',
|
||||||
|
'Tenant Identification' => 'https://tenancyforlaravel.com/docs/v3/tenant-identification',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,16 @@ use Stancl\Tenancy\Exceptions\NoConnectionSetException;
|
||||||
|
|
||||||
interface TenantDatabaseManager
|
interface TenantDatabaseManager
|
||||||
{
|
{
|
||||||
/**
|
/** Create a database. */
|
||||||
* Create a database.
|
|
||||||
*/
|
|
||||||
public function createDatabase(TenantWithDatabase $tenant): bool;
|
public function createDatabase(TenantWithDatabase $tenant): bool;
|
||||||
|
|
||||||
/**
|
/** Delete a database. */
|
||||||
* Delete a database.
|
|
||||||
*/
|
|
||||||
public function deleteDatabase(TenantWithDatabase $tenant): bool;
|
public function deleteDatabase(TenantWithDatabase $tenant): bool;
|
||||||
|
|
||||||
/**
|
/** Does a database exist? */
|
||||||
* Does a database exist.
|
|
||||||
*/
|
|
||||||
public function databaseExists(string $name): bool;
|
public function databaseExists(string $name): bool;
|
||||||
|
|
||||||
/**
|
/** Construct a DB connection config array. */
|
||||||
* Make a DB connection config array.
|
|
||||||
*/
|
|
||||||
public function makeConnectionConfig(array $baseConfig, string $databaseName): array;
|
public function makeConnectionConfig(array $baseConfig, string $databaseName): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ interface TenantWithDatabase extends Tenant
|
||||||
public function database(): DatabaseConfig;
|
public function database(): DatabaseConfig;
|
||||||
|
|
||||||
/** Get an internal key. */
|
/** Get an internal key. */
|
||||||
public function getInternal(string $key);
|
public function getInternal(string $key): mixed;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,20 @@ namespace Stancl\Tenancy\Database\Concerns;
|
||||||
|
|
||||||
trait HasInternalKeys
|
trait HasInternalKeys
|
||||||
{
|
{
|
||||||
/**
|
/** Get the internal prefix. */
|
||||||
* Get the internal prefix.
|
|
||||||
*/
|
|
||||||
public static function internalPrefix(): string
|
public static function internalPrefix(): string
|
||||||
{
|
{
|
||||||
return 'tenancy_';
|
return 'tenancy_';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get an internal key. */
|
||||||
* Get an internal key.
|
public function getInternal(string $key): mixed
|
||||||
*/
|
|
||||||
public function getInternal(string $key)
|
|
||||||
{
|
{
|
||||||
return $this->getAttribute(static::internalPrefix() . $key);
|
return $this->getAttribute(static::internalPrefix() . $key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Set internal key. */
|
||||||
* Set internal key.
|
public function setInternal(string $key, mixed $value): static
|
||||||
*/
|
|
||||||
public function setInternal(string $key, $value)
|
|
||||||
{
|
{
|
||||||
$this->setAttribute(static::internalPrefix() . $key, $value);
|
$this->setAttribute(static::internalPrefix() . $key, $value);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,17 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Database\Concerns;
|
namespace Stancl\Tenancy\Database\Concerns;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
trait TenantRun
|
trait TenantRun
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Run a callback in this tenant's context.
|
* Run a callback in this tenant's context.
|
||||||
* Atomic, safely reverts to previous context.
|
*
|
||||||
|
* This method is atomic and safely reverts to the previous context.
|
||||||
*/
|
*/
|
||||||
public function run(callable $callback)
|
public function run(Closure $callback): mixed
|
||||||
{
|
{
|
||||||
/** @var Tenant $this */
|
/** @var Tenant $this */
|
||||||
$originalTenant = tenant();
|
$originalTenant = tenant();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
|
||||||
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
|
use Stancl\Tenancy\Exceptions\TenantDatabaseAlreadyExistsException;
|
||||||
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
|
use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
|
||||||
|
|
||||||
|
// todo move to Database namespace
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,8 @@ class ImpersonationToken extends Model
|
||||||
'created_at',
|
'created_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function boot()
|
public static function booted()
|
||||||
{
|
{
|
||||||
parent::boot();
|
|
||||||
|
|
||||||
static::creating(function ($model) {
|
static::creating(function ($model) {
|
||||||
$model->created_at = $model->created_at ?? $model->freshTimestamp();
|
$model->created_at = $model->created_at ?? $model->freshTimestamp();
|
||||||
$model->token = $model->token ?? Str::random(128);
|
$model->token = $model->token ?? Str::random(128);
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class Tenant extends Model implements Contracts\Tenant
|
||||||
return 'id';
|
return 'id';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTenantKey()
|
public function getTenantKey(): int|string
|
||||||
{
|
{
|
||||||
return $this->getAttribute($this->getTenantKeyName());
|
return $this->getAttribute($this->getTenantKeyName());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ use Stancl\Tenancy\Contracts\Syncable;
|
||||||
|
|
||||||
class TenantPivot extends Pivot
|
class TenantPivot extends Pivot
|
||||||
{
|
{
|
||||||
public static function boot()
|
public static function booted()
|
||||||
{
|
{
|
||||||
parent::boot();
|
|
||||||
|
|
||||||
static::saved(function (self $pivot) {
|
static::saved(function (self $pivot) {
|
||||||
$parent = $pivot->pivotParent;
|
$parent = $pivot->pivotParent;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Scope;
|
||||||
|
|
||||||
class ParentModelScope implements Scope
|
class ParentModelScope implements Scope
|
||||||
{
|
{
|
||||||
public function apply(Builder $builder, Model $model)
|
public function apply(Builder $builder, Model $model): void
|
||||||
{
|
{
|
||||||
if (! tenancy()->initialized) {
|
if (! tenancy()->initialized) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Database;
|
namespace Stancl\Tenancy\Database;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ use Stancl\Tenancy\Contracts\Tenant;
|
||||||
*/
|
*/
|
||||||
class TenantCollection extends Collection
|
class TenantCollection extends Collection
|
||||||
{
|
{
|
||||||
public function runForEach(callable $callable): self
|
public function runForEach(Closure $callable): self
|
||||||
{
|
{
|
||||||
tenancy()->runForMultiple($this->items, $callable);
|
tenancy()->runForMultiple($this->items, $callable);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy;
|
namespace Stancl\Tenancy;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
@ -48,17 +49,17 @@ class DatabaseConfig
|
||||||
$this->tenant = $tenant;
|
$this->tenant = $tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateDatabaseNamesUsing(callable $databaseNameGenerator): void
|
public static function generateDatabaseNamesUsing(Closure $databaseNameGenerator): void
|
||||||
{
|
{
|
||||||
static::$databaseNameGenerator = $databaseNameGenerator;
|
static::$databaseNameGenerator = $databaseNameGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateUsernamesUsing(callable $usernameGenerator): void
|
public static function generateUsernamesUsing(Closure $usernameGenerator): void
|
||||||
{
|
{
|
||||||
static::$usernameGenerator = $usernameGenerator;
|
static::$usernameGenerator = $usernameGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generatePasswordsUsing(callable $passwordGenerator): void
|
public static function generatePasswordsUsing(Closure $passwordGenerator): void
|
||||||
{
|
{
|
||||||
static::$passwordGenerator = $passwordGenerator;
|
static::$passwordGenerator = $passwordGenerator;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Exceptions;
|
|
||||||
|
|
||||||
use Facade\IgnitionContracts\BaseSolution;
|
|
||||||
use Facade\IgnitionContracts\ProvidesSolution;
|
|
||||||
use Facade\IgnitionContracts\Solution;
|
|
||||||
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
|
||||||
|
|
||||||
// todo: in v4 this should be suffixed with Exception
|
|
||||||
class TenantCouldNotBeIdentifiedById extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
|
|
||||||
{
|
|
||||||
public function __construct($tenant_id)
|
|
||||||
{
|
|
||||||
parent::__construct("Tenant could not be identified with tenant_id: $tenant_id");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSolution(): Solution
|
|
||||||
{
|
|
||||||
return BaseSolution::create('Tenant could not be identified with that ID')
|
|
||||||
->setSolutionDescription('Are you sure the ID is correct and the tenant exists?')
|
|
||||||
->setDocumentationLinks([
|
|
||||||
'Initializing Tenants' => 'https://tenancyforlaravel.com/docs/v3/tenants',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
src/Exceptions/TenantCouldNotBeIdentifiedByIdException.php
Normal file
18
src/Exceptions/TenantCouldNotBeIdentifiedByIdException.php
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
|
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||||
|
|
||||||
|
class TenantCouldNotBeIdentifiedByIdException extends TenantCouldNotBeIdentifiedException
|
||||||
|
{
|
||||||
|
public function __construct(int|string $tenant_id)
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->tenantCouldNotBeIdentified("by tenant id: $tenant_id")
|
||||||
|
->title('Tenant could not be identified with that ID')
|
||||||
|
->description('Are you sure the ID is correct and the tenant exists?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,24 +4,15 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Exceptions;
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
use Facade\IgnitionContracts\BaseSolution;
|
|
||||||
use Facade\IgnitionContracts\ProvidesSolution;
|
|
||||||
use Facade\IgnitionContracts\Solution;
|
|
||||||
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||||
|
|
||||||
class TenantCouldNotBeIdentifiedByPathException extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
|
class TenantCouldNotBeIdentifiedByPathException extends TenantCouldNotBeIdentifiedException
|
||||||
{
|
{
|
||||||
public function __construct($tenant_id)
|
public function __construct(int|string $tenant_id)
|
||||||
{
|
{
|
||||||
parent::__construct("Tenant could not be identified on path with tenant_id: $tenant_id");
|
$this
|
||||||
}
|
->tenantCouldNotBeIdentified("on path with tenant id: $tenant_id")
|
||||||
|
->title('Tenant could not be identified on this path')
|
||||||
public function getSolution(): Solution
|
->description('Did you forget to create a tenant for this path?');
|
||||||
{
|
|
||||||
return BaseSolution::create('Tenant could not be identified on this path')
|
|
||||||
->setSolutionDescription('Did you forget to create a tenant for this path?')
|
|
||||||
->setDocumentationLinks([
|
|
||||||
'Creating Tenants' => 'https://tenancyforlaravel.com/docs/v3/tenants/',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,15 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Exceptions;
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
use Facade\IgnitionContracts\BaseSolution;
|
|
||||||
use Facade\IgnitionContracts\ProvidesSolution;
|
|
||||||
use Facade\IgnitionContracts\Solution;
|
|
||||||
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||||
|
|
||||||
class TenantCouldNotBeIdentifiedByRequestDataException extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
|
class TenantCouldNotBeIdentifiedByRequestDataException extends TenantCouldNotBeIdentifiedException
|
||||||
{
|
{
|
||||||
public function __construct($tenant_id)
|
public function __construct(mixed $payload)
|
||||||
{
|
{
|
||||||
parent::__construct("Tenant could not be identified by request data with payload: $tenant_id");
|
$this
|
||||||
}
|
->tenantCouldNotBeIdentified("by request data with payload: $payload")
|
||||||
|
->title('Tenant could not be identified using this request data')
|
||||||
public function getSolution(): Solution
|
->description('Did you forget to create a tenant with this id?');
|
||||||
{
|
|
||||||
return BaseSolution::create('Tenant could not be identified with this request data')
|
|
||||||
->setSolutionDescription('Did you forget to create a tenant with this id?')
|
|
||||||
->setDocumentationLinks([
|
|
||||||
'Creating Tenants' => 'https://tenancyforlaravel.com/docs/v3/tenants/',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,15 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Exceptions;
|
namespace Stancl\Tenancy\Exceptions;
|
||||||
|
|
||||||
use Facade\IgnitionContracts\BaseSolution;
|
|
||||||
use Facade\IgnitionContracts\ProvidesSolution;
|
|
||||||
use Facade\IgnitionContracts\Solution;
|
|
||||||
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException;
|
||||||
|
|
||||||
class TenantCouldNotBeIdentifiedOnDomainException extends TenantCouldNotBeIdentifiedException implements ProvidesSolution
|
class TenantCouldNotBeIdentifiedOnDomainException extends TenantCouldNotBeIdentifiedException
|
||||||
{
|
{
|
||||||
public function __construct($domain)
|
public function __construct(string $domain)
|
||||||
{
|
{
|
||||||
parent::__construct("Tenant could not be identified on domain $domain");
|
$this
|
||||||
}
|
->tenantCouldNotBeIdentified("on domain $domain")
|
||||||
|
->title('Tenant could not be identified on this domain')
|
||||||
public function getSolution(): Solution
|
->description('Did you forget to create a tenant for this domain?');
|
||||||
{
|
|
||||||
return BaseSolution::create('Tenant could not be identified on this domain')
|
|
||||||
->setSolutionDescription('Did you forget to create a tenant for this domain?')
|
|
||||||
->setDocumentationLinks([
|
|
||||||
'Creating Tenants' => 'https://tenancyforlaravel.com/docs/v3/tenants/',
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,14 @@ use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
||||||
|
|
||||||
class TenantDatabaseAlreadyExistsException extends TenantCannotBeCreatedException
|
class TenantDatabaseAlreadyExistsException extends TenantCannotBeCreatedException
|
||||||
{
|
{
|
||||||
/** @var string */
|
public function __construct(
|
||||||
protected $database;
|
protected string $database,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
public function reason(): string
|
public function reason(): string
|
||||||
{
|
{
|
||||||
return "Database {$this->database} already exists.";
|
return "Database {$this->database} already exists.";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(string $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use Exception;
|
||||||
|
|
||||||
class TenantDatabaseDoesNotExistException extends Exception
|
class TenantDatabaseDoesNotExistException extends Exception
|
||||||
{
|
{
|
||||||
public function __construct($database)
|
public function __construct(string $database)
|
||||||
{
|
{
|
||||||
parent::__construct("Database $database does not exist.");
|
parent::__construct("Database $database does not exist.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,14 @@ use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
||||||
|
|
||||||
class TenantDatabaseUserAlreadyExistsException extends TenantCannotBeCreatedException
|
class TenantDatabaseUserAlreadyExistsException extends TenantCannotBeCreatedException
|
||||||
{
|
{
|
||||||
/** @var string */
|
public function __construct(
|
||||||
protected $user;
|
protected string $user,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
public function reason(): string
|
public function reason(): string
|
||||||
{
|
{
|
||||||
return "Database user {$this->user} already exists.";
|
return "Database user {$this->user} already exists.";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(string $user)
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class CrossDomainRedirect implements Feature
|
||||||
RedirectResponse::macro('domain', function (string $domain) {
|
RedirectResponse::macro('domain', function (string $domain) {
|
||||||
/** @var RedirectResponse $this */
|
/** @var RedirectResponse $this */
|
||||||
|
|
||||||
// replace first occurance of hostname fragment with $domain
|
// Replace first occurrence of the hostname fragment with $domain
|
||||||
$url = $this->getTargetUrl();
|
$url = $this->getTargetUrl();
|
||||||
$hostname = parse_url($url, PHP_URL_HOST);
|
$hostname = parse_url($url, PHP_URL_HOST);
|
||||||
$position = strpos($url, $hostname);
|
$position = strpos($url, $hostname);
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class UniversalRoutes implements Feature
|
class UniversalRoutes implements Feature
|
||||||
{
|
{
|
||||||
public static $middlewareGroup = 'universal';
|
public static string $middlewareGroup = 'universal';
|
||||||
|
|
||||||
public static $identificationMiddlewares = [
|
// todo docblock
|
||||||
|
public static array $identificationMiddlewares = [
|
||||||
Middleware\InitializeTenancyByDomain::class,
|
Middleware\InitializeTenancyByDomain::class,
|
||||||
Middleware\InitializeTenancyBySubdomain::class,
|
Middleware\InitializeTenancyBySubdomain::class,
|
||||||
];
|
];
|
||||||
|
|
@ -39,7 +40,7 @@ class UniversalRoutes implements Feature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function routeHasMiddleware(Route $route, $middleware): bool
|
public static function routeHasMiddleware(Route $route, string $middleware): bool
|
||||||
{
|
{
|
||||||
if (in_array($middleware, $route->middleware(), true)) {
|
if (in_array($middleware, $route->middleware(), true)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Features;
|
namespace Stancl\Tenancy\Features;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Stancl\Tenancy\Contracts\Feature;
|
use Stancl\Tenancy\Contracts\Feature;
|
||||||
|
|
@ -14,7 +13,8 @@ use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
class UserImpersonation implements Feature
|
class UserImpersonation implements Feature
|
||||||
{
|
{
|
||||||
public static $ttl = 60; // seconds
|
/** The lifespan of impersonation tokens (in seconds). */
|
||||||
|
public static int $ttl = 60;
|
||||||
|
|
||||||
public function bootstrap(Tenancy $tenancy): void
|
public function bootstrap(Tenancy $tenancy): void
|
||||||
{
|
{
|
||||||
|
|
@ -28,25 +28,20 @@ class UserImpersonation implements Feature
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Impersonate a user and get an HTTP redirect response. */
|
||||||
* Impersonate a user and get an HTTP redirect response.
|
public static function makeResponse(string|ImpersonationToken $token, int $ttl = null): RedirectResponse
|
||||||
*
|
|
||||||
* @param string|ImpersonationToken $token
|
|
||||||
* @param int $ttl
|
|
||||||
*/
|
|
||||||
public static function makeResponse($token, int $ttl = null): RedirectResponse
|
|
||||||
{
|
{
|
||||||
$token = $token instanceof ImpersonationToken ? $token : ImpersonationToken::findOrFail($token);
|
$token = $token instanceof ImpersonationToken ? $token : ImpersonationToken::findOrFail($token);
|
||||||
|
$ttl ??= static::$ttl;
|
||||||
|
|
||||||
if (((string) $token->tenant_id) !== ((string) tenant()->getTenantKey())) {
|
$tokenExpired = $token->created_at->diffInSeconds(now()) > $ttl;
|
||||||
abort(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
$ttl = $ttl ?? static::$ttl;
|
abort_if($tokenExpired, 403);
|
||||||
|
|
||||||
if ($token->created_at->diffInSeconds(Carbon::now()) > $ttl) {
|
$tokenTenantId = (string) $token->tenant_id;
|
||||||
abort(403);
|
$currentTenantId = (string) tenant()->getTenantKey();
|
||||||
}
|
|
||||||
|
abort_unless($tokenTenantId === $currentTenantId, 403);
|
||||||
|
|
||||||
Auth::guard($token->auth_guard)->loginUsingId($token->user_id);
|
Auth::guard($token->auth_guard)->loginUsingId($token->user_id);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,11 @@ use Stancl\Tenancy\Contracts\TenantResolver;
|
||||||
|
|
||||||
abstract class CachedTenantResolver implements TenantResolver
|
abstract class CachedTenantResolver implements TenantResolver
|
||||||
{
|
{
|
||||||
/** @var bool */
|
public static bool $shouldCache = false; // todo docblocks for these
|
||||||
public static $shouldCache = false;
|
|
||||||
|
|
||||||
/** @var int */
|
public static int $cacheTTL = 3600; // seconds
|
||||||
public static $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
/** @var string|null */
|
public static string|null $cacheStore = null; // default
|
||||||
public static $cacheStore = null; // default
|
|
||||||
|
|
||||||
/** @var Repository */
|
/** @var Repository */
|
||||||
protected $cache;
|
protected $cache;
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,14 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
|
||||||
|
|
||||||
class DomainTenantResolver extends Contracts\CachedTenantResolver
|
class DomainTenantResolver extends Contracts\CachedTenantResolver
|
||||||
{
|
{
|
||||||
/**
|
/** The model representing the domain that the tenant was identified on. */
|
||||||
* The model representing the domain that the tenant was identified on.
|
public static Domain $currentDomain; // todo |null?
|
||||||
*
|
|
||||||
* @var Domain
|
|
||||||
*/
|
|
||||||
public static $currentDomain;
|
|
||||||
|
|
||||||
/** @var bool */
|
public static bool $shouldCache = false;
|
||||||
public static $shouldCache = false;
|
|
||||||
|
|
||||||
/** @var int */
|
public static int $cacheTTL = 3600; // seconds
|
||||||
public static $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
/** @var string|null */
|
public static string|null $cacheStore = null; // default
|
||||||
public static $cacheStore = null; // default
|
|
||||||
|
|
||||||
public function resolveWithoutCache(...$args): Tenant
|
public function resolveWithoutCache(...$args): Tenant
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,13 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException;
|
||||||
|
|
||||||
class PathTenantResolver extends Contracts\CachedTenantResolver
|
class PathTenantResolver extends Contracts\CachedTenantResolver
|
||||||
{
|
{
|
||||||
public static $tenantParameterName = 'tenant';
|
public static string $tenantParameterName = 'tenant';
|
||||||
|
|
||||||
/** @var bool */
|
public static bool $shouldCache = false;
|
||||||
public static $shouldCache = false;
|
|
||||||
|
|
||||||
/** @var int */
|
public static int $cacheTTL = 3600; // seconds
|
||||||
public static $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
/** @var string|null */
|
public static string|null $cacheStore = null; // default
|
||||||
public static $cacheStore = null; // default
|
|
||||||
|
|
||||||
public function resolveWithoutCache(...$args): Tenant
|
public function resolveWithoutCache(...$args): Tenant
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,11 @@ use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByRequestDataException;
|
||||||
|
|
||||||
class RequestDataTenantResolver extends Contracts\CachedTenantResolver
|
class RequestDataTenantResolver extends Contracts\CachedTenantResolver
|
||||||
{
|
{
|
||||||
/** @var bool */
|
public static bool $shouldCache = false;
|
||||||
public static $shouldCache = false;
|
|
||||||
|
|
||||||
/** @var int */
|
public static int $cacheTTL = 3600; // seconds
|
||||||
public static $cacheTTL = 3600; // seconds
|
|
||||||
|
|
||||||
/** @var string|null */
|
public static string|null $cacheStore = null; // default
|
||||||
public static $cacheStore = null; // default
|
|
||||||
|
|
||||||
public function resolveWithoutCache(...$args): Tenant
|
public function resolveWithoutCache(...$args): Tenant
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use Illuminate\Support\Traits\Macroable;
|
||||||
use Stancl\Tenancy\Concerns\Debuggable;
|
use Stancl\Tenancy\Concerns\Debuggable;
|
||||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedById;
|
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByIdException;
|
||||||
|
|
||||||
class Tenancy
|
class Tenancy
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +38,7 @@ class Tenancy
|
||||||
$tenant = $this->find($tenantId);
|
$tenant = $this->find($tenantId);
|
||||||
|
|
||||||
if (! $tenant) {
|
if (! $tenant) {
|
||||||
throw new TenantCouldNotBeIdentifiedById($tenantId);
|
throw new TenantCouldNotBeIdentifiedByIdException($tenantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,17 +62,17 @@ class Tenancy
|
||||||
|
|
||||||
public function end(): void
|
public function end(): void
|
||||||
{
|
{
|
||||||
event(new Events\EndingTenancy($this));
|
|
||||||
|
|
||||||
if (! $this->initialized) {
|
if (! $this->initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event(new Events\TenancyEnded($this));
|
event(new Events\EndingTenancy($this));
|
||||||
|
|
||||||
|
$this->tenant = null;
|
||||||
|
|
||||||
$this->initialized = false;
|
$this->initialized = false;
|
||||||
|
|
||||||
$this->tenant = null;
|
event(new Events\TenancyEnded($this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return TenancyBootstrapper[] */
|
/** @return TenancyBootstrapper[] */
|
||||||
|
|
@ -131,7 +131,7 @@ class Tenancy
|
||||||
*
|
*
|
||||||
* @param Tenant[]|\Traversable|string[]|null $tenants
|
* @param Tenant[]|\Traversable|string[]|null $tenants
|
||||||
*/
|
*/
|
||||||
public function runForMultiple($tenants, callable $callback): void
|
public function runForMultiple($tenants, Closure $callback): void
|
||||||
{
|
{
|
||||||
// Convert null to all tenants
|
// Convert null to all tenants
|
||||||
$tenants = is_null($tenants) ? $this->model()->cursor() : $tenants;
|
$tenants = is_null($tenants) ? $this->model()->cursor() : $tenants;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// todo likely move all of these classes to Database\
|
||||||
|
|
||||||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||||
|
|
||||||
use Illuminate\Database\Connection;
|
use Illuminate\Database\Connection;
|
||||||
|
|
@ -12,10 +14,9 @@ use Stancl\Tenancy\Exceptions\NoConnectionSetException;
|
||||||
|
|
||||||
class MicrosoftSQLDatabaseManager implements TenantDatabaseManager
|
class MicrosoftSQLDatabaseManager implements TenantDatabaseManager
|
||||||
{
|
{
|
||||||
/** @var string */
|
protected string $connection; // todo docblock, in all of these classes
|
||||||
protected $connection;
|
|
||||||
|
|
||||||
protected function database(): Connection
|
protected function database(): Connection // todo consider abstracting this method & setConnection() into a base class
|
||||||
{
|
{
|
||||||
if ($this->connection === null) {
|
if ($this->connection === null) {
|
||||||
throw new NoConnectionSetException(static::class);
|
throw new NoConnectionSetException(static::class);
|
||||||
|
|
@ -33,7 +34,7 @@ class MicrosoftSQLDatabaseManager implements TenantDatabaseManager
|
||||||
{
|
{
|
||||||
$database = $tenant->database()->getName();
|
$database = $tenant->database()->getName();
|
||||||
$charset = $this->database()->getConfig('charset');
|
$charset = $this->database()->getConfig('charset');
|
||||||
$collation = $this->database()->getConfig('collation');
|
$collation = $this->database()->getConfig('collation'); // todo check why these are not used
|
||||||
|
|
||||||
return $this->database()->statement("CREATE DATABASE [{$database}]");
|
return $this->database()->statement("CREATE DATABASE [{$database}]");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,23 +157,25 @@ class AnotherTenant extends Model implements Contracts\Tenant
|
||||||
return 'id';
|
return 'id';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTenantKey()
|
public function getTenantKey(): int|string
|
||||||
{
|
{
|
||||||
return $this->getAttribute('id');
|
return $this->getAttribute('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function run(callable $callback)
|
public function run(Closure $callback): mixed
|
||||||
{
|
{
|
||||||
$callback();
|
$callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInternal(string $key)
|
public function getInternal(string $key): mixed
|
||||||
{
|
{
|
||||||
return $this->$key;
|
return $this->$key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setInternal(string $key, $value)
|
public function setInternal(string $key, mixed $value): static
|
||||||
{
|
{
|
||||||
$this->$key = $value;
|
$this->$key = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue