Laravel: защита от повторного добавления данных при обновлении страницы
Доброго времени суток. В данной статье хочу поделиться одним методом, который позволяет нам защититься от повторного добавления данных при обновлении страницы. Данный способ защиты подходит не только для Laravel. Но мы будем рассматривать защиту от повторного добавления в контексте Laravel. Дальше мы рассмотрим основной принцип проверки, а затем напишем собственное правило валидации.
И так приступим. Первым делом подготовимся. Создадим таблицу users в базе данных со следующими полями:
- id
- first_name
- last_name
- created_at
- updated_at
Вот дамп:
CREATE TABLE IF NOT EXISTS `users` ( `id` int(11) NOT NULL, `first_name` varchar(255) NOT NULL, `last_name` varchar(255) NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `users` ADD PRIMARY KEY (`id`); ALTER TABLE `users` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
Теперь создадим модель User:
<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $table = 'users'; protected $fillable = [ 'first_name', 'last_name', ]; }
И не забываем прописать настройки для подключения к базе данных.
Теперь создаем контроллер, который будет содержат три метода: первый метод будет вызывать вьюшку с формой, второй будет принимать пост запросы и передавать данные в модель для записи в базу, а третий – будет получать из модели записи и передавать их для отображения во вьюшку.
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\User; class MainController extends Controller { public function index() { $list = User::all(); return view('index',['list' => $list]); } public function getForm() { return view('form'); } public function postForm(Request $request) { User::create(['first_name' => $request->firstName, 'last_name' => $request->lastName]); echo "Добавлено"; } }
Отлично, осталось добавить две вьюшки. Вьюшка для отображения данных: index.blade.php
<!DOCTYPE html> <html> <head> <title>Laravel</title> <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css"> <style> html, body { height: 100%; } body { margin: 0; padding: 0; width: 100%; display: table; font-weight: 100; font-family: 'Lato'; } .container { text-align: center; display: table-cell; vertical-align: middle; } .content { text-align: center; display: inline-block; } .title { font-size: 96px; } </style> </head> <body> <div class="container"> <div class="content"> @if($list) <table> <tr> <th>Имя</th> <th>Фамилия</th> </tr> @foreach($list as $l) <tr> <td>{{ $l->first_name }}</td> <td>{{ $l->last_name }}</td> </tr> @endforeach </table> @endif </div> </div> </body> </html>
И вьюшка для формы: form.blade.php
<!DOCTYPE html> <html> <head> <title>Laravel</title> <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css"> <style> html, body { height: 100%; } body { margin: 0; padding: 0; width: 100%; display: table; font-weight: 100; font-family: 'Lato'; } .container { text-align: center; display: table-cell; vertical-align: middle; } .content { text-align: center; display: inline-block; } .title { font-size: 96px; } </style> </head> <body> <div class="container"> <div class="content"> <form method="POST" action="{{ route('add') }}"> <input name="_token" type="hidden" value="{{ csrf_token() }}"> <label>Имя:</label> <input type="text" name="firstName"><br /><br /> <label>Фамилия:</label> <input type="text" name="lastName"><br /><br /> <input type="submit" value="Сохранить"> </form> </div> </div> </body> </html>
Теперь, чтобы все это заработало необходимо добавить роуты:
Route::get('/', ['as' =>; 'home', 'uses' => 'MainController@index']); Route::get('add', ['as' => 'form', 'uses' => 'MainController@getForm']); Route::post('add', ['as' => 'add', 'uses' => 'MainController@postForm']);
Проверяем. Адрес моего хоста http://lesson.loc/
Данных нет.
Переходим на форму для добавления http://lesson.loc/add
Добавляем данные.
И смотрим, что получилось http://lesson.loc/
Работает.
Давайте посмотрим в чем заключается проблема. Переходим на форму добавления и вводим, например, Имя: имя-2, Фамилия: фам-2 и жмем «Сохранить» появилась страница.
Теперь обновим страницу (нажмем F5). Подтверждаем отправку. И переходим к списку:
И что видим. У нас данные добавились два раза.
Один из самых простых способов – это добавить в форму метку (time()).
Открываем вьюшку с формой.
И добавляем:
<input name="_check" type="hidden" value="{{ time() }}">
А в методе, который обрабатывает пост запросы от формы, добавляем обработку. Вот как наш метод:
public function postForm(Request $request) { if(!$request->session()->has('check')){ $request->session()->set('check',$request->_check); $request->session()->save(); $check = true; } else { if($request->_check != $request->session()->get('check')){ $request->session()->set('check',$request->_check); $request->session()->save(); $check = true; } else { $check = false; } } if(!$check){ return redirect()->route('home'); } User::create(['first_name' => $request->firstName, 'last_name' => $request->lastName]); echo "Добавлено"; }
Решения проблем с сессиями в Laravel смотри у меня в статье «Фишки Laravel: не сохраняются сессии»
Проверяем и видим, что нас перекидывает на главную страницу, когда мы обновляем страницу (второй раз данные не добавляются)
Отлично. Все работает. Но мы же используем Laravel. И наша проверка выглядит как-то не «кашерно». Давайте создадим новое правило валидации.
Для этого создадим файл CustomValidator.php в app следующего содержания:
<?php namespace App; use Illuminate\Validation\Validator; use Session; class CustomValidator extends Validator { public function validateReinclusion($attribute, $value, $parameters) { if(!session($attribute)){ session([$attribute => $value]); return true; } else { if($value != session($attribute)){ session([$attribute => $value]); return true; } else { return false; } } } }
Где:
- $attribute – название поля
- $value – значение
- $parameters – параметры, которые передаются
Теперь зарегистрируем новое правило. Для этого открываем AppServiceProvider.php, который находится в app/Providers и вносим в метод boot код
Validator::resolver(function($translator, $data, $rules, $messages) { return new CustomValidator($translator, $data, $rules, $messages); });
Вот код файла AppServiceProvider.php:
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Validator; use App\CustomValidator; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { Validator::resolver(function($translator, $data, $rules, $messages) { return new CustomValidator($translator, $data, $rules, $messages); }); } /** * Register any application services. * * @return void */ public function register() { // } }
Ну вот и все. Мы создали собственное правило валидации и зарегистрировали его. Проверим.
Переходим в наш контроллер MainController.php и изменим его (добавим новое правило)
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; use App\User; class MainController extends Controller { public function index() { $list = User::all(); return view('index',['list' => $list]); } public function getForm() { return view('form'); } public function postForm(Request $request) { $valid = Validator::make($request->all(),[ '_check' => 'reinclusion', ]); if($valid->fails()){ return redirect()->route('home'); } User::create(['first_name' => $request->firstName, 'last_name' => $request->lastName]); echo "Добавлено"; } }
Проверяем. Работает.
P.S. Мы с вами сделали проверку на повторное добавление данных при обновлении страницы. Затем создали собственное правило валидации в Laravel, зарегистрировали его.