Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти
Інше
Події Про нас

Статті за тегом «Бекенд»

Статті, новини, туторіали та переклади від учасників спільноти

No results.
Tips 17 липня 2026

Soft Deletes - ніколи не втрачайте дані

Користувач випадково видалив свій акаунт. Відновити не можна. Негатив забезпечено.

Soft Deletes позначають запис як видалений, але фактично не прибирають його з бази.

Як працює:

Додаємо поле deleted_at, а Laravel автоматично приховує такі записи.

Корисні методи:

  • forceDelete() - повне видалення
  • withTrashed() - включити видалені
  • onlyTrashed() - лише видалені
  • restore() - відновити

Порада: Сумісно з GDPR: можна відновити за 30 днів, а потім зробити force delete.

// Міграція
Schema::table('users', function (Blueprint $table) {
  $table->softDeletes();
});

// Модель
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
  use SoftDeletes;
}

// Використання
$user->delete(); // Soft delete
$user->forceDelete(); // Повне видалення

// Запити
User::all(); // Тільки активні
User::withTrashed()->get(); // Включно з видаленими
User::onlyTrashed()->get(); // Лише видалені

// Відновлення
$user = User::withTrashed()->find(1);
$user->restore();

// Перевірка
if ($user->trashed()) {
  // Користувач видалений
}
Tips 16 липня 2026

updateOrCreate() - простий спосіб upsert

Перевіряєте, чи існує запис, а потім оновлюєте або створюєте? Забагато коду.

updateOrCreate() виконує обидві операції за один раз.

Як працює:

  • Перший аргумент - умови пошуку
  • Другий - дані для оновлення/створення

Магія:

  • Якщо запис знайдено - оновлює
  • Якщо ні - створює

Порада: Ідеально підходить для інтеграцій з API.

// ПОГАНО: два запити, зайвий код
$user = User::where('email', 'john@example.com')->first();
if ($user) {
  $user->update(['name' => 'John Doe']);
} else {
  User::create([
    'email' => 'john@example.com',
    'name' => 'John Doe'
  ]);
}

// ДОБРЕ: один метод, один запит
User::updateOrCreate(
  ['email' => 'john@example.com'],
  ['name' => 'John Doe', 'verified' => true]
);

// Синхронізація даних з API
$apiUsers = ExternalAPI::getUsers();
foreach ($apiUsers as $apiUser) {
  User::updateOrCreate(
    ['external_id' => $apiUser['id']],
    ['name' => $apiUser['name'], 'email' => $apiUser['email']]
  );
}
Tips 15 липня 2026

Query Scopes - принцип DRY для запитів

Пишете where('status', 'active') у 20 контролерах? Перестаньте дублювати код.

Query Scopes дозволяють повторно використовувати логіку запитів. Описали один раз - застосовуєте всюди.

Два типи:

  • Локальні скоупи - методи в моделі (з параметрами або без).
  • Анонімні скоупи - inline-фільтри без визначення методу.

Чому варто використовувати скоупи:

  1. Повторне використання коду
  2. Краща читабельність
  3. Простота тестування
  4. Легко комбінуються між собою

Порада: Методи локальних скоупів починаються зі слова scope - Laravel автоматично прибирає його.

// User Model
public function scopeActive($query)
{
  return $query->where('status', 'active');
}

public function scopeVerified($query)
{
  return $query->whereNotNull('email_verified_at');
}

public function scopeCreatedAfter($query, $date)
{
  return $query->where('created_at', '>', $date);
}

// Використання - чисто та читабельно
$users = User::active()->verified()->get();

$recentUsers = User::active()
  ->createdAfter(now()->subDays(7))
  ->get();

// Комбінування зі звичайними методами запиту
$admins = User::active()
  ->where('role', 'admin')
  ->orderBy('name')
  ->get();
Tips 02 липня 2026

Уникати select(*) в продакшені

Завантаження всіх колонок? Витрачається пам'ять та пропускну здатність.

Обирай тільки колонки які реально використовуєш. Менший об'єм даних. Швидші запити. Менше пам'яті.

Прихована ціна:

Текстові колонки можуть бути величезними. JSON колонки ще більші. Timestamps які можливо не потрібні.

