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

Refactor more old code and get tests to pass

This commit is contained in:
Samuel Štancl 2020-05-13 04:51:37 +02:00
parent c5377a16f7
commit c32f229dd5
72 changed files with 425 additions and 531 deletions

View file

@ -2,20 +2,36 @@
namespace App\Providers;
use Closure;
use Stancl\Tenancy\Contracts\Tenant;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
use Stancl\Tenancy\Events\DatabaseCreated;
use Stancl\Tenancy\Events\DatabaseDeleted;
use Stancl\Tenancy\Events\DatabaseMigrated;
use Stancl\Tenancy\Events\DatabaseRolledBack;
use Stancl\Tenancy\Events\DatabaseSeeded;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\DomainCreated;
use Stancl\Tenancy\Events\DomainDeleted;
use Stancl\Tenancy\Events\DomainSaved;
use Stancl\Tenancy\Events\DomainUpdated;
use Stancl\Tenancy\Events\RevertedToCentralContext;
use Stancl\Tenancy\Events\TenancyBootstrapped;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Events\TenantDeleted;
use Stancl\Tenancy\Events\TenantSaved;
use Stancl\Tenancy\Events\TenantUpdated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Jobs\DeleteDatabase;
use Stancl\Tenancy\Jobs\MigrateDatabase;
use Stancl\Tenancy\Jobs\SeedDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tenancy;
class TenancyServiceProvider extends ServiceProvider
{
@ -25,35 +41,82 @@ class TenancyServiceProvider extends ServiceProvider
TenantCreated::class => [
JobPipeline::make([
CreateDatabase::class,
MigrateDatabase::class, // triggers DatabaseMigrated event
MigrateDatabase::class,
SeedDatabase::class,
// Your own jobs to prepare the tenant.
// Provision API keys, create S3 buckets, anything you want!
])->send(function (TenantCreated $event) {
return $event->tenant;
})->queue(true),
})->queue(false), // `false` by default, but you probably want to make this `true` for production.
],
DatabaseCreated::class => [],
DatabaseMigrated::class => [],
DatabaseSeeded::class => [],
TenantSaved::class => [],
TenantUpdated::class => [],
TenantDeleted::class => [
JobPipeline::make([
DeleteDatabase::class,
])->send(function (TenantDeleted $event) {
return $event->tenant;
})->queue(true),
// DeleteStorage::class,
})->queue(false), // `false` by default, but you probably want to make this `true` for production.
],
DomainCreated::class => [],
DomainSaved::class => [],
DomainUpdated::class => [],
DomainDeleted::class => [],
DatabaseCreated::class => [],
DatabaseMigrated::class => [],
DatabaseSeeded::class => [],
DatabaseRolledBack::class => [],
DatabaseDeleted::class => [],
TenancyInitialized::class => [
BootstrapTenancy::class,
],
TenancyEnded::class => [
RevertToCentralContext::class,
],
TenancyBootstrapped::class => [],
RevertedToCentralContext::class => [],
];
}
public function register()
{
//
// Make sure Tenancy is stateful.
$this->app->singleton(Tenancy::class);
// Make sure features are bootstrapped as soon as Tenancy is instantiated.
$this->app->extend(Tenancy::class, function (Tenancy $tenancy) {
foreach ($this->app['config']['tenancy.features'] as $feature) {
$this->app[$feature]->bootstrap($tenancy);
}
return $tenancy;
});
// Make it possible to inject the current tenant by typehinting the Tenant contract.
$this->app->bind(Tenant::class, function ($app) {
return $app[Tenancy::class]->tenant;
});
// Make sure bootstrappers are stateful (singletons).
foreach ($this->app['config']['tenancy.bootstrappers'] as $bootstrapper) {
$this->app->singleton($bootstrapper);
}
// Bind the class in the tenancy.id_generator config to the UniqueIdentifierGenerator abstract.
$this->app->bind(UniqueIdentifierGenerator::class, $this->app['config']['tenancy.id_generator']);
}
public function boot()
{
$this->bootEvents();
$this->mapRoutes();
//
}
@ -70,4 +133,15 @@ class TenancyServiceProvider extends ServiceProvider
}
}
}
protected function mapRoutes()
{
$this->app->booted(function () {
if (file_exists(base_path('routes/tenant.php'))) {
Route::middleware(['web'])
->namespace($this->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
->group(base_path('routes/tenant.php'));
}
});
}
}

View file

@ -24,23 +24,6 @@ return [
'localhost',
],
'storage' => [
'data_column' => 'data',
'custom_columns' => [
// 'plan',
],
/**
* Here you can enable the Cached Tenant Lookup.
*
* You can specify what cache store should be used to cache the tenant resolution.
* Set to string with a specific cache store name, or to null to disable cache.
*/
'cache_store' => null, // env('CACHE_DRIVER')
'cache_ttl' => 3600, // seconds
],
/**
* Controller namespace used by routes in routes/tenant.php.
*/
@ -76,7 +59,6 @@ return [
*/
'prefix' => 'tenant',
'suffix' => '',
// todo get rid of this stuff, just set the closure instead
],
/**
@ -194,18 +176,11 @@ return [
* See the documentation page for each class to
* understand which ones you want to enable.
*/
'features' => [ // todo test features
// Stancl\Tenancy\Features\Timestamps::class, // https://tenancy.samuelstancl.me/docs/v2/features/timestamps/
'features' => [
// Stancl\Tenancy\Features\TenantConfig::class, // https://tenancy.samuelstancl.me/docs/v2/features/tenant-config/
// Stancl\Tenancy\Features\TelescopeTags::class, // https://tenancy.samuelstancl.me/docs/v2/telescope/
// Stancl\Tenancy\Features\CrossDomainRedirect::class, // https://tenancy.samuelstancl.me/docs/v2/features/tenant-redirect/
],
/**
* The URL to which users will be redirected when they try to acceess a central route on a tenant domain.
*/
'home_url' => '/app', // todo move this to static
'migration_parameters' => [
'--force' => true, // Set this to true to be able to run migrations in production
'--path' => [database_path('migrations/tenant')],

View file

@ -7,10 +7,17 @@
|
| Here you can register the tenant routes for your application.
| These routes are loaded by the TenantRouteServiceProvider
| with the tenancy and web middleware groups. Good luck!
| with the namespace configured in your tenancy config.
|
| Feel free to customize them however you want. Good luck!
|
*/
Route::get('/app', function () {
Route::group([
'middleware' => InitializeTenancyByDomain::class,
'prefix' => '/app',
], function () {
Route::get('/', function () {
return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
});
});

View file

@ -9,6 +9,7 @@ use Illuminate\Database\Console\Migrations\RollbackCommand;
use Illuminate\Database\Migrations\Migrator;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\DatabaseManager;
use Stancl\Tenancy\Events\DatabaseRolledBack;
use Stancl\Tenancy\Traits\DealsWithMigrations;
use Stancl\Tenancy\Traits\HasATenantsOption;
@ -62,7 +63,7 @@ class Rollback extends RollbackCommand
// Rollback
parent::handle();
// todo DatabaseRolledBack event
event(new DatabaseRolledBack($tenant));
});
}
}

