Cakephp dynamic model/realation tables - mysql

I'm trying to implement this structure in my APP (i'll use fantasy model names):
User hasMany Box
Box hasMany Item
Item hasOne Itemdata
"Item" have some metadata/info
"Itemdata" has 2 or 3 mediumblobs
This would be very easy if I want a db with 4 tables: users,boxes,items,itemdata.
In my db structure, due to amount of data, I'd like to split Itemdata in user-dependent tables, like this:
a) user1_itemdatas, user2_itemdatas ...
or at least,
b) myapp_user1.itemdatas, myapp_user2.itemdatas
(where myapp_user* are different DB).
How can I change on the fly the table in model "Itemdatas" when finding/reading data belonging to certain users?
Should I choose for custom query?

Ok I found a way:
I created another database, in app/Config/database.php I added a new connection:
public $itemsdb = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'myuser',
'password' => 'mypass',
'database' => 'myapp_items',
'prefix' => ''
);
Then in Itemdata model I put:
class Itemdata extends AppModel {
public $useDbConfig = 'itemsdb';
..
}
Finally when performing search:
$user_id = $this->getUserByItemId($item_id);
$this->Itemdatas->useTable = 'itemdatas_user_'.$user_id;
$itemdata = $this->Itemdata->find('first',....);
An alternative can be to use user depending table prefix, instead of table name.

Related

How to create a relation for User, groups & groups metaData using TypeORM?

Creating the MySQL Table relationships, and confused to implement which Relationship should be implemented on User -> Groups -> GroupsMetaData.
User.id = OneToMany (Groups)
Groups = ManyToOne (Users)
Groups.id = ManyToMany (GroupsMetaData)
GroupsMetaData = ManyToOne(Groups)
Using TypeORM for implementing this approach.
Users.entity.ts
#OneToMany(type => Groups, groups => groups.uid)
groups:Groups[]
Groups.entity.ts
#ManyToOne(type => User, user => user.groups)
uid:User
Stucked to create the relations for Groups and GroupsMetaData.
Thanks
**It's not a professional way for an explanation, I tried for the best explanation
User entity
#OneToMany(type => Groups, groups => groups.uid)
groups:Groups[]
Group Entity
#ManyToOne(type => User, user => user.groups)
user: User
#ManyToOne(type => GroupMetadata, gmeta => gmeta.groups)
details: GroupMetadata
GroupMetadata Entity
#OneToMany(type => Groups, groups => groups.uid)
groups: Groups[]
With that config you'll have something like this:
A many to many relation between user and groupmetadata using group as junction table. I recommend you to change the name of some tables, group could be called "user_group" and group metadata can be renamed as group.
Let me know if this answers your question

Model vs active records

I am a newbie in YII2 and started learning this on my own. Recently I was working on YII2 models. While learning some pre built model files I noticed that some model files are extends through yii\base\Model where as some from \yii\db\ActiveRecord.
So I want to know the reason when one should use active records and when model i.e. we can do the query in model too so why is there need of active records. Which are the tasks can be done by active records but not by models. In simple words I want to know specific work of both model and active records.
ActiveRecord is a Model that uses a database engine to store the model(s) data.
yii\base\Model is a Model that does not specify how the data is being stored.
Eg a Model could be without a data table and have the actual data stored inside the class code instead of a database. A good example is in the yii2-basic app - the User model here.
I has the data stored in the class code as:
private static $users = [
'100' => [
'id' => '100',
'username' => 'admin',
'password' => 'admin',
'authKey' => 'test100key',
'accessToken' => '100-token',
],
'101' => [
'id' => '101',
'username' => 'demo',
'password' => 'demo',
'authKey' => 'test101key',
'accessToken' => '101-token',
],
];
Active records use for working with data tables and forms, but yii\base\Models for only for forms.

CakePHP 3 Auth on model other than User

I'm working on a project rebuild using CakePHP, and following the new Authentication documentation here:
http://book.cakephp.org/3.0/en/controllers/components/authentication.html
From what I'm reading, Cake3 uses the userModel='User' by default, but it has the option to set it to whatever you want. In my case, I have all the auth data in the 'Account' model (i.e. userModel => 'Account').
So, in my Account Entity, I added the following code:
protected function _setPassword($password)
{
return (new DefaultPasswordHasher)->hash($password);
}
Additionally, in my accounts table, my 'passwd' field is set to varchar(255) [I've read that's required for some reason].
When I use my default baked 'add' and 'edit' methods, the password is stored in plain text, and not hashed. The ONLY way I've found to get around this is to create a custom method in the AccountsTable class then call it using this kludge:
$this->request->data['passwd'] = $this->Accounts->hashPassword($this->request->data['passwd']);
My Auth component looks like this...
$this->loadComponent('Auth', [
'loginAction' => [
'controller' => 'Accounts',
'action' => 'login'
],
'authError' => 'Unauthorized Access',
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'username',
'password' => 'passwd'
],
'userModel'=>'Accounts'
]
]
]);
Is there a way to do this without dinking around with the raw request data?
Your mutator is named wrongly, the convention for mutators is _set followed by the camel cased field/property name. So since your field name is passwd, not password, it has to be named _setPasswd instead.
protected function _setPasswd($password)
{
return (new DefaultPasswordHasher)->hash($password);
}
See also Cookbook > Entities > Accessors & Mutators

Multiple database connections and Yii 2.0

