Specifying saveStrategy for belongsToMany on save? - cakephp-3.0

I am using the default "replace" strategy in a belongsToMany relationship in my database. However, in one GUI situation, I need to use the "append" strategy instead.
How can I specify the saveStrategy for a belongsToMany relationship for a single save() call?
To be more specific, I can't use the link() method since my join table has other fields so I need to specify _joinData. Here's the code:
$this->JobOrdersEducations->patchEntity($joe,
[
'degree_disciplines' => [
[
'id' => $degree_discipline_id,
'_joinData' => [
"criticality_id" => $criticality_id
]
],
]
]
);
$this->JobOrdersEducations->save($joe);

You can change the strategy on the fly:
$this->JobOrdersEducations->association('DegreeDisciplines')->saveStrategy('replace');

Related

Join Table with the id of another join table

i'm currently trying to build a backend for a project. In this project you will be able to create "ContentElements" that can be used to display content in a page (in my case Sites). Every ContentElement can have multiple options. When a user creates a new Site with an ContentElement (e.g. header) he would enter all options of the element. For example:
"src": "/img/bg.jpg",
"text": "Lorem ipsum..."
In order to save the option's value per page it is used in, i store these values in a separate table (content_elements_sites_values).
My scheme currently looks like this: data scheme
So what i'm currently trying to do is when i get all data associated with the Site i also want to get the data from 'content_elements_sites_values'
$site = $this->Sites->get($id, [
'contain' => ['Templates', 'Colors', 'SiteTypes', 'ContentElements' => [
'ContentElementOptions' => [
'ContentElementsSitesValues'
]
]
],
'conditions' => [
// Just to explain my problem.
'ContentElementsSites.id' => 'ContentElementsSitesValues.content_elements_sites_id'
]
]);
I really don't know if this is even possible or even if my "design" is a total bull***t. But i cannot think of another way to store the filled in data. I'm very open to suggestions of a better way to store this data. Please ask if you need further information in order to help me.
Thank you in advance!
EDIT
I try to explain better what i want to achieve.
Every site can have multiple content_elements of the same type (association is stored in content_elements_sites junction table).
Every content_element can have multiple content_element_options
All content_element_options and content_elements are defined by an Admin.
Another user can create a site and populate it with content_elements and enter content_elements_sites_value for all content_element_options. And as the same content_element (e.g. a paragraph or a list) can have multiple occurrences in the same site, i'll need to store every content_elements_sites_value the user entered.
Thats why i created the link between content_elements_sites and content_element_options.
Currently i'm using this query to get everything expect the value:
$site = $this->Sites->find('all', [
'conditions' => [
'Sites.id' => $id
],
'contain' => ['ContentElements' => [
'sort' => [
'ContentElementsSites.order' => 'ASC'
],
'ContentElementOptions' => [
'ContentElementsSitesValues' => [
'conditions' => [
'ContentElementsSitesValues.content_elements_sites_id' => 'ContentElementsSites.id',
]
]
]
]
]
]);
This results in empty content_elements_sites_values
(int) 1 => object(App\Model\Entity\ContentElementOption) {
'id' => (int) 7,
'content_element_id' => (int) 1,
'name' => 'Test',
'json_name' => 'test',
'option_type_id' => (int) 1,
'content_elements_sites_value' => null,
}
My scheme currently looks like this: data scheme
I'm wondering if this query is even possible. Or if the whole thing is just too flexible.
The way you have defined the relationships signifies that you wish to have a very modular approach so that a content element can be used with multiple sites and a content element option can be used with multiple content elements.
If that is the case, schema direction looks okay with few changes :
1) content_elements_sites_values table can have site_id column directly instead of content_elements_sites_id column as site will be always unique for an entry in that table so the connection id of content_elements_sites isn't required.
2) content_elements_sites_values table can be renamed to content_element_options_values.
3) You can remove id column from content_elements_sites and content_elements_sites_values junction tables.
maybe this is what you are looking for :
$site = $this->Sites->get($id, [
'contain' => ['Templates', 'Colors', 'SiteTypes', 'ContentElements', 'ContentElements.ContentElementOptions','ContentElements.ContentElementOptions.ContentElementsSitesValues'],
....]);
dont forget to define association relationship in model table.

CakePHP 3.x UnitTest "Base table or view not found"

