Три способа работать с БД в Laravel
Glossary overview

Три способа работать с БД в Laravel

В Laravel не всё взаимодействие с базой данных идёт через Eloquent. Есть три уровня абстракции — от высокого к низкому. Все три используют одно и то же подключение к БД и поддерживают транзакции.

Уровень абстракции:

  Высокий    [Eloquent ORM]     — модели, связи, автоматизация
      ↓
  Средний    [Query Builder]    — построитель запросов, без моделей
      ↓
  Низкий     [Raw SQL]          — чистый SQL

1. Eloquent ORM

Основной способ работы с БД в Laravel. Каждая таблица представлена моделью — PHP-классом. Модель умеет сама заполнять поля, отслеживать timestamps, подгружать связи. Это 80–90% повседневного кода.

CRUD через Eloquent

// Создание
$order = Order::create([
    'user_id'     => 1,
    'total_price' => 259.98,
    'status'      => 'pending',
]);

// Чтение
$order = Order::find(1);
$order = Order::where('status', 'pending')->first();
$orders = Order::where('status', 'paid')->get();

// Обновление
$order->update(['status' => 'paid']);

// Удаление
$order->delete();

Связи (Relations)

Eloquent автоматически подгружает связанные модели. Не нужно писать JOIN вручную:

// Модель
class Order extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }
}

// Использование
$order = Order::with('items', 'user')->find(1);

$order->user->name;          // имя покупателя
$order->items;               // коллекция товаров
$order->items->count();      // количество позиций

Scopes — переиспользуемые фильтры

class Order extends Model
{
    // Scope — именованный фильтр
    public function scopePaid($query)
    {
        return $query->where('status', 'paid');
    }

    public function scopeRecent($query)
    {
        return $query->where('created_at', '>=', now()->subDays(7));
    }
}

// Использование — читается как текст
$orders = Order::paid()->recent()->get();

Когда использовать Eloquent

Обычный CRUD, работа с отдельными записями, связи между моделями, валидация, события модели. Всё, где ты оперируешь объектами: «создай заказ», «обнови статус», «получи товары заказа».

2. Query Builder

Построитель запросов через фасад DB. Работает напрямую с таблицами, без моделей. Ближе к SQL, но всё ещё с удобным PHP-синтаксисом. Результат — массивы или объекты stdClass, не модели Eloquent.

Примеры

use Illuminate\Support\Facades\DB;

// Выборка
$orders = DB::table('orders')
    ->where('status', 'paid')
    ->orderBy('created_at', 'desc')
    ->get();

// Агрегация — сумма продаж за март
$revenue = DB::table('orders')
    ->where('status', 'paid')
    ->whereMonth('created_at', 3)
    ->sum('total_price');

// Средний чек по дням
$daily = DB::table('orders')
    ->select(DB::raw('DATE(created_at) as date'), DB::raw('AVG(total_price) as avg_price'))
    ->where('status', 'paid')
    ->groupBy('date')
    ->get();

// JOIN — топ товаров по продажам
$topProducts = DB::table('order_items')
    ->join('products', 'products.id', '=', 'order_items.product_id')
    ->select('products.name', DB::raw('SUM(order_items.quantity) as total_sold'))
    ->groupBy('products.id', 'products.name')
    ->orderByDesc('total_sold')
    ->limit(10)
    ->get();

// Вставка
DB::table('logs')->insert([
    'action'     => 'order_created',
    'payload'    => json_encode($data),
    'created_at' => now(),
]);

// Обновление
DB::table('orders')
    ->where('id', 42)
    ->update(['status' => 'shipped']);

Когда использовать Query Builder

Отчёты, агрегации, массовые операции, сложные JOIN-ы, работа с таблицами без моделей (логи, временные таблицы). Везде, где не нужны фишки Eloquent (связи, события, мутаторы) и важна производительность или гибкость запроса.

3. Raw SQL

Чистый SQL через методы DB::select(), DB::insert(), DB::update(), DB::delete(), DB::statement(). Для случаев, когда Query Builder не может выразить нужный запрос.

Примеры

use Illuminate\Support\Facades\DB;

// SELECT — параметры через плейсхолдеры (защита от SQL-инъекций)
$results = DB::select('
    SELECT products.name, SUM(order_items.quantity) as total_sold
    FROM order_items
    JOIN products ON products.id = order_items.product_id
    WHERE order_items.created_at >= ?
    GROUP BY products.id, products.name
    HAVING total_sold > ?
    ORDER BY total_sold DESC
', [now()->subMonth(), 100]);

// INSERT
DB::insert('INSERT INTO logs (action, created_at) VALUES (?, ?)', [
    'manual_import',
    now(),
]);

// UPDATE
DB::update('UPDATE orders SET status = ? WHERE created_at < ?', [
    'archived',
    now()->subYear(),
]);

// Произвольный SQL (CREATE, ALTER, DROP и т.д.)
DB::statement('ALTER TABLE orders ADD COLUMN notes TEXT AFTER status');

Raw-выражения внутри Eloquent и Query Builder

Необязательно полностью переходить на Raw SQL — можно вставить сырое выражение в обычный запрос:

// DB::raw() внутри Query Builder
$orders = DB::table('orders')
    ->select(DB::raw('YEAR(created_at) as year'), DB::raw('COUNT(*) as count'))
    ->groupBy('year')
    ->get();

// selectRaw, whereRaw, orderByRaw — в Eloquent
$orders = Order::selectRaw('MONTH(created_at) as month, COUNT(*) as count')
    ->whereRaw('total_price > ? AND status = ?', [100, 'paid'])
    ->groupByRaw('MONTH(created_at)')
    ->orderByRaw('count DESC')
    ->get();

Когда использовать Raw SQL

Сложные подзапросы, оконные функции (OVER, PARTITION BY), специфичный синтаксис конкретной СУБД, оптимизация узких мест. На практике — редко, но полезно знать, что можно.

Сравнение

                  Eloquent           Query Builder        Raw SQL
──────────────────────────────────────────────────────────────────────
Результат         Модели             stdClass / массивы   stdClass / массивы
Связи             Да (with, has)     Нет (JOIN вручную)   Нет
Timestamps        Автоматически      Вручную              Вручную
События модели    Да (creating,      Нет                  Нет
                  updated и т.д.)
Читаемость        Высокая            Средняя              Зависит от SQL
Производительность Чуть медленнее    Быстрее              Самый быстрый
Гибкость SQL      Ограниченная       Высокая              Полная
Типичное          CRUD, связи,       Отчёты, агрегации,   Сложные запросы,
применение        бизнес-логика      массовые операции    оптимизация

Аналогия с WordPress

В WordPress похожая градация: WP_Query / get_posts() — это аналог Eloquent (высокий уровень, автоматизация). $wpdb->get_results() с подготовленными запросами — аналог Query Builder. $wpdb->query() с чистым SQL — аналог Raw SQL. Принцип тот же: начинаешь с высокого уровня и опускаешься ниже только когда нужно.