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
//::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