cakephp: How to save hasmany chain by saveAssociated - cakephp

Basically, there are 3 models:
Class Model1 {
Public $hasMany = array {
'Model2' => array(
'className' => 'Model2',
'foreignKey' => 'model1id'
)
};
}
Class Model2 {
Public $belongsTo = array {
'Model1' => array(
'className' => 'Model1',
'foreignKey' => 'model1id'
)
};
Public $hasMany = array {
'Model3' => array(
'className' => 'Model3',
'foreignKey' => 'model2id'
)
};
}
Class Model3 {
Public $belongsTo = array {
'Model2' => array(
'className' => 'Model2',
'foreignKey' => 'model2id'
)
};
}
In ctp file under Model1s view folder, I have code as below:
echo $this->Form->create('Model1');
echo $this->Form->input('[some_model1_field]');
echo $this->Form->input('Model2.0.[some_field]');
echo $this->Form->input('Model3.0.[some_field]');
echo $this->Form->end('Save');
In Controller, having a function like below:
$this->Model1->saveAssociated($this->request->data, array('deep' => true));
The result is the all fields are inserted into db, including the foreignkey model1id saved into model2 table automatically by framework. The only issue is that the model2id cannot saved into model3 table automatically by framework. How to deal with it?

Model1 is not associated with Model3, your input field names and the resulting request data however are representing such an association. Debug the request data and compare it to what the docs are showing
http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
The problem with the structure should be obvious, it's going to be something like
[
// ...
'Model2' => [
[/* ... */]
]
'Model3' => [
[/* ... */]
]
]
If you'd wanted to save Model3s for Model2, then you'd have to properly nest the data like
echo $this->Form->input('Model2.0.Model3.0.[some_field]');
which should result in something like
[
// ...
'Model2' => [
// ...
'Model3' => [
[/* ... */]
]
]
]

Related

Cakephp3 doesn't recognize the table with different name in deep associations

I have created a table called Delegates, and I created a UsersTable and User Entity. And I have used $this->setTable('delegates'); in UsersTable to be able to access Delegates from $this->Users; (I just want to say I have created a User Model with delegates table)
So far so good...
In my application I am trying to access deep associations. Every thing is fine with this query but when I contain the User model I get The Users association is not defined on Comments.
I can confirm the associations are set correctly.
...
// There are more associations up there.
...
'Comments', [
'className' => 'App\Model\Table\CommentsTable',
'foreignKey' => 'delegate_assessment_criteria_id',
'belongsTo' => [
'Assessors' => [
'className' => 'App\Model\Table\AssessorsTable',
'foreignKey' => 'assessor_id',
],
'Users' => [
'className' => 'App\Model\Table\UsersTable',
'foreignKey' => 'delegate_id',
]
]
]
Here is deep association.
...
// There are more associations up there.
...
'Comments' => function($q) {
return $q
->select([
'id'
])
->order(['Comments.created DESC'])
->contain([
'Assessors' => function($q) {
return $q
->select([
'id'
])
->enableAutoFields();
}
])
->contain([
'Users' => function($q) {
return $q
->select([
'id',
'delegate_id'
])
->enableAutoFields();
}
])
->enableAutoFields();
}
2 Notes:
If I contain the User model in very first in hierarchy of my query I
can access the User fields but in deep association this doesn't
work.
If I contain Delegates it works.
I believe there is a problem with Cakephp query builder.
Alright finally I figured it out. I have done it before I don't know why I forgot. Probably because I was in deep association I drained into it.
The deep association contain was creating conflict with very first contain. if I would set different propertyName in deep associations then it does the purpose.
Bear in mind you must set this associations on your Table Models.
'Comments', [
'className' => 'App\Model\Table\CommentsTable',
'foreignKey' => 'delegate_assessment_criteria_id',
'belongsTo' => [
'Assessors' => [
'className' => 'App\Model\Table\AssessorsTable',
'foreignKey' => 'assessor_id',
],
'Delegates' => [ // <= Here set the name of Assossiation you want to be shown in array
'className' => 'App\Model\Table\UsersTable',
'propertyName' => 'delegates', // <= Add propertyName and set another name here
'foreignKey' => 'delegate_id',
]
]
]
And on association
'Comments' => function($q) {
return $q
->select([
'id'
])
->order(['Comments.created DESC'])
->contain([
'Assessors' => function($q) {
return $q
->select([
'id'
])
->enableAutoFields();
}
])
->contain([
'Users' => function($q) {
return $q
->select([
'id',
// 'delegate_id' // <= Remove this - We have already done it when we set the association on above code.
])
->enableAutoFields();
}
])
->enableAutoFields();
}

