Laravel Route Model Binding Reusability

612 Views Asked by At

I'm creating a like feature for my application where users can like posts, events, products etc.

I have a LikeController and a Trait which handles the like functionality

public function store(Post $post)
{
    $post->like();
    return back();
}

The code above is to like a post

I don't want to duplicate code and create separate functions to perfrom the same exact thing on events or products and I was wondering how to perform route model binding and get the application to just execute the one function, passing model information depending on what is being liked post, event or product.

The following code works fine but does not implement the DRY principle

public function store(Post $post)
{
    $post->like();
    return back();
}

public function store_event(Event $event)
{
    $event->like();
    return back();
}

The followinf is the trait

trait LikeTrait
{

    public function getLikesCountAtrribute()
    {
        return $this->likes->count();
    }

    public function like()
    {
        if (!$this->likes()->where(['user_id' => auth()->id()])->exists()) {
            $this->likes()->create(['user_id' => auth()->id()]);
        }
    }

    public function likes()
    {
        return $this->morphMany(Like::class, 'likeable');
    }

    public function isLiked()
    {
        return !!$this->likes->where('user_id', auth()->id())->count();
    }
}

My web routes file is as follows

Route::post('post/{post}/likes', 'LikeController@store');
Route::post('event/{event}/likes', 'LikeController@store_event');

So the outcome I want is to call the same method and pass the relevant model.

Thanks in advance

2

There are 2 best solutions below

1
Emad Ha On

You can have a specific route for such actions, may it be 'actions':

Route::post('actions/like','ActionsController@like')->name('actions.like');

Then in the request you send the object you wish to perform the action on, i personal have it hashed, the hash contains the ID and the class_name (object type) in an stdClass.

That's how i personally do it: Every Model i have inherits Base Model, which contains hash attribute, which contains

$hash = new stdClass;
$hash->id = $this->id;
$hash->type = get_class($this);
return encrypt($hash);

This will return a string value of what's there, and encrypted, you can have a password for that as well.

Then you let's say you have the like button inside a form or javascript you can do that:

<form action="{{ route('actions.like') }} method="post">
   <input type="hidden" name="item" value="{{ $thisViewItem->hash }}">
   <button type="submit">Like</button>
</form>

Doing so, when liking an object you send the hashed string as the data, thus getting a request of $request->get('item') containing the object (id and type). then process it in the controller however you like.

If you're sending this through javascript you may want to urlencode that.

then in ActionsController@like you can have something like that:

$item = decrypt($request->get('item'));
# Will result in:
# Item->id = 1;
# Item->type = 'App\Post';

$Type = $Item->type;

# Build the model from variable
# Get the model by $item->id
$Model = (new $Type)->find($item->id);

# Like the model
$Like = $Model->like();

// the rest...

I personally prefer to combine and encrypt the id+type in a string, but you can send the id and type in plain text and have a route named like so:

Route::post('actions/like/{type}/{id}','ActionsController@like');

Then build the model from the Type+ID followed by what you have in trait ($Model->like());

It's all up to you, but i'm trying to hint that if you want to reuse the like action in many places, you may want to start building the logic starting from the action itself(likes, comments) not from the target (posts, events).

The codes i placed in here are written in here and not pasted from what i actually do, I'm trying to get you the concept. You can write it however you prefer.

0
Kevin Bui On

I don't know whether this is gonna work, but please have a go.

You could try explicit binding. Lets define explicit bindings in the App\Providers\RouteServiceProvider:

public function boot()
{
    parent::boot();

    Route::model('post', App\Post::class);

    Route::model('event', App\Event::class);
}

Then defining the routes:

Route::post('/posts/{post}/likes', 'LikesController@store');
Route::post('/events/{event}/likes', 'LikesController@store');

Finally in the controller:

class LikesController extends Controller
{
    public function store($object)
    {
        $object->like();
    }
}