Перевага індексу:

Запити з меншою кількістю колонок можуть використовувати covering індекси.

Порада: Профілюй з EXPLAIN щоб побачити чи запит використовує index-only scan.

// ПОГАНО: Завантажує все включаючи великі текстові поля
$posts = Post::where('published', true)->get();

// ДОБРЕ: Тільки потрібні колонки
$posts = Post::select('id', 'title', 'slug', 'published_at')
    ->where('published', true)
    ->get();

// Приклад API відповіді
public function index()
{
    return Post::select([
            'id',
            'title',
            'slug',
            'excerpt',
            'published_at',
        ])
        ->with('author:id,name,avatar')
        ->where('published', true)
        ->paginate(20);
}

// Dropdown/автозаповнення
$users = User::select('id', 'name')
    ->where('active', true)
    ->get();

// Зі зв'язками - вказуй колонки для обох
$posts = Post::select('id', 'title', 'author_id')
    ->with('author:id,name')
    ->get();
Tips 01 липня 2026

selectRaw() для обчислюваних колонок

Потрібні обчислені колонки без завантаження повних моделей?

selectRaw() робить обчислення на рівні SQL. База даних обробляє. Повертає тільки результати.

Поширене використання:

  • Ціна з податком
  • Вік з дати народження
  • Обчислення відстані
  • Об'єднані рядки

Виграш продуктивності:

База даних робить математику. Не PHP. Повертає тільки те що потрібно.

Порада: Завжди використовувати parameter binding щоб запобігти SQL ін'єкції.

