Laravel whereHas getting the relationship - php

I'm doing a Laravel application to manage events. An Attendant and and Event have a belongsToMany relationship. An attendant has multiple events and viceversa.
The pivot column has an extra field called uuid, this is like the ticket id to the event.
I need to validate the entrance of an Attendant to an Event. So the Attendant scans a QR code with the uuid of the pivot table.
So I have this function
public function validateTicket(Request $request, $ticket) {
$event = Event::fromTicket($ticket);
\Log::info($event);
return new EventResource($event);
}
And the static method fromTicket is:
public static function fromTicket($ticket) {
return static::whereHas('attendants', function($q) use ($ticket) {
$q->where('uuid', $ticket);
})->first();
}
The problem is that I need the relationship too, this because I need to register the hour of entrance of an Attendant, and this entrance could happen many times.
Is there a way to return the relationship too during the whereHas query?
Thanks in advance.

No because it needs the original models relationship to get the pivot table, fear not there is a solution that is very close to the original. Instead include all attendants and do a condition in the include and only the attendent you need will be present.
public static function fromTicket($ticket) {
return static::whereHas('attendants',
function($query) use ($ticket) {
$query->where('uuid', $ticket);
})->with(['attendants' => function ($query) use ($ticket) {
$query->where('uuid', $ticket);
}])->first();
}
This will include the attendants, but only the one with the correct ticket, so now you can do $event->attendants->first(), this is two queries still quite optimized but not optimal, my easiest solution for how you portrayed your problem.

Related

Laravel 5.8 - How sort (orderBy) multiple relationship

I am trying to sort the serials by video views.
Relations:
The Serial has a hasMany relationship to series.
The Series has a hasMany relationship to episodes.
The Episodes has a hasOne relationship to video.
The Video has a hasMany relationship to viewcounts.
<?php
//sort method:
public function mostPopular()
{
$serials = Serial::with(['series.episodes.video' => function ($query) {
$query->withCount(['videoViews' => function($query) {
}])->orderBy('video_views_count', 'desc');
}])->get();
return $serials;
}
//Serial model:
public function series()
{
return $this->hasMany(Series::class);
}
//Series model:
public function episodes()
{
return $this->hasMany(Episode::class);
}
public function serial()
{
return $this->belongsTo(Serial::class);
}
//Episode model:
public function video()
{
return $this->hasOne(Video::class);
}
public function series()
{
return $this->belongsTo(Series::class);
}
//Video model:
public function videoViews()
{
return $this->hasMany(VideoView::class);
}
public function episode()
{
return $this->belongsTo(Episode::class);
}
?>
I expect the sorted serials by video views (series.episodes.video.videoViews), but the actual output is not sorted.
Laravel 5.8
PHP 7
This is a silly one actually but I've learnt that multiple ->sortBy on collections actually are possible with no workarounds. It's just that you need to reverse the order of them. So, to sort a catalogue of artists with their album titles this would be the solution...
Instead of :
$collection->sortBy('artist')->sortBy('title');
Do this :
$collection->sortBy('title')->sortBy('artist');
Because "With" queries run as seperate queries (not subqueries as previously suggested), exposing extrapolated fuax-columns from one query to the other gets rather tricky. I'm sure there's non-documented solution in the API docs but I've never come across it. You could try putting your with and withCount in the orderBy:
Serial::orderBy(function($query) { some combo of with and withCount })
But that too will get tricky. Since either approach will hit the database multiple times, it would be just as performant to do the separation yourself and keep your sanity at the same time. This first query uses a left join, raw group by and raw select because I don't want laravel running the with query as a separate query (the problem in the first place).
$seriesWithViewCounts = VideoView::leftJoin('episodes', 'episodes.id', '=', 'video_views.episode_id')
->groupBy(DB::raw('episodes.series_id'))
->selectRaw("episodes.series_id, count(video_views.id) as views")
->get();
$series = Series::findMany($seriesWithViewCounts->pluck('series_id'));
foreach($series as $s) {
$s->view_count = $seriesWithViewCounts->first(function($value, $key) use ($s) {
return $value->series_id = $s->id
})->video_views_count;
});
$sortedSeries = $series->sortBy('video_views_count');
This will ignore any series that has no views for all episodes, so you may want to grab those and append it to the end. Not my definition of "popular".
I'd love to see a more eloquent way of handling this, but this would do the job.

Laravel get model with relation's pivot condition

I need to get all appeals, that have appeal_stage.expiration_date less than NOW().
Now I have following solution:
public function scopeExpired($query) {
$query->join('appeal_stage', 'appeals.id', 'appeal_stage.appeal_id')
->where('appeal_stage.expiration_date', '<=', new Expression('NOW()'));
}
but resulted model dump shows that joined table is recognized as pivot table:
So, I want to ask - Is there some more convenient way to perform this request?
My suggestions is use Illuminate\Database\Eloquent\Relations\Pivot somehow, bu I do not quiet understand, how Pivot can be used here.
UPD 1
Models has next relations:
public function stages()
{
return $this->belongsToMany(Stage::class)->withPivot('prolongated_count', 'expiration_date')->withTimestamps();
}
public function appeals() {
return $this->belongsToMany(Appeal::class);
}
You should be able to do something like this:
$appeal->stages()->wherePivot('expiration_date', '<', $now)->get()
You should create relationship in appeal model
public function stages()
{
return $this->belongsToMany(Stage::class,'appeal_stage','appeal_id','stage_id')->wherePivot('expiration_date','<',Carbon::now())->withTimestamps();
}
In belongs To Many relationship second argument is your Pivot table name

