mirror of
https://github.com/archtechx/tenancy.git
synced 2025-12-12 18:44:03 +00:00
Merge branch '2.x'
This commit is contained in:
commit
a266a46c83
66 changed files with 1503 additions and 600 deletions
|
|
@ -20,7 +20,7 @@ before_script:
|
|||
- export DB_USERNAME=root DB_PASSWORD="" DB_DATABASE=tenancy CODECOV_TOKEN="24382d15-84e7-4a55-bea4-c4df96a24a9b"
|
||||
- cat vendor/laravel/framework/src/Illuminate/Foundation/Application.php| grep 'const VERSION'
|
||||
|
||||
script: ./test
|
||||
script: ./fulltest
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
|
|
|||
119
CHANGELOG-1.x.md
119
CHANGELOG-1.x.md
|
|
@ -1,119 +0,0 @@
|
|||
# Release Notes for 1.x
|
||||
|
||||
## [v1.8.0 (2019-08-17)](https://github.com/stancl/tenancy/compare/v1.7.0...v1.8.0)
|
||||
|
||||
### Added
|
||||
|
||||
- **Multi-tenant Jobs:** Jobs are now automatically multi-tenant. The [documentation page](https://stancl-tenancy.netlify.com/docs/jobs-queues/) covers the small tweaks you will have to make to your config to get multi-tenant jobs to work.
|
||||
- **Telescope Integration**: You can read more about this on the [documentation page](https://stancl-tenancy.netlify.com/docs/telescope/).
|
||||
- **Horizon Integration**: You can read more about this on the [documentation page](https://stancl-tenancy.netlify.com/docs/horizon/).
|
||||
- **Tenant Redirect** and **Custom ID schemes**: You can now easily redirect to tenant domains. You can also use a custom tenant ID scheme if you don't like UUIDs. You can read about these features [here](https://stancl-tenancy.netlify.com/docs/misc-tips/).
|
||||
|
||||
### Fixed
|
||||
|
||||
- #112 *PostgreSQL Database creation error.*
|
||||
|
||||
### Code
|
||||
|
||||
- Strict types declaration is now used in every file.
|
||||
|
||||
## [v1.7.0 (2019-08-17)](https://github.com/stancl/tenancy/compare/v1.6.1...v1.7.0)
|
||||
|
||||
### Added:
|
||||
|
||||
- DB storage driver - you don't have to use Redis to store tenants anymore. Relational databases are now supported as well. [more info](https://stancl-tenancy.netlify.com/docs/storage-drivers/#database)
|
||||
- `tenancy:install` will do everything except DB/Redis connection creation for you. It will make changes to Http/Kernel.php, create `routes/tenant.php`, publish config, and (optionally) publish the migration. [more info](https://stancl-tenancy.netlify.com/docs/installation/)
|
||||
- `tenants:run` [more info](https://stancl-tenancy.netlify.com/docs/console-commands/#run)
|
||||
- New documentation: https://stancl-tenancy.netlify.com
|
||||
- Custom tenant DB names [more info](https://stancl-tenancy.netlify.com/docs/custom-database-names/)
|
||||
- stancl/tenancy events [more info](https://stancl-tenancy.netlify.com/docs/event-system/)
|
||||
|
||||
### Fixed:
|
||||
|
||||
- #89 *Command "tenants:migrate" cannot be found when used in app code*
|
||||
- #87 *Unable to migrate multiple tenants at once when using MySQL*
|
||||
- #96 *Issue w/ redis->scan() in getAllTenants logic.*
|
||||
|
||||
## [v1.6.1 (2019-08-04)](https://github.com/stancl/tenancy/compare/v1.6.0...v1.6.1)
|
||||
|
||||
Multiple phpunit.xml configs are now generated to run the tests with different configurations, such as different Redis drivers.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `tenancy()->all()` with predis [`0dc8c80`](https://github.com/stancl/tenancy/commit/0dc8c80a02efbee5676cc72e648e108037ca5268)
|
||||
|
||||
### Dropped
|
||||
|
||||
- Laravel 5.7 support [`65b3882`](https://github.com/stancl/tenancy/commit/65b38827d5a2fa183838a9dce9fb6a157fd7e859)
|
||||
|
||||
## [v1.6.0 (2019-07-30)](https://github.com/stancl/tenancy/compare/v1.5.1...v1.6.0)
|
||||
|
||||
### Added
|
||||
|
||||
- `GlobalCache` facade [#78](https://github.com/stancl/tenancy/pull/78)
|
||||
|
||||
## [v1.5.1 (2019-07-25)](https://github.com/stancl/tenancy/compare/v1.5.0...v1.5.1)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Database is reconnected after migrating/rolling back/seeding is done [#71](https://github.com/stancl/tenancy/pull/71)
|
||||
- Fixed tenant()->delete() (it used to delete the record from the `tenants` namespace but not the `domains` namespace) [#73](https://github.com/stancl/tenancy/pull/73)
|
||||
|
||||
## [v1.5.0 (2019-07-13)](https://github.com/stancl/tenancy/compare/v1.4.0...v1.5.0)
|
||||
|
||||
### Added
|
||||
|
||||
- PostgreSQL DB manager [#52](https://github.com/stancl/tenancy/pull/52)
|
||||
- `tenancy()->end()` [#68](https://github.com/stancl/tenancy/pull/68)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Return type docblock for `TenantManager::all()` [#63](https://github.com/stancl/tenancy/issue/63)
|
||||
|
||||
## [v1.4.0 (2019-07-03)](https://github.com/stancl/tenancy/compare/v1.3.1...v1.4.0)
|
||||
|
||||
### Added
|
||||
|
||||
- Predis support [#59](https://github.com/stancl/tenancy/pull/59)
|
||||
|
||||
## [v1.3.1 (2019-05-06)](https://github.com/stancl/tenancy/compare/v1.3.0...v1.3.1)
|
||||
|
||||
### Fixed
|
||||
- Fix jobs [#38](https://github.com/stancl/tenancy/pull/38)
|
||||
- Fix tests for 5.8 [#41](https://github.com/stancl/tenancy/issues/41)
|
||||
|
||||
|
||||
## [v1.3.0 (2019-02-27)](https://github.com/stancl/tenancy/compare/v1.2.0...v1.3.0)
|
||||
|
||||
### Added
|
||||
- Add 5.8 support [#33](https://github.com/stancl/tenancy/pull/33)
|
||||
|
||||
|
||||
## [v1.2.0 (2019-02-15)](https://github.com/stancl/tenancy/compare/v1.1.3...v1.2.0)
|
||||
|
||||
### Added
|
||||
- Add `Tenancy` facade [#29](https://github.com/stancl/tenancy/issues/29) [`987c54f`](https://github.com/stancl/tenancy/commit/987c54f04e6ff3bdef068d92da6a9ace847f6c37)
|
||||
|
||||
|
||||
## [v1.1.3 (2019-02-13)](https://github.com/stancl/tenancy/compare/v1.1.2...v1.1.3)
|
||||
|
||||
### Fixed
|
||||
- Fix CacheManager (it merged tags incorrectly), write tests for CacheManager [#31](https://github.com/stancl/tenancy/issues/31) [`a2d68b1`](https://github.com/stancl/tenancy/commit/a2d68b12611350f70befa3eb97fb56c99d006b54)
|
||||
|
||||
|
||||
## [v1.1.2 (2019-02-13)](https://github.com/stancl/tenancy/compare/v1.1.1...v1.1.2)
|
||||
|
||||
### Fixed
|
||||
- Fix small bug in CacheManager [`d4d4119`](https://github.com/stancl/tenancy/commit/d4d411975496272158d7823597427fad8966fff8)
|
||||
|
||||
|
||||
## [v1.1.1 (2019-02-11)](https://github.com/stancl/tenancy/compare/v1.1.0...v1.1.1)
|
||||
|
||||
### Fixed
|
||||
- Fix "Associative arrays are stored as objects" [#28](https://github.com/stancl/tenancy/issues/28)
|
||||
|
||||
|
||||
## [v1.1.0 (2019-02-10)](https://github.com/stancl/tenancy/compare/v1.0.0...v1.1.0)
|
||||
|
||||
### Added
|
||||
- Add array support to the storage [#27](https://github.com/stancl/tenancy/pull/27)
|
||||
15
CONTRIBUTING.md
Normal file
15
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Contributing
|
||||
|
||||
## Code style
|
||||
|
||||
StyleCI will automatically fix code style violations in your pull requests.
|
||||
|
||||
## Running tests
|
||||
|
||||
### With Docker
|
||||
If you have Docker installed, simply run ./test. When you're done testing, run docker-compose down to shut down the containers.
|
||||
|
||||
### Without Docker
|
||||
If you run the tests of this package, please make sure you don't store anything in Redis @ 127.0.0.1:6379 db#14. The contents of this database are flushed everytime the tests are run.
|
||||
|
||||
Some tests are run only if the CI, TRAVIS and CONTINUOUS_INTEGRATION environment variables are set to true. This is to avoid things like bloating your MySQL instance with test databases.
|
||||
17
README.md
17
README.md
|
|
@ -1,22 +1,21 @@
|
|||
# [stancl/tenancy](https://tenancy.samuelstancl.me)
|
||||
|
||||
[](https://laravel.com)
|
||||
[](https://laravel.com)
|
||||
[](https://packagist.org/packages/stancl/tenancy)
|
||||
[](https://travis-ci.com/stancl/tenancy)
|
||||
[](https://codecov.io/gh/stancl/tenancy)
|
||||
[](https://travis-ci.com/stancl/tenancy)
|
||||
[](https://codecov.io/gh/stancl/tenancy)
|
||||
[](https://gumroad.com/l/tenancy)
|
||||
|
||||
### *A Laravel multi-database tenancy package that respects your code.*
|
||||
### *Automatic multi-tenancy for your Laravel app.*
|
||||
|
||||
You won't have to change a thing in your application's code.\*
|
||||
You won't have to change a thing in your application's code.
|
||||
|
||||
- :heavy_check_mark: No model traits to change database connection
|
||||
- :heavy_check_mark: No replacing of Laravel classes (`Cache`, `Storage`, ...) with tenancy-aware classes
|
||||
- :heavy_check_mark: Built-in tenant identification based on hostname (including second level domains)
|
||||
|
||||
\* depending on how you use the filesystem. Everything else will work out of the box.
|
||||
### [Documentation](https://tenancy.samuelstancl.me/docs/v2/)
|
||||
|
||||
### [Documentation](https://tenancy.samuelstancl.me/docs)
|
||||
|
||||
Documentation can be found here: https://tenancy.samuelstancl.me/docs
|
||||
Documentation can be found here: https://tenancy.samuelstancl.me/docs/v2/
|
||||
|
||||
The repository with the documentation source code can be found here: [stancl/tenancy-docs](https://github.com/stancl/tenancy-docs).
|
||||
|
|
|
|||
|
|
@ -3,16 +3,22 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'storage_driver' => 'Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver',
|
||||
'storage' => [
|
||||
'db' => [ // Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver
|
||||
'storage_driver' => 'db',
|
||||
'storage_drivers' => [
|
||||
'db' => [
|
||||
'driver' => Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver::class,
|
||||
'data_column' => 'data',
|
||||
'custom_columns' => [
|
||||
// 'plan',
|
||||
],
|
||||
'connection' => 'central',
|
||||
'connection' => null,
|
||||
'table_names' => [
|
||||
'TenantModel' => 'tenants',
|
||||
'DomainModel' => 'domains',
|
||||
],
|
||||
'redis' => [ // Stancl\Tenancy\StorageDrivers\RedisStorageDriver
|
||||
],
|
||||
'redis' => [
|
||||
'driver' => Stancl\Tenancy\StorageDrivers\RedisStorageDriver::class,
|
||||
'connection' => 'tenancy',
|
||||
],
|
||||
],
|
||||
|
|
@ -21,7 +27,7 @@ return [
|
|||
// 'localhost',
|
||||
],
|
||||
'database' => [
|
||||
'based_on' => 'mysql', // The connection that will be used as a base for the dynamically created tenant connection.
|
||||
'based_on' => null, // The connection that will be used as a base for the dynamically created tenant connection.
|
||||
'prefix' => 'tenant',
|
||||
'suffix' => '',
|
||||
],
|
||||
|
|
@ -35,7 +41,7 @@ return [
|
|||
'cache' => [
|
||||
'tag_base' => 'tenant',
|
||||
],
|
||||
'filesystem' => [ // https://stancl-tenancy.netlify.com/docs/filesystem-tenancy/
|
||||
'filesystem' => [ // https://tenancy.samuelstancl.me/docs/v2/filesystem-tenancy/
|
||||
'suffix_base' => 'tenant',
|
||||
// Disks which should be suffixed with the suffix_base + tenant id.
|
||||
'disks' => [
|
||||
|
|
@ -51,29 +57,43 @@ return [
|
|||
],
|
||||
'database_managers' => [
|
||||
// Tenant database managers handle the creation & deletion of tenant databases.
|
||||
'sqlite' => 'Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager',
|
||||
'mysql' => 'Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager',
|
||||
'pgsql' => 'Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager',
|
||||
'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
|
||||
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
|
||||
'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
|
||||
],
|
||||
'database_manager_connections' => [
|
||||
// Connections used by TenantDatabaseManagers. This tells, for example, the
|
||||
// MySQLDatabaseManager to use the mysql connection to create databases.
|
||||
'sqlite' => 'sqlite',
|
||||
'mysql' => 'mysql',
|
||||
'pgsql' => 'pgsql',
|
||||
],
|
||||
'bootstrappers' => [
|
||||
// Tenancy bootstrappers are executed when tenancy is initialized.
|
||||
// Their responsibility is making Laravel features tenant-aware.
|
||||
'database' => 'Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper',
|
||||
'cache' => 'Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper',
|
||||
'filesystem' => 'Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper',
|
||||
'redis' => 'Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper',
|
||||
'queue' => 'Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper',
|
||||
'database' => Stancl\Tenancy\TenancyBootstrappers\DatabaseTenancyBootstrapper::class,
|
||||
'cache' => Stancl\Tenancy\TenancyBootstrappers\CacheTenancyBootstrapper::class,
|
||||
'filesystem' => Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper::class,
|
||||
'queue' => Stancl\Tenancy\TenancyBootstrappers\QueueTenancyBootstrapper::class,
|
||||
// 'redis' => Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper::class, // Note: phpredis is needed
|
||||
],
|
||||
'features' => [
|
||||
// Features are classes that provide additional functionality
|
||||
// not needed for tenancy to be bootstrapped. They are run
|
||||
// regardless of whether tenancy has been initialized.
|
||||
'Stancl\Tenancy\Features\TelescopeTags',
|
||||
'Stancl\Tenancy\Features\TenantRedirect',
|
||||
|
||||
// Stancl\Tenancy\Features\TenantConfig::class,
|
||||
// Stancl\Tenancy\Features\TelescopeTags::class,
|
||||
// Stancl\Tenancy\Features\TenantRedirect::class,
|
||||
],
|
||||
'storage_to_config_map' => [ // Used by the TenantConfig feature
|
||||
// 'paypal_api_key' => 'services.paypal.api_key',
|
||||
],
|
||||
'home_url' => '/app',
|
||||
'migrate_after_creation' => false, // run migrations after creating a tenant
|
||||
'delete_database_after_tenant_deletion' => false, // delete tenant's database after deleting him
|
||||
'queue_automatic_migration' => false, // queue the automatic post-tenant-creation migrations
|
||||
'delete_database_after_tenant_deletion' => false, // delete the tenant's database after deleting the tenant
|
||||
'queue_database_creation' => false,
|
||||
'queue_database_deletion' => false,
|
||||
'unique_id_generator' => 'Stancl\Tenancy\UUIDGenerator',
|
||||
'unique_id_generator' => Stancl\Tenancy\UniqueIDGenerators\UUIDGenerator::class,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ class CreateTenantsTable extends Migration
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('tenants', function (Blueprint $table) {
|
||||
$table->string('id', 36)->primary(); // 36 characters is the default uuid length
|
||||
// your custom, indexed columns go here
|
||||
|
||||
// (optional) your custom, indexed columns may go here
|
||||
|
||||
$table->json('data');
|
||||
});
|
||||
|
|
@ -28,8 +29,8 @@ class CreateTenantsTable extends Migration
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('tenants');
|
||||
Schema::dropIfExists('tenants');
|
||||
}
|
||||
}
|
||||
|
|
@ -13,11 +13,13 @@ class CreateDomainsTable extends Migration
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('domains', function (Blueprint $table) {
|
||||
$table->string('tenant_id', 36)->primary(); // 36 characters is the default uuid length
|
||||
$table->string('domain', 255)->index(); // don't change this
|
||||
$table->string('domain', 255)->primary();
|
||||
$table->string('tenant_id', 36);
|
||||
|
||||
$table->foreign('tenant_id')->references('id')->on('tenants')->onUpdate('cascade')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -26,8 +28,8 @@ class CreateDomainsTable extends Migration
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
public function down(): void
|
||||
{
|
||||
Schema::drop('domains');
|
||||
Schema::dropIfExists('domains');
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,8 @@
|
|||
],
|
||||
"require": {
|
||||
"illuminate/support": "^6.0",
|
||||
"webpatser/laravel-uuid": "^3.0"
|
||||
"facade/ignition-contracts": "^1.0",
|
||||
"ramsey/uuid": "^3.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"vlucas/phpdotenv": "^3.3",
|
||||
|
|
|
|||
7
fulltest
Executable file
7
fulltest
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# for development
|
||||
docker-compose up -d
|
||||
./test "$@"
|
||||
docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/
|
||||
|
|
@ -8,19 +8,26 @@ use Illuminate\Cache\CacheManager as BaseCacheManager;
|
|||
|
||||
class CacheManager extends BaseCacheManager
|
||||
{
|
||||
/**
|
||||
* Add tags and forward the call to the inner cache store.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
$tags = [config('tenancy.cache.tag_base') . tenant('id')];
|
||||
|
||||
if ($method === 'tags') {
|
||||
if (\count($parameters) !== 1) {
|
||||
if (count($parameters) !== 1) {
|
||||
throw new \Exception("Method tags() takes exactly 1 argument. {count($parameters)} passed.");
|
||||
}
|
||||
|
||||
$names = $parameters[0];
|
||||
$names = (array) $names; // cache()->tags('foo') https://laravel.com/docs/5.7/cache#removing-tagged-cache-items
|
||||
|
||||
return $this->store()->tags(\array_merge($tags, $names));
|
||||
return $this->store()->tags(array_merge($tags, $names));
|
||||
}
|
||||
|
||||
return $this->store()->tags($tags)->$method(...$parameters);
|
||||
|
|
|
|||
47
src/Commands/CreateTenant.php
Normal file
47
src/Commands/CreateTenant.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class CreateTenant extends Command
|
||||
{
|
||||
protected $signature = 'tenants:create
|
||||
{--d|domain=* : The tenant\'s domains.}
|
||||
{data?* : The tenant\'s data. Separate keys and values by `=`, e.g. `plan=free`.}';
|
||||
|
||||
protected $description = 'Create a tenant.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$tenant = Tenant::new()
|
||||
->withDomains($this->getDomains())
|
||||
->withData($this->getData())
|
||||
->save();
|
||||
|
||||
$this->info($tenant->id);
|
||||
}
|
||||
|
||||
public function getDomains(): array
|
||||
{
|
||||
return $this->option('domain');
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return array_reduce($this->argument('data'), function ($data, $pair) {
|
||||
[$key, $value] = explode('=', $pair, 2);
|
||||
$data[$key] = $value;
|
||||
|
||||
return $data;
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
|
|
@ -36,21 +36,21 @@ class Install extends Command
|
|||
]);
|
||||
$this->info('✔️ Created config/tenancy.php');
|
||||
|
||||
$newKernel = \str_replace(
|
||||
$newKernel = str_replace(
|
||||
'protected $middlewarePriority = [',
|
||||
"protected \$middlewarePriority = [
|
||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,",
|
||||
\file_get_contents(app_path('Http/Kernel.php'))
|
||||
file_get_contents(app_path('Http/Kernel.php'))
|
||||
);
|
||||
|
||||
$newKernel = \str_replace("'web' => [", "'web' => [
|
||||
$newKernel = str_replace("'web' => [", "'web' => [
|
||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,", $newKernel);
|
||||
|
||||
\file_put_contents(app_path('Http/Kernel.php'), $newKernel);
|
||||
file_put_contents(app_path('Http/Kernel.php'), $newKernel);
|
||||
$this->info('✔️ Set middleware priority');
|
||||
|
||||
\file_put_contents(
|
||||
file_put_contents(
|
||||
base_path('routes/tenant.php'),
|
||||
"<?php
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class Install extends Command
|
|||
|
|
||||
*/
|
||||
|
||||
Route::get('/', function () {
|
||||
Route::get('/app', function () {
|
||||
return 'This is your multi-tenant application. The id of the current tenant is ' . tenant('id');
|
||||
});
|
||||
"
|
||||
|
|
@ -74,16 +74,16 @@ Route::get('/', function () {
|
|||
|
||||
$this->line('');
|
||||
$this->line("This package lets you store data about tenants either in Redis or in a relational database like MySQL. If you're going to use the database storage, you need to create a tenants table.");
|
||||
if ($this->confirm('Do you want to publish the default database migration?', true)) {
|
||||
if ($this->confirm('Do you want to publish the default database migrations?', true)) {
|
||||
$this->callSilent('vendor:publish', [
|
||||
'--provider' => 'Stancl\Tenancy\TenancyServiceProvider',
|
||||
'--tag' => 'migrations',
|
||||
]);
|
||||
$this->info('✔️ Created migration.');
|
||||
$this->info('✔️ Created migrations.');
|
||||
}
|
||||
|
||||
if (! \is_dir(database_path('migrations/tenant'))) {
|
||||
\mkdir(database_path('migrations/tenant'));
|
||||
if (! is_dir(database_path('migrations/tenant'))) {
|
||||
mkdir(database_path('migrations/tenant'));
|
||||
$this->info('✔️ Created database/migrations/tenant folder.');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,19 +53,17 @@ class Migrate extends MigrateCommand
|
|||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
|
||||
// See Illuminate\Database\Migrations\DatabaseMigrationRepository::getConnection.
|
||||
// Database connections are cached by Illuminate\Database\ConnectionResolver.
|
||||
$this->input->setOption('database', 'tenant');
|
||||
tenancy()->initialize($tenant); // todo2 test that this works with multiple tenants with MySQL
|
||||
$this->input->setOption('database', $tenant->getConnectionName());
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// Migrate
|
||||
parent::handle();
|
||||
|
||||
tenancy()->endTenancy();
|
||||
});
|
||||
|
||||
if ($originalTenant) {
|
||||
tenancy()->initialize($originalTenant);
|
||||
} else {
|
||||
tenancy()->endTenancy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
62
src/Commands/MigrateFresh.php
Normal file
62
src/Commands/MigrateFresh.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Stancl\Tenancy\Traits\DealsWithMigrations;
|
||||
use Stancl\Tenancy\Traits\HasATenantsOption;
|
||||
|
||||
final class MigrateFresh extends Command
|
||||
{
|
||||
use HasATenantsOption, DealsWithMigrations;
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Drop all tables and re-run all migrations for tenant(s)';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->setName('tenants:migrate-fresh');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$originalTenant = tenancy()->getTenant();
|
||||
$this->info('Dropping tables.');
|
||||
|
||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
$this->line("Tenant: {$tenant->id}");
|
||||
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
$this->call('db:wipe', array_filter([
|
||||
'--database' => $tenant->getConnectionName(),
|
||||
'--force' => true,
|
||||
]));
|
||||
|
||||
$this->call('tenants:migrate', [
|
||||
'--tenants' => [$tenant->id],
|
||||
]);
|
||||
|
||||
tenancy()->end();
|
||||
});
|
||||
|
||||
$this->info('Done.');
|
||||
|
||||
if ($originalTenant) {
|
||||
tenancy()->initialize($originalTenant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,21 +49,21 @@ class Rollback extends RollbackCommand
|
|||
return;
|
||||
}
|
||||
|
||||
$this->input->setOption('database', 'tenant');
|
||||
|
||||
$originalTenant = tenancy()->getTenant();
|
||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
|
||||
$this->input->setOption('database', $tenant->getConnectionName());
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// Migrate
|
||||
parent::handle();
|
||||
|
||||
tenancy()->endTenancy();
|
||||
});
|
||||
|
||||
if ($originalTenant) {
|
||||
tenancy()->initialize($originalTenant);
|
||||
} else {
|
||||
tenancy()->endTenancy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Run extends Command
|
|||
|
||||
$callback = function ($prefix = '') {
|
||||
return function ($arguments, $argument) use ($prefix) {
|
||||
[$key, $value] = \explode('=', $argument, 2);
|
||||
[$key, $value] = explode('=', $argument, 2);
|
||||
$arguments[$prefix . $key] = $value;
|
||||
|
||||
return $arguments;
|
||||
|
|
@ -47,21 +47,19 @@ class Run extends Command
|
|||
};
|
||||
|
||||
// Turns ['foo=bar', 'abc=xyz=zzz'] into ['foo' => 'bar', 'abc' => 'xyz=zzz']
|
||||
$arguments = \array_reduce($this->option('argument'), $callback(), []);
|
||||
$arguments = array_reduce($this->option('argument'), $callback(), []);
|
||||
|
||||
// Turns ['foo=bar', 'abc=xyz=zzz'] into ['--foo' => 'bar', '--abc' => 'xyz=zzz']
|
||||
$options = \array_reduce($this->option('option'), $callback('--'), []);
|
||||
$options = array_reduce($this->option('option'), $callback('--'), []);
|
||||
|
||||
// Run command
|
||||
$this->call($this->argument('commandname'), \array_merge($arguments, $options));
|
||||
$this->call($this->argument('commandname'), array_merge($arguments, $options));
|
||||
|
||||
tenancy()->endTenancy();
|
||||
});
|
||||
|
||||
if ($originalTenant) {
|
||||
tenancy()->initialize($originalTenant);
|
||||
} else {
|
||||
tenancy()->endTenancy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,21 +47,21 @@ class Seed extends SeedCommand
|
|||
return;
|
||||
}
|
||||
|
||||
$this->input->setOption('database', 'tenant');
|
||||
|
||||
$originalTenant = tenancy()->getTenant();
|
||||
tenancy()->all($this->option('tenants'))->each(function ($tenant) {
|
||||
$this->line("Tenant: {$tenant['id']}");
|
||||
|
||||
$this->input->setOption('database', $tenant->getConnectionName());
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
// Seed
|
||||
parent::handle();
|
||||
|
||||
tenancy()->endTenancy();
|
||||
});
|
||||
|
||||
if ($originalTenant) {
|
||||
tenancy()->initialize($originalTenant);
|
||||
} else {
|
||||
tenancy()->endTenancy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class TenantList extends Command
|
|||
{
|
||||
$this->info('Listing all tenants.');
|
||||
tenancy()->all()->each(function ($tenant) {
|
||||
$this->line("[Tenant] id: {$tenant['id']} @ ", implode('; ', $tenant->domains));
|
||||
$this->line("[Tenant] id: {$tenant['id']} @ " . implode('; ', $tenant->domains));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ namespace Stancl\Tenancy\Contracts;
|
|||
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
/**
|
||||
* TenancyBootstrappers are classes that make existing code tenant-aware.
|
||||
*/
|
||||
interface TenancyBootstrapper
|
||||
{
|
||||
public function start(Tenant $tenant); // todo2 TenantManager instead of Tenant
|
||||
public function start(Tenant $tenant);
|
||||
|
||||
public function end();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class DatabaseManager
|
|||
public function createTenantConnection($databaseName, $connectionName)
|
||||
{
|
||||
// Create the database connection.
|
||||
$based_on = $this->app['config']['tenancy.database.based_on'] ?? $this->originalDefaultConnectionName;
|
||||
$based_on = $this->getBaseConnection($connectionName);
|
||||
$this->app['config']["database.connections.$connectionName"] = $this->app['config']['database.connections.' . $based_on];
|
||||
|
||||
// Change database name.
|
||||
|
|
@ -71,17 +71,36 @@ class DatabaseManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the driver of a database connection.
|
||||
* Get the name of the connection that $connectionName should be based on.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @return string
|
||||
*/
|
||||
protected function getDriver(string $connectionName): string
|
||||
public function getBaseConnection(string $connectionName): string
|
||||
{
|
||||
return ($connectionName !== 'tenant' ? $connectionName : null) // 'tenant' is not a specific connection, it's the default
|
||||
?? $this->app['config']['tenancy.database.based_on']
|
||||
?? $this->originalDefaultConnectionName; // tenancy.database.based_on === null => use the default connection
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the driver of a database connection.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDriver(string $connectionName): ?string
|
||||
{
|
||||
return $this->app['config']["database.connections.$connectionName.driver"];
|
||||
}
|
||||
|
||||
public function switchConnection($connection)
|
||||
/**
|
||||
* Switch the application's connection.
|
||||
*
|
||||
* @param string $connection
|
||||
* @return void
|
||||
*/
|
||||
public function switchConnection(string $connection)
|
||||
{
|
||||
$this->app['config']['database.default'] = $connection;
|
||||
$this->database->purge();
|
||||
|
|
@ -103,6 +122,12 @@ class DatabaseManager
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a database for a tenant.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return void
|
||||
*/
|
||||
public function createDatabase(Tenant $tenant)
|
||||
{
|
||||
$database = $tenant->getDatabaseName();
|
||||
|
|
@ -111,10 +136,16 @@ class DatabaseManager
|
|||
if ($this->app['config']['tenancy.queue_database_creation'] ?? false) {
|
||||
QueuedTenantDatabaseCreator::dispatch($manager, $database);
|
||||
} else {
|
||||
return $manager->createDatabase($database);
|
||||
$manager->createDatabase($database);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tenant's database.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return void
|
||||
*/
|
||||
public function deleteDatabase(Tenant $tenant)
|
||||
{
|
||||
$database = $tenant->getDatabaseName();
|
||||
|
|
@ -123,15 +154,19 @@ class DatabaseManager
|
|||
if ($this->app['config']['tenancy.queue_database_deletion'] ?? false) {
|
||||
QueuedTenantDatabaseDeleter::dispatch($manager, $database);
|
||||
} else {
|
||||
return $manager->deleteDatabase($database);
|
||||
$manager->deleteDatabase($database);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TenantDatabaseManager for a tenant's database connection.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return TenantDatabaseManager
|
||||
*/
|
||||
protected function getTenantDatabaseManager(Tenant $tenant): TenantDatabaseManager
|
||||
{
|
||||
// todo2 this shouldn't have to create a connection
|
||||
$this->createTenantConnection($tenant->getDatabaseName(), $tenant->getConnectionName());
|
||||
$driver = $this->getDriver($tenant->getConnectionName());
|
||||
$driver = $this->getDriver($this->getBaseConnection($tenant->getConnectionName()));
|
||||
|
||||
$databaseManagers = $this->app['config']['tenancy.database_managers'];
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ class DatabaseManagerNotRegisteredException extends \Exception
|
|||
{
|
||||
public function __construct($driver)
|
||||
{
|
||||
$this->message = "Database manager for driver $driver is not registered.";
|
||||
parent::__construct("Database manager for driver $driver is not registered.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,23 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Exceptions;
|
||||
|
||||
class TenantCouldNotBeIdentifiedException extends \Exception
|
||||
use Facade\IgnitionContracts\BaseSolution;
|
||||
use Facade\IgnitionContracts\ProvidesSolution;
|
||||
use Facade\IgnitionContracts\Solution;
|
||||
|
||||
class TenantCouldNotBeIdentifiedException extends \Exception implements ProvidesSolution
|
||||
{
|
||||
public function __construct($domain)
|
||||
{
|
||||
$this->message = "Tenant could not be identified on domain $domain";
|
||||
parent::__construct("Tenant could not be identified on domain $domain");
|
||||
}
|
||||
|
||||
public function getSolution(): Solution
|
||||
{
|
||||
return BaseSolution::create('Tenant could not be identified on this domain')
|
||||
->setSolutionDescription('Did you forget to create a tenant for this domain?')
|
||||
->setDocumentationLinks([
|
||||
'Creating Tenants' => 'https://tenancy.samuelstancl.me/docs/v2/creating-tenants/',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,17 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Stancl\Tenancy\Tenant as Tenant;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
// todo2 rename to CurrentTenant?
|
||||
class TenantFacade extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return Tenant::class;
|
||||
}
|
||||
|
||||
public static function create($domains, array $data = []): Tenant
|
||||
{
|
||||
return Tenant::create($domains, $data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,13 @@ 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(TenantManager $tenantManager): void
|
||||
{
|
||||
if (! class_exists(Telescope::class)) {
|
||||
|
|
@ -26,7 +33,6 @@ class TelescopeTags implements Feature
|
|||
if (in_array('tenancy', optional(request()->route())->middleware() ?? [])) {
|
||||
$tags = array_merge($tags, [
|
||||
'tenant:' . tenant('id'),
|
||||
// todo2 domain?
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
61
src/Features/TenantConfig.php
Normal file
61
src/Features/TenantConfig.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Features;
|
||||
|
||||
use Illuminate\Config\Repository;
|
||||
use Stancl\Tenancy\Contracts\Feature;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
|
||||
class TenantConfig implements Feature
|
||||
{
|
||||
/** @var Repository */
|
||||
protected $config;
|
||||
|
||||
/** @var array */
|
||||
public $originalConfig = [];
|
||||
|
||||
public function __construct(Repository $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
foreach ($this->getStorageToConfigMap() as $configKey) {
|
||||
$this->originalConfig[$configKey] = $this->config[$configKey];
|
||||
}
|
||||
}
|
||||
|
||||
public function bootstrap(TenantManager $tenantManager): void
|
||||
{
|
||||
$tenantManager->eventListener('bootstrapped', function (TenantManager $manager) {
|
||||
$this->setTenantConfig($manager->getTenant());
|
||||
});
|
||||
|
||||
$tenantManager->eventListener('ended', function () {
|
||||
$this->unsetTenantConfig();
|
||||
});
|
||||
}
|
||||
|
||||
public function setTenantConfig(Tenant $tenant): void
|
||||
{
|
||||
foreach ($this->getStorageToConfigMap() as $storageKey => $configKey) {
|
||||
$override = $tenant->data[$storageKey] ?? null;
|
||||
if (! is_null($override)) {
|
||||
$this->config[$configKey] = $override;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function unsetTenantConfig(): void
|
||||
{
|
||||
foreach ($this->getStorageToConfigMap() as $configKey) {
|
||||
$this->config[$configKey] = $this->originalConfig[$configKey];
|
||||
}
|
||||
}
|
||||
|
||||
public function getStorageToConfigMap(): array
|
||||
{
|
||||
return $this->config['tenancy.storage_to_config_map'] ?? [];
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,10 @@ class QueuedTenantDatabaseCreator implements ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/** @var TenantDatabaseManager */
|
||||
protected $databaseManager;
|
||||
|
||||
/** @var string */
|
||||
protected $databaseName;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ class QueuedTenantDatabaseDeleter implements ShouldQueue
|
|||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/** @var TenantDatabaseManager */
|
||||
protected $databaseManager;
|
||||
|
||||
/** @var string */
|
||||
protected $databaseName;
|
||||
|
||||
/**
|
||||
|
|
|
|||
38
src/Jobs/QueuedTenantDatabaseMigrator.php
Normal file
38
src/Jobs/QueuedTenantDatabaseMigrator.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class QueuedTenantDatabaseMigrator implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/** @var Tenant */
|
||||
protected $tenant;
|
||||
|
||||
public function __construct(Tenant $tenant)
|
||||
{
|
||||
$this->tenant = $tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Artisan::call('tenants:migrate', [
|
||||
'--tenants' => [$this->tenant->id],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,14 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
|
||||
class InitializeTenancy
|
||||
{
|
||||
public function __construct(Closure $onFail = null)
|
||||
/** @var callable */
|
||||
protected $onFail;
|
||||
|
||||
public function __construct(callable $onFail = null)
|
||||
{
|
||||
$this->onFail = $onFail ?? function ($e) {
|
||||
throw $e;
|
||||
|
|
@ -25,8 +29,8 @@ class InitializeTenancy
|
|||
public function handle($request, Closure $next)
|
||||
{
|
||||
try {
|
||||
tenancy()->init();
|
||||
} catch (\Exception $e) {
|
||||
tenancy()->init($request->getHost());
|
||||
} catch (TenantCouldNotBeIdentifiedException $e) {
|
||||
($this->onFail)($e);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ namespace Stancl\Tenancy\Middleware;
|
|||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Prevent access to non-tenant routes from domains that are not exempt from tenancy.
|
||||
* = allow access to central routes only from routes listed in tenancy.exempt_routes.
|
||||
*/
|
||||
class PreventAccessFromTenantDomains
|
||||
{
|
||||
/**
|
||||
|
|
@ -19,8 +23,16 @@ class PreventAccessFromTenantDomains
|
|||
{
|
||||
// If the domain is not in exempt domains, it's a tenant domain.
|
||||
// Tenant domains can't have routes without tenancy middleware.
|
||||
if (! \in_array(request()->getHost(), config('tenancy.exempt_domains')) &&
|
||||
! \in_array('tenancy', request()->route()->middleware())) {
|
||||
$isExemptDomain = in_array($request->getHost(), config('tenancy.exempt_domains'));
|
||||
$isTenantDomain = ! $isExemptDomain;
|
||||
|
||||
$isTenantRoute = in_array('tenancy', $request->route()->middleware());
|
||||
|
||||
if ($isTenantDomain && ! $isTenantRoute) { // accessing web routes from tenant domains
|
||||
return redirect(config('tenancy.home_url'));
|
||||
}
|
||||
|
||||
if ($isExemptDomain && $isTenantRoute) { // accessing tenant routes on web domains
|
||||
abort(404);
|
||||
}
|
||||
|
||||
|
|
|
|||
13
src/StorageDrivers/Database/CentralConnection.php
Normal file
13
src/StorageDrivers/Database/CentralConnection.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\StorageDrivers\Database;
|
||||
|
||||
trait CentralConnection
|
||||
{
|
||||
public function getConnectionName()
|
||||
{
|
||||
return DatabaseStorageDriver::getCentralConnectionName();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ namespace Stancl\Tenancy\StorageDrivers\Database;
|
|||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
||||
use Stancl\Tenancy\Exceptions\DomainOccupiedByOtherTenantException;
|
||||
use Stancl\Tenancy\DatabaseManager;
|
||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
|
||||
use Stancl\Tenancy\StorageDrivers\Database\DomainModel as Domains;
|
||||
|
|
@ -16,17 +17,34 @@ use Stancl\Tenancy\Tenant;
|
|||
|
||||
class DatabaseStorageDriver implements StorageDriver
|
||||
{
|
||||
// todo2 write tests verifying that data is decoded and added to the array
|
||||
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
||||
/** @var \Illuminate\Database\Connection */
|
||||
protected $centralDatabase;
|
||||
|
||||
/** @var Tenant The default tenant. */
|
||||
protected $tenant;
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->centralDatabase = $this->getCentralConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the central database connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
public static function getCentralConnection(): \Illuminate\Database\Connection
|
||||
{
|
||||
return DB::connection(static::getCentralConnectionName());
|
||||
}
|
||||
|
||||
public static function getCentralConnectionName(): string
|
||||
{
|
||||
return config('tenancy.storage_drivers.db.connection') ?? app(DatabaseManager::class)->originalDefaultConnectionName;
|
||||
}
|
||||
|
||||
public function findByDomain(string $domain): Tenant
|
||||
|
|
@ -54,13 +72,12 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
|
||||
public function ensureTenantCanBeCreated(Tenant $tenant): void
|
||||
{
|
||||
// todo2 test this
|
||||
if (Tenants::find($tenant->id)) {
|
||||
throw new TenantWithThisIdAlreadyExistsException($tenant->id);
|
||||
}
|
||||
|
||||
if (Domains::whereIn('domain', $tenant->domains)->exists()) {
|
||||
throw new DomainOccupiedByOtherTenantException();
|
||||
throw new DomainsOccupiedByOtherTenantException;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,32 +95,45 @@ class DatabaseStorageDriver implements StorageDriver
|
|||
|
||||
public function createTenant(Tenant $tenant): void
|
||||
{
|
||||
DB::transaction(function () use ($tenant) {
|
||||
Tenants::create(['id' => $tenant->id, 'data' => '{}'])->toArray();
|
||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||
Tenants::create(['id' => $tenant->id, 'data' => json_encode($tenant->data)])->toArray();
|
||||
|
||||
$domainData = [];
|
||||
foreach ($tenant->domains as $domain) {
|
||||
$domainData[] = ['domain' => $domain, 'tenant_id' => $tenant->id];
|
||||
}
|
||||
Domains::create($domainData);
|
||||
|
||||
Domains::insert($domainData);
|
||||
});
|
||||
}
|
||||
|
||||
public function updateTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||
Tenants::find($tenant->id)->putMany($tenant->data);
|
||||
Domains::firstOrCreate(array_map(function ($domain) use ($tenant) {
|
||||
return [
|
||||
|
||||
$original_domains = Domains::where('tenant_id', $tenant->id)->get()->map(function ($model) {
|
||||
return $model->domain;
|
||||
})->toArray();
|
||||
$deleted_domains = array_diff($original_domains, $tenant->domains);
|
||||
|
||||
Domains::whereIn('domain', $deleted_domains)->delete();
|
||||
|
||||
foreach ($tenant->domains as $domain) {
|
||||
Domains::firstOrCreate([
|
||||
'tenant_id' => $tenant->id,
|
||||
'domain' => $domain,
|
||||
];
|
||||
}, $tenant->domains));
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->centralDatabase->transaction(function () use ($tenant) {
|
||||
Tenants::find($tenant->id)->delete();
|
||||
Domains::where('tenant_id', $tenant->id)->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,14 +11,15 @@ use Illuminate\Database\Eloquent\Model;
|
|||
*/
|
||||
class DomainModel extends Model
|
||||
{
|
||||
use CentralConnection;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $primaryKey = 'id';
|
||||
protected $primaryKey = 'domain';
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
public $table = 'domains';
|
||||
|
||||
public function getConnectionName()
|
||||
public function getTable()
|
||||
{
|
||||
return config('tenancy.storage.db.connection') ?? app(DatabaseManager::class)->originalDefaultConnectionName;
|
||||
return config('tenancy.storage_drivers.db.table_names.DomainModel', 'domains');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,25 +11,26 @@ use Illuminate\Database\Eloquent\Model;
|
|||
*/
|
||||
class TenantModel extends Model
|
||||
{
|
||||
use CentralConnection;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $primaryKey = 'id';
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
public $table = 'tenants';
|
||||
|
||||
public function getTable()
|
||||
{
|
||||
return config('tenancy.storage_drivers.db.table_names.TenantModel', 'tenants');
|
||||
}
|
||||
|
||||
public static function dataColumn()
|
||||
{
|
||||
return config('tenancy.storage.db.data_column', 'data');
|
||||
return config('tenancy.storage_drivers.db.data_column', 'data');
|
||||
}
|
||||
|
||||
public static function customColumns()
|
||||
{
|
||||
return config('tenancy.storage.db.custom_columns', []);
|
||||
}
|
||||
|
||||
public function getConnectionName()
|
||||
{
|
||||
return config('tenancy.storage.db.connection') ?? app(DatabaseManager::class)->originalDefaultConnectionName;
|
||||
return config('tenancy.storage_drivers.db.custom_columns', []);
|
||||
}
|
||||
|
||||
public static function getAllTenants(array $ids)
|
||||
|
|
@ -81,7 +82,7 @@ class TenantModel extends Model
|
|||
|
||||
public function getMany(array $keys): array
|
||||
{
|
||||
return array_reduce($keys, function ($result, $key) { // todo2 performance
|
||||
return array_reduce($keys, function ($result, $key) {
|
||||
$result[$key] = $this->get($key);
|
||||
|
||||
return $result;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ namespace Stancl\Tenancy\StorageDrivers;
|
|||
use Illuminate\Contracts\Redis\Factory as Redis;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
// todo2 transactions instead of pipelines?
|
||||
class RedisStorageDriver implements StorageDriver
|
||||
{
|
||||
// todo2 json encoding?
|
||||
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
||||
|
|
@ -27,7 +26,7 @@ class RedisStorageDriver implements StorageDriver
|
|||
public function __construct(Application $app, Redis $redis)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->redis = $redis->connection($app['config']['tenancy.redis.connection'] ?? 'tenancy');
|
||||
$this->redis = $redis->connection($app['config']['tenancy.storage_drivers.redis.connection'] ?? 'tenancy');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -49,7 +48,17 @@ class RedisStorageDriver implements StorageDriver
|
|||
|
||||
public function ensureTenantCanBeCreated(Tenant $tenant): void
|
||||
{
|
||||
// todo2
|
||||
// Tenant ID
|
||||
if ($this->redis->exists("tenants:{$tenant->id}")) {
|
||||
throw new TenantWithThisIdAlreadyExistsException($tenant->id);
|
||||
}
|
||||
|
||||
// Domains
|
||||
if ($this->redis->exists(...array_map(function ($domain) {
|
||||
return "domains:$domain";
|
||||
}, $tenant->domains))) {
|
||||
throw new DomainsOccupiedByOtherTenantException;
|
||||
}
|
||||
}
|
||||
|
||||
public function findByDomain(string $domain): Tenant
|
||||
|
|
@ -59,26 +68,12 @@ class RedisStorageDriver implements StorageDriver
|
|||
throw new TenantCouldNotBeIdentifiedException($domain);
|
||||
}
|
||||
|
||||
return $this->find($id);
|
||||
return $this->findById($id);
|
||||
}
|
||||
|
||||
public function findById(string $id): Tenant
|
||||
{
|
||||
$data = $this->redis->hgetall("tenants:$id");
|
||||
$keys = [];
|
||||
$values = [];
|
||||
foreach ($data as $i => $value) {
|
||||
if ($i & 1) { // is odd
|
||||
$values[] = $value;
|
||||
} else {
|
||||
$keys[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$data = array_combine($keys, $values);
|
||||
$domains = []; // todo2
|
||||
|
||||
return Tenant::fromStorage($data)->withDomains($domains);
|
||||
return $this->makeTenant($this->redis->hgetall("tenants:$id"));
|
||||
}
|
||||
|
||||
public function getTenantIdByDomain(string $domain): ?string
|
||||
|
|
@ -88,32 +83,49 @@ class RedisStorageDriver implements StorageDriver
|
|||
|
||||
public function createTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->redis->pipeline(function ($pipe) use ($tenant) {
|
||||
$id = $tenant->id;
|
||||
|
||||
$this->redis->transaction(function ($pipe) use ($tenant) {
|
||||
foreach ($tenant->domains as $domain) {
|
||||
$pipe->hmset("domains:$domain", 'tenant_id', $id);
|
||||
$pipe->hmset("domains:$domain", ['tenant_id' => $tenant->id]);
|
||||
}
|
||||
$pipe->hmset("tenants:$id", 'id', json_encode($id), 'domain', json_encode($domain));
|
||||
|
||||
$data = [];
|
||||
foreach ($tenant->data as $key => $value) {
|
||||
$data[$key] = json_encode($value);
|
||||
}
|
||||
|
||||
$pipe->hmset("tenants:{$tenant->id}", array_merge($data, ['_tenancy_domains' => json_encode($tenant->domains)]));
|
||||
});
|
||||
}
|
||||
|
||||
public function updateTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->redis->pipeline(function ($pipe) use ($tenant) {
|
||||
$pipe->hmset("tenants:{$tenant->id}", $tenant->data);
|
||||
$id = $tenant->id;
|
||||
|
||||
foreach ($tenant->domains as $domain) {
|
||||
$pipe->hmset("domains:$domain", 'tenant_id', $tenant->id);
|
||||
$old_domains = json_decode($this->redis->hget("tenants:$id", '_tenancy_domains'), true);
|
||||
$deleted_domains = array_diff($old_domains, $tenant->domains);
|
||||
$domains = $tenant->domains;
|
||||
|
||||
$data = [];
|
||||
foreach ($tenant->data as $key => $value) {
|
||||
$data[$key] = json_encode($value);
|
||||
}
|
||||
|
||||
// todo2 deleted domains
|
||||
$this->redis->transaction(function ($pipe) use ($id, $data, $deleted_domains, $domains) {
|
||||
foreach ($deleted_domains as $deleted_domain) {
|
||||
$pipe->del("domains:$deleted_domain");
|
||||
}
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
$pipe->hset("domains:$domain", 'tenant_id', $id);
|
||||
}
|
||||
|
||||
$pipe->hmset("tenants:$id", array_merge($data, ['_tenancy_domains' => json_encode($domains)]));
|
||||
});
|
||||
}
|
||||
|
||||
public function deleteTenant(Tenant $tenant): void
|
||||
{
|
||||
$this->redis->pipeline(function ($pipe) use ($tenant) {
|
||||
$this->redis->transaction(function ($pipe) use ($tenant) {
|
||||
foreach ($tenant->domains as $domain) {
|
||||
$pipe->del("domains:$domain");
|
||||
}
|
||||
|
|
@ -122,9 +134,14 @@ class RedisStorageDriver implements StorageDriver
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all tenants.
|
||||
*
|
||||
* @param string[] $ids
|
||||
* @return Tenant[]
|
||||
*/
|
||||
public function all(array $ids = []): array
|
||||
{
|
||||
// todo2 $this->redis->pipeline()
|
||||
$hashes = array_map(function ($hash) {
|
||||
return "tenants:{$hash}";
|
||||
}, $ids);
|
||||
|
|
@ -143,15 +160,38 @@ class RedisStorageDriver implements StorageDriver
|
|||
}
|
||||
|
||||
return array_map(function ($tenant) {
|
||||
return $this->redis->hgetall($tenant);
|
||||
return $this->makeTenant($this->redis->hgetall($tenant));
|
||||
}, $hashes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Tenant instance from low-level array data.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Tenant
|
||||
*/
|
||||
protected function makeTenant(array $data): Tenant
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$data[$key] = json_decode($value, true);
|
||||
}
|
||||
|
||||
$domains = $data['_tenancy_domains'];
|
||||
unset($data['_tenancy_domains']);
|
||||
|
||||
return Tenant::fromStorage($data)->withDomains($domains);
|
||||
}
|
||||
|
||||
public function get(string $key, Tenant $tenant = null)
|
||||
{
|
||||
$tenant = $tenant ?? $this->tenant();
|
||||
|
||||
return json_decode($this->redis->hget("tenants:{$tenant->id}", $key), true);
|
||||
$json_data = $this->redis->hget("tenants:{$tenant->id}", $key);
|
||||
if ($json_data === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
return json_decode($json_data, true);
|
||||
}
|
||||
|
||||
public function getMany(array $keys, Tenant $tenant = null): array
|
||||
|
|
@ -161,7 +201,7 @@ class RedisStorageDriver implements StorageDriver
|
|||
$result = [];
|
||||
$values = $this->redis->hmget("tenants:{$tenant->id}", $keys);
|
||||
foreach ($keys as $i => $key) {
|
||||
$result[$key] = $values[$i];
|
||||
$result[$key] = json_decode($values[$i], true);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
|
|
|||
|
|
@ -36,5 +36,7 @@ class CacheTenancyBootstrapper implements TenancyBootstrapper
|
|||
$this->app->extend('cache', function () {
|
||||
return $this->originalCache;
|
||||
});
|
||||
|
||||
$this->originalCache = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,22 +4,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\TenancyBootstrappers;
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||
use Stancl\Tenancy\DatabaseManager;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class DatabaseTenancyBootstrapper implements TenancyBootstrapper
|
||||
{
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
||||
/** @var DatabaseManager */
|
||||
protected $database;
|
||||
|
||||
public function __construct(Application $app, DatabaseManager $database)
|
||||
public function __construct(DatabaseManager $database)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,37 +9,50 @@ use Illuminate\Support\Facades\Storage;
|
|||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
// todo better solution than tenant_asset?
|
||||
|
||||
class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
||||
{
|
||||
protected $originalPaths = [];
|
||||
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
||||
/** @var array */
|
||||
public $originalPaths = [];
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->originalPaths = [
|
||||
'disks' => [],
|
||||
'path' => $this->app->storagePath(),
|
||||
'storage' => $this->app->storagePath(),
|
||||
'asset_url' => $this->app['config']['app.asset_url'],
|
||||
];
|
||||
|
||||
$this->app['url']->macro('setAssetRoot', function ($root) {
|
||||
$this->assetRoot = $root;
|
||||
|
||||
return $this;
|
||||
});
|
||||
}
|
||||
|
||||
public function start(Tenant $tenant)
|
||||
{
|
||||
// todo2 revisit this
|
||||
$suffix = $this->app['config']['tenancy.filesystem.suffix_base'] . $tenant->id;
|
||||
|
||||
// storage_path()
|
||||
$this->app->useStoragePath($this->originalPaths['path'] . "/{$suffix}");
|
||||
$this->app->useStoragePath($this->originalPaths['storage'] . "/{$suffix}");
|
||||
|
||||
// asset()
|
||||
if ($this->originalPaths['asset_url']) {
|
||||
$this->app['config']['app.asset_url'] = ($this->originalPaths['asset_url'] ?? $this->app['config']['app.url']) . "/$suffix";
|
||||
$this->app['url']->setAssetRoot($this->app['config']['app.asset_url']);
|
||||
} else {
|
||||
$this->app['url']->setAssetRoot($this->app['url']->route('stancl.tenancy.asset', ['path' => '']));
|
||||
}
|
||||
|
||||
// Storage facade
|
||||
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
|
||||
$this->originalPaths['disks'][$disk] = Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||
|
||||
if ($root = \str_replace('%storage_path%', storage_path(), $this->app['config']["tenancy.filesystem.root_override.{$disk}"])) {
|
||||
if ($root = str_replace('%storage_path%', storage_path(), $this->app['config']["tenancy.filesystem.root_override.{$disk}"])) {
|
||||
Storage::disk($disk)->getAdapter()->setPathPrefix($root);
|
||||
} else {
|
||||
$root = $this->app['config']["filesystems.disks.{$disk}.root"];
|
||||
|
|
@ -52,7 +65,11 @@ class FilesystemTenancyBootstrapper implements TenancyBootstrapper
|
|||
public function end()
|
||||
{
|
||||
// storage_path()
|
||||
$this->app->useStoragePath($this->originalPaths['path']);
|
||||
$this->app->useStoragePath($this->originalPaths['storage']);
|
||||
|
||||
// asset()
|
||||
$this->app['config']['app.asset_url'] = $this->originalPaths['asset_url'];
|
||||
$this->app['url']->setAssetRoot($this->app['config']['app.asset_url']);
|
||||
|
||||
// Storage facade
|
||||
foreach ($this->app['config']['tenancy.filesystem.disks'] as $disk) {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\TenancyBootstrappers;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\Testing\Fakes\QueueFake;
|
||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class QueueTenancyBootstrapper implements TenancyBootstrapper
|
||||
{
|
||||
/** @var bool Has tenancy been started. */
|
||||
protected $started = false;
|
||||
public $started = false;
|
||||
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
|
|
@ -20,13 +21,15 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
|||
{
|
||||
$this->app = $app;
|
||||
|
||||
$this->app['queue']->createPayloadUsing([$this, 'createPayload']);
|
||||
$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function ($event) {
|
||||
if (\array_key_exists('tenant_id', $event->job->payload())) {
|
||||
tenancy()->initById($event->job->payload()['tenant_id']);
|
||||
}
|
||||
$bootstrapper = &$this;
|
||||
|
||||
$queue = $this->app['queue'];
|
||||
if (! $queue instanceof QueueFake) {
|
||||
$queue->createPayloadUsing(function () use (&$bootstrapper) {
|
||||
return $bootstrapper->getPayload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function start(Tenant $tenant)
|
||||
{
|
||||
|
|
@ -38,19 +41,18 @@ class QueueTenancyBootstrapper implements TenancyBootstrapper
|
|||
$this->started = false;
|
||||
}
|
||||
|
||||
public function createPayload()
|
||||
public function getPayload()
|
||||
{
|
||||
if (! $this->started) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$id = tenant()->get('id');
|
||||
$id = tenant('id');
|
||||
|
||||
return [
|
||||
'tenant_id' => $id,
|
||||
'tags' => [
|
||||
"tenant:$id",
|
||||
// todo2 domain
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,28 +4,28 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\TenancyBootstrappers;
|
||||
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class RedisTenancyBootstrapper implements TenancyBootstrapper
|
||||
{
|
||||
/** @var string[string] Original prefixes of connections */
|
||||
protected $originalPrefixes = [];
|
||||
/** @var array<string, string> Original prefixes of connections */
|
||||
public $originalPrefixes = [];
|
||||
|
||||
/** @var Application */
|
||||
protected $app;
|
||||
/** @var Repository */
|
||||
protected $config;
|
||||
|
||||
public function __construct(Application $app)
|
||||
public function __construct(Repository $config)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function start(Tenant $tenant)
|
||||
{
|
||||
foreach ($this->prefixedConnections() as $connection) {
|
||||
$prefix = $this->app['config']['tenancy.redis.prefix_base'] . $tenant['id'];
|
||||
$prefix = $this->config['tenancy.redis.prefix_base'] . $tenant['id'];
|
||||
$client = Redis::connection($connection)->client();
|
||||
|
||||
$this->originalPrefixes[$connection] = $client->getOption($client::OPT_PREFIX);
|
||||
|
|
@ -40,10 +40,12 @@ class RedisTenancyBootstrapper implements TenancyBootstrapper
|
|||
|
||||
$client->setOption($client::OPT_PREFIX, $this->originalPrefixes[$connection]);
|
||||
}
|
||||
|
||||
$this->originalPrefixes = [];
|
||||
}
|
||||
|
||||
protected function prefixedConnections()
|
||||
{
|
||||
return config('tenancy.redis.prefixed_connections');
|
||||
return $this->config['tenancy.redis.prefixed_connections'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,53 +7,21 @@ namespace Stancl\Tenancy;
|
|||
use Illuminate\Cache\CacheManager;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Stancl\Tenancy\TenancyBootstrappers\FilesystemTenancyBootstrapper;
|
||||
|
||||
class TenancyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->commands([
|
||||
Commands\Run::class,
|
||||
Commands\Seed::class,
|
||||
Commands\Install::class,
|
||||
Commands\Migrate::class,
|
||||
Commands\Rollback::class,
|
||||
Commands\TenantList::class,
|
||||
]);
|
||||
|
||||
$this->publishes([
|
||||
__DIR__ . '/../assets/config.php' => config_path('tenancy.php'),
|
||||
], 'config');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__ . '/../assets/migrations/' => database_path('migrations'),
|
||||
], 'migrations');
|
||||
|
||||
$this->loadRoutesFrom(__DIR__ . '/routes.php');
|
||||
|
||||
Route::middlewareGroup('tenancy', [
|
||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
||||
]);
|
||||
|
||||
$this->app->register(TenantRouteServiceProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
$this->mergeConfigFrom(__DIR__ . '/../assets/config.php', 'tenancy');
|
||||
|
||||
$this->app->bind(Contracts\StorageDriver::class, function ($app) {
|
||||
return $app->make($app['config']['tenancy.storage_driver']);
|
||||
return $app->make($app['config']['tenancy.storage_drivers'][$app['config']['tenancy.storage_driver']]['driver']);
|
||||
});
|
||||
$this->app->bind(Contracts\UniqueIdentifierGenerator::class, $this->app['config']['tenancy.unique_id_generator']);
|
||||
$this->app->singleton(DatabaseManager::class);
|
||||
|
|
@ -79,5 +47,54 @@ class TenancyServiceProvider extends ServiceProvider
|
|||
$this->app->bind('globalCache', function ($app) {
|
||||
return new CacheManager($app);
|
||||
});
|
||||
|
||||
$this->app->register(TenantRouteServiceProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->commands([
|
||||
Commands\Run::class,
|
||||
Commands\Seed::class,
|
||||
Commands\Install::class,
|
||||
Commands\Migrate::class,
|
||||
Commands\Rollback::class,
|
||||
Commands\TenantList::class,
|
||||
Commands\CreateTenant::class,
|
||||
Commands\MigrateFresh::class,
|
||||
]);
|
||||
|
||||
$this->publishes([
|
||||
__DIR__ . '/../assets/config.php' => config_path('tenancy.php'),
|
||||
], 'config');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__ . '/../assets/migrations/' => database_path('migrations'),
|
||||
], 'migrations');
|
||||
|
||||
$this->loadRoutesFrom(__DIR__ . '/routes.php');
|
||||
|
||||
Route::middlewareGroup('tenancy', [
|
||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
||||
]);
|
||||
|
||||
$this->app->singleton('globalUrl', function ($app) {
|
||||
$instance = clone $app['url'];
|
||||
$instance->setAssetRoot($app[FilesystemTenancyBootstrapper::class]->originalPaths['asset_url']);
|
||||
|
||||
return $instance;
|
||||
});
|
||||
|
||||
// Queue tenancy
|
||||
$this->app['events']->listen(\Illuminate\Queue\Events\JobProcessing::class, function ($event) {
|
||||
if (array_key_exists('tenant_id', $event->job->payload())) {
|
||||
tenancy()->initialize(tenancy()->find($event->job->payload()['tenant_id']));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
src/Tenant.php
130
src/Tenant.php
|
|
@ -6,19 +6,19 @@ namespace Stancl\Tenancy;
|
|||
|
||||
use ArrayAccess;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Traits\ForwardsCalls;
|
||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
||||
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||
use Stancl\Tenancy\Exceptions\TenantStorageException;
|
||||
|
||||
// todo2 write tests for updating the tenant
|
||||
// todo2 addDomain(), removeDomain()
|
||||
|
||||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
*/
|
||||
class Tenant implements ArrayAccess
|
||||
{
|
||||
use Traits\HasArrayAccess;
|
||||
use Traits\HasArrayAccess,
|
||||
ForwardsCalls;
|
||||
|
||||
/**
|
||||
* Tenant data. A "cache" of tenant storage.
|
||||
|
|
@ -51,8 +51,16 @@ class Tenant implements ArrayAccess
|
|||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $persisted = false;
|
||||
public $persisted = false;
|
||||
|
||||
/**
|
||||
* Use new() if you don't want to swap dependencies.
|
||||
*
|
||||
* @param Application $app
|
||||
* @param StorageDriver $storage
|
||||
* @param TenantManager $tenantManager
|
||||
* @param UniqueIdentifierGenerator $idGenerator
|
||||
*/
|
||||
public function __construct(Application $app, StorageDriver $storage, TenantManager $tenantManager, UniqueIdentifierGenerator $idGenerator)
|
||||
{
|
||||
$this->app = $app;
|
||||
|
|
@ -61,6 +69,12 @@ class Tenant implements ArrayAccess
|
|||
$this->idGenerator = $idGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public constructor.
|
||||
*
|
||||
* @param Application $app
|
||||
* @return self
|
||||
*/
|
||||
public static function new(Application $app = null): self
|
||||
{
|
||||
$app = $app ?? app();
|
||||
|
|
@ -73,27 +87,49 @@ class Tenant implements ArrayAccess
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT CALL THIS METHOD FROM USERLAND. Used by storage
|
||||
* drivers to create persisted instances of Tenant.
|
||||
*
|
||||
* @param array $data
|
||||
* @return self
|
||||
*/
|
||||
public static function fromStorage(array $data): self
|
||||
{
|
||||
return static::new()->withData($data)->persisted(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tenant in a single call.
|
||||
*
|
||||
* @param string|string[] $domains
|
||||
* @param array $data
|
||||
* @return self
|
||||
*/
|
||||
public static function create($domains, array $data = []): self
|
||||
{
|
||||
return static::new()->withDomains((array) $domains)->withData($data)->save();
|
||||
}
|
||||
|
||||
protected function persisted($persisted = null)
|
||||
/**
|
||||
* DO NOT CALL THIS METHOD FROM USERLAND UNLESS YOU KNOW WHAT YOU ARE DOING.
|
||||
* Set $persisted.
|
||||
*
|
||||
* @param bool $persisted
|
||||
* @return self
|
||||
*/
|
||||
public function persisted(bool $persisted): self
|
||||
{
|
||||
if (gettype($persisted) === 'boolean') {
|
||||
$this->persisted = $persisted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this model exist in the tenant storage.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPersisted(): bool
|
||||
{
|
||||
return $this->persisted;
|
||||
|
|
@ -127,6 +163,11 @@ class Tenant implements ArrayAccess
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unassign all domains from the tenant.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function clearDomains(): self
|
||||
{
|
||||
$this->domains = [];
|
||||
|
|
@ -134,6 +175,12 @@ class Tenant implements ArrayAccess
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (overwrite) the tenant's domains.
|
||||
*
|
||||
* @param string|string[] $domains
|
||||
* @return self
|
||||
*/
|
||||
public function withDomains($domains): self
|
||||
{
|
||||
$domains = (array) $domains;
|
||||
|
|
@ -143,6 +190,12 @@ class Tenant implements ArrayAccess
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (overwrite) tenant data.
|
||||
*
|
||||
* @param array $data
|
||||
* @return self
|
||||
*/
|
||||
public function withData(array $data): self
|
||||
{
|
||||
$this->data = $data;
|
||||
|
|
@ -150,11 +203,21 @@ class Tenant implements ArrayAccess
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function generateId()
|
||||
{
|
||||
$this->id = $this->idGenerator->generate($this->domains, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the tenant's state to storage.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function save(): self
|
||||
{
|
||||
if (! isset($this->data['id'])) {
|
||||
|
|
@ -188,7 +251,7 @@ class Tenant implements ArrayAccess
|
|||
}
|
||||
|
||||
/**
|
||||
* Unassign all domains from the tenant.
|
||||
* Unassign all domains from the tenant and write to storage.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
|
|
@ -201,12 +264,22 @@ class Tenant implements ArrayAccess
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getDatabaseName()
|
||||
/**
|
||||
* Get the tenant's database's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatabaseName(): string
|
||||
{
|
||||
return $this->data['_tenancy_db_name'] ?? ($this->app['config']['tenancy.database.prefix'] . $this->id . $this->app['config']['tenancy.database.suffix']);
|
||||
}
|
||||
|
||||
public function getConnectionName()
|
||||
/**
|
||||
* Get the tenant's database connection's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectionName(): string
|
||||
{
|
||||
return $this->data['_tenancy_db_connection'] ?? 'tenant';
|
||||
}
|
||||
|
|
@ -243,6 +316,13 @@ class Tenant implements ArrayAccess
|
|||
return $this->data[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value and write to storage.
|
||||
*
|
||||
* @param string|array<string, mixed> $key
|
||||
* @param mixed $value
|
||||
* @return self
|
||||
*/
|
||||
public function put($key, $value = null): self
|
||||
{
|
||||
if ($key === 'id') {
|
||||
|
|
@ -268,6 +348,20 @@ class Tenant implements ArrayAccess
|
|||
return $this->put($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return self
|
||||
*/
|
||||
public function with(string $key, $value): self
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
|
|
@ -278,6 +372,16 @@ class Tenant implements ArrayAccess
|
|||
if ($key === 'id' && isset($this->data['id'])) {
|
||||
throw new TenantStorageException("Tenant ids can't be changed.");
|
||||
}
|
||||
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (Str::startsWith($method, 'with')) {
|
||||
return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
|
||||
}
|
||||
|
||||
static::throwBadMethodCallException($method);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,32 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Database\DatabaseManager as IlluminateDatabaseManager;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
|
||||
class MySQLDatabaseManager implements TenantDatabaseManager
|
||||
{
|
||||
/** @var \Illuminate\Database\Connection */
|
||||
protected $database;
|
||||
|
||||
public function __construct(Repository $config, IlluminateDatabaseManager $databaseManager)
|
||||
{
|
||||
$this->database = $databaseManager->connection($config['tenancy.database_manager_connections.mysql']);
|
||||
}
|
||||
|
||||
public function createDatabase(string $name): bool
|
||||
{
|
||||
return DB::statement("CREATE DATABASE `$name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
return $this->database->statement("CREATE DATABASE `$name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
}
|
||||
|
||||
public function deleteDatabase(string $name): bool
|
||||
{
|
||||
return DB::statement("DROP DATABASE `$name`");
|
||||
return $this->database->statement("DROP DATABASE `$name`");
|
||||
}
|
||||
|
||||
public function databaseExists(string $name): bool
|
||||
{
|
||||
return (bool) DB::select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
|
||||
return (bool) $this->database->select("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '$name'");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,32 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\TenantDatabaseManagers;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Database\DatabaseManager as IlluminateDatabaseManager;
|
||||
use Stancl\Tenancy\Contracts\TenantDatabaseManager;
|
||||
|
||||
class PostgreSQLDatabaseManager implements TenantDatabaseManager
|
||||
{
|
||||
/** @var \Illuminate\Database\Connection */
|
||||
protected $database;
|
||||
|
||||
public function __construct(Repository $config, IlluminateDatabaseManager $databaseManager)
|
||||
{
|
||||
$this->database = $databaseManager->connection($config['tenancy.database_manager_connections.pgsql']);
|
||||
}
|
||||
|
||||
public function createDatabase(string $name): bool
|
||||
{
|
||||
return DB::statement("CREATE DATABASE \"$name\" WITH TEMPLATE=template0");
|
||||
return $this->database->statement("CREATE DATABASE \"$name\" WITH TEMPLATE=template0");
|
||||
}
|
||||
|
||||
public function deleteDatabase(string $name): bool
|
||||
{
|
||||
return DB::statement("DROP DATABASE \"$name\"");
|
||||
return $this->database->statement("DROP DATABASE \"$name\"");
|
||||
}
|
||||
|
||||
public function databaseExists(string $name): bool
|
||||
{
|
||||
return (bool) DB::select("SELECT datname FROM pg_database WHERE datname = '$name'");
|
||||
return (bool) $this->database->select("SELECT datname FROM pg_database WHERE datname = '$name'");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Illuminate\Foundation\Application;
|
|||
use Illuminate\Support\Collection;
|
||||
use Stancl\Tenancy\Contracts\TenantCannotBeCreatedException;
|
||||
use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedException;
|
||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseMigrator;
|
||||
|
||||
/**
|
||||
* @internal Class is subject to breaking changes in minor and patch versions.
|
||||
|
|
@ -50,6 +51,12 @@ class TenantManager
|
|||
$this->bootstrapFeatures();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a new tenant to storage.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return self
|
||||
*/
|
||||
public function createTenant(Tenant $tenant): self
|
||||
{
|
||||
$this->ensureTenantCanBeCreated($tenant);
|
||||
|
|
@ -58,14 +65,24 @@ class TenantManager
|
|||
$this->database->createDatabase($tenant);
|
||||
|
||||
if ($this->shouldMigrateAfterCreation()) {
|
||||
if ($this->shouldQueueMigration()) {
|
||||
QueuedTenantDatabaseMigrator::dispatch($tenant);
|
||||
} else {
|
||||
$this->artisan->call('tenants:migrate', [
|
||||
'--tenants' => [$tenant['id']],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tenant from storage.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return self
|
||||
*/
|
||||
public function deleteTenant(Tenant $tenant): self
|
||||
{
|
||||
$this->storage->deleteTenant($tenant);
|
||||
|
|
@ -77,6 +94,13 @@ class TenantManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for Stancl\Tenancy\Tenant::create.
|
||||
*
|
||||
* @param string|string[] $domains
|
||||
* @param array $data
|
||||
* @return Tenant
|
||||
*/
|
||||
public static function create($domains, array $data = []): Tenant
|
||||
{
|
||||
return Tenant::create($domains, $data);
|
||||
|
|
@ -95,6 +119,12 @@ class TenantManager
|
|||
$this->database->ensureTenantCanBeCreated($tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing tenant in storage.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return self
|
||||
*/
|
||||
public function updateTenant(Tenant $tenant): self
|
||||
{
|
||||
$this->storage->updateTenant($tenant);
|
||||
|
|
@ -102,6 +132,12 @@ class TenantManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find tenant by domain & initialize tenancy.
|
||||
*
|
||||
* @param string|null $domain
|
||||
* @return self
|
||||
*/
|
||||
public function init(string $domain = null): self
|
||||
{
|
||||
$domain = $domain ?? request()->getHost();
|
||||
|
|
@ -110,6 +146,12 @@ class TenantManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find tenant by ID & initialize tenancy.
|
||||
*
|
||||
* @param string $id
|
||||
* @return self
|
||||
*/
|
||||
public function initById(string $id): self
|
||||
{
|
||||
$this->initializeTenancy($this->find($id));
|
||||
|
|
@ -156,6 +198,12 @@ class TenantManager
|
|||
return collect($this->storage->all($only));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tenancy.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return self
|
||||
*/
|
||||
public function initializeTenancy(Tenant $tenant): self
|
||||
{
|
||||
$this->setTenant($tenant);
|
||||
|
|
@ -171,6 +219,12 @@ class TenantManager
|
|||
return $this->initializeTenancy($tenant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute TenancyBootstrappers.
|
||||
*
|
||||
* @param Tenant $tenant
|
||||
* @return self
|
||||
*/
|
||||
public function bootstrapTenancy(Tenant $tenant): self
|
||||
{
|
||||
$prevented = $this->event('bootstrapping');
|
||||
|
|
@ -257,6 +311,11 @@ class TenantManager
|
|||
return $this->app['config']['tenancy.migrate_after_creation'] ?? false;
|
||||
}
|
||||
|
||||
public function shouldQueueMigration(): bool
|
||||
{
|
||||
return $this->app['config']['tenancy.queue_automatic_migration'] ?? false;
|
||||
}
|
||||
|
||||
public function shouldDeleteDatabase(): bool
|
||||
{
|
||||
return $this->app['config']['tenancy.delete_database_after_tenant_deletion'] ?? false;
|
||||
|
|
@ -277,6 +336,19 @@ class TenantManager
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event hook.
|
||||
* @alias eventListener
|
||||
*
|
||||
* @param string $name
|
||||
* @param callable $listener
|
||||
* @return self
|
||||
*/
|
||||
public function hook(string $name, callable $listener): self
|
||||
{
|
||||
return $this->eventListener($name, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute event listeners.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ class TenantRouteServiceProvider extends RouteServiceProvider
|
|||
{
|
||||
public function map()
|
||||
{
|
||||
if (! \in_array(request()->getHost(), $this->app['config']['tenancy.exempt_domains'] ?? [])
|
||||
&& \file_exists(base_path('routes/tenant.php'))) {
|
||||
if (! in_array(request()->getHost(), $this->app['config']['tenancy.exempt_domains'] ?? [])
|
||||
&& file_exists(base_path('routes/tenant.php'))) {
|
||||
Route::middleware(['web', 'tenancy'])
|
||||
->namespace($this->app['config']['tenant_route_namespace'] ?? 'App\Http\Controllers')
|
||||
->group(base_path('routes/tenant.php'));
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ trait HasATenantsOption
|
|||
{
|
||||
protected function getOptions()
|
||||
{
|
||||
return \array_merge([
|
||||
return array_merge([
|
||||
['tenants', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', null],
|
||||
], parent::getOptions());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy;
|
||||
namespace Stancl\Tenancy\UniqueIDGenerators;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Stancl\Tenancy\Contracts\UniqueIdentifierGenerator;
|
||||
|
||||
class UUIDGenerator implements UniqueIdentifierGenerator
|
||||
{
|
||||
public static function generate(array $domains, array $data = []): string
|
||||
{
|
||||
return (string) \Webpatser\Uuid\Uuid::generate(1, $domains[0] ?? '');
|
||||
return Uuid::uuid4()->toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ if (! \function_exists('tenant')) {
|
|||
function tenant($key = null)
|
||||
{
|
||||
if (! is_null($key)) {
|
||||
return app(Tenant::class)->get($key);
|
||||
return optional(app(Tenant::class))->get($key) ?? null;
|
||||
}
|
||||
|
||||
return app(Tenant::class);
|
||||
|
|
@ -30,6 +30,20 @@ if (! \function_exists('tenant')) {
|
|||
if (! \function_exists('tenant_asset')) {
|
||||
function tenant_asset($asset)
|
||||
{
|
||||
return route('stancl.tenancy.asset', ['asset' => $asset]);
|
||||
return route('stancl.tenancy.asset', ['path' => $asset]);
|
||||
}
|
||||
}
|
||||
|
||||
if (! \function_exists('global_asset')) {
|
||||
function global_asset($asset)
|
||||
{
|
||||
return app('globalUrl')->asset($asset);
|
||||
}
|
||||
}
|
||||
|
||||
if (! \function_exists('global_cache')) {
|
||||
function global_cache()
|
||||
{
|
||||
return app('globalCache');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
test
3
test
|
|
@ -1,10 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# for development
|
||||
docker-compose up -d
|
||||
printf "Variant 1\n\n"
|
||||
docker-compose exec test env TENANCY_TEST_STORAGE_DRIVER=db vendor/bin/phpunit --coverage-php coverage/2.cov "$@"
|
||||
printf "Variant 2\n\n"
|
||||
docker-compose exec test env TENANCY_TEST_STORAGE_DRIVER=redis vendor/bin/phpunit --coverage-php coverage/1.cov "$@"
|
||||
docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/
|
||||
|
|
|
|||
|
|
@ -117,190 +117,55 @@ class CommandsTest extends TestCase
|
|||
->expectsOutput('xyz');
|
||||
}
|
||||
|
||||
// todo2 check that multiple tenants can be migrated at once using all database engines
|
||||
|
||||
/** @test */
|
||||
public function install_command_works()
|
||||
{
|
||||
if (! \is_dir($dir = app_path('Http'))) {
|
||||
\mkdir($dir, 0777, true);
|
||||
if (! is_dir($dir = app_path('Http'))) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
if (! \is_dir($dir = base_path('routes'))) {
|
||||
\mkdir($dir, 0777, true);
|
||||
if (! is_dir($dir = base_path('routes'))) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
// todo2 move this to a file
|
||||
\file_put_contents(app_path('Http/Kernel.php'), "<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$middleware = [
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The priority-sorted list of middleware.
|
||||
*
|
||||
* This forces non-global middleware to always be in the given order.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$middlewarePriority = [
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\Authenticate::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\Illuminate\Auth\Middleware\Authorize::class,
|
||||
];
|
||||
}
|
||||
");
|
||||
file_put_contents(app_path('Http/Kernel.php'), file_get_contents(__DIR__ . '/Etc/defaultHttpKernel.stub'));
|
||||
|
||||
$this->artisan('tenancy:install')
|
||||
->expectsQuestion('Do you want to publish the default database migration?', 'yes');
|
||||
->expectsQuestion('Do you want to publish the default database migrations?', 'yes');
|
||||
$this->assertFileExists(base_path('routes/tenant.php'));
|
||||
$this->assertFileExists(base_path('config/tenancy.php'));
|
||||
$this->assertFileExists(database_path('migrations/2019_08_08_000000_create_tenants_table.php'));
|
||||
$this->assertFileExists(database_path('migrations/2019_09_15_000010_create_tenants_table.php'));
|
||||
$this->assertFileExists(database_path('migrations/2019_09_15_000020_create_domains_table.php'));
|
||||
$this->assertDirectoryExists(database_path('migrations/tenant'));
|
||||
$this->assertSame("<?php
|
||||
$this->assertSame(file_get_contents(__DIR__ . '/Etc/modifiedHttpKernel.stub'), file_get_contents(app_path('Http/Kernel.php')));
|
||||
}
|
||||
|
||||
namespace App\Http;
|
||||
/** @test */
|
||||
public function migrate_fresh_command_works()
|
||||
{
|
||||
$this->assertFalse(Schema::hasTable('users'));
|
||||
Artisan::call('tenants:migrate-fresh');
|
||||
$this->assertFalse(Schema::hasTable('users'));
|
||||
tenancy()->init('test.localhost');
|
||||
$this->assertTrue(Schema::hasTable('users'));
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
$this->assertFalse(DB::table('users')->exists());
|
||||
DB::table('users')->insert(['name' => 'xxx', 'password' => bcrypt('password'), 'email' => 'foo@bar.xxx']);
|
||||
$this->assertTrue(DB::table('users')->exists());
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$middleware = [
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
// test that db is wiped
|
||||
Artisan::call('tenants:migrate-fresh');
|
||||
$this->assertFalse(DB::table('users')->exists());
|
||||
}
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$middlewareGroups = [
|
||||
'web' => [
|
||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
/** @test */
|
||||
public function create_command_works()
|
||||
{
|
||||
Artisan::call('tenants:create -d aaa.localhost -d bbb.localhost plan=free email=foo@test.local');
|
||||
$tenant = tenancy()->all()[1]; // a tenant is autocreated prior to this
|
||||
$data = $tenant->data;
|
||||
unset($data['id']);
|
||||
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The priority-sorted list of middleware.
|
||||
*
|
||||
* This forces non-global middleware to always be in the given order.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected \$middlewarePriority = [
|
||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\Authenticate::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\Illuminate\Auth\Middleware\Authorize::class,
|
||||
];
|
||||
}
|
||||
", \file_get_contents(app_path('Http/Kernel.php')));
|
||||
$this->assertSame(['plan' => 'free', 'email' => 'foo@test.local'], $data);
|
||||
$this->assertSame(['aaa.localhost', 'bbb.localhost'], $tenant->domains);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,24 @@ class DatabaseManagerTest extends TestCase
|
|||
/** @test */
|
||||
public function db_name_is_prefixed_with_db_path_when_sqlite_is_used()
|
||||
{
|
||||
// make `tenant` not sqlite so that it has to detect sqlite from fooconn
|
||||
config(['database.connections.tenant.driver' => 'mysql']);
|
||||
config(['database.connections.fooconn.driver' => 'sqlite']);
|
||||
app(DatabaseManager::class)->createTenantConnection('foodb', 'fooconn');
|
||||
|
||||
$this->assertSame(config('database.connections.fooconn.database'), database_path('foodb'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function the_default_db_is_used_when_based_on_is_null()
|
||||
{
|
||||
$this->assertSame('sqlite', config('database.default'));
|
||||
config([
|
||||
'database.connections.sqlite.foo' => 'bar',
|
||||
'tenancy.database.based_on' => null,
|
||||
]);
|
||||
|
||||
tenancy()->init('test.localhost');
|
||||
|
||||
$this->assertSame('tenant', config('database.default'));
|
||||
$this->assertSame('bar', config('database.connections.' . config('database.default') . '.foo'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
80
tests/Etc/defaultHttpKernel.stub
Normal file
80
tests/Etc/defaultHttpKernel.stub
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The priority-sorted list of middleware.
|
||||
*
|
||||
* This forces non-global middleware to always be in the given order.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewarePriority = [
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\Authenticate::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\Illuminate\Auth\Middleware\Authorize::class,
|
||||
];
|
||||
}
|
||||
83
tests/Etc/modifiedHttpKernel.stub
Normal file
83
tests/Etc/modifiedHttpKernel.stub
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The priority-sorted list of middleware.
|
||||
*
|
||||
* This forces non-global middleware to always be in the given order.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewarePriority = [
|
||||
\Stancl\Tenancy\Middleware\PreventAccessFromTenantDomains::class,
|
||||
\Stancl\Tenancy\Middleware\InitializeTenancy::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\Authenticate::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\Illuminate\Auth\Middleware\Authorize::class,
|
||||
];
|
||||
}
|
||||
|
|
@ -4,12 +4,19 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Tenancy;
|
||||
use Tenant;
|
||||
|
||||
class FacadeTest extends TestCase
|
||||
{
|
||||
/** @test */
|
||||
public function tenant_manager_can_be_accessed_using_the_Tenant_facade()
|
||||
public function tenant_manager_can_be_accessed_using_the_Tenancy_facade()
|
||||
{
|
||||
$this->assertSame(tenancy()->getTenant(), Tenancy::getTenant());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_storage_can_be_accessed_using_the_Tenant_facade()
|
||||
{
|
||||
tenant()->put('foo', 'bar');
|
||||
Tenant::put('abc', 'xyz');
|
||||
|
|
@ -17,4 +24,10 @@ class FacadeTest extends TestCase
|
|||
$this->assertSame('bar', Tenant::get('foo'));
|
||||
$this->assertSame('xyz', Tenant::get('abc'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_can_be_created_using_the_Tenant_facade()
|
||||
{
|
||||
$this->assertSame('bar', Tenant::create(['foo.localhost'], ['foo' => 'bar'])->foo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class QueueTest extends TestCase
|
|||
/** @test */
|
||||
public function queues_use_non_tenant_db_connection()
|
||||
{
|
||||
// todo2 finish this test. requires using the db driver
|
||||
// requires using the db driver
|
||||
$this->markTestIncomplete();
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +56,6 @@ class TestJob implements ShouldQueue
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
logger(\json_encode(\DB::table('users')->get()));
|
||||
logger(json_encode(\DB::table('users')->get()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class ReidentificationTest extends TestCase
|
|||
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||
|
||||
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
||||
$correct_path_prefix = \str_replace('%storage_path%', storage_path(), $override);
|
||||
$correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
|
||||
} else {
|
||||
if ($base = $originals[$disk]) {
|
||||
$correct_path_prefix = $base . "/$suffix/";
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ namespace Stancl\Tenancy\Tests;
|
|||
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
// todo2 rename
|
||||
class BootstrapsTenancyTest extends TestCase
|
||||
class TenancyBootstrappersTest extends TestCase
|
||||
{
|
||||
public $autoInitTenancy = false;
|
||||
|
||||
|
|
@ -56,7 +55,7 @@ class BootstrapsTenancyTest extends TestCase
|
|||
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||
|
||||
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
||||
$correct_path_prefix = \str_replace('%storage_path%', storage_path(), $override);
|
||||
$correct_path_prefix = str_replace('%storage_path%', storage_path(), $override);
|
||||
} else {
|
||||
if ($base = $old_storage_facade_roots[$disk]) {
|
||||
$correct_path_prefix = $base . "/$suffix/";
|
||||
|
|
@ -78,4 +77,20 @@ class BootstrapsTenancyTest extends TestCase
|
|||
$expected = [config('tenancy.cache.tag_base') . tenant('id'), 'foo', 'bar'];
|
||||
$this->assertEquals($expected, cache()->tags(['foo', 'bar'])->getTags()->getNames());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function the_default_db_connection_is_used_when_the_config_value_is_null()
|
||||
{
|
||||
$original = config('database.default');
|
||||
tenancy()->create(['foo.localhost']);
|
||||
tenancy()->init('foo.localhost');
|
||||
|
||||
$this->assertSame(null, config("database.connections.$original.foo"));
|
||||
|
||||
config(["database.connections.$original.foo" => 'bar']);
|
||||
tenancy()->create(['bar.localhost']);
|
||||
tenancy()->init('bar.localhost');
|
||||
|
||||
$this->assertSame('bar', config("database.connections.$original.foo"));
|
||||
}
|
||||
}
|
||||
|
|
@ -4,25 +4,68 @@ declare(strict_types=1);
|
|||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class TenantAssetTest extends TestCase
|
||||
{
|
||||
public $autoCreateTenant = false;
|
||||
public $autoInitTenancy = false;
|
||||
|
||||
/** @test */
|
||||
public function asset_can_be_accessed_using_the_url_returned_by_the_tenant_asset_helper()
|
||||
{
|
||||
Tenant::create('localhost');
|
||||
tenancy()->init('localhost');
|
||||
|
||||
$filename = 'testfile' . $this->randomString(10);
|
||||
\Storage::disk('public')->put($filename, 'bar');
|
||||
$path = storage_path("app/public/$filename");
|
||||
|
||||
// response()->file() returns BinaryFileResponse whose content is
|
||||
// inaccessible via getContent, so ->assertSee() can't be used
|
||||
// $this->get(tenant_asset($filename))->assertSuccessful(); // TODO2 COMMENTED ASSERTIONS
|
||||
// $this->assertFileExists($path); // TODO2 COMMENTED ASSERTIONS
|
||||
$this->assertFileExists($path);
|
||||
$response = $this->get(tenant_asset($filename));
|
||||
|
||||
$f = \fopen($path, 'r');
|
||||
$content = \fread($f, \filesize($path));
|
||||
\fclose($f);
|
||||
$response->assertSuccessful();
|
||||
|
||||
// $this->assertSame('bar', $content); // TODO2 COMMENTED ASSERTIONS
|
||||
$this->assertTrue(true); // TODO2 COMMENTED ASSERTIONS
|
||||
$f = fopen($path, 'r');
|
||||
$content = fread($f, filesize($path));
|
||||
fclose($f);
|
||||
|
||||
$this->assertSame('bar', $content);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function asset_helper_returns_a_link_to_TenantAssetController_when_asset_url_is_null()
|
||||
{
|
||||
config(['app.asset_url' => null]);
|
||||
|
||||
Tenant::create('foo.localhost');
|
||||
tenancy()->init('foo.localhost');
|
||||
|
||||
$this->assertSame(route('stancl.tenancy.asset', ['path' => 'foo']), asset('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function asset_helper_returns_a_link_to_an_external_url_when_asset_url_is_not_null()
|
||||
{
|
||||
config(['app.asset_url' => 'https://an-s3-bucket']);
|
||||
|
||||
$tenant = Tenant::create(['foo.localhost']);
|
||||
tenancy()->init('foo.localhost');
|
||||
|
||||
$this->assertSame("https://an-s3-bucket/tenant{$tenant->id}/foo", asset('foo'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function global_asset_helper_returns_the_same_url_regardless_of_tenancy_initialization()
|
||||
{
|
||||
$original = global_asset('foobar');
|
||||
$this->assertSame(asset('foobar'), global_asset('foobar'));
|
||||
|
||||
Tenant::create(['foo.localhost']);
|
||||
tenancy()->init('foo.localhost');
|
||||
|
||||
$this->assertSame($original, global_asset('foobar'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
108
tests/TenantClassTest.php
Normal file
108
tests/TenantClassTest.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Mockery;
|
||||
use Stancl\Tenancy\Contracts\StorageDriver;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
use Tenancy;
|
||||
|
||||
class TenantClassTest extends TestCase
|
||||
{
|
||||
public $autoInitTenancy = false;
|
||||
public $autoCreateTenant = false;
|
||||
|
||||
/** @test */
|
||||
public function data_cache_works_properly()
|
||||
{
|
||||
// $spy = Mockery::spy(config('tenancy.storage_driver'))->makePartial();
|
||||
// $this->instance(StorageDriver::class, $spy);
|
||||
|
||||
$tenant = Tenant::create(['foo.localhost'], ['foo' => 'bar']);
|
||||
$this->assertSame('bar', $tenant->data['foo']);
|
||||
|
||||
$tenant->put('abc', 'xyz');
|
||||
$this->assertSame('xyz', $tenant->data['abc']);
|
||||
|
||||
$tenant->put(['aaa' => 'bbb', 'ccc' => 'ddd']);
|
||||
$this->assertSame('bbb', $tenant->data['aaa']);
|
||||
$this->assertSame('ddd', $tenant->data['ccc']);
|
||||
|
||||
// $spy->shouldNotHaveReceived('get');
|
||||
|
||||
$this->assertSame(null, $tenant->dfuighdfuigfhdui);
|
||||
// $spy->shouldHaveReceived('get')->once();
|
||||
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_can_have_multiple_domains()
|
||||
{
|
||||
$tenant = Tenant::create(['foo.localhost', 'bar.localhost']);
|
||||
$this->assertSame(['foo.localhost', 'bar.localhost'], $tenant->domains);
|
||||
$this->assertSame($tenant->id, Tenancy::findByDomain('foo.localhost')->id);
|
||||
$this->assertSame($tenant->id, Tenancy::findByDomain('bar.localhost')->id);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function updating_a_tenant_works()
|
||||
{
|
||||
$id = 'abc' . $this->randomString();
|
||||
$tenant = Tenant::create(['foo.localhost'], ['id' => $id]);
|
||||
$tenant->foo = 'bar';
|
||||
$tenant->save();
|
||||
$this->assertEquals(['id' => $id, 'foo' => 'bar'], $tenant->data);
|
||||
$this->assertEquals(['id' => $id, 'foo' => 'bar'], tenancy()->find($id)->data);
|
||||
|
||||
$tenant->addDomains('abc.localhost');
|
||||
$tenant->save();
|
||||
$this->assertEqualsCanonicalizing(['foo.localhost', 'abc.localhost'], $tenant->domains);
|
||||
$this->assertEqualsCanonicalizing(['foo.localhost', 'abc.localhost'], tenancy()->find($id)->domains);
|
||||
|
||||
$tenant->removeDomains(['foo.localhost']);
|
||||
$tenant->save();
|
||||
$this->assertEqualsCanonicalizing(['abc.localhost'], $tenant->domains);
|
||||
$this->assertEqualsCanonicalizing(['abc.localhost'], tenancy()->find($id)->domains);
|
||||
|
||||
$tenant->withDomains(['completely.localhost', 'different.localhost', 'domains.localhost']);
|
||||
$tenant->save();
|
||||
$this->assertEqualsCanonicalizing(['completely.localhost', 'different.localhost', 'domains.localhost'], $tenant->domains);
|
||||
$this->assertEqualsCanonicalizing(['completely.localhost', 'different.localhost', 'domains.localhost'], tenancy()->find($id)->domains);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function with_methods_work()
|
||||
{
|
||||
$id = 'foo' . $this->randomString();
|
||||
$tenant = Tenant::new()->withDomains(['foo.localhost'])->with('id', $id);
|
||||
$this->assertSame($id, $tenant->id);
|
||||
|
||||
$id2 = 'bar' . $this->randomString();
|
||||
$tenant2 = Tenant::new()->withDomains(['bar.localhost'])->withId($id2)->withFooBar('xyz');
|
||||
$this->assertSame($id2, $tenant2->data['id']);
|
||||
$this->assertSame('xyz', $tenant2->foo_bar);
|
||||
$this->assertArrayHasKey('foo_bar', $tenant2->data);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function an_exception_is_thrown_when_an_unknown_method_is_called()
|
||||
{
|
||||
$tenant = Tenant::new();
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$tenant->sdjigndfgnjdfgj();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_data_can_be_set_during_creation()
|
||||
{
|
||||
Tenant::new()->withData(['foo' => 'bar'])->save();
|
||||
|
||||
$data = tenancy()->all()->first()->data;
|
||||
unset($data['id']);
|
||||
|
||||
$this->assertSame(['foo' => 'bar'], $data);
|
||||
}
|
||||
}
|
||||
38
tests/TenantConfigTest.php
Normal file
38
tests/TenantConfigTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
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.storage_to_config_map' => [
|
||||
'paypal_api_public' => 'services.paypal.public',
|
||||
'paypal_api_private' => 'services.paypal.private',
|
||||
],
|
||||
'tenancy.features' => ['Stancl\Tenancy\Features\TenantConfig'],
|
||||
]);
|
||||
|
||||
tenancy()->create('foo.localhost', [
|
||||
'paypal_api_public' => 'foo',
|
||||
'paypal_api_private' => 'bar',
|
||||
]);
|
||||
|
||||
tenancy()->init('foo.localhost');
|
||||
$this->assertSame(['public' => 'foo', 'private' => 'bar'], config('services.paypal'));
|
||||
|
||||
tenancy()->end();
|
||||
$this->assertSame([
|
||||
'public' => null,
|
||||
'private' => null,
|
||||
], config('services.paypal'));
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ use Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager;
|
|||
|
||||
class TenantDatabaseManagerTest extends TestCase
|
||||
{
|
||||
public $autoInitTenancy = false;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider database_manager_provider
|
||||
|
|
@ -24,8 +26,6 @@ class TenantDatabaseManagerTest extends TestCase
|
|||
$this->markTestSkipped('As to not bloat your computer with test databases, this test is not run by default.');
|
||||
}
|
||||
|
||||
config()->set('database.default', $driver); // todo the DB creator would not work for MySQL when sqlite is used for the central DB
|
||||
|
||||
$name = 'db' . $this->randomString();
|
||||
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
||||
app($databaseManager)->createDatabase($name);
|
||||
|
|
@ -34,6 +34,20 @@ class TenantDatabaseManagerTest extends TestCase
|
|||
$this->assertFalse(app($databaseManager)->databaseExists($name));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function dbs_can_be_created_when_another_driver_is_used_for_the_central_db()
|
||||
{
|
||||
$this->assertSame('sqlite', config('database.default'));
|
||||
|
||||
$database = 'db' . $this->randomString();
|
||||
app(MySQLDatabaseManager::class)->createDatabase($database);
|
||||
$this->assertTrue(app(MySQLDatabaseManager::class)->databaseExists($database));
|
||||
|
||||
$database = 'db2' . $this->randomString();
|
||||
app(PostgreSQLDatabaseManager::class)->createDatabase($database);
|
||||
$this->assertTrue(app(PostgreSQLDatabaseManager::class)->databaseExists($database));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider database_manager_provider
|
||||
|
|
|
|||
|
|
@ -5,8 +5,13 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Stancl\Tenancy\Exceptions\DomainsOccupiedByOtherTenantException;
|
||||
use Stancl\Tenancy\Exceptions\TenantWithThisIdAlreadyExistsException;
|
||||
use Stancl\Tenancy\Jobs\QueuedTenantDatabaseMigrator;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
use Stancl\Tenancy\TenantManager;
|
||||
|
||||
class TenantManagerTest extends TestCase
|
||||
{
|
||||
|
|
@ -147,7 +152,7 @@ class TenantManagerTest extends TestCase
|
|||
{
|
||||
$tenant1 = Tenant::new()->withDomains(['foo.localhost'])->save();
|
||||
$tenant2 = Tenant::new()->withDomains(['bar.localhost'])->save();
|
||||
$this->assertEquals([$tenant1, $tenant2], tenancy()->all()->toArray());
|
||||
$this->assertEqualsCanonicalizing([$tenant1, $tenant2], tenancy()->all()->toArray());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
@ -187,4 +192,78 @@ class TenantManagerTest extends TestCase
|
|||
$this->expectException(\Stancl\Tenancy\Exceptions\TenantStorageException::class);
|
||||
$tenant2->put('id', 'foo');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function all_returns_a_collection_of_tenant_objects()
|
||||
{
|
||||
Tenant::create('foo.localhost');
|
||||
$this->assertSame('Tenant', class_basename(tenancy()->all()[0]));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function Tenant_is_bound_correctly_to_the_service_container()
|
||||
{
|
||||
$this->assertSame(null, app(Tenant::class));
|
||||
$tenant = Tenant::create(['foo.localhost']);
|
||||
app(TenantManager::class)->initializeTenancy($tenant);
|
||||
$this->assertSame($tenant->id, app(Tenant::class)->id);
|
||||
$this->assertSame(app(Tenant::class), app(TenantManager::class)->getTenant());
|
||||
app(TenantManager::class)->endTenancy();
|
||||
$this->assertSame(app(Tenant::class), app(TenantManager::class)->getTenant());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function id_can_be_supplied_during_creation()
|
||||
{
|
||||
$id = 'abc' . $this->randomString();
|
||||
$this->assertSame($id, Tenant::create(['foo.localhost'], ['id' => $id])->id);
|
||||
$this->assertTrue(tenancy()->all()->contains(function ($tenant) use ($id) {
|
||||
return $tenant->id === $id;
|
||||
}));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function automatic_migrations_work()
|
||||
{
|
||||
$tenant = Tenant::create(['foo.localhost']);
|
||||
tenancy()->initialize($tenant);
|
||||
$this->assertFalse(\Schema::hasTable('users'));
|
||||
|
||||
config(['tenancy.migrate_after_creation' => true]);
|
||||
$tenant2 = Tenant::create(['bar.localhost']);
|
||||
tenancy()->initialize($tenant2);
|
||||
$this->assertTrue(\Schema::hasTable('users'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function ensureTenantCanBeCreated_works()
|
||||
{
|
||||
$id = 'foo' . $this->randomString();
|
||||
Tenant::create(['foo.localhost'], ['id' => $id]);
|
||||
$this->expectException(DomainsOccupiedByOtherTenantException::class);
|
||||
Tenant::create(['foo.localhost']);
|
||||
|
||||
$this->expectException(TenantWithThisIdAlreadyExistsException::class);
|
||||
Tenant::create(['bar.localhost'], ['id' => $id]);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function automigration_can_be_queued()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
config([
|
||||
'tenancy.migrate_after_creation' => true,
|
||||
'tenancy.queue_automatic_migration' => true,
|
||||
]);
|
||||
|
||||
$tenant = Tenant::new()->save();
|
||||
tenancy()->initialize($tenant);
|
||||
|
||||
Queue::assertPushed(QueuedTenantDatabaseMigrator::class);
|
||||
|
||||
$this->assertFalse(\Schema::hasTable('users'));
|
||||
(new QueuedTenantDatabaseMigrator($tenant))->handle();
|
||||
$this->assertTrue(\Schema::hasTable('users'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,20 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Route;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
class TenantRedirectMacroTest extends TestCase
|
||||
{
|
||||
public $autoCreateTenant = false;
|
||||
public $autoInitTenancy = false;
|
||||
|
||||
/** @test */
|
||||
public function tenant_redirect_macro_replaces_only_the_hostname()
|
||||
{
|
||||
config([
|
||||
'tenancy.features' => ['Stancl\Tenancy\Features\TenantRedirect'],
|
||||
]);
|
||||
|
||||
Route::get('/foobar', function () {
|
||||
return 'Foo';
|
||||
})->name('home');
|
||||
|
|
@ -19,6 +27,9 @@ class TenantRedirectMacroTest extends TestCase
|
|||
return redirect()->route('home')->tenant('abcd');
|
||||
});
|
||||
|
||||
Tenant::create('foo.localhost');
|
||||
tenancy()->init('foo.localhost');
|
||||
|
||||
$this->get('/redirect')
|
||||
->assertRedirect('http://abcd/foobar');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ class TenantStorageTest extends TestCase
|
|||
{
|
||||
$abc = Tenant::new()->withDomains(['abc.localhost'])->save();
|
||||
$exists = function () use ($abc) {
|
||||
return tenancy()->all()->reduce(function ($result, $tenant) use ($abc) {
|
||||
return $result ?: $tenant->id === $abc->id;
|
||||
}, false);
|
||||
return tenancy()->all()->contains(function ($tenant) use ($abc) {
|
||||
return $tenant->id === $abc->id;
|
||||
});
|
||||
};
|
||||
|
||||
$this->assertTrue($exists());
|
||||
|
|
@ -95,26 +95,26 @@ class TenantStorageTest extends TestCase
|
|||
public function data_is_stored_with_correct_data_types()
|
||||
{
|
||||
tenant()->put('someBool', false);
|
||||
$this->assertSame('boolean', \gettype(tenant()->get('someBool')));
|
||||
$this->assertSame('boolean', \gettype(tenant()->get(['someBool'])['someBool']));
|
||||
$this->assertSame('boolean', gettype(tenant()->get('someBool')));
|
||||
$this->assertSame('boolean', gettype(tenant()->get(['someBool'])['someBool']));
|
||||
|
||||
tenant()->put('someInt', 5);
|
||||
$this->assertSame('integer', \gettype(tenant()->get('someInt')));
|
||||
$this->assertSame('integer', \gettype(tenant()->get(['someInt'])['someInt']));
|
||||
$this->assertSame('integer', gettype(tenant()->get('someInt')));
|
||||
$this->assertSame('integer', gettype(tenant()->get(['someInt'])['someInt']));
|
||||
|
||||
tenant()->put('someDouble', 11.40);
|
||||
$this->assertSame('double', \gettype(tenant()->get('someDouble')));
|
||||
$this->assertSame('double', \gettype(tenant()->get(['someDouble'])['someDouble']));
|
||||
$this->assertSame('double', gettype(tenant()->get('someDouble')));
|
||||
$this->assertSame('double', gettype(tenant()->get(['someDouble'])['someDouble']));
|
||||
|
||||
tenant()->put('string', 'foo');
|
||||
$this->assertSame('string', \gettype(tenant()->get('string')));
|
||||
$this->assertSame('string', \gettype(tenant()->get(['string'])['string']));
|
||||
$this->assertSame('string', gettype(tenant()->get('string')));
|
||||
$this->assertSame('string', gettype(tenant()->get(['string'])['string']));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function tenant_model_uses_correct_connection()
|
||||
{
|
||||
config(['tenancy.storage.db.connection' => 'foo']);
|
||||
config(['tenancy.storage_drivers.db.connection' => 'foo']);
|
||||
$this->assertSame('foo', (new TenantModel)->getConnectionName());
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ class TenantStorageTest extends TestCase
|
|||
]);
|
||||
config(['database.default' => 'sqlite']); // fix issue caused by loadMigrationsFrom
|
||||
|
||||
config(['tenancy.storage.db.custom_columns' => [
|
||||
config(['tenancy.storage_drivers.db.custom_columns' => [
|
||||
'foo',
|
||||
]]);
|
||||
|
||||
|
|
@ -159,6 +159,6 @@ class TenantStorageTest extends TestCase
|
|||
tenant()->put(['foo' => 'bar', 'abc' => 'xyz']);
|
||||
$this->assertSame(['bar', 'xyz'], tenant()->get(['foo', 'abc']));
|
||||
|
||||
$this->assertSame('bar', \DB::connection('central')->table('tenants')->where('id', tenant('id'))->first()->foo);
|
||||
$this->assertSame('bar', DB::connection('central')->table('tenants')->where('id', tenant('id'))->first()->foo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ declare(strict_types=1);
|
|||
namespace Stancl\Tenancy\Tests;
|
||||
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Stancl\Tenancy\StorageDrivers\Database\DatabaseStorageDriver;
|
||||
use Stancl\Tenancy\StorageDrivers\RedisStorageDriver;
|
||||
use Stancl\Tenancy\Tenant;
|
||||
|
||||
abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||
|
|
@ -27,7 +25,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
Redis::connection('cache')->flushdb();
|
||||
|
||||
$this->loadMigrationsFrom([
|
||||
'--path' => \realpath(__DIR__ . '/../assets/migrations'),
|
||||
'--path' => realpath(__DIR__ . '/../assets/migrations'),
|
||||
'--database' => 'central',
|
||||
]);
|
||||
config(['database.default' => 'sqlite']); // fix issue caused by loadMigrationsFrom
|
||||
|
|
@ -59,11 +57,11 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
*/
|
||||
protected function getEnvironmentSetUp($app)
|
||||
{
|
||||
if (\file_exists(__DIR__ . '/../.env')) {
|
||||
if (file_exists(__DIR__ . '/../.env')) {
|
||||
\Dotenv\Dotenv::create(__DIR__ . '/..')->load();
|
||||
}
|
||||
|
||||
\fclose(\fopen(database_path('central.sqlite'), 'w'));
|
||||
fclose(fopen(database_path('central.sqlite'), 'w'));
|
||||
|
||||
$app['config']->set([
|
||||
'database.redis.cache.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
||||
|
|
@ -99,17 +97,13 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
'database.redis.client' => env('TENANCY_TEST_REDIS_CLIENT', 'phpredis'),
|
||||
'tenancy.redis.prefixed_connections' => ['default'],
|
||||
'tenancy.migrations_directory' => database_path('../migrations'),
|
||||
'tenancy.storage_drivers.db.connection' => 'central',
|
||||
'tenancy.bootstrappers.redis' => \Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper::class,
|
||||
]);
|
||||
|
||||
if (env('TENANCY_TEST_STORAGE_DRIVER', 'redis') === 'redis') {
|
||||
$app['config']->set([
|
||||
'tenancy.storage_driver' => RedisStorageDriver::class,
|
||||
]);
|
||||
} elseif (env('TENANCY_TEST_STORAGE_DRIVER', 'redis') === 'db') {
|
||||
$app['config']->set([
|
||||
'tenancy.storage_driver' => DatabaseStorageDriver::class,
|
||||
]);
|
||||
}
|
||||
$app->singleton(\Stancl\Tenancy\TenancyBootstrappers\RedisTenancyBootstrapper::class);
|
||||
|
||||
$app['config']->set(['tenancy.storage_driver' => env('TENANCY_TEST_STORAGE_DRIVER', 'redis')]);
|
||||
}
|
||||
|
||||
protected function getPackageProviders($app)
|
||||
|
|
@ -152,7 +146,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
|
||||
public function randomString(int $length = 10)
|
||||
{
|
||||
return \substr(\str_shuffle(\str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', (int) (\ceil($length / \strlen($x))))), 1, $length);
|
||||
return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', (int) (ceil($length / strlen($x))))), 1, $length);
|
||||
}
|
||||
|
||||
public function isContainerized()
|
||||
|
|
@ -162,6 +156,6 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
|||
|
||||
public function assertArrayIsSubset($subset, $array, string $message = ''): void
|
||||
{
|
||||
parent::assertTrue(\array_intersect($subset, $array) == $subset, $message);
|
||||
parent::assertTrue(array_intersect($subset, $array) == $subset, $message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue