What is the correct (most Laravel) approach to this probem (if any)

In my application I have a model named ‘Order’ like this, following the default Laravel behavior, and with an additional unique string property named ‘hash’:

class Order extends Model {
  // $table = 'orders'; 
  // $primaryKey = 'id';
  // $incrementing = true;
  $hash = 'a1b2c3d4e5f6g7h8i9';

  // many related models

The default behavior of function Model::find is to look for the primary key which is a feature I definitely want to keep. Currently my routes/api.php contains functions like this:

public function show(Request $request, Order $order) {
  // the order will be loaded via the id
  // if the model is not found an exception is thrown

On some occasions I would now like to inject the same order but searched via the order’s hash, so I assume my api.php would look something like this:

public function show(Request $request, OrderSearchByHash $order) {
  // the order will be loaded via the id
  // if the model is not found an exception is thrown

And here comes the question: is this achievable and if yes, how? I don’t actually need the source code for that but a hint to the direction I have to go to. I’ve already read about ServiceProviders and Facades, tried creating an extended model (but I would have to “patch” all relations because the pivot tables would have a different name) and many other stuff on the Laravel documentation but I still don’t even know exactly if and what covers my specific problem?

Has anybody a hint for me?
Thanks in advance!


Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

You can use a custom Model binding :

Both your method in your controller will look like this:

public function show(Request $request, Order $order) {
Route::get('orders/{order}', '[email protected]');
Route::get('orders-by-hash/{order_by_hash}', '[email protected]');
Route::bind('order_by_hash', function ($value) {
// Use your logic for finding your order by hash
            return Order::where[...]->first();

you can also authorise to find an order by ID only when you are connected :

Route::bind('order', function ($value) {
// Use your logic for finding your order by hash
            return Order::where[...]->when(auth()->check(), function()
                      $query->where(order_id, $value)
                            ->where(user_id, auth()->id());
So when you are connected, IF an order with the id belongs to the user , you can find it that way

This is only relevant if your route using an id and your hash are using the same controller's methods, else I recommend to use 2 different model bindings

Method 2

You can create a condition where the {id} is PK or hash. And then, perform a query according to the type of {id} obtained.

For example :

Route::get('orders/{id}', [OrderController::class, 'show']);
public function show(Request $request, $id)
    $type = ctype_alnum($id) ? 'hash' : 'pk'; // or another checking method
    $item = $type == 'pk'
    ? Order::find($id)
    : Order::where('hash', $id)->first();


Method 3

@Mathieu: thanks for the tip with Route::bind, I ended up with this in RouteServiceProvider

Route::bind('order', function($value) {
  if((int)$value > 0 && env('APP_ENV', 'production') == 'dev') {
    return Order::find($value);

  return Order::where('hash', (string)$value)->first();

I allowed ID usage on dev environment so I can test the API more easily with Postman.

Again: Thank you very much!

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Notify of

Inline Feedbacks
View all comments
Would love your thoughts, please comment.x