Get siblings from parent id and exclude itself Laravel relationship - php

CONTEXT
I am managing products. This is a shoe store. I would like to offer a view of the other variants.
The database is shaped like this:
For example you can have a leather shoe (id 1), and there is 3 variants of this shoe: a black (id 1), a brown (id 2), and a grey (id 3).
What I try to do is to construct a Laravel relationship to be able, from one variant, to get its siblings. Here is what it looks like in the database according to the example I mentioned.
SHOE
id
====
1
SHOE_VARIANT
id shoeId colorId
===================
1 1 1
2 1 2
3 1 3
...
8 2 5
9 3 2
10 3 4
In this case, if the user is viewing the black variant (id 1), I whish I could show him the 2 others variants (brown, id 2, and grey, id 3).
QUESTION
How can I construct a Laravel relationship in order to retrieve siblings from a parent id, and make sure the current record itself is not included?
EXPERIMENTS
I already tried to construct a relationship like below, but the current record itself is included and I can't figure out how to exclude the record itself because I did not find how to get the current id of the record.
// app/ShoeVariant.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ShoeVariant extends Model {
public function siblings() {
return $this->hasMany("App\ShoeVariant", "shoeId", "shoeId");
}
}
This means:
For the current shoe variant, get the shoe variants that matches knowing that you should match the foreign column named "shoeId" and the local column named "shoeId"
So if 2 shoe variants share the same column "shoeId", this works. Stuck in excluding the current record from these results.

This should do what you want:
public function siblings() {
return $this->hasMany('App\ShoeVariant', 'shoeId', 'shoeId')
->where('id', '!=', $this->id);
}
Just filter out the current variant by id and get all the others.
Alternatively, you can just make a new property:
public function getVariantsAttribute() {
return $this->siblings->reject(function($elem) {
return $elem->id == $this->id;
});
}
And then use it in code like:
$variants = $model->variants; // all except this one

Related

Laravel filament, is there any way to mutate TextColumn value in table?

How to get only one value in the filament table for with hasMany relationship?
I have two DB tables:
products
id
sku
1
SKU_1
2
SKU_2
product_descriptions
id
product_id
translation_id
name
1
1
1
Opel
2
1
2
Vauxhall
In my Product model I have hasMany relationship
public function productDescriptions(): HasMany
{
return $this->hasMany(ProductDescription::class);
}
When I do Tables\Columns\TextColumn::make('productDescriptions.name') it return all values separated by comma. In my example "Opel, Vauxhall"
Is there any way to manipulate/mutate return value using callback? Let say, return only first value "Opel"?
You can use calculated states.
Tables\Columns\TextColumn::make('productDescriptions.name')
->getStateUsing( function (Model $record){
return $record->productDescriptions()->first()?->name;
});

Select rows with same values in relationship Eloquent