cakephp hasAndBelongsToMany

My 'Reservation' model and 'Profile' model have hasAndBelongsToMany association.
Here is my Reservation Model.
class Reservation extends AppModel {
.
.
var $hasAndBelongsToMany = array(
'Profile' => array(
'className' => 'Profile',
'joinTable' => 'profiles_reservations',
'foreignKey' => 'reservation_id',
'associationForeignKey' => 'profile_id',
'unique' => true,
)
);
And Here is my Profile Model.
class Profile extends AppModel {
var $name = 'Profile';
}
And here is my controller .
function prac3($lname, $fname) {
$profiles = $this->Profile->find('all', array(
'conditions' => array(
'Profile.lname LIKE' => $lname.'%',
'Profile.fname LIKE' => '%'.$fname.'%'
),
'order'=>array( 'Profile.created DESC' ),
));
$this->set('profiles', $profiles);
}
And here is my view.
<?php
if($profiles) {
foreach($profiles as $key => $profile): ?>
<tr>
<td><?= $profile['Profile']['id'] ?></td>
<td><?= $profile['Profile']['lname'] ?></td>
<td><?= $profile['Profile']['fname'] ?></td>
<td><?= $profile['Profile']['home_phone'] ?></td>
</tr>
endforeach;
echo '</table>';
}
?>
I wanna get ['Reservation']['name'] in the view using Profile model. How can I do this?
Update Your Profile class
class Profile extends AppModel {
var $name = 'Profile';
var $hasAndBelongsToMany = array(
'Reservation' => array(
'className' => 'Reservation',
'joinTable' => 'profiles_reservations',
'foreignKey' => 'profile_id',
'associationForeignKey' => 'reservation_id',
'unique' => true, // More about update below
)
}
In Your find() method shoud use recursive:
$profiles = $this->Profile->find('all', array(
//...
'recursive' => 2,
));
or Containable behavior:
$this->Profile->Behaviors->load('Containable');
$profiles = $this->Profile->find('all', array(
//...
'contain' => array(
'Reservation',
),
'recursive' => -1,
));
and Your reservetion will be in array like $profiles['Reservation'][n]['name'].
Additional, in Your comment You wrote "When I add new reservation and profile, the rows that has the profile id were disappeared in the profiles_reservations Table."
Because You are using 'unique' => true in $hasAndBelongsToMany property. The cookbook says:
If true (default value) CakePHP will first delete existing relationship records in the foreign keys table before inserting new ones. Existing associations need to be passed again when updating.
See: https://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
Try this
class ReservationsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Profiles', [
'joinTable' => 'reservation_profiles',
]);
}
}
class ProfilesTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Reservations', [
'joinTable' => 'reservation_profiles',
]);
}
}
Then as you can see ReservationsTable is the model that relate Reservation to Profiles using reservation_profiles table to do that Profiles does the same. You don't need to have a model for reservation_profiles, but you must have this table on your databse, I sugest you to use migration to create them. Finally in your controller, this could be in your ReservationController you call
$this->Reservation->Profiles->find()->where(['condition'=> 'param']);
This may solve your problem, explainning ($this) refer to the Controller class, ->REservation refer to the model Reservation -> Profiles refer to related model to the class Reservation model, ->find()... refer to the query executed. If you need more https://book.cakephp.org/3.0/en/orm/associations.html#belongstomany-associations

CakePHP 3 - Users belongsToMany Users

I have a specific request, to build an association between users. This causes me confusion, how to reduce duplicate associations, query and results?
The starting point would look like this?
// UsersTable
$this->belongsToMany('Users', [
'through' => 'Connections',
]);
How to fetch all associations in one query, regardless of whether users key in "user_from" or "user_to" field?
How about using aliases?
Your users table:
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('ToConnections', [
'className' => 'Connections',
'foreignKey' => 'user_to'
]);
$this->hasMany('FromConnections', [
'className' => 'Connections',
'foreignKey' => 'user_from'
]);
}
}
And your connections table:
class ConnectionsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('ToUsers', [
'className' => 'Users',
'foreignKey' => 'user_to'
]);
$this->belongsTo('FromUsers', [
'className' => 'Users',
'foreignKey' => 'user_from'
]);
}
}
You can then use contain() to load associated models as required.
$query = $conections->find()->contain([
'ToUsers',
'FromUsers'
]);
$recipients = TableRegistry::get('users');
$query = $recipients->find()->contain([
'ToConnections.FromUsers',
]);

CakePHP multiple raltions

