How to include associations in find() with fieldlist - php

I want to receive model data by find(all), but the user should get only a restricted set of table fields. That's easy:
$ret = $this->find('all',array('fields'=>array(
'Employee.id','Employee.address_id'
)));
But this model (Employees model) also has a belongsTo association:
var $belongsTo = array(
'Address' => array(
'className' => 'Address',
'foreignKey' => 'address_id',
'fields' => array('Address.full_name')
)
);
I want the Address.full_name field to appear in my fetched data too. But it doesn't work with the find() call above, and it throws an error (SQL Error: 1054: Unknown column 'Address.full_name' in 'field list') when trying this:
'fields'=>array('Employee.id','Employee.address_id','Address.full_name')
Anyone knows how to solve this?
EDIT: I totally forgot that Address.full_name is a virtual field. Looking at the Cakephp-produced SQL, it's obvious why it doesn't work:
SELECT
`Employee`.`id`, `Employee`.`address_id`, `Address`.`full_name`
FROM
`employees` AS `Employee`
LEFT JOIN `addresses` AS `Address`
ON (`Employee`.`address_id` = `Address`.`id`)
WHERE 1 = 1
In the address model, full_name is defined like this:
var $virtualFields = array(
'full_name' => 'CONCAT_WS(" ", Address.firstname, Address.surname)'
);
So then, the question is: Is it a CakePHP bug that it's not able to include (foreign model's) virtual fields within a fieldlist supplied to find()?

Unfortunately, you cannot use virtual fields the way you wish to. From Limitations of Virtual Fields in the Cake documentation:
The implementation of virtualFields in 1.3 has a few limitations. First you cannot use virtualFields on associated models for conditions, order, or fields arrays. Doing so will generally result in an SQL error as the fields are not replaced by the ORM. This is because it difficult to estimate the depth at which an associated model might be found.
It looks like you'll have to use the Containable behaviour.

I would use the Containable behavior in this case.
Make sure you have the Containable behavior loaded in your Employee model first:
var $actsAs = array('Containable');
Then, when you're trying to get your data, do it like this:
$params = array('fields' => array('Employee.id', 'Employee.address_id'),
'contain' => array('Address' => array('fields' => array('Address.full_name')));
$ret = $this->find('all', $params);
More on the containable behavior here: http://book.cakephp.org/view/1323/Containable

SQL Error: 1054: Unknown column
'Address.full_name' in 'field list')
This error gives you a clue that something is amiss with either your column name call (could it be fullname rather than full_name) or, more likely your Model definition. Employee belongsTo an Address but does the Address haveOne or haveMany Employees?

Related

Relational Databases in Yii

So I've tried this: http://www.yiiframework.com/wiki/285/accessing-data-in-a-join-table-with-the-related-models
Basically I have a table called User which relates to ToolAccess; related via a primary key on User and a field for userID on ToolAccess. Now tool access relates to the table Tool which contains a ToolID. Now this doesn't work in Yii, I can't seem to get the toolName field off of the tool table using Yii. Any ideas on how to do this on a Active Record?
I'm using giix if that matters.
Relations code:
public function relations() {
return array(
'usergalleries' => array(self::HAS_MANY, 'Usergallery', 'userid'),
'userinroles' => array(self::HAS_MANY, 'Userinroles', 'userid'),
'userfailedlogin' => array(self::HAS_MANY, 'Userfailedlogin','userid'),
// table name, relation, class name, relation key
'toolaccess' =>array(self::HAS_MANY, 'Toolaccess','userid'),
'tool' =>array(self::HAS_MANY, 'Tool','toolid')
);
}
I'm assuming your schema looks something like this:
User table tool_access table Tool table
id | other columns userid | toolid id | name | other columns
In this case, the User model should have a relation like this (note that the tools will be ordered by name in this case):
public function relations() {
return array(
// other relations here...
'tools' =>array(self::MANY_MANY, 'Tool', 'tool_access(userid,toolid)',
'order' => 'tools.name',
),
);
}
and the code to read the tools should look like this:
$user = User::model()->with('tools')->findByPk($id);
foreach($user->tools as $tool) {
echo $tool->name;
}
I used eager loading of the tools here mostly because of personal preference, using lazy loading should work just as well in this case. But eager loading should be preferred whenever you're processing multiple User records at once.
So if I have understood it properly, user and tool are related in a many-to-many relationship by their primary keys.
So you should define this relationship in the User model like:
'tools' => array(self::MANY_MANY, 'Tool', 'tool_access(userid, toolid)', 'index' => 'id'),
This way you can access the name of the tool after getting the user model
$user = User::model->findByPk($id);
$tools = $user->tools;
foreach ($tools as $tool)
{
echo $tool->name;
}
I hope it works for you.

Yii Relations error Trying to get property of non-object

I have Cinema table & City table and I have relation with this tables by id.. and when I echo results i have PHP notice "Trying to get property of non-object"
What is the problem ? or I missed something ??
My Code:
Cinema model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'countryCode' => array(self::BELONGS_TO, 'TblCountry', 'country_code'),
'city' => array(self::BELONGS_TO, 'TblCity', 'city_id'),
'tblCinemaMovies' => array(self::HAS_MANY, 'TblCinemaMovies', 'cinema_id'),
'tblDays' => array(self::HAS_MANY, 'TblDay', 'cinema_id'),
'tblShowtimes' => array(self::HAS_MANY, 'TblShowtime', 'cinema_id'),
);
}
City model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'tblCinemas' => array(self::HAS_MANY, 'TblCinema', 'city_id'),
'countryCode' => array(self::BELONGS_TO, 'TblCountry', 'country_code'),
);
}
View file:
<?php echo $model->city_id->name; ?>
I recommend you to always check if the relation does not return null or an empty array.
The following code works well with HAS_ONE and BELONGS_TO relationship types:
$cinema = Cinema::model()->find(); // get a cinema instance, assuming there is at least one row in the database.
$city = $cinema->city; // get the relation
if ($city !== null) {
// $city is a valid model
} else {
// $city is null, the corresponding row does not exist in the database
}
You may also make this check without using a new variable (in this case, $city):
if ($cinema->city!==null) {
// the relation is available
}
Checking if the relation does not return null is the best way to avoid the PHP error "trying to get property of non-object". Also, if you are stuck with similar errors, it is recommended to use functions like var_dump() or, even better, a debugger.
Also, notice that array keys in the array returned from relations() function is the property that has to be accessed to get the relation model(s):
public function relations() {
return array(
'city' => array( ... ),
// city is the property that has to be accessed
// Yii's conventions recommend to use 'city_id' for the foreign key column name
);
}
Also note that it is good to follow Yii's conventions for naming relationships and columns to avoid using the same name for both a property and a relations - in this case, the relation will be unavailable and probably an error like "trying to access property of a non-object" will pop up when playing with the relation.
The last thing, when working with HAS_MANY or MANY_TO_MANY relationships, the relation returns an array of models even if there is only one model available and an empty array if there is nothing available.
Explanation in the docs:
http://www.yiiframework.com/doc/guide/1.1/en/database.arr#performing-relational-query
<?php echo $model->city->name; ?>
You must use $model->city to get the corresponding city of the $model.
Because the city_id is a array, in your view file, you should write the code like this
<?php echo $model->city_id[0]->name; ?>
That error happens when city_id is emptied or nulled (or the value doesn't exist in the foreign table).
If you're not sure that "city_id" exists, you can check it like this:
CHtml::value($model, 'city.name');
That way you always ensure that you'll not have an Exception when the value is empty or clear

Yii framework: Using data from related Active Record models for searching

Yii 1.1 application development Cookbook explain a method for using data from related Active Record Models for searching the related models as well. This method is explained in page number 193 and 194. i have tried to integrate this method in to my application but it does not work. could anybody explain me whether this feature is still available in Yii framework version 1.1.8
At this location also i could find comments for searching data form related active record models. But it also does not work.
http://www.yiiframework.com/doc/api/1.1/CDbCriteria
I have order table and user table
Order table and User table has One to many Relation.
User has many orders and order has exactly one user.
So , i am editing following CDbCriterial to include user tables name and email field in to Order tables search entries.
Order table has following relations
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'comments' => array(self::HAS_MANY, 'Comment', 'order_id'),
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
'orderstatus' => array(self::BELONGS_TO, 'Orderstatus', 'orderstatus_id'),
'commentCount' => array(self::STAT, 'Comment' , 'order_id')
);
}
This is the search/filter conditions with user table's name filed included
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('order_create_date',$this->order_create_date,true);
$criteria->compare('price',$this->price,true);
$criteria->compare('bank_account_number',$this->bank_account_number,true);
$criteria->compare('hardwaredetail_Id',$this->hardwaredetail_Id);
$criteria->compare('user_id',$this->user_id);
$criteria->compare('order_update_date',$this->order_update_date,true);
$criteria->compare('is_received',$this->is_received);
$criteria->compare('order_received_date',$this->order_received_date,true);
$criteria->compare('is_notify_by_email',$this->is_notify_by_email);
$criteria->compare('warehouse_offered_price',$this->warehouse_offered_price,true);
$criteria->compare('warehouse_offered_price_date',$this->warehouse_offered_price_date,true);
$criteria->compare('orderstatus_id',$this->orderstatus_id);
$criteria->together = true;
$criteria->with = array('user');
$criteria->compare('user.name',$this->user,true);
//$criteria->compare('user.name',$this->user->name);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
and Order admin page is edited to display the name filed as follows
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'order-grid',
'dataProvider'=>$model->search(),
//'filter'=>$model,
'columns'=>array(
'id',
'order_create_date',
'price',
'bank_account_number',
array(
'name'=>'user',
'value'=>'$data->user->name'
),
),
));
Error message returned
After solving the id column ambiguity problem by applying the solution that thaddeusmt gave i have faced with the following error message.
Thanks in advance for any help
I could find the answer. This is all i did.
Following are the two tables i have. Order and User
I have Order/Admin page, Default generated code provide searching order table data only. i want to relate user tables name field in the search criteria.
This is the initial look of the search page.
I want to integrated user name filed from other table in to this search. then final look will be as follows.
so these are the steps i did.
first in the Order model i have following relations
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
);
}
This is generated by Yii framework , i did nothing :)
Then , i changed the search method as follows
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('t.order_create_date',$this->order_create_date,true);
$criteria->compare('t.price',$this->price,true);
$criteria->compare('t.bank_account_number',$this->bank_account_number,true);
$criteria->compare('t.hardwaredetail_Id',$this->hardwaredetail_Id);
//$criteria->compare('user_id',$this->user_id);
$criteria->compare('t.order_update_date',$this->order_update_date,true);
$criteria->compare('t.is_received',$this->is_received);
$criteria->compare('t.order_received_date',$this->order_received_date,true);
$criteria->compare('t.is_notify_by_email',$this->is_notify_by_email);
$criteria->compare('t.warehouse_offered_price',$this->warehouse_offered_price,true);
$criteria->compare('t.warehouse_offered_price_date',$this->warehouse_offered_price_date,true);
$criteria->compare('t.orderstatus_id',$this->orderstatus_id);
$criteria->together = true;
$criteria->compare('t.id',$this->id,true);
$criteria->with = array('user');
$criteria->compare('name',$this->user,true,"OR");
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
it is important to put t in-front of the t if your Order table primary key field if both have save name. in my case it is id and id, so i had to put t.
Other thing is the order of the elements
$criterial->togeter = true; should come before the relational elements.
then u updated to rules method in Order table. i added user filed and name filed to safe attributes.
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
//array(' orderstatus_id', 'required'),
array('hardwaredetail_Id, user_id, is_received, is_notify_by_email, orderstatus_id', 'numerical', 'integerOnly'=>true),
array('price, warehouse_offered_price', 'length', 'max'=>10),
array('bank_account_number', 'length', 'max'=>100),
array('order_create_date, order_update_date, order_received_date, warehouse_offered_price_date, user,name', 'safe'),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, order_create_date, price, bank_account_number, hardwaredetail_Id, user_id, order_update_date, is_received, order_received_date, is_notify_by_email, warehouse_offered_price, warehouse_offered_price_date, orderstatus_id', 'safe', 'on'=>'search'),
);
}
Finally update your UI code.
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'order-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'id',
'order_create_date',
'price',
'bank_account_number',
array(
'name'=>'user',
'value'=>'$data->user->name'
)
)); ?>
i updated the order admin with the
array(
'name'=>'user',
'value'=>'$data->user->name'
)
That is what i did and it worked for me. ask me if you need any help. Thanks every one looking in to this issue.
It looks like both the user and order have columns named id. And your criteria uses them both in the WHERE clause, which is giving the "ambiguous" mySql error message.
When using with criteria (which does a SQL JOIN of the tables), if two of your tables have columns with the same name you need to use the mySql "dot" prefix on conditions, like so:
$criteria->compare('t.id',$this->id); // the default table prefix in Yii is "t"
When I JOIN a table using $criteria->with I prefix all of the column names (in my compare and condition criteria, etc)., like so:
$criteria->compare('t.id',$this->id); // t
$criteria->compare('t.order_create_date',$this->order_create_date,true); // t
$criteria->with = array('user');
$criteria->compare('user.name',$this->user_id,true); // user (the filter will set user_id)
Your gridview will need to look like this:
array(
'name'=>'user_id',
'header'=>'User',
'sortable'=>false,
'value'=>'$data->user->name'
),
Also, I think there is a larger problem, as you point out with your edit:
Your search function is set up like this: user.name',$this->user - but $this->user is going to return the current user object via the relation, not the search criteria. The column filter will set the user_id, property.
EDIT: Nope, you can $this->user as your column name so long as you set it as a safe attribute.
The way I am getting around this is shown in more detail here:
Search in BELONGS_TO model column with CGridView, Yii
Sorting will not work with that though - just the filtering.
Here is good post in the Yii forum that might give you more clues, too:
http://www.yiiframework.com/forum/index.php?/topic/9083-search-filter-of-a-relations-field-through-cgridview/
Sadly, sorting and filter CGridViews on relations is not really default functionality. YOu can easily display related info with a column name like user.name, but it won't sort or filter. Good luck!

Retrieving models without getting associated models - CakePHP

I use the find('all') function to retrieve the post records from my database, but this will also return all the User information that is associated with the Post model with a belongsTo - hasMany relationship.
The downside of this is that the user model contains password and other important information. Is this considered a security issue? I am nowhere echo-ing the information on the view.
Thanks
EDIT:
I modified my code but I am still getting the associated models.
$this->set('posts_list',$this->Post->find('all',array('contain' => false, 'order' => array('Post.price ASC'))));
Any ideas?
You have several options. You can set the recursive property on a model:
$this->Post->recursive = -1;
$posts = $this->Post->find('all');
Alterantively, you can specify recursive as an option to your search:
$posts = $this->Post->find('all', array(
'recursive' => -1,
'conditions' => ...
);
You can also use the Containable behaviour in your Post model. In that case you can specify an empty set:
class Post extends AppModel {
var $actsAs = array('Containable');
}
$this->Post->contain();
$posts = $this->Post->find('all');
Or, specified in the query:
$posts = $this->Post->find('all', array(
'contain' => false,
);
The upside for the Containable behaviour is when you later on associate other models with your post. Suppose that you implement a Tag model. Now you want to find a post with it's tags, but not the use model:
$posts = $this->Post->find('all', array(
'contain' => array('Tag'),
);
Not necessarily.
But you are retrieving information when you don't need it. It's not a problem now, but keep in mind this becomes a huge problem when you have a lot of associated data
Consider setting your recursive attribute to -1 (or 0 if needed)
$this->Model->recursive = -1;
This will pull data only from the selected model
Or for more fine tuned selection, you can use the Containable behavior : http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
This allows you to select which associations to keep when retrieving data.
just so you know
$this->Model->recursive = -1 will remove all associations
$this->Model->recursive = 0 will remove only hasMany assosiation (so it keeps belongsTo)
Do u use this:
$this->Post->find('all')// If u access it from Post controller
OR,
$this->User->Post->find('all')//If u access it from User controller

CakePHP paginate results with a condition on another table linked with HABTM

I've done some searching but I can't find anything relevant enough/working for my scenario. I've got:
Jobs <--> HABTM (Users_jobs table) <--> Users
I would like to do a paginate() from my Job controller with a condition on the User.id, as I do need to fetch -and paginate- all the jobs from the current user.
If you do provide a link to another topic/site, please provide an explanation with it and how you would apply it to my case.
Cheers,
Nicolas.
Make sure your HABTM associations are setup correctly (cake bake would expect a join table of jobs_users rather than users_jobs (see: Cakephp-HABTM) If they are, change the references to JobsUser in the example below to UsersJob to match the layout you described.
//In an action in jobs_controller.php
//Fake a HasOne association to the JobsUser model, setting $reset to false
$this->Job->bindModel(array(
'hasOne' => array(
'JobsUser'
)
), false);
//Setup the paginate conditions, grouping on job.id
//Set $user_id to the user to filter results by (can also be an array of users)
$options = array(
'group' => 'Job.id',
'conditions' => array(
'JobsUser.user.id' => $user_id
)
);
$this->paginate = $options;
$users = $this->paginate();

Categories