CakePHP: How to specify models while retrieving data? - php

I want to retrive data with recursive level 3. The problem is that It adds all 8 linked models but I need data from only three data models. Is there any way to ignore some models or specifically asked some models but not all. something like useModel('Model1', 'Model2')?

It's better to use the Containable behavior, which will allow you to specify find conditions like this:
$this->Post->find('all', array(
'contain' => array(
'Tag',
'Comment' => array(
'User')
)
)
);
Also, in conjunction with this, it's good to set $recursive to -1 in your AppModel.
class AppModel extends Model {
var $recursive = -1;
var $actsAs = array('Containable');
}
This will give you the finer control you need and ensure that your queries don't bloat as more relationships get added to your models over time.

Related

Cakephp - Multiple Models for one Table

I have a User model that is used to store data on users of a dental examination system.
Typically, three types of users will exist: Admininistrator, Location Manager and Examiner.
It seems to have become necessary to treat these three roles as seperate models in my application (imagine how I'd have a different view for each role with different options etc... It's a nightmare).
How would I go about setting up the relationships in each Model.
My first thought is:
//Administrator Model
class Administrator extends User {
$name = 'Administrator';
$table = 'User';
$belongsTo = array(
'User' => array(
'className' => 'User',
'conditions' => array('User.role' => 'administrator'),
)
);
}
And then the User model will reference this one using a hasMany? In terms of CakePHP convention, how would one actually model this accurately?
Also, would this model extend the User model or the AppModel?
Of course you can create different models using the same table name. To do so, link each model with specific table with $useTable property (not $table):
class Administrator extends AppModel {
public $useTable = 'User';
}
But I don't know any CakePHP model property which will allow you to filter data returned when fetching results... You can only write your own conditions when linking model with another one, for example:
class SomeOtherModel extends AppModel {
public $hasMany = array(
'Administrator' => array(
'className' => 'Administrator',
'conditions' => array('Administrator.role' => 'administrator')
)
);
}
Which is not a good solution, because it will work only when executing queries for SomeOtherModel model.
You can also try applying an afterFind() callback to your Administrator / Examiner / ... models which will delete users of another role than needed, but of course it is not an efficient solution.
Another solution is just creating different database views which will contain only users of selected role (more on views - here). Then you can point each view to your CakePHP model, just as it was a normal database table.
But this solution is also not perfect. If you will have to add a new user role or change your table schema in the future, you will also have to modify your database views.

CakePHP HABTM Filtering

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.

CakePHP: Containable behaviour doesn't work with find() even if contain() is called in beforeFind

My Problem: Linked to my Employees table I've got an Address table containing a virtual field called full_name (I guess you can imagine by yourself what it does). I added the Containable Behaviour and this function
function beforeFind() {
$this->contain('Address.full_name');
}
to my Employees model, so that I don't have to call $this->contain(..) in every single controller action (I need the full_name field in pretty every action). BUT id doesn't work then if the controller action does just a $this->Employee->find('all') (or read(..). Contrary, it works if
The controller action uses $this->paginate(); instead
$this->Employee->contain('Address.full_name'); gets called before the $this->Employee->find('all'); call. I can't imagine the cause for this because after this explicit contain(..) call, contain(..) gets called again by the Model callback function beforeFind(), as a debug proofed which I inserted into the cake/libs/models/behaviours/containable.php:contain() function *cough*.
As far as I recall, a contain() statement only works once, for the query operation immediately following it. Subsequent queries will require their own contain() statement e.g.
$this->Employee->contain('Address.full_name');
$this->Employee->find('all'); //first find
// returns all of Employee + Address.full_name
$this->Employee->find('all'); //second find
// returns all of Employee + all of Address + all of any other associated tables.
I don't recommend using contain() in beforeFind() as it is intended to modify specific returns. It soon becomes second nature to use it before each query where you will then have fine control of the data returned.
If you have a widespread requirement for a limited return, you can set that up in the associations on the model.
1) Your Model beforeFind() should accept a param $queryData and return true if the find() should be executed, or false if it should abort beforeFind. Generally the beforeFind($queryData) method will modify the $queryData array and return it.
function beforeFind($queryData) {
// Modify $queryData here if you want
return $queryData
}
2) Trying to maintain a persistant $contain is a bit strange. Containable obviously contains assocations so that extra/additional information is fetched. Your virtual field should be returned in a normal find() operation. If you want to restrict the fields which are returned you should define those either in the Model or in the Model association
var $belongsTo = array(
'Employee' => array(
'className' => 'Employee',
'fields' => array('Employee.id', 'Employee.full_name'),
'conditions' => array()
)
);
Containable will rebind associations quickly for you, but you should instead define default fields/conditions through the Model association ($belongsTo, $hasMany, $hasOne etc)
Another alternative is to actually create Model methods which reflect the data you are trying to fetch, a very basic example:
function activeEmployees() {
$contain = array('Address');
$conditions array('Employee.started' => '2010-09-01');
$this->find('all', array('conditions' => $conditions, 'contain' => $contain));
}
And then call these convienience methods from the Controller just like you would a find
$this->Employee->activeEmployees();
You could also choose to pass a param to Employee::activeEmployees(); which is an array of additional $conditions or $contain options which are merged with the standard options.

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