You are viewing revision #4 of this wiki article.
This version may not be up to date with the latest version.
You may want to view the differences to the latest version.
- SaaS application structure in YII using only one database
- create RActiveRecord.php
- Add a field in all tables named "tenant"
- The RActiveRecord.php
- Example for getTenant()
- 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 neede . 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
Column 'tenant' in where clause is ambiguous
Hello,
I got error:
Integrity constraint violation: 1052 Column 'tenant' in where clause is ambiguous
When many model use this with relations.
I updated defaultScope() then it work:
public function defaultScope() { $tenant = $this->getTenant(); return array( 'alias' => $this->tableName(), 'condition'=> sprintf("%s.tenant=:tenant", $this->tableName()), 'params' => array(":tenant"=>$tenant)); }
Thanks with great tutorial. I learn a lot from this.
Thanh
try using beforeFind()
if you have relations use beforeFind(), i think it will work
public function beforeFind() { $tenant = $this->getTenant(); $criteria = new CDbCriteria; $criteria->condition = "tenant=:tenant"; $criteria->params = array(":tenant"=>$tenant); $this->dbCriteria->mergeWith($criteria); parent::beforeFind(); }
i dont think that this wiki is not a good solution for all type of uses.
need good suggestions.
thanks Thanh
Thank you Thanh,
That is a good method
managing user in SAaS Application
how do you manage user in this approach?
@ziii
Hi,
I am using this with yii user module. The user table is also having the tenant field.
We are using this identifier saved with user as the tenant value for the active login.
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.