CakePhp: Find using Contain with condition - cakephp

I have the following models:
Company(id, name)
Employee(id, name, company_id, isRemoved) [Company has many Employees]
In the association specified, the employee has a default condition, that
public $hasMany = array(
'Employee' => array(
'className' => 'Employee',
'foreignKey' => 'company_id',
'dependent' => true,
'conditions' => array(
'Employee.isRemoved' => 0
),
)
);
The association has a default condition of an employee being not removed. I am using the following Find Query on company to get only those employees whose name matches a string:
$this->Company->find('all', array(
'contain' => array(
'Employee' => array(
'conditions' => array(
'Employee.name LIKE' => '%'.$search_text.'%')
),
'fields' => array('Employee.id, Employee.name')
)
)
));
The problem I am facing is that, When I use conditions within contain, the default condition specified in the association is not applied and when the conditions key is not specified, the default condition specified in the association is applied.
Is this a default behaviour of Cakephp and How to proceed about it? I am using Cakephp 2.8.4

I can not tell you if the conditions in the model being overwritten is default behaviour of CakePHP. I can however offer you a possible alternative:
By using the beforeFind() callback in your model you could add your 'Employee.isRemoved' => 0 condition.
So in your Company model you could do something like:
function beforeFind(array $queryData) {
if(isset($queryData['contain']['Employee'])) {
//Notice the extra [] to not overwrite the conditions set in the controller
$queryData['contain']['Employee']['conditions'][]['Employee.isRemoved'] = 0;
}
return $queryData;
}
Disclaimer: I did not test this code.
Source: https://stackoverflow.com/a/17544106/6786476

Related

An instance of a model cannot be created once relation has been added CakePHP

I have three models in question: Customer, Company and User. Customer and User both belong to Company and Company has many Customers as following:
Customer:
var $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id',
'dependent' => false,
),
);
Company:
var $hasMany = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'company_id',
'dependent' => false
),
'Customer'=>array(
'className' => 'Customer',
'foreignKey' => 'company_id',
'dependent' => false
)
);
User:
var $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id',
'dependent' => false,
),
);
I have a problem when creating/editing Customer objects. Here is how to create form looks like:
echo $this->Form->input('Customer.customer_nr');
echo $this->Form->input('Customer.name');
echo $this->Form->input('Customer.phone');
echo $this->Form->input('Customer.email');
echo $this->Form->input('Customer.address');
echo $this->Form->input('Customer.post_nr');
echo $this->Form->input('Customer.city');
echo $this->Form->input('Customer.company_id', array('value' => $current_user['company_id'], 'type'=>'hidden'));
What I do in the end of the form is I take company_id from a currently logged in user and insert it as a Customer.company_id. It used to work without any problems before the new relations have been introduced. But now as I try to create/edit Customer, I receive the following SQL error:
Error: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'name' in where clause is ambiguous
Any help is much much appreciated.
Here is the controller add function:
function add() {
if (!empty($this->data) ) {
$this->Customer->create();
if ($this->Customer->save($this->data)) {
$this->Session->setFlash(__('Customer was saved'), 'positive_notification');
$this->redirect(array('controller'=>'events', 'action' => 'dashboard'));
} else {
$this->Session->setFlash(__('Customer has been saved. Please, try again'), 'negative_notification');
}
}
}
The error is definately not being cause by redirect as it was fully tested.
the problem is somewhere else.
It's in fact related to a find() call.
Try to locate the exact code that trigger the error and post it in your question.
Probably you set some conditions like
'conditions' => array(
'name' => 'john'
)
but you better do something like
'conditions' => array(
'User.name' => 'john'
)
after you created the relationship between User and Company (it's just an example, maybe the two tabler involved are others) Cake started to join the two tables. So when you search for a particular name mysql doesn't know if you want user name or company name because you have name column in both tables.
If you look at the generated query (the one that gives you that error) you'll see the two tables joined. If you don't want that join you have to specify recursive => -1
'conditions' => array(
'name' => 'john'
),
'recursive' => -1

Associate different column name to one Model + cakephp

how to get name of (UserTransactionType.name) with Transaction.who_pay_fee_1,2,3 fields.
'user_transaction_type_id' works well but how to get the rest of fields work :(
//Transaction Model
public $belongsTo = array(
'UserTransactionType' => array(
'className' => 'UserTransactionType',
'foreignKey' => 'user_transaction_type_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
//UserTransactionType Model
public $hasMany = array(
'Transaction' => array(
'className' => 'Transaction',
'foreignKey' => 'user_transaction_type_id',
'dependent' => false,
))
This is the sample code for your controller:
$this->UserTransactionType->find('all',array(
'fields' => array('name'),
'contain' => array('Transaction')
)
);
If Models are associated you can specify in 'contain' which of them you want to get in the result.
If you want to have only some fields of related Model, you can determine them after 'Transaction' in 'contain' just like in the regular find() query:
'contain' => array('Transaction' => array('fields' => array('field_1',
'field_2') ))
But in your case, you don't need to specify fields, because by default you get all fields.
So no matter if you define or not fields "who_pay_fee_1,2,3" because if you use 'contain' by default you will get foreing_key - user_transaction_type_id.
I hope it's helpful
For people they like CakePhp :)
in Controller ->
get the list of 'UserTransactionType'
in View ->
after looping trough all the transactions; in Transaction Status column simply load the 'UserTransactionType'array and assign the number of array to $userTransactionTypes.
$userTransactionTypes[$transaction['Who_pay_fee_1']];
To be honest it was straight forward but needed a bit concentration :)

Containable behavior doesn't return deeper model association and selecting fields

I want to limit the fields returned by a deeper association using containable.
My associations:
Game hasMany Review
The paginate and containable code:
$this->paginate = array(
'conditions' => $conditions,
'fields' => array(
'Game.id', 'Game.name',
'Publisher.id', 'Publisher.name'
),
'contain' => array(
'Game' => array(
'Review' => array(
'fields' => array('Review.id', 'ROUND(AVG(Review.score),1)')
)
),
)
);
$games = $this->paginate('Game');
Currently, all of the fields in the Review table are returned. 'ROUND(AVG(Review.score),1)' is never returned. How can I specify what fields I want returned from the Review association?
SQL dumps for two search results using #theJetzah's answer. The first is a search with one game as a result and the second is a search returning three games.
SELECT `Review`.`id`, `Review`.`review_text`, `Review`.`score`, `Review`.`user_id`, `Review`.`game_id`, `Review`.`created`, `Review`.`platform_id`, (ROUND(AVG(`Review`.`score`),1)) AS `Review__average_score` FROM `videogamedb`.`reviews` AS `Review` WHERE `Review`.`game_id` = (55)
SELECT `Review`.`id`, `Review`.`review_text`, `Review`.`score`, `Review`.`user_id`, `Review`.`game_id`, `Review`.`created`, `Review`.`platform_id`, (ROUND(AVG(`Review`.`score`),1)) AS `Review__average_score` FROM `videogamedb`.`reviews` AS `Review` WHERE `Review`.`game_id` IN (55, 56, 57)
Not a full answer, but an attempt to get it working :)
Approach1 (UPDATE: Containable doesn't support 'group by')
First of all, try to add the 'Game' model to the $uses array of your Controller, if it is not included yet, and re-organise the pagination array (as previously suggested by Sam), so that you'll be pagination the Game model itself.
Then, It may help to create a virtual field for the calculated score, but the results of 'Review' need to be grouped, otherwise you'll not be able to calculate the average score.
I'm not able to test this, but it may worth trying
something like this;
public $uses = array(
'Game',
// other models
);
public function myfunction()
{
$this->Game->Review->virtualFields['average_score'] = 'ROUND(AVG(Review.score),1)';
$this->paginate = array(
'Game' => array(
'fields' => array(
'Game.id',
'Game.name',
'Publisher.id',
'Publisher.name'
),
'contain' => array(
'Review' => array(
'fields' => array(
'Review.game_id,
'Review.average_score',
),
'group' => array(
'Review.game_id,
),
)
)
)
);
// Conditions can be passed to paginate,
// that way you can specify 'paginate' at
// one place and don't have to modify it
// to include the conditions
$games = $this->paginate('Game', $conditions);
}
Alternative approach: Using joins and a database-view
Apparently, the Containable behavior doesn't like group-by clauses; See this ticket for more information: Containable behavior does not implement 'group' option
CakePHP allows you to manually specify a join: Joining Tables
To simplify things and to prevent having to add a 'group by' for all fields, create a simple database-view in your database;
CREATE VIEW review_scores AS
SELECT
game_id,
ROUND(AVG(score),1) AS average_score,
COUNT(id) AS total_reviews
FROM
reviews
GROUP BY
game_id;
If you're unfamiliar with this; a database 'view' is basically a 'stored query', which can be accessed as if it was a regular table. See Create View
Then, use a 'manual' join, using the newly created database-view as the source-table. In your case, this will look something like this;
$this->paginate = array(
'Game' => array(
'fields' => array(
'Game.id',
'Game.name',
'Publisher.id',
'Publisher.name',
'ReviewScore.average_score',
'ReviewScore.total_reviews',
),
'joins' => array(
array(
'table' => 'review_scores',
'alias' => 'ReviewScore',
'type' => 'LEFT',
'conditions' => array(
'ReviewScores.game_id = Game.id',
)
)
)
)
);
Hope this helps
I think your array is a configured a little wrong, try:
$this->paginate = array(
'Game' => array(
'conditions' => $conditions,
'fields' => array(
'Game.id', 'Game.name',
'Publisher.id', 'Publisher.name'
),
'contain' => array(
'Review' => array(
'fields' => array('Review.id', 'ROUND(AVG(Review.score),1)')
)
)
)
);
$games = $this->paginate('Game');
As an aside, from personal experience, specifying the fields in a query doesn't always speed it up (certainly for small number of fields), assuming this is the motive for doing so. It does reduce memory occupancy but this is only relative to original size of the record and the number of records returned.

AND / OR conditions cakephp FIND for has many association

I have a model Post that has many association with the model Comment.
Post has a primary key post_id which is Comment s foreign key.
Both of these have a visible column.
I have a working query on Post.visible options, and I need to add the AND to find all Posts that have one of Post.visible values.
For these posts I need all Comments that have a Comment.visible value = 1.
My code:
$conditions = array(
"OR" => array(
"Post.visible" => array(
1,
2,
3,
4
),
),
"AND" => array (
"Comment.visible" => 1
)
);
$result = $this->Post->find('all', array(
'order' => 'Post.created DESC',
'conditions' => $conditions
));
The result without the AND is OK (but I get also the Comments with visible = 0).
When I put the condition "Comment.visible" => 1 in the has manyassociation, it works well (but I can not do this, because I need to get the Comment with visibility 0 elsewhere).
With the and it shows this Error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Comment.visible' in 'where clause'
When I dump the SQL, the comments table is not even matched in the SELECT clause (nor in the LEFT JOIN).
You can limit another model's results using CakePHP's Containable Behavior with something like this (this should work, but feel free to tweak per your needs):
//Post model
public $recursive = -1;
public $actsAs = array('Containable');
public function getPosts() {
$posts = $this->find('all',
array(
'conditions' => array(
'Post.visible' => 1
),
'contain' => array(
'Comment' => array(
'conditions' => array('Comment.visible' => 1)
)
)
)
);
return $posts;
}
Or, you can set up your association to only ever pull comments that are visible (even WITH this way, I still recommend using 'contain' like above - you just wouldn't need to specify the condition each time):
//Post model
public $hasMany = array(
'Comment' => array(
'conditions' => array('Comment.visible' => 1)
)
);

CakePHP Model Relationship with Multiple Foreign Keys

In my CakePHP app I have models for Matches and Teams. Each Match has a home_team_id and an away_team_id, both of which reference a different Team.
In my team.php file, I am able to form the relationship for a Team's home matches:
var $hasMany = array(
'HomeMatch' => array('className' => 'Match', 'foreignKey' => 'home_team_id'),
'AwayMatch' => array('className' => 'Match', 'foreignKey' => 'away_team_id')
);
My problem is that I cannot automatically retrieve a Team's home and away Matches in a single array. That is, the retrieved Matches are returned in separate HomeMatch and AwayMatch arrays, which causes sorting difficulties.
I have tried the following:
var $hasMany = array(
'Match' => array('foreignKey' => array('home_team_id', 'away_team_id'))
);
...with no luck.
Any ideas on how to combine these two foreign keys into a single relationship?
Thanks, Ben
A custom finderQuery should do the trick:
public $hasMany = array(
'Match' => array(
'className' => 'Match',
'foreignKey' => false,
'finderQuery' => 'SELECT *
FROM `matches` as `Match`
WHERE `Match`.`home_team_id` = {$__cakeID__$}
OR `Match`.`away_team_id` = {$__cakeID__$}'
)
);
I was having a similar issue and instead of creating a finderQuery I used the conditions operator and it worked great!
public $hasMany = array(
'Match' => array(
'className' => 'Match',
'foreignKey' => false,
'conditions' => array(
'OR' => array(
array('Match.home_team_id' => '{$__cakeID__$}'),
array('Match.away_team_id' => '{$__cakeID__$}')
)
),
)
);
They are returned in seperate array's because the sort of represent different models (in this particular case the model is the same).
You should probably build a helper method to go over the retrieved data (in the model object or in a separate helper class) and "flatten" it. then you'd be able to sort it.
Ken.

Resources