Model vs active records - yii2

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.

Related

Yii2/PHP: Abstracting Database Access for InfluxDB and MySQL

In my Yii2/PHP project I need to have both databases integrated:
MySQL for meta data, Web-UI, ...
InfluxDB for measurement data (heavy loads of timeserie data)
To reduce complexity I'd like to start with MySQL only and add InfluxDB later.
My idea is to create an abstraction/superclass for both databases (for measurement data only) which allow to do implementation and perform tests with MySQL and enable speedup with InfluxDB at a later stage in the project.
The abstraction should have methods for:
database connection management
writing data
reading data (raw data, aggregations)
Since I am no InfluxDB expert (yet): Does this architecture make sense or are both datamodels and schemes fundamentally different so an abstraction would be worthless? Are there projects out there to learn from?
First, you need to configure your databases like below this example take two mysql db:
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();
I'm not sure trying to fit MySQL and InfluxDB in the same mould would make a lot of sense.
A better approach IMHO, would be to have some sort of helper class for your computations (i.e.: Stats::getViews(), Stats::getVisitors(), ..) first using MySQL, and later rewrite it to use InfluxDB, keeping the same methods signatures and responses formats.

How to add and then display images urls from database in Laravel/Backpack CRUD?

I can upload files with special field upload_multiply and then save their names in DB, but how to print them back when editing the record? I use one-to-many relation, database structure like objects and objects_images, where exist object_id, name and url of uploaded image. Im using Laravel 5.4.
Now my code is very typicall:
I have a ObjectCrudController where i build form for my project. I use buildt-in Backpack/CRUD fields like this (for categories):
$this->crud->addField([
'label' => 'Category to display',
'type' => 'select',
'name' => 'category_id',
'entity' => 'category',
'attribute' => 'name',
'model' => 'App\Models\Category',
'wrapperAttributes' => [
'class' => 'form-group col-md-4'
]
]);
But I cant understand how to upload some images and then display their records as miniatures, also i need an ability to remove already uploaded image record. I think there can be used something like html field or view from backpack CRUD, but i don't know where to start.

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

Cakephp dynamic model/realation tables

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.

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.