Sort an Eloquent model - laravel

Table for MyModel is like below
id name parent_id
--------------------------
1 parent1 null
2 p1-c1 1
3 parent2 null
4 p1-c1-g1 2
5 p2-c1 3
6 p1-c1-g1-f1 4
7 parent3 null
8 p1-c2 1
9 p3-c1 7
10 p1-c2-c1 8
How order with eloquent or raw DB query without getting all rows like below
Collection {#499 ▼
#items: array:22 [▼
0 => MyModel {#524 ▶} // parent1
1 => MyModel {#525 ▶} // p1-c1
2 => MyModel {#526 ▶} // p1-c1-g1
3 => MyModel {#527 ▶} // p1-c1-g1-f1
4 => MyModel {#528 ▶} // p1-c2
5 => MyModel {#529 ▶} // p1-c2-c1
6 => MyModel {#530 ▶} // parent2
7 => MyModel {#531 ▶} // p2-c1
8 => MyModel {#532 ▶} // parent3
9 => MyModel {#533 ▶} // p3-c1
]
}
for a better view and understanding
parent1
p1-c1
p1-c1-g1
p1-c1-g1-f1
p1-c2
p1-c2-c1
parent2
p2-c1
parent3
p3-c1

use Recursive relationship to same table
For example if model name is MyModel then the relationship will be
public function child(){
return $this->hasMany(MyModel::class,'parent_id','id');
}
public function recursiveChild(){
return $this->child()->with('recursiveChild');
}
So in your controller
MyModel::with(['recursiveChild'])->get();

What is your actual code, what are you trying to accomplish with this?
Collections are your friend in Laravel.
You can do a groupBy or using a With if you have a table that's related... your question is really too ambiguous.
You can get fairly complex with many chained Where orWhere with limits and groupBy
What I do see is that you need to have is not only the parent in the db but you also need a sort order on the children. Another thought is do you need a Grandparent, Parent, child? In that case you're going to have to have columns for all of those to know who they belong to when building your collection or doing just a straight eloquent pull.
My guess is this is for a menu or something you have here. Again ambiguity, so totally guessing.
EDIT
I've done this kind of thing in several apps...
You're table is going to look something like this:
id, name, menu_type, url, page_id, parent_id, sidebar, order, depth, role, created_at, updated_at, deleted_at
So in your model your going to want to do a relationship:
public function parent()
{
return $this->belongsTo('App\Models\Menus', 'parent_id');
}
public function children()
{
return $this->hasMany('App\Models\Menus', 'parent_id')->orderBy('order', 'asc');
}
Then what I've done is use this as a trait so it's available wherever I need to use it.
The trait is a little more complex than you are asking but it points you in the direction.
public function PrimaryMenu($pageid , $namespace, $user) {
// Get all primary menu items.
$appspace = "\App\Models\\".$namespace."\\Menus";
$roles = $user->roles->pluck('name');
$pmenus = $appspace::with('children')->where([['menu_type', '=', 'primary'],['parent_id','=', NULL]])->
where(function ($q) use($pageid) {
$q->where('page_id', '=', $pageid)->orWhere('page_id', NULL);
})->
where(function ($q2) use($roles) {
$q2->whereIn('role', $roles)->orWhere('role', '=', NULL);
})->
get();
return $pmenus;
}
You can see I'm checking a type of menu (this can be anything as long as you have something to group them by category) in this case primary, then I check for the page and see if we should include a row or not on the page then I check for any roles the user might have that we should include items.
Then in your controller it's just a matter of using the trait at the top:
use App\Traits\CommonTrait;
Now all of those traits are available to use in your controller. So you just call the function you need:
$pmenus = $this->PrimaryMenu($pageid, $namespace, $user);
You can see I'm sending along 3 variables to this as I have this set up for having many menus. Really the code translates to anything though that you want children associated with a parent.
The important bits are the model actions on relationships.

Thanks everyone, i used this package to achieve a sort like that.
https://github.com/lazychaser/laravel-nestedset#building-flat-tree

Related

Nested eager loading dynamically created in a constrain

songs table
id version number
1 AAA 1
2 BBB 1
3 CCC 1
4 DDD 2
5 EEE 3
6 FFF 4
7 GGG 4
The objective is to obtain, for example, all songs that have the same number as the song with id 1 (songs with ids 1, 2 e 3)
Song model
public function songs()
{
return $this->hasMany(Song::class, 'id_hierarchy');
}
Query: it doesn't work it's just for demonstration (there is no sameNumber model)
public function readSongsVersions()
{
$data = Hierarchy::query()
->whereNull('parent_id')
->with([
'children.songs'
'children.songs.sameNumber'
])
->get();
}
There is no sameNumber model.
Theoretically, a relationship would have to be created in the model song and it would be a relationship with the model song itself; Is such a solution possible?
The solution will eventually go through a constraint.
$data = Hierarchy::query()
->whereNull('parent_id')
->with([
'children.songs'
'children.songs' => function ($query) {
$query->where('number', '=', function($query) {
$query->select(DB::raw('i.number)'))
->from('songs as i')
->whereRaw('i.number = songs.number')
});
},
])
->get();
}
How to nest in eager loading the result of the subquery ( 'children.songs' => function ($query) {...}) to get the sameNumber music collection for each song;
the equivalent of: 'children.songs.sameNumber'
You can create relationships, that references itself. A where statement to ensure, not the same row is returned, since you are relying on the number column.
In Song.php
public function sameNumber()
{
return $this->hasMany(Song::class, 'number', 'songs.number')
->whereColumn('version', '!=', 'songs.version');
}
There is one part you have to figure out, but when you run the code it should be fairly obvious, 'version' and 'number' columns are gonna be ambiguous and you have to check what naming Laravel uses, when dealing with tables named the same. Change the code like so songs2.version, dependent on the naming. You can also always call ->toSql() on the relationships to see the SQL query.

how to define methods for a model that has a 1: 1 self relationship

I have this relational table on my db:
id, is referenced to: "attivitaSost" (and attivitaSpostata).
The relathionship is "optional" so the foreignkey is nullable.
But since the problem is the same, I will try to solve the first relationship first.
My model "cciActivities" have this 2 methods:
public function attOrig()
{
return $this->hasOne(CcieActivity::class,'id', 'attivitaSost');
}
public function attSpost(){
return $this->belongsTo(CcieActivity::class,'attivitaSost','id');
If I set the inverse:
public function attOrig()
{
return $this->hasOne(CcieActivity::class,'attivitaSost','id');
}
not works, and goes in a infinite loop thats goes in 500.
are well written? who needs to carry the foreign key? the children or the parent? there is a standard or I make work as was thinking:
save the new model,
pick up the id,
save it on the parent model,
The code:
$ccieActPadre= CcieActivity::where('id',$ccieActivityId)->first();
$ccieActivityNew = CcieActivity::create($data);
$ccieActPadre -> attivitaSost = $ccieActivityNew->id;
$ccieActPadre->save();
I am asking this, because when i try to apply methods filters like
$ccieActivities = CcieActivity::doesntHave('attOrig')
->get();
are returned not what i am expected.
When I am trying to render the resource activities, im using an api Resource like:
return [
'id' => $this->id,
'project' =>new ProjectResource($this->project) , //id, nomeEnte, name, email, ruolo
'catAttivita' => $this->catAttivita,
'nomeAttivita' => $this->nomeAttivita,
'descrizione' => $this->descrizione,
'dataInizioPrevista' => $this->dataInizioPrevista,
'dataFinePrevista'=> $this->dataFinePrevista,
'numNegoziAderentiPrevisti'=> $this->numNegoziAderentiPrevisti,
'numAziendeCoinvoltePreviste'=> $this->numAziendeCoinvoltePreviste,
'numInfluencerPartecipantiPrevisti'=> $this->numInfluencerPartecipantiPrevisti,
'numBuyerPrevistiB2B'=> $this->numBuyerPrevistiB2B,
'budgetTotalePrevisto'=> $this->budgetTotalePrevisto,
'modalitaRealizzazionePrevista'=> $this->modalitaRealizzazionePrevista,
'attivitaSpostata' => new CcieActivityResource($this->attOrigSpost),
'attivitaSostituitaaaaa' => new CcieActivityResource($this->attOrig),
];
this part
'attivitaSostituita' => new CcieActivityResource($this->attOrig),
never works! whatever method I apply!
So I need to understand which is the right convention to menage a 1:1 optional self relationship over a laravel model, thanks.
The second parameters for hasOne and belongsTo are not the same.
belongsTo is for the related model and hasOne is for the local model
$this->hasOne(Phone::class, 'foreign_key', 'local_key');
$this->belongsTo(User::class, 'foreign_key', 'owner_key');
In your case, the hasOne has the wrong parameters. change it to
public function attOrig()
{
return $this->hasOne(CcieActivity::class, 'attivitaSost', 'id');
}
EDIT:
Never eager load by default the parent in the child model and the child in the parent model even if they are seperate Classes. It will lead to an infinite loop.

Cannot using where in eloquent

I'm using Eloquent ORM in Laravel. There is 2 model, User and Trans. User hasMany Trans, and Trans belongsTo User. The problem is when I'm using query where, it doesn't work.
I tried using ->get() in the last code, it still doesn't work. I tried using ->all() in the last code it still doesn't work. I tried whereIn, it still doesn't work.
User Model
public function trans()
{
return $this->hasMany('App\Trans', 'user_id');
}
Trans Model
public function user ()
{
return $this->belongsTo('App\User','user_id');
}
Controller
$trans = Auth::user()->trans->where('date', $date);
I want the output is based on query where the date is based on user input, when I delete ->where , it works and the output like this.
Collection {#797 ▼
#items: array:13 [▼
0 => Trans {#783 ▶}
1 => Trans {#784 ▶}
2 => Trans {#785 ▶}
3 => Trans {#786 ▶}
]
}
try to change like that
Auth::user()->trans->where('date', $date);
to
Auth::user()->trans()->where('date', $date)->get();
note : if you want to get only property then u get property without
pointer but if you want to used another method then must used(add)
pointer(->).

Laravel: Check if Models have same relation

I have models: Partido and Coalicion related in a many to many relationship.
I need to know when two or more Coalicion has the same Partido related.
Hope I have explained myself.
Edit 1:
Model:
class Coalicion extends Model
{
public function partidos()
{
return $this->belongsToMany(Partido::class);
}
}
Let's say users selected some elements from a select input and I grabbed them in an array and send them to the controller.
...
public function example(Request $request)
{
$coaliciones = $request->coaliciones;
foreach ($coaliciones as $c) {
$coalicion = Coalicion::find($c);
# Here we have a list of Coalicion model in a loop
# Let's say the first iteration I can see the relationship
dump($c->partidos);
}
}
This for example give me the following answer at the browser:
Collection {#1 ▼
#items: array:2 [▼
0 => Partido {#271 ▶} #This is Partido with id 1
1 => Partido {#268 ▶}
]
}
Collection {#2 ▼
#items: array:3 [▼
0 => Partido {#279 ▶}
1 => Partido {#280 ▶}
2 => Partido {#283 ▶} #This is Partido with id 1
]
}
I need to know when the item 0 of the first Collection and the item 2 of the second Collection are the same.
I kinda found a way but I don't know if it's the correct or best approach.
In Coalicion model I add the following function:
public function partidosId()
{
return $this->partidos->pluck('id');
}
With this I can get only the id's from the relations, then in the controller I created an empty array() and fill it with all my ids with a foreach loop, then, finishing up I evaluated if the id's in the now filled array are unique, if the unique values are less than the length of the array then some models have the same relation (I don't know which but It's a start), e.g.:
public function example(Request $request)
{
$coaliciones = $request->coaliciones;
$allItems = [];
foreach ($coaliciones as $c) {
$coalicion = Coalicion::find($c);
$allItems = array_merge($allItems, $coalicion->partidosId()->toArray());
}
if (count(array_unique($allItems))<count($allItems)) {
dd("Duplicates");
}else{
dd("Uniques");
}
}
If anyone find a better way or a way to know which are the ones with the duplicate relation please let me know

Laravel 5: Build dynamic query for Eloquent Model and pass to view

I have just started to learn Laravel 5, so I hope my question is not too stupid. I am trying to achieve the following. I have 3 tables
adverts
categories
advert_category
In my categories table I have main and subcategories. So parent categories have got parent_id NULL otherwise they show the id of the parent. When I save a new advert, I have the advert_id and the category_id saved in the pivot table advert_category. All that works fine. I am now trying the following. I want a Navigation with Maincategories and Subcategories shown. When I click on a Subcategory then I can read out easily the advert_id's from the advert_category table, because I am only looking for only 1 category_id, but I now plan to be able to click on the Maincategory and now show all the Adverts which belong to all the Subcategories from this Maincategory. So my approach is the following:
// Look up if passed id is found under parent_id column
$cat = Category::where('parent_id', $id)->get();
// The choosen category is a maincategory,
// therefore build dynamic where to look for all the id's
if (count($cat) > 0)
{
$query = Category::select();
foreach($cat as $value)
{
$query->orWhere('id', '=', $value->id);
}
$advertList = $query->get();
}
else{
$advertList = Category::findOrFail($id);
}
return view ('publicpage.adverts.showall',['advertList' => $advertList->adverts]);
Unfortunately, when I go on the Maincategory, I get below error. When I go on the Subcategory, I get the adverts shown.
Undefined property: Illuminate\Database\Eloquent\Collection::$adverts
A dd($advertlist) brings the following result:
Clicked on Maincategory:
Collection {#203 ▼
#items: array:2 [▼
0 => Category {#204 ▶}
1 => Category {#205 ▶}
] }
So it shows Collection.
Clicked on Subcategory
Category {#201 ▼
#table: "Categories"
#connection: null
I can see that there is obviously a difference, but I have got no idea, how to fix that or what the best approach would be. I hope I explained it well enough to understand my task and look forward to any advice. Thanks in advance !
UPDATE
After the below comment, I tried it out:
// Look up if passed id is found under parent_id column
$cat = Category::where('parent_id', $id)->get();
if (count($cat) > 0)
{
$query = Category::select();
foreach($cat as $value)
{
$query->orWhere('id', '=', $value->id);
}
$advertList = $query->get();
return view ('frontend.adverts.showall',['advertList' => $advertList[0]->adverts]);
}
else{
// 1 Category,
$advertList = Category::findOrFail($id);
return view ('frontend.adverts.showall',['advertList' => $advertList->adverts]);
}
This way I get the adverts for the first category, but how would I manage to hand over the adverts for all the categories or how can I handle this? I just don't seem to to be able to fix this problem. Or would the change be in the view? I tried it with another foreach in the view aswell to run through the adervertList, but again no success. Any advice? Thank you very much in advance!
tl;dr
The problem is here, that you are using $advertList->adverts and $advertList might be in some cases collection (you can think it as it was some kind of array).
So you cannot simply write $array->adverts but you should do something like this $array[0]->adverts in case you want to get adverts for 1st element only.

Resources