Skip to content

Commit

Permalink
Merge pull request #35 from HipsterJazzbo/v2.0-wip
Browse files Browse the repository at this point in the history
V2.0 WIP
  • Loading branch information
Caleb Fidecaro authored Sep 27, 2016
2 parents f87e974 + f27cea2 commit 1ffceaa
Show file tree
Hide file tree
Showing 14 changed files with 563 additions and 454 deletions.
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
152 changes: 123 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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,
],
```

Expand All @@ -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
<?php

use Illuminate\Database\Eloquent\Model;
use HipsterJazzbo\Landlord\BelongsToTenant;
use HipsterJazzbo\Landlord\BelongsToTenants;

class ExampleModel extends Model
{
use BelongsToTenant;
use BelongsToTenants;
}
```

Henceforth, all operations against that model will be scoped automatically.
If you’d like to override the tenants that apply to a particular model, you can set the `$tenantColumns` property:

You can also set a `$tenantColumns` property on the model to override the tenants applicable to that model.
```php

use Illuminate\Database\Eloquent\Model;
use HipsterJazzbo\Landlord\BelongsToTenants;

class ExampleModel extends Model
{
use BelongsToTenants;

public $tenantColumns = ['tenant_id'];
}
```

### Creating new Tenant scoped Models

When you create a new instance of a Model which uses `BelongsToTenants`, Landlord will automatically add any applicable Tenant ids, if they are not already set:

```php
$models = Model::all(); // Only the Models with the correct tenant id
// 'tenant_id' will automatically be set by Landlord
$model = ExampleModel::create(['name' => '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.
11 changes: 8 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
"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",
"email": "calebfidecaro@gmail.com"
}
],
"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": {
Expand Down
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
syntaxCheck="false"
>
<testsuites>
<testsuite name="Laravel Multi Tenant Test Suite">
<testsuite name="Landlord Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
Expand Down
70 changes: 0 additions & 70 deletions src/BelongsToTenant.php

This file was deleted.

Loading

0 comments on commit 1ffceaa

Please sign in to comment.