mirror of
https://github.com/archtechx/tenancy.git
synced 2026-02-05 08:44:04 +00:00
merge 1.x into .
This commit is contained in:
commit
545c2330d4
33 changed files with 214 additions and 103 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,3 +4,6 @@ vendor/
|
||||||
.vscode/
|
.vscode/
|
||||||
psysh
|
psysh
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
phpunit_var_*.xml
|
||||||
|
coverage/
|
||||||
|
clover.xml
|
||||||
|
|
|
||||||
4
.styleci.yml
Normal file
4
.styleci.yml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
preset: laravel
|
||||||
|
disabled:
|
||||||
|
- concat_without_spaces
|
||||||
|
- ternary_operator_spaces
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
env:
|
env:
|
||||||
- LARAVEL_VERSION="5.7.*" TESTBENCH_VERSION="~3.7" REDIS_DRIVER=phpredis
|
|
||||||
- LARAVEL_VERSION="5.8.*" TESTBENCH_VERSION="~3.8" REDIS_DRIVER=phpredis
|
- LARAVEL_VERSION="5.8.*" TESTBENCH_VERSION="~3.8" REDIS_DRIVER=phpredis
|
||||||
|
|
||||||
language: php
|
language: php
|
||||||
|
|
@ -19,10 +18,7 @@ before_script:
|
||||||
- export DB_USERNAME=root DB_PASSWORD="" DB_DATABASE=tenancy CODECOV_TOKEN="24382d15-84e7-4a55-bea4-c4df96a24a9b"
|
- 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'
|
- cat vendor/laravel/framework/src/Illuminate/Foundation/Application.php| grep 'const VERSION'
|
||||||
|
|
||||||
script: docker-compose exec test vendor/bin/phpunit -v --coverage-clover=coverage.xml
|
script: ./test
|
||||||
|
|
||||||
after_script:
|
|
||||||
- docker-compose down
|
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
# Release Notes for 1.x
|
# Release Notes for 1.x
|
||||||
|
|
||||||
|
## [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)
|
## [v1.6.0 (2019-07-30)](https://github.com/stancl/tenancy/compare/v1.5.1...v1.6.0)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
10
Dockerfile
10
Dockerfile
|
|
@ -16,7 +16,15 @@ RUN apt-get update \
|
||||||
&& php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
|
&& php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
|
||||||
&& mkdir /run/php
|
&& mkdir /run/php
|
||||||
|
|
||||||
RUN apt-get install php7.2-redis
|
RUN apt-get install -y php7.2-redis
|
||||||
|
|
||||||
|
RUN apt-get install -y python3
|
||||||
|
|
||||||
|
RUN apt-get install -y php7.2-dev php-pear
|
||||||
|
RUN pecl install xdebug
|
||||||
|
# RUN echo '' > /etc/php/7.2/cli/conf.d/20-xdebug.ini
|
||||||
|
# RUN echo 'zend_extension=/usr/lib/php/20170718/xdebug.so' >> /etc/php/7.2/cli/php.ini
|
||||||
|
RUN echo 'zend_extension=/usr/lib/php/20170718/xdebug.so' > /etc/php/7.2/cli/conf.d/20-xdebug.ini
|
||||||
|
|
||||||
RUN apt-get -y autoremove \
|
RUN apt-get -y autoremove \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
|
|
|
||||||
27
README.md
27
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# [stancl/tenancy](https://stancl.github.io/tenancy/)
|
# [stancl/tenancy](https://stancl.github.io/tenancy/)
|
||||||
|
|
||||||
[](https://laravel.com)
|
[](https://laravel.com)
|
||||||
[](https://packagist.org/packages/stancl/tenancy)
|
[](https://packagist.org/packages/stancl/tenancy)
|
||||||
[](https://travis-ci.com/stancl/tenancy)
|
[](https://travis-ci.com/stancl/tenancy)
|
||||||
[](https://codecov.io/gh/stancl/tenancy)
|
[](https://codecov.io/gh/stancl/tenancy)
|
||||||
|
|
@ -54,12 +54,14 @@ You won't have to change a thing in your application's code.\*
|
||||||
- [`tenants:list`](#-tenants-list-)
|
- [`tenants:list`](#-tenants-list-)
|
||||||
- [`tenants:migrate`, `tenants:rollback`, `tenants:seed`](#-tenants-migrate----tenants-rollback----tenants-seed-)
|
- [`tenants:migrate`, `tenants:rollback`, `tenants:seed`](#-tenants-migrate----tenants-rollback----tenants-seed-)
|
||||||
+ [Tenant migrations](#tenant-migrations)
|
+ [Tenant migrations](#tenant-migrations)
|
||||||
|
* [Testing](#testing)
|
||||||
- [Tips](#tips)
|
- [Tips](#tips)
|
||||||
* [HTTPS certificates](#https-certificates)
|
* [HTTPS certificates](#https-certificates)
|
||||||
+ [1. Use nginx with the lua module](#1-use-nginx-with-the-lua-module)
|
+ [1. Use nginx with the lua module](#1-use-nginx-with-the-lua-module)
|
||||||
+ [2. Add a simple server block for each tenant](#2-add-a-simple-server-block-for-each-tenant)
|
+ [2. Add a simple server block for each tenant](#2-add-a-simple-server-block-for-each-tenant)
|
||||||
+ [Generating certificates](#generating-certificates)
|
+ [Generating certificates](#generating-certificates)
|
||||||
- [Testing](#testing)
|
- [Development](#development)
|
||||||
|
* [Running tests](#running-tests)
|
||||||
+ [With Docker](#with-docker)
|
+ [With Docker](#with-docker)
|
||||||
+ [Without Docker](#without-docker)
|
+ [Without Docker](#without-docker)
|
||||||
|
|
||||||
|
|
@ -76,7 +78,7 @@ If you'd like to be notified about new versions and related stuff, [sign up for
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Laravel 5.7 or 5.8
|
- Laravel 5.8
|
||||||
|
|
||||||
### Installing the package
|
### Installing the package
|
||||||
|
|
||||||
|
|
@ -226,7 +228,7 @@ You can also seed the database in the same way. The only difference is the comma
|
||||||
|
|
||||||
### Starting a session as a tenant
|
### Starting a session as a tenant
|
||||||
|
|
||||||
This runs `TenantManager::bootstrap()` which switches the DB connection, prefixes Redis, changes filesystem root paths, etc.
|
This switches the DB connection, prefixes Redis, changes filesystem root paths and tags cache.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
tenancy()->init();
|
tenancy()->init();
|
||||||
|
|
@ -511,6 +513,17 @@ Database seeding completed successfully.
|
||||||
|
|
||||||
Tenant migrations are located in `database/migrations/tenant`, so you should move your tenant migrations there.
|
Tenant migrations are located in `database/migrations/tenant`, so you should move your tenant migrations there.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To test your multi-tenant application, simply run the following at the beginning of every test:
|
||||||
|
|
||||||
|
```php
|
||||||
|
tenant()->create('test.localhost')
|
||||||
|
tenancy()->init('test.localhost')
|
||||||
|
```
|
||||||
|
|
||||||
|
To do this automatically, you can make this part of your `TestCase::setUp()` method. [Here](https://github.com/stancl/tenancy/blob/13048002ef687c3c85207df1fbf8b09ce89fb430/tests/TestCase.php#L31-L37)'s how this package handles it.
|
||||||
|
|
||||||
# Tips
|
# Tips
|
||||||
|
|
||||||
- If you create a tenant using the interactive console (`artisan tinker`) and use sqlite, you might need to change the database's permissions and/or ownership (`chmod`/`chown`) so that the web application can access it.
|
- If you create a tenant using the interactive console (`artisan tinker`) and use sqlite, you might need to change the database's permissions and/or ownership (`chmod`/`chown`) so that the web application can access it.
|
||||||
|
|
@ -549,11 +562,13 @@ Creating this config dynamically from PHP is not easy, but is probably feasible.
|
||||||
|
|
||||||
However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP.
|
However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP.
|
||||||
|
|
||||||
# Testing
|
# Development
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
### With Docker
|
### With Docker
|
||||||
|
|
||||||
If you have Docker installed, simply run `docker-compose exec test vendor/bin/phpunit -v`. If you need to run the tests multiple times during development, run `./test` to run the tests. This script runs `docker-compose up -d` and phpunit via the `test` container. When you're done testing, run `docker-compose down` to shut down the containers.
|
If you have Docker installed, simply run `./test`. When you're done testing, run `docker-compose down` to shut down the containers.
|
||||||
|
|
||||||
### Without Docker
|
### Without Docker
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,18 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/support": "5.8.*||5.7.*",
|
"illuminate/support": "5.8.*",
|
||||||
"webpatser/laravel-uuid": "^3.0",
|
"webpatser/laravel-uuid": "^3.0",
|
||||||
"predis/predis": "^1.1"
|
"predis/predis": "^1.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"vlucas/phpdotenv": "^2.2||^3.3",
|
"vlucas/phpdotenv": "^3.3",
|
||||||
"psy/psysh": "@stable",
|
"psy/psysh": "@stable",
|
||||||
"laravel/framework": "5.8.*||5.7.*",
|
|
||||||
"orchestra/testbench": "~3.7||~3.8",
|
|
||||||
"league/flysystem-aws-s3-v3": "~1.0",
|
"league/flysystem-aws-s3-v3": "~1.0",
|
||||||
"laravel/telescope": "^2.0"
|
"laravel/telescope": "^2.0",
|
||||||
|
"laravel/framework": "5.8.*",
|
||||||
|
"orchestra/testbench": "~3.8",
|
||||||
|
"phpunit/phpcov": "^6.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|
|
||||||
|
|
@ -30,5 +30,6 @@
|
||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="DB_CONNECTION" value="sqlite"/>
|
<env name="DB_CONNECTION" value="sqlite"/>
|
||||||
<env name="AWS_DEFAULT_REGION" value="us-west-2"/>
|
<env name="AWS_DEFAULT_REGION" value="us-west-2"/>
|
||||||
|
<env name="STANCL_TENANCY_TEST_VARIANT" value="1"/>
|
||||||
</php>
|
</php>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
|
@ -10,7 +10,7 @@ class CacheManager extends BaseCacheManager
|
||||||
{
|
{
|
||||||
$tags = [config('tenancy.cache.tag_base') . tenant('uuid')];
|
$tags = [config('tenancy.cache.tag_base') . tenant('uuid')];
|
||||||
|
|
||||||
if ($method === "tags") {
|
if ($method === 'tags') {
|
||||||
if (\count($parameters) !== 1) {
|
if (\count($parameters) !== 1) {
|
||||||
throw new \Exception("Method tags() takes exactly 1 argument. {count($parameters)} passed.");
|
throw new \Exception("Method tags() takes exactly 1 argument. {count($parameters)} passed.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class TenantList extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->info("Listing all tenants.");
|
$this->info('Listing all tenants.');
|
||||||
tenancy()->all()->each(function ($tenant) {
|
tenancy()->all()->each(function ($tenant) {
|
||||||
$this->line("[Tenant] uuid: {$tenant['uuid']} @ {$tenant['domain']}");
|
$this->line("[Tenant] uuid: {$tenant['uuid']} @ {$tenant['domain']}");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ class DatabaseManager
|
||||||
|
|
||||||
public function getDriver(): ?string
|
public function getDriver(): ?string
|
||||||
{
|
{
|
||||||
return config("database.connections.tenant.driver");
|
return config('database.connections.tenant.driver');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTenantConnection(string $database_name)
|
public function createTenantConnection(string $database_name)
|
||||||
|
|
@ -98,11 +98,11 @@ class DatabaseManager
|
||||||
// Create the `tenancy` database connection.
|
// Create the `tenancy` database connection.
|
||||||
$based_on = config('tenancy.database.based_on') ?: config('database.default');
|
$based_on = config('tenancy.database.based_on') ?: config('database.default');
|
||||||
config()->set([
|
config()->set([
|
||||||
'database.connections.tenant' => config('database.connections.' . $based_on)
|
'database.connections.tenant' => config('database.connections.' . $based_on),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Change DB name
|
// Change DB name
|
||||||
$database_name = $this->getDriver() === "sqlite" ? database_path($database_name) : $database_name;
|
$database_name = $this->getDriver() === 'sqlite' ? database_path($database_name) : $database_name;
|
||||||
config()->set(['database.connections.tenant.database' => $database_name]);
|
config()->set(['database.connections.tenant.database' => $database_name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,22 @@ namespace Stancl\Tenancy\Interfaces;
|
||||||
interface StorageDriver
|
interface StorageDriver
|
||||||
{
|
{
|
||||||
public function identifyTenant(string $domain): array;
|
public function identifyTenant(string $domain): array;
|
||||||
|
|
||||||
public function getAllTenants(array $uuids = []): array;
|
public function getAllTenants(array $uuids = []): array;
|
||||||
|
|
||||||
public function getTenantById(string $uuid, array $fields = []): array;
|
public function getTenantById(string $uuid, array $fields = []): array;
|
||||||
|
|
||||||
public function getTenantIdByDomain(string $domain): ?string;
|
public function getTenantIdByDomain(string $domain): ?string;
|
||||||
|
|
||||||
public function createTenant(string $domain, string $uuid): array;
|
public function createTenant(string $domain, string $uuid): array;
|
||||||
|
|
||||||
public function deleteTenant(string $uuid): bool;
|
public function deleteTenant(string $uuid): bool;
|
||||||
|
|
||||||
public function get(string $uuid, string $key);
|
public function get(string $uuid, string $key);
|
||||||
|
|
||||||
public function getMany(string $uuid, array $keys): array;
|
public function getMany(string $uuid, array $keys): array;
|
||||||
|
|
||||||
public function put(string $uuid, string $key, $value);
|
public function put(string $uuid, string $key, $value);
|
||||||
|
|
||||||
public function putMany(string $uuid, array $values): array;
|
public function putMany(string $uuid, array $values): array;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ interface TenantDatabaseManager
|
||||||
* Create a database.
|
* Create a database.
|
||||||
*
|
*
|
||||||
* @param string $name Name of the database.
|
* @param string $name Name of the database.
|
||||||
* @return boolean
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function createDatabase(string $name): bool;
|
public function createDatabase(string $name): bool;
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ interface TenantDatabaseManager
|
||||||
* Delete a database.
|
* Delete a database.
|
||||||
*
|
*
|
||||||
* @param string $name Name of the database.
|
* @param string $name Name of the database.
|
||||||
* @return boolean
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function deleteDatabase(string $name): bool;
|
public function deleteDatabase(string $name): bool;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ namespace Stancl\Tenancy\Jobs;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Stancl\Tenancy\Interfaces\TenantDatabaseManager;
|
use Stancl\Tenancy\Interfaces\TenantDatabaseManager;
|
||||||
|
|
||||||
class QueuedTenantDatabaseCreator implements ShouldQueue
|
class QueuedTenantDatabaseCreator implements ShouldQueue
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ namespace Stancl\Tenancy\Jobs;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Stancl\Tenancy\Interfaces\TenantDatabaseManager;
|
use Stancl\Tenancy\Interfaces\TenantDatabaseManager;
|
||||||
|
|
||||||
class QueuedTenantDatabaseDeleter implements ShouldQueue
|
class QueuedTenantDatabaseDeleter implements ShouldQueue
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class RedisStorageDriver implements StorageDriver
|
||||||
if (! $id) {
|
if (! $id) {
|
||||||
throw new \Exception("Tenant could not be identified on domain {$domain}");
|
throw new \Exception("Tenant could not be identified on domain {$domain}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getTenantById($id);
|
return $this->getTenantById($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,14 +51,15 @@ class RedisStorageDriver implements StorageDriver
|
||||||
{
|
{
|
||||||
$this->redis->hmset("domains:$domain", 'tenant_id', $uuid);
|
$this->redis->hmset("domains:$domain", 'tenant_id', $uuid);
|
||||||
$this->redis->hmset("tenants:$uuid", 'uuid', json_encode($uuid), 'domain', json_encode($domain));
|
$this->redis->hmset("tenants:$uuid", 'uuid', json_encode($uuid), 'domain', json_encode($domain));
|
||||||
|
|
||||||
return $this->redis->hgetall("tenants:$uuid");
|
return $this->redis->hgetall("tenants:$uuid");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* {@inheritdoc}
|
||||||
*
|
*
|
||||||
* @param string $id
|
* @param string $id
|
||||||
* @return boolean
|
* @return bool
|
||||||
* @todo Make tenant & domain deletion atomic.
|
* @todo Make tenant & domain deletion atomic.
|
||||||
*/
|
*/
|
||||||
public function deleteTenant(string $id): bool
|
public function deleteTenant(string $id): bool
|
||||||
|
|
@ -69,6 +71,7 @@ class RedisStorageDriver implements StorageDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->redis->del("domains:$domain");
|
$this->redis->del("domains:$domain");
|
||||||
|
|
||||||
return (bool) $this->redis->del("tenants:$id");
|
return (bool) $this->redis->del("tenants:$id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,17 +82,21 @@ class RedisStorageDriver implements StorageDriver
|
||||||
}, $uuids);
|
}, $uuids);
|
||||||
|
|
||||||
if (! $hashes) {
|
if (! $hashes) {
|
||||||
// Apparently, the PREFIX is applied to all functions except scan().
|
// Prefix is applied to all functions except scan().
|
||||||
// Therefore, if the `tenancy` Redis connection has a prefix set
|
// This code applies the correct prefix manually.
|
||||||
// (and PhpRedis is used), prepend the prefix to the search.
|
$redis_prefix = config('database.redis.options.prefix');
|
||||||
$redis_prefix = '';
|
|
||||||
if (config('database.redis.client') === 'phpredis') {
|
if (config('database.redis.client') === 'phpredis') {
|
||||||
$redis_prefix = $this->redis->getOption($this->redis->client()::OPT_PREFIX);
|
$redis_prefix = $this->redis->getOption($this->redis->client()::OPT_PREFIX) ?? $redis_prefix;
|
||||||
|
$all_keys = $this->redis->scan(null, $redis_prefix . 'tenants:*');
|
||||||
|
} else {
|
||||||
|
$all_keys = $this->redis->scan(null, 'MATCH', $redis_prefix . 'tenants:*')[1];
|
||||||
}
|
}
|
||||||
$hashes = array_map(function ($hash) use ($redis_prefix) {
|
|
||||||
// Left strip $redis_prefix from $hash
|
$hashes = array_map(function ($key) use ($redis_prefix) {
|
||||||
return substr($hash, strlen($redis_prefix));
|
// Left strip $redis_prefix from $key
|
||||||
}, $this->redis->scan(null, $redis_prefix.'tenants:*'));
|
return substr($key, strlen($redis_prefix));
|
||||||
|
}, $all_keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_map(function ($tenant) {
|
return array_map(function ($tenant) {
|
||||||
|
|
@ -110,12 +117,14 @@ class RedisStorageDriver implements StorageDriver
|
||||||
public function put(string $uuid, string $key, $value)
|
public function put(string $uuid, string $key, $value)
|
||||||
{
|
{
|
||||||
$this->redis->hset("tenants:$uuid", $key, $value);
|
$this->redis->hset("tenants:$uuid", $key, $value);
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function putMany(string $uuid, array $values): array
|
public function putMany(string $uuid, array $values): array
|
||||||
{
|
{
|
||||||
$this->redis->hmset("tenants:$uuid", $values);
|
$this->redis->hmset("tenants:$uuid", $values);
|
||||||
|
|
||||||
return $values;
|
return $values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class TenantManager
|
||||||
{
|
{
|
||||||
$this->setTenant($this->identify($domain));
|
$this->setTenant($this->identify($domain));
|
||||||
$this->bootstrap();
|
$this->bootstrap();
|
||||||
|
|
||||||
return $this->tenant;
|
return $this->tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +58,7 @@ class TenantManager
|
||||||
$domain = $domain ?: $this->currentDomain();
|
$domain = $domain ?: $this->currentDomain();
|
||||||
|
|
||||||
if (! $domain) {
|
if (! $domain) {
|
||||||
throw new \Exception("No domain supplied nor detected.");
|
throw new \Exception('No domain supplied nor detected.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$tenant = $this->storage->identifyTenant($domain);
|
$tenant = $this->storage->identifyTenant($domain);
|
||||||
|
|
@ -98,6 +99,7 @@ class TenantManager
|
||||||
public function getTenantById(string $uuid, $fields = [])
|
public function getTenantById(string $uuid, $fields = [])
|
||||||
{
|
{
|
||||||
$fields = (array) $fields;
|
$fields = (array) $fields;
|
||||||
|
|
||||||
return $this->jsonDecodeArrayValues($this->storage->getTenantById($uuid, $fields));
|
return $this->jsonDecodeArrayValues($this->storage->getTenantById($uuid, $fields));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +167,7 @@ class TenantManager
|
||||||
public function getDatabaseName($tenant = []): string
|
public function getDatabaseName($tenant = []): string
|
||||||
{
|
{
|
||||||
$tenant = $tenant ?: $this->tenant;
|
$tenant = $tenant ?: $this->tenant;
|
||||||
|
|
||||||
return $this->app['config']['tenancy.database.prefix'] . $tenant['uuid'] . $this->app['config']['tenancy.database.suffix'];
|
return $this->app['config']['tenancy.database.prefix'] . $tenant['uuid'] . $this->app['config']['tenancy.database.suffix'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,6 +222,7 @@ class TenantManager
|
||||||
{
|
{
|
||||||
$this->setTenant($this->storage->getTenantById($uuid));
|
$this->setTenant($this->storage->getTenantById($uuid));
|
||||||
$this->bootstrap();
|
$this->bootstrap();
|
||||||
|
|
||||||
return $this->tenant;
|
return $this->tenant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,7 +256,7 @@ class TenantManager
|
||||||
{
|
{
|
||||||
if (\is_null($uuid)) {
|
if (\is_null($uuid)) {
|
||||||
if (! isset($this->tenant['uuid'])) {
|
if (! isset($this->tenant['uuid'])) {
|
||||||
throw new \Exception("No UUID supplied (and no tenant is currently identified).");
|
throw new \Exception('No UUID supplied (and no tenant is currently identified).');
|
||||||
}
|
}
|
||||||
|
|
||||||
$uuid = $this->tenant['uuid'];
|
$uuid = $this->tenant['uuid'];
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ trait BootstrapsTenancy
|
||||||
/**
|
/**
|
||||||
* Was tenancy initialized/bootstrapped?
|
* Was tenancy initialized/bootstrapped?
|
||||||
*
|
*
|
||||||
* @var boolean
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $initialized = false;
|
public $initialized = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ trait HasATenantsOption
|
||||||
protected function getOptions()
|
protected function getOptions()
|
||||||
{
|
{
|
||||||
return array_merge([
|
return array_merge([
|
||||||
['tenants', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', null]
|
['tenants', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, '', null],
|
||||||
], parent::getOptions());
|
], parent::getOptions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
test
29
test
|
|
@ -1,5 +1,26 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env python3
|
||||||
|
from os import system
|
||||||
|
import argparse
|
||||||
|
|
||||||
# for development
|
system('docker-compose up -d')
|
||||||
docker-compose up -d
|
|
||||||
docker-compose exec test vendor/bin/phpunit "$@"
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--variants", default='1,2',
|
||||||
|
help="Comma-separated values. Which test variants should be run.")
|
||||||
|
args, other = parser.parse_known_args()
|
||||||
|
|
||||||
|
variants = args.variants.split(',')
|
||||||
|
|
||||||
|
for variant in variants:
|
||||||
|
filename_base = "phpunit_var_" + variant
|
||||||
|
with open('phpunit.xml', 'r') as inp, open(filename_base + '.xml', 'w') as out:
|
||||||
|
out.write(inp.read().replace('"STANCL_TENANCY_TEST_VARIANT" value="1"',
|
||||||
|
'"STANCL_TENANCY_TEST_VARIANT" value="%s"' % variant))
|
||||||
|
|
||||||
|
print("Test variant: %s\n" % variant)
|
||||||
|
|
||||||
|
system('docker-compose exec test vendor/bin/phpunit --configuration "%s" --coverage-php %s %s'
|
||||||
|
% (filename_base + '.xml', 'coverage/' + filename_base + '.cov', ' '.join(other)))
|
||||||
|
|
||||||
|
# todo delete folder contents first?
|
||||||
|
system("docker-compose exec test vendor/bin/phpcov merge --clover clover.xml coverage/")
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ class BootstrapsTenancyTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function redis_is_prefixed()
|
public function redis_is_prefixed()
|
||||||
{
|
{
|
||||||
|
if (! config('tenancy.redis.tenancy')) {
|
||||||
|
$this->markTestSkipped('Redis tenancy disabled.');
|
||||||
|
}
|
||||||
|
|
||||||
$this->initTenancy();
|
$this->initTenancy();
|
||||||
foreach (config('tenancy.redis.prefixed_connections', ['default']) as $connection) {
|
foreach (config('tenancy.redis.prefixed_connections', ['default']) as $connection) {
|
||||||
$prefix = config('tenancy.redis.prefix_base') . tenant('uuid');
|
$prefix = config('tenancy.redis.prefix_base') . tenant('uuid');
|
||||||
|
|
@ -35,6 +39,7 @@ class BootstrapsTenancyTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function predis_is_supported()
|
public function predis_is_supported()
|
||||||
{
|
{
|
||||||
|
// No setDriver() before that version.
|
||||||
if (app()->version() < 'v5.8.27') {
|
if (app()->version() < 'v5.8.27') {
|
||||||
$this->markTestSkipped();
|
$this->markTestSkipped();
|
||||||
}
|
}
|
||||||
|
|
@ -74,14 +79,14 @@ class BootstrapsTenancyTest extends TestCase
|
||||||
$this->initTenancy();
|
$this->initTenancy();
|
||||||
|
|
||||||
$new_storage_path = storage_path();
|
$new_storage_path = storage_path();
|
||||||
$this->assertEquals($old_storage_path . "/" . config('tenancy.filesystem.suffix_base') . tenant('uuid'), $new_storage_path);
|
$this->assertEquals($old_storage_path . '/' . config('tenancy.filesystem.suffix_base') . tenant('uuid'), $new_storage_path);
|
||||||
|
|
||||||
foreach (config('tenancy.filesystem.disks') as $disk) {
|
foreach (config('tenancy.filesystem.disks') as $disk) {
|
||||||
$suffix = config('tenancy.filesystem.suffix_base') . tenant('uuid');
|
$suffix = config('tenancy.filesystem.suffix_base') . tenant('uuid');
|
||||||
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||||
|
|
||||||
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
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 {
|
} else {
|
||||||
if ($base = $old_storage_facade_roots[$disk]) {
|
if ($base = $old_storage_facade_roots[$disk]) {
|
||||||
$correct_path_prefix = $base . "/$suffix/";
|
$correct_path_prefix = $base . "/$suffix/";
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
use Stancl\Tenancy\Tests\Etc\ExampleSeeder;
|
||||||
|
|
||||||
class CommandsTest extends TestCase
|
class CommandsTest extends TestCase
|
||||||
|
|
@ -47,7 +47,7 @@ class CommandsTest extends TestCase
|
||||||
{
|
{
|
||||||
$tenant = tenant()->create('test.localhost');
|
$tenant = tenant()->create('test.localhost');
|
||||||
Artisan::call('tenants:migrate', [
|
Artisan::call('tenants:migrate', [
|
||||||
'--tenants' => [$tenant['uuid']]
|
'--tenants' => [$tenant['uuid']],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertFalse(Schema::hasTable('users'));
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class DataSeparationTest extends TestCase
|
class DataSeparationTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +19,7 @@ class DataSeparationTest extends TestCase
|
||||||
$tenant1 = tenancy()->create('tenant1.localhost');
|
$tenant1 = tenancy()->create('tenant1.localhost');
|
||||||
$tenant2 = tenancy()->create('tenant2.localhost');
|
$tenant2 = tenancy()->create('tenant2.localhost');
|
||||||
\Artisan::call('tenants:migrate', [
|
\Artisan::call('tenants:migrate', [
|
||||||
'--tenants' => [$tenant1['uuid'], $tenant2['uuid']]
|
'--tenants' => [$tenant1['uuid'], $tenant2['uuid']],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
tenancy()->init('tenant1.localhost');
|
tenancy()->init('tenant1.localhost');
|
||||||
|
|
@ -52,7 +52,7 @@ class DataSeparationTest extends TestCase
|
||||||
|
|
||||||
$tenant3 = tenancy()->create('tenant3.localhost');
|
$tenant3 = tenancy()->create('tenant3.localhost');
|
||||||
\Artisan::call('tenants:migrate', [
|
\Artisan::call('tenants:migrate', [
|
||||||
'--tenants' => [$tenant1['uuid'], $tenant3['uuid']]
|
'--tenants' => [$tenant1['uuid'], $tenant3['uuid']],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
tenancy()->init('tenant3.localhost');
|
tenancy()->init('tenant3.localhost');
|
||||||
|
|
@ -66,6 +66,10 @@ class DataSeparationTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function redis_is_separated()
|
public function redis_is_separated()
|
||||||
{
|
{
|
||||||
|
if (! config('tenancy.redis.tenancy')) {
|
||||||
|
$this->markTestSkipped('Redis tenancy disabled.');
|
||||||
|
}
|
||||||
|
|
||||||
tenancy()->create('tenant1.localhost');
|
tenancy()->create('tenant1.localhost');
|
||||||
tenancy()->create('tenant2.localhost');
|
tenancy()->create('tenant2.localhost');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class ReidentificationTest extends TestCase
|
||||||
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
$current_path_prefix = \Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||||
|
|
||||||
if ($override = config("tenancy.filesystem.root_override.{$disk}")) {
|
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 {
|
} else {
|
||||||
if ($base = $originals[$disk]) {
|
if ($base = $originals[$disk]) {
|
||||||
$correct_path_prefix = $base . "/$suffix/";
|
$correct_path_prefix = $base . "/$suffix/";
|
||||||
|
|
@ -49,7 +49,6 @@ class ReidentificationTest extends TestCase
|
||||||
tenant()->create('second.localhost');
|
tenant()->create('second.localhost');
|
||||||
tenancy()->init('second.localhost');
|
tenancy()->init('second.localhost');
|
||||||
|
|
||||||
|
|
||||||
$suffix = config('tenancy.filesystem.suffix_base') . tenant('uuid');
|
$suffix = config('tenancy.filesystem.suffix_base') . tenant('uuid');
|
||||||
$this->assertSame($original . "/$suffix", storage_path());
|
$this->assertSame($original . "/$suffix", storage_path());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -186,4 +186,12 @@ class TenantManagerTest extends TestCase
|
||||||
$tenant = tenant()->create('foo.localhost');
|
$tenant = tenant()->create('foo.localhost');
|
||||||
$this->assertSame([$tenant], tenancy()->all()->toArray());
|
$this->assertSame([$tenant], tenancy()->all()->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function all_returns_a_list_of_all_tenants()
|
||||||
|
{
|
||||||
|
$tenant1 = tenant()->create('foo.localhost');
|
||||||
|
$tenant2 = tenant()->create('bar.localhost');
|
||||||
|
$this->assertEqualsCanonicalizing([$tenant1, $tenant2], tenant()->all()->toArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests;
|
||||||
|
|
||||||
use Stancl\Tenancy\Interfaces\StorageDriver;
|
|
||||||
|
|
||||||
class TenantStorageTest extends TestCase
|
class TenantStorageTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @test */
|
/** @test */
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,15 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
public $autoCreateTenant = true;
|
public $autoCreateTenant = true;
|
||||||
public $autoInitTenancy = true;
|
public $autoInitTenancy = true;
|
||||||
|
|
||||||
|
private function checkRequirements(): void
|
||||||
|
{
|
||||||
|
parent::checkRequirements();
|
||||||
|
|
||||||
|
dd($this->getAnnotations());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the test environment
|
* Setup the test environment.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
|
@ -49,13 +56,14 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
protected function getEnvironmentSetUp($app)
|
protected function getEnvironmentSetUp($app)
|
||||||
{
|
{
|
||||||
if (file_exists(__DIR__ . '/../.env')) {
|
if (file_exists(__DIR__ . '/../.env')) {
|
||||||
$this->loadDotEnv();
|
\Dotenv\Dotenv::create(__DIR__ . '/..')->load();
|
||||||
}
|
}
|
||||||
|
|
||||||
$app['config']->set([
|
$app['config']->set([
|
||||||
'database.redis.client' => 'phpredis',
|
'database.redis.client' => 'phpredis',
|
||||||
'database.redis.cache.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
'database.redis.cache.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
||||||
'database.redis.default.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
'database.redis.default.host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
||||||
|
'database.redis.options.prefix' => 'foo',
|
||||||
'database.redis.tenancy' => [
|
'database.redis.tenancy' => [
|
||||||
'host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
'host' => env('TENANCY_TEST_REDIS_HOST', '127.0.0.1'),
|
||||||
'password' => env('TENANCY_TEST_REDIS_PASSWORD', null),
|
'password' => env('TENANCY_TEST_REDIS_PASSWORD', null),
|
||||||
|
|
@ -63,6 +71,7 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
// Use the #14 Redis database unless specified otherwise.
|
// Use the #14 Redis database unless specified otherwise.
|
||||||
// Make sure you don't store anything in this db!
|
// Make sure you don't store anything in this db!
|
||||||
'database' => env('TENANCY_TEST_REDIS_DB', 14),
|
'database' => env('TENANCY_TEST_REDIS_DB', 14),
|
||||||
|
'prefix' => 'abc', // todo unrelated to tenancy, but this doesn't seem to have an effect? try to replicate in a fresh laravel installation
|
||||||
],
|
],
|
||||||
'tenancy.database' => [
|
'tenancy.database' => [
|
||||||
'based_on' => 'sqlite',
|
'based_on' => 'sqlite',
|
||||||
|
|
@ -81,14 +90,19 @@ abstract class TestCase extends \Orchestra\Testbench\TestCase
|
||||||
'tenancy.redis.prefixed_connections' => ['default'],
|
'tenancy.redis.prefixed_connections' => ['default'],
|
||||||
'tenancy.migrations_directory' => database_path('../migrations'),
|
'tenancy.migrations_directory' => database_path('../migrations'),
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
protected function loadDotEnv()
|
switch ((string) env('STANCL_TENANCY_TEST_VARIANT', '1')) {
|
||||||
{
|
case '2':
|
||||||
if (app()::VERSION > '5.8.0') {
|
$app['config']->set([
|
||||||
\Dotenv\Dotenv::create(__DIR__ . '/..')->load();
|
'tenancy.redis.tenancy' => false,
|
||||||
} else {
|
'database.redis.client' => 'predis',
|
||||||
(new \Dotenv\Dotenv(__DIR__ . '/..'))->load();
|
]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$app['config']->set([
|
||||||
|
'tenancy.redis.tenancy' => true,
|
||||||
|
'database.redis.client' => 'phpredis',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue