Когда пишешь что-то чуть сложнее «вывести список пользователей», рано или поздно ты сталкиваешься с тем, что база превращается в помойку. Сохранил юзера, но пост не создался. Деньги списались, но подписка не включилась. И дальше начинается весёлое расследование, кто накосячил — кодер, база или вселенная.
На самом деле виноват ты. Потому что забыл про транзакции.
Laravel, к счастью, даёт тебе инструмент под названием DB::transaction(). Это значит: "давай я заверну твои грязные запросы в пакет и либо всё сделаю, либо пошлю тебя нахрен". То есть либо база живёт в согласованном состоянии, либо ничего не меняется. И это единственный нормальный вариант, если не хочешь потом руками чинить разъехавшиеся данные.
Как это работает на самом деле
У Laravel есть метод:
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
Выглядит невинно. Но в реальности это твоя страховка: если на второй строке у тебя всё взорвётся, то первая тоже откатится.
А если не завернёшь в транзакцию? Получишь классическую картину: у юзеров уже votes = 1, а посты всё ещё на месте. И попробуй потом объяснить, почему бизнес-логика "голоса есть, постов нет" выглядит как глючный криптокошелёк 2017 года.
Когда надо ронять исключения
Иногда ты сам понимаешь, что всё идёт в задницу. Вот тогда нужно кидать исключение — Laravel честно откатит транзакцию.
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
throw new Exception('Пошло не так — и я не собираюсь это чинить руками');
});
И всё, база осталась чистой. Да, ты облажался, но хотя бы без мусора.
Контроллеры и транзакции
Самый популярная ситуация — регистрация юзера + создание связанного ресурса. Ты ведь не хочешь, чтобы у тебя в таблице валялись "пустые" аккаунты без постов, правда?
public function store(Request $request)
{
DB::transaction(function () use ($request) {
$user = User::create($request->all());
$post = new Post(['title' => 'Первый пост']);
$user->posts()->save($post);
});
return redirect()->route('users.index');
}
Тут всё просто: если не сохранился пост, то и пользователь не появится. И наоборот. Либо вместе, либо никак.
Где тебя подстерегает засада
Идея транзакций проста, но часто про неё забывают. Люди пишут сложные сервисы, где на каждом шаге полсотни запросов к базе, и только потом начинают удивляться: «А что это у нас подписка без оплаты активировалась?». Ответ — потому что ты забыл завернуть код в транзакцию.
Ещё хуже, когда кто-то решает сделать "частичный откат":
— вот здесь сохраним,
— а вот тут посмотрим,
— а если не получится, ну и ладно.
Так база и превращается в рассадник багов.
Финалочка
Транзакции — это не серебряная пуля. Это ремень безопасности. Он не спасёт, если ты поехал 200 км/ч навстречу фуре, но зато не даст вылететь из машины, когда просто врежешься в бордюр.
Laravel сделал их удобными: кинул код в DB::transaction(), и забыл про половину головной боли. Дальше всё зависит от того, хватит ли у тебя мозгов не писать кашу из запросов без этого костыля.
0 комментариев