mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 17:44:04 +00:00
Add broadcasting channel prefixing bootstrapper (#12)
* Rename old broadcast bootstrapper, add new one * Add broadcast tenancy bootstrapper + tests * Fix code style (php-cs-fixer) * Fix prefixing * Work on th bootstrapper's tests (wip – problem with events) * Fix bootstrapper * Test that auth closures of channels work correctly * Fix bootstrapper * Fix code style (php-cs-fixer) * Delete channel cloning bootstrapper * Add bootstrapper that prefixes broadcastOn channels under the hood * Add broadcast channel registering helpers * Update prefixing tests (WIP) * Fix code style (php-cs-fixer) * Improve comment * Fix code style (php-cs-fixer) * Allow customization of Pusher/Ably broadcaster extension * Fix code style (php-cs-fixer) * Implement prefix bootstrapper logic, test channel prefixing using a closure * Work on the prefixing bootstrapper and tests * Fix code style (php-cs-fixer) * Add optional $options param to broadcasting helpers * Test broadcasting helpers * Fix code style (php-cs-fixer) * Broadcasting channel prefixing + testing progress * Improve helper methods * Fix and improve test * Fix extending in bootstrap() * Fix code style (php-cs-fixer) * Add docblocks, name things more accurately * Fix code style (php-cs-fixer) * Delete redundant method from testing broadcaster * Test Pusher channel prefixing (probabaly redundant?) * Test if channels get prefixed correctly when switching tenants * Work with the current broadcast manager instead of overriding it * Give the original channels to the overriden broadcasters * Fix code style (php-cs-fixer) * Simplify channel prefix bootstrapper * Fix code style (php-cs-fixer) * Fix comment * Fix test * Delete annotation * Delete unused classes from test * Delete outdated test * Move broadcasting bootstrapper test to BootstrapperTest * Improve bootstrapper test, delete unused event * Add annotations to the bootstrapper * Fix code style (php-cs-fixer) * Improve wording * Improve comment * Update src/Bootstrappers/BroadcastChannelPrefixBootstrapper.php * Apply suggestions from code review * Optionally skip prefixing of specific channels * Add and test central channel helper, update formatChannels overrides and tests * Fix code style (php-cs-fixer) * minor fixes * Improve annotation * Use "global__" prefix instead of "central__", add comments * Correct tests --------- Co-authored-by: PHP CS Fixer <phpcsfixer@example.com> Co-authored-by: Samuel Štancl <samuel.stancl@gmail.com>
This commit is contained in:
parent
b503dbf33d
commit
c34952f328
7 changed files with 450 additions and 24 deletions
160
src/Bootstrappers/BroadcastChannelPrefixBootstrapper.php
Normal file
160
src/Bootstrappers/BroadcastChannelPrefixBootstrapper.php
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Bootstrappers;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Broadcasting\Broadcasters\AblyBroadcaster;
|
||||||
|
use Illuminate\Broadcasting\Broadcasters\PusherBroadcaster;
|
||||||
|
use Illuminate\Broadcasting\BroadcastManager;
|
||||||
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
|
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides broadcasters (by default, using $broadcasterManager->extend())
|
||||||
|
* so that the channel names they actually use to broadcast events get prefixed.
|
||||||
|
*
|
||||||
|
* Channels you return in the broadcastOn() methods of the events are passed to the formatChannels() method.
|
||||||
|
* Broadcasters use that method to format the names of the channels on which the event will broadcast,
|
||||||
|
* so we override it to prefix the final channel names the broadcasters use for event broadcasting.
|
||||||
|
*/
|
||||||
|
class BroadcastChannelPrefixBootstrapper implements TenancyBootstrapper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Closures overriding broadcasters with custom broadcasters that prefix the channel names with the tenant keys.
|
||||||
|
*
|
||||||
|
* The key is the broadcaster's name, and the value is a closure that should prefix the broadcaster's channels.
|
||||||
|
* $broadcasterOverrides['custom'] = fn () => ...; // Custom override closure
|
||||||
|
*
|
||||||
|
* For more info, see the default override methods in this class (pusher() and ably()).
|
||||||
|
*/
|
||||||
|
public static array $broadcasterOverrides = [];
|
||||||
|
|
||||||
|
protected array $originalBroadcasters = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected Application $app,
|
||||||
|
protected BroadcastManager $broadcastManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bootstrap(Tenant $tenant): void
|
||||||
|
{
|
||||||
|
foreach (static::$broadcasterOverrides as $broadcaster => $broadcasterOverride) {
|
||||||
|
// Save the original broadcaster, so that we can revert to it later
|
||||||
|
$this->originalBroadcasters[$broadcaster] = $this->broadcastManager->driver($broadcaster);
|
||||||
|
|
||||||
|
// Delete the cached broadcaster, so that the manager uses the new one
|
||||||
|
$this->broadcastManager->purge($broadcaster);
|
||||||
|
|
||||||
|
$broadcasterOverride();
|
||||||
|
|
||||||
|
// Get the overriden broadcaster
|
||||||
|
$newBroadcaster = $this->broadcastManager->driver($broadcaster);
|
||||||
|
|
||||||
|
// Register the original broadcaster's channels in the new broadcaster
|
||||||
|
foreach ($this->originalBroadcasters[$broadcaster]->getChannels() as $channel => $callback) {
|
||||||
|
$newBroadcaster->channel($channel, $callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revert(): void
|
||||||
|
{
|
||||||
|
// Revert to the original broadcasters
|
||||||
|
foreach ($this->originalBroadcasters as $name => $broadcaster) {
|
||||||
|
// Delete the cached (overriden) broadcaster
|
||||||
|
$this->broadcastManager->purge($name);
|
||||||
|
|
||||||
|
// Make manager return the original broadcaster instance
|
||||||
|
// Whenever the broadcaster is requested
|
||||||
|
$this->broadcastManager->extend($name, fn ($app, $config) => $broadcaster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the closure that overrides the 'pusher' broadcaster.
|
||||||
|
*
|
||||||
|
* By default, override the 'pusher' broadcaster with a broadcaster that
|
||||||
|
* extends PusherBroadcaster, and overrides the formatChannels() method,
|
||||||
|
* such that e.g. 'private-channel' becomes 'private-tenantKey.channel'.
|
||||||
|
*/
|
||||||
|
public static function pusher(Closure|null $override = null): void
|
||||||
|
{
|
||||||
|
static::$broadcasterOverrides['pusher'] = $override ?? function () {
|
||||||
|
$broadcastManager = app(BroadcastManager::class);
|
||||||
|
|
||||||
|
return $broadcastManager->extend('pusher', function ($app, $config) use ($broadcastManager) {
|
||||||
|
return new class($broadcastManager->pusher($config)) extends PusherBroadcaster {
|
||||||
|
protected function formatChannels(array $channels)
|
||||||
|
{
|
||||||
|
$formatChannel = function (string $channel) {
|
||||||
|
$prefixes = ['private-', 'presence-', 'private-encrypted-'];
|
||||||
|
$defaultPrefix = '';
|
||||||
|
|
||||||
|
foreach ($prefixes as $prefix) {
|
||||||
|
if (str($channel)->startsWith($prefix)) {
|
||||||
|
$defaultPrefix = $prefix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the tenant prefix to channels that aren't flagged as central
|
||||||
|
if (! str($channel)->startsWith('global__')) {
|
||||||
|
$channel = str($channel)->after($defaultPrefix)->prepend($defaultPrefix . tenant()->getTenantKey() . '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
return array_map($formatChannel, $channels);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the closure that overrides the 'ably' broadcaster.
|
||||||
|
*
|
||||||
|
* By default, override the 'ably' broadcaster with a broadcaster that
|
||||||
|
* Extends AblyBroadcaster, and overrides the formatChannels() method
|
||||||
|
* such that e.g. 'private-channel' becomes 'private:tenantKey.channel'.
|
||||||
|
*/
|
||||||
|
public static function ably(Closure|null $override = null): void
|
||||||
|
{
|
||||||
|
static::$broadcasterOverrides['ably'] = $override ?? function () {
|
||||||
|
$broadcastManager = app(BroadcastManager::class);
|
||||||
|
|
||||||
|
return $broadcastManager->extend('ably', function ($app, $config) use ($broadcastManager) {
|
||||||
|
return new class($broadcastManager->ably($config)) extends AblyBroadcaster {
|
||||||
|
protected function formatChannels(array $channels)
|
||||||
|
{
|
||||||
|
$formatChannel = function (string $channel) {
|
||||||
|
$prefixes = ['private:', 'presence:'];
|
||||||
|
$defaultPrefix = '';
|
||||||
|
|
||||||
|
foreach ($prefixes as $prefix) {
|
||||||
|
if (str($channel)->startsWith($prefix)) {
|
||||||
|
$defaultPrefix = $prefix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the tenant prefix to channels that aren't flagged as central
|
||||||
|
if (! str($channel)->startsWith('global__')) {
|
||||||
|
$channel = str($channel)->after($defaultPrefix)->prepend($defaultPrefix . tenant()->getTenantKey() . '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
return array_map($formatChannel, parent::formatChannels($channels));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
||||||
|
|
||||||
class BroadcastTenancyBootstrapper implements TenancyBootstrapper
|
class BroadcastingConfigBootstrapper implements TenancyBootstrapper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Tenant properties to be mapped to config (similarly to the TenantConfig feature).
|
* Tenant properties to be mapped to config (similarly to the TenantConfig feature).
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
use Stancl\Tenancy\Contracts\Tenant;
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Tenancy;
|
use Stancl\Tenancy\Tenancy;
|
||||||
|
|
||||||
|
|
@ -106,3 +107,31 @@ if (! function_exists('tenant_route')) {
|
||||||
return (string) str($url)->replace($hostname, $domain);
|
return (string) str($url)->replace($hostname, $domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! function_exists('tenant_channel')) {
|
||||||
|
function tenant_channel(string $channelName, Closure $callback, array $options = []): void
|
||||||
|
{
|
||||||
|
// Register '{tenant}.channelName'
|
||||||
|
Broadcast::channel('{tenant}.' . $channelName, fn ($user, $tenantKey, ...$args) => $callback($user, ...$args), $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('global_channel')) {
|
||||||
|
function global_channel(string $channelName, Closure $callback, array $options = []): void
|
||||||
|
{
|
||||||
|
// Register 'global__channelName'
|
||||||
|
// Global channels are available in both the central and tenant contexts
|
||||||
|
Broadcast::channel('global__' . $channelName, fn ($user, ...$args) => $callback($user, ...$args), $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('universal_channel')) {
|
||||||
|
function universal_channel(string $channelName, Closure $callback, array $options = []): void
|
||||||
|
{
|
||||||
|
// Register 'channelName'
|
||||||
|
Broadcast::channel($channelName, $callback, $options);
|
||||||
|
|
||||||
|
// Register '{tenant}.channelName'
|
||||||
|
tenant_channel($channelName, $callback, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,23 @@ use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
use Stancl\JobPipeline\JobPipeline;
|
use Stancl\JobPipeline\JobPipeline;
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Stancl\Tenancy\Events\TenancyEnded;
|
use Stancl\Tenancy\Events\TenancyEnded;
|
||||||
use Stancl\Tenancy\Jobs\CreateDatabase;
|
use Stancl\Tenancy\Jobs\CreateDatabase;
|
||||||
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
|
||||||
use Stancl\Tenancy\Events\TenantCreated;
|
use Stancl\Tenancy\Events\TenantCreated;
|
||||||
use Stancl\Tenancy\Events\TenantDeleted;
|
use Stancl\Tenancy\Events\TenantDeleted;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
use Stancl\Tenancy\Events\DeletingTenant;
|
use Stancl\Tenancy\Events\DeletingTenant;
|
||||||
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
use Illuminate\Filesystem\FilesystemAdapter;
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
use Illuminate\Broadcasting\BroadcastManager;
|
use Illuminate\Broadcasting\BroadcastManager;
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
|
|
@ -29,20 +32,24 @@ use Stancl\Tenancy\Jobs\RemoveStorageSymlinks;
|
||||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
||||||
use Stancl\Tenancy\Listeners\DeleteTenantStorage;
|
use Stancl\Tenancy\Listeners\DeleteTenantStorage;
|
||||||
|
use Stancl\Tenancy\Overrides\TenancyUrlGenerator;
|
||||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
|
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
||||||
use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\CacheTagsBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\UrlBindingBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\UrlBindingBootstrapper;
|
|
||||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
use Stancl\Tenancy\Middleware\InitializeTenancyBySubdomain;
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastTenancyBootstrapper;
|
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\Integrations\FortifyRouteTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByPath;
|
|
||||||
use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
$this->mockConsoleOutput = false;
|
$this->mockConsoleOutput = false;
|
||||||
|
|
@ -50,7 +57,7 @@ beforeEach(function () {
|
||||||
config(['cache.default' => $cacheDriver = 'redis']);
|
config(['cache.default' => $cacheDriver = 'redis']);
|
||||||
PrefixCacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver];
|
PrefixCacheTenancyBootstrapper::$tenantCacheStores = [$cacheDriver];
|
||||||
// Reset static properties of classes used in this test file to their default values
|
// Reset static properties of classes used in this test file to their default values
|
||||||
BroadcastTenancyBootstrapper::$credentialsMap = [];
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
UrlTenancyBootstrapper::$rootUrlOverride = null;
|
UrlTenancyBootstrapper::$rootUrlOverride = null;
|
||||||
|
|
||||||
|
|
@ -70,7 +77,7 @@ afterEach(function () {
|
||||||
UrlTenancyBootstrapper::$rootUrlOverride = null;
|
UrlTenancyBootstrapper::$rootUrlOverride = null;
|
||||||
PrefixCacheTenancyBootstrapper::$tenantCacheStores = [];
|
PrefixCacheTenancyBootstrapper::$tenantCacheStores = [];
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
BroadcastTenancyBootstrapper::$credentialsMap = [];
|
BroadcastingConfigBootstrapper::$credentialsMap = [];
|
||||||
TenancyUrlGenerator::$prefixRouteNames = false;
|
TenancyUrlGenerator::$prefixRouteNames = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -365,8 +372,8 @@ test('local storage public urls are generated correctly', function() {
|
||||||
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
expect(File::isDirectory($tenantStoragePath))->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('BroadcastTenancyBootstrapper binds TenancyBroadcastManager to BroadcastManager and reverts the binding when tenancy is ended', function() {
|
test('BroadcastingConfigBootstrapper binds TenancyBroadcastManager to BroadcastManager and reverts the binding when tenancy is ended', function() {
|
||||||
config(['tenancy.bootstrappers' => [BroadcastTenancyBootstrapper::class]]);
|
config(['tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class]]);
|
||||||
|
|
||||||
expect(app(BroadcastManager::class))->toBeInstanceOf(BroadcastManager::class);
|
expect(app(BroadcastManager::class))->toBeInstanceOf(BroadcastManager::class);
|
||||||
|
|
||||||
|
|
@ -379,14 +386,14 @@ test('BroadcastTenancyBootstrapper binds TenancyBroadcastManager to BroadcastMan
|
||||||
expect(app(BroadcastManager::class))->toBeInstanceOf(BroadcastManager::class);
|
expect(app(BroadcastManager::class))->toBeInstanceOf(BroadcastManager::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('BroadcastTenancyBootstrapper maps tenant broadcaster credentials to config as specified in the $credentialsMap property and reverts the config after ending tenancy', function() {
|
test('BroadcastingConfigBootstrapper maps tenant broadcaster credentials to config as specified in the $credentialsMap property and reverts the config after ending tenancy', function() {
|
||||||
config([
|
config([
|
||||||
'broadcasting.connections.testing.driver' => 'testing',
|
'broadcasting.connections.testing.driver' => 'testing',
|
||||||
'broadcasting.connections.testing.message' => $defaultMessage = 'default',
|
'broadcasting.connections.testing.message' => $defaultMessage = 'default',
|
||||||
'tenancy.bootstrappers' => [BroadcastTenancyBootstrapper::class],
|
'tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
BroadcastTenancyBootstrapper::$credentialsMap = [
|
BroadcastingConfigBootstrapper::$credentialsMap = [
|
||||||
'broadcasting.connections.testing.message' => 'testing_broadcaster_message',
|
'broadcasting.connections.testing.message' => 'testing_broadcaster_message',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -407,16 +414,16 @@ test('BroadcastTenancyBootstrapper maps tenant broadcaster credentials to config
|
||||||
expect(config('broadcasting.connections.testing.message'))->toBe($defaultMessage);
|
expect(config('broadcasting.connections.testing.message'))->toBe($defaultMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('BroadcastTenancyBootstrapper makes the app use broadcasters with the correct credentials', function() {
|
test('BroadcastingConfigBootstrapper makes the app use broadcasters with the correct credentials', function() {
|
||||||
config([
|
config([
|
||||||
'broadcasting.default' => 'testing',
|
'broadcasting.default' => 'testing',
|
||||||
'broadcasting.connections.testing.driver' => 'testing',
|
'broadcasting.connections.testing.driver' => 'testing',
|
||||||
'broadcasting.connections.testing.message' => $defaultMessage = 'default',
|
'broadcasting.connections.testing.message' => $defaultMessage = 'default',
|
||||||
'tenancy.bootstrappers' => [BroadcastTenancyBootstrapper::class],
|
'tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
TenancyBroadcastManager::$tenantBroadcasters[] = 'testing';
|
TenancyBroadcastManager::$tenantBroadcasters[] = 'testing';
|
||||||
BroadcastTenancyBootstrapper::$credentialsMap = [
|
BroadcastingConfigBootstrapper::$credentialsMap = [
|
||||||
'broadcasting.connections.testing.message' => 'testing_broadcaster_message',
|
'broadcasting.connections.testing.message' => 'testing_broadcaster_message',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -608,7 +615,7 @@ test('url binding tenancy bootstrapper changes route helper behavior correctly',
|
||||||
pest()->get("http://localhost/central/home")->assertSee($centralRouteUrl);
|
pest()->get("http://localhost/central/home")->assertSee($centralRouteUrl);
|
||||||
pest()->get("http://localhost/$tenantKey/home")->assertSee($tenantRouteUrl);
|
pest()->get("http://localhost/$tenantKey/home")->assertSee($tenantRouteUrl);
|
||||||
pest()->get("http://localhost/query-string?tenant=$tenantKey")->assertSee($queryStringTenantUrl);
|
pest()->get("http://localhost/query-string?tenant=$tenantKey")->assertSee($queryStringTenantUrl);
|
||||||
})->group('string');
|
});
|
||||||
|
|
||||||
test('fortify route tenancy bootstrapper updates fortify config correctly', function() {
|
test('fortify route tenancy bootstrapper updates fortify config correctly', function() {
|
||||||
config(['tenancy.bootstrappers' => [FortifyRouteTenancyBootstrapper::class]]);
|
config(['tenancy.bootstrappers' => [FortifyRouteTenancyBootstrapper::class]]);
|
||||||
|
|
@ -649,6 +656,119 @@ test('database tenancy bootstrapper throws an exception if DATABASE_URL is set',
|
||||||
expect(true)->toBe(true);
|
expect(true)->toBe(true);
|
||||||
})->with(['abc.us-east-1.rds.amazonaws.com', null]);
|
})->with(['abc.us-east-1.rds.amazonaws.com', null]);
|
||||||
|
|
||||||
|
test('BroadcastChannelPrefixBootstrapper prefixes the channels events are broadcast on while tenancy is initialized', function() {
|
||||||
|
config([
|
||||||
|
'broadcasting.default' => $driver = 'testing',
|
||||||
|
'broadcasting.connections.testing.driver' => $driver,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Use custom broadcaster
|
||||||
|
app(BroadcastManager::class)->extend($driver, fn () => new TestingBroadcaster('original broadcaster'));
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [BroadcastChannelPrefixBootstrapper::class, DatabaseTenancyBootstrapper::class]]);
|
||||||
|
|
||||||
|
Schema::create('users', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->string('password');
|
||||||
|
$table->rememberToken();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
universal_channel('users.{userId}', function ($user, $userId) {
|
||||||
|
return User::find($userId)->is($user);
|
||||||
|
});
|
||||||
|
|
||||||
|
$broadcaster = app(BroadcastManager::class)->driver();
|
||||||
|
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
$tenant2 = Tenant::create();
|
||||||
|
|
||||||
|
pest()->artisan('tenants:migrate');
|
||||||
|
|
||||||
|
// Set up the 'testing' broadcaster override
|
||||||
|
// Identical to the default Pusher override (BroadcastChannelPrefixBootstrapper::pusher())
|
||||||
|
// Except for the parent class (TestingBroadcaster instead of PusherBroadcaster)
|
||||||
|
BroadcastChannelPrefixBootstrapper::$broadcasterOverrides['testing'] = function () {
|
||||||
|
return app(BroadcastManager::class)->extend('testing', function ($app, $config) {
|
||||||
|
return new class('tenant broadcaster') extends TestingBroadcaster {
|
||||||
|
protected function formatChannels(array $channels)
|
||||||
|
{
|
||||||
|
$formatChannel = function (string $channel) {
|
||||||
|
$prefixes = ['private-', 'presence-'];
|
||||||
|
$defaultPrefix = '';
|
||||||
|
|
||||||
|
foreach ($prefixes as $prefix) {
|
||||||
|
if (str($channel)->startsWith($prefix)) {
|
||||||
|
$defaultPrefix = $prefix;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip prefixing channels flagged with the central channel prefix
|
||||||
|
if (! str($channel)->startsWith('global__')) {
|
||||||
|
$channel = str($channel)->after($defaultPrefix)->prepend($defaultPrefix . tenant()->getTenantKey() . '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
return array_map($formatChannel, parent::formatChannels($channels));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
auth()->login($user = User::create(['name' => 'central', 'email' => 'test@central.cz', 'password' => 'test']));
|
||||||
|
|
||||||
|
// The channel names used for testing the formatChannels() method (not real channels)
|
||||||
|
$channelNames = [
|
||||||
|
'channel',
|
||||||
|
'global__channel', // Channels prefixed with 'global__' shouldn't get prefixed with the tenant key
|
||||||
|
'private-user.' . $user->id,
|
||||||
|
];
|
||||||
|
|
||||||
|
// formatChannels doesn't prefix the channel names until tenancy is initialized
|
||||||
|
expect(invade(app(BroadcastManager::class)->driver())->formatChannels($channelNames))->toEqual($channelNames);
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
$tenantBroadcaster = app(BroadcastManager::class)->driver();
|
||||||
|
|
||||||
|
auth()->login($tenantUser = User::create(['name' => 'tenant', 'email' => 'test@tenant.cz', 'password' => 'test']));
|
||||||
|
|
||||||
|
// The current (tenant) broadcaster isn't the same as the central one
|
||||||
|
expect($tenantBroadcaster->message)->not()->toBe($broadcaster->message);
|
||||||
|
// Tenant broadcaster has the same channels as the central broadcaster
|
||||||
|
expect($tenantBroadcaster->getChannels())->toEqualCanonicalizing($broadcaster->getChannels());
|
||||||
|
// formatChannels prefixes the channel names now
|
||||||
|
expect(invade($tenantBroadcaster)->formatChannels($channelNames))->toEqualCanonicalizing([
|
||||||
|
'global__channel',
|
||||||
|
$tenant->getTenantKey() . '.channel',
|
||||||
|
'private-' . $tenant->getTenantKey() . '.user.' . $tenantUser->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Initialize another tenant
|
||||||
|
tenancy()->initialize($tenant2);
|
||||||
|
|
||||||
|
auth()->login($tenantUser = User::create(['name' => 'tenant', 'email' => 'test2@tenant.cz', 'password' => 'test']));
|
||||||
|
|
||||||
|
// formatChannels prefixes channels with the second tenant's key now
|
||||||
|
expect(invade(app(BroadcastManager::class)->driver())->formatChannels($channelNames))->toEqualCanonicalizing([
|
||||||
|
'global__channel',
|
||||||
|
$tenant2->getTenantKey() . '.channel',
|
||||||
|
'private-' . $tenant2->getTenantKey() . '.user.' . $tenantUser->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The bootstrapper reverts to the tenant context – the channel names won't be prefixed anymore
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
// The current broadcaster is the same as the central one again
|
||||||
|
expect(app(BroadcastManager::class)->driver())->toBe($broadcaster);
|
||||||
|
expect(invade(app(BroadcastManager::class)->driver())->formatChannels($channelNames))->toEqual($channelNames);
|
||||||
|
});
|
||||||
|
|
||||||
function getDiskPrefix(string $disk): string
|
function getDiskPrefix(string $disk): string
|
||||||
{
|
{
|
||||||
/** @var FilesystemAdapter $disk */
|
/** @var FilesystemAdapter $disk */
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,27 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\Broadcasters\NullBroadcaster;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Stancl\Tenancy\Events\TenancyEnded;
|
use Stancl\Tenancy\Events\TenancyEnded;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Broadcast;
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
|
||||||
use Illuminate\Broadcasting\BroadcastManager;
|
use Illuminate\Broadcasting\BroadcastManager;
|
||||||
use Stancl\Tenancy\Events\TenancyInitialized;
|
use Stancl\Tenancy\Events\TenancyInitialized;
|
||||||
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
use Stancl\Tenancy\Listeners\BootstrapTenancy;
|
||||||
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
use Stancl\Tenancy\Tests\Etc\TestingBroadcaster;
|
||||||
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
use Stancl\Tenancy\Listeners\RevertToCentralContext;
|
||||||
|
use Stancl\Tenancy\Overrides\TenancyBroadcastManager;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||||
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
use Illuminate\Contracts\Broadcasting\Broadcaster as BroadcasterContract;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastTenancyBootstrapper;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
withTenantDatabases();
|
withTenantDatabases();
|
||||||
config(['tenancy.bootstrappers' => [BroadcastTenancyBootstrapper::class]]);
|
|
||||||
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
TenancyBroadcastManager::$tenantBroadcasters = ['pusher', 'ably'];
|
||||||
|
|
||||||
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
|
||||||
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
|
||||||
});
|
});
|
||||||
|
|
@ -27,6 +32,7 @@ afterEach(function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bound broadcaster instance is the same before initializing tenancy and after ending it', function() {
|
test('bound broadcaster instance is the same before initializing tenancy and after ending it', function() {
|
||||||
|
config(['tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class]]);
|
||||||
config(['broadcasting.default' => 'null']);
|
config(['broadcasting.default' => 'null']);
|
||||||
TenancyBroadcastManager::$tenantBroadcasters[] = 'null';
|
TenancyBroadcastManager::$tenantBroadcasters[] = 'null';
|
||||||
|
|
||||||
|
|
@ -45,6 +51,7 @@ test('bound broadcaster instance is the same before initializing tenancy and aft
|
||||||
});
|
});
|
||||||
|
|
||||||
test('new broadcasters get the channels from the previously bound broadcaster', function() {
|
test('new broadcasters get the channels from the previously bound broadcaster', function() {
|
||||||
|
config(['tenancy.bootstrappers' => [BroadcastingConfigBootstrapper::class]]);
|
||||||
config([
|
config([
|
||||||
'broadcasting.default' => $driver = 'testing',
|
'broadcasting.default' => $driver = 'testing',
|
||||||
'broadcasting.connections.testing.driver' => $driver,
|
'broadcasting.connections.testing.driver' => $driver,
|
||||||
|
|
@ -70,3 +77,111 @@ test('new broadcasters get the channels from the previously bound broadcaster',
|
||||||
|
|
||||||
expect($channel)->toBeIn($getCurrentChannels());
|
expect($channel)->toBeIn($getCurrentChannels());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('broadcasting channel helpers register channels correctly', function() {
|
||||||
|
config([
|
||||||
|
'broadcasting.default' => $driver = 'testing',
|
||||||
|
'broadcasting.connections.testing.driver' => $driver,
|
||||||
|
]);
|
||||||
|
|
||||||
|
config(['tenancy.bootstrappers' => [DatabaseTenancyBootstrapper::class]]);
|
||||||
|
|
||||||
|
Schema::create('users', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->string('password');
|
||||||
|
$table->rememberToken();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
$centralUser = User::create(['name' => 'central', 'email' => 'test@central.cz', 'password' => 'test']);
|
||||||
|
$tenant = Tenant::create();
|
||||||
|
|
||||||
|
migrateTenants();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
// Same ID as $centralUser
|
||||||
|
$tenantUser = User::create(['name' => 'tenant', 'email' => 'test@tenant.cz', 'password' => 'test']);
|
||||||
|
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
/** @var BroadcastManager $broadcastManager */
|
||||||
|
$broadcastManager = app(BroadcastManager::class);
|
||||||
|
|
||||||
|
// Use a driver with no channels
|
||||||
|
$broadcastManager->extend($driver, fn () => new NullBroadcaster);
|
||||||
|
|
||||||
|
$getChannels = fn (): Collection => $broadcastManager->driver($driver)->getChannels();
|
||||||
|
|
||||||
|
expect($getChannels())->toBeEmpty();
|
||||||
|
|
||||||
|
// Basic channel registration
|
||||||
|
Broadcast::channel($channelName = 'user.{userName}', $channelClosure = function ($user, $userName) {
|
||||||
|
return User::firstWhere('name', $userName)?->is($user) ?? false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the channel is registered
|
||||||
|
$centralChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === $channelName);
|
||||||
|
expect($centralChannelClosure)->not()->toBeNull();
|
||||||
|
|
||||||
|
// Channel closures work as expected (running in central context)
|
||||||
|
expect($centralChannelClosure($centralUser, $centralUser->name))->toBeTrue();
|
||||||
|
expect($centralChannelClosure($centralUser, $tenantUser->name))->toBeFalse();
|
||||||
|
|
||||||
|
// Register a tenant broadcasting channel (almost identical to the original channel, just able to accept the tenant key)
|
||||||
|
tenant_channel($channelName, $channelClosure);
|
||||||
|
|
||||||
|
// Tenant channel registered – its name is correctly prefixed ("{tenant}.user.{userId}")
|
||||||
|
$tenantChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === "{tenant}.$channelName");
|
||||||
|
expect($tenantChannelClosure)
|
||||||
|
->not()->toBeNull() // Channel registered
|
||||||
|
->not()->toBe($centralChannelClosure); // The tenant channel closure is different – after the auth user, it accepts the tenant ID
|
||||||
|
|
||||||
|
// The tenant channels are prefixed with '{tenant}.'
|
||||||
|
// They accept the tenant key, but their closures only run in tenant context when tenancy is initialized
|
||||||
|
// The regular channels don't accept the tenant key, but they also respect the current context
|
||||||
|
// The tenant key is used solely for the name prefixing – the closures can still run in the central context
|
||||||
|
expect($tenantChannelClosure($centralUser, $tenant->getTenantKey(), $centralUser->name))->toBeTrue();
|
||||||
|
expect($tenantChannelClosure($centralUser, $tenant->getTenantKey(), $tenantUser->name))->toBeFalse();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant);
|
||||||
|
|
||||||
|
// The channel closure runs in the central context
|
||||||
|
// Only the central user is available
|
||||||
|
expect($tenantChannelClosure($centralUser, $tenant->getTenantKey(), $tenantUser->name))->toBeFalse();
|
||||||
|
expect($tenantChannelClosure($tenantUser, $tenant->getTenantKey(), $tenantUser->name))->toBeTrue();
|
||||||
|
|
||||||
|
// Use a new channel instance to delete the previously registered channels before testing the univeresal_channel helper
|
||||||
|
$broadcastManager->purge($driver);
|
||||||
|
$broadcastManager->extend($driver, fn () => new NullBroadcaster);
|
||||||
|
|
||||||
|
expect($getChannels())->toBeEmpty();
|
||||||
|
|
||||||
|
// universal_channel helper registers both the unprefixed and the prefixed broadcasting channel correctly
|
||||||
|
// Using the tenant_channel helper + basic channel registration (Broadcast::channel())
|
||||||
|
universal_channel($channelName, $channelClosure);
|
||||||
|
|
||||||
|
// Regular channel registered correctly
|
||||||
|
$centralChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === $channelName);
|
||||||
|
expect($centralChannelClosure)->not()->toBeNull();
|
||||||
|
|
||||||
|
// Tenant channel registered correctly
|
||||||
|
$tenantChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === "{tenant}.$channelName");
|
||||||
|
expect($tenantChannelClosure)
|
||||||
|
->not()->toBeNull() // Channel registered
|
||||||
|
->not()->toBe($centralChannelClosure); // The tenant channel callback is different – after the auth user, it accepts the tenant ID
|
||||||
|
|
||||||
|
$broadcastManager->purge($driver);
|
||||||
|
$broadcastManager->extend($driver, fn () => new NullBroadcaster);
|
||||||
|
|
||||||
|
expect($getChannels())->toBeEmpty();
|
||||||
|
|
||||||
|
// Central channel prefixes the channel name with 'global__'
|
||||||
|
global_channel($channelName, $channelClosure);
|
||||||
|
|
||||||
|
// Channel prefixed with 'global__' found
|
||||||
|
$foundChannelClosure = $getChannels()->first(fn ($closure, $name) => $name === 'global__' . $channelName);
|
||||||
|
expect($foundChannelClosure)->not()->toBeNull();
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use Illuminate\Broadcasting\Broadcasters\Broadcaster;
|
||||||
|
|
||||||
class TestingBroadcaster extends Broadcaster {
|
class TestingBroadcaster extends Broadcaster {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $message
|
public string $message = 'nothing'
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function auth($request)
|
public function auth($request)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use Stancl\Tenancy\Tenancy;
|
||||||
use Stancl\Tenancy\Tests\Etc\Tenant;
|
use Stancl\Tenancy\Tests\Etc\Tenant;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
|
use Stancl\Tenancy\Bootstrappers\BroadcastChannelPrefixBootstrapper;
|
||||||
use Stancl\Tenancy\Facades\GlobalCache;
|
use Stancl\Tenancy\Facades\GlobalCache;
|
||||||
use Stancl\Tenancy\TenancyServiceProvider;
|
use Stancl\Tenancy\TenancyServiceProvider;
|
||||||
use Stancl\Tenancy\Facades\Tenancy as TenancyFacade;
|
use Stancl\Tenancy\Facades\Tenancy as TenancyFacade;
|
||||||
|
|
@ -17,7 +18,7 @@ use Stancl\Tenancy\Bootstrappers\UrlTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\MailTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\RedisTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\DatabaseTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\BroadcastTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\BroadcastingConfigBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper;
|
||||||
use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper;
|
use Stancl\Tenancy\Bootstrappers\PrefixCacheTenancyBootstrapper;
|
||||||
|
|
||||||
|
|
@ -124,7 +125,8 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
|
|
||||||
$app->singleton(RedisTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
$app->singleton(RedisTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
||||||
$app->singleton(PrefixCacheTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
$app->singleton(PrefixCacheTenancyBootstrapper::class); // todo (Samuel) use proper approach eg config for singleton registration
|
||||||
$app->singleton(BroadcastTenancyBootstrapper::class);
|
$app->singleton(BroadcastingConfigBootstrapper::class);
|
||||||
|
$app->singleton(BroadcastChannelPrefixBootstrapper::class);
|
||||||
$app->singleton(MailTenancyBootstrapper::class);
|
$app->singleton(MailTenancyBootstrapper::class);
|
||||||
$app->singleton(UrlTenancyBootstrapper::class);
|
$app->singleton(UrlTenancyBootstrapper::class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue