diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f4fdf91 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Change log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +## [v2.0] - 2016-09-13 +### Added +- Landlord now supports Lumen (5.2+) out of the box. +- Landlord now uses Laravel's anonymous global scopes, so you can disable scoping for one or more individual tenants for a single query using `Model::withoutGlobalScope('tenant_column')`, or `Model::withoutGlobalScopes(['tenant_column_a', 'tenant_column_b'])`. + + **Note:** `Model::allTenants()` still returns a query with *none* of the tenant scopes applied. + +- You can now pass a Model instance to `addTenant()`. Landlord will use Eloquent's `getForeignKey()` method as the tenant column name. + +### Changed +- Renamed `LandlordFacade` → `Landlord`. +- Renamed `BelongsToTenant` → `BelongsToTenants` (plural). + + **Note:** You will have to update your use statements in scoped models. + +- Renamed `TenantModelNotFoundException` → `ModelNotFoundForTenantException`. Make sure to update any `catch` statements. +- Renamed `Landlord` → `TenantManager`. + + **Note:** You will have to update any places you're injecting an instance: + +```php +//Before +public function __construct(\HipsterJazzbo\Landlord\Landlord $landlord) { + $this->landlord = $landlord; +} + +// After +public function __construct(\HipsterJazzbo\Landlord\TenantManager $landlord) { + $this->landlord = $landlord; +} +``` + +- `TenantManager` now uses an `\Illuminate\Support\Collection` instance to manage tenants internally. This has cleaned up the code a lot. + + **Note** `getTenants()` now returns the `Collection` instance instead of an array. If you need a plain array of tenants, you may call `Landlord::getTenants()->all()`. +- The service provider no longer registers the `Landlord` facade for you. You'll need to do it in your `config/app.php` if you weren't already. +- Landlord now actually checks for non-tenanted existence before throwing a `ModelNotFoundForTenantException`. \ No newline at end of file diff --git a/README.md b/README.md index e2f64b6..0ecfdd0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ -# Landlord for Laravel 5.2 +# Landlord for Laravel & Lumen 5.2+ -![Landlord for Laravel 5.2](readme-header.jpg) +![Landlord for Laravel & Lumen 5.2+](readme-header.jpg) -![Build Status](https://travis-ci.org/HipsterJazzbo/Landlord.svg?branch=master) +![StyleCI Status](https://styleci.io/repos/49851417/shield?branch=v2.0-wip) +![Build Status](https://travis-ci.org/HipsterJazzbo/Landlord.svg?branch=v2.0-wip) -A single database multi-tenancy package for Laravel 5.2+. (Formerly https://github.com/AuraEQ/laravel-multi-tenant) +A single database multi-tenancy package for Laravel & Lumen 5.2+. + +> **Upgrading from Landlord v1?** Make sure to read the [change log](CHANGELOG.md) to see what needs updating. ## Installation @@ -14,19 +17,23 @@ To get started, require this package: composer require hipsterjazzbo/landlord ``` -Add the ServiceProvider and Alias to their relative arrays in `config/app.php`: +### Laravel + +Add the ServiceProvider in `config/app.php`: ```php 'providers' => [ ... HipsterJazzbo\Landlord\LandlordServiceProvider::class, ], +``` -... +Register the Facade if you’d like: +```php 'aliases' => [ ... - 'Landlord' => HipsterJazzbo\Landlord\Facades\LandlordFacade::class, + 'Landlord' => HipsterJazzbo\Landlord\Facades\Landlord::class, ], ``` @@ -36,58 +43,145 @@ You could also publish the config file: php artisan vendor:publish --provider="HipsterJazzbo\Landlord\LandlordServiceProvider" ``` -and set up your `tenant_column` setting, if you have an app-wide default. +and set your `default_tenant_columns` setting, if you have an app-wide default. LandLord will use this setting to scope models that don’t have a `$tenantColumns` property set. + +### Lumen + +You'll need to set the service provider in your `bootstrap/app.php`: + +```php +$app->register(HipsterJazzbo\Landlord\LandlordServiceProvider::class); +``` + +And make sure you've un-commented `$app->withEloquent()`. ## Usage -First off, this package assumes that you have at least one column on all of your tenant-scoped tables that references which tenant each row belongs to. +This package assumes that you have at least one column on all of your Tenant scoped tables that references which tenant each row belongs to. + +For example, you might have a `companies` table, and a bunch of other tables that have a `company_id` column. + +### Adding and Removing Tenants + +> **IMPORTANT NOTE:** Landlord is stateless. This means that when you call `addTenant()`, it will only scope the *current request*. +> +> Make sure that you are adding your tenants in such a way that it happens on every request, and before you need Models scoped, like in a middleware or as part of a stateless authentication method like OAuth. + +You can tell Landlord to automatically scope by a given Tenant by calling `addTenant()`, either from the `Landlord` facade, or by injecting an instance of `TenantManager()`. + +You can pass in either a tenant column and id: + +```php +Landlord::addTenant('tenant_id', 1); +``` + +Or an instance of a Tenant model: + +```php +$tenant = Tenant::find(1); + +Landlord::addTenant($tenant); +``` + +If you pass a Model instance, Landlord will use Eloquent’s `getForeignKey()` method to decide the tenant column name. + +You can add as many tenants as you need to, however Landlord will only allow **one** of each type of tenant at a time. + +To remove a tenant and stop scoping by it, simply call `removeTenant()`: + +```php +Landlord::removeTenant('tenant_id'); + +// Or you can again pass a Model instance: +$tenant = Tenant::find(1); + +Landlord::removeTenant($tenant); +``` -For example, you might have a `companies` table, and all your other tables might have a `company_id` column (with a foreign key, right?). +You can also check whether Landlord currently is scoping by a given tenant: + +```php +// As you would expect by now, $tenant can be either a string column name or a Model instance +Landlord::hasTenant($tenant); +``` -Next, you'll have to call `Landlord::addTenant($tenantColumn, $tenantId)`. It doesn't matter where, **as long as it happens on every request**. This is important; if you only set the tenant in your login method for example, that won't run for subsequent requests and queries will no longer be scoped. You almost certainly will want to do this in a middleware. +And if for some reason you need to, you can retrieve Landlord's tenants: -Some examples of good places to call `Landlord::addTenant($tenantColumn, $tenantId)` might be: +```php +// $tenants is a Laravel Collection object, in the format 'tenant_id' => 1 +$tenants = Landlord::getTenants(); +``` -- In a global Middleware -- In an oauth system, wherever you're checking the token on each request -- In the constructor of a base controller +### Setting up your Models -Once you've got that all worked out, simply `use` the trait in all your models that you'd like to scope by tenant: +To set up a model to be scoped automatically, simply use the `BelongsToTenants` trait: ```php - 'whatever']); +``` + +### Querying Tenant scoped Models -$model = Model::find(1); // Will fail if the Model with `id` 1 belongs to a different tenant +After you've added tenants, all queries against a Model which uses `BelongsToTenant` will be scoped automatically: -$newModel = Model::create(); // Will have the tenant id added automatically +```php +// This will only include Models belonging to the current tenant(s) +ExampleModel::all(); + +// This will fail with a ModelNotFoundForTenantException if it belongs to the wrong tenant +ExampleModel::find(2); ``` -If you need to run queries across all tenants, you can do it easily: +> **Note:** When you are developing a multi tenanted application, it can be confusing sometimes why you keep getting `ModelNotFound` exceptions for rows that DO exist, because they belong to the wrong tenant. +> +> Landlord will catch those exceptions, and re-throw them as `ModelNotFoundForTenantException`, to help you out :) + +If you need to query across all tenants, you can use `allTenants()`: ```php -$allModels = Model::allTenants()->get(); //You can run any fluent query builder methods here, and they will not be scoped by tenant +// Will include results from ALL tenants, just for this query +ExampleModel::allTenants()->get() ``` -When you are developing a multi tenanted application, it can be confusing sometimes why you keep getting `ModelNotFound` exceptions. +Under the hood, Landlord uses Laravel's [anonymous global scopes](https://laravel.com/docs/5.3/eloquent#global-scopes). This means that if you are scoping by multiple tenants simultaneously, and you want to exclude one of the for a single query, you can do so: + +```php +// Will not scope by 'tenant_id', but will continue to scope by any other tenants that have been set +ExampleModel::withoutGlobalScope('tenant_id')->get(); +``` -Landlord will catch those exceptions, and re-throw them as `TenantModelNotFoundException`, to help you out :) ## Contributing -Please! This is not yet a complete solution, but there's no point in all of us re-inventing this wheel over and over. If you find an issue, or have a better way to do something, open an issue or a pull request. +If you find an issue, or have a better way to do something, feel free to open an issue or a pull request. diff --git a/composer.json b/composer.json index ffad674..ff0a3d4 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,12 @@ "name": "hipsterjazzbo/landlord", "description": "A simple, single database multi-tenancy solution for Laravel 5.2+", "license": "MIT", - "keywords": ["tenant", "tenancy", "multitenant", "multitenancy"], + "keywords": [ + "tenant", + "tenancy", + "multitenant", + "multitenancy" + ], "authors": [ { "name": "Caleb Fidecaro", @@ -10,12 +15,12 @@ } ], "require": { - "php": ">=5.4.0", + "php": ">=5.6.0", "illuminate/support": "5.2.*|5.3.*" }, "require-dev": { "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.0", + "phpunit/phpunit": "5.5.*", "laravel/framework": "5.2.*|5.3.*" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1406a16..b94cf2b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@ syntaxCheck="false" > - + ./tests/ diff --git a/src/BelongsToTenant.php b/src/BelongsToTenant.php deleted file mode 100644 index 8afa2eb..0000000 --- a/src/BelongsToTenant.php +++ /dev/null @@ -1,70 +0,0 @@ -creating($model); - }); - } - - /** - * Returns a new builder without the tenant scope applied. - * - * $allUsers = User::allTenants()->get(); - * - * @return \Illuminate\Database\Eloquent\Builder - */ - public static function allTenants() - { - return (new static())->newQueryWithoutScope(Landlord::class); - } - - /** - * Get the name of the "tenant id" column(s). - * - * @return string - */ - public function getTenantColumns() - { - return isset($this->tenantColumns) ? $this->tenantColumns : config('landlord.default_tenant_columns'); - } - - /** - * Override the default findOrFail method so that we can rethrow a more useful exception. - * Otherwise it can be very confusing why queries don't work because of tenant scoping issues. - * - * @param $id - * @param array $columns - * - * @throws TenantModelNotFoundException - */ - public static function findOrFail($id, $columns = ['*']) - { - try { - return parent::query()->findOrFail($id, $columns); - } catch (ModelNotFoundException $e) { - throw (new TenantModelNotFoundException())->setModel(get_called_class()); - } - } -} diff --git a/src/BelongsToTenants.php b/src/BelongsToTenants.php new file mode 100644 index 0000000..c89e911 --- /dev/null +++ b/src/BelongsToTenants.php @@ -0,0 +1,85 @@ +applyTenantScopes(new static()); + + // Add tenantColumns automatically when creating models + static::creating(function (Model $model) { + static::$landlord->newModel($model); + }); + } + + /** + * Get the tenantColumns for this model. + * + * @return array + */ + public function getTenantColumns() + { + return isset($this->tenantColumns) ? $this->tenantColumns : config('landlord.default_tenant_columns'); + } + + /** + * Returns a new query builder without any of the tenant scopes applied. + * + * $allUsers = User::allTenants()->get(); + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function allTenants() + { + return static::$landlord->newQueryWithoutTenants(new static()); + } + + /** + * Override the default findOrFail method so that we can re-throw + * a more useful exception. Otherwise it can be very confusing + * why queries don't work because of tenant scoping issues. + * + * @param mixed $id + * @param array $columns + * + * @throws ModelNotFoundForTenantException + * + * @return \Illuminate\Database\Eloquent\Collection|Model + */ + public static function findOrFail($id, $columns = ['*']) + { + try { + return static::query()->findOrFail($id, $columns); + } catch (ModelNotFoundException $e) { + // If it DOES exist, just not for this tenant, throw a nicer exception + if (!is_null(static::allTenants()->find($id, $columns))) { + throw (new ModelNotFoundForTenantException())->setModel(get_called_class()); + } + + throw $e; + } + } +} diff --git a/src/Exceptions/TenantModelNotFoundException.php b/src/Exceptions/ModelNotFoundForTenantException.php similarity index 78% rename from src/Exceptions/TenantModelNotFoundException.php rename to src/Exceptions/ModelNotFoundForTenantException.php index b02331b..9855326 100644 --- a/src/Exceptions/TenantModelNotFoundException.php +++ b/src/Exceptions/ModelNotFoundForTenantException.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; -class TenantModelNotFoundException extends ModelNotFoundException implements TenantExceptionInterface +class ModelNotFoundForTenantException extends ModelNotFoundException implements TenantExceptionInterface { /** * @param string $model diff --git a/src/Facades/LandlordFacade.php b/src/Facades/Landlord.php similarity index 69% rename from src/Facades/LandlordFacade.php rename to src/Facades/Landlord.php index 5dae864..9daed38 100644 --- a/src/Facades/LandlordFacade.php +++ b/src/Facades/Landlord.php @@ -2,10 +2,10 @@ namespace HipsterJazzbo\Landlord\Facades; -use HipsterJazzbo\Landlord\Landlord; +use HipsterJazzbo\Landlord\TenantManager; use Illuminate\Support\Facades\Facade; -class LandlordFacade extends Facade +class Landlord extends Facade { /** * Get the registered name of the component. @@ -14,6 +14,6 @@ class LandlordFacade extends Facade */ protected static function getFacadeAccessor() { - return Landlord::class; + return TenantManager::class; } } diff --git a/src/Landlord.php b/src/Landlord.php deleted file mode 100644 index 494d63f..0000000 --- a/src/Landlord.php +++ /dev/null @@ -1,175 +0,0 @@ -tenants; - } - - /** - * Add $tenantColumn => $tenantId to the current tenants array. - * - * @param string $tenantColumn - * @param mixed $tenantId - */ - public function addTenant($tenantColumn, $tenantId) - { - $this->enable(); - - $this->tenants[$tenantColumn] = $tenantId; - } - - /** - * Remove $tenantColumn => $id from the current tenants array. - * - * @param string $tenantColumn - * - * @return boolean - */ - public function removeTenant($tenantColumn) - { - if ($this->hasTenant($tenantColumn)) { - unset($this->tenants[$tenantColumn]); - - return true; - } - - return false; - } - - /** - * Test whether current tenants include a given tenant. - * - * @param string $tenantColumn - * - * @return boolean - */ - public function hasTenant($tenantColumn) - { - return isset($this->tenants[$tenantColumn]); - } - - /** - * Apply the scope to a given Eloquent query builder. - * - * @param Builder $builder - * @param Model|\HipsterJazzbo\Landlord\BelongsToTenant $model - */ - public function apply(Builder $builder, Model $model) - { - if (! $this->enabled) { - return; - } - - foreach ($this->getModelTenants($model) as $tenantColumn => $tenantId) { - $builder->where($model->getTable() . '.' . $tenantColumn, '=', $tenantId); - } - } - - /** - * @param Model|\HipsterJazzbo\Landlord\BelongsToTenant $model - */ - public function creating(Model $model) - { - // If the model has had the global scope removed, bail - if (! $model->hasGlobalScope($this)) { - return; - } - - // Otherwise, scope the new model - foreach ($this->getModelTenants($model) as $tenantColumn => $tenantId) { - $model->{$tenantColumn} = $tenantId; - } - } - - /** - * Return which tenantColumn => tenantId are really in use for this model. - * - * @param Model|\HipsterJazzbo\Landlord\BelongsToTenant $model - * - * @throws TenantColumnUnknownException - * - * @return array - */ - public function getModelTenants(Model $model) - { - $modelTenantColumns = (array)$model->getTenantColumns(); - $modelTenants = []; - - foreach ($modelTenantColumns as $tenantColumn) { - if ($this->hasTenant($tenantColumn)) { - $modelTenants[$tenantColumn] = $this->getTenantId($tenantColumn); - } - } - - return $modelTenants; - } - - /** - * Gets the Tenant ID - * - * @param $tenantColumn - * - * @throws TenantColumnUnknownException - * - * @return mixed The id of the tenant - */ - public function getTenantId($tenantColumn) - { - if (! $this->hasTenant($tenantColumn)) { - throw new TenantColumnUnknownException( - get_class($this->model) . ': tenant column "' . $tenantColumn . '" NOT found in tenants scope "' . json_encode($this->tenants) . '"' - ); - } - - return $this->tenants[$tenantColumn]; - } - - /** - * Disables the scoping of tenants - * - * @return void - */ - public function disable() - { - $this->enabled = false; - } - - /** - * Enables the scoping of tenants - * - * @return void - */ - public function enable() - { - $this->enabled = true; - } -} diff --git a/src/LandlordServiceProvider.php b/src/LandlordServiceProvider.php index 919c8c7..6d3c4f7 100644 --- a/src/LandlordServiceProvider.php +++ b/src/LandlordServiceProvider.php @@ -2,9 +2,6 @@ namespace HipsterJazzbo\Landlord; -use HipsterJazzbo\Landlord\Facades\LandlordFacade; -use Illuminate\Contracts\Foundation\Application; -use Illuminate\Foundation\AliasLoader; use Illuminate\Support\ServiceProvider; class LandlordServiceProvider extends ServiceProvider @@ -17,7 +14,7 @@ class LandlordServiceProvider extends ServiceProvider public function boot() { $this->publishes([ - realpath(__DIR__ . '/../config/landlord.php') => config_path('landlord.php') + realpath(__DIR__.'/../config/landlord.php') => config_path('landlord.php'), ]); } @@ -28,15 +25,8 @@ public function boot() */ public function register() { - $this->app->singleton(Landlord::class, function () { - return new Landlord(); - }); - - // Define alias 'Landlord' - $this->app->booting(function () { - $loader = AliasLoader::getInstance(); - - $loader->alias('Landlord', LandlordFacade::class); + $this->app->singleton(TenantManager::class, function () { + return new TenantManager(); }); } } diff --git a/src/TenantManager.php b/src/TenantManager.php new file mode 100644 index 0000000..0a308f3 --- /dev/null +++ b/src/TenantManager.php @@ -0,0 +1,179 @@ +tenants = collect(); + } + + /** + * Enable scoping by tenantColumns. + * + * @return void + */ + public function enable() + { + $this->enabled = true; + } + + /** + * Disable scoping by tenantColumns. + * + * @return void + */ + public function disable() + { + $this->enabled = false; + } + + /** + * Add a tenant to scope by. + * + * @param string|Model $tenant + * @param mixed|null $id + */ + public function addTenant($tenant, $id = null) + { + if (func_num_args() == 1) { + $id = $tenant->getKey(); + } + + $this->tenants->put($this->getTenantKey($tenant), $id); + } + + /** + * Remove a tenant so that queries are no longer scoped by it. + * + * @param string|Model $tenant + */ + public function removeTenant($tenant) + { + $this->tenants->pull($this->getTenantKey($tenant)); + } + + /** + * Whether a tenant is currently being scoped. + * + * @param string|Model $tenant + * + * @return bool + */ + public function hasTenant($tenant) + { + return $this->tenants->has($this->getTenantKey($tenant)); + } + + /** + * @return Collection + */ + public function getTenants() + { + return $this->tenants; + } + + /** + * Applies applicable tenant scopes to a model. + * + * @param Model $model + */ + public function applyTenantScopes(Model $model) + { + if (!$this->enabled) { + return; + } + + $this->modelTenants($model)->each(function ($id, $tenant) use ($model) { + $model->addGlobalScope($tenant, function (Builder $builder) use ($tenant, $id, $model) { + $builder->where($model->getTable().'.'.$tenant, '=', $id); + }); + }); + } + + /** + * Add tenant columns as needed to a new model instance before it is created. + * + * @param Model $model + */ + public function newModel(Model $model) + { + if (!$this->enabled) { + return; + } + + $this->modelTenants($model)->each(function ($tenantId, $tenantColumn) use ($model) { + if (!isset($model->{$tenantColumn})) { + $model->setAttribute($tenantColumn, $tenantId); + } + }); + } + + /** + * Get a new Eloquent Builder instance without any of the tenant scopes applied. + * + * @param Model $model + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutTenants(Model $model) + { + return $model->newQuery()->withoutGlobalScopes($this->tenants->keys()); + } + + /** + * Get the key for a tenant, either form a Model instance or a string. + * + * @param string|Model $tenant + * + * @throws TenantColumnUnknownException + * + * @return string + */ + protected function getTenantKey($tenant) + { + if ($tenant instanceof Model) { + $tenant = $tenant->getForeignKey(); + } + + if (!is_string($tenant)) { + throw new TenantColumnUnknownException( + '$tenant must be a string key or an instance of \Illuminate\Database\Eloquent\Model' + ); + } + + return $tenant; + } + + /** + * Get the tenantColumns that are actually applicable to the given + * model, in case they've been manually specified. + * + * @param Model|BelongsToTenants $model + * + * @return Collection + */ + protected function modelTenants(Model $model) + { + return $this->tenants->only($model->getTenantColumns()); + } +} diff --git a/tests/LandlordTest.php b/tests/LandlordTest.php new file mode 100644 index 0000000..a9b9696 --- /dev/null +++ b/tests/LandlordTest.php @@ -0,0 +1,117 @@ +addTenant('tenant_a_id', 1); + + $this->assertEquals(['tenant_a_id' => 1], $landlord->getTenants()->toArray()); + + $landlord->addTenant('tenant_b_id', 2); + + $this->assertEquals(['tenant_a_id' => 1, 'tenant_b_id' => 2], $landlord->getTenants()->toArray()); + + $landlord->removeTenant('tenant_a_id'); + + $this->assertEquals(['tenant_b_id' => 2], $landlord->getTenants()->toArray()); + + $this->assertTrue($landlord->hasTenant('tenant_b_id')); + + $this->assertFalse($landlord->hasTenant('tenant_a_id')); + } + + public function testTenantsWithModels() + { + Landlord::shouldReceive('applyTenantScopes'); + + $tenantA = new TenantA(); + + $tenantA->id = 1; + + $tenantB = new TenantB(); + + $tenantB->id = 2; + + $landlord = new TenantManager(); + + $landlord->addTenant($tenantA); + + $this->assertEquals(['tenant_a_id' => 1], $landlord->getTenants()->toArray()); + + $landlord->addTenant($tenantB); + + $this->assertEquals(['tenant_a_id' => 1, 'tenant_b_id' => 2], $landlord->getTenants()->toArray()); + + $landlord->removeTenant($tenantA); + + $this->assertEquals(['tenant_b_id' => 2], $landlord->getTenants()->toArray()); + + $this->assertTrue($landlord->hasTenant('tenant_b_id')); + + $this->assertFalse($landlord->hasTenant('tenant_a_id')); + } + + public function testApplyTenantScopes() + { + $landlord = new TenantManager(); + + $landlord->addTenant('tenant_a_id', 1); + + $landlord->addTenant('tenant_b_id', 2); + + Landlord::shouldReceive('applyTenantScopes'); + + $model = new ModelStub(); + + $landlord->applyTenantScopes($model); + + $this->assertArrayHasKey('tenant_a_id', $model->getGlobalScopes()); + + $this->assertArrayNotHasKey('tenant_b_id', $model->getGlobalScopes()); + } + + public function testNewModel() + { + $landlord = new TenantManager(); + + $landlord->addTenant('tenant_a_id', 1); + + $landlord->addTenant('tenant_b_id', 2); + + Landlord::shouldReceive('applyTenantScopes'); + + $model = new ModelStub(); + + $landlord->newModel($model); + + $this->assertEquals(1, $model->tenant_a_id); + + $this->assertNull($model->tenant_b_id); + } +} + +class ModelStub extends Model +{ + use BelongsToTenants; + + public $tenantColumns = ['tenant_a_id']; +} + +class TenantA extends Model +{ + // +} + +class TenantB extends Model +{ + // +} diff --git a/tests/TenantScopeTest.php b/tests/TenantScopeTest.php deleted file mode 100644 index 1031e15..0000000 --- a/tests/TenantScopeTest.php +++ /dev/null @@ -1,106 +0,0 @@ -addTenant('column', 1); - - $tenants = $tenantScope->getTenants(); - $this->assertEquals(['column' => 1], $tenants); - - $this->assertTrue($tenantScope->hasTenant('column')); - - $tenantScope->removeTenant('column'); - - $tenants = $tenantScope->getTenants(); - $this->assertEquals([], $tenants); - } - - public function testApply() - { - $scope = m::mock(\HipsterJazzbo\Landlord\Landlord::class); - $builder = m::mock(\Illuminate\Database\Eloquent\Builder::class); - $model = m::mock(\Illuminate\Database\Eloquent\Model::class); - - $scope->shouldDeferMissing(); - $scope->shouldReceive('getModelTenants')->once()->with($model)->andReturn(['column' => 1]); - - $builder->shouldReceive('getModel')->andReturn($model); - $builder->shouldReceive('where')->once()->with('table.column', '=', '1'); - - $model->shouldReceive('getTable')->andReturn('table'); - - $scope->apply($builder, $model); - } - - public function testCreating() - { - $scope = m::mock(\HipsterJazzbo\Landlord\Landlord::class); - $model = m::mock(\Illuminate\Database\Eloquent\Model::class); - - $scope->shouldDeferMissing(); - $scope->shouldReceive('getModelTenants')->with($model)->andReturn(['column' => 1]); - - $model->shouldDeferMissing(); - $model->shouldReceive('hasGlobalScope')->andReturn(true); - - $scope->creating($model); - - $this->assertEquals(1, $model->column); - } - - public function testGetModelTenants() - { - $scope = m::mock(\HipsterJazzbo\Landlord\Landlord::class); - $model = m::mock(\Illuminate\Database\Eloquent\Model::class); - - $scope->shouldDeferMissing(); - $scope->shouldReceive('getTenantId')->once()->andReturn(1); - $scope->shouldReceive('hasTenant')->once()->andReturn(true); - - $model->shouldReceive('getTenantColumns')->once()->andReturn(['column']); - - $modelTenants = $scope->getModelTenants($model); - - $this->assertEquals(['column' => 1], $modelTenants); - } - - /** - * @expectedException \HipsterJazzbo\Landlord\Exceptions\TenantColumnUnknownException - */ - public function testGetTenantIdThrowsException() - { - $scope = new Landlord(); - - $scope->getTenantId('column'); - } - - public function testDisable() - { - $scope = m::mock(\HipsterJazzbo\Landlord\Landlord::class); - $builder = m::mock(\Illuminate\Database\Eloquent\Builder::class); - $model = m::mock(\Illuminate\Database\Eloquent\Model::class); - - $scope->shouldDeferMissing(); - $scope->shouldReceive('getModelTenants')->with($model)->andReturn(['column' => 1])->never(); - - $builder->shouldReceive('getModel')->andReturn($model)->never(); - $builder->shouldReceive('whereRaw')->with("table.column = '1'")->never(); - - $model->shouldReceive('getTenantWhereClause')->with('column', 1)->andReturn("table.column = '1'")->never(); - - $scope->disable(); - $scope->apply($builder, $model); - } -} diff --git a/tests/TenantScopedModelTraitTest.php b/tests/TenantScopedModelTraitTest.php deleted file mode 100644 index 789d533..0000000 --- a/tests/TenantScopedModelTraitTest.php +++ /dev/null @@ -1,53 +0,0 @@ -