I have a many-to-many relation between 2 models Invoiceand Position defined with the hasMany through method like described in the Cookbook.
Now when i create a Invoice, i want to save multiple Positions to that Invoice in one save()-operation. Something like this:
$data = array(
'Invoice' => array(
// invoice stuff
),
'InvoicesPosition' => array(
[0] => array(
// additional meta information
'Position' => array(
// position stuff
// ...
),
),
[1] => array(
// ...
'Position' => array(
// position stuff
// ...
),
),
// and so on
),
);
$this->Invoice->saveAll($data, array('deep' => true));
The result should be one new Invoice, with 2 Positions linked to it
(That means 1 new record in the invoices table, 2 new records in the positions table and 2 new records in the join table).
Is it possible with Cake's built-in methods? Or do i have to overwrite the saveAll()-method for that model?
Use 'deep' => true option in saveAll() method.
$this->Invoice->saveAll($data, array('deep' => true));
Related
I am trying to get messages with their corresponding users, in order to display a chat list of profiles with their last message in chronological order.
array(
0 => array(
'Recepient' => array(
'id' => ...
'name' => ...
...
),
'Message' => array(
'content' => ...
'created' => ...
...
)
),
1 => ...
)
and in order to retrieve the results, I've written this find() method:
$msgs = $this->Message->find('all', array(
'group' => array('Recepient.id'),
'order'=>'Message.created DESC',
'conditions'=>
array(
'OR'=> array(
array('recepient_id'=>$pid),
array('sender_id' => $pid)
)
)
));
What I have:
message with corresponding "Recepient",
in chronological order
The problem:
the query DOES NOT retrieve the most recent message from $recepient_id/$sender_id combination.
So instead of list of users with the last message, I have a list of users with a message. What's wrong with my query? Thanks for help!
METHOD 2 results
I've created "chat_id" field in the database which is basically recepient_id+sender_id sorted alphabetically (because if user1 send user2 a message user1 is sender, later when user2 responds, he becomes the sender, so sorting will ensure two users will always have the same chat_id).
Than I added DISTINCT to the query:
$this->Message->recursive = 0;
$msgs = $this->Message->find('all', array(
'fields' => array('DISTINCT Message.chat_id','Message.*','Recepient.*'),
'order'=>'Message.created DESC',
'conditions'=>
array(
'OR'=> array(
array('recepient_id'=>$pid),
array('sender_id' => $pid)
)
)
));
and it does NOT work! I am now getting multiple messages for the same conversation.
If I remove Message fields and Recipient fields from the query, I get correct amount of "chats".
'fields' => array('DISTINCT Message.chat_id'),
but that's not the solution.
CakePHP version 2.7.0
MySQL DB
METHOD 3 results
$msgs = $this->Message->find('all', array(
'order'=>'Message.created DESC',
'fields' => 'recepient_id, content, max(Message.created) as max_created',
'group'=>'recepient_id',
// 'contain' => array('Recepient'),
'conditions'=>array( 'chat_id' => $chats )
));
I gave up on single-find method to resolve this, so now 1.I am getting list of chats, 2.I want to find the last message from each chat.
Acording to http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/ my find query should work. What happens is
(int) 0 => array(
'Message' => array(
'recepient_id' => '55e6d764-1444-4aad-a909-9042f07f76da',
'content' => '1st msg',
'created' => '2015-09-20 18:24:17',
'created_nice' => '2 hours ago'
),
(int) 0 => array(
'max_created' => '2015-09-20 18:24:28'
)
),
the field max(Message.created) is indeed showing the latest message from conversation but the 0=>Message array is for different message! As you can see $results[0]['Message']['created'] time and $results[0][0]['max_created'] are different!
Finally! Working solution:
$db = $this->Message->getDataSource();
$chats = $db->fetchAll(
'select id from (select * from messages order by created desc) Message
where recepient_id = "'.$pid.'" or sender_id = "'.$pid.'"
group by `chat_id`'
);
Above code will retrieve all messages, following the requirements. You can
a) SINGLE QUERY add mysql JOIN to include associated model (we keep the code single-query neat)
b) TWO QUERIES BUT SMARTER easier method, to select just "ids" of the messages and create another query that will have cake's build in containable behaviour. This might be better also because Behaviours will be applied.
You will have array tree as a result, to dig out the actual ids for next query use following:
$chats = Set::extract("/Message/id",$chats);
c) translate the solution to CakePHP query builder... :) up for a challenge?
I have a User model with two relations:
HasAndBelongsToMany
public $hasAndBelongsToMany = array(
'group' => array(
'className' => 'group',
'foreignKey' => 'user_id'
)
);
HasMany
public $hasMany = array(
'phonenumber' => array(
'className' => 'phonenumber',
'foreignKey' => 'user_id'
)
);
Phonenumber and Group have set
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
When I use
$this->saveAll(array(
'User' => $data,
'phonenumber' => $numbers, //array with data
'group' => $groups //array with data
)
);
The data gets saved in the tabels but User_id is "0" in phonenumber and group table.
How can I get the correct ID saved ? (CakePHP v 2.5)
FWIW saveAll() should work as advertised, populating the new user_id in the child tables in one fell swoop.
Have you paid attention to the relevant options: atomic & deep?
Especially if database does not support transactions, you'll need to pass in atomic:
$this->saveAll(array(
'User' => $data,
'phonenumber' => $numbers, //array with data
'group' => $groups //array with data
),
array('atomic' => false)
);
Considering the CakePHP documentation you will find this hint:
When working with associated models, it is important to realize that
saving model data should always be done by the corresponding CakePHP
model. If you are saving a new Post and its associated Comments, then
you would use both Post and Comment models during the save operation
(http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-hasone-hasmany-belongsto)
Based on that information I suggest you try the following:
$this->User->save($data); // first save the user
Assuming you have multiple numbers:
foreach($numbers as $key => $nbr) {
// set user_id related data
$numbers[ $key ]['Phonenumber']['user_id'] = $this->User->id;
}
Finally save your related data:
$this->User->Phonenumber->saveAll($numbers);
Since this code is untested you may need to take some adjustments. Always ensure to follow the Cake-Conventions and use CamelCase ModelNames.
I have few tables which are connected (hasMany) like a tree: 1 -> 2 -> 3.
There are few records in table 1, table 2 and 3.
I'm using CakePHP to fetch all data from table 1 with connected table 2, which is connected with table 3.
However few records in table 1 don't have any connected records in table 2. The same is in table 2, some records don't have connected records in table 3.
For the second situation scripts work fine. I get something like this:
1 -> 2 -> empty. But in the first situation, when data looks similar to: 1-> empty -> empty I get errors that table 3 doesn't exist.
Is there any solution to skip this errors and get pretty formatted association table as return to my query?
$options = array(
'conditions' => array(
'Table1.id' => $table1_ids
),
'contain' => array(
'Table2' => array(
'conditions' => array(
'id' => $table2_ids
),
'Table3' => array(
'conditions' => array(
'date_end >' => date('Y-m-d H:i:s')
),
'fields' => array('id'),
),
'fields' => array()
),
),
'fields' => array('id', 'name')
);
$this->Table1->recursive = -1;
$table1 = $this->Table1->find('all', $options);
It's not really cakephp to blame here but your lack of understanding what contain does.
What you're looking for are left joins, which is actually hinted in the documentation of contain.
I am trying to save an array like this
$myData = array(
'User' => array('id' => 17),
'Group' => array(
array('group_id' => 2),
array('group_id' => 3),
array('group_id' => 4),
array('group_id' => 5),
array('group_id' => 6)
)
);
In my HABTM join table (groups_users). I tried the following save calls, but none of them worked.
$this->User->save($myData);
$this->User->saveAssociated($myData);
$this->User->saveAll($myData);
$this->User->GroupsUsers->save($myData);
$this->User->GroupsUsers->saveAll($myData);
Before you ask: Yes, my associations are set-up correctly and I was able to save data by calling:
$this->User->GroupsUsers->saveAll(array(
0 => array(
'GroupsUsers' => array('user_id' => $id, 'group_id' => 1)
),
1 => array(
'GroupsUsers' => array('user_id' => $id, 'group_id' => 2)
)
));
BUT only one of both records are saved, although I set unique to false in the model's HABTM relationship definition.
Where is the error? Is the structure of my array invalid?
The problem is that your data array is incorrectly formatted.
Your Group model will probably have an id field, and your join model GroupsUser will have a group_id field.
So you need to change your group_id to id
try
$myData = array(
'User' => array('id' => 17),
'Group' => array(
'Group' => array(
0 => 2,
1 => 3,
2 => 4,
3 => 5,
4 => 6
)
)
);
Just to answer your question even if it's old.
You doesn't have to call your 'GroupsUsers' model in your saveAll call, this is precisely the goal of associating Models, cakePHP does that for you.
You should pass this array to a saveAll function on your User/Group model:
array{
array{
'User' => array {
'id' => 25
},
'Group' => array {
'id' => 2
}
}
array{
'User' => array {
'id' => 47
},
'Group' => array {
'id' => 2
}
}
}
Both your User and Group Models should have and HABTM relation with each other, so that you just have to do that:
From the User/Group controller:
$this->User/Group->SaveAll($myData);
From the User/Group Model:
$this->SaveAll($myData);
And that's it, the GroupsUsers table will save 2 records, by saving by yourself in the HABTM relation table, you are not using the power of cakePHP.
And the records in both User and Group tables will be created if the ID doesn't exist, updated if not.
The only reason to save in an HABTM table is if you have some extra information you want to save in this table such as a 'validated' fields if you want to validate a member after he asked to join a group for example.
My model relationship is as follows:
Student hasMany ClassStudent
Class hasMany ClassStudent
ClassStudent belongsTo Student
ClassStudent belongsTo Class
ClassStudent is a join model.
What I want to do is create Student(s), use an existing Class or create a new one, and create a join model record that links the Students and classes.
I want to do this all in one call to save (if this is even possible).
I have tried:
$data = array(
'Student' => array(
'0' => array( ... ), // Data in here
'1' => array( ... ),
...,
'n' => array( ... )
),
'Class' => array(
'class_id' => x // The class that I want the above students to be associated with
)
)
What I want to do is create n records of students and also add them to a class (possibly creating a class at the same time if the users wants to add a new one). I also want to create a join model record for each Student to the Class when I am creating the Student records.
Is this possible? I am using Cake 2.1.0 (today's stable release), and I have tried the different types of saveAll (saveAssociated and saveMany) with $options['deep'] = true.
Is it possible my data array is not in the correct format?
EDIT:
I have also tried:
$data = array(
'ClassStudent' => array(
'0' => array(
'Student' => array (...), // Data
'Class' => array(id => x) // The id of the Class the Student should be associated to
...,
'n' => array(
'Student' => array(...), // n-th Student
'Class' => array(id => x)
)
);
$this->saveAll($data['ClassStudent'], array('deep' => true));
In the above case, it successfully creates new Student records in the students table, but nothing is created in the join table.
saveAll (saveAll is a wrapper to saveMany and saveAssociated) is the right tool for the job. Take a look at the documentation for saveAll, I don't see any notes about it changing for 2.1. Having taking more time to read, here are some thoughts
First off, is there a type in your array structure. You have
'Class' => array ( 'class_id' => x )
If the Class is already defined and you are just wanting to add a student, then it would be
'ClassStudent' => array( 'class_id' => x )
With that said, CakesPHP ORM should allow you to use a saveAll with on a hasMany using a numerical index, so if assuming you have a typo, there follow might work for you
$data = array(
'Student' => array(
'0' => array( ... ), // Data in here
'1' => array( ... ),
...,
'n' => array( ... )
),
'ClassStudent' => array(
'class_id' => x // The class that I want the above students to be associated with
)
)
$this->ClassStudent->saveAll($data);