YII SaaS Multi Tenant application with single database

  1. SaaS application structure in YII using only one database
  2. create RActiveRecord.php
  3. Add a field in all tables named "tenant"
  4. The RActiveRecord.php
  5. Example for getTenant()
  6. Final step

SaaS application structure in YII using only one database ¶

Lots of people are asking how to solve it with YII,We think its difficult with YII. But its easy to solve . There is no database triggers needed . we can simply sove it by extending a class(say "RActiveRecord") from CActiveRecord .Then extend all our model classes from that class.

create RActiveRecord.php ¶

create a file under protected/components named RActiveRecord.php .

Extend it from CActiveRecord .

WE need override the methods beforeSave() , defaultScope() or beforeFind() and beforeDelete() .

Add a field in all tables named "tenant" ¶

Add a field in all tables named "tenant" . This is the unique identifier .

There is no need to add "tenant" in any model class.

The RActiveRecord.php ¶

Please paste this code to RActiveRecord.php file

<?php

//::Rajith:: SaaS
class RActiveRecord extends CActiveRecord
{
	
	//saving model->tenant to all tables automatic ::Rajith::
	public function beforeSave()
    {
		$tenant = $this->getTenant();
		$this->tenant = $tenant;
		return parent::beforeSave();
	}
	
	//Find only tenant match by default ::Rajith::
	//use  defaultScope() or  beforeFind()
	//comment defaultScope(), if you using beforeFind()
	public function defaultScope()
    {
		$tenant = $this->getTenant();
		return array(
        'condition'=> "tenant=:tenant",
		'params' => array(":tenant"=>$tenant));
	}
	
	
	//Find only tenant match by default ::Rajith::
	//uncomment if you using beforeFind()
	/*public function beforeFind()
	{
		$tenant = $this->getTenant();
		
		$criteria = new CDbCriteria;
		$criteria->condition = "tenant=:tenant";
		$criteria->params = array(":tenant"=>$tenant);
		
		$this->dbCriteria->mergeWith($criteria);
		parent::beforeFind();
    }*/
	
	
	
    //before deletion check for the ownership ::Rajith::
    //not working for deleteAllByAttributes
    public function beforeDelete()
    {
		
			    $tenant = $this->getTenant();
                if ($this->tenant == $tenant)
                {
                        return true;
                        
                }
                else
                {
                        
                        return false; // prevent actual DELETE query from being run

                }
    }


    //to get the unique UNIQUE identifier
    public function getTenant()
    {
    //this is the unique identifier . Use your own ideas to get a unique identifier(tenent)
   
    return 'identifier-id-name';
    }
	
}

In my application i used the host name to find the tenant

Example for getTenant() ¶

//to get the unique UNIQUE identifier
    public function getTenant()
    {
		
			    $domain = $_SERVER['HTTP_HOST'];
				$connect = mysql_connect("localhost","root","password") or die("not connecting");
				mysql_select_db("databasename") or die("no db");
				$query = mysql_query("SELECT * FROM users WHERE customdomain='$domain'");
				$numrows = mysql_num_rows($query);
				if($numrows)
				{
					 
					$results = mysql_fetch_assoc($query);
					return $results['username'];
				}
				else
				{
					$subdomain = implode(array_slice(array_reverse(explode('.', $_SERVER['SERVER_NAME'])),2));
					$query = mysql_query("SELECT * FROM users WHERE username='$subdomain' AND whitelabel=1");
					$numrows = mysql_num_rows($query);
					if($numrows)
					{
					return $subdomain;
					}
					else
					{ return 'parent';}
				}
				
    }

WE can simply use the subdomain name or domain name as the tenant.

or

use setstate at the time of login in Useridentity

Yii::app()->user->setState('tenant', "something-unique(domain-name or sudomain)");

and use that in getTenant()

//to get the unique UNIQUE identifier
	public function getTenant()
    {
		
	 return Yii::app()->user->tenant;
				
    }

Final step ¶

change in the model class

class Model-name extends RActiveRecord
{

...........

...........

Thats it!!

Please note that the commented parts in the beforeDelete() .

if you want to use the deleteAllByAttributes() and other deletion methods except delete(), then change the CActiveRecord class . because in the CActiveRecord , the beforeDelete() method only invoked for the delete() method .

Or

Use the $this->getTenant() in the conditon, check whether 'tenant' and '$this->getTenant()' matching

i dont think this is the best way to achieve SaaS in YII. Appreciate suggestions and more ideas .

Thank You - Rajith