Rafael Milewski
Rafael Milewski

Reputation: 799

Polymorphic Route Model Binding in Laravel?

is there any way of creating a Polymorphic-like model implicitly binding on laravel 5.* ??

for example:

route('project.comment.store', $project->commentable)

register the route like this

Route::get('comments/{commentable}/store', ....controller . '@store')

and receive the exactly model on the controller store() method?

im guessing the only way i could archive this is if i modify the route to something like this

Route::get('comments/{commentable_type}/{commentable_id}/store', ....controller . '@store')

which looks a big ugly.. but works... any idea if is there any short and smarter way of doing so?

Upvotes: 4

Views: 2048

Answers (3)

komakino
komakino

Reputation: 46

I have found a solution to this problem that is pretty clean. It only requires that you use morph maps and that the route has the same name(or pluralized) as the morph entry.

Route

Route::apiResource('customers.addresses', AddressController::class)
    ->parameter('customers','morphParent')
    ->scoped();

Service Provider

Route::bind('morphParent', function($value, RoutingRoute $route) {
    $name = str($route->action['as'])->before('.')->singular()->toString();
    $model = Relation::getMorphedModel($name);

    return $model::findOrFail($value);
});

Controller

public function store(Model $addressable, AddressRequest $request): JsonResource
{
    ...
}

You could make morphGrandParent binding etc too.

Upvotes: 0

Alexey Tsinya
Alexey Tsinya

Reputation: 401

You can check this

Route::apiResource('projects.comments', [CommentController::class, 'index'])
    ->parameters([
        'projects' => 'commentable',
    ]);

...and then in controller (without type hint)

public function index($commentable)

Upvotes: 0

Tarek Adam
Tarek Adam

Reputation: 3525

Sorry I'm late. You can polymorphic route model bind like this:

  1. Add a Route::bind() to either the RouteServiceProvider or your packages ServiceProvider. Your custom model resolution logic will go there.

  2. Inject a plain old \Illuminate\Database\Eloquent\Model rather than anything specific. (Or whatever base model you extend from).

Here's an example from my existing code... My binding is on /my-model/{key}/seo. During the bind, I custom resolve MyModel from my-model and then use {key} for the id.

// my package routes file

Route::group(['as' => 'fuqu::', 'namespace' => 'FuquIo\\LaravelTags', 'middleware' => ['web', 'auth']], function (){

$models = config(\FuquIo\LaravelTags\ServiceProvider::SHORT_NAME .'.orm');

foreach($models as $short_name => $fqns_class){
    $kebbab_name= kebab_case($short_name);

    Route::get("/$kebbab_name/{key}/seo", [
        'as'   => "$kebbab_name.seo",
        'uses' => function(\Illuminate\Database\Eloquent\Model $model){
        return $model;
    }
    ]);
}

});

// my package ServiceProvider.php (or your RouteServiceProvider.php) in the boot() but you need to use Illuminate\Routing\Route as RouteInfo; for that injection I'm doing in the bind.

// at the top
use Illuminate\Routing\Route as RouteInfo;
...
// then inject RouteInfo for your binding logic so you can tear apart your url/uri.
Route::bind('key', function ($key, RouteInfo $route) {
        $info = explode('/{key}', $route->uri());
        $kebab = $info[0];
        $orm = studly_case($kebab);
        $fqns_class = config(self::SHORT_NAME .'.orm.'. $orm);
        return $fqns_class::findOrFail($key);
    });

So, in my case I've got a config file mapping SimpleNames => Fully\NameSpaced\Names::class, but you don't need that. You can resolve however you like.

Upvotes: 1

Related Questions