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