I get an Error-Message in a UnitTest in CakePHP 3.2 and the official documentation doesn't help me here anymore. I think the error has something todo with the SQL-Joins I try to use.
The Error-Message is the following:
`1) App\Test\TestCase\Controller\GetContentControllerTest::testIndex
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'contentmapper_test.CmDeviceclasses' doesn't exist`
In my Testclass GetContentControllerTest I load my fixtures that I need and that creates my Database-Tables on start:
`public $fixtures = [
'app.cm_content_options',
'app.cm_content_addresses',
'app.cm_deviceclasses',
'app.cm_properties'
];`
In the setUp()-Method I load the Main-Table:
`public function setUp()
{
parent::setUp();
$this->CmContentOptions = TableRegistry::get('CmContentOptions');
}`
My Test-Method testIndex() looks like this:
public function testIndex()
{
//find the belonging ContentOption to address data
//submitted by the client
$this->testFindAllByUriAndDeviceclassAndBoxId();
assert($this->arrObjContentOptions->count() == 1);
}
The testFindAllByUriAandDeviceclassAndBoxID() looks like shown in the following Image (the Editor is not able to prettyprint it correctly):
testFindAllByUriAandDeviceclassAndBoxID()
It's hard to describe the whole Context; I hope it is possible to understand.
The Error happens exactly on the statement shown in the image:
$result = $query->toArray()
I think I just forgot something to add in the setUp() Method or something like that.
I hope anyone can help.
You joins are set up incorrectly, you're mixing up aliases and table names.
The alias is the key of the join array, and the table key should hold the actual database table name, not the table class name.
Given that you are following CakePHPs naming conventions for your database table names, your join setup should look more like this
[
'CmDeviceclasses' => [ /* < this is the SQL alias */
'table' => 'cm_deviceclasses', /* < this is the database table name */
'type' => 'LEFT',
'conditions' => [
'CmDeviceclasses.classname' => $this->deviceclass
]
],
'CmContentAddresses' => [
'table' => 'cm_content_addresses',
'type' => 'INNER',
'conditions' => [
'CmContentAddresses.uri' => $this->uri,
'CmContentAddresses.boxid' => $this->boxid,
]
],
],
[
'CmDeviceclasses.classname' => 'string',
'CmContentAddresses.uri' => 'string',
'CmContentAddresses.boxid' => 'string'
]
There is no technical need to follow the CamelCase conventions for the aliases, but for sure it doesn't hurt to generally stick to the conventions.
ps, if you setup the associations properly, then there should be no need to use manual joins, you could just use Query::contain() and Query::innerJoinWith() or Query::matching().
See
Cookbook > Database Access & ORM > Associations - Linking Tables Together
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Retrieving Associated Data
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Filtering by Associated Data

Yii2 mongodb: how to change database?

My application use many databases, they are in same structure, but data has no relation between different databases. I need to change database via request params.
In the config can only setup dsn, but I want to change database dynamically.
How can I do that.
I got myself:
$mongo = Yii::$app->get('mongodb');
$mongo->options['db'] = 'foo';
The simplest solution is to defined multiple connections in the configuration:
'components' =>
[
...
'mongodb' =>
[
'class' => '\yii\mongodb\Connection',
'dsn' => 'mongodb://localhost:27017/database1',
],
'othermongodb' =>
[
'class' => '\yii\mongodb\Connection',
'dsn' => 'mongodb://localhost:27017/database2',
],
...
]
You can then access your connections with Yii::$app->mongodb and Yii::$app->othermongodb (or using the get()-method if you prefer). This also allows you to specify the correct database for the ActiveRecord classes that come from a different database:
class MyOtherDBMongo extends \yii\mongodb\ActiveRecord
{
public static function getDb()
{
return \Yii::$app->get('othermongodb');
}
}

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

How to add current timestamp in the database. What is the format?

I want to add the current system time into database while inserting new record into database as in "time_created" column. PHP's time() function don't have support in yii2. I want yii2 specific time function that will help me save current timestamp. Anyone knows????
You can use yii\db\Expression to perform SQL function
<?php
use yii\db\Expression;
$model->time_created = new Expression('NOW()');
$model->save();
You can also use Yii2's formatter like below:
Yii::$app->formatter->asTimestamp(date('Y-d-m h:i:s')); //1410488596
Yii::$app->formatter->asDatetime(date('Y-d-m h:i:s')); //Sep 12, 2014, 2:21:56 AM
Yii 2 has special behavior for this. Just attach it to the model.
Add this to your model to behaviors() method:
use yii\behaviors\TimestampBehavior;
use yii\db\Expression;
public function behaviors()
{
return [
// Other behaviors
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'time_created',
'updatedAtAttribute' => false,
'value' => new Expression('NOW()'),
],
];
}
Maybe this is old but it may help some one else. Add the below code in your model. Make sure you change the createdAttribute to your attribute. You also have to include:
use yii\behaviors\TimestampBehavior;
use yii\db\Expression;
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'entry_date',
'updatedAtAttribute' => false,
'value' => new Expression('NOW()'),
],
];
}
You can Simply use PHP's date() function as - date('Y-m-d H:i:s');
There are several ways of adding timestamp, I suggest using UNIX timestamp for sake of dealing with timezone. For example in Google SQL instances you cannot setup timezone, but offset, which means that you would need to update offset twice a year because of summer/winter time. For that as someone mentioned you can also use behaviours:
public function behaviors()
{
return [
[
'class' => AttributeBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'entry_date',
],
'value' => function ($event) {
return time();
},
],
];
}
or you can simply just add before insert, like $model->entry_date=time(); but as you will do this on every INSERT then behaviors are better choice.
And of course if you want to read formatted date, you can use:
\Yii::$app->formatter->asDate($model->entry_date);
For asDate formatter, you can read here:
http://www.yiiframework.com/doc-2.0/yii-i18n-formatter.html#asDate()-detail