Laravel: How to get records from a pivot table? - php

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

Related

How to search relationship in laravel?

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.

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 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 Query builder in many to many Relationship

Let's consider that I have a model called Sportman and another one Sport who are linked via a pivot table : many to many relationship.
A sample code for their migrations.
# Sportmans migration
Schema::create('sportsmans', function (Blueprint $table) {
$table->increments('id');
$table->string('firstname');
$table->string('lastname');
});
# Sports migration
Schema::create('sports', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description');
});
Here is their relationship in models :
# Defining association in Sportsman model
public function sports(){
return $this->belongsToMany( Sport::class, 'sportsman_has_sports', 'person_id', 'sport_id' );
}
# Defining association in Sports model
public function sportsman(){
return $this->belongsToMany( Sportsman::class );
}
How can I using Laravel with Eloquent get the sportsman that play:
Only "Footbal"
Box or "Swimming"
Both "Tennis and Basket-ball
Here is what I tried to do for question 2 :
Sportsman::with(['sports:person_id,id,name'->whereHas('sports', function ($query) {
$query->whereIn('name', ['Box', 'Swimming'] );
});
Most difficult is the question 3
You put a subquery on whereHas function...
Sportsman::whereHas('sports', function ($query) {
$query->whereIn('name', ['Box', 'Swimming'] );
})
->with('sports')
->get();
// Below is your last question
Sportsman::where(function ($query) {
$query->whereHas('sports', function ($subquery) {
$subquery->where('name', 'Tennis');
});
$query->whereHas('sports', function ($subquery) {
$subquery->where('name', 'Basket-ball');
});
})
->with('sports')
->get();

Is it possible to run a join on a variable created by a sub-query?

Right now I am running a sub-query to get the most recent status for a server, this sub-query is returning through the variable last_status.
//This is ran when WithLastStatusDate() is called
$query->addSubSelect('last_status', ServerStatus::select('status_id')
->whereRaw('server_id = servers.id')
->latest()
);
$servers = Server::WithLastStatusDate()
->OrderBy('servers.id', 'desc')
->where('servers.isPublic', '=', 1)
->get();
What I am trying to do now is do a join on this so that it gives me the actual name of the status based on the result of this query in the statuses table. I have tried to do a simple left join but am getting the error that the last_status column isn't found.
$servers = Server::WithLastStatusDate()
->OrderBy('servers.id', 'desc')
->where('servers.isPublic', '=', 1)
->leftjoin('statuses','servers.last_status', '=', 'statuses.id')
->get();
Can anyone point me in the right direction on how to accomplish this?
EDIT::
Server Table:
Schema::create('servers', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name');
$table->string('url');
$table->boolean('isPublic');
$table->timestamps();
});
Server_statuses Table:
Schema::create('server_statuses', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('server_id')->unsigned();
$table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade');
$table->integer('status_id')->unsigned();
$table->foreign('status_id')->references('id')->on('statuses');
$table->timestamps();
});
statuses table:
Schema::create('statuses', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('key');
$table->string('status');
$table->timestamps();
});
What $servers looks like after sub-query:
Raw SQL of query:
select `servers`.*, (select `status_id` from `server_statuses` where server_id = servers.id order by `created_at` desc limit 1) as `last_status` from `servers` where `servers`.`isPublic` = '1' order by `servers`.`id` desc
EDIT 2::
$servers = DB::table('servers as sv')
->join('server_statuses as ss', 'sv.id', '=', 'ss.server_id')
->join('statuses as st', 'ss.status_id', '=', 'st.id')
->WithLastStatus()
->OrderBy('servers.id', 'desc')
->where('servers.isPublic', '=', 1)
->get();
Combine LEFT JOINs with a subquery WHERE clause:
$servers = Server::select('servers.*', 'statuses.status as status_name')
->leftJoin('server_statuses', function($join) {
$join->on('server_statuses.server_id', '=', 'servers.id')
->where('server_statuses.id', function($query) {
$query->select('id')
->from('server_statuses')
->whereColumn('server_id', 'servers.id')
->latest()
->limit(1);
});
})
->leftJoin('statuses', 'statuses.id', '=', 'server_statuses.status_id')
->where('servers.isPublic', '=', 1)
->orderBy('servers.id', 'desc')
->get();
Since i'm not sure what exactly do you want to get from your query i going with a long solution and add some examples. With these tables you should have these models:
Server model:
class Server extends Model {
public function statuses() {
return $this->belongsToMany(Status::class, 'server_statuses');
}
}
Status model:
class Status extends Model {
public function servers() {
return $this->belongsToMany(Server::class, 'server_statuses');
}
}
Examples:
Get last status of server:
Server::find($serverId)->statuses()->latest()->first()->status;
Get all server statuses:
Server::find($serverId)->statuses;
Get server's specific status:
Server::find($serverId)->statuses()->where('status', 'SomeStatus')->get();
Get servers that have specific status:
Server::whereHas('statuses', function ($join) use ($status) {
return $join->where('status', $status);
})->get();
Hope you find your answer.
As far as I understand, both your Server and Status models have a OneToMany relationship to ServerStatus. In this case, you could fake a OneToOne relationship on your Server model which is selected as the latest row of serverStatuses():
class Server
{
public function serverStatuses()
{
return $this->hasMany(ServerStatus::class, 'server_id', 'id');
}
public function latestServerStatus()
{
return $this->hasOne(ServerStatus::class, 'server_id', 'id')
->latest(); // this is the most important line of this example
// `->orderBy('created_at', 'desc')` would do the same
}
}
class ServerStatus
{
public function server()
{
return $this->belongsTo(Server::class, 'server_id', 'id');
}
public function status()
{
return $this->belongsTo(Status::class, 'status_id', 'id');
}
}
class Status
{
public function serverStatuses()
{
return $this->hasMany(ServerStatus::class, 'status_id', 'id');
}
}
You can then also eager load the latest status for your servers as well as the status itself:
Server::with('latestServerStatus.status')->get();
Please be aware that $server->latestServerStatus is no collection but one object, just like in a normal OneToOne relation.

Categories