Skip to content

Instantly share code, notes, and snippets.

@MartinTale
Last active August 29, 2015 14:23
Show Gist options
  • Save MartinTale/ec5773442b419044230e to your computer and use it in GitHub Desktop.
Save MartinTale/ec5773442b419044230e to your computer and use it in GitHub Desktop.
Multi Tenancy
<?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