Транзакції в базі даних є важливим засобом для забезпечення цілісності даних у складних додатках. Laravel пропонує потужні можливості для роботи з транзакціями, які гарантують, що пов’язані операції в базі даних виконуються атомарно — або всі зміни відбуваються, або жодна з них не застосовується.
Метод DB::transaction в Laravel є найзручнішим способом для управління транзакціями:
use Illuminate\Support\Facades\DB;
DB::transaction(function () {
DB::table('accounts')->where('id', 1)->decrement('balance', 100);
DB::table('accounts')->where('id', 2)->increment('balance', 100);
});
Цей метод автоматично керує життєвим циклом транзакції, скасовуючи всі зміни, якщо в середині замикання виникне будь-яка помилка.
Розглянемо фінансовий застосунок, що обробляє операції з перерозподілу інвестиційних портфелів. Необхідно одночасно внести кілька змін в акаунти для підтримання точності фінансових записів:
<?php
namespace App\Services;
use App\Models\Account;
use App\Models\Transaction;
use App\Models\Portfolio;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class PortfolioRebalancingService
{
public function rebalancePortfolio(Portfolio $portfolio, array $adjustments): bool
{
return DB::transaction(function () use ($portfolio, $adjustments) {
$totalAdjustment = 0;
foreach ($adjustments as $adjustment) {
$account = Account::findOrFail($adjustment['account_id']);
if ($adjustment['amount'] < 0 && $account->balance < abs($adjustment['amount'])) {
throw new \Exception("Недостатньо коштів на рахунку {$account->id}");
}
$account->increment('balance', $adjustment['amount']);
$totalAdjustment += $adjustment['amount'];
Transaction::create([
'account_id' => $account->id,
'portfolio_id' => $portfolio->id,
'amount' => $adjustment['amount'],
'type' => $adjustment['amount'] > 0 ? 'credit' : 'debit',
'description' => 'Перерозподіл портфеля',
'processed_at' => Carbon::now(),
]);
}
if (abs($totalAdjustment) > 0.01) {
throw new \Exception('Суми транзакцій повинні зрівноважуватись');
}
$portfolio->update([
'last_rebalanced_at' => Carbon::now(),
'status' => 'balanced'
]);
return true;
}, 3);
}
public function transferFunds(Account $fromAccount, Account $toAccount, float $amount): void
{
DB::beginTransaction();
try {
if ($fromAccount->balance < $amount) {
throw new \Exception('Недостатньо коштів для переказу');
}
$fromAccount->decrement('balance', $amount);
$toAccount->increment('balance', $amount);
Transaction::create([
'account_id' => $fromAccount->id,
'amount' => -$amount,
'type' => 'transfer_out',
'reference_account_id' => $toAccount->id,
'processed_at' => Carbon::now(),
]);
Transaction::create([
'account_id' => $toAccount->id,
'amount' => $amount,
'type' => 'transfer_in',
'reference_account_id' => $fromAccount->id,
'processed_at' => Carbon::now(),
]);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
public function batchUpdateHoldings(Portfolio $portfolio, array $holdings): void
{
DB::transaction(function () use ($portfolio, $holdings) {
$portfolio->holdings()->delete();
foreach ($holdings as $holding) {
$portfolio->holdings()->create([
'symbol' => $holding['symbol'],
'quantity' => $holding['quantity'],
'average_cost' => $holding['average_cost'],
'current_value' => $holding['current_value'],
]);
}
$totalValue = collect($holdings)->sum('current_value');
$portfolio->update(['total_value' => $totalValue]);
});
}
}
class SubscriptionManager
{
public function upgradePlan(User $user, Plan $newPlan): void
{
DB::transaction(function () use ($user, $newPlan) {
$currentSubscription = $user->subscription;
if ($currentSubscription) {
$remainingDays = $currentSubscription->ends_at->diffInDays(Carbon::now());
$creditAmount = ($currentSubscription->plan->price / 30) * $remainingDays;
$user->account->increment('credit_balance', $creditAmount);
$currentSubscription->update(['status' => 'cancelled']);
}
$user->subscriptions()->create([
'plan_id' => $newPlan->id,
'starts_at' => Carbon::now(),
'ends_at' => Carbon::now()->addMonth(),
'status' => 'active'
]);
$user->account->decrement('credit_balance', $newPlan->price);
if ($user->account->credit_balance < 0) {
throw new \Exception('Недостатньо коштів на рахунку');
}
});
}
}
Laravel підтримує механізми повторних спроб транзакцій для обробки блокувань та надає контроль рівня ізоляції для складніших сценаріїв. Фреймворк також інтегрує обробку транзакцій з моделями Eloquent, що дозволяє викликати User::transaction() прямо в класах моделей для більш семантичних операцій.
Ви готові відкрити нові горизонти у роботі з геопросторовими даними в Laravel? Дізнайтеся, як за допомогою PostGIS та пакету Laravel-Magellan можна легко зберігати, запитувати та маніпулювати інформацією про розташування, перетворюючи ваші проекти на вражаючі рішення у сфері картографії та геолокації!
Хочете забезпечити повну прозорість у своїх Laravel-додатках? Пакет Laravel Audit Log допоможе вам детально відстежувати всі зміни моделей Eloquent та відповідати вимогам регуляторів. Читайте далі, щоб дізнатися, як цей потужний інструмент може підвищити надійність вашого проєкту
PHP 8.5 обіцяє безліч нових можливостей, таких як оператор Pipe, функції `array_first()` та `array_last()`, а також нове розширення URI. Чи готові ви дізнатися, як ці функції можуть спростити вашу розробку? Читайте далі, щоб дізнатися більше про ці захоплюючі нововведення