How to search relationship in laravel? - php

I have two models which are related. I am trying to do a search in orders and only display the actual search results instead of ALL orders of the category and user in which the order was found.
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->foreignId('report_id')->constrained()->cascadeOnDelete();
$table->string('time');
$table->string('date');
$table->integer('issue_number');
$table->boolean('status');
$table->text('description')->nullable();
$table->timestamps();
});
}
So, what I WANT to achieve is the following. What I WANT to be displayed is:
OrderController.php
public function index()
{
$keyword = request('search') ?? null;
$orders = Order::query()->whereIn('user_id', $user->id)->whereIn('category_id', $category->id)
->orWhere(function ($query) use ($keyword) {
$query->when($keyword, function ($query) use ($keyword) {
$query->where('first_name' , 'LIKE' , "%{$keyword}%");
});
})->latest()->paginate(25);
return view('Admin.orders.index', compact('orders'));
}
Order.php
public function user()
{
return $this->belongsTo(User::class);
}
public function report()
{
return $this->belongsTo(Report::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}

If I got you right you want to apply filter to your related table. For that kind of operation you can use whereHas or whereRelation methods of eloquent.
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
or
$posts = Post::whereRelation(
'comments', 'created_at', '>=', now()->subHour()
)->get();
comments is related column.
For more information check Querying Relationship Existence.

Related

Laravel WhereHas only the latest record on the HasMany Relationship

I have Items table that has relation to Histories table.
I want to get count of items that has only latest history.status
I still can't get the exact same count result because its always count all of the histories not the latest one
Here is my code:
create_items_table.php
Schema::create('items', function (Blueprint $table) {
$table->id();
$table->string('code');
$table->string('name');
$table->longText('description')->nullable();
$table->longText('picture')->nullable();
$table->timestamps();
});
create_histories_table.php
$table->foreignId('item_id')->constrained();
$table->string('status')->nullable();
$table->longText('description')->nullable();
$table->dateTime('date')->nullable();
model of Item.php
public function histories(){
return $this->hasMany(History::class);
}
public function latestHistory(){
return $this->hasOne(History::class)->latest();
}
model of History.php
public function item()
{
return $this->belongsTo(Item::class);
}
MyController.php
$items_status['good'] = Item::with('latestHistory')->whereHas('latestHistory', function ($q) {
$q->where('status', 'good');
})->count();
$items_status['broken'] = Item::with('latestHistory')->whereHas('latestHistory', function ($q) {
$q->where('status', 'broken');
})->count();
dd($items_status);
i guess you mean latestOfMany() ?
//Item.php
public function latestHistory() {
return $this->hasOne(History::class)->latestOfMany();
}
Also do you have any solution for count the items that doesn't have
history?
Check docs for doesntHave
$items_status['no_history'] = Item::doesntHave('history')->count();

Laravel 5.8: MorphMany relation returns Empty

Comment Model:
public function commentable()
{
return $this->morphTo();
}
public function comments()
{
return $this->hasMany(Comment::class , 'parent_id' , 'id');
}
public function setCommentAttribute($value)
{
$this->attributes['comment'] = str_replace(PHP_EOL , "<br>" , $value);
}
Post Model:
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
And Controller:
public function show_comments(Post $post)
{
$comments = $post->comments()
->where('approved' , 1)
->where('parent_id', 0)
->latest()
->with(['comments' => function($query) {
$query->where('approved' , 1)->latest();
}])->get();
dd($comments);
return view('post',compact('comments'));
}
Database table Comments:
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('parent_id')->unsigned()->default(0);
$table->boolean('approved')->default(0);
$table->text('comment');
$table->integer('commentable_id')->unsigned();
$table->string('commentable_type');
$table->timestamps();
});
$dd($comments) returns #items: [] or Empty. There are database records and I can access them with another methods.
I did searching alot before asking but no luck.
I was trying to resolve the same issue for a few hours. For anyone searching for the answer :
Check if the commentable_type field in comments table has properly formatted route strings
'commentable_type' => 'App/Models/Comment', // Does not work
'commentable_type' => 'App\Models\Comment', // Works 🥳

Laravel query relationship based on name not id