// Обчислення на стороні бази даних
$products = Product::selectRaw('
    id,
    name,
    price,
    price * 1.2 as price_with_tax,
    price * quantity as total
')->get();

// Обчислення віку
$users = User::selectRaw('
    id,
    name,
    YEAR(CURDATE()) - YEAR(birthdate) as age
')->get();

// Конкатенація
$users = User::selectRaw("
    CONCAT(first_name, ' ', last_name) as full_name,
    email
")->get();

// Безпечний parameter binding
$taxRate = 1.15;
$products = Product::selectRaw(
    'price * ? as price_with_tax',
    [$taxRate]
)->get();
// Обчислення відстані
$lat = 40.7128;
$lng = -74.0060;

$stores = Store::selectRaw("
    *,
    (6371 * acos(cos(radians(?))
    * cos(radians(lat))
    * cos(radians(lng) - radians(?))
    + sin(radians(?))
    * sin(radians(lat)))) AS distance
", [$lat, $lng, $lat])
    ->having('distance', '<', 10)
    ->orderBy('distance')
    ->get();

// Складні обчислення з декількома параметрами
$discount = 0.1;
$minPrice = 100;

$items = Product::selectRaw('
    id,
    name,
    price,
    price * (1 - ?) as discounted_price,
    CASE WHEN price > ? THEN "premium" ELSE "standard" END as tier
', [$discount, $minPrice])->get();
Tips 30 червня 2026

Lazy Collections з альтернативою cursor()

cursor() тримає з'єднання з БД відкритим. Ризиковано для довгих операцій.

lazy() використовує chunks внутрішньо. Безпечніше.

Як працює lazy():

Завантажує дані пакетами (типово 1000). Видає елементи один за одним. Закриває з'єднання між пакетами.

Продуктивність:

Ефективна пам'ять як cursor(). Безпека з'єднання як chunk(). Найкраще з обох світів.

Порада: Налаштуй розмір chunk з lazy(500) для точного налаштування.

// Ризиковано: З'єднання залишається відкритим
User::cursor()->each(function ($user) {
    // Довга обробка тут може таймаутнути
    $this->sendEmail($user);
});

// Краще: Chunks з lazy ітерацією
User::lazy()->each(function ($user) {
    // Безпечніше для довгих операцій
    $this->sendEmail($user);
});

// Власний розмір chunk
User::lazy(200)->each(function ($user) {
    $this->processUser($user);
});

// Працює з фільтрами
User::where('active', true)
    ->orderBy('created_at')
    ->lazy()
    ->filter(fn($user) => $user->hasOrders())
    ->each(fn($user) => $this->sendPromo($user));
Tips 29 червня 2026

toBase() - скидання накладних витрат Eloquent

Потрібна сира продуктивність? Eloquent моделі додають накладні витрати.

toBase() конвертує запит в базовий query builder. Без гідратації моделі.

Повертає stdClass замість моделей. Швидше. Менше пам'яті.

Коли використовувати:

  • Великі експорти де не потрібні методи моделі
  • Запити агрегації
  • Трансформація даних перед відправкою в API
  • Фонова обробка

Компроміс:

Немає accessors, mutators або зв'язків. Тільки сирі дані.

Порада: Використовувати для операцій тільки читання на великих наборах даних.

// ПОГАНО: Створює 10,000 Eloquent моделей
$users = User::where('active', true)->get();

// ДОБРЕ: Повертає stdClass об'єкти
$users = User::where('active', true)
    ->toBase()
    ->get();

// Приклад: Експорт в CSV
$users = User::select('name', 'email')
    ->toBase()
    ->get();

foreach ($users as $user) {
    // $user це stdClass, не User модель
    fputcsv($file, [$user->name, $user->email]);
}

// Працює з chunk теж
User::where('active', true)
    ->toBase()
    ->chunk(1000, function($users) {
        // Обробка без накладних витрат моделі
    });
Tips 27 червня 2026

DB::raw() - коли Eloquent недостатньо

Потрібен складний SQL? Функції дат? Математичні операції?

Eloquent має межі. DB::raw() дозволяє писати сирий SQL всередині запитів.

Поширені випадки використання:

  • Функції дати/часу
  • Математичні операції
  • Специфічні для бази даних функції
  • Складна агрегація

Попередження безпеки:

Ніколи не передавай вхідні дані користувача безпосередньо в raw(). Використовувати біндінги.

Порада: Розглянь власні query scopes щоб повторно використовувати складні raw вирази.

// Функції дат
$users = User::where('created_at', '>=',
    DB::raw('DATE_SUB(NOW(), INTERVAL 30 DAY)')
)->get();

// Математичні операції
$products = Product::select('*',
    DB::raw('price * quantity as total')
)->get();

// CASE вирази
$posts = Post::select('*',
    DB::raw('CASE WHEN views > 1000 THEN "popular"
             ELSE "regular" END as status')
)->get();

// Агрегація з групуванням
$stats = Order::select(
    DB::raw('DATE(created_at) as date'),
    DB::raw('SUM(total) as revenue'),
    DB::raw('COUNT(*) as orders')
)
->groupBy('date')
->get();

// БЕЗПЕЧНО: Використання біндінгів
$status = 'published';
Post::whereRaw('LOWER(status) = ?', [strtolower($status)])->get();
Tips 10 квітня 2026

sole() - суворе отримання одного результату

Запит має повернути рівно один запис. Повертає нуль або декілька? Це баг.

first() мовчки повертає null. sole() кидає виняток якщо не рівно один.

Коли використовувати:

  • Отримання за унікальним ідентифікатором
  • Коли декілька результатів вказують на пошкодження даних
  • Критична бізнес-логіка

Винятки що кидаються:

  • RecordsNotFoundException - нуль результатів
  • MultipleRecordsFoundException - більше одного

Порада: Використовувати у фінансових операціях де очікується рівно один запис.

// ПОГАНО: Мовчки повертає null або перший з багатьох
$user = User::where('email', $email)->first();
// Що якщо є дублікати? Мовчазний баг.

// ДОБРЕ: Гарантує рівно один результат
try {
    $user = User::where('email', $email)->sole();
    // Гарантовано єдиний користувач що підходить
} catch (RecordsNotFoundException $e) {
    // Користувач не знайдений
} catch (MultipleRecordsFoundException $e) {
    // Пошкодження даних - існують дублікати
}

// В обробці рахунків
$invoice = Invoice::where('invoice_number', $number)->sole();
// Дублікати були б серйозним багом

// firstOr vs soleOr
$user = User::where('email', $email)->soleOr(function () {
    throw new UserNotFoundException();
});

// Зі зв'язками
$user = User::with('profile')->where('id', $id)->sole();