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
Related
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.
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.
This is my Report Model
protected $fillable = [
'site_url',
'reciepients',
'monthly_email_date'
];
public function site()
{
return $this->belongsTo('App\Site');
}
This is my Site Model
public function report()
{
return $this->hasMany('App\Report');
}
This is my ReportController
public function showSpecificSite($site_name)
{
$records = DB::table('reports')
->select('email_date','url','recipient')
->whereHas('sites', function($query){
$query->where('site_name',$site_name);
})
->get();
return view('newsite')->with('records',$records)
->with('site_name',$site_name);
}
My Controller is not yet working as well.
The thing is I would like to copy all the three files from sites table to reports table.
Is it possible in insertInto ?
My code on ReportController shows you that I'm selecting data from reports table but I am the one who puts data to reports table to see the output but it is not yet working because of the it cant reach out the value of site_name even though I already put a relationship between the two tables.
You're not actually using Eloquent in your controller you're just using the Query Builder (DB). This will mean that you don't have access to anything from your Eloquent models.
Try:
$records = \App\Report::whereHas('site', function($query) use($site_name) {
$query->where('site_name', $site_name);
})->get(['id', 'email_date', 'url', 'recipient']);
I've added id to the list of columns as I'm pretty sure you'll need that to use whereHas.
NB to use a variable from the parent scope inside a closure you need to pass it in using use().
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
I have 3 tables: orders, codes, events
I want to be able to pull all events that an order has, but there's an intermediary table that acts as a pivot table. I've been trying to use hasManyThrough and belongsToMany (along with withPivot) without any luck.
Examples:
public function events()
{
return $this->belongsToMany('events'); // tried this, fails
return $this->hasManyThrough('events', 'codes'); // tried this, fails
return $this->hasManyThrough('events', 'codes', 'event_id', 'id'); // tried this, fails
}
Any pointers would be great!
That's a belongsToMany setup. First, the first parameter is the name of the related class. Second, since your pivot table doesn't follow the Laravel naming conventions, you need to specify the name of the pivot table in your relationship definition:
public function events()
{
// first parameter is the name of the related class
// second parameter is pivot table name
return $this->belongsToMany(Event::class, 'codes');
}
With this setup, you can do:
// get an order
$order = Order::first();
// has all the events related to an order
$events = $order->events;
There are many ways to do this. I will show a one you can get it done.
In Order.php model
public function codes(){
return $this->has('App\Http\Code');
}
In Code.php model
public function orders(){
return $this->belongsTo('App\Http\Order');
}
public function events(){
return $this->hasMany('App\Http\Event');
}
In Event.php model
public function codes(){
return $this->belongsTo('App\Http\Code');
}
Then in you Controller, call them to get required data.
In your case you can do it like below:
$orders = Order::with(['codes' => function($q){
$q->with('events');
})->get();
May be you can get them with nested manner(not sure about this because i didn't tried before posting):
$orders = Order::with('codes.events')->get();
put return $orders; in your controller to see the query.
Enjoy!