I'm currently trying to replicate something similar to a forum, but I'm stumped on how I could create nested comments. I understand that for regular replies I could create a replies table and run a loop for each comment that matches the thread id. But I don't know how I would easily do this for nested replies.
Could someone please give me some advice or point me in the right direction? Thanks.
This is the structure for my posts table:
Screenshot of phpMyAdmin http://bitdrops.co/drops/J5v.png
You want to look into polymorphic relations to solve this. You want to be able to comment on Posts and Comments.
What I have done is set up a commentable trait and have the models I want to add comments to use it. This way if you ever want to comment on another model, you can just add the trait to that model.
Laracasts is a great resource for laravel and has a good lesson on traits.
There is a bit more to it than this, but hopefully it will get you started.
You set up your database structure like this.
User Table
`id` int(10),
`name` varchar(255),
`username` varchar(255)
Comments table
`id` int(10),
`user_id` int(10),
`body` text,
`commentable_id` int(10),
`commentable_type` varchar(255)
Posts Table
`id` int(10),
`user_id` int(10),
`body` text
Your models like this.
Comment Model
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model {
use CommentableTrait;
/**
* Get all of the owning commentable models.
*/
public function commentable()
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo('App\User');
}
}
Post Model
<?php namespace App;
use CommentableTrait;
use Illuminate\Database\Eloquent\Model;
class Post extends Model {
use CommentableTrait;
}
and of course you need the trait.
Trait
<?php namespace App;
use Comment;
trait CommentableTrait {
/**
* List of users who have favorited this item
* #return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function comments()
{
return $this->morphMany('App\Comments\Comment', 'commentable')->latest();
}
/**
* #return \Illuminate\Http\RedirectResponse
*/
public function addComment($body, $user_id)
{
$comment = new Comment();
$comment->body = $body;
$comment->user_id = $user_id;
$this->comments()->save($comment);
return $comment;
}
}
Related
So basically, I have a photo application and the relation between photos and likes is hasMany(). How can I make the relation to be ordered by count(number_of_likes) for each photo?
TABLE `Likes` (
`id_lk` int(11) NOT NULL AUTO_INCREMENT,
`idusr_lk` int(11) NOT NULL,
`idpht_lk` int(11) NOT NULL,
PRIMARY KEY (`id_lk`),
KEY `idusr_lk` (`idusr_lk`),
KEY `idpht_lk` (`idpht_lk`),
CONSTRAINT `Likes_ibfk_1` FOREIGN KEY (`idusr_lk`) REFERENCES `users_usr` (`id_usr`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `Likes_ibfk_2` FOREIGN KEY (`idpht_lk`) REFERENCES `photos_pht` (`id_pht`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
the php code of the relations:
Photo model
public function getLikes()
{
return $this->hasMany(Likes::className(), ['idpht_lk' => 'id_pht']);
}
Likes model
public function getPhoto()
{
return $this->hasOne(Photo::className(), ['id_pht' => 'idpht_lk']);
}
I know that you can add an orderBy clause after the relations, but I just simply don't know if i am allowed to write an SQL query there and if I am, how am I supposed to write it?
Well what you require is that you don't have to build the query manually every time and still able to call the result set which has
All photos along with the number of likes they have using the existing relation getLikes().
They should be ordered according to the number of likes they have.
Well what i suggest wont just use Photo::find()->all() to do the job but yes if you are ok with doing Photo::find()->byLikes()->all() then you can use the following approach
Create ActiveQuery Class PhotoQuery for the Photo model.
Override the find() method inside your Photo Model to use the newly generate/created PhotoQuery class.
You can use Gii to generate the default PhotoQuery class or you can create manually like the below one.
Adjust the namespaces accordingly.
<?php
namespace app\models;
/**
* This is the ActiveQuery class for [[Photo]].
*
* #see Photo
*/
class PhotoQuery extends \yii\db\ActiveQuery
{
/**
* {#inheritdoc}
* #return Photo[]|array
*/
public function all($db = null)
{
return parent::all($db);
}
/**
* {#inheritdoc}
* #return Photo|array|null
*/
public function one($db = null)
{
return parent::one($db);
}
}
Now what you need to do is to add a new method in the PhotoQuery with the name byLikes()
/**
* #return mixed
*/
public function byLikes()
{
return $this->alias('p')
->select(['p.*', new \yii\db\Expression('count(l.idpht_lk) as likeCount')])
->joinWith(['likes l'])
->groupBy('p.id_pht')
->orderBy('likeCount desc');
}
and then add the following method in your Photo model to use PhotoQuery class instance.
/**
* {#inheritdoc}
* #return PhotoQuery the active query used by this AR class.
*/
public static function find()
{
return new PhotoQuery(get_called_class());
}
Now you can call the query like Photo::find()->byLikes()->all() and it will return the results ordered by total number of likes along with the likes count, where at the same time you can still call Photo::find()->all() to get only the photo specific result set if you want to somewhere in the future.
Hope it helps.
In your table Likes you have to add a field where you can store the number of likes for each photo, for example: number_of_likes.
Then you can run a query like this to get the number of like for each photo on descending order:
SELECT idusr_lk, idpht_lk, number_of_likes
FROM photos_pht
INNER JOIN Likes
ON photos_pht.id_pht = Likes.idpht_lk
ORDER BY Likes.number_of_likes DESC
I've just started using Eloquent ORM (Without Laravel) and I am having issues with the many to many relationships.
I have a table where I store Families (Article categories), another one for the Articles, and a third one as a "pivot". I want to be able to get all the articles a Family has, and all the families an article belongs to. So I have coded this models.
Families
class Families extends Model {
public $table = 'Families';
public function Articles() {
return $this->belongsToMany('Articles', 'articles_families', 'families_id', 'articles_id');
}
}
Articles
class Articles extends Model {
public $table = 'Articles';
public function Families() {
return $this->belongsToMany('Families', null, 'articles_id', 'families_id');
}
}
Then I am trying to retrieve the data like this:
$families = Families::all();
echo $families[1]->Articles;
However, it just returns an empty array, when it should return a couple of articles. I have tripled checked that all the values are correct in the three tables. If I echo the Eloquent query debugger I can see that it is looking for a null value and I'm pretty sure that's the problem, but I don't quite know how to fix it. Here:
{"query":"select * from `Families`","bindings":[],"time":49.13},{"query":"select `Articles`.*, `articles_families`.`families_id` as `pivot_families_id`, `articles_families`.`articles_id` as `pivot_articles_id` from `Articles` inner join `articles_families` on `Articles`.`id` = `articles_families`.`articles_id` where `articles_families`.`families_id` is null","bindings":[],"time":38.93}
The null value is at the end of the last query.
I just found the solution myself. As my primary key columns are called Id, and Eloquent by default assumes the primary key is called id, I needed to override that by adding a class property protected $primaryKey = "Id"; and it now retrieves the data properly.
I have two Models that I would like to merge into one timeline. I have been able to do this by creating a View in mysql that normalizes and unions the tables. I created a Model for this view, NewsFeed. This works well if I do not want related Comment model. I have gotten close to this by overriding the getMorphClass method on the model. This allows me to get the related comments for the pictures, but not the posts, because when getMorphClass is called the model doesn't have any data.
I am open to any approach on how to solve this, not just the way I am proposing, but I don't want to pull more data than I have to from the database.
NewsFeed
<?php
namespace App\Users;
use App\Pictures\Picture;
use App\Social\Comments\CommentableTrait;
use App\Posts\Post;
use App\Users\User;
use Illuminate\Database\Eloquent\Model;
class UserFeed extends Model
{
use CommentableTrait;
public function user()
{
return $this->belongsTo(User::class);
}
public function getMorphClass(){
if ($this->type == 'post'){
return Post::class;
}
return Picture::class;
}
}
MySQL View
CREATE VIEW
`user_feeds`
AS SELECT
`posts`.`id` AS `id`,
`posts`.`user_id` AS `user_id`,
'post' AS `type`,
NULL AS `name`,
NULL AS `thumbnail`,
`posts`.`body` AS `body`,
`posts`.`updated_at` AS `updated_at`,
`posts`.`created_at` AS `created_at`
FROM
`posts`
UNION SELECT
`pictures`.`id` AS `id`,
`pictures`.`user_id` AS `user_id`,
'picture' AS `type`,
`pictures`.`name` AS `name`,
`pictures`.`thumbnail` AS `thumbnail`,
`pictures`.`description` AS `body`,
`pictures`.`updated_at` AS `updated_at`,
`pictures`.`created_at` AS `created_at`
FROM
`pictures`;
pictures table
id
user_id
title
img
img_width
img_height
img_other
description
created_at
updated_at
posts
id
user_id
title
body
created_at
updated_at
You are really close with your idea to build a view. In fact, if you create an actual table instead of a view, the solution becomes quite simple.
With a 'FeedItem' polymorph object that points to your Post class or Picture class, you can attach the comments directly to the FeedItem with a hasMany relationship.
class FeedItem extends Model {
use CommentableTrait;
public function feedable()
{
return $this->morphTo();
}
}
class Post extends Model {
public function feeditem()
{
return $this->morphOne('FeedItem', 'feedable');
}
}
class Picture extends Model {
public function feeditem()
{
return $this->morphOne('FeedItem', 'feedable');
}
}
This solution may require some refactoring on your forms since you will need to create a FeedItem entry for each Post entry and Picture entry. Event listeners for Picture::created and Post::created should do the trick (http://laravel.com/docs/5.1/eloquent#events).
Once it's set up, you can use:
FeedItem::with('comments')->orderBy('created_at','desc')->paginate(15);
Although I am not familiar with Laravel nor Eloquent for that matter, Here's my input on this.
Assuming you're getting the output from that SQL view into $Entries
As I understand Eloquent allows you to set the values for yourself, therefore something like this might work for you (I am not sure about the syntax or usage for that matter).
$Collection = [];
foreach( $Entries as $Entry ) {
if( $Entry->type === 'post' ) {
$Collection[] = new Post($Entry->toArray())->with('comments');
}else{
$Collection[] = new Picture($Entry->toArray())->with('comments');
}
}
return $Collection;
Suppose I have the following tables:
User:
-userID
-userName
...
Exercises:
-exerciseID
...
User model:
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
protected $primaryKey = 'userID';
...
public function hasPassedExercises() {
return $this->hasMany('Exercise', 'exerciseID');
}
}
I want to say that a User has many completedExercises, so when the user completes an exercise, I update the model like so:
Route::post('dbm/userPassedExercise', function () {
$user = User::with('hasPassedExercises')->find($_POST['userID']);
$exercise = Exercise::find($_POST['exerciseID']);
$user->hasPassedExercises->save($exercise);
});
However, this has no effect on any underlying table, as far as I have understood. I'm trying to make sense of the documentation and see how it applies to my problem. So my question is what is the right course of action to do here.
Should I create a table users_completed_exercises that has userID and exerciseID as foreign keys, and if so, how do I link them to my user when I do the update? Or is there a more elegant solution?
Indeed, you have to use a relationship table (called pivot table).
In the laravel documentation, you have to name your pivot table with your tables name ordered by their name (you have not to, but it's prefered).
We'll take your naming convention so : users_completed_exercises
So here we shoud have this :
users:
- userId // Unsigned Int PRIMARY KEY AUTO_INCREMENT
Exercises:
- exerciseId // Unsigned Int PRIMARY KEY AUTO_INCREMENT
users_completed_exercises:
- id // Unsigned Int PRIMARY KEY AUTO_INCREMENT
- exerciseId // Unsigned Int FOREIGN KEY REFERECES EXERCICES ON ID
- userId // Unsigned Int FOREIGN KEY REFERECES USERS ON ID
On the user model, you should have :
public function passedExercises()
{
// Alphabetical order of your id's are here, very important because laravel
// retreives the good ID with your table name.
return $this->belongsToMany('Exercise', 'users_completed_exercises', 'exerciseId', 'userId');
}
And the inverse on Excercise Model
public function usersWhoPassed()
{
// Alphabetical order of your id's are here, very important because laravel
// retreives the good ID with your table name.
return $this->belongsToMany('User', 'users_completed_exercises', 'exerciseId', 'userId');
}
Retreiving infos are now, so easy.
Route::post('dbm/userPassedExercise', function () {
// Don't use $_POST with laravel, they are exceptions indeed, but avoid as much as
// possible.
$user = User::find(Input::get('userId'));
$exercise = Exercise::find(Input::get('exerciseId'));
// Very important, use () on relationships only if you want to continue the query
// Without () you will get an Exercises Collection. Use ->get() or ->first() to end
// the query and get the result(s)
$exercise->usersWhoPassed()->save($user);
});
You can easly check if user has passed an exercise too
Route::get('/exercises/{id}/passed_users', function($id)
{
$exercise = Exercise::find($id);
if ($exercise->usersWhoPassed()
->where('userId', '=', Input::get('userId'))->count()) {
return 'User has passed';
}
return 'User has failed';
});
I have a User table and need to allow for users to have a parent user.
the table would have the fields:
id
parent_id
email
password
How would I define this self referencing relationship in Eloquent ORM?
I had some success like this, using your exact DB table.
User Model:
class User extends Eloquent {
protected $table = 'users';
public $timestamps = false;
public function parent()
{
return $this->belongsTo('User', 'parent_id');
}
public function children()
{
return $this->hasMany('User', 'parent_id');
}
}
and then I could use it in my code like this:
$user = User::find($id);
$parent = $user->parent()->first();
$children = $user->children()->get();
Give that a try and let me know how you get on!
I had a chain of self referencing contracts (a contract can be continued by another contract) and also needed self referencing. Each contract has zero or one previous and also zero or one next contract.
My data table looked like the following:
+------------------+
| contracts |
+------------------+
| id |
| next_contract_id |
+------------------+
To define the inverse of a relationship (previous contract) you have to inverse the related columns, that means setting
* foreign key column on the model table
* associated column on the parent table (which is the same table)
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Contract extends Model {
// The contract this contract followed
function previousContract()
{
// switching id and next_contract_id
return $this->belongsTo('App\Contract', 'id', 'next_contract_id');
}
// The contract that followed this contract
function nextContract()
{
return $this->belongsTo('App\Contract');
// this is the same as
// return $this->belongsTo('App\Contract', 'next_contract_id', 'id');
}
}
See http://laravel.com/docs/5.0/eloquent#one-to-one for further details.