Hi I am new laravel and struggling a bit on understanding how to query relationships. I am trying to make a basic restful api in laravel and have 3 models
class Book extends Model
{
public function author()
{
return $this->belongsTo(Author::class);
}
public function categories()
{
return $this->belongsToMany('App\Category', 'category_book')
->withTimestamps();
}
}
class Author extends Model
{
public function books(){
return $this->hasMany(Book::class);
}
}
class Category extends Model
{
public function books()
{
return $this->belongsToMany('App\Book', 'category_book')
->withTimestamps();
}
}
Table migrations:
Schema::create('books', function (Blueprint $table) {
$table->engine = "InnoDB";
$table->increments('id');
$table->string('ISBN', 32);
$table->string('title');
$table->integer('author_id')->unsigned();
$table->float('price')->default(0);
$table->timestamps();
});
Schema::create('authors', function (Blueprint $table) {
$table->engine = "InnoDB";
$table->bigIncrements('id');
$table->string('name');
$table->string('surname');
$table->timestamps();
});
Schema::create('categories', function (Blueprint $table) {
$table->engine = "InnoDB";
$table->bigIncrements('id');
$table->string('name');
$table->timestamps();
});
Schema::create('category_book', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('category_id')->unsigned();
//$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->integer('book_id')->unsigned();
//$table->foreign('book_id')->references('id')->on('books')->onDelete('cascade');
$table->timestamps();
});
books is the main table and author has a one to many relationship with books. Category has a many to many relationship with books as a book can be in more than one category.
The books table has an author_id field to link it to the authors table. There is also a pivot table called category_books that contains category_id and book_id to link books to categories
But how do I query books so that it returns only books based on the authors name ?
I would also like to do the same thing based on the category name?
I my books controller i have the following but not sure how to do it correctly
public function index(request $request, Author $author, Category $category)
{
$author = $request->author;
$books = Book::find()->author()->where('name', $author);
$books = Book::with(['categories'])->where('name', $category);
return response()->json($books, 200);
}
By author:
$books = App\Book::whereHas('author', function ($query) use ($authorName) {
$query->where('name', $authorName);
})->get();
By category:
$books = App\Book::whereHas('categories', function ($query) use ($categoryName) {
$query->where('name', $categoryName);
})->get();
whereHas allows you to perform where queries on the specified relationship
Together:
$books = App\Book::whereHas('author', function ($query) use ($authorName) {
$query->where('name', $authorName);
})->whereHas('categories', function ($query) use ($categoryName) {
$query->where('name', $categoryName);
})->get();
To filter both author and book at the same query you may use both queries made by gbalduzzi combined:
$books = App\Book::whereHas('author', function ($query) use ($authorName) {
$query->where('name', $authorName);
})->whereHas('categories', function ($query) use ($categoryName) {
$query->where('name', $categoryName);
})->get();

Laravel: How to get records from a pivot table?