I have two models in my project
Product and Product attributes
The relationship between them is one to many, so i can get product with relative attributes. Now I need to get a list of products and also get common product attributes between selected products. As in ex:
Product
id
Product
1
Apple
2
Pear
3
Ananas
Product attributes
id
attribute
product_id
1
fruit
1
2
fruit
2
3
fruit
3
4
green
1
5
yellow
2
6
brown
3
Now when I'm extracting all 3 products, i want to get their common attributes, in this case the common attribute is "fruit". How can I do this with laravel eloquent?
As per your current schema (which doesn't look good) you can get common attributes as
$products = Product::take(3)->get();
From above products collect product ids
$productIds = $products->pluck('id');
And then query product_attributes matching previously collected $productIds, this query will involve aggregation to satisfy your condition for common attributes.
$attributes = DB::table('product_attributes')
->select(['name',DB::raw('count(distinct product_id) total_count')])
->whereIn('product_id', $productIds)
->groupBy('name')
->havingRaw('total_count = ?', [count($productIds)])
->get();
Above query will return common attributes by checking the result of count() expression with count of $productIds if they match and this means that every products has this attributed attached to it.
At first, as said above, add many-to-many relationships, one-to-many is not suitable for that case. Then I can propose such a variant with two queries:
// select here product ids you need
$products = Product::all(['id']);
//select common attributes
$attibuteBuilder = ProductAttribute::query();
foreach($products as $product){
$attibuteBuilder->whereHas('products', function (Builder $builder) use ($product) {
$builder->where('product_id', $product->id);
})
}
$atributes = $attibuteBuilder->get();
The second variant: get the collection of products with attributes and check the general/

Laravel Many to Many and One to Many Table Model and Controller

Could Some one help me to get figure this out.To start of this is not a duplicate of CRUD and relation between three tables/Models in Laravel.
There are 2 tables Medication_Patient Pivot Table and Med_Time
Medication_Patient Pivot Table
id medication_id patient_id
1 1 (MED X) 1 (Patient X)
2 2 (MED y) 1 (Patient X)
3 2 (MED y) 2 (Patient Y)
And MEdTime which store time and where the medication was given or not
id med_patient_id(foreign key) Day time given
1 1 Yesterday 0900 1
2 1 Today 0900 0
3 1 Today 2000 0
4 2 Today 0600 1
On the Models I have
class Medication extends Model {
protected $guarded = [];
public function patient()
{
return $this->belongsToMany('App\Patient');
} }
class Patient extends Model
{
public function medication()
{
return $this->belongsToMany('App\Medication');
}
}
To get the Medication's assign to a Patient
$assignedMeds = $patient->medication()->get();
but it doesn't give me the ID of Pivot table which I need to find the Time for medication, So I used (PLEASE LET ME KNOW IF THERE IS A BETTER WAY TO DO THIS)
//get the id from medication_patient pivot Table
$medPatient = DB::table('medication_patient')->select('id')
->Where([
['patient_id','=', $patient->id],
['medication_id' ,'=', $medication->id]
])->get()->first;
$medPatientId = $medPatient->id->id;
//Using Medication_patient_id to find MedTime
$assignedMedTimes = MedTime::where('med_patient_id' ,'=' , $medPatientId)->get();
//Filtering the Med Time according to the day
$yesterdayMedTimes = $assignedMedTimes->where('day', '=', 'yesterday')->all();
$todayMedTimes = $assignedMedTimes->where('day', '=', 'today')->all();
$tomorrowMedTimes = $assignedMedTimes->where('day', '=', 'tomorrow')->all();
return view('medicationPatient.medTime', compact('patient','medication'),[
'assignedMedTimes' => $assignedMedTimes,
'yesterdayMedTimes' => $yesterdayMedTimes,
'todayMedTimes' => $todayMedTimes,
'tomorrowMedTimes' => $tomorrowMedTimes,
]);
}
But this only works when I am getting the Time for 1 Medication (Med X time assigned to Patient X), how do I setup a loop or relation in query or eloquent to get me all the medication time (MED X,Y time for Patient X) and pass it into blade.
Sorry of the long post. Would Appreciate if you could show me the Code.
Thank you
You may be making it a little harder on yourself having to go directly to the table in that query. If it were me, I might consider refactoring the database a little bit to make this easier, and to easily take advantage of the Laravel relationships & pivots.
I'm not sure you need to store the data in two separate tables. I would look to normalize as much as possible and collapse this down to a single table. You don't seem to need to reiterate yourself in the Med_Time table -- the med_patient table adds multiple pill's given, so it serves the same purpose as the med_time table (I think). I suggest just a medication_patient table with the pivots:
id medication_id patient_id Day time given
1 1 (MED X) 1 (Patient X) Yesterday 0900 1
2 2 (MED y) 1 (Patient X) Today 0900 0
Your relations will be much the same as you have them, but you can draw the pivots right from the model. This is from the Patient model, but your relations are good for both in your question
public function medication()
{
return $this->belongsToMany('App\Medication')->withPivot('Day', 'time', 'given');
}
Then, when you need to access the data, just pull the pivot. Example:
$patient->pivot->Day... or $patient->pivot->time
Have you tried something like?
public function patient()
{
return $this->belongsToMany('App\Patient')->withPivot(['id']);
}

Complex relations in database (with Laravel)

