Forcing left join on my simple inner join query - cakephp

The site is already built from years. I am doing some modifications to it. It has controllers "Posts" and "Topics". On the topics page all recent posts are displayed. So it is a simple find of posts from "Posts" table. The fetch is not all assiciated to topic as we are showing all "Posts" and not "Topic" specific "Posts". In Topics controller,
App::import('Model', 'Post'); and $Posts = new Post;
are added and I call the action "getDiscussions" from Model "Post". The problem is that though I am not using any left join with "Topics" and "Users" (which is one more model used to display user details with post), the query is adding Left joins with two tables and that is giving a wrong result.
Please help.
Many Thanks
Yukti

I think it is better to try to find out why cake is doing these "joins", rather than performing yet another forced query, so you might want to look into your model associations to check dependancies. Also, you can set $this->Posts->recursive = -1; to make sure you don't import unwanted stuff.
In the end, I am not sure what query you are trying to perform, so I can't help you further. However, here is a sample of a query I have successfully used to perform a left join, as I've noticed that many examples I found while writing my own were bugged:
$user_record = $this->User->find('first', array(
'conditions' => array('`Openidurl`.`openid`' => $openid),
'joins' => array(
array(
'table' => 'openidurls',
'alias' => 'Openidurl',
'type' => 'LEFT',
'foreignKey' => 'user_id',
'conditions'=> array('`Openidurl`.`user_id` = `User`.`id`')
)
)
)
);

Related

Ways to use array in cakephp

Hello I am having a tought time figuring out how to use arrays in cakephp. right now i have a view with 2 columns, active and startYear. i need to grab the start years for all of the columns in the view and sho i have this code.
public function initialize(array $config)
{
$this->setTable('odb.SchoolYear');
}
controller
public function index()
{
$deleteTable = $this->loadModel('DeletedTranscripts');
$this->$deleteTable->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
$this->set('startYear',$deleteTable );
}
once i have the array captured and put into lets say startYear can in input a statement like this into my dropdown list to populate it?
<div class="dropdown-menu">
<a class="dropdown-item" href="#"><?= $delete->startYear; ?></a>
</div>
i have been looking for answers for quite awhile any help would be awesome.
Couple of things:
Loading Tables in CakePHP
For this line:
$deleteTable = $this->loadModel('DeletedTranscripts');
While you can get a table this way, there's really no reason to set the return of loadModel to a variable. This function sets a property of the same name on the Controller, which almost correctly used on the next line. Just use:
$this->loadModel('DeletedTranscripts');
Then you can start referencing this Table with:
$this->DeletedTranscripts
Additionally, if you're in say the DeletedTranscriptsController - the corresponding Table is loaded for you automatically, this call might be unnecessary entirely.
Getting Query Results
Next, you're close on the query part, you've can start to build a new Query with:
$this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
));
But note that the find() function does not immediately return results - it's just building a query. You can continue to modify this query with additional functions (like ->where() or ->contain()).
To get results from a query you need to call something like toArray() to get all results or first() to get a single one, like so:
$deletedTranscriptsList = $this->DeletedTranscripts->find('all', array(
'conditions' => array(
'field' => 500,
'status' => 'Confirmed'
),
'order' => 'ASC'
))->toArray();
Sending data to the view
Now that you've got the list, set that so it's available in your view as an array:
$this->set('startYear', $deletedTranscriptsList );
See also:
Using Finders to Load Data
Setting View Variables
I also noticed you've had a few other related questions recently - CakePHP's docs are really good overall, it does cover these systems pretty well. I'd encourage you to read up as much as possible on Controller's & View's.
I'd also maybe suggest running through the CMS Tutorial if you've not done so already, the section covering Controllers might help explain a number of CakePHP concepts related here & has some great working examples.
Hope this helps!

Multi level ordering in cakePHP

