Retrieving models without getting associated models - CakePHP - php

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

Related

cake php accessing a table

I just started cakephp following there tutorials
I'm able to grab the posts table in my post controller and spew it onto my index.ctp
In my view for the post controller i also want to list the User name that posted the article. My post table has a user_id, so i need to match it to my user table and pass it along
class PostsController extends AppController {
public function index() {
//passes values to the view
$this->set('posts', $this->Post->find('all'));
//is "Post" a post method? or is it the name of the table? i'm unsure of the syntax
$this->set('users', $this->Users->find('all')); //this does not work
}
}
thank you for your help with this basic question
You must use 'recursive'
$this->Post->find('all', array(
'recursive' => 2,
// ...
));
Of course, you first need to link models together
I assume that you have already set a belongsTo association (Post belongsTo User) and/or a hasMany association (User hasMany Post). If so, cake will automaticly brings the associated models (unless you put $recursive = -1 on your model).
Thus you'll have access to the users related to each post on the view: posts[i]['User']
You can also use this on your view to see the view variables:
debug($this->viewVars)
put this on your Post model if you don't:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
)
);
Make sure that you load models corretly (in case you want to load the User model inside PostsController).
So simply add this attribute inside your class controller.
public $uses = array('Post','User');
to link models together . u need to add the association inside your Post model .
public $belongsTo = array(
'User'=>array(
'className'=> 'User',
'foreignKey'=>'user_id'
)
);
and i you want to retrieve data from database you have to set your recursivity and there is two ways
first one :
$posts = $this->Post->find('all',array('recursive'=>2));
// or
$this->Post->recursive = 2;
$posts = $this->Post->find('all');
second one : use the Containable behavior
set the recursivity to -1 in the AppModel and include the behavior
public $recursive = -1;
public $actsAs = array('Containable');
so simply u can retieve posts with any other linked models like that
$posts = $this->Post->find('all',array(
'contain'=>array('User'),
// ...
)));

unable to fetch records from multiple tables using join

I am using Yii framework. i am wonder how i can get records from multiple tables i did research but couldn't find any usefull link i am using following code for this please let me know where i am missing
my model Task.php
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(
'prj_user' => array(self::BELONGS_TO, 'User', 'id'),
);
}
model User.php
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(
array('task', self::HAS_MANY, 'Task','project_id')
);
}
and this is my main controller
$criteria = new CDbCriteria;
$criteria->compare('t.id', 1);
$criteria->with = array( 'prj_user' => array('select' => 'username,title,roles', 'joinType'=>'inner join'));
$rows = Task::model()->findAll( $criteria );
but still i am getting columns only from task table but i need more three columns from users table please help me
Let Yii worry about joining your tables. Your relations looks fine so you should be able to access them directly
For example, what does this return?
foreach ($rows as $task)
{
if ( isset($task->prj_user) )
echo $task->prj_user->username . "<br>";
}
Or this?
this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>new CActiveDataProvider('Task'),
'columns'=>array(
'id',
'prj_user.username',
'prj_user.title',
'prj_user.roles',
)
));
->with() is used for eager loading, so at this point you probably don't need it. In fact, unless I misread you completely, you can remove your criteria all together.

Show a list of records to have a join to another table using CakePHP

I have an application that stores Posts and Topics and joins them using a Topic_Posts table.
The associations for the application are as follows:
Post.php
class Post extends AppModel
{
public $name = 'Post';
public $belongsTo = 'User';
public $hasMany = array('Answer');
// Has many topics that belong to topic post join table... jazz
public $hasAndBelongsToMany = array(
'Topic' => array('with' => 'TopicPost')
);
}
Topic.php
class Topic extends AppModel
{
public $hasMany = array(
'TopicPost'
);
}
TopicPost.php
class TopicPost extends AppModel {
public $belongsTo = array(
'Topic', 'Post'
);
}
When a user views a topic e.g. /topics/view/topicname I want to show all the posts that contain that Topic.
So far I have the following method in my TopicsController for the view:
public function view ( $slug )
{
$topic = $this->Topic->find('first', array('conditions'=>array('Topic.slug'=>$slug)));
$this->set('topic', $topic);
$this->set('title_for_layout', $topic['Topic']['title'] . ' – Topics');
$this->paginate = array
(
'Post' => array
(
'limit'=>15,
'conditions'=>array
(
'Post.status'=>array(1,2),
'TopicPost.topic_id' => $topic['Topic']['id'],
),
'order'=>array('Post.datetime'=>'desc'),
'contain'=>array('User'=>'Profile', 'TopicPost')
)
);
$posts = $this->paginate('Post'); // this one
$this->set('posts', $posts);
}
And so that I can use Posts and TopicPosts I have added: public $uses = array('Topic','TopicPost','Post'); to the top of the controller and made all models act as containable.
So basically I need to find Posts that have a match in the database model TopicPosts for the id of the topic I'm viewing.
I just couldn't get it to work the "proper" way. I'm not sure if this is a bug in cake or something, but the paginate function simply refuses to budge.. The proper way to do this would probably be to write your own paginate function in your Post model, there is some info on how to do that in the cookbook.
Meanwhile, I offer you the workaround below. It's not optimal (at least not without caching) but it works. You can do it the proper way when/if you run into performance problem, but until then, this code below should do it.
public function view ( $slug )
{
$topic = $this->Topic->find('first', array('conditions'=>array('Topic.slug'=>$slug)));
$this->set('topic', $topic);
$this->set('title_for_layout', $topic['Topic']['title'] . ' – Topics');
// step 1: get post IDs related to your topic
$postIDs = $this->Topic->TopicPost->find
(
'list',
array
(
'fields' => array('TopicPost.post_id'),
'conditions' => array('TopicPost.topic_id' => $topic['Topic']['id'])
)
);
$this->paginate = array
(
'Post' => array
(
'limit'=>15,
'conditions'=>array
(
'Post.status' => array(1,2),
// step 2: include them in your paginate conditions
'Post.id' => $postIDs,
),
'order' => array('Post.datetime'=>'desc'),
)
);
$posts = $this->paginate('Post');
$this->set('posts', $posts);
}
(Please note that I've stripped some of the stuff in my tests as I didn't have some of the stuff in your app, so don't forget to put it back)
I think you have a problem with your model relationships, I don't understand why you have a HABTM relationship on the posts model, when you are actually emulating this (using the Has-many-through method) on the TopicPost model itself. If you don't want to use the HABTM behaviour built into cake (and I don't blame you), you should setup relationships like this:
class PostTopic extends AppModel { // note PostTopic, name should be alphabetical
public $belongsTo = array('Post', 'Topic');
}
class Post extends AppModel {
public $hasMany = array('PostTopic');
}
class Topic extends AppModel {
public $hasMany = array('PostTopic');
}
Then to fetch the IDs of posts or topics that relate, you simply load the PostTopic class and do a search:
// ie in Post Controller
$this->paginate($this->Post->PostTopic, array('PostTopic.topic_id' => $post['PostTopic']['topic_id']));
I have a similar setup on my site, where users can add a product to their inventory, It's kind of a HABTM relatipnship but with more data attached to it. See the Product, Inventory and Users models here for a more complicated example.
From question comment:
Also just to note the DB structure and relationships work fine as I
can pull the topics for a post fine using TopicPost so I KNOW that
works, it's just getting Posts for a topic that seems to be not
working...
Right, because you setup the HABTM for Post->Topic, but not for Topic->Post. The 2.0 book defines an example just like yours (but using recipes and ingredients) to describe the use case.
The main difference between hasMany and HABTM is that a link between
models in HABTM is not exclusive. For example, we’re about to join up
our Recipe model with an Ingredient model using HABTM. Using tomatoes
as an Ingredient for my grandma’s spaghetti recipe doesn’t “use up”
the ingredient. I can also use it for a salad Recipe.
The same can be said for a Post using a Topic (your name for a tag/category). And just like their example:
Remember to define a HABTM association in the Ingredient model if
you’d like to fetch Recipe data when using the Ingredient model.
Or, in your case, the Topic model.
So here's the models that you should use:
class Post extends AppModel
{
public $name = 'Post';
public $belongsTo = 'User';
public $hasMany = array('Answer');
// Has many topics that belong to topic post join table... jazz
public $hasAndBelongsToMany = array(
'Topic' => array('with' => 'TopicPost')
);
}
class Topic extends AppModel
{
// Has many posts that belong to topic post join table... jazz
public $hasAndBelongsToMany = array(
'Post' => array('with' => 'TopicPost')
);
}
Now, you don't really need TopicPost unless you are doing something special, but it is fine as it is. And, though you can use that model for adding Topics to to a post (or vice-versa), I'd suggest you use the build in method of handling HABTM saves. Review this page for details on saving HABTM.
If you decide to remove it, make sure you define the join tables and conditions for the HABTM options in each model per the docs

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();

How to include associations in find() with fieldlist

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?

Categories