I use cake a lot, and generally it gives me DB data back in a reasonable structure.
However sometimes I would like the data to be returned with the primary key of the DB as the key in the array. Cake insist on a separate primary key so is there any reason why cake can't/won't do the following:
$this->data = array(2 => array('PK' => 2, 'name' => 'Item 2'))
rather than:
$this->data = array(0 => array('PK' => 2, 'name' => 'Item 2'))
?
EDIT: Just to clarify, I know about 'list' but this is no good as I regularly need more than just 1 element in the 'value' bit
You can use something similar to this:
$myModels = $this->MyModel->find('all', [
'fields' => [...],
'joins' => [...],
...
]);
$result = Set::combine($myModels, '{n}.MyModel.id', '{n}.MyModel');
See also here: http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::combine
You can use find('list') query to retrieve your data with primary key as key of array.
It will return data as
[1]=>Item1
[2]=>Item2
.........
........
........
........
[n]=>ItemN
Read manual
You can use find('list') function of the cakephp to get primary key as key element of the array.This url mayhelp you to do it properly.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Related
I am using cakephp 2.5, and need to join Vehicle table with Address table where vei_id is the foreign key
I have one find operation that is generating a wrong condition for the two models: Vehicle and Address.
Address has the vei_id column wich is the foreign key to join the vehicle table.
The query is generating vehicle_id as the column to join the two tables, the probem is that this column does not even exists.
I have mapped the two models using the vei_id column.
How can i avoid this situation ? seems cakephp try to guess the join column even if i have already write the condition using the column i want.
//Vehicle Model
public $hasOne = array(
'Address' => array(
'className' => 'Address',
'conditions' => array('Vehicle.vei_id = Address.vei_id'),
'foreignkey' => false
)
//Address Model
public $belongsTo = array(
'Vehicle' => array(
'className' => 'Vehicle',
'conditions'=> array('Vehicle.vei_id=Address.vei_id'),
'foreignKey' => 'vei_id'
),
);
//At vehiclecontroller
$data = $this->Vehicle->find('first', array(
'conditions' => array('Vehicle.vei_id' => $vehicleId),
'contain' => array(
'Address' => array('conditions'=> array('Address.vei_id'=>'Vehicle.vei_id',
'Vehicle.vei_id' => $vehicleId
),
)),
));
it generates this line :
LEFT JOIN Address ON (
Address.vehicle_id = Vehicle.vei_id
AND Address.vei_id = 'Vehicle.vei_id'
AND Vehicle.vei_id = 123
)
Where this column does not exists :
Address.vehicle_id = Vehicle.vei_id
Your query looks little bit confusing:
Just look at following conditions within contain:
'contain' => array(
'Address' => array('conditions'=>
array(
'Address.vei_id'=>'Vehicle.vei_id', // why is this ?
'Vehicle.vei_id' => $vehicleId
),
));
Why are you using following conditions within contain ?
Address.vei_id'=>'Vehicle.vei_id
Did you do that to join two tables ?
When you use contain these things are done by cakephp's convention.
Address table is already joined with vehicle table.
See here:Cakephp contain.
Also why not to follow cakephp convention?
If you have vehicles table,
the foreign key would be vehicle_id according to cakephp convention.
And if you have users table foreign key would be user_id.
These things also reduces your work and make things easier.
See Here: (cakephp model and database conventions).
I am joining multiple tables like the following.
$recommend_logs = $this->RecommendingProductLog->find('all', array(
'recursive' => 2,
'fields' => array('Product.ProductName', 'Product.Gender', 'Product.Price', 'RecommendingProductLog.preference', 'Brand.BrandName'),
'conditions' => array('RecommendingProductLog.user_id' => $user_id),
'contain' => array('Product', 'Product.Brand')
));
I am getting this query from log.
SQL Query: SELECT `Product`.`ProductName`, `Product`.`Gender`, `Product`.`Price`, `RecommendingProductLog`.`preference`, `Brand`.`BrandName`, `Product`.`id` FROM `database`.`recommending_Product_log` AS `RecommendingProductLog` LEFT JOIN `database`.`Products` AS `Product` ON (`RecommendingProductLog`.`Product_id` = `Product`.`id`) WHERE `RecommendingProductLog`.`user_id` = 32
Even though 'Product' table is a child table of 'Brand' table, somehow I don't see 'Brand' table in the query. That's why I am getting an error 'Unknown column 'Brand.BrandName' in 'field list'.
I specified 'Brand' in the Perfume model as 'belongsTo' and 'Perfume' in RecommendingPerfumeLog model and 'Perfume' as hasMany in Brand model.
can somebody point where the problem is?
thanks.
Have you correctly put the foreign keys on the respective tables of the relation? In this case Product have to have a a key with the name brand_id and it must be a foreign key to the brand id field.
Also check this and try to put your joins.
I don't remenber this 'contain' field from the docs. Are you using the latest version?
I have a database table in which along with the data, provider_id is also unique. Now I have a set of data in which I got provider_id not the row id. So is it possible to edit the row using this provider_id ?
If you don't want to use the primary key, then seems that updateAll() is your friend
http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-updateall-array-fields-array-conditions
Get the row you want to edit by doing something like:
$row = $this->ModelName->findByProviderId($data['provider_id']);
Now modify $row however you like:
$row['some_field'] = 'someValue';
Then you can use Model::save() like:
$this->ModelName->save($row);
As long as $row will have the primaryKey field of your model, which is generally id, Model::save() will perform an update.
Try this:
Let the model be "Contact".
$this->Contact->updateAll(
array( 'Contact.any field' => 'value' ), //fields to update ex. Contact.name
array( 'Contact.provider_id' => $id ) //condition $id is the provider_id
);
Thanks
My model1 hasMany model2, I need to do the condition statement on the model2 so in other words:
$result = $this->Model1->find("all",array("conditions" => array("Model2"."field" => $outsideValue)));
I am however getting a unknown column Model2.field which looks due to Model1 not including Model2 as a join. How do I do cakePHP joins with a hasMany?
You can either use ad-hoc joins or the linkable behavior to get the joins.
Try:
$result= $this->Model1->find('all',array('recursive'=>1,"conditions"
=> array("Model2"."field" => $outsideValue)));
or:
$result= $this->Model1->find('all',array("contains"
=> array("Model2"=>array('conditions'=>array("field" => $outsideValue))));
The first one will give you all model 1 elements which are linked to a Model 2 verifying the condition.
The second one will you all Model 1 elements, with only the associated Model2 verifying the condition.
Try This:
$this->Model1->bindModel(array(
'hasMany' => array(
'Model2' => array('conditions'=>array('Model2.field'=>$outsidevalue))
)));
$data = $this->Model1->find('all');
Most of the CakePHP documentation seems to tell you how to filter based on a concrete relationship result. What I cannot seem to find is how to filter out results which have a relationship that returns no data.
For example, take the typical blog example which has Posts and Tags. Tags has and belongs to many Posts (HABTM). For this discussion, assume the following table structure:
posts ( id, title )
tags ( id, name )
posts_tags ( post_id, tag_id )
How do you find only the Tags which have one or more Posts associated with them (i.e. exclude Tags which would return no Posts)?
The ideal result set would look something like (quotes added for formatting):
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' )
[1] => Array (
[id] => 4
[title] => 'Post4' ) )
)
[1] => Array (
[Tag] => Array (
[id] => 4
[name] => 'Tag5' )
[Post] => Array (
[0] => Array (
[id] => 4
[title] => 'Post4' )
[1] => Array (
[id] => 5
[title] => 'Post5' )
[2] => Array (
[id] => 6
[title] => 'Post6' ) )
) )
The only way I've ever found to do this in a reliable way is to use ad hoc joins. Using these, you can specify an inner join type and get exactly what you want.
The following was tested with Cake 1.3.
To start you probably want to, or already do, have the HABTM relationship defined on the models for all the other circumstances where this normally applies:
class Post extends AppModel {
var $hasAndBelongsToMany = 'Tag';
}
class Tag extends AppModel {
var $hasAndBelongsToMany = 'Post';
}
According to Cake's own documentation:[1]
In CakePHP some associations (belongsTo and hasOne) perform automatic joins to retrieve data, so you can issue queries to retrieve models
based on data in the related one.
But this is not the case with hasMany and hasAndBelongsToMany associations. Here is
where forcing joins comes to the rescue. You only have to define the necessary joins
to combine tables and get the desired results for your query.
Excluding empty HABTM results is one of these times. This same section of the Cake Book explains how to accomplish this, but I didn't find it overly obvious from reading the text that the result achieves this. In the example in the Cake Book, they use the\ join path Book -> BooksTag -> Tags, instead of our Tag -> PostsTag -> Posts. For our example, we'd set it up as follows from in the TagController:
$options['joins'] = array(
array(
'table' => 'posts_tags',
'alias' => 'PostsTag',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'PostsTag.tag_id = Tag.id'
),
array(
'table' => 'posts',
'alias' => 'Post',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Post.id = PostsTag.post_id'
)
);
$tagsWithPosts = $this->Tag->find('all', $options);
Make sure to set the foreignKey to false. This tells Cake that it should not attempt to figure out the join condition and instead use only the condition we supplied.
It's common that this will bring back duplicate rows due to the nature of the joins. To reduce the SQL returned use a DISTINCT on the fields as necessary. If you want all fields as is normally returned by find('all'), this adds the complication that you need to hard code each column. (Sure your table structure shouldn't change that often, but it could happen, or if you may just have a lot of columns). To grab all columns programmatically, add the following before the find method call:
$options['fields'] = array('DISTINCT Tag.'
. implode(', Tag.', array_keys($this->Tag->_schema)));
// **See note
It is important to note that the HABTM relationship runs AFTER the main select. Essentially, Cake gets the list of eligible Tags and then runs another round of SELECT statement(s) to get the associated Posts; you can see this from the SQL dump. The 'joins' we manually setup apply to the first select giving us the desired set of Tags. Then the built-in HABTM will run again to give us ALL associated Posts to those tags. We won't have any tags which have no Posts, our goal, but we may get posts associated with the tag that are not part of any of our initial 'conditions', if they were added.
For example, adding the following condition:
$options['conditions'] = 'Post.id = 1';
Will yield the following result:
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' )
[1] => Array (
[id] => 4
[title] => 'Post4' ) )
)
)
Based on the sample data in the question, only Tag1 was associated with our 'conditions' statement. So this was the only result returned by the 'joins'. However, since the HABTM ran after this, it grabbed all Posts (Post1 and Post4) that were associated with Tag1.
This method of using explicit joins to get the desired initial data set is also discussed in Quick Tip - Doing Ad-hoc Joins in Model::find(). This article also shows how to generalize the technique and add it to the AppModel extending find().
If we really only wanted to see Post1 as well, we would need to add a 'contain'[2] option clause:
$this->Tag->Behaviors->attach('Containable');
$options['contain'] = 'Post.id = 1';
Giving the result:
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' ) )
)
)
Instead of using Containable you could use bindModel to redefine the HABTM relationship with this instance of find(). In the bindModel you would add the desired Post condition:
$this->Tag->bindModel(array(
'hasAndBelongsToMany' => array(
'Post' => array('conditions' => 'Post.id = 1'))
)
);
I feel that for beginners trying to wrap their head around the automagic abilities of cake, making the explicit joins is easier to see and understand (I know it was for me). Another valid and arguably more 'Cake' way to do this would be to use unbindModel and bindModel exclusively. Teknoid over at http://nuts-and-bolts-of-cakephp.com has a good write up on how to do this: http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp/. Additionally, teknoid made this into a Behavior which you can grab from github: http://nuts-and-bolts-of-cakephp.com/2009/09/26/habtamable-behavior/
** This will pull the columns in the order defined in the database. So if the primary key is not defined first it may not apply DISTINCT as expected. You may need to modify this to use array_diff_key to filter out the primary key from $this->Model->primaryKey.