FrankMawn's avatar

Weird try/catch behavior with Collection's sole method.

Here's a code snippet to illustrate my confusion :

use Illuminate\Support\ItemNotFoundException;

dump(ItemNotFoundException::class); 
// Illuminate\Support\ItemNotFoundException

try {
  $search = collect([['id' => 1], ['id' => 2]]);
  $id = $search->where('id', 3)->sole();
} catch (ItemNotFoundException $e) {
  dd('Catch ItemNotFoundException');
} catch (Exception $e) {
  dd('Catch Exception');
}

You would think that code would execute the first catch block but it goes into the generic Exception catch.

If I instead put the full namespace of the ItemNotFoundException in the catch block, then it triggers as excepted.

Anyone has an idea of what is going on here?

0 likes
6 replies
Glukinho's avatar

Function \Illuminate\Database\Eloquent\Builder::sole() throws \Illuminate\Database\Eloquent\ModelNotFoundException and \Illuminate\Database\MultipleRecordsFoundException, not \Illuminate\Support\ItemNotFoundException

upd: sorry, I see you call sole() on a collection not a query bulider...

Glukinho's avatar
Level 31

Your code behaves exactly as it should on Laravel 12.19:

C:\nastroim\pipka>php artisan app:test-command

"Illuminate\Support\ItemNotFoundException" // app\Console\Commands\TestCommand.php:122
"Catch ItemNotFoundException" // app\Console\Commands\TestCommand.php:129 

What Laravel version you have?

Maybe you have another ItemNotFoundException in use statements?

FrankMawn's avatar

@Glukinho Thanks for taking the time to look into this.

I'm running Laravel version 12.20.0 and PHP 8.4.7.

I tried running the code both in my app and in isolation in a Tinkerwell session. My colleague also tried and got the same behavior.

Weirdly enough, when I test the code within a command, I do get the expected behavior where it catches the ItemNotFoundException exception.

Glukinho's avatar

@FrankMawn I created a new command just for this case, it gives normal result too:

C:\nastroim\pipka>php artisan app:test-sole-command
"Illuminate\Support\ItemNotFoundException" // app\Console\Commands\TestSoleCommand.php:19
"Catch ItemNotFoundException" // app\Console\Commands\TestSoleCommand.php:26

One thing I fixed in your code, it should be catch (\Exception $e) (backslash before "Exception"). It doesn't seem to affect catching other exception.

TestSoleCommand.php:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\ItemNotFoundException;

class TestSoleCommand extends Command
{
    protected $signature = 'app:test-sole-command';

    protected $description = 'Command description';

    public function handle()
    {
        dump(ItemNotFoundException::class);
        // Illuminate\Support\ItemNotFoundException

        try {
            $search = collect([['id' => 1], ['id' => 2]]);
            $id     = $search->where('id', 3)->sole();
        } catch (ItemNotFoundException $e) {
            dd('Catch ItemNotFoundException');
        } catch (\Exception $e) {
            dd('Catch Exception');
        }
    }
}

I have PHP 8.3.10.

Glukinho's avatar

@FrankMawn Tinker gives proper result too:

C:\nastroim\pipka>php artisan tinker
Psy Shell v0.12.8 (PHP 8.3.10 — cli) by Justin Hileman
> $search = collect([['id' => 1], ['id' => 2]]);
= Illuminate\Support\Collection {#7271
    all: [
      [
        "id" => 1,
      ],
      [
        "id" => 2,
      ],
    ],
  }

> $id = $search->where('id', 3)->sole();

   Illuminate\Support\ItemNotFoundException

> 
FrankMawn's avatar

It might be a glitch with Tinkerwell then. You are right that running the code within a command or controller does seem to work as expected.

Thank you for taking the time, I guess I'll move on lol

Please or to participate in this conversation.