Limiting retrieved columns when using withPivot on belonsToMany relationship - php

I have a model called Shifts with a belongsToMany relationship to a shift_employee table that acts as a pivot table to record applications for employees to shifts. I also have a scope so that I can return applications with shift objects. Here is part my Shift model:
class Shift extends Model
{
//
use SoftDeletes;
use \App\Http\Traits\UsesUuid;
protected $guarded = [];
public function applications()
{
return $this->belongsToMany(Employee::class, 'shift_employee')->as('application')->withTimestamps()->withPivot('shortlisted');
}
...
public function scopeWithApplications($query)
{
$query->with('applications');
}
...
}
My shift_employee pivot table is pretty simple and the structure is shown below. I have one extra field to determine if an application has been shortlisted:
Schema::create('shift_employee', function (Blueprint $table) {
$table->primary(['employee_id', 'shift_id']);
$table->uuid('employee_id');
$table->uuid('shift_id');
$table->boolean('shortlisted')->default(false);
$table->timestamps();
$table->foreign('employee_id')
->references('id')
->on('employees');
$table->foreign('shift_id')
->references('id')
->on('shifts')
->onDelete('cascade');
});
Below is my API show function for retrieving shift info:
public function show($id)
{
$shift = Shift::where('id', $id)
->with...()
->withApplications()
->with...()
->first();
return response([
'shift' => $shift,
]);
}
This is the response that I'm getting:
"shift": {
"id": "2b91f55b-c0ff-4bdb-abc4-02604ba6a161",
"some_field": "some_value",
...
"applications": [
{
some_field: "some_value",
...
application: {
shift_id: "2b91f55b-c0ff-4bdb-abc4-02604ba6a161",
employee_id: "some_uuid",
created_at: ...,
updated_at: ...,
shortlisted: 0
}
},
{
...
}
]
...
}
What I want to do, is to replace the whole "application" inner object with only the field "shortlisted" from the pivot table so that it looks like this:
"shift": {
"id": "2b91f55b-c0ff-4bdb-abc4-02604ba6a161",
"some_field": "some_value",
...
"applications": [
{
some_field: "some_value",
...
shortlisted: 0
}
},
{
...
}
]
...
}
How can I do that? Ideally an eloquent call to something like withPivot but that excludes other fields and does not return an object. I couldn't find it in the docs, but does something like that exist?

i think that the most straightforward way is to make independent relation based on the pivot table using pivot model:
class ShiftEmployee extends Pivot
{
protected $table='shift_employee';
}
now the new relation in Shift Model:
class Shift extends Model
{
public function shortlistedApplications()
{
return $this->hasMany(ShiftEmployee::class,'shift_id');
}
public function scopeWithShortlistedApplications($query)
{
$query->with('shortlistedApplications:shift_id,shortlisted');
}
}
now this new scope would bring the data you want

What I think you need is to only load the shortlisted attribute of your employee's application in your scopeWithApllications:
public function scopeWithApplications($query)
{
$query->with('applications.application:id,shortlisted');
}
This will still return an Application instance as a relationship, but will only load it's shortlisted attribute. Then, after retrieval, you can map your collection in order to merge the application's attribute to your employee, if that's really important. But in terms of data shortage, this will do the trick.

In your application model use withPivot method. Like this:
public function applications(){
return $this->belongsToMany('App\Application')
->withPivot('shortlisted')
->withTimestamps();}
You can use this link for more clear example
https://laraveldaily.com/pivot-tables-and-many-to-many-relationships/

Related

Beauty name instead id

I have a two table:
Users:
id,
name,
role_id
Roles:
id,
name
And i will response to requset like this:
user_id, user_name, role_name (not the role_id)
My controller for route function:
public function getUserList(): JsonResponse {
$users = User::All()->toArray();
return response()->json($users);
}
How include roles.name field in the response ?
Create a relationship on your User model.
class User extends Authenticable
{
public function role()
{
return $this->belongsTo(Role::class);
}
}
Include the relationship using with().
$users = User::with('role')->get();
This will transform it into the following structure, when the models are transformed to JSON.
[
{
"name": "Martin",
"email": "me#email.com",
"role": {
"name": "admin"
}
}
]
Working with Laravel, i will call it an anti pattern, transforming objects or data structures to arrays. In between classes, keep em objects for flexibility. In controllers Laravel, is smart enough to automatically transform your data. So the following code, is enough for a JSON route.
public function getUserList()
{
return User::with('role')->get();
}
public function getUserList(): JsonResponse {
return response()->json(User::with('role')->get());
}
Note: You don't need to call ->toArray() when returning as a JSON response; that is done automatically
https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
Then user.role.name on your front-end.
If you want user.role_name instead of user.role.name, then you'll need to define an accessor and append it:
User.php:
class User extends Model {
public $appends = ['role_name'];
public function role() {
return $this->belongsTo(Role::class);
}
public function getRoleNameAttribute() {
return $this->role->name;
}
}
Now, user.role_name will be available.

Associating multiple single instances of the same model in Laravel

I am working on a project in which there are events, which each relate to two single forms on two separate relations – booking and survey. These forms are identically constructed, making it seem unnecessary to use two entirely distinct form models – I instead wanted to use a polymorphic relation, but it appears that isn't possible.
What is the appropriate way to structure this relationship?
Events have one or no booking form
Events have one or no survey form
Forms are a separate, single table
What I have tried:
Polymorphic relationship: Not compatible with two relations to the same model.
Has one relationship: This used a booking_id and survey_id but refused to set either of these fields.
Has many relationship with a type field: Made it difficult to easily save the forms, as it wasn't possible to save to the single relationship. There was also no restriction on the number of forms.
class Event extends Model
{
public function booking()
{
return $this->hasOne(Form::class, 'id', 'booking_form_id');
}
public function survey()
{
return $this->hasOne(Form::class, 'id', 'survey_form_id');
}
}
...
class Form extends Model
{
public function event()
{
return $this->belongsTo(Event::class);
}
}
...
$event = new Event;
$event->name = 'Event';
$event->save();
$booking = new Form;
$booking->name = 'booking';
$event->booking()->save($booking);
$survey = new Form;
$survey->name = 'survey';
$event->survey()->save($survey);
...
Schema::create('events', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->unsignedInteger('booking_form_id')->nullable()->index();
$table->unsignedInteger('survey_form_id')->nullable()->index();
$table->timestamps();
});
Schema::create('forms', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
What would be preferable:
Using a polymorphic relationship which would allow forms to be used in other parts of the application.
Using multiple hasOne relationships to limit the number of forms to one for each type.
I think you got your param order wrong. It's hasOne($related, $foreignKey, $localKey)
class Event extends Model
{
/* if you haven't changed the default primary keys, $localKey should be equal to 'id' */
public function booking()
{
return $this->belongsTo(Form::class, 'booking_form_id');
}
public function survey()
{
return $this->belongsTo(Form::class, 'survey_form_id');
}
}
class Form extends Model
{
public function booking_event()
{
return $this->hasOne(Event::class, 'booking_form_id');
}
public function survey_event()
{
return $this->hasOne(Event::class, 'survey_form_id');
}
}
Now there's 2 ways you can go about this.
If a Form can belong to both kind of events, you need to return a collection when accessing $form->event.
If a Form can belong to only one kind of event, you need to guess which kind and return the model when accessing $form->event.
# Form model
# 1. can be achieved using an accessor. Cannot be eager loaded but can be appended with the $appends Model property
public function getEventsAttribute()
{
return collect([$this->booking_event, $this->survey_event]);
}
# Form model
# 2. can be achieved using a relationship that guesses which relation it should return. Since it returns a relationship, it can be eager loaded.
public function event()
{
return ($this->booking_event()->count() != 0) ? $this->booking_event() : $this->survey_event();
}

how to make a relationship of users with reporter table

I have a table named reports in which i have two columns. One is reported_post_id and the other is reporter_id. In reporter_id the value can be more than 1 and it's json. I want to make a relationship between the unique reported_post_id with the many reporter_id which is already saved in column as json data.
public function reported() {
return $this->hasMany('App\Models\Repoters', 'reported_post_id');
}
public function reporters() {
return $this->belongsTo('App\Model\Repoters', 'reporter_id');
}
$column = [\DB::raw("*"), \DB::raw("id as postable_id"),\DB::raw("report_type as postable_type")];
$data = Repoters::select($column)->with("repos")->orderBy('id', 'DESC')->paginate(50)->unique("reported_id");
I want that to show all data of reported_post_id with reporter_id.
You could make use of the eloquent-json-relations package.
Install it:
composer require staudenmeir/eloquent-json-relations:"^1.1"
Then in your User model (or the relevant one):
class Repoters extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function reported() // or the name that you want
{
return $this->hasManyJson('App\Model\Repoters', 'reporter_id');
} // ^^^^^^^^^^^the json column.
public function reporter() // or the name that you want
{
return $this->belongsTo('App\Model\Repoters', 'reported_post_id');
}
}
From the package documentation:
Many-To-Many Relationships
This package also introduces two new relationship types:
BelongsToJson and HasManyJson
On Laravel 5.6.25+, you can use them to implement many-to-many
relationships with JSON arrays.
In this example, User has a BelongsToMany relationship with
Role. There is no pivot table, but the foreign keys are stored as an
array in a JSON field (users.options):
Array of IDs
By default, the relationship stores pivot records as an array of IDs:
class User extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
protected $casts = [
'options' => 'json',
];
public function roles()
{
return $this->belongsToJson('App\Role', 'options->role_ids');
}
}
class Role extends Model
{
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;
public function users()
{
return $this->hasManyJson('App\User', 'options->role_ids');
}
}

Eloquent Relationship Between Tables in Laravel

I have three tables:
collections which has id, name
genre_collection which has id, genre_id, collection_id
genres which has id, name
I want to retrieve data from collections with generes.
Collections Model
class Collections extends Model{
public function genres(){
return $this->hasMany('App\Models\GenreCollectionRelationships', 'genre_id' , 'id');
}
}
generic_collection
class GenreCollectionRelationships extends Model{
public function genre(){
return $this->hasOne('App\Models\Genres', 'id', 'genre_id');
}
}
Search Controller
class SearchController extends Controller{
$collection->genres;
foreach($collection->genres as $item){
$item->genre;
}
}
This code is working fine. And the output is
Actual
"genres": [{
"id": 1,
"genre_id": 1,
"collection_id": 1,
"created_at": "2019-02-07 17:13:36",
"updated_at": "2019-02-07 17:13:36",
"genre": {
"name": "Action",
"meta": null
}
}]
Is there any way i could directly get the output as shown below
Expected
"genres": [ {
"name": "Action",
"meta": null
}]
I tried hasManyThrough, belongsToMany but nothing worked out.
Note. I am on laravel 5.7
Thanks in advance.
You could build your own query to achieve what you are looking for. Try this:
$collection = Collection
::join('genres', 'genre.id', '=', 'collections.genre_id')
->select('collections.*', 'genres.name','genre.meta')
->get();
I find your code a bit hard to follow...
Let me try and see if I understood it correctly...
You basically have two models:
Model Collection saved in table collections
Model Genre saved in table genres
Since you have a many to many relationship between them, you need a third table to link the both of them together.
By naming convention, Laravel expects you to name it based on the two models, ordered alphabetically. So to create a link between collections and genres, you would need to create a table collection_genre which has a collection_id as a reference to the collections table, and likewise a genre_id to identify the linked genre.
You can then define your relationships as follows:
class Collection extends Model {
public function genres() {
$this->belongsToMany(\App\Models\Genre::class);
}
}
and
class Genre extends Model {
public function collections() {
$this->belongsToMany(\App\Models\Collection::class);
}
}
Now, I'm not sure what your controller looks like as the question has some invalid code to it, but I suspect you want to search the genres for a given collection.
Your code could like like this:
Class CollectionController extends Controller {
function getGenres(Collection $collection) {
return $collection->genres;
}
}
This would return the genres for the given collection.
If you want to format this, you could create an Eloquent Resource for this:
Class CollectionResource extends Resource {
public function toArray() {
return [
'name' => $this->name,
'meta' => $this->meta
];
}
}
In your controller you can then do:
Class CollectionController extends Controller {
function getGenres(Collection $collection) {
return CollectionResource::collection($collection->genres);
}
}
in your collection model
class Collections extends Model
{
protected $table='collections';
public $primaryKey='id';
protected $fillable = ['name'];
public function genres()
{
return $this->belongsToMany('App\Model\Genres','genre_collection','collection_id','genre_id')->withTimestamps();
}
}
in your genres model
class Genre extends Model {
protected $table='genres';
public $primaryKey='id';
protected $fillable = ['name'];
public function collections()
{
return $this->belongsToMany('App\Model\Collections','genre_collection','genre_id','collection_id')->get();
}
}
You are creating many to many relationship between collections and genre using genre_collection pivot table. In that case, belongsToMany is appropriate. And you don't need any model for genre_collection table.
Collections model
class Collections extends Model
{
public function genres(){
return $this->belongsToMany('App\Models\Genres', 'genre_collection', 'genre_id', 'collection_id');
}
}
Genres model
class Genres extends Model
{
public function collections(){
return $this->belongsToMany('App\Models\Collections', 'genre_collection', 'collection_id', 'genre_id');
}
}
SearchController
class SearchController extends Controller
{
foreach($collection->genres as $item){
$item->genre; // get genre info
}
}
I'm assuming that you want to access Generic directly from collection . If this is the case you can define a many-to-many relationship in collection model directly to generic model to access it . Please refer this : https://laravel.com/docs/5.7/eloquent-relationships#many-to-many . Sorry if I'm wrong

Laravel: Sorting a collection with a many to many relationship

I have two tables: assessments and benchmarks. benchmarks has a field called content. There is a many to many relationship between them: assessment_benchmark. I want to sort a collection of records from the assessment_benchmark table by the content attribute of the corresponding benchmark. I have tried:
$sorted = AssessmentBenchmark::all()->sortBy(function($assessmentBenchmark){
return $assessmentBenchmark->benchmark->content;
});
But this just does not work (it just returns the original order). However, when I return $assessmentBenchmark->comment for example, it does work (comment is a field in assessment_benchmark).
The models look like this:
class AssessmentBenchmark extends Model
{
public function benchmark()
{
return $this->belongsTo(Benchmark::class);
}
public function assessment()
{
return $this->belongsTo(Assessment::class);
}
}
class Benchmark extends Model
{
public function assessments()
{
return $this->belongsToMany(Assessment::class);
}
}
class Assessment extends Model
{
public function benchmarks()
{
return $this->belongsToMany(Benchmark::class);
}
}
Well, you can use below query for sorting, I'm gonna use Assessment model, because, I'm never use pivot modal before. Actually, I never had pivot model..
$assessments = Assessment::with(["benchmarks"=>function($query){
$query->orderBy("content","DESC");
}])
With method aşso provide you eagerloading, so when you put $assessments in iteration , you won't make new query for each relation
From chat discussion, it found that you have pivot field and for that you can change your belongsToMany relationship like this
class Benchmark extends Model
{
public function assessments()
{
return $this->belongsToMany(Assessment::class)->withPivot('comment','score')->withTimestamps();
}
}
class Assessment extends Model
{
public function benchmarks()
{
return $this->belongsToMany(Benchmark::class)->withPivot('comment','score')->withTimestamps();
}
}
Now fetch data
$assessment = Assessment::with(['benchmarks' => function($query){
$query->orderBy('content', 'desc');
}])->find($assessmentId);
In view you can render it like this
#foreach($assessment->benchmarks as $benchmark)
<tr>
<td>{{$benchmark->id}}</td>
<td>{{$benchmark->name}}</td>
<td>{{$benchmark->pivot->score}}</td>
<td>{{$benchmark->pivot->comment}}</td>
</tr>
#endforeach
For update you can use updateExistingPivot
For details check ManyToMany relationship https://laravel.com/docs/5.6/eloquent-relationships#many-to-many

Categories