View file

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Contracts;
use Stancl\Tenancy\Facades\Tenancy;
use Stancl\Tenancy\Tenancy;
/** Additional features, like Telescope tags and tenant redirects. */
interface Feature

View file

@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Contracts\Future;
use Stancl\Tenancy\Tenant;
/**
* This interface will be part of the StorageDriver interface in 3.x.
*/
interface CanDeleteKeys
{
/**
* Delete keys from the storage.
*
* @param string[] $keys
* @return void
*/
public function deleteMany(array $keys, Tenant $tenant = null): void;
}

View file

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Contracts\Future;
use Stancl\Tenancy\Exceptions\TenantDoesNotExistException;
use Stancl\Tenancy\Tenant;
/**
* This interface *might* be part of the StorageDriver interface in 3.x.
*/
interface CanFindByAnyKey
{
/**
* Find a tenant using an arbitrary key.
*
* @param string $key
* @param mixed $value
* @return Tenant
* @throws TenantDoesNotExistException
*/
public function findBy(string $key, $value): Tenant;
}

View file

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Contracts\Future;
/**
* This interface *might* be part of the TenantDatabaseManager interface in 3.x.
*/
interface CanSetConnection
{
public function setConnection(string $connection): void;
}

View file

@ -9,8 +9,7 @@ namespace Stancl\Tenancy\Contracts;
*/
interface TenancyBootstrapper
{
// todo rename methods
public function start(Tenant $tenant);
public function bootstrap(Tenant $tenant);
public function end();
public function revert();
}

View file

@ -32,4 +32,12 @@ interface TenantDatabaseManager
* @return array
*/
public function makeConnectionConfig(array $baseConfig, string $databaseName): array;
/**
* Set the DB connection that should be used by the tenant database manager.
*
* @param string $connection
* @return void
*/
public function setConnection(string $connection): void;
}

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Database\Models\Concerns;
namespace Stancl\Tenancy\Database\Concerns;
trait CentralConnection
{

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Database\Models\Concerns;
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
@ -14,4 +14,9 @@ trait GeneratesIds
}
});
}
public function getIncrementing()
{
return ! app()->bound(UniqueIdentifierGenerator::class);
}
}

View file

@ -1,9 +1,7 @@
<?php
// todo move namespace one dir above
namespace Stancl\Tenancy\Database\Models\Concerns;
namespace Stancl\Tenancy\Database\Concerns;
// todo rename
trait HasADataColumn
{
public static $priorityListeners = [];
@ -53,11 +51,17 @@ trait HasADataColumn
$model->dataEncodingStatus = 'decoded';
};
static::registerPriorityListener('retrieved', $decode);
static::registerPriorityListener('retrieved', function ($model) use ($decode) {
// We always decode after model retrieval.
$model->dataEncodingStatus = 'encoded';
$decode($model);
});
static::registerPriorityListener('saving', $encode);
static::registerPriorityListener('creating', $encode);
static::registerPriorityListener('updating', $encode);
static::registerPriorityListener('saved', $decode);
static::registerPriorityListener('created', $decode);
static::registerPriorityListener('updated', $decode);

View file

