CakePHP 3 - How to order find result by associated model - php

I am building a comment, with replies system using CakePHP and Backbone. But I am having some issues with my CakePHP query, that does not order in the way it needs to be.
So this is database system I have right now,
Posts
ID (Auto, PK)
MainPost (Long Text)
Timestamp
User_id
Comments
ID (Auto, PK)
SubComment (Long Text)
Timestamp
User_id
Post_id (FK, linking to the Posts table)
Now I think my model is ok (if needed I can post that code). I can save and update the posts and comments. But what I need to when a new comment is added to the system. That whole post is moved to the top.
This is the query I have done on my Posts Table,
$Table->find('all')
->contain(['Comments',
'Comments.Users',
])
// ->order(['Posts.Comments.timestamp' => 'DESC'])
// ->matching('Comments', function ($q) {
// return $q->where(['Comments.timestamp' => 'DESC']);
// })
->toArray();
So I want to do a sort, like my order looks above, I basically want to order my Posts Table based on the timestamp of my Comments Table.
When I run this query, with order enabled, it just says that "Unknown column" but I don't know why? I have a LeftJoin in my model linking the Comments to my Posts table.
I have tried doing sorts and orders within the contain call of the query, however these just sort/order the comments and not the posts!
So what am I doing wrong?

Related

Using special union functions inside Laravel Eloquent model

