How can I make Laravel automatically generate text for ModelNotFoundException when searching for a model with a custom formatted primary key? My model has a primary key that’s a specifically formatted string. I want to avoid database queries if the string has an invalid format. When manually throwing throw new ModelNotFoundException();, the error message is empty. I can manually create the message ‘No query results for model [$classname] $value’ based on input parameters and class name, but is there a built-in way for the framework to do this automatically (in case the message format changes in the future) without additional database queries?
To make Laravel automatically generate text for ModelNotFoundException when searching for a model with a custom formatted primary key and avoid database queries for incorrectly formatted values, use Laravel’s built-in key format validation mechanisms before executing the query.
Contents
- Problem with ModelNotFoundException for custom primary keys
- Built-in format validation using UUID and ULID
- Setting up implicit model binding in routes
- Custom primary key format validation
- Handling exceptions with proper messages
Problem with ModelNotFoundException for custom primary keys
By default, Laravel generates the error message “No query results for model [ClassName] $value” when throwing ModelNotFoundException. However, when using custom primary keys with specific formatting, two main issues arise:
- Unnecessary database queries when passing incorrectly formatted keys
- Empty or incomplete error messages when manually throwing the exception
As seen in the Laravel documentation, the findOrFail and firstOrFail methods throw ModelNotFoundException when no results are found, but they don’t validate the key format before executing the query.
Built-in format validation using UUID and ULID
Laravel provides built-in mechanisms for validating primary key formats through various traits that check the key format before executing the database query:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
class YourModel extends Model
{
use HasUuids; // or HasUlids, HasVersion7Uuids
// Laravel will automatically validate UUID format before querying
protected $keyType = 'string';
public $incrementing = false;
}
As explained in the cosmastech article, when using these traits:
“If your model uses the HasUniqueStringIds trait or its descendants (HasUuids, HasUlids, HasVersion7Uuids), then a ModelNotFoundException will be thrown if the route parameter doesn’t match the specified valid type. For example, if you’re using HasUuids and a user requests GET /podcasts/this-is-not-a-uuid, then this-is-not-a-uuid is not a UUID and returns a 404. Although this throws the same exception as if the string wasn’t found in the database, the database is never queried because the binding fails during key validation.”
Setting up implicit model binding in routes
For automatic validation of custom key formats, configure implicit model binding in your routes:
// In routes/web.php or routes/api.php
use App\Models\YourModel;
Route::get('/models/{model}', function (YourModel $model) {
return $model;
})->where('model', '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}');
When using the HasUuids or HasUlids traits, validation happens automatically without needing to specify a regular expression in the route.
Custom primary key format validation
If your key has a specific format that’s not a standard UUID/ULID, implement a custom validator:
1. Creating a custom trait
namespace App\Traits;
trait HasCustomPrimaryKeyFormat
{
public function getRouteKeyName()
{
return 'your_custom_key';
}
public function resolveRouteBinding($value, $field = null)
{
// First validate the key format
if (!$this->isValidCustomKeyFormat($value)) {
throw new \Illuminate\Database\Eloquent\ModelNotFoundException(
"No query results for model [".get_class($this)."] ".$value
);
}
// Then execute the query only if the format is correct
return parent::resolveRouteBinding($value, $field);
}
protected function isValidCustomKeyFormat($value)
{
// Implement your format validation logic
return preg_match('/^[A-Z]{2}-\d{6}$/', $value) === 1;
}
}
2. Using the trait in your model
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasCustomPrimaryKeyFormat;
class YourModel extends Model
{
use HasCustomPrimaryKeyFormat;
protected $primaryKey = 'your_custom_key';
public $incrementing = false;
protected $keyType = 'string';
}
Handling exceptions with proper messages
To ensure error messages are automatically generated without manual construction, use the exception rendering mechanism:
// In your App\Exceptions\Handler.php
public function register()
{
$this->renderable(function (\Illuminate\Database\Eloquent\ModelNotFoundException $e, $request) {
if ($request->wantsJson()) {
return response()->json([
'message' => $e->getMessage(),
'model' => class_basename($e->getModel()),
'key' => $e->getKey()
], 404);
}
});
}
To ensure proper message formatting without additional queries, add to your model:
use Illuminate\Database\Eloquent\ModelNotFoundException;
class YourModel extends Model
{
// ... other model properties
public static function findOrFail($id, $columns = ['*'])
{
// Check format before querying
if (!static::isValidPrimaryKeyFormat($id)) {
throw new ModelNotFoundException(
"No query results for model [".static::class."] ".$id
);
}
return parent::findOrFail($id, $columns);
}
protected static function isValidPrimaryKeyFormat($value)
{
// Your validation logic
return true; // actual implementation is yours
}
}
Sources
- Laravel Eloquent: Getting Started - ModelNotFoundException
- Avoid Leaking Model Info: Securing Responses When a Model Is Not Found - cosmastech
- Laravel eloquent find() having a custom primary key returns not found - Stack Overflow
- Validator Error: Forcing A Unique Rule To Ignore A Given ID fails if primary key is not named “id” - GitHub
Conclusion
To automatically generate messages for ModelNotFoundException with custom primary keys without database queries:
- Use built-in traits like
HasUuids,HasUlids, orHasVersion7Uuidsfor standard UUID/ULID formats - Implement a custom trait with format validation for specific key formats
- Configure implicit model binding for automatic validation in routes
- Override methods like
findOrFailorresolveRouteBindingto add validation logic - Use the exception rendering mechanism for automatic message formatting
These approaches help avoid unnecessary database queries when passing incorrectly formatted keys and ensure automatic error message generation according to Laravel standards.