@ -0,0 +1,16 @@
<?php
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\Domain;
/**
* @property-read Domain[] $domains
*/
trait HasDomains
{
public function domains()
{
return $this->hasMany(config('tenancy.domain_model'), 'tenant_id');
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Database\Models\Concerns;
namespace Stancl\Tenancy\Database\Concerns;
/**
* @property-read string $primary_domain_hostname

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Database\Models\Concerns;
namespace Stancl\Tenancy\Database\Concerns;
use Stancl\Tenancy\Contracts\Syncable;
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Database\Models\Concerns;
namespace Stancl\Tenancy\Database\Concerns;
trait TenantConnection
{

View file

@ -1,11 +0,0 @@
<?php
namespace Stancl\Tenancy\Database\Models\Concerns;
trait HasDomains
{
public function domains()
{
return $this->hasMany(config('tenancy.domain_model'));
}
}

View file

@ -2,18 +2,30 @@
namespace Stancl\Tenancy\Database\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Stancl\Tenancy\DatabaseConfig;
use Stancl\Tenancy\Events;
use Stancl\Tenancy\Contracts;
use Stancl\Tenancy\Database\Concerns;
// todo @property
class Tenant extends Model implements Contracts\TenantWithDatabase
/**
* @property string|int $id
* @property Carbon $created_at
* @property Carbon $updated_at
* @property array $data
*/
class Tenant extends Model implements Contracts\TenantWithDatabase // todo base model that isn't TenantWithDatabase & domains
{
use Concerns\CentralConnection, Concerns\HasADataColumn, Concerns\GeneratesIds, Concerns\HasADataColumn {
use Concerns\CentralConnection,
Concerns\HasADataColumn,
Concerns\GeneratesIds,
Concerns\HasADataColumn,
Concerns\HasDomains {
Concerns\HasADataColumn::getCasts as dataColumnCasts;
}
protected $table = 'tenants';
public $primaryKey = 'id';
public $guarded = [];
@ -34,11 +46,6 @@ class Tenant extends Model implements Contracts\TenantWithDatabase
]);
}
public function getIncrementing()
{
return config('tenancy.id_generator') === null;
}
public static function internalPrefix(): string
{
return config('tenancy.internal_prefix');

View file

@ -6,9 +6,7 @@ namespace Stancl\Tenancy;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
use Stancl\Tenancy\Contracts\ManagesDatabaseUsers;
use Stancl\Tenancy\Contracts\ModifiesDatabaseNameForConnection;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Exceptions\DatabaseManagerNotRegisteredException;
@ -79,6 +77,11 @@ class DatabaseConfig
return $this->tenant->getInternal('db_password') ?? null;
}
/**
* Generate DB name, username & password and write them to the tenant model.
*
* @return void
*/
public function makeCredentials(): void
{
$this->tenant->setInternal('db_name', $this->getName() ?? (static::$databaseNameGenerator)($this->tenant));
@ -151,9 +154,7 @@ class DatabaseConfig
/** @var TenantDatabaseManager $databaseManager */
$databaseManager = app($databaseManagers[$driver]);
if ($databaseManager instanceof CanSetConnection) {
$databaseManager->setConnection($this->getTemplateConnectionName());
}
return $databaseManager;
}

View file

@ -0,0 +1,6 @@
<?php
namespace Stancl\Tenancy\Events;
class DatabaseRolledBack extends Contracts\TenantEvent
{}

View file

@ -0,0 +1,16 @@
<?php
namespace Stancl\Tenancy\Events;
use Stancl\Tenancy\Tenancy;
class RevertedToCentralContext
{
/** @var Tenancy */
public $tenancy;
public function __construct(Tenancy $tenancy)
{
$this->tenancy = $tenancy;
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Stancl\Tenancy\Events;
use Stancl\Tenancy\Tenancy;
class TenancyBootstrapped
{
/** @var Tenancy */
public $tenancy;
public function __construct(Tenancy $tenancy)
{
$this->tenancy = $tenancy;
}
}

View file

@ -5,12 +5,11 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Facades;
use Illuminate\Support\Facades\Facade;
use Stancl\Tenancy\TenantManager;
class Tenancy extends Facade
{
protected static function getFacadeAccessor()
{
return TenantManager::class;
return \Stancl\Tenancy\Tenancy::class;
}
}

View file

@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Features;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Stancl\Tenancy\Contracts\Feature;
use Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains;
use Stancl\Tenancy\Tenancy;
// todo rewrite this
class TelescopeTags implements Feature
{
/** @var callable User-specific callback that returns tags. */
protected $callback;
public function __construct()
{
$this->callback = function ($entry) {
return [];
};
}
public function bootstrap(Tenancy $tenancy): void
{
if (! class_exists(Telescope::class)) {
return;
}
Telescope::tag(function (IncomingEntry $entry) {
$tags = $this->getTags($entry);
if (! request()->route()) {
return $tags;
}
// todo lines below
$tenantRoute = PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'tenancy')
|| PreventAccessFromTenantDomains::routeHasMiddleware(request()->route(), 'universal');
// Don't do anything if we're visiting a universal route on a central domain
if ($tenantRoute && tenancy()->initialized) {
$tags = array_merge($tags, [
'tenant:' . tenant('id'),
]);
}
return $tags;
});
}
public function getTags(IncomingEntry $entry): array
{
return ($this->callback)($entry);
}
public function setCallback(callable $callback)
{
$this->callback = $callback;
}
}

View file

@ -5,12 +5,13 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Features;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Contracts\Feature;
use Stancl\Tenancy\Events\RevertedToCentralContext;
use Stancl\Tenancy\Events\TenancyBootstrapped;
use Stancl\Tenancy\Tenancy;
use Stancl\Tenancy\Tenant;
use Stancl\Tenancy\TenantManager;
use Stancl\Tenancy\Contracts\Tenant;
// todo rewrite this
class TenantConfig implements Feature
{
/** @var Repository */
@ -27,26 +28,26 @@ class TenantConfig implements Feature
{
$this->config = $config;
foreach ($this->getStorageToConfigMap() as $configKey) {
foreach (static::$storageToConfigMap as $configKey) {
$this->originalConfig[$configKey] = $this->config[$configKey];
}
}
public function bootstrap(Tenancy $tenancy): void
{
$tenantManager->eventListener('bootstrapped', function (TenantManager $manager) {
$this->setTenantConfig($manager->getTenant());
Event::listen(TenancyBootstrapped::class, function (TenancyBootstrapped $event) {
$this->setTenantConfig($event->tenancy->tenant);
});
$tenantManager->eventListener('ended', function () {
Event::listen(RevertedToCentralContext::class, function () {
$this->unsetTenantConfig();
});
}
public function setTenantConfig(Tenant $tenant): void
{
foreach ($this->getStorageToConfigMap() as $storageKey => $configKey) {
$override = $tenant->data[$storageKey] ?? null;
foreach (static::$storageToConfigMap as $storageKey => $configKey) {
$override = $tenant->$storageKey ?? null;
if (! is_null($override)) {
$this->config[$configKey] = $override;
}
@ -55,13 +56,8 @@ class TenantConfig implements Feature
public function unsetTenantConfig(): void
{
foreach ($this->getStorageToConfigMap() as $configKey) {
foreach (static::$storageToConfigMap as $configKey) {
$this->config[$configKey] = $this->originalConfig[$configKey];
}
}
public function getStorageToConfigMap(): array
{
return static::$storageToConfigMap;
}
}

View file

@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Features;
use Illuminate\Config\Repository;
use Illuminate\Support\Facades\Date;
use Stancl\Tenancy\Contracts\Feature;
use Stancl\Tenancy\Tenant;
use Stancl\Tenancy\TenantManager;
// todo rewrite this
class Timestamps implements Feature
{
/** @var Repository */
protected $config;
public static $format = 'c'; // ISO 8601
public function __construct(Repository $config)
{
$this->config = $config;
}
public function bootstrap(TenantManager $tenantManager): void
{
$tenantManager->hook('tenant.creating', function ($tm, Tenant $tenant) {
$tenant->with('created_at', $this->now());
$tenant->with('updated_at', $this->now());
});
$tenantManager->hook('tenant.updating', function ($tm, Tenant $tenant) {
$tenant->with('updated_at', $this->now());
});
$tenantManager->hook('tenant.softDeleting', function ($tm, Tenant $tenant) {
$tenant->with('deleted_at', $this->now());
});
}
public function now(): string
{
return Date::now()->format(static::$format);
}
}

View file

@ -12,6 +12,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Events\DatabaseCreated;
class CreateDatabase implements ShouldQueue
{
@ -30,6 +31,8 @@ class CreateDatabase implements ShouldQueue
if ($this->tenant->getInternal('create_database') !== false) {
$this->tenant->database()->makeCredentials();
$this->tenant->database()->manager()->createDatabase($this->tenant);
event(new DatabaseCreated($this->tenant));
}
}
}

View file

@ -11,6 +11,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\DatabaseDeleted;
class DeleteDatabase implements ShouldQueue
{
@ -27,5 +28,7 @@ class DeleteDatabase implements ShouldQueue
public function handle()
{
$this->tenant->database()->manager()->deleteDatabase($this->tenant);
event(new DatabaseDeleted($this->tenant));
}
}

View file

@ -31,12 +31,8 @@ class MigrateDatabase implements ShouldQueue
*/
public function handle()
{
$migrationParameters = [
// todo ...
];
Artisan::call('tenants:migrate', [
'--tenants' => [$this->tenant->id],
] + $migrationParameters);
]);
}
}

View file

@ -1,7 +1,8 @@
<?php
namespace Stancl\Tenancy\Events\Listeners;
namespace Stancl\Tenancy\Listeners;
use Stancl\Tenancy\Events\TenancyBootstrapped;
use Stancl\Tenancy\Events\TenancyInitialized;
class BootstrapTenancy
@ -9,7 +10,9 @@ class BootstrapTenancy
public function handle(TenancyInitialized $event)
{
foreach ($event->tenancy->getBootstrappers() as $bootstrapper) {
$bootstrapper->start($event->tenancy->tenant);
}
$bootstrapper->bootstrap($event->tenancy->tenant);
}
event(new TenancyBootstrapped($event->tenancy));
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Events\Listeners;
namespace Stancl\Tenancy\Listeners;
use Closure;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -8,7 +8,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
class JobPipeline implements ShouldQueue
{
/** @var bool */
public static $shouldBeQueuedByDefault = false;
public static $queueByDefault = false;
/** @var callable[]|string[] */
public $jobs;
@ -22,16 +22,16 @@ class JobPipeline implements ShouldQueue
public $passable;
/** @var bool */
public $shouldBeQueued;
public $queue;
public function __construct($jobs, callable $send = null, bool $shouldBeQueued = null)
public function __construct($jobs, callable $send = null, bool $queue = null)
{
$this->jobs = $jobs;
$this->send = $send ?? function ($event) {
// If no $send callback is set, we'll just pass the event through the jobs.
return $event;
};
$this->shouldBeQueued = $shouldBeQueued ?? static::$shouldBeQueuedByDefault;
$this->queue = $queue ?? static::$queueByDefault;
}
/** @param callable[]|string[] $jobs */
@ -47,9 +47,9 @@ class JobPipeline implements ShouldQueue
return $this;
}
public function shouldBeQueued(bool $shouldBeQueued)
public function queue(bool $queue)
{
$this->shouldBeQueued = $shouldBeQueued;
$this->queue = $queue;
return $this;
}
@ -69,7 +69,7 @@ class JobPipeline implements ShouldQueue
return function (...$args) {
$executable = $this->executable($args);
if ($this->shouldBeQueued) {
if ($this->queue) {
dispatch($executable);
} else {
dispatch_now($executable);

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Events\Listeners;
namespace Stancl\Tenancy\Listeners;
use Illuminate\Contracts\Queue\ShouldQueue;

View file

@ -1,7 +1,8 @@
<?php
namespace Stancl\Tenancy\Events\Listeners;
namespace Stancl\Tenancy\Listeners;
use Stancl\Tenancy\Events\RevertedToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
class RevertToCentralContext
@ -9,7 +10,9 @@ class RevertToCentralContext
public function handle(TenancyEnded $event)
{
foreach ($event->tenancy->getBootstrappers() as $bootstrapper) {
$bootstrapper->end();
}
$bootstrapper->revert();
}
event(new RevertedToCentralContext($event->tenancy));
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Events\Listeners;
namespace Stancl\Tenancy\Listeners;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Pivot;

View file

@ -9,7 +9,6 @@ use Illuminate\Http\Request;
use Stancl\Tenancy\Resolvers\RequestDataTenantResolver;
use Stancl\Tenancy\Tenancy;
// todo write tests for this
class InitializeTenancyByRequestData extends IdentificationMiddleware
{
/** @var string|null */

View file

@ -24,7 +24,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
$this->app = $app;
}
public function start(Tenant $tenant)
public function bootstrap(Tenant $tenant)
{
$this->resetFacadeCache();
@ -34,7 +34,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
});
}
public function end()
public function revert()
{
$this->resetFacadeCache();

View file

@ -20,7 +20,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
$this->database = $database;
}
public function start(Tenant $tenant)
public function bootstrap(Tenant $tenant)
{
/** @var TenantWithDatabase $tenant */
@ -32,7 +32,7 @@ class DatabaseTenancyBootstrapper implements TenancyBootstrapper
$this->database->connectToTenant($tenant);
}
public function end()
public function revert()
{
$this->database->reconnectToCentral();
}

View file

@ -34,7 +34,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
});
}
public function start(Tenant $tenant)
public function bootstrap(Tenant $tenant)
{
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->getTenantKey();
@ -69,7 +69,7 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
}
}
public function end()
public function revert()
{
// storage_path()
$this->app->useStoragePath($this->originalPaths['storage']);

View file

@ -67,12 +67,12 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
}
}
public function start(Tenant $tenant)
public function bootstrap(Tenant $tenant)
{
$this->tenancyInitialized = true;
}
public function end()
public function revert()
{
$this->tenancyInitialized = false;
}

View file

@ -22,7 +22,7 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
$this->config = $config;
}
public function start(Tenant $tenant)
public function bootstrap(Tenant $tenant)
{
foreach ($this->prefixedConnections() as $connection) {
$prefix = $this->config['tenancy.redis.prefix_base'] . $tenant->getTenantKey();
@ -33,7 +33,7 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
}
}
public function end()
public function revert()
{
foreach ($this->prefixedConnections() as $connection) {
$client = Redis::connection($connection)->client();

View file

@ -21,23 +21,7 @@ class TenancyServiceProvider extends ServiceProvider
{
$this->mergeConfigFrom(__DIR__ . '/../assets/config.php', 'tenancy');
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.id_generator']);
$this->app->singleton(DatabaseManager::class);
$this->app->singleton(Tenancy::class);
$this->app->extend(Tenancy::class, function (Tenancy $tenancy) {
foreach ($this->app['config']['tenancy.features'] as $feature) {
$this->app[$feature]->bootstrap($tenancy);
}
return $tenancy;
});
$this->app->bind(Tenant::class, function ($app) {
return $app[Tenancy::class]->tenant;
});
foreach ($this->app['config']['tenancy.bootstrappers'] as $bootstrapper) {
$this->app->singleton($bootstrapper);
}
$this->app->singleton(Commands\Migrate::class, function ($app) {
return new Commands\Migrate($app['migrator'], $app[DatabaseManager::class]);
@ -52,8 +36,6 @@ class TenancyServiceProvider extends ServiceProvider
$this->app->bind('globalCache', function ($app) {
return new CacheManager($app);
});
$this->app->register(TenantRouteServiceProvider::class);
}
/**

View file

@ -7,11 +7,10 @@ namespace Stancl\Tenancy\TenantDatabaseManagers;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
class MySQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
class MySQLDatabaseManager implements TenantDatabaseManager
{
/** @var string */
protected $connection;

View file

@ -7,11 +7,10 @@ namespace Stancl\Tenancy\TenantDatabaseManagers;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
class PostgreSQLDatabaseManager implements TenantDatabaseManager, CanSetConnection
class PostgreSQLDatabaseManager implements TenantDatabaseManager
{
/** @var string */
protected $connection;

View file

@ -7,11 +7,10 @@ namespace Stancl\Tenancy\TenantDatabaseManagers;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Stancl\Tenancy\Contracts\Future\CanSetConnection;
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
class PostgreSQLSchemaManager implements TenantDatabaseManager, CanSetConnection
class PostgreSQLSchemaManager implements TenantDatabaseManager
{
/** @var string */
protected $connection;

View file

@ -38,4 +38,9 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
return $baseConfig;
}
public function setConnection(string $connection): void
{
//
}
}

View file

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Route;
class TenantRouteServiceProvider extends RouteServiceProvider
{
public function map()
{
$this->app->booted(function () {
if (file_exists(base_path('routes/tenant.php'))) {
Route::middleware(['web'])
->namespace($this->app['config']['tenancy.tenant_route_namespace'] ?? 'App\Http\Controllers')
->group(base_path('routes/tenant.php'));
}
});
}
}

View file

@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Traits;
use Stancl\Tenancy\Tenant;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Stancl\Tenancy\Contracts\Tenant;
trait TenantAwareCommand
{

View file

@ -1,12 +1,12 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Tests\TestCase;

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
@ -8,9 +8,9 @@ use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;

View file

@ -2,11 +2,11 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;

View file

@ -1,10 +1,10 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomainOrSubdomain;
use Stancl\Tenancy\Tests\TestCase;
@ -22,7 +22,7 @@ class CombinedDomainAndSubdomainIdentificationTest extends TestCase
});
});
config(['tenancy.tenant_model' => Tenant::class]);
config(['tenancy.tenant_model' => CombinedTenant::class]);
}
/** @test */
@ -30,7 +30,7 @@ class CombinedDomainAndSubdomainIdentificationTest extends TestCase
{
config(['tenancy.central_domains' => ['localhost']]);
$tenant = Tenant::create([
$tenant = CombinedTenant::create([
'id' => 'acme',
]);
@ -53,7 +53,7 @@ class CombinedDomainAndSubdomainIdentificationTest extends TestCase
{
config(['tenancy.central_domains' => []]);
$tenant = Tenant::create([
$tenant = CombinedTenant::create([
'id' => 'acme',
]);
@ -72,7 +72,7 @@ class CombinedDomainAndSubdomainIdentificationTest extends TestCase
}
}
class Tenant extends Models\Tenant
class CombinedTenant extends Models\Tenant
{
use HasDomains;
}

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
@ -10,9 +10,9 @@ use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Database\Seeder;
use Illuminate\Foundation\Auth\User as Authenticable;
@ -8,7 +8,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Jobs\MigrateDatabase;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
@ -12,9 +12,12 @@ use Stancl\Tenancy\Exceptions\TenantDatabaseUserAlreadyExistsException;
use Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager;
use Stancl\Tenancy\TenantDatabaseManagers\PermissionControlledMySQLDatabaseManager;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
class DatabaseUsersTest extends TestCase
@ -96,19 +99,20 @@ class DatabaseUsersTest extends TestCase
config([
'tenancy.database_managers.mysql' => MySQLDatabaseManager::class,
'tenancy.database.suffix' => '',
'tenancy.database.template_connection' => 'mysql',
'tenancy.template_tenant_connection' => 'mysql',
'tenancy.bootstrappers' => [
DatabaseTenancyBootstrapper::class,
],
]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
$tenant = Tenant::create([
'id' => 'foo' . Str::random(10),
]);
$this->assertTrue($tenant->database()->manager() instanceof MySQLDatabaseManager);
$tenant = Tenant::create([
'id' => 'foo' . Str::random(10),
]);
tenancy()->initialize($tenant); // check if everything works
tenancy()->end();

View file

@ -1,10 +1,10 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
@ -25,13 +25,13 @@ class DomainTest extends TestCase
});
});
config(['tenancy.tenant_model' => Tenant::class]);
config(['tenancy.tenant_model' => DomainTenant::class]);
}
/** @test */
public function tenant_can_be_identified_using_hostname()
{
$tenant = Tenant::create();
$tenant = DomainTenant::create();
$id = $tenant->id;
@ -48,13 +48,13 @@ class DomainTest extends TestCase
/** @test */
public function a_domain_can_belong_to_only_one_tenant()
{
$tenant = Tenant::create();
$tenant = DomainTenant::create();
$tenant->domains()->create([
'domain' => 'foo.localhost',
]);
$tenant2 = Tenant::create();
$tenant2 = DomainTenant::create();
$this->expectException(DomainOccupiedByOtherTenantException::class);
$tenant2->domains()->create([
@ -73,7 +73,7 @@ class DomainTest extends TestCase
/** @test */
public function tenant_can_be_identified_by_domain()
{
$tenant = Tenant::create([
$tenant = DomainTenant::create([
'id' => 'acme',
]);
@ -104,7 +104,7 @@ class DomainTest extends TestCase
}
}
class Tenant extends Models\Tenant
class DomainTenant extends Models\Tenant
{
use HasDomains;
}

View file

@ -6,7 +6,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
class TestCreateUsersTable extends Migration
{
/**
* Run the migrations.

View file

@ -1 +0,0 @@
{"foo":"bar"}

View file

@ -1,12 +1,12 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Events\CallQueuedListener;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\QueueableListener;
use Stancl\Tenancy\Listeners\QueueableListener;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Tests\TestCase;
@ -41,8 +41,6 @@ class EventListenerTest extends TestCase
$this->assertFalse(app()->bound('foo'));
}
// todo test that the way the published SP registers events works
}
class FooListener extends QueueableListener

View file

@ -4,32 +4,39 @@ declare(strict_types=1);
namespace Stancl\Tenancy\Tests\Features;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Features\TenantConfig;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\TestCase;
class TenantConfigTest extends TestCase
{
public $autoInitTenancy = false;
public $autoCreateTenant = false;
/** @test */
public function config_is_merged_and_removed()
{
$this->assertSame(null, config('services.paypal'));
config([
'tenancy.features' => [TenantConfig::class],
'tenancy.bootstrappers' => [],
]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
TenantConfig::$storageToConfigMap = [
'paypal_api_public' => 'services.paypal.public',
'paypal_api_private' => 'services.paypal.private',
];
tenancy()->create('foo.localhost', [
$tenant = Tenant::create([
'paypal_api_public' => 'foo',
'paypal_api_private' => 'bar',
]);
tenancy()->init('foo.localhost');
tenancy()->initialize($tenant);
$this->assertSame(['public' => 'foo', 'private' => 'bar'], config('services.paypal'));
tenancy()->end();

View file

@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\Features;
use Stancl\Tenancy\Features\Timestamps;
use Stancl\Tenancy\Tenant;
use Stancl\Tenancy\Tests\TestCase;
class TimestampTest extends TestCase
{
public $autoCreateTenant = false;
public $autoInitTenancy = false;
public function setUp(): void
{
parent::setUp();
config(['tenancy.features' => [
Timestamps::class,
]]);
}
/** @test */
public function create_and_update_timestamps_are_added_on_create()
{
$tenant = Tenant::new()->save();
$this->assertArrayHasKey('created_at', $tenant->data);
$this->assertArrayHasKey('updated_at', $tenant->data);
}
/** @test */
public function update_timestamps_are_added()
{
$tenant = Tenant::new()->save();
$this->assertSame($tenant->created_at, $tenant->updated_at);
$this->assertSame('string', gettype($tenant->created_at));
sleep(1);
$tenant->put('abc', 'def');
$this->assertTrue($tenant->updated_at > $tenant->created_at);
}
/** @test */
public function softdelete_timestamps_are_added()
{
$tenant = Tenant::new()->save();
$this->assertNull($tenant->deleted_at);
$tenant->softDelete();
$this->assertNotNull($tenant->deleted_at);
}
}

View file

@ -2,12 +2,12 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Facades\GlobalCache;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;

View file

@ -1,12 +1,12 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Spatie\Valuestore\Valuestore;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Tests\TestCase;
@ -23,7 +23,7 @@ class JobPipelineTest extends TestCase
config(['queue.default' => 'redis']);
$this->valuestore = Valuestore::make(__DIR__ . '/../Etc/tmp/jobpipelinetest.json')->flush();
$this->valuestore = Valuestore::make(__DIR__ . '/Etc/tmp/jobpipelinetest.json')->flush();
}
/** @test */
@ -51,7 +51,7 @@ class JobPipelineTest extends TestCase
FooJob::class,
])->send(function () {
return $this->valuestore;
})->shouldBeQueued(true)->toListener());
})->queue(true)->toListener());
Queue::assertNothingPushed();
@ -70,7 +70,7 @@ class JobPipelineTest extends TestCase
FooJob::class,
])->send(function () {
return $this->valuestore;
})->shouldBeQueued(true)->toListener());
})->queue(true)->toListener());
$this->assertFalse($this->valuestore->has('foo'));
Tenant::create();

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models\Tenant;

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -11,7 +11,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Event;
use Spatie\Valuestore\Valuestore;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper;
use Stancl\Tenancy\Tests\TestCase;
@ -36,17 +36,19 @@ class QueueTest extends TestCase
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
$this->valuestore = Valuestore::make(__DIR__ . '/../Etc/tmp/queuetest.json')->flush();
$this->valuestore = Valuestore::make(__DIR__ . '/Etc/tmp/queuetest.json')->flush();
}
/** @test */
public function tenant_id_is_passed_to_tenant_queues()
{
config(['queue.default' => 'sync']);
$tenant = Tenant::create();
tenancy()->initialize($tenant);
Event::fake();
Event::fake([JobProcessing::class]);
dispatch(new TestJob($this->valuestore));

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models\Tenant;

View file

@ -9,14 +9,14 @@ use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Queue;
use Stancl\Tenancy\Contracts\Syncable;
use Stancl\Tenancy\Contracts\SyncMaster;
use Stancl\Tenancy\Database\Models\Concerns\CentralConnection;
use Stancl\Tenancy\Database\Models\Concerns\ResourceSyncing;
use Stancl\Tenancy\Database\Concerns\CentralConnection;
use Stancl\Tenancy\Database\Concerns\ResourceSyncing;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\TenantPivot;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Events\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Events\Listeners\UpdateSyncedResource;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Listeners\UpdateSyncedResource;
use Stancl\Tenancy\Events\SyncedResourceChangedInForeignDatabase;
use Stancl\Tenancy\Events\SyncedResourceSaved;
use Stancl\Tenancy\Events\TenancyEnded;
@ -49,8 +49,8 @@ class ResourceSyncingTest extends TestCase
$this->artisan('migrate', [
'--path' => [
__DIR__ . '/../Etc/synced_resource_migrations',
__DIR__ . '/../Etc/synced_resource_migrations/users'
__DIR__ . '/Etc/synced_resource_migrations',
__DIR__ . '/Etc/synced_resource_migrations/users'
],
'--realpath' => true,
])->assertExitCode(0);
@ -59,7 +59,7 @@ class ResourceSyncingTest extends TestCase
protected function migrateTenants()
{
$this->artisan('tenants:migrate', [
'--path' => __DIR__ . '/../Etc/synced_resource_migrations/users',
'--path' => __DIR__ . '/Etc/synced_resource_migrations/users',
'--realpath' => true,
])->assertExitCode(0);
}
@ -69,7 +69,7 @@ class ResourceSyncingTest extends TestCase
{
Event::fake([SyncedResourceSaved::class]);
$user = User::create([
$user = ResourceUser::create([
'name' => 'Foo',
'email' => 'foo@email.com',
'password' => 'secret',
@ -94,13 +94,13 @@ class ResourceSyncingTest extends TestCase
'role' => 'superadmin', // unsynced
]);
$tenant = Tenant::create();
$tenant = ResourceTenant::create();
$this->migrateTenants();
tenancy()->initialize($tenant);
// Create the same user in tenant DB
$user = User::create([
$user = ResourceUser::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
@ -135,22 +135,22 @@ class ResourceSyncingTest extends TestCase
'email' => 'john@foreignhost', // synced
'password' => 'secret', // no changes
'role' => 'superadmin', // unsynced
], User::first()->getAttributes());
], ResourceUser::first()->getAttributes());
}
/** @test */
public function creating_the_resource_in_tenant_database_creates_it_in_central_database_and_creates_the_mapping()
{
// Assert no user in central DB
$this->assertCount(0, User::all());
$this->assertCount(0, ResourceUser::all());
$tenant = Tenant::create();
$tenant = ResourceTenant::create();
$this->migrateTenants();
tenancy()->initialize($tenant);
// Create the same user in tenant DB
User::create([
ResourceUser::create([
'global_id' => 'acme',
'name' => 'John Doe',
'email' => 'john@localhost',
@ -170,7 +170,7 @@ class ResourceSyncingTest extends TestCase
// Assert role change doesn't cascade
CentralUser::first()->update(['role' => 'central superadmin']);
tenancy()->initialize($tenant);
$this->assertSame('commenter', User::first()->role);
$this->assertSame('commenter', ResourceUser::first()->role);
}
/** @test */
@ -182,7 +182,7 @@ class ResourceSyncingTest extends TestCase
$this->assertFalse(tenancy()->initialized);
$this->expectException(ModelNotSyncMaster::class);
User::first()->update(['role' => 'foobar']);
ResourceUser::first()->update(['role' => 'foobar']);
}
/** @test */
@ -196,19 +196,19 @@ class ResourceSyncingTest extends TestCase
'role' => 'commenter', // unsynced
]);
$tenant = Tenant::create([
$tenant = ResourceTenant::create([
'id' => 't1',
]);
$this->migrateTenants();
$tenant->run(function () {
$this->assertCount(0, User::all());
$this->assertCount(0, ResourceUser::all());
});
$centralUser->tenants()->attach('t1');
$tenant->run(function () {
$this->assertCount(1, User::all());
$this->assertCount(1, ResourceUser::all());
});
}
@ -223,13 +223,13 @@ class ResourceSyncingTest extends TestCase
'role' => 'commenter', // unsynced
]);
$tenant = Tenant::create([
$tenant = ResourceTenant::create([
'id' => 't1',
]);
$this->migrateTenants();
$tenant->run(function () {
$this->assertCount(0, User::all());
$this->assertCount(0, ResourceUser::all());
});
// The child model is inaccessible in the Pivot Model, so we can't fire any events.
@ -237,7 +237,7 @@ class ResourceSyncingTest extends TestCase
$tenant->run(function () {
// Still zero
$this->assertCount(0, User::all());
$this->assertCount(0, ResourceUser::all());
});
}
@ -252,15 +252,15 @@ class ResourceSyncingTest extends TestCase
'role' => 'commenter', // unsynced
]);
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
$t2 = Tenant::create([
$t2 = ResourceTenant::create([
'id' => 't2',
]);
$t3 = Tenant::create([
$t3 = ResourceTenant::create([
'id' => 't3',
]);
$this->migrateTenants();
@ -271,17 +271,17 @@ class ResourceSyncingTest extends TestCase
$t1->run(function () {
// assert user exists
$this->assertCount(1, User::all());
$this->assertCount(1, ResourceUser::all());
});
$t2->run(function () {
// assert user exists
$this->assertCount(1, User::all());
$this->assertCount(1, ResourceUser::all());
});
$t3->run(function () {
// assert user does NOT exist
$this->assertCount(0, User::all());
$this->assertCount(0, ResourceUser::all());
});
}
@ -297,10 +297,10 @@ class ResourceSyncingTest extends TestCase
'role' => 'commenter', // unsynced
]);
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
$t2 = Tenant::create([
$t2 = ResourceTenant::create([
'id' => 't2',
]);
$this->migrateTenants();
@ -310,7 +310,7 @@ class ResourceSyncingTest extends TestCase
$t2->run(function () {
// Create user with the same global ID in t2 database
User::create([
ResourceUser::create([
'global_id' => 'acme',
'name' => 'John Foo', // changed
'email' => 'john@foo', // changed
@ -325,7 +325,7 @@ class ResourceSyncingTest extends TestCase
$this->assertSame('commenter', $centralUser->role); // role didn't change
$t1->run(function () {
$user = User::first();
$user = ResourceUser::first();
$this->assertSame('John Foo', $user->name); // name changed
$this->assertSame('john@foo', $user->email); // email changed
$this->assertSame('commenter', $user->role); // role didn't change, i.e. is the same as from the original copy from central
@ -344,13 +344,13 @@ class ResourceSyncingTest extends TestCase
'role' => 'commenter', // unsynced
]);
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
$t2 = Tenant::create([
$t2 = ResourceTenant::create([
'id' => 't2',
]);
$t3 = Tenant::create([
$t3 = ResourceTenant::create([
'id' => 't3',
]);
$this->migrateTenants();
@ -361,17 +361,17 @@ class ResourceSyncingTest extends TestCase
$centralUser->tenants()->attach('t3');
$t3->run(function () {
User::first()->update([
ResourceUser::first()->update([
'name' => 'John 3',
'role' => 'employee', // unsynced
]);
$this->assertSame('employee', User::first()->role);
$this->assertSame('employee', ResourceUser::first()->role);
});
// Check that change was cascaded to other tenants
$t1->run($check = function () {
$user = User::first();
$user = ResourceUser::first();
$this->assertSame('John 3', $user->name); // synced
$this->assertSame('commenter', $user->role); // unsynced
@ -408,7 +408,7 @@ class ResourceSyncingTest extends TestCase
'role' => 'employee',
]);
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
@ -417,21 +417,21 @@ class ResourceSyncingTest extends TestCase
$centralUser->tenants()->attach('t1');
$t1->run(function () {
$this->assertSame('employee', User::first()->role);
$this->assertSame('employee', ResourceUser::first()->role);
});
}
/** @test */
public function when_the_resource_doesnt_exist_in_the_central_db_non_synced_columns_will_bubble_up_too()
{
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
$this->migrateTenants();
$t1->run(function () {
User::create([
ResourceUser::create([
'name' => 'John Doe',
'email' => 'john@doe',
'password' => 'secret',
@ -448,7 +448,7 @@ class ResourceSyncingTest extends TestCase
Queue::fake();
UpdateSyncedResource::$shouldQueue = true;
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
@ -457,7 +457,7 @@ class ResourceSyncingTest extends TestCase
Queue::assertNothingPushed();
$t1->run(function () {
User::create([
ResourceUser::create([
'name' => 'John Doe',
'email' => 'john@doe',
'password' => 'secret',
@ -484,13 +484,13 @@ class ResourceSyncingTest extends TestCase
'role' => 'commenter', // unsynced
]);
$t1 = Tenant::create([
$t1 = ResourceTenant::create([
'id' => 't1',
]);
$t2 = Tenant::create([
$t2 = ResourceTenant::create([
'id' => 't2',
]);
$t3 = Tenant::create([
$t3 = ResourceTenant::create([
'id' => 't3',
]);
$this->migrateTenants();
@ -520,12 +520,12 @@ class ResourceSyncingTest extends TestCase
Event::fake([SyncedResourceChangedInForeignDatabase::class]);
$t3->run(function () {
User::first()->update([
ResourceUser::first()->update([
'name' => 'John 3',
'role' => 'employee', // unsynced
]);
$this->assertSame('employee', User::first()->role);
$this->assertSame('employee', ResourceUser::first()->role);
});
Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
@ -545,11 +545,30 @@ class ResourceSyncingTest extends TestCase
return $event->tenant === null;
});
// todo update in global
// Flush
Event::fake([SyncedResourceChangedInForeignDatabase::class]);
$centralUser->update([
'name' => 'John Central',
]);
Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
return optional($event->tenant)->getTenantKey() === 't1';
});
Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
return optional($event->tenant)->getTenantKey() === 't2';
});
Event::assertDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
return optional($event->tenant)->getTenantKey() === 't3';
});
// Assert NOT dispatched in central
Event::assertNotDispatched(SyncedResourceChangedInForeignDatabase::class, function (SyncedResourceChangedInForeignDatabase $event) {
return $event->tenant === null;
});
}
}
class Tenant extends Models\Tenant
class ResourceTenant extends Models\Tenant
{
public function users()
{
@ -568,13 +587,13 @@ class CentralUser extends Model implements SyncMaster
public function tenants(): BelongsToMany
{
return $this->belongsToMany(Tenant::class, 'tenant_users', 'global_user_id', 'tenant_id')
return $this->belongsToMany(ResourceTenant::class, 'tenant_users', 'global_user_id', 'tenant_id')
->using(TenantPivot::class);
}
public function getTenantModelName(): string
{
return User::class;
return ResourceUser::class;
}
public function getTenantIdColumnInMapTable(): string
@ -607,10 +626,11 @@ class CentralUser extends Model implements SyncMaster
}
}
class User extends Model implements Syncable
class ResourceUser extends Model implements Syncable
{
use ResourceSyncing;
protected $table = 'users';
protected $guarded = [];
public $timestamps = false;

View file

@ -1,10 +1,10 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Route;
use Stancl\Tenancy\Database\Models;
use Stancl\Tenancy\Database\Models\Concerns\HasDomains;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Exceptions\NotASubdomainException;
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
use Stancl\Tenancy\Tests\TestCase;
@ -26,13 +26,13 @@ class SubdomainTest extends TestCase
});
});
config(['tenancy.tenant_model' => Tenant::class]);
config(['tenancy.tenant_model' => SubdomainTenant::class]);
}
/** @test */
public function tenant_can_be_identified_by_subdomain()
{
$tenant = Tenant::create([
$tenant = SubdomainTenant::create([
'id' => 'acme',
]);
@ -105,7 +105,7 @@ class SubdomainTest extends TestCase
// not 'localhost'
]]);
$tenant = Tenant::create([
$tenant = SubdomainTenant::create([
'id' => 'acme',
]);
@ -121,7 +121,7 @@ class SubdomainTest extends TestCase
}
}
class Tenant extends Models\Tenant
class SubdomainTenant extends Models\Tenant
{
use HasDomains;
}

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;
use Stancl\Tenancy\Controllers\TenantAssetsController;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;

View file

@ -2,13 +2,13 @@
declare(strict_types=1);
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Database\Models\Tenant;
use Stancl\Tenancy\DatabaseManager;
use Stancl\Tenancy\Events\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Events\Listeners\JobPipeline;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\JobPipeline;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Jobs\CreateDatabase;

View file

@ -1,6 +1,6 @@
<?php
namespace Stancl\Tenancy\Tests\v3;
namespace Stancl\Tenancy\Tests;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
@ -39,7 +39,7 @@ class TenantModelTest extends TestCase
tenancy()->end();
$this->assertSame(null, app(Tenant::class));
$this->assertSame(null, app(Contracts\Tenant::class));
}
/** @test */
@ -53,7 +53,7 @@ class TenantModelTest extends TestCase
$this->assertSame('bar', $tenant->foo);
$this->assertSame(null, $tenant->data);
// Low level test to test database structure
// Low level test to assert database structure
$this->assertSame(json_encode(['foo' => 'bar']), DB::table('tenants')->where('id', $tenant->id)->first()->data);
$this->assertSame(null, DB::table('tenants')->where('id', $tenant->id)->first()->foo ?? null);
@ -103,8 +103,8 @@ class TenantModelTest extends TestCase
unset(app()[UniqueIdentifierGenerator::class]);
$tenant1 = MyTenant::create();
$tenant2 = MyTenant::create();
$tenant1 = Tenant::create();
$tenant2 = Tenant::create();
$this->assertSame(1, $tenant1->id);
$this->assertSame(2, $tenant2->id);
@ -138,7 +138,6 @@ class TenantModelTest extends TestCase
class MyTenant extends Tenant
{
protected $table = 'tenants';
public $increments = true;
}
class AnotherTenant extends Model implements Contracts\Tenant