Laravel Eloquent Query absence through shared relationship

I have a relationship like this
Requests
=> id
public function proposals(){
return $this->hasMany(Proposal::class)
}
Proposals
=> request_id
=> company_id
public function request(){
return $this->belongsTo(Request::class)
}
public function proposals(){
return $this->belongsTo(Company::class)
}
Companies
=> id
public function proposals(){
return $this->hasMany(Proposal::class)
}
I've tought in something like this:
$request->with('proposals')->whereDoesntHave('company', function($query){
$query->where('company_id', '<>', 1);
})->get()
But it didn't work out.
In this scenario how can I retrieve all the requests that one company has not sent a proposal?
EDIT
A short version of my DB Schema
A User can have many Companies, a User can make Requests, a Company respond to this Request through a Proposal
What I am seeing here from your schema is a pivot table relationship.
A Request can belong to many Companies, and a Company can have many Requests. The relationship between the two is the Proposal, which is your pivot table.
So firstly, you want add the correct relationships to your models
class Request
{
public function companies()
{
// You will need to probably set the foreign and local keys as extra parameters here
return $this->belongsToMany(Company::class, 'proposals')->using(Proposal::class);
}
}
and
class Company
{
public function requests()
{
// You will need to probably set the foreign and local keys as extra parameters here
return $this->belongsToMany(Request::class, 'proposals')->using(Proposal::class);
}
}
Finally, you can get the proposal using the pivot which will be a Proposal object.
To address your original query, you would just do:
$requests = Request::with(['companies' => function($q) {
$q->where('companyid', '!=', 1);
}]);

How to block 'follower' laravel

I have created the ability to add and remove friends in a laravel app using a pivot table and many to many relationship.
I am now stuck on the best way to handle blocking a member from friending another.
Right now I have a 'blocked' table with timestamps, blocked_user and created_by.
What would be the best way to check is that relationship exists in the block table before adding the friend?
Presuming you're using models. And that your User model has a relationship blockedUsers:
Edit:
User extends Model {
// ...
public function isBlocked($userId)
{
return (boolean) $this->blockedUsers()
->where('blocked_user_id', $userId)->count();
}
public function blockedUsers()
{
return $this->belongsToMany(User::class, 'blocked_users_table', 'user_id', 'blocked_id');
}
}
Just call this method wherever you think fits. You could call it from addFriend to check if this user is blocked or not.
the aproach could be like this:
$blocked_user = DB::table('blocked')
->where([
['created_by', '=', $user_id],
['blocked_user', '=', $blocked_user_id],
])
->first();
# user isn't blocked
if (empty($blocked_user)) {
# add friend procedure
}

Laravel -- Flatten data appended via `with`

I've got two models, User and Seminar. In English, the basic idea is that a bunch of users attend any number of seminars. Additionally, exactly one user may volunteer to speak at each of the seminars.
My implementation consists of a users table, a seminars table, and a seminar_user pivot table.
The seminar_user table has a structure like this:
seminar_id | user_id | speaking
-------------|-----------|---------
int | int | bool
The relationships are defined as follows:
/** On the Seminar model */
public function members()
{
return $this->belongsToMany(User::class);
}
/** On the User model */
public function seminars()
{
return $this->belongsToMany(Seminar::class);
}
I am struggling to figure out how to set up a "relationship" which will help me get a Seminar's speaker. I have currently defined a method like this:
public function speaker()
{
return $this->members()->where('speaking', true);
}
The reason I'd like this is because ultimately, I'd like my API call to look something like this:
public function index()
{
return Seminar::active()
->with(['speaker' => function ($query) {
$query->select('name');
}])
->get()
->toJson();
}
The problem is that since the members relationship is actually a belongsToMany, even though I know there is only to ever be a single User where speaking is true, an array of User's will always be returned.
One workaround would be to post-format the response before sending it off, by first setting a temp $seminars variable, then going through a foreach and setting each $seminar['speaker'] = $seminar['speaker'][0] but that really stinks and I feel like there should be a way to achieve this through Eloquent itself.
How can I flatten the data that is added via the with call? (Or rewrite my relationship methods)
Try changing your speaker function to this
public function speaker()
{
return $this->members()->where('speaking', true)->first();
}
This will always give you an Item as opposed to a Collection that you currently receive.
You can define a new relation on Seminar model as:
public function speaker()
{
return $this->belongsToMany(User::class)->wherePivot('speaking', true);
}
And your query will be as:
Seminar::active()
->with(['speaker' => function ($query) {
$query->select('name');
}])
->get()
->toJson();
Docs scroll down to Filtering Relationships Via Intermediate Table Columns

Categories