Laravel fetch only pivot columns in many to many relationship - php

I have a User model that relates to a Section model through a pivot model UserTrainingSection. I have a pivot table that stores the foreign keys for both tables called section_user.
I have 2 additional pivot table columns called completed and completed_date.
The problem I am having is that when I fetch my data it returns all the columns from the User model along with the additional pivot columns.
class Section extends Model
{
public $table = "sections";
public $fillable = [
'id',
'name',
'description',
'parent',
'position',
'completion_percentage'
];
public function users()
{
return $this->belongsToMany(User::class, 'section_user')->using('App\Models\UserTrainingSection')->withPivot('completed', 'completed_date');
}
}
In my API service I fetch my data like this:
Section::with(['users' => function ($q) {
$q->where('users.id', Auth::user()->id);
}])->first();
How do I only return the pivot table columns and exclude the columns from the user table?
At the moment it returns something like this:
"sections": [
{
"id": 2,
"name": "Subsection 1 training",
"description": null,
"parent": 1,
"position": 2,
"completion_percentage": null,
"created_at": "2018-05-04 09:54:09",
"updated_at": "2018-05-11 09:14:59",
"users": [
{
"id": 1,
"name": "Test",
"email": "test#test.com",
"created_at": "12-04-2018 14:51:42",
"updated_at": "2018-04-19 14:14:36",
"pivot": {
"section_id": 2,
"user_id": 1,
"completed": 1,
"completed_date": "31/05/2018",
"expires": "31/05/2019"
},
}
]
}
]
What I would like to return is something like this:
"sections": [
{
"id": 2,
"name": "Subsection 1 training",
"description": null,
"parent": 1,
"position": 2,
"completion_percentage": null,
"created_at": "2018-05-04 09:54:09",
"updated_at": "2018-05-11 09:14:59",
"users": [
{
"pivot": {
"section_id": 2,
"user_id": 1,
"completed": 1,
"completed_date": "31/05/2018",
"expires": "31/05/2019"
}
}
]
}
]
So I basically get rid of the user data I don't want and only return the pivot data.

As per your expected output i guess you need data from junction model only, If that is the case i suggest you to define direct mapping of Section and UserTrainingSection
class Section extends Model
{
public function training_users()
{
return $this->hasMany('App\Models\UserTrainingSection', 'section_id');
}
public function users()
{
return $this->belongsToMany(User::class, 'section_user')->using('App\Models\UserTrainingSection')->withPivot('completed', 'completed_date');
}
}
In query you can simply do
Section::with('training_users')->first();

In your with statement you are fetching all User data by definition.
You can add a select statement to only get the results you want. You can add that either in the relationship in your model, or where you access the model in your API.
Example 1:
Section::with(['users' => function ($q) {
$q->where('users.id', Auth::user()->id)->select('completed', 'completed_date');
}])->first();
Example 2:
public function users()
{
return $this->belongsToMany(User::class, 'section_user')->using('App\Models\UserTrainingSection')->withPivot('completed', 'completed_date')->select('completed', 'completed_date');
}
(Not tested)
See the documentation

Related

Laravel: How do i return data combinely when one table is related to more than one table?

