Валидация обязательна для любого современного проекта, а в Laravel начать очень просто. В методах вашего контроллера вы можете вызвать метод валидации, передать запрос и массив правил, которые вы хотите проверить.
Является ли этот подход правильным? А может неправильно делать это таким образом? Конечно, нет, а любой, кто говорит вам иначе - просто глупец. В этом подходе нет ничего плохого, он работает и его можно протестировать. Важно помнить, что, хотя его можно улучшить, он может и остаться таким.
В этом уроке я расскажу вам о своем путешествии по валидации в Laravel, о том, какие изменения я сделал и почему. Начнем с самого начала.
Когда я начал работать с Laravel, я делал то, что мне говорила документация, просто и ясно. Я бы расширил app/Http/Controller
и вызвал $this->validate
на этом этапе. Мои контролеры были объемными. Мой типичный метод сохранения (store) будет выглядеть примерно так, как показано ниже, модернизированный до современного синтаксиса:
namespace App\Http\Controllers\Api;
class PostController extends Controller
{
public function store(Request $request): JsonResponse
{
$this->validate($request, [
'title' => 'required|string|min:2|max:255',
'content' => 'required|string',
'category_id' => 'required|exists:categories,id',
]);
$post = Post::query()->create(
attributes: [
...$request->validated(),
'user_id' => auth()->id(),
],
);
return new JsonResponse(
data: new PostResource(
resource: $post,
),
status: Http::CREATED->value,
);
}
}
Помимо логики создания, в том, как работает эта валидация, нет ничего плохого. Я могу протестировать его и управлять им, и я знаю, что он провалидирует мои данные, как мне нужно. Так что, если ваша валидация выглядит подобным образом - все в порядке!
Затем я перешел к вызываемым (invokable) контроллерам, так как предпочитал, чтобы все было проще — на данный момент все выглядело так же, только с методом вызова (invoke) вместо метода сохранения (store).
namespace App\Http\Controllers\Api\Posts;
class StoreController extends Controller
{
public function __invoke(Request $request): JsonResponse
{
$this->validate($request, [
'title' => 'required|string|min:2|max:255',
'content' => 'required|string',
'category_id' => 'required|exists:categories,id',
]);
$post = Post::query()->create(
attributes: [
...$request->validated(),
'user_id' => auth()->id(),
],
);
return new JsonResponse(
data: new PostResource(
resource: $post,
),
status: Http::CREATED->value,
);
}
}
После этого я обнаружил, насколько полезными были Form Requests, и как мне помогла инкапсуляция моей проверки в эти классы. Оттуда мой контроллер снова изменился. На этот раз это выглядело так:
namespace App\Http\Controllers\Api\Posts;
class StoreController
{
public function __invoke(StoreRequest $request): JsonResponse
{
$post = Post::query()->create(
attributes: [
...$request->validated(),
'user_id' => auth()->id(),
],
);
return new JsonResponse(
data: new PostResource(
resource: $post,
),
status: Http::CREATED->value,
);
}
}
Мне больше не нужно было расширять базовый контроллер, так как мне не нужен был метод проверки. Я мог бы легко внедрить From Request в метод вызова моего контроллера, и все данные были бы предварительно провалидированны. Это сделало мои контроллеры очень маленькими и легкими, так как я передал проверку отдельному классу. Мой From Request будет выглядеть примерно так:
namespace App\Http\Requests\Api\Posts;
class StoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'min:2', 'max:255',]
'content' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
];
}
}
Какое-то время я придерживался этой проверки стиля, так как в этом нет ничего плохого. Если ваша валидация выглядит подобным образом - все в порядке! Опять же, это масштабируемо, тестируемо и может использоваться в других местах. Вы можете внедрить это везде, где вы используете HTTP-запросы и вам нужна валидация.
Можем ли мы пойти еще дальше? Можем ли мы еще улучшить такой подход? Это вопрос, который я задал себе и застрял в течение довольно долгого времени. Позвольте мне объяснить сценарий, который заставил меня задаться вопросом, как к этому можно было подойти.
Представьте, что у вас есть проект, который позволяет создавать сообщения через API, веб-интерфейс и, возможно, командную строку. API и веб-интерфейс могут совместно использовать запрос формы, так как оба могут быть внедрены в контроллер. Как насчет командной строки? Нужно ли нам повторять проверку для этого? Некоторые могут возразить, что вам не нужно проверять командную строку в той же степени, но вы захотите добавить некоторую проверку.
Я некоторое время обдумывал идею валидаторов. В этом нет ничего нового, поэтому я понятия не имею, почему потребовалось так много времени, чтобы понять это! Валидаторы, по крайней мере для меня, были классами, содержащими правила и информацию, необходимые для проверки любого запроса — HTTP или иного. Позвольте мне показать вам, как это может выглядеть:
namespace App\Validators\Posts;
class StoreValidator implements ValidatorContract
{
public function rules(): array
{
return [
'title' => ['required', 'string', 'min:2', 'max:255',]
'content' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
];
}
}
Все начинается просто, просто место, где я хотел централизовать хранилище этих правил проверки. Оттуда я мог расширить его по мере необходимости.
namespace App\Validators\Posts;
class StoreValidator implements ValidatorContract
{
public function rules(): array
{
return [
'title' => ['required', 'string', 'min:2', 'max:255',]
'content' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
];
}
public function messages(): array
{
return [
'category_id.exists' => 'This category does not exist, you Doughnut',
];
}
}
Я мог бы добавить такие вещи, как сообщения, когда мне нужно было настроить сообщения проверки. Я мог бы добавить больше методов, чтобы инкапсулировать больше логики проверки. Но как это выглядит на практике? Вернемся к примеру с Store Controller. Наш контроллер будет выглядеть так же, как мы уже убрали валидацию, поэтому давайте вместо этого посмотрим на from Request:
namespace App\Http\Requests\Api\Posts;
class StoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return (new StoreValidator())->rules();
}
}
Вот так просто, я могу переключить массив, застрявший в классе, и заменить его вызовом класса, специфичным для того, как мы хотим сохранить и валидировать эти данные.
Я видел другой подход, который я считаю хорошим и плохим. Позвольте мне рассказать вам об этом. Я видел, как некоторые люди хранят свои правила проверки в своих моделях Eloquent. Теперь я не уверен на 100% в этом, так как кажется, что мы немного смешиваем цели - однако, это также гениально. Поскольку то, что вы хотите сделать, это сохранить правила создания этой модели внутри самой модели. Он знает свои правила. Это будет выглядеть примерно так:
namespace App\Models;
class Post extends Model
{
public static array $rules = [
'title' => ['required', 'string', 'min:2', 'max:255',]
'content' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
];
// The rest of your model here.
}
Это может быть легко использовано в запросе формы и останется с вашей моделью, поэтому вы можете управлять им из одной центральной точки в классе, который заботится об этом.
namespace App\Http\Requests\Api\Posts;
class StoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return Post::$rules;
}
}
Вот несколько способов проверки данных. Все верно, и все можно проверить. Какой способ вы предпочитаете для валидации? У вас есть способ, не упомянутый здесь или в документах? Дайте нам знать в Твиттере!
Оригинал статьи: https://laravel-news.com/validation