Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Gecko2811's avatar

Model Relationship - best practise to sort

Hello,

I made a relationship between three different tables. First one ("maps") has got "name" and "id_usermaps". The second one ("route") uses "id" and "id_table1". The third ("run") one uses "id" and "id_table2".

The relation: One Map has Many Routes 1:m One Route has Many Run 1:m

What I am currently doing: Using Table 1 as Model to filter for "name" in "map"-table to get the results from Table 2. Table 2 then gets the result from Table 3.

My question: How can I get all results from "run" for each "route" and sort these by a specific column with my models? I could get all these "run" entries within the array but then I can not sort it anymore using php. What is the best practise the get all runs for each route on one specific map?

My JSON looks like this:

{
	"ID_usermaps": 198,
	"name": "mp_zone",
	"routes_external": [
	{
		"id": 364,
		"map_id": 193,
		"name": "Easy",
		"runs": [
			{
				"id": 10284,
				"way_id": 364,
				"saves": 382
			},
			{
				"id": 10285,
				"way_id": 364,
				"saves": 386
			},
			{
				"id": 10286,
				"way_id": 364,
				"saves": 220
			},
		]
	},
	{
		"id": 365,
		"map_id": 193,
		"name": "Hard",
		"runs": [
			{
				"id": 10293,
				"way_id": 365,
				"saves": 104
			},
			{
				"id": 10302,
				"way_id": 365,
				"saves": 450
			}
		]
	}
}
0 likes
8 replies
vincent15000's avatar

Can you write your code between backticks ? So that it will be more readabled.

```

your code here

```

Waiting for the backticks, if you are using Laravel, I suggest your to name the foreign keys using the Laravel naming convention, for example map_id and not id_table1.

Gecko2811's avatar

Hey,

thank you already for your response. I have fixed the JSON.

I am using laravel. id_table1 was just an example name of the table. I can not affect these names anyway, my Models should only display my result on a web page.

My Controller's Show function:

public function show($map)
{   
    $routes = Map::with('routesExternal', 'routesExternal.runs')
    ->where('ID_usermaps', $map)
    ->limit(50)
    ->get();
    
    return response()->json($routes);
}

Map Model:

// id_3xp_map = PK  - map_id is the FK for "Route" table
protected $visible = ['ID_usermaps', 'name', 'routesExternal'];

public function routesExternal()
{
    return $this->hasMany(RouteExternal::class, 'map_id', 'id_3xp_map');
}

RouteExternal Model:

// id= PK  - way_id is the FK for "Run" table
protected $visible = ['name','id', 'map_id', 'runs'];

public function runs() 
{
    return $this->hasMany(Run::class, "way_id", "id");
}

I have also had another idea. If i first fetch all runs with map_id XY it would work fine too. I just don't know how this works. I have tried a bit starting from "Run" model but I don't know how to add the condition that the map ID has to be a specific ID.

1 like
PovilasKorop's avatar

@gecko2811 you have three options to define the ordering:

Option 1. Callback Function with orderBy

// Controller:
Map::with(['routesExternal.runs' => function($query) {
    $query->orderBy('runs.saves', 'desc');
}])->get();

Option 2. Model: ALWAYS Order By Field X

Maybe you want that relationship to always be ordered by that field?

You can do this:

app/Models/RouteExternal.php:

public function runs() 
{
    return $this->hasMany(Run::class, "way_id", "id")->orderBy('saves', 'desc');
}

Then, in the Controller, you just leave it as it was.

Option 3. Separate Ordered Relationship

If you want to use this ordering often but not always, another option is to create a separate relation function:

app/Models/RouteExternal.php:

public function runs() 
{
    return $this->hasMany(Run::class, "way_id", "id");
}

public function runsOrderedBySaves() 
{
    return $this->hasMany(Run::class, "way_id", "id")->orderBy('saves', 'desc');

    // Or alternatively, even...
    return $this->runs()->orderBy('saves', 'desc');            
}
// Controller:
Map::with('routesExternal', 'routesExternal.runsOrderedBySaves')->get();

I've quickly created a post on my blog, as a reference to answer similar questions in the future.

Let me know if it works, and which way you've chosen :)

3 likes
Sinnbeck's avatar

@PovilasKorop nice list. Small suggestion. Mention the reorder method in the second example in case you want to order it differently

Map::with(['routesExternal.runs' => function($query) {
    $query->reorder('runs.saves', 'desc');
}])->get();
 
3 likes
PovilasKorop's avatar

@Sinnbeck good suggestion, updated in the article, as this reply apparently is not exactly what the author wanted so not worth updating here.

1 like
Gecko2811's avatar

This will sort the runs from each route. I need some way to get ALL runs from ALL routes by ONE map in one array and order these by "saves".

$routes = Map::with('routesExternal', 'routesExternal.runs')
->where('ID_usermaps', $mapName)
->limit(50)
->first();

$runs = array();

foreach($routes->routesExternal as $route) {
    $runs = array_merge($runs, $route->runs->toArray());
}

This would work. Does not seem like the best practise but the result is correct. I don't have a direct relationship from map to runs.

1 like
PovilasKorop's avatar

@Gecko2811 then maybe try hasManyThrough() relationship and then add orderBy()?

Alternatively, you can use ->sortBy() on the Collection, after you fetch the results from the DB, but that would be slower than ordering in the DB.

1 like
Gecko2811's avatar

@PovilasKorop So I've figured a bit more out to get a "cleaner" way to the result. So I started outgoing from Run model this time like this:

$runs = Run::with(['routeExternal', 'routeExternal.map' => function ($query) use ($mapName) {
    $query->where('usermaps.name', '=', $mapName);
}])->first();

and the SQL statement does not throw any errors. It looks fine so far, sadly map result is NULL.

Current result:

{
    "id": 221,
    "way_id": 17,
    "saves": 96,
    "route_external": 
    {
        "id": 17,
        "map_id": 51,
        "map": null
    }

}

routeExternal:

public function map()
{
return $this->belongsTo(Map::class, 'map_id', 'id_3xp_map');
}

Any ideas?

Edit: I see why the result is null. I am looking for the first entry from runs and that does not match my value within the show() function.

1 like

Please or to participate in this conversation.