I'm using Lumen 5.1, I have many to many relation between tasks and users
Task model
public function user()
{
return $this->belongsToMany('App\Models\Auth\User', 'task_user');
}
public function domain()
{
return $this->hasOne('App\Models\Domain', 'domain_id');
}
User model
public function tasks()
{
return $this->belongsToMany(Task::class, 'task_user');
}
UserTask model
class UserTask {}
I want to search the get the user of the task, my code is
$tasks = Task::Where(function ($query) use ($domainId) {
$query->where("domain_id", $domainId)
->where("is_done", 0)
->orwherehas('tasks.user.id', \Auth::id)
->orderBy('due_date', 'DESC');
})
->orWhere(function ($query) use ($domainId) {
$query->where("domain_id", $domainId)
->Where("is_done", 1)
->Where("closed_dated", Carbon::today())
->orwherehas('tasks.user.id', \Auth::id)
->orderBy('closed_date', 'ASC');
})
->get();
My question is whereHas correct? Is tasks.user.id correct? Can I get to the user's id that way? I did it that way because of this question
The tech lead tells me that my code is wrong, he would you use where, he said that whereHas when you want to run a closure.
Migrations:
Tasks
public function up()
{
Schema::create($this->getTable(), function (Blueprint $table) {
$table->increments('id');
$table->string('title')->nullable();
$table->dateTime('submit_date');
$table->dateTime('closed_date');
$table->dateTime('due_date');
$table->tinyInteger('is_done')->default(0);
$table->integer('domain_id')->unsigned()->nullable();
$table->foreign('domain_id')->references('id')
->on(self::getTableName('domains'))->onDelete('cascade');
$table->bigInteger('created_by')->unsigned()->nullable();
$table->foreign('created_by')->references('id')
->on(self::getTableName('auth_users', false))->onDelete('cascade');
$table->bigInteger('closed_by')->unsigned()->nullable();
$table->foreign('closed_by')->references('id')
->on(self::getTableName('auth_users', false))->onDelete('cascade');
$table->timestamps();
});
}
public function down()
{
Schema::drop($this->getTable());
}
task_user
public function up()
{
Schema::create($this->getTable(), function (Blueprint $table) {
$table->increments('id');
$table->integer('task_id')->unsigned()->nullable();
$table->foreign('task_id')->references('id')
->on(self::getTableName('tasks'))
->onDelete('cascade');
$table->bigInteger('user_id')->unsigned()->nullable();
$table->foreign('user_id')->references('id')
->on(self::getTableName('auth_users', false))
->onDelete('cascade');
});
}
public function down()
{
Schema::drop($this->getTable());
}
No, whereHas would not be correct for both here. Also, you wouldn't be saying whereHas('tasks...') on the Task model.
NB
The 2nd param for whereHas should be a closure (function) and Auth::id should be Auth::id(). You can also use the auth() helper function instead of the Auth facade if you want to.
The following should give you what you want:
$tasks = Task::where("domain_id", $domainId)
->where(function ($query) use ($domainId) {
$query
->where("is_done", 0)
//whereHas - 1st arg = name of the relationship on the model, 2nd arg = closure
->whereHas('user', function ($query) {
$query->where('id', auth()->id());
});
})
->orWhere(function ($query) use ($domainId) {
$query
//If "is_done" = 1 only when it's been closed then you won't need to check "is_done"
->where("is_done", 1)
->where('closed_by', auth()->id())
->whereDate("closed_dated", '>=', Carbon::today()->startOfDay());
})
->orderBy('due_date', 'DESC')
->orderBy('closed_date', 'ASC')
->get();
The following blog post covers a one to many task assignment example.
https://medium.com/#brice_hartmann/building-a-user-based-task-list-application-in-laravel-eff4a07e2688
Getting tasks for current user, Relation on belongsToMany to the User model would be:
Auth::user()->tasks()->where('is_done',0) .... ->get();
Getting tasks with users:
Tasks::with(['user'])->where('is_done',1) ... ->get();
Authed user conclusion ... I am not 100% sure this is correct:
Auth::user()->tasks()
->where('domain_id', $domainId)
->where(function ($query) {
$query->where('is_done', 1)
->orWhere(function($query) {
$query->where('is_done', 0)
->where('closed_dated', Carbon::today())
});
});
})
->get();

Bring the latest commented topic on top on forum index page

I am building a forum. I want to bring published topics on top whenever users leave a reply. For topics without replies, I want to order them by created_at column.
How do you do that?
Forum controller
public function index()
{
$categories = Category::all();
$topics = Topic::with(['comments' => function ($query) {
$query->orderBy('comments.created_at', 'desc');
}])->paginate(20);
}
Here is my topic table
Schema::create('topics', function (Blueprint $table) {
$table->increments('id');
$table->integer('category_id')->unsigned();
$table->integer('user_id')->unsigned();
$table->string('title');
$table->text('body');
$table->timestamps();
});
Here is my comment table
$table->increments('id');
$table->text('reply');
$table->integer('user_id')->unsigned();
$table->integer('topic_id')->unsigned();
$table->foreign('topic_id')->refrenced('id')->on('topics')->onDelete('cascade');
$table->timestamps();
Comments model
class Comment extends Model
{
protected $fillable = [
'reply',
'user_id',
'topic_id'
];
public function topic()
{
return $this->belongsTo('App\Topic');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
Topic model
class topic extends Model
{
protected $fillable = [
'title',
'body',
'category_id'
];
public function category()
{
return $this->belongsTo('App\category');
}
public function user()
{
return $this->belongsTo('App\User');
}
public function comments()
{
return $this->hasMany('App\Comment');
}
}
still trying to figure this out. any help will be hugely appreciated!!
Try using eager load with constraint:
public function index()
{
$categories = Category::all();
$topics = Topic::with(['comments' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->paginate(20);
return view('forums.index',compact('categories','topics'));
}
DB::table('topics')
->leftjoin('comments', function($join) {
$join->on('topics.id', '=', 'comments.topic_id');
})
->select(DB::raw('IF(comments.id IS NULL,0, 1) as topic_comment'))
->orderBy('topic_comment', 'DESC')
->orderBy('topics.created_at', 'DESC')
->get();
the topic_comment as 1 records you can show on top right and rest wherever you want

Categories