Laravel Eloquent how to get relationship self object? - php

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']);

Related

Laravel 6 pluck in hasManyThrough

Controller:
$files = File::where('agent_id', $user->id)->with('posts')->get();
Model:
public function posts()
{
return $this->hasManyThrough('App\User', 'App\Post', 'id', 'id', 'post_id', 'user_id');
}
So this return a bunch of data, for example:
{
"success": [
{
"id": 2,
"post_id": 1,
"transaction_id": 4,
"agent_id": 2,
"status": 0,
"posts": [
{
"id": 1,
"name": "john",
"email": "john#gmail.com",
"phone": "489797878",
"type": "1",
"verified": 1,
"otp": null,
"created_at": "2019-11-23 10:17:31",
"updated_at": "2019-11-23 10:17:51",
"api_token": null,
"laravel_through_key": 1
}
]
}
...
}
What I want is, exclude some data, like email or verified and etc. I tried pluck('email') and also makeHidden but no success. any idea how can I do this?
How about doing some thing like below, ( haven't tested the code but should give you a clue )
files = File::where('agent_id', $user->id)
->with(['posts' => function ($q) {
$q->select('name','phone'); // specify whatever you want
}])->get(['column1','column2']);
Just use ->get() include list of those you want to get:
example:
$files = File::where('agent_id', $user->id)->with('posts')->get(['post_id', 'status']);

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!

How to select specific fields from nested relations in Eloquent

I have the following query in Eloquent:
public function firstSubsectionIdsOnly()
{
return $this->model->with(['sections' => function ($q) {
$q->with(['subsections' => function ($q2) {
$q2->first();
}
])->first();
}])->whereHas('sections.subsections')->first();
}
This returns something like this:
{
"id": 1,
"name": "Training exercise",
"entry": "<p>sss</p>",
"created_at": "2018-04-20 09:38:36",
"updated_at": "2018-04-20 10:08:27",
"sections": [
{
"id": 1,
"name": "Section 1 Training",
"parent": null,
"position": 1,
"created_at": "2018-05-04 09:37:23",
"updated_at": "2018-05-04 09:37:23",
"pivot": {
"training_exercise_id": 1,
"section_id": 1
},
"subsections": [
{
"id": 2,
"name": "Subsection 1 training",
"parent": 1,
"created_at": "2018-05-04 09:54:09",
"updated_at": "2018-05-04 09:54:09"
}
]
}
]
}
I would like for it to only select the id fields for each relation so it should return something like this:
{
"id": 1,
"name": "Training exercise",
"entry": "<p>sss</p>",
"created_at": "2018-04-20 09:38:36",
"updated_at": "2018-04-20 10:08:27",
"sections": [
{
"id": 1,
"subsections": [
{
"id": 2,
}
]
}
]
}
I have tried adding $q->select('id'); to the subsections nested closure but that returns an empty subsections array.
Any idea on how I can achieve this? I am using Laravel 5.6
As others have stated before, you need to include the foreign key in the values you select. Let's say we have two models. Parent and Child. This is the bare minimum you have to select.
App\Parent::select('id')->with(['children' => function ($query) {
$query->select('id', 'parent_id');
}])->get();
It nets you something like
Illuminate\Database\Eloquent\Collection {
all: [
App\Parent {
id: 1,
children: Illuminate\Database\Eloquent\Collection {
all: [
App\Child {
id: 109,
parent_id: 1,
},
App\Child {
id: 153,
parent_id: 1,
},
],
},
},
],
}
If you're trying to go for the inverse, you still need the foreign key, like so
App\Child::select('id', 'parent_id')->with('parent' => function ($query) {
$query->select('id');
}])->get();
This nets you
Illuminate\Database\Eloquent\Collection {
all: [
App\Child {
id: 1,
parent_id: 58,
parent: App\Parent {
id: 58,
},
},
],
}
I'm using select() instead of get() for legibility on the queries but you must use select() for the subqueries.
If you use with('relation') => function ($query) { $query->get(['id']); } you'll get the whole object.
You have to include the foreign key columns. For the subsections relationship:
$q2->select('id', 'parent');

Laravel fetch only pivot columns in many to many relationship

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

Laravel scope query with nested models in 3-way pivot table

I have a Product Model that needs to be connected with Processings, that describe how the Product can be manipulated, and Positions, that describe where the Product can be manipulated.
In other words, a Product can be processed into multiple positions.
The "Product Data" should be returned with nested processings into positions, so that, for every Position into the database, a Product can be associated with related Processings, in a scheme similar to the one below:
Products [
product_attributes,
Positions [
position_attributes,
Processings: [
processing_attributes
]
]
]
So, I have created a 3-way Pivot Table with the following code:
Schema::create('product_position_processing', function (Blueprint $table) {
// 3-way pivot
$table->integer('product_id')->unsigned()->index();
$table->foreign('product_id', 'ppp_aid_foreign')->references('id')->on('products')->onDelete('cascade');
$table->integer('position_id')->unsigned()->index();
$table->foreign('position_id', 'ppp_bid_foreign')->references('id')->on('positions')->onDelete('cascade');
$table->integer('processing_id')->unsigned()->index();
$table->foreign('processing_id', 'ppp_cid_foreign')->references('id')->on('processings')->onDelete('cascade');
$table->primary(['product_id', 'position_id', 'processing_id'], 'index-base-combined');
// these attributes define the minimum and maximum order amount based on the product + position + processing combination
$table->integer('minimum_order_amount')->nullable();
$table->integer('maximum_order_amount')->nullable();
$table->timestamps();
});
Then, I have defined the relations between the 3 different models in this way:
Processing Model
class Processing extends Model {
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
protected $casts = [
'is_publicly_hidden' => 'boolean'
];
protected $hidden = ['pivot'];
public function products() {
return $this -> belongsToMany ( Product::class, 'product_position_processing', 'processing_id', 'product_id' );
}
public function positions () {
return $this -> belongsToMany ( Position::class, 'product_position_processing', 'processing_id', 'position_id' );
}
...
...
}
Position Model
class Position extends Model {
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
protected $hidden = ['pivot'];
public function products() {
return $this->belongsToMany ( Product::class, 'product_position_processing', 'position_id', 'product_id' );
}
public function processings ( ) {
return $this -> belongsToMany ( Processing::class, 'product_position_processing', 'position_id', 'processing_id' );
}
...
...
}
Product Model
class Product extends Model implements SluggableInterface {
use SoftDeletes;
use SluggableTrait;
protected $dates = ['deleted_at'];
protected $guarded = ['id', 'created_at', 'updated_at'];
protected $sluggable = [
'build_from' => 'name',
'save_to' => 'slug',
'include_trashed' => true
];
...
...
public function processings() {
return $this->belongsToMany(Processing::class, 'product_position_processing', 'product_id', 'processing_id');
}
public function positions() {
return $this->belongsToMany(Position::class, 'product_position_processing', 'product_id', 'position_id');
}
...
...
public function scopeWithCompleteData ($query) {
return $query->with([
...
...
'positions' => function($query) {
return $query->with(['processings' => function ( $query ) {
return $query->select('id', 'name')->groupBy('processing_id');
}])->groupBy('position_id');
},
...
...
]);
}
}
Now, the Products are returned in json via the ProductsController that, with the following code, returns a JSON in this format.
Product Getter:
...
...
$products = Product::withCompleteData() -> get();
return $this -> ok ( $products );
Example "ALL Products" #get JSON:
{
"status": "OK",
"data": [
{
"id": 1,
"category_id": 1,
"subcategory_id": 0,
"brand_id": 1,
"sku": "BD615149D6",
...
...
"positions": [
{
"id": 1,
"name": "POS1",
"processings": [
{
"id": 1,
"name": "PROC1",
"pivot": {
"position_id": 1,
"processing_id": 1
}
},
{
"id": 2,
"name": "PROC2",
"pivot": {
"position_id": 1,
"processing_id": 2
}
},
{
"id": 3,
"name": "PROC3",
"pivot": {
"position_id": 1,
"processing_id": 3
}
}
]
},
{
"id": 2,
"name": "POS2",
"created_at": "2016-08-01 07:39:11",
"updated_at": null,
"deleted_at": null,
"processings": []
},
{
"id": 3,
"name": "POS3",
"created_at": "2016-08-01 07:39:11",
"updated_at": null,
"deleted_at": null,
"processings": []
}
]
},
{
"id": 2,
"category_id": 2,
"subcategory_id": 0,
"brand_id": 2,
"sku": "BD615149D6",
...
...
"positions": [
{
"id": 5,
"name": "POS5",
"created_at": "2016-08-01 07:39:11",
"updated_at": null,
"deleted_at": null,
"processings": []
}
]
}
]
}
The problem is, the data returned is not correct. The Query Builder seems to connect the various relationships in a strange way. Unfortunately I'm still not enough proactive in understanding what lies below the magic of the query builder, and don't know how to debug and where to make modifications to the code.
Can anyone help out with this 3-way pivot table with nested objects into the Laravel query builder?

Categories