I've started developing a website using Laravel, and im pretty much finding everything through the official documentation and answers that I find here. However there is 1 thing that -even though i've found a way to do-, I have a feeling that could be done in another, more optimized way than the one I'm doing it right now. Let me explain.
For my website, I have a table called "players", which has data about some players extracted from a football game. Let's say the structure is like this:
ID (int, primary key, A_I)
GameID (int, unique) //what the game uses
PlayerName
Data (basically many different columns)
Since the purpose of my website is to allow users to do modifications on the game content, I also have another another table that I call "userplayers", which I use for doing modifications on the players that exist on the original table, or for adding new ones. The structure is like the "players" table, however with just one column added, called userID, which is to identify which modification belongs to which user.
ID (int, primary key, A_I)
GameID (int, unique together with userID)
PlayerName
Data (basically many different columns)
UserID (int, unique together with GameID)
If I add an entry on the userplayers table, if that entry has the same GameID as any entry on the players table (and the user that has created it is logged in), then on runtime it overwrites the players' table entry. If the GameID of the new entry doesnt exist on the original players table, then it just gets appended to it.
If the user is not logged in, then I just return the players table.
By using eloquent laravel model I can easily retrieve the players table for when the user is not logged in. However, I can't figure out an efficient way to return the whole DB + the user created content with just using core eloquent model functions.
In the past (without Laravel) I was using this DB query:
SELECT * FROM (SELECT *, NULL FROM players WHERE NOT EXISTS (SELECT * FROM userplayers WHERE players.gameid=userplayers.gameid AND userId=$userId) UNION (SELECT * FROM userplayers WHERE userId=$userId)) AS pl;
And the way I've "found" to do something like this in Laravel is by adding a local scope inside the Players model like this:
public function scopeIncludeCustom($query, $user)
{
return \DB::select('SELECT * FROM (SELECT *, NULL FROM players WHERE NOT EXISTS (SELECT * FROM userplayers WHERE userplayers.gameid=players.gameid AND userId='.$user.') UNION (SELECT * FROM userplayers WHERE userId='.$user.')) AS players_full);
}
However you can understand that this doesn't return the $query as intended by scopes, but just a php array, which im returning back, and I think that's not the correct way to do this. For example, when I'm just searching the players table (without using user created content), it takes a much much shorter time to return results than returning results with the custom content.
Any ideas are hugely appreciated.
So, after some days of researching my issue and possible solutions, I came up with this solution.
So, lets take this step by step.
I had a "Player" model, that fetched data from the "players" table.
I also had a "Userplayer" model, that fetched data from the "userplayers" table.
I had to create a relation between those 2 models. An entry in the "players" table may have many entries related to them in the "userplayers" table. So, in the Player Model I added this:
public function userplayers()
{
return $this->hasMany('App\Userplayer', 'gameid', 'gameid');
}
While in the Userplayer Model, I added this:
public function player()
{
return $this->belongsTo('App\Player', 'gameid', 'gameid');
}
When requesting data, the first step was to remove every row from the "players" table that had the same "GameId" as any row returned from the "userplayers" table, which also had a restriction that the "userId" in every row of this table must be a specific one.
This is the code that does this in my SearchPlayerController:
$orig_players = \App\Player::whereDoesntHave('userplayers', function ($query) use($user)
{
$query->where('userId', $user);
})->get();
At the same time, I need to get every row from the "userplayers" table that has the "userid" I want
$userplayers = \App\Userplayer::where([
['pesid', $search]
])->get();
Now, I just merge the 2 results (I can't use UNION because the data I fetch from the "players" table has one less column).
$players = $orig_players->merge($userplayers)
->sortBy('registeredName')
->take($limit);
And everything works perfectly fine, and a lot faster than before!

How to stop auto sorting in laravel pivot table of many to many realtion

I have a author table and a publication table. Both are related to each other in a many to many relation. When I'm inserting the publications the authors of the publications are inserted in the pivot table by the order of authors id. But I need to insert it by the order i'm selecting the authors in the front-end. Whatever the order of the authors in the front end is it is getting ordered by the author's id in the pivot table. How can i stop this automatic ordering
You can't add rows in a specific order into a pivot table, because it doesn't really make sense.
Let's consider an users table:
The first user you enter will have the id 1
The second will be assigned to the id 2
And so on...
So you can enter the users in a specific order and retrieve them by their id.
However, in a standard pivot table, the primary key is composed by two columns, in your case the author_id and publication_id. Nothing new is created here, you just associate the primary key of two existing rows in two differents tables in order to achieve one - and unique - composed primary key.
If i explained well (and i hope so :p), you should understand why saying
But I need to insert it by the order i'm selecting the authors in the front-end.
doesn't really make sense.
But, don't worry, it is still possible to achieve your goal here.
Instead of using a pivot table, you can use a normal table with an id. This way, the order of insertion will be preserved. It will work but that's not very nice.
A better approach would be to add an additional column to the pivot table. A column like position. This column could be incremented for each author you insert. Then, you can order the table by the position column, by simply adding ->orderBy('position') to your relationship or every queries that needs to.
Here is an example to illustrate what i said above:
foreach($authors as $position => $author)
{
$publication->authors()->attach($author, ['position' => $position]);
}
If $authors contains the authors in the order you selected them on the front-end, they will be added accordingly.
If you need to sync instead of attach, that's also possible, it's just a little bit more verbose:
$syncData = $authors->mapWithKeys(function($author, $position){
return [$author->id => ['position' => $position]];
});
$publication->authors()->sync($syncData);
Don't forget that you can add false as a second parameter on the sync method so it'll only add new authors.
After that, just change your authors relationship in your Publication model like this:
public function authors(){
return $this->belongsToMany(Author::class)->orderBy('position');
}
Or everywhere you need to:
$publication->authors()->orderBy('position')->get();
I hope it helps !

Laravel join table using eloquent

I'm trying to load last 5 comments with related posts selecting only id and title from post.
In first case I decided get all columns from posts:
Comment::with(['post'])->take(5)->orderBy('id', 'desc')->get();
And it's working fine.
But when I try get only two columns ("id, title") then nothing from posts is loaded.
Comment::with(['post:id,title'])->take($number)->orderBy('id', 'desc')->get();
I did a test and when I removed "orderBy('id', 'desc')" then was fine again.
Comment::with(['post:id,title'])->take($number)->get();
So must be some problem with "orderBy" option.
Is it any way to fix it? It's mean get only selected columns from "posts" table and order results from the last one?
Thank you.
As both your Comment and Post models contain id, Laravel doesn't know by which one do you want to sort. So, add a table name to your orderBy:
Comment::with(['post:id,title'])->take($number)->orderBy('comments.id', 'desc')->get();
Here comments is name of your table/entity, maybe it is comment.

How can I look in different table to see if a page is active?

Some difficulties with "condition"
I have a table Tags with fields : id, name, count
I have table PagesTags with fields : id, page_id and tag_id
I have table Pages with several fields : id, name, shownmenu, onilne.
In my TagsController, I have a simple code to extract and display the Tags
function tagsList(){
//$this->loadModel('PageTag');
return $this->Tag->find('all',
array('order'=>'name ASC','conditions'=>array('Tag.count >= 0')));
}
The problem is, I do not want to display the tags associated to a page which is Offline.
Then in my aboce I should use somethink like $this->loadModel('PageTag'); to get the Id of the associated page and a second $this->loadModel('Page');
to get the page status 'online' (true/false)
Can we do it at once?
How can I do it simply?
How can I look in two table at one?
You need to use model relationships to link your models together. Read the manual about it or see my answer on this topic here:
CakePHP 1.3 - Unknown column in where clause
In your case, PagesTags hasOne Page and hasMany Tags. You will write these relationships into the model. Also you will write into Tags model that it hasOne PagesTags, which will in turn retrieve the associated Page.
After you have done all of that, you don't need to call loadModel, you just query Tag and it will implicitly join the associated PagesTags and Page records, so you can just put this into your conditions:
'Page.status !=' => 'Offline'
I think the relation of the table is HABTM so you have to look this component for searching http://bakery.cakephp.org/articles/Kumazatheef/2008/10/22/habtm-searching

Update parent model from child (belongsto) model

I have Posts and Comments and users can "Like" either. I'm using cakePHP.
The Posts and Comments tables each have a 'likes' row on them because I don't want to re-count the likes each time the post / comments are loaded. I have a Likes table too, that contains the IDs (post id, user id) so that I know what users have already 'liked' something.
I was wondering how I would set up this relationship within the models in cakePHP and also how I would update Posts.likes field when at the same time adding a new like into the Likes table.
I've set up Likes to "belongTo" Posts and Comments in the Like Model and at the moment, my LikesController.php looks like this:
public function add(){
...
if ($this->Like->save($this->request->data)) {
//like is added to Likes table, now how to add to the "parent" Post or Comment??
}
...
}
Keep your tables as they are, but add a like_count field to your posts and comments tables.
Also add a comment_count to the posts table.
Then just use CakePHP's counterCache, and it will keep track of the # of likes and comments per post automatically.

Categories