I want to make a Library in cakePHP and I have some problems with the database relations.
Basically there is a Book object, a Publisher object, a Comment object and a User object.
Every Book can have a Publisher, and multiple Comments. A comment can belongsTo only one book and one user. Now the question is, How can i show the users also in the returned array
I want to show user info for every comment
Here is the code.
class Book extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment'
)
);
public $belongsTo = 'Publisher';
}
class Publisher extends AppModel{
public $hasMany = array(
'Book' =>array(
'className' => 'Book'
)
);
}
class Comment extends AppModel {
public $hasOne = array(
'User' => array(
'className' => 'User',
),
'Book' => array(
'className' => 'Book',
)
);
}
class User extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment'
)
);
}
And here is the result:
[
{
"Book":{
"id":"1",
"title":"Egy magyar nabob",
"author_id":"1",
"publisher_id":"1",
"punblishing_date":"2014-10-08 00:00:00",
"creation_date":"2014-10-01 22:48:55"
},
"Publisher":{
"id":"1",
"name":"Koinonia Kiado",
"description":"Egy kolozsvari kiado",
"address":null
},
"Comment":[
{
"id":"3",
"comment":"Ez egy irto jo konyv :D",
"user_id":"1",
"book_id":"1",
"creation_date":"2014-10-01 23:08:21"
},
{
"id":"4",
"comment":"Ujabb COmment :D",
"user_id":"1",
"book_id":"1",
"creation_date":"2014-10-01 23:17:02"
}
]
},
{
"Book":{
"id":"2",
"title":"Ket magyar nabob",
"author_id":"1",
"publisher_id":"1",
"punblishing_date":null,
"creation_date":"2014-10-01 23:17:22"
},
"Publisher":{
"id":"1",
"name":"Koinonia Kiado",
"description":"Egy kolozsvari kiado",
"address":null
},
"Comment":[
{
"id":"5",
"comment":"Ez ismet egy fantasztikus konyv :D",
"user_id":"1",
"book_id":"2",
"creation_date":"2014-10-01 23:17:49"
}
]
}
]
Thanks!
A relatively simple way is to have a getUsers() action in your Users controller, that is similar to the view action, e.g. you can pass it a User ID, but instead of loading a view it will just return an array. Then each time you come across a comment in your Book controller/view you can use requestAction to call Users/getUsers(), passing it Comment.user_id, to return the related User array.

How to save HABTM association in CakePhp?

I have a Product model and an Image model. They have an HABTM association.
Some Images exist but they are not linked to the product.
Now when I save a Product I would like to link it to some unlinked images using an array of images IDs (I MUST use this array).
Here's my code:
class Image extends AppModel {
public $useTable = 'images';
var $hasAndBelongToMany = array(
'Product' => array(
'className' => 'Product',
'joinTable' => 'products_images',
'foreignKey' => 'id_image',
'associationForeignKey' => 'id_product'
)
);
}
class Product extends AppModel {
public $useTable = 'products';
var $hasAndBelongToMany = array(
'Image' => array(
'className' => 'Image',
'joinTable' => 'products_images',
'foreignKey' => 'id_product',
'associationForeignKey' => 'id_image'
)
);
}
class productsController extends AppController {
public $name = 'Products';
public $uses = array('Products', 'File');
public function add() {
if (!empty($this->data)) {
$this->Product->create();
if ($this->AnnuncioImmobiliare->save($this->request->data)) {
$idProduct = $this->Product->getLastInsertID();
$this->request->data['imagesIds'] = array("1", "2", "3");
if(isset($this->request->data['imagesIds'])){
foreach($this->request->data['imagesIds'] as $imageId){
$this->Image->id = $imageId;
$this->Image->save(array('Product'=>array('id'=>$idProduct)));
}
}
}
}
}
}
This doesn't work. Where am I wrong?
1) you didn't provide the data, so we can't verify it's in the correct format.
2) you're using "save()" not "saveAll()" or "saveMany()" or "saveAssociated()". Just using "save()" will not save any associated model data.
I found a solution myself!
I observed how CakePHP handles $this->data->response array when data is passed using an input automagically created for HABTM associations and I found the array should be formatted like this:
array(
'Product' => array(
'name' => 'myProduct',
'id' => ''
),
'Image' => array(
'Image'=>array(
0 => '1',
1 => '2',
2 => '3'
)
)
)
So the correct code for the controller is
class productsController extends AppController {
public $name = 'Products';
public $uses = array('Products', 'File');
public function add() {
if (!empty($this->data)) {
$this->Product->create();
if (isset($this->request->data['imagesIds'])) {
$this->request->data['Image'] = array('Image' => $this->request->data['imagesIds']);
}
if ($this->AnnuncioImmobiliare->save($this->request->data)) {
/* success */
}
}
}
}
That's it! I hope you find this useful.

Resources