So, in my application I have models: user, category, service(i.e subcategory) and userService.
There exists many to many relationship between user and category, user and service. And one to many between category and service.
// Tables
Users : id, name, email, password
Categories : id, name
Services : id, name, category_id
user_services: id, user_id, category_id, service_id
Relationship is defined in model as:
// User Model
public function services()
{
return $this->belongsToMany('App\Models\V1\Service', 'user_services', 'user_id', 'service_id');
}
public function categories()
{
return $this->belongsToMany('App\Models\V1\Category', 'user_services', 'user_id', 'category_id');
}
// category model
public function services()
{
return $this->hasMany(Service::class);
}
After populating my db with some random data, the user_services table look like this:
// user_services table
id user_id category_id service_id
1 1 1 1
2 1 1 2
3 2 1 1
so far code is good but when i want particular user with services and categories, i am not able to get it as i expected.
The eloquent method that used to fetch user with services and categories:
public function userWithCategoryServices(Request $request)
{
return User::where('id', $request->id)->with(categories.services)->get();
}
The problem with this is:
As there is case one user with one category can have multiple services, so this eloquent method will return same category multiple times. But i want all services belonging to one category grouped in single category key for that particular user.
Another problem, instead of fetching services of that particular user only,but it will fetch all services from db for users particular category.
[
{
"id": 1,
"name": "Ashish Bogati",
"email": "abbaasdaashisdh23#gmail.com",
"categories": [
{
"id": 1,
"name": "IT",
"pivot": {
"user_id": 1,
"category_id": 1
},
"services": [
{
"id": 1,
"name": "Web Developer",
"category_id": 1,
},
{
"id": 2,
"name": "Web Design",
"category_id": 1
},
{
"id": 3,
"name": "Database Design",
"category_id": 1
}
]
},
{
"id": 1,
"name": "IT",
"pivot": {
"user_id": 1,
"category_id": 1
},
"services": {
"id": 1,
"name": "Web Developer",
"category_id": 1
},
{
"id": 2,
"name": "Web Design",
"category_id": 1
},
{
"id": 3,
"name": "Database Design",
"category_id": 1
}
]
}
]
}
]
As in this response, category name IT is repeated twice for user and all services are fetched for that category instead of for particular user only.
Response i wanted:
[
{
"id": 1,
"name": "Ashish Bogati",
"email": "abbaasdaashisdh23#gmail.com",
"categories": [
{
"id": 1,
"name": "IT",
"pivot": {
"user_id": 1,
"category_id": 1
},
"services": [
{
"id": 1,
"name": "Web Developer",
"category_id": 1,
},
{
"id": 2,
"name": "Web Design",
"category_id": 1
},
]
}
]
the database design is a little strange.we meet duplicate category_id in user_services.but that's is not the problem
//in service model
public function category()
{
return $this->hasOne(Category::class);
}
public function userWithCategoryServices(Request $request)
{
$services = User::where('id', $request->id)->with("services.category")->get();
$servicesGrouped = $services['services']->groupBy('category_id');
}
the services will be right.there will be no more unwanted data.
this is so far we can do by Laravel eloquent.
because the relation map is user->services->category.not user->categories->services
use php to build the structure of the wanted response.
But as Restful principle.you should make api pure.which means one api give only one type of entity.client will be more clear with the rest api and deal with complex structure themself.
public function userWithCategoryServices(Request $request)
{
$services = User::where('id', $request->id)->with("services.category")->get()->toArray();
$categories = []
for($services as $service){
if(!isset($categories[$service['category_id']])){
$categories[$service['category_id']] = $service['category'];
$categories[$service['category_id']]['services'] = [];
}
unset($service['category']);
$categories[$service['category_id']]['services'][] = $service;
}
$categories = array_values($categories);
unset($services['services']);
$services['categories'] = $categories;
return $services;
}

Laravel Eloquent how to get relationship self object?

I have tables with below relationship
And my HousingAdvertisement model has
public function nearPlaces()
{
return $this->hasMany(HousingAdNearPlace::class);
}
and HousingAdNearPlace
public function nearPlace()
{
return $this->hasOne(NearPlace::class, 'id');
}
when I make query like this:
HousingAdvertisement::with('nearPlaces.nearPlace')->where('user_id', '=', auth()->user()->id)->get();
I got HousingAdNearPlace object in HousingAdvertisement model:
[...
{
...,
"near_places": [
{
"id": 27,
"housing_advertisement_id": 48,
"near_place_id": 3,
"created_at": "2021-06-29T12:23:35.000000Z",
"updated_at": "2021-06-29T12:23:35.000000Z",
"near_place": null
},
{
"id": 28,
"housing_advertisement_id": 48,
"near_place_id": 4,
"created_at": "2021-06-29T12:23:35.000000Z",
"updated_at": "2021-06-29T12:23:35.000000Z",
"near_place": null
}
]
...]
How can I got self NearPlace model like this:
[...
{
...,
"near_places": [
{
"id": 3,
"name": "Park",
"slug": "park",
"created_at": "2021-06-29T06:25:57.000000Z",
"updated_at": "2021-06-29T06:25:57.000000Z"
},
{
"id": 4,
"name": "Beach",
"slug": "beach",
"created_at": "2021-06-29T06:25:57.000000Z",
"updated_at": "2021-06-29T06:25:57.000000Z"
}
]
...]
You need "Has Many Through" relationship on HousingAdvertisement
public function nearPlaces()
{
return $this->hasManyThrough(NearPlace::class, HousingAdNearPlace::class);
}
And also define id keys as in ducumentation: https://laravel.com/docs/8.x/eloquent-relationships#has-many-through-key-conventions
You need to use laravel Model Accessor for generating slug from HousingAdvertisement's name row.
For that use -
public function slug()
{
return Str::lower($this->name);
}
This will make a slug from name attribute. Add it on your HousingAdvertisement model. Now you need to cast it with query builder.
For that use -
protected $casts = [
'slug' => 'string',
];
Add this on your HousingAdvertisement model. Finally you can query your database like -
HousingAdvertisement::find(auth()->user()->id)->get(['id', 'name', 'slug', 'created_at', 'updated_at']);

Select * from a pivot table in laravel

I have a many to many relation with table users,items and the pivot table user_item and i need to call the query : Select * from user_item where user_id=$user->id in laravel and return the results in json format. I try with
$user=User::find(session('user_id'))->items()->get();
return response ()->json($user);
But it doesn't work. How can I do that?
class User extends Authenticatable {
public function items (){
return $this->belongsToMany ("App\Models\Item", "user_item", "user", "item");
}
}
class Item extends Models {
public function users (){
return $this->belongsToMany ("App\Models\User", "user_item", "item", "user");
}
}
You can define columns in your pivote table using withpivot method
public function items (){
return $this->belongsToMany ("App\Models\Item", "user_item",
"user", "item")->withPivot(['column1', 'column2','another_column']);
}
to get relation instead of using get(), you should use like below:
$user=User::find(session('user_id'))->items;
return response ()->json($user);
above will give below json result:
[{
"id": 4,
"name": "PC",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
},
{
"id": 5,
"name": "Phone",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
}]
you can also include pivot in use user model using with():
$user=User::with('items')->find(session('user_id'));
return response ()->json($user);
give json result something like:
{
"id": 1,
"name": "User Name",
"email": "email#user.com",
"created_at": null,
"updated_at": null,
"items": [{
"id": 4,
"name": "PC",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
},
{
"id": 5,
"name": "Phone",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
}]
}]

Eloquent Model relationship does not return sub-relationship specific column value

I have attempted to achieve to pull the user's info and the product details (Which specified the nickname and name) when the product name equal to "Oops" but I have no idea why the getProducts does not return any things.
User Model
public function getProducts(){
return $this->hasMany('App\Models\Product','users_id');
}
Product Model
public function users(){
return $this->belongsTo(User::class);
}
The code of pulling data:
$products = User::with(['getProducts' => function($query){
$query->select("users_id","name","nickname");
}])->get();
The current output:
"data": [
{
"id": 1,
"name": "John Smith",
"email": "john.smith#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-08T13:29:13.000000Z",
"role": 0,
"get_products": [
]
},
{
"id": 2,
"name": "Kelvin Ooi",
"email": "kelvin.ooi#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-13T12:07:11.000000Z",
"role": 1,
"get_products": [
{
"nickname":"MCD",
"name":"Oops"
},
{
"nickname":"Mary Brown",
"name":"Oops"
},
{
"nickname":"Kentucy",
"name":"KFC"
},
{
"nickname":"Texas Chicken",
"name":"TXS"
}
]
}
]
The expected output
"data": [
{
"id": 1,
"name": "John Smith",
"email": "john.smith#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-08T13:29:13.000000Z",
"role": 0,
"get_products": [
]
},
{
"id": 2,
"name": "Kelvin Ooi",
"email": "kelvin.ooi#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-13T12:07:11.000000Z",
"role": 1,
"get_products": [
{
"nickname":"MCD",
"name":"Oops"
},
{
"nickname":"Mary Brown",
"name":"Oops"
}
]
}
]
getProducts is not a good name for the relationship, lets simply call it products.
public function products()
{
return $this->hasMany('App\Models\Product','users_id');
}
Your code in the comment doesn't work because you specify the where clause in the main query, not the sub query.
return User::with(['products' => function ($query) {
$query->select("users_id","name","nickname");
}])
// This got to be in the sub query.
->where("products.name","Oops")
->get();
So let's update your code to this:
$productName = 'Oops';
return User::with(['products' => function ($query) use ($productName) {
$query->select("users_id","name","nickname", "price")
->where("name","LIKE", "%{$productName}%");
}])
->get();
I have seen your comment to this answer. Let's define a total custom attribute for the User model:
class User extends Model
{
protected $appends = ['total'];
public function getTotalAttribute()
{
// This is higher order message, if you haven't used it: https://laravel.com/docs/8.x/collections#higher-order-messages
return $this->products->sum->price;
}
}
Then the total attribute will be part of any user.

Hide attributes relationship from laravel eloquent resources collection

I'm trying to building API Resource and I wanna hide in collection the relationship attribute.
For example, I want to hide attribute 'permissions' only in RoleCollection. I mean I just only wanna hide this attribute in Collection, not Resource. Because Collection be called from Resource but I don't want to hide it in Resource.
Role.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Role extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
'permissions' => Permission::collection($this->permissions),
];
}
}
RoleCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class RoleCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
}
RoleController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Spatie\Permission\Models\Role;
use App\Http\Resources\Role as RoleResource;
use App\Http\Resources\RoleCollection;
class RoleController extends Controller
{
public function index()
{
$resource = Role::paginate();
return new RoleCollection($resource);
}
public function show($id)
{
$resource = Role::with('permissions')->find($id);
return new RoleResource($resource);
}
}
Response from: api/role/1
{
"data": {
"id": 1,
"name": "Super Administrador",
"created_at": "2019-05-07 16:45:38",
"updated_at": "2019-05-07 16:45:38",
"permissions": [
{
"id": 1,
"name": "user.list"
},
{
"id": 2,
"name": "user.view"
},
{
"id": 3,
"name": "user.save"
},
{
"id": 4,
"name": "user.delete"
}
]
}
}
Response from : /api/roles
{
"data": [
{
"id": 1,
"name": "Super Administrador",
"created_at": "2019-05-07 16:45:38",
"updated_at": "2019-05-07 16:45:38",
"permissions": [
{
"id": 1,
"name": "user.list"
},
{
"id": 2,
"name": "user.view"
},
{
"id": 3,
"name": "user.save"
},
{
"id": 4,
"name": "user.delete"
}
]
},
{
"id": 2,
"name": "Administrador",
"created_at": "2019-05-07 16:45:38",
"updated_at": "2019-05-07 16:45:38",
"permissions": []
}
],
"links": {
"first": "http://127.0.0.1:32773/api/roles?page=1",
"last": "http://127.0.0.1:32773/api/roles?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "http://127.0.0.1:32773/api/roles",
"per_page": 15,
"to": 2,
"total": 2
}
}
One way to do this is to use the whenLoaded method. The whenLoaded will return a MissingValue instance when a relationship has not been loaded. Laravel in turn will exclude this property from your resonse.
It is not only useful for hiding properties in certain responses, but also helps with performance. Currently your resource will do a query to fetch Permission models for every Role when this relationship was not loaded.
Your resource could look like:
return [
...
'permissions' => Permission::collection($this->whenLoaded('permissions')),
];
Laravel docs
One of the best way to handle this stuff is to use Fractals.
You can define a Transformer class for all your model, handle whether to include relationships or not every time you call them, and you can also define what attributes to show or to hide.
Basically you have one and only one point where your model can be serialized in JSON and you put all your logic there.
Also you can JSON-ize whole collections, through the single model transformers, very handy!

Categories