Avatar ·

Проверка существования записи в Laravel: правильные способы и частые ошибки

Всем привет! Я новичок в Laravel и PHP, пишу свой первый проект — простой блог. У меня есть модель Post и таблица posts. Мне нужно перед добавлением нового поста проверить, существует ли уже запись с таким же заголовком (slug). Я перепробовал несколько вариантов, но чувствую, что делаю что-то не так.

Вот мой текущий код в контроллере:

public function store(Request $request)
{
    $slug = $request->input('slug');
    $existing = DB::table('posts')->where('slug', $slug)->get();
    if (count($existing) > 0) {
        return back()->with('error', 'Такой slug уже существует');
    }
    // ... сохранение
}

Но я прочитал, что использовать count() и get() для этого неэффективно, особенно если таблица большая. Также я видел, что кто-то использует first(), exists() или doesntExist(). А ещё есть метод find() для проверки по ID.

Подскажите, пожалуйста:

  • Какой способ самый правильный и производительный для проверки существования записи?
  • Чем отличается exists() от doesntExist()?
  • Нужно ли использовать first() или достаточно exists()?
  • Как правильно проверять по первичному ключу?

Заранее спасибо за помощь! Хочется сразу писать правильно, чтобы потом не переделывать.

<<>>

Лучший способ проверки существования записи в Laravel

Вы абсолютно правы, что подход с get() и count() — не лучший. Давайте разберём все правильные варианты от самых простых до продвинутых.

1. Метод exists() — самый правильный и производительный

Это оптимальный способ, если вам нужно только узнать, есть ли запись. Он выполняет SQL запрос вида SELECT EXISTS(...), который возвращает булево значение без загрузки всей модели:

use App\Models\Post;

public function store(Request $request)
{
    $slug = $request->input('slug');
    
    $exists = Post::where('slug', $slug)->exists();
    
    if ($exists) {
        return back()->with('error', 'Такой slug уже существует');
    }
    
    // ... сохранение
}

2. Метод doesntExist() — обратная логика

Если вам удобнее проверять на отсутствие, используйте doesntExist():

if (Post::where('slug', $slug)->doesntExist()) {
    // запись не найдена — можно сохранять
    $post = Post::create([...]);
}

3. Проверка по первичному ключу (ID)

Для проверки по ID используйте find() или whereKey():

// По ID
$post = Post::find($id);
if ($post) {
    // запись существует
}

// Или через exists()
$exists = Post::whereKey($id)->exists();

4. Когда нужен first()?

first() загружает всю модель в память. Используйте его, когда вам нужны данные записи, а не просто факт существования:

$post = Post::where('slug', $slug)->first();

if ($post) {
    // $post — это объект модели, можно получить его поля
    return view('edit', compact('post'));
}

5. Проверка через уникальную валидацию (рекомендуемый способ для форм)

Лучший способ для проверки уникальности при создании/обновлении — использовать валидацию Laravel:

public function store(Request $request)
{
    $validated = $request->validate([
        'slug' => 'required|unique:posts,slug',
        'title' => 'required|max:255',
    ]);
    
    // Если валидация пройдена — slug уникален
    $post = Post::create($validated);
}

Для обновления записи (исключая текущую):

'slug' => 'required|unique:posts,slug,' . $post->id,

Сравнение производительности

Вот как выглядят SQL запросы для разных методов:

  • get() + count(): SELECT * FROM posts WHERE slug = ? — загружает все поля всех записей
  • first(): SELECT * FROM posts WHERE slug = ? LIMIT 1 — загружает одну полную запись
  • exists(): SELECT EXISTS(SELECT 1 FROM posts WHERE slug = ?) — только булево значение

Вывод: Для простой проверки используйте exists() или doesntExist(). Для проверки уникальности в формах — валидацию с правилом unique. И никогда не используйте get() только для подсчёта — это пустая трата ресурсов.

<<>>

Альтернативный подход: через Query Builder и агрегатные функции

Если вы не используете Eloquent, а работаете напрямую с Query Builder, тоже можно оптимизировать проверку. Вместо get() и count() используйте value() с агрегатной функцией:

use Illuminate\Support\Facades\DB;

$exists = DB::table('posts')
    ->where('slug', $slug)
    ->value(DB::raw('COUNT(*)'));

if ($exists > 0) {
    // запись существует
}

Или ещё короче — через exists() в Query Builder:

$exists = DB::table('posts')
    ->where('slug', $slug)
    ->exists();

Также полезно знать про метод when() для условных проверок в цепочке запросов:

$query = Post::query();

if ($request->has('slug')) {
    $query->where('slug', $request->slug);
}

$exists = $query->exists();

И не забывайте про индексы в базе данных! Для полей, по которым часто проверяете существование (slug, email), обязательно создайте индекс:

// В миграции
$table->string('slug')->unique();  // автоматически создаёт индекс

// Или отдельно
$table->index('slug');

Это ускорит проверку в десятки раз на больших таблицах.