Reputation: 2042
Model A
has a polymorphic relation to models X
, Y
, Z
. Relevant fields in A
are:
poly_id
(integer foreign key)
poly_type
(string App\X
, App\Y
or App\Z
)
Given an instance of model A
, I can successfully use $a->poly
to retrieve the related object of type X, Y or Z. (E.g. {"id":1,"name":Object X}
).
In a Blade template for A
, how should I generate an show link to X
such as '/x/1'? What springs to mind is URL::route('x.show', $a-poly_>id)
however as far as I can see, we don't actually have the 'x' part of the route available to us - only the poly_id, poly_type and both objects.
Am I missing something? A solution like taking the poly_type string 'App\X' and split off the last segment and lowercase to get 'x' but that doesn't seem ideal, and potentially the defined route could be something else.
As an aside, in Rails I'm pretty sure you can do link_to($a->poly)
and it would magically return the URL '/x/3'. Not sure if Laravel can do that. I tried url($a->poly)
and it doesn't work.
Upvotes: 9
Views: 3537
Reputation: 1050
With Laravel don't possible bind route and Model. But i have an idea for your problem.
You can add custom attribute with mutators to your A
model that will be responsible decide which route using when an object is calling. For Example;
A
Model;
/**
* Get the route
*
* @return string
*/
public function getRouteAttribute()
{
$route = null;
switch ($this->poly_type){
case 'App\X':
$route = 'your_x_route_name';
break;
case 'App\Y':
$route = 'your_y_route_name';
break;
case 'App\Z':
$route = 'your_z_route_name';
break;
}
return $route;
}
We can thinks it like a Factory method.
When we want use it, we can use a route following way.
@foreach($a->polys as $object)
<a href="{{ route($a->route, [your parameters.]) }}">But what name?</a>
@endforeach
That is may be not perfect practice, but i think it useful managing from one point.
Upvotes: 0
Reputation: 12470
Laravel doesn't have a simple solution for this like Rails does (at the moment). That's because there is no implicit connection between route names and model names. There is a naming convention but it isn't really applied in the code. I can't ask for a model's URL, I need to call route('model.show', $model)
.
Some other solutions here propose using a redirection controller but that's inconvenient and poor as a user experience (and for SEO). You're better off creating a helper function that can generate the route you need - that way the functionality is available anywhere in the app and it's not tied to the model or controller layer.
If you wanted to have control over the actual page visited (i.e. not just the show route) then you could wrap the route
helper that can take the action you want and generate the right URL.
use Illuminate\Database\Eloquent\Model;
function poly_route(string $route, Model $model): string
{
return route($model->getTable() . '.' . $route, $model);
}
This would let you do something like poly_route('show', $poly)
;
Generally you'll be placing helper functions in bootstrap/helpers.php
and registering that file in your composer.json
file - if you are using a helpers file already. Of course, if you're not using a helpers file then this solution might already feel hacky.
I'd then suggest you explore moving the function to a method on the model.
class Poly extends Model
{
public function route($name)
{
return route($this->getTable() . '.' . $route, $this);
}
}
Then you can simply call $poly->route('show')
. Again, neither of these solutions are totally elegant but they might beat having a heap of if/else or switch cases in your app supporting each use-case. Hopefully Laravel will provide better functionality for this sort of thing going forward.
Upvotes: 0
Reputation: 780
Use MorphToMany & MorphedByMany
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class A extends Model
{
public function xs()
{
return $this->morphedByMany('App\X', 'poly_type');
}
public function ys()
{
return $this->morphedByMany('App\Y', 'poly_type');
}
public function zs()
{
return $this->morphedByMany('App\Z', 'poly_type');
}
}
Route:
Route::get('/a/show/{a}/{poly}', 'AController@index');
And Controller:
class AController extends Controller {
public function index(A $a, $poly)
{
dd($a->{$poly}->first()); // a $poly Object
}
}
So, /a/show/1/xs is a valid path
Upvotes: 5
Reputation: 1616
I think my solution to this is going to be as follows. All tables that are on the poly-end of a polymorphic relationship will have a property identifying the route or action (depending on how I go with this idea).
Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Y extends Model
{
/**
* The route name associated with the model.
*
* @var string
*/
protected $routeName= 'path.to.y.show';
}
Then, you could use code like this to find the route, regardless of the model at the end:
route($a->poly->routeName, $a->poly)
Or, for hasMany
@foreach($a->polys as $object)
<a href="{{route($object->routeName, [$object])}}">But what name?</a>
@endforeach
I don't know if this is acceptable, though. If you haven't defined the routeName on a model, then you'll run into errors. I'm also not sure the model should know about routing!
In order to determine the name to be shown, should some sort of getGenericNameAttribute be defined that returns the appropriate property from the model?
I'm answering in the hope that somebody has a more elegant solution. For example, registering a service provider that then:
It's just I wouldn't know how to do this!
Upvotes: 0
Reputation: 1366
This might seem overly simplistic but you could use something like this to solve your problem
I would create a controller something like this:
use App\Http\Controllers\Controller;
use A;
use X;
use Y;
use Z;
class PolyController extends Controller {
public function aPoly(A $a)
{
$poly = $a->ploy;
switch ($ploy)
case instance of X:
return response()->redirectToRoute('X route name', $poly);
break;
case instance of Y:
return response()->redirectToRoute('Y route name', $poly);
break;
case instance of Z:
return response()->redirectToRoute('Y route name', $poly);
break;
default:
report(new Exception('Failed to get route for ploy relationship');
}
}
This would then allow you to use the following in your routes file:
Route::get('enter desired url/{a}', 'PolyController@aPoly')->name('name for poly route');
And then in your controller you just do something like this:
<a href="{{ route('name for poly route', $a) }}">$a->ploy->name</a>
This is how I would like deal with the situation
Upvotes: 0