I want to know how to make relations between tables:
I have a shops table and a users table.
I want to associate to the users table: shops (id) AND area (that's a group of shops).
I don't know how to do that in a beautiful and right way.
For example, in a very complex way we can have:
Area A contains [Area B (3 shops) and Area C (1 shop)] and 1 shop : total of 5 shops.
John is associate to the Area A, Area X and 3 other shops (no area).
How to represent this in database?
It's like a recursive thing :(
Thanks by advance!
You'd probably want to have the following tables:
users
- id
- username
shops
- id
- name
areas
- id
- name
- parent_id
user_shop
- user_id
- shop_id
area_shop
- shop_id
- area_id
Then for your example, a row might look like
users
id: 1
username: jappleseed
shops
id: 1
name: some shop name
id: 2
name: some other shop name
areas
id: 1
name: Area A
parent_id: null
id: 2
name: Area B
parent_id: 1
id: 3
name: Area C
parent_id: 1
area_shop
area_id: 2
shop_id: 1
area_id: 2
shop_id: 2
Then you could define your relationship for nested areas like:
class Area extends Model {
/**
* Parent Area
*/
public function parent_area()
{
return $this->belongsTo('Area', 'parent_id');
}
/**
* Child Areas
*/
public function child_areas()
{
return $this->hasMany('Area', 'parent_id', 'id');
}
}
The other relationships should be fairly straight forward.
This is untested and might need a little tweaking, but the overarching idea should get you there. Otherwise, using a prebuilt nested set library would also work.

PHP, MySQL: Need to show Parent name only once.. almost there but still a problem

I have a parent category that holds all Cars names, denoted by parent_name in table "parent". For each of these parents, there could be any number of car models & they all go in table called "model". Each of these models can have any number of images & refereced via the model_id as the Foreign Key. My task is to show all the Parent Name only once (as in a group) and at the same time, list all the models under that Parent with just 1 corresponding image. The parent_name should not be shown more than once.
MY EXPERIMENTS:
I basically tried to write 2 queries. One was to left join "parent" table on "models" & use GROUP BY parent_id and then in the while loop, write another query to fetch only 1 image using by joining the models & images tables by using model_id field. But doing this lists only 1 Model, even though there are multiple models. So I tried to use GROUP BY parent_id, model_id. Using this does show all the models but at the same time, also repeats showing the parent_name & I need the parent_name to show only once throughout the page. You can say that I am trying to GROUP the model_name under the parent & show all the models under a single parent and I am showing only 1 image of the model. If somehow I can avoid showing the parent_name multiple times, the issue would be solved.
Following are my table schemas:
//Table parent
parent_id parent_name
1 Par1
2 Par2
//Table model
model_id parent_id model_name
1 1 Model1
2 2 Model2
3 1 Model3
4 1 Model4
5 2 Model5
//Table model_images
image_id model_id
1 1
2 1
3 1
4 2
5 3
6 3
DESIRED OUTPUT:
Par1 ---> This is the parent. Needs to be shown only once.
Model1 --> This is a model. List all models that belong to this parent.
image_id 1 -> Show only 1 image of the model (model may have multiple images but I need just one)
Model3 --> This is a model.
image_id 5 -> Show only 1 image of the model
Model4 --> This is a model.
No Image -> Note that no image exists for this model. So we show "No Image" text.
------------------------------------------------------------
Par2 ---> This is the parent. Needs to be shown only once.
Model2 --> This is a model.
image_id 4 -> Show only 1 image of the model
Model5 --> This is a model.
No Image -> Note that no image exists for this model. So we show "No Image" text.
I need the PHP & mySQL code to achieve the above. All help in resolving the issue is appreciated.
Thank you very much.
EDIT 1:
Sorry, I forgot to add this. I am non-object oriented programmer. So I would really be thankful if you can avoid object oriented code in your solution and show me the same in a non-oops way. Thanks.
You might do it in one query and than combine it to an associative array:
$query = ' SELECT *
FROM parent AS p
LEFT JOIN model AS m
ON p.id = m.parent_id
LEFT JOIN model_images AS m_i
ON m.model_id = m_i.model_id';
$array = array();
if($mysli->query($quer)){
while($row = $result->fetch_assoc()){
$array[$row['parent_name']][$row['model_id']] = $row;
}
}
You will than have an associative array with the parent name as the key of the array. You can then use a for loop to print the key only once (with $i = 0) but the rest value by value.
Is that clear enough?
EDIT: Your array than might look like this:
Array(
'Par 1' =>
Array(
[0] => Array(
'parent_id' => 1,
'parent_name' => 'Par 1',
'model_id' => 1,
'model_name' => 'Model 1',
'image_id',
),
[1] => Array(...)
),
'Par 2' => Array(...)
)
So to print out you need two loops. One for the parents (and there names) and one for their childs (models in this case).
foreach($array as $par_name => $models){
echo 'Parent name: '.$par_name.'<br />';
echo 'Model ID: '.$models[0]['model_id'].', Model Name: '.$models[0]['name']; // replace with your desired output
}
Now an idea of how it works? An sure as Artefacto said, you can use procedural functions if you don't like OOP functions.

Categories