Дата публикации: 09.09.2023 в 16:25

Валидация Laravel

0 комментария

Валидация обязательна для любого современного проекта, а в 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

ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай
ОбщайсяРазвивайсяУчисьРаботай