I have two databases, and every database has the same table with the same fields, but how do I get all records from all of two databases at the same time in Yii 2.0?
First you need to configure your databases like below:
return [
'components' => [
'db1' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=db1name', //maybe other dbms such as psql,...
'username' => 'db1username',
'password' => 'db1password',
],
'db2' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=db2name', // Maybe other DBMS such as psql (PostgreSQL),...
'username' => 'db2username',
'password' => 'db2password',
],
],
];
Then you can simply:
// To get from db1
Yii::$app->db1->createCommand((new \yii\db\Query)->select('*')->from('tbl_name'))->queryAll()
// To get from db2
Yii::$app->db2->createCommand((new \yii\db\Query)->select('*')->from('tbl_name'))->queryAll()
If you are using an active record model, in your model you can define:
public static function getDb() {
return Yii::$app->db1;
}
//Or db2
public static function getDb() {
return Yii::$app->db2;
}
Then:
If you have set db1 in the getDb() method, the result will be fetched from db1 and so on.
ModelName::find()->select('*')->all();
Just to add:
I followed the answer provided but still got an error:
"Unknown component ID: db"
After some testing, here is what I discovered: The function getDB is only called AFTER a connection is made to db. Therefore, you cannot delete or rename 'db' in the config file. Instead, you need to let the call to 'db' proceed as normal and then override it afterwards.
The solution (for me) was as follows:
In config/web.php add your second database configuration below db as follows:
'db' => require(__DIR__ . '/db.php'),
'db2' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=name',
'username' => 'user',
'password' => 'password',
'charset' => 'utf8',
'on afterOpen' => function ($event) {
$event->sender->createCommand("SET time_zone = '+00:00'")->execute();
},
],
DO NOT rename db. Failure to find db will cause an error. You can name db2 whatever you like.
Now in the model, add the following code:
class ModelNameHere extends \yii\db\ActiveRecord {
// add the function below:
public static function getDb() {
return Yii::$app->get('db2'); // second database
}
This will now override the default db configuration.
I hope that helps somebody else.
Note: you can include the configuration for db2 in another file but you cannot include it in the db.php file (obviously). Instead, create a file called db2.php and call it as you do db:
'db' => require(__DIR__ . '/db.php'),
'db2' => require(__DIR__ . '/db2.php'),
Thanks
Our situation is a little more complex, we have a "parent" database which has a table that contains the name of one or more "child" databases.
The reason for this is that the Yii project is instantiated for each of our clients, and the number of child databases depends on the client, also the database names are arbitrary (although following a pattern).
So we override
\yii\db\ActiveRecord
as follows:
class LodgeActiveRecord extends \yii\db\ActiveRecord
{
public static function getDb()
{
$lodgedb = Yii::$app->params['lodgedb'];
if(array_key_exists( $lodgedb, Yii::$app->params['dbs'])) {
return Yii::$app->params['dbs'][ $lodgedb ];
}
$connection = new \yii\db\Connection([
'dsn' => 'mysql:host=localhost;dbname=' . $lodgedb,
'username' => Yii::$app->params['dbuser'],
'password' => Yii::$app->params['dbpasswd'],
'charset' => 'utf8',
]);
$connection->open(); // not sure if this is necessary at this point
Yii::$app->params['dbs'][ $lodgedb ] = $connection;
return $connection;
}
}
Before calling any database function, first set Yii::$app->params['lodgedb'] to the name of the database required:
Yii::$app->params['lodgedb'] = $lodge->dbname; // used by LodgeActiveRecord
Your model classes don't change except they extend from LodgeActiveRecord:
class BookingRooms extends \app\models\LodgeActiveRecord
If you're using schmunk42/yii2-giiant to generate model classes, there is a 'modelDb' property which you can set to use a database component other than 'db'.

CakePHP - $hasAndBelongsToMany and $hasMany relationships between tables that reside on separate databases and have different querying languages?

I have quite the complex problem to tell you about Stackoverflow, you see I have three tables that I need to define associations between for an application. These tables are: engineers, tickets, and testcases. I need to have a $hasMany relationship between engineers and tickets, and a hasAndBelongsToMany relationship between testcases and tickets. Here is the catch, engineers and testcases are both on a mysql database, while tickets is on a sqlite3 database(trac) on a separate server. The separate server part is not an issue, because we have the server mounted on the same machine that my application is on. I am basically wondering how you would setup these relationships, so that as each model is loaded its dependencies are loaded as well. I will literally use just about any solution that gets the job done. I am using CakePHP by the way.
HABTM associations are not supported across multiple databases in CakePHP. In order to make the associations you will need to change the core. At least one person have achieved that. Look at his method.
You could just not define the association and do the querying manually. I.e., you associate the Ticket model with its HABTM join table TestcaseTickets in a belongsTo relationship (assuming they're both in the SQLite database) and query it manually:
$testcases = $this->Testcase->find(…);
$tickets = $this->Ticket->TestcaseTickets->find('all', array(
'conditions' => array(
'TestcaseTickets.testcase_id' => Set::extract('/Testcase/id', $testcases)
)
));
It takes away a bit of convenience, but doesn't make a big difference in the end, especially if you do this automatically in the afterFind callback of the Testcase model.
I don't think it's a problem to work with 2 (or more) database connections in CakePHP.
Basically you need 2 connection strings in your /app/config/database.php i.e.:
class DATABASE_CONFIG {
var $mysql = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'user',
'password' => 'password',
'database' => 'database',
'prefix' => '',
'encoding'=>'utf8'
);
var $sqlite = array(
'driver' => 'sqlite',
'persistent' => false,
'host' => 'localhost',
'login' => 'user',
'password' => 'password',
'database' => 'database',
'prefix' => '',
'encoding'=>'utf8'
);
}
btw. I am not quite sure about sqlite driver, but it should be this way.
And finally, you need to set to each model which connection to use. This could be done with:
class tickets extends AppModel {
...
var $useDbConfig = 'sqlite';
...
}
And especially if you don't use any special SQL "hacks" it should work.