Как заставить Laravel автоматически генерировать текст для исключения ModelNotFoundException при поиске модели с кастомным форматированием первичного ключа? Моя модель имеет первичный ключ в виде специфически отформатированной строки. Я хочу избежать запроса к базе данных, если строка имеет неверный формат. При ручном выбрасывании исключения throw new ModelNotFoundException(); сообщение об ошибке пустое. Я могу сам сформировать сообщение ‘No query results for model [$classname] $value’ на основе входных параметров и имени класса, но существует ли встроенный способ, чтобы фреймворк сделал это автоматически (на случай изменения формата сообщения в будущем) без дополнительного запроса к базе данных?
Чтобы заставить Laravel автоматически генерировать текст для исключения ModelNotFoundException при поиске модели с кастомным форматированием первичного ключа и избежать запросов к базе данных для некорректно отформатированных значений, используйте встроенные механизмы валидации формата ключа перед выполнением запроса.
Содержание
- Проблема с ModelNotFoundException при кастомных первичных ключах
- Встроенная валидация формата с использованием UUID и ULID
- Настройка неявного связывания моделей в маршрутах
- Кастомная валидация формата первичного ключа
- Обработка исключений с правильными сообщениями
Проблема с ModelNotFoundException при кастомных первичных ключах
По умолчанию Laravel генерирует сообщение об ошибке “No query results for model [ClassName] $value” при выбрасывании ModelNotFoundException. Однако при использовании кастомных первичных ключей с определенным форматированием возникают две основные проблемы:
- Ненужные запросы к базе данных при передаче некорректно отформатированных ключей
- Пустые или неполные сообщения об ошибках при ручном выбрасывании исключения
Из документации Laravel видно, что findOrFail и firstOrFail методы выбрасывают ModelNotFoundException при отсутствии результатов, но не валидируют формат ключа до выполнения запроса.
Встроенная валидация формата с использованием UUID и ULID
Laravel предоставляет встроенные механизмы валидации формата первичных ключей через различные трейты, которые проверяют формат ключа до выполнения запроса к базе данных:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
class YourModel extends Model
{
use HasUuids; // или HasUlids, HasVersion7Uuids
// Laravel автоматически будет проверять формат UUID перед запросом
protected $keyType = 'string';
public $incrementing = false;
}
Как объясняется в статье cosmastech, при использовании этих трейтов:
“Если ваша модель использует трейт HasUniqueStringIds или его наследников (HasUuids, HasUlids, HasVersion7Uuids), то ModelNotFoundException будет выброшен, если параметр маршрута не соответствует указанному валидному типу. Например, если вы используете HasUuids, а пользователь запрашивает GET /podcasts/this-is-not-a-uuid, то this-is-not-a-uuid не является UUID и возвращает 404. Хотя это выбрасывает то же исключение, что и если бы строка не была найдена в базе данных, база данных никогда не запрашивается, потому что привязка не проходит во время валидации ключа.”
Настройка неявного связывания моделей в маршрутах
Для автоматической валидации кастомных форматов ключей настройте неявное связывание моделей в маршрутах:
// В файле routes/web.php или 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}');
При использовании трейтов HasUuids или HasUlids валидация происходит автоматически без необходимости указывать регулярное выражение в маршруте.
Кастомная валидация формата первичного ключа
Если ваш ключ имеет специфический формат, не являющийся стандартным UUID/ULID, реализуйте кастомный валидатор:
1. Создание кастомного трейта
namespace App\Traits;
trait HasCustomPrimaryKeyFormat
{
public function getRouteKeyName()
{
return 'your_custom_key';
}
public function resolveRouteBinding($value, $field = null)
{
// Сначала валидируем формат ключа
if (!$this->isValidCustomKeyFormat($value)) {
throw new \Illuminate\Database\Eloquent\ModelNotFoundException(
"No query results for model [".get_class($this)."] ".$value
);
}
// Затем выполняем запрос только если формат корректен
return parent::resolveRouteBinding($value, $field);
}
protected function isValidCustomKeyFormat($value)
{
// Реализуйте вашу логику валидации формата
return preg_match('/^[A-Z]{2}-\d{6}$/', $value) === 1;
}
}
2. Использование трейта в модели
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';
}
Обработка исключений с правильными сообщениями
Чтобы гарантировать, что сообщения об ошибках генерируются автоматически без ручного конструирования, используйте механизм преобразования исключений:
// В вашем 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);
}
});
}
Для обеспечения правильного формата сообщения без дополнительных запросов, добавьте в вашу модель:
use Illuminate\Database\Eloquent\ModelNotFoundException;
class YourModel extends Model
{
// ... другие свойства модели
public static function findOrFail($id, $columns = ['*'])
{
// Проверяем формат перед запросом
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)
{
// Ваша логика валидации
return true; // реальная реализация ваша
}
}
Источники
- 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
Заключение
Чтобы автоматически генерировать сообщения для ModelNotFoundException при кастомных первичных ключах без запросов к базе данных:
- Используйте встроенные трейты
HasUuids,HasUlidsилиHasVersion7Uuidsдля стандартных форматов UUID/ULID - Реализуйте кастомный трейт с валидацией формата для специфических форматов ключей
- Настройте неявное связывание моделей для автоматической валидации в маршрутах
- Переопределите методы
findOrFailилиresolveRouteBindingдля добавления логики валидации - Используйте механизм преобразования исключений для автоматического форматирования сообщений
Эти подходы позволяют избежать ненужных запросов к базе данных при передаче некорректно отформатированных ключей и обеспечивают автоматическую генерацию сообщений об ошибках в соответствии со стандартами Laravel.