Why don't cakePHP include all my associations into one SQL query? The only way I have been able to do this is using "joins", but I hoped belongsTo and Containable behaviour was enough.
Here is an example:
Post->belongsTo->Category->belongsTo->category_type
(All models are setup correctly and work.
listing posts with pagination in index, I try this:
public function index() {
$this->paginate = array( 'contain' => array('Category' => array('CategoryType')));
$this->set('posts', $this->paginate());
}
This fetches the array correctly, but it does it in many SQLs like this:
SELECT `Post`.`id`, `Post`.`name`, `Post`.`content`, `Post`.`category_id`, `Category`.`id`, `Category`.`name`, `Category`.`category_type_id` FROM `unit_app`.`post` AS `Post` LEFT JOIN `unit_app`.`categories` AS `Category` ON (`Post`.`category_id` = `Category`.`id`) WHERE 1 = 1 LIMIT 20
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 1
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
This makes it difficult to order this query on CategoryType.name ASC.
Any suggestions?
If joins are only option, do I have to unbind the models before querying?
Will pagination work fine with joins?
Note! this is just a small part of all models, the resulting post->index need to fetch many other models through similar associations also.
(tested on cake 2.2.0 and v2.4.0-dev, php v5.4.11)
UPDATE! ---------
I just wanted to show my findings. I have now solved this without joins, but I had to re-bind in the model to get it working.
This is basically what I did to get it to work (also with paginations and sorts):
In Post model:
Added a bind function:
$this->unbindModel(array(
'belongsTo' => array('Category')
));
$this->bindModel(array(
'hasOne' => array(
'Category' => array(
'foreignKey' => false,
'conditions' => array('Category.id = Post.category_id')
),
'CategoryType' => array(
'foreignKey' => false,
'conditions' => array('CategoryType.id = Category.category_type_id')
))));
Then I added this to my index in Post controller:
$this->Post->bindCategory();
$this->paginate = array('contain' => array('Category' ,'CategoryType'));
$this->set('posts', $this->paginate());
I include table headers also just for documentation:
<th><?php echo $this->Paginator->sort('CategoryType.name', 'Type'); ?></th>
<th><?php echo $this->Paginator->sort('Category.name', 'Category'); ?></th>
I hope this post can help others as well :)
I am also going to test this Behaviour to see if I can omit all the bind-functions as well: https://github.com/siran/linkable/
There are lots of plugins to cake, but cake should have a "certification" of the plugins. It is quite difficult to find the fully working and tested ones on github :)
I also miss a site like railscasts.com just for cake :D
/MartOn
Yes, you must use JOINs to be able to order based on an associated models results.
Yes, you can paginate with JOINs. Just pass your options (including JOINs) to your paginate prior to actually calling $this->paginate();. (there are many resources online for how to paginate with JOINs)

CakePHP: bi-directional self-referential hasMany Through associations

I'm trying to get my head around bi-directional self-referential hasMany through relationships in CakePHP (what a mouthful!).
I'm working on a picture matching website.
Pictures are associated to other pictures via a 'match' (the join model).
Each match has two pictures and stores the current rating and the total number of votes.
When viewing a picture, all of its related images from either direction should be available (via its matches).
I've started by defining a hasMany through relationship with a join model.
The pictures_matches join table has this structure:
id | picture_id | partner_id | rating | total_votes
My match join model association looks like this:
class PictureMatch extends AppModel {
...
public $belongsTo = array(
'Picture' => array(
'className' => 'Picture',
'foreignKey' => 'picture_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Partner' => array(
'className' => 'Picture',
'foreignKey' => 'partner_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
Each picture needs to be able to access its related pictures from either direction, but this is where my grasp is slipping.
It looks like I need to save both sides of the relationship but this destroys the extra data stored in the join model - with two db entries, voting could vary depending on the direction.
Can anyone shed any light on the best way to do this in CakePHP? I'm rather confused.
Is it possible to create the inverse relationships on the fly?
You can create realtions on the fly vie Model::bindModel(), very usefull stuff this would alow you to bind reverse relations or rather any direction you would like on the fly.
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Also using Containable behaviour you can create infinite chain of retriving your associated date ex.
contain('Picture.PictureMatch.Partner.PictureMatch.Picture.....')
Basically you can loop through all of your models as long as each chain is somehow related to the next one to explain it better simple example ( please disregard logic in it )
Circle belongsTo Square
Square belongsTo Triangle
So Triangle is not related to Circle ( directly ), but Square is kinda in between
Circle->find('all', array('...', contain => array('Square.Triangle'));
or to have more fun lets get circle by circle with loop around
Circle->find('all', array('...', contain => array('Square.Trinagle.Square.Circle'));
and so on, of course those example are useless and without any programming logic, but I hope you understand the point that you can loop trough infinite number of relations going back and forth.
I am not sure if this is the best solution but if you did this:
public $belongsTo = array(
'Picture1' => array(
'className' => 'Picture',
'foreignKey' => 'picture_id',
),
'Picture2' => array(
'className' => 'Picture',
'foreignKey' => 'picture_id',
),
'Partner' => array(
'className' => 'Partner',
'foreignKey' => 'partner_id',
),
);
then when you do a search you just search for ($this->data['Picture1'] == $var || $this->data['Picture2'] == $var) and so long as you have recursive set to 1 or 2 you should get back all the related data for that Picture.
I assume this is abandoned, but it's easily resolvable -- it has to do with the phase
destroys the extra data stored in the join model
That means your saves are running a deleteAll and inserting the record on matching... instead you need to find and update that record...
This can be done in a few ways, but the easiest is before your save call, look it up, and include the primary key in the match record data. Basically don't save it as a HABTM, and only save it as a hasMany if you've already tried to find an existing match record primary key (id) and updated the data to save with it.

Why isn't my CakePHP find function returning any results?

I'm fairly new to cakePHP and ran into a problem that I can't seem to get my head around.
While trying to find some info from two tables I get this:
ERROR: missing FROM-clause entry for table "Match"
I have two models, Club and Match.
The clubs table doesn't have anything related to matches.
The matches table has an hometeamid and an awayteamid which relates to clubs(id).
Since a match is played by two clubs and can be played both at home or away I've had to do the connections a bit differently than normal. As for the connections on the models:
Club $hasMany = array('HomeMatch' => array('className' => 'Match','foreignKey' => 'hometeamid'), 'AwayMatch' => array('className' => 'Match','foreignKey' => 'awayteamid'));
Match $belongsTo = array('HomeTeam' => array('className' => 'Club', 'foreignKey' => 'hometeamid'), 'AwayTeam' => array('className' => 'Club', 'foreignKey' => 'awayteamid'));
http://book.cakephp.org/view/1046/Multiple-relations-to-the-same-model
While trying to find all home matches, I do this:
$this->Club->HomeMatch->find('all',array('fields' => array('Match.id', 'Match.status', 'Match.matchdate', 'Match.stadium_id')));
Unfortunately, this gives the error I mentioned at the top.
I'm not quite sure what the problem is. I can see that it says it's missing a FROM clause for table "Match" but, I can't really make sense of that.
Updated info from comments:
Here is the debug SQL:
SELECT "Match"."id" AS "Match__id", "Match"."status" AS "Match__status", "Match"."matchdate" AS "Match__matchdate", "Match"."stadium_id" AS "Match__stadium_id"
FROM "matches" AS "HomeMatch"
LEFT JOIN "clubs" AS "HomeTeam" ON ("HomeMatch"."hometeamid" = "HomeTeam"."id")
LEFT JOIN "clubs" AS "AwayTeam" ON ("HomeMatch"."awayteamid" = "AwayTeam"."id")
WHERE 1 = 1
A bit more work and I've narrowed it down to my find. I can see that the model connections work perfectly, but my find is not working as it should.
$this->set('homematches', $this->Club->HomeMatch->find('all', array( 'fields' => array('HomeMatch.id', 'HomeMatch.status', 'HomeMatch.matchdate', 'HomeMatch.stadium_id'))));
This find doesn't produce any errors, but it doesn't give any result either. When I try to retrieve something from homematches in the view, cake tells me:
Undefined variable: homematches
What now?
The error you get doesn't mean that there something wrong with the query.
You need to make sure that this line:
$this->set('homematches', $this->Club->HomeMatch->find('all', array( 'fields' => array('HomeMatch.id', HomeMatch.status', 'HomeMatch.matchdate', 'HomeMatch.stadium_id'))));
is in the controller file and there's a render function after it.
There's a disconnection between the "set" function to the view file.
Add the whole function code (where the above line is located) please.

CakePHP 2.0 - How to remove join tables from containable

I'm using Containable in an action like this:
public function index()
{
$this->User->recursive = -1;
$this->User->Behaviors->load('Containable');
if ($this->RequestHandler->accepts('xml'))
{
$this->set('users', array("Users" => array("UserEntry" => $this->User->find('all',
array(
'fields' => array('User.id','User.username', 'User.email', 'User.created', 'User.modified'),
'contain' => array(
'Group' => array(
'fields' => array('Group.id','Group.name','Group.created'),
)
)
)
))));
}
else if ($this->RequestHandler->accepts('json'))
{
}
else if ($this->RequestHandler->accepts('html'))
{
$this->set('users', $this->paginate());
}
}
It gets all of the data I need, but there is one thing that I can't figure out. There is a HABTM relationship between users and groups with a join table users_groups. I'm serializing the output of find('all') into Xml for a REST Api in the view. The problem is that the data contains an extra 'GroupsUser' array nested in my 'Groups' array. The users of the Api do not need to know about the join table information so I would like to remove it. The current output looks like this:
index.ctp
<?php
//debug($users);
$xml = Xml::build($users, array('return' => 'domdocument'));
echo $xml->saveXML();
?>
output of index.ctp -> http://www.pastie.org/2789367
See the GroupsUser tag nested in the Group tag? That is what I want to remove. If there is not a nice easy way to do this I will either build the xml by hand using some loops in the view or create my own find method in the the model and use unset() on GroupsUser. Both of those solutions are not ideal, so I'm hoping someone here has a better one. :)
I you are positive that everything is being done in a join (only belongsTo associations) you may use the containable component with autofields in false something like this
$this->Post->Behaviors->attach('Containable', array('autoFields' => false));
(this is to attach it dynamiclly in the controller part.
if your find has hasMany association, you will have 2 queries instead of so cake needs to fetch for this fields to do the join. You may also use linkable component for this case, that put all in joins instead of a lot of queries giving you chance to select only the fields you want.
here is a link for the linkable component

Resources