Last active
August 29, 2015 14:23
-
-
Save MartinTale/ec5773442b419044230e to your computer and use it in GitHub Desktop.
Multi Tenancy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// b/app/Tenant/Tenant.php | |
namespace Cms\Tenant; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Database\Eloquent\SoftDeletes; | |
class Tenant extends Model { | |
use SoftDeletes; | |
public $timestamps = false; | |
protected $table = 'tenants'; | |
protected $fillable = ['domain', 'path', 'namespace']; | |
protected $dates = ['deleted_at']; | |
} | |
// b/app/Tenant/TenantScope.php | |
namespace Cms\Tenant; | |
use Illuminate\Database\Query\Builder as BaseBuilder; | |
use Illuminate\Database\Eloquent\Builder; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Database\Eloquent\ScopeInterface; | |
use Session; | |
class TenantScope implements ScopeInterface { | |
/** | |
* Apply scope on the query. | |
* | |
* Storing tenant id in session | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @param \Illuminate\Database\Eloquent\Model $model | |
* @return void | |
*/ | |
public function apply(Builder $builder, Model $model) | |
{ | |
$column = $model->getQualifiedTenantColumn(); | |
$builder->where($column, '=', Session::get('tenant')); | |
$this->addWithAllTenants($builder); | |
} | |
/** | |
* Remove scope from the query. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @param \Illuminate\Database\Eloquent\Model $model | |
* @return void | |
*/ | |
public function remove(Builder $builder, Model $model) | |
{ | |
$query = $builder->getQuery(); | |
$column = $model->getQualifiedTenantColumn(); | |
$bindingKey = 0; | |
foreach ((array) $query->wheres as $key => $where) | |
{ | |
if ($this->isTenantConstraint($where, $column)) | |
{ | |
$this->removeWhere($query, $key); | |
// Here SoftDeletingScope simply removes the where | |
// but since we use Basic where (not Null type) | |
// we need to get rid of the binding as well | |
$this->removeBinding($query, $bindingKey); | |
} | |
// Check if where is either NULL or NOT NULL type, | |
// if that's the case, don't increment the key | |
// since there is no binding for these types | |
if ( ! in_array($where['type'], ['Null', 'NotNull'])) $bindingKey; | |
} | |
} | |
/** | |
* Remove scope constraint from the query. | |
* | |
* @param \Illuminate\Database\Query\Builder $builder | |
* @param int $key | |
* @return void | |
*/ | |
protected function removeWhere(BaseBuilder $query, $key) | |
{ | |
unset($query->wheres[$key]); | |
$query->wheres = array_values($query->wheres); | |
} | |
/** | |
* Remove scope constraint from the query. | |
* | |
* @param \Illuminate\Database\Query\Builder $builder | |
* @param int $key | |
* @return void | |
*/ | |
protected function removeBinding(BaseBuilder $query, $key) | |
{ | |
$bindings = $query->getRawBindings()['where']; | |
unset($bindings[$key]); | |
$query->setBindings($bindings); | |
} | |
/** | |
* Check if given where is the scope constraint. | |
* | |
* @param array $where | |
* @param string $column | |
* @return boolean | |
*/ | |
protected function isTenantConstraint(array $where, $column) | |
{ | |
return ($where['type'] == 'Basic' && $where['column'] == $column && $where['value'] == 1); | |
} | |
/** | |
* Extend Builder with custom method. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
*/ | |
protected function addWithAllTenants(Builder $builder) | |
{ | |
$builder->macro('withAllTenants', function(Builder $builder) | |
{ | |
$this->remove($builder, $builder->getModel()); | |
return $builder; | |
}); | |
} | |
} | |
// b/app/Tenant/TenantTrait.php | |
namespace Cms\Tenant; | |
trait TenantTrait { | |
public static function bootTenantTrait() | |
{ | |
static::addGlobalScope(new TenantScope); | |
} | |
public function getTenantColumn() | |
{ | |
return defined('static::TENANT_COLUMN') ? static::TENANT_COLUMN : 'tenant_id'; | |
} | |
public function getQualifiedTenantColumn() | |
{ | |
return $this->getTable().'.'.$this->getTenantColumn(); | |
} | |
public static function withAllTenants() | |
{ | |
return with(new static)->newQueryWithoutScope(new TenantScope); | |
} | |
} | |
// b/app/User.php | |
use Illuminate\Auth\Passwords\CanResetPassword; | |
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; | |
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; | |
use Cms\Tenant\TenantTrait; | |
class User extends Model implements AuthenticatableContract, CanResetPasswordContract { | |
use Authenticatable, CanResetPassword; | |
use TenantTrait; | |
// b/database/migrations/2015_04_24_232529_create_tenants_table.php | |
use Illuminate\Database\Schema\Blueprint; | |
use Illuminate\Database\Migrations\Migration; | |
class CreateTenantsTable extends Migration { | |
/** | |
* Run the migrations. | |
* | |
* @return void | |
*/ | |
public function up() | |
{ | |
Schema::create('tenants', function(Blueprint $table) | |
{ | |
$table->increments('id'); | |
$table->string('domain'); | |
$table->string('path'); | |
$table->string('namespace'); | |
$table->softDeletes(); | |
}); | |
} | |
/** | |
* Reverse the migrations. | |
* | |
* @return void | |
*/ | |
public function down() | |
{ | |
Schema::drop('tenants'); | |
} | |
} | |
// b/database/migrations/2015_04_25_001042_add_tenant_contstrain_on_users_table.php | |
use Illuminate\Database\Schema\Blueprint; | |
use Illuminate\Database\Migrations\Migration; | |
class AddTenantContstrainOnUsersTable extends Migration { | |
/** | |
* Run the migrations. | |
* | |
* @return void | |
*/ | |
public function up() | |
{ | |
Schema::table('users', function(Blueprint $table) | |
{ | |
$table->integer('tenant_id')->unsigned()->after('id'); | |
$table->foreign('tenant_id')->references('id')->on('tenants'); | |
}); | |
} | |
/** | |
* Reverse the migrations. | |
* | |
* @return void | |
*/ | |
public function down() | |
{ | |
Schema::table('users', function(Blueprint $table) | |
{ | |
$table->dropForeign('users_tenant_id_foreign'); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment