CakePHP HABTM Filtering - php

I've got two tables - users and servers, and for the HABTM relationship, users_servers. Users HABTM servers and vice versa.
I'm trying to find a way for Cake to select the servers that a user is assigned to.
I'm trying things like $this->User->Server->find('all'); which just returns all the servers, regardless of whether they belong to the user.
$this->User->Server->find('all', array('conditions' => array('Server.user_id' => 1))) just gives an unknown column SQL error.
I'm sure I'm missing something obvious but just need someone to point me in the right direction.
Thanks!

Your table names are right. There are many ways to do this:
Use the Containable behavior
In your AppModel, set the following:
var $recursive = -1;
var $actsAs = array('Containable');
Then, use the following code to query your servers:
$userWithServers = $this->User->find('all', array(
'conditions' => array('User.id' => 1),
'contain' => array('Server')
));
Note that we are querying the User model, instead of the Server model to accomplish this.
Use bindModel
$this->Server->bindModel(array('hasOne' => array('UsersServer')));
$this->Server->find('all', array(
'fields' => array('Server.*'),
'conditions' => array('Server.user_id' => 1)
));
I personally don't recommend using bindModel a lot. Eventually, your code becomes a bit unmanagable. You should try using the Containable behavior whenever possible. The code looks cleaner and simpler. More on the bindModel method can be found here.
HTH.

I think you're supposed to name tour table user_servers.

Related

cakephp 3 query is not executing

I am new in cakephp3. I am trying to execute below query but its showing me error.
$lifeinsurances = TableRegistry::get('LifeInsurances')->find("all");
$life_insurances = $lifeinsurances->find()
->join([
'table' => 'institutions',
'alias' => 'institutions',
'type' => 'LEFT',
'conditions' => 'institutions.id = LifeInsurances.institute_id',
]);
I have fixed previous query. Now I am getting only one table data.
EDIT
Now I created association using cake bake. But a new error showing this time. Below is my code.
public function index() {
$this->paginate = [
'contain' => ['Institutions']
];
$lifeInsurances = $this->paginate($this->LifeInsurances);
$this->set(compact('lifeInsurances'));
$this->set('_serialize', ['lifeInsurances']);
}
Internal server error
if I remove
$this->paginate = [
'contain' => ['Institutions']
];
$lifeInsurances = $this->paginate($this->LifeInsurances);
error stop showing
If you have your table associations set up correctly (which they should be automatically if you used bake to create your code), you should be able to simply say:
$lifeinsurances = TableRegistry::get('LifeInsurances')
->find('all')
->contain(['Institutions']);
If you want contain to work , you need to define associations in your respective Models (lies in Table Folder in case of cakephp 3.x).
Since you are saying that you have baked the models, Ensure that relationships are defined in the respective models.
That may be the reason that it is throwing error.
Normally when you have created all the tables in your database , then you should bake the models. Because adding table after you have baked the models do not define relationships in the new models and you have to explicitly define it.
Have a look at this -
How associations get defined when code is baked in Cakephp
Also check the naming conventions of the foreign keys defined in the tables. Cakephp use this naming conventions to define the relationships between models.
Furthermore it would be great if you can post the error log, so as to find out more exact solution to your problem.

Yii 'limit' on related model's scope

I have a model called Guesses that has_many Comments. I'm making eager queries to this to then pass on as JSON as response to an API call.
The relations are obviously set between the two models and they are correct(one2many <=> belongs2)
I added a scope to Comments called 'api' like this:
public function scopes()
{
return array(
'api' => array(
'select' => 'id, comment, date',
'limit'=>3,
'order'=>'date DESC',
'together'=>true,
),
);
}
And I'm running the following one-liner query:
$data = Guesses::model()->with('comments:api')->findAll();
The issue here is that when calling the 'api' scope using a with('relation'), the limit property simply doesn't apply.
I added the 'together'=>true there for another type of scope, plus I hear it might help. It doesn't make a difference.
I don't need all the comments of all Guesses. I want the top 3 (or 5). I am also trying to keep the one-liner call intact and simple, manage everything through scopes, relations and parameterized functions so that the API call itself is clean and simple.
Any advice?
There are several methods to achieve this, which i know.
1st. You can add limit to relation, for ex.
'photos' => array(self::HAS_MANY, 'Photo', 'companyId',
'limit'=>3
)
2nd. You can call limit results when call relation, for ex.
$model->photos(array('limit' => 4));
Maybe there are some other methods, but i don't know them.
According to the documentation, that should be ok..
My only suggestions would be:
$data = Guesses::model()->with(
array('comments'=>array('scopes'=>array('api')))
)->findAll();
or:
$data = Guesses::model()->findAll(array(
'with'=>array('comments'=>array('scopes'=>array('api')))
));
but that should be the same logic. I don't think together will help you.. it is true by default.
Update
These examples are taken straight from the relational documentation
$posts=Post::model()->with('comments:recently:approved')->findAll();
or since 1.1.7:
$posts=Post::model()->with(array(
'comments'=>array(
'scopes'=>array('recently','approved')
),
))->findAll();
or since 1.1.7
$posts=Post::model()->findAll(array(
'with'=>array(
'comments'=>array(
'scopes'=>array('recently','approved')
),
),
));
and lazy-loading:
$approvedComments = $post->comments('comments:approved');
Which suggests that your code should work.. what query is being run in your trace?
Do you have any default scopes that you haven't posted? Are their any extra options defined in your relation? I have had trouble with both of these interfering with named scopes.

cakephp - how to get the data from a model->model->model type relationship

I've got three models, Equipment which hasmany Booking, which in turn hasmany PaypalTransaction, and I'm trying to find PaypalTransactions which belong to the booking which belongs to the particular listing I'm dealing with, and which was created less than 1 day ago. All this in the Listings Controller
So in my Listings controller I have
$oneDayAgo = date('Y-m-d H:i:s', strtotime('-1 day'));
$toBeConfdBookings = $this->Equipment->Booking->PaypalTransaction->find('all', array('conditions' => array('PaypalTransaction.created' > $oneDayAgo)));
All the models have the appropriate relationships in them as created by cake bake but what ends up in $toBeConfdBookings is all wrong.
Anyone tell me what I'm doing wrong?
Simple answer:
You cannot run a find() on models three-deep like that. Instead, try just loading the model, then running the find():
$this->loadModel('PaypalTransaction');
$oneDayAgo = date('Y-m-d H:i:s', strtotime('-1 day'));
$toBeConfdBookings = $this->PaypalTransaction->find('all', array('conditions' => array('PaypalTransaction.created' > $oneDayAgo)));
(You can only run find()s on loaded models or models that are directly related to a loaded model.)
Answer for how I first interpreted your question:
Normally when you want to pull related results, it's VERY simple - just use CakePHP's Containable Behavior.
What you're trying to do, however, is to get the related model data AND limit the results based on a related model. So, because Containable creates separate queries, you cannot limit based on related models - in that case, you'll need to utilize joins.
Code Example:
$oneDayAgo = date('Y-m-d H:i:s', strtotime('-1 day'));
$this->loadModel('Booking'); // if neccessary
$paypalTransactions = $this->Booking->find('all', array(
'conditions' => array(
'Booking.equipment_id' => $equipmentId
),
'contain' => array(
'Equipment'
),
'joins' => array(
array('table' => 'paypal_transactions',
'alias' => 'PaypalTransaction',
'type' => 'INNER',
'conditions' => array(
'PaypalTransaction.booking_id = Booking.id',
"PaypalTransaction.created > '".$oneDayAgo."'"
)
)
));
The above code basically reads:
The find: find all Bookings that are owned by $equipmentId
The contain: also retrieve the data for the associated Equipment (optional)
The join: limit the results of the Bookings and Paypal Transactions to only those where the transaction occurred after one day ago (and retrieve the transaction data too)
The way you have it now you are basically just calling the PaypalTransaction model and request its find to return you all transactions in the last day.
$this->FirstModel->SecondModel->ThirdModel doesn't mean to connect results from the first model to the second and then to the third, it's only a way to help you load the linked model instead of using $this->loadModel('ThirdModel') to load it.
So you still need to place conditions according to what you want to do. For example, if it was just one model linked (assuming $this->Booking is set to a record) you could do:
$this->Booking->PaypalTransaction->find('all', array(
'conditions' => array(
'PaypalTransaction.created >' => $oneDayAgo,
'PaypalTransaction.booking_id => $this->Booking->id
)
);
You can't extend this to 2 extra models unless your third model contains keys for both previous models which I doubt it does (and probably shouldn't).
CakePHP will autobind models in find conditions for belongsTo and hasOne associations, and for the rest 2 types you need to be joining your tables.
It is a little bit of extra work but you get used to it quickly.
When dealing with deeper associations i think you should look at this link. You can retrieve data from tables by bindModel.
http://mark-story.com/posts/view/using-bindmodel-to-get-to-deep-relations

adding hasMany association causes find() to not work well

OK, I am a little bit lost...
I am pretty new to PHP, and I am trying to use CakePHP for my web-site.
My DB is composed of two tables:
users with user_id, name columns
copies with copy_id, copy_name, user_id (as foreign key to users) columns.
and I have the matching CakePHP elements:
User and Copy as a model
UserController as controller
I don't use a view since I just send the json from the controller.
I have added hasMany relation between the user model and the copy model see below.
var $hasMany = array(
'Copy' => array(
'className' => 'Friendship',
'foreignKey' => 'user_id'
)
);
Without the association every find() query on the users table works well, but after adding the hasMany to the model, the same find() queries on the users stop working (print_r doesn't show anything), and every find() query I am applying on the Copy model
$copy = $this->User->Copy->find('all', array(
'condition' => array('Copy.user_id' => '2')
));
ignores the condition part and just return the whole data base.
How can I debug the code execution? When I add debug($var) nothing happens.
I'm not an expert, but you can start with the following tips:
Try to follow the CakePHP database naming conventions. You don't have to, but it's so much easier to let the automagic happen... Change the primary keys in your tabel to 'id', e.g. users.user_is --> users.id, copies.copy_id -->copies.id.
Define a view, just for the sake of debugging. Pass whatever info from model to view with $this->set('users', $users); and display that in a <pre></pre> block
If this is your first php and/or CakePHP attempt, make sure you do at least the blog tutorial
Make CakePHP generate (bake) a working set of model/view/controllers for users and copies and examine the resulting code
There's good documentation about find: the multifunctional workhorseof all model data-retrieval functions
I think the main problem is this:
'condition' => array('Copy.user_id' => '2')
It should be "conditions".
Also, stick to the naming conventions. Thankfully Cake lets you override pretty much all its assumed names, but it's easier to just do what they expect by default.
The primary keys should be all named id
The controller should be pluralised: UsersController
First off, try as much as possible to follow CakePHP convention.
var $hasMany = array(
'Copy' => array(
'className' => 'Friendship',
'foreignKey' => 'user_id'
)
);
Your association name is 'Copy' which is a different table and model then on your classname, you have 'Friendship'.
Why not
var $hasMany = array(
'Copy' => array('className'=>'Copy')
);
or
var $hasMany = array(
'Friendship' => array('className'=>'Friendship')
);
or
var $hasMany = array(
'Copy' => array('className'=>'Copy'),
'Friendship' => array('className'=>'Friendship')
);
Also, check typo errors like conditions instead of condition
Your table name might be the problem too. I had a table named "Class" and that gave cake fits. I changed it to something like Myclass and it worked. Class was a reserved word and Copy might be one too.

How to limit the the number of row retrieved from an associated table in CakePHP?

Assuming two database tables: Funds and Prices, in which Funds hasMany Prices.
What I wanted to do is to retrieve the latest 15 prices of a particular fund in a certain scenario. Is there a means in CakePHP to make a $this->Fund->find('all') call that would allow me to limit the number of rows to be retrieved from the associated Price table?
Note that I prefer not setting the 'limit' option in the Fund model's $hasMany variable.
Note on accepted answer [Nov 2]:
In Jason's answer which I had accepted, I personally opt for the bindModel solution as I felt despite feeling a bit “hack-y”, it bodes much better with me as to make a one-off override on the default Model bindings.
The code I used is as follows:
$this->Fund->bindModel(array(
'hasMany' => array(
'Price' => array(
'limit' => 15,
'order' => 'Price.date DESC'
)
)
);
No unbindModel is necessary. More information could be read from “3.7.6.6 Creating and Destroying Associations on the Fly” in the CakePHP manual.
You can use the Containable behavior to accomplish this easily.
in your AppModel or Fund model add :
var $actsAs = array('Containable');
then in your controller you can add the 'contain' option to your find('all') :
$this->Fund->find('all', array(
'contain' => array(
'Price' => array(
'limit' => 15,
'order' => 'Price.date DESC')
)));
More information is available in the book : http://book.cakephp.org/view/474/Containable
As I understand the question, you don't want to set the limit statically in the model. If you wish to use a hasMany association, there really isn't any other way that I'm aware of other than changing that limit one way or another.
Here are a few ways to dynamically change it:
You could change the limit with a call to Funds->bindModel(). (This may require an unbindModel() first, but I can't remember for certain)
You could add a function to Funds to change the limit directly. (See example below.)
If you're feeling ambitious, you could write a Behavior to add the functionality to specify it per find() call similarly to the Containable behavior.
Example for #2
<?php
function setPriceLimit($limit = 10) {
// Assuming the association is 'Price'
$this->hasMany['Price']['limit'] = $limit;
}
?>
Your $this->Model->find('all') could include conditions. ie.
$this->Model->find('all', array('conditions' => array('Model.field' => $value), 'limit' => 15));
For more info check the cake docs.
Retrieving Your Data

Categories