使用laravel中的表单请求改善代码

In this blogpost I’m going to show you how to merge validation of 4 different type of requests on the same model into one FormRequest class.

在本博文中,我将向您展示如何将对同一模型的4种不同类型的请求的验证合并到一个FormRequest类中。

When you start a project in Laravel, soon you most likely will be handling form validation. The framework provides different ways of dealing with validation, for example by adding the validation rules inside your controller:

当您在Laravel中开始一个项目时,很快您很可能会处理表单验证。 该框架提供了处理验证的不同方法,例如,通过在控制器内部添加验证规则:

namespace App\Http\Controllers;class
{
{
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'date' => 'sometimes|date',
]);
// The blog post is valid...
}
}

There is nothing wrong with this approach. You are sure that you only deal with validated data, and it is clear by looking at the controller what you can expect from the $ValidatedData object.

这种方法没有错。 您确定只处理经验证的数据,并且通过查看控制器可以清楚地看到$ValidatedData对象会带来什么。

But writing the validation logic in the controller will break The Single Responsibility Principle. Having many responsibilities in a single class makes it more difficult to handle new requirements over time. Even in this controller it could be possible that you need a method to update the blog post. You would probably copy the rules from the store method to the update method. And imagine that you need to add an API later on. You would create a separate controller handling the API request, while the validation rules stay the same.

但是在控制器中编写验证逻辑会破坏单一责任原则 。 在一堂课中承担许多责任,使得随着时间的推移处理新要求变得更加困难。 即使在此控制器中,也可能需要一种方法来更新博客文章。 您可能会将规则从store方法复制到update方法。 并设想您以后需要添加一个API。 您将创建一个单独的控制器来处理API请求,而验证规则保持不变。

表格要求救援 (Form Requests to the rescue)

Form requests in Laravel allows us to extract the validation logic to a dedicated class. Creating a form request is possible with a simple Artisan CLI command as shown in the Laravel documentation:

Laravel中的表单请求使我们能够将验证逻辑提取到专用类中。 可以使用一个简单的Artisan CLI命令创建表单请求,如Laravel文档中所示:

php artisan make:request StoreBlogPost

This however creates a StoreBlogPost form request, which as the name implies would be used for storing new blog posts. This would mean that we need to create another UpdateBlogPost form request, which would duplicate (most) of the validation rules. It would make more sense to have one form request which will validate both the store (create) and update (edit) requests.

但是,这将创建一个StoreBlogPost表单请求,顾名思义,该请求将用于存储新的博客文章。 这意味着我们需要创建另一个UpdateBlogPost表单请求,该请求将复制(大部分)验证规则。 拥有一个表单请求将同时验证存储(创建)和更新(编辑)请求会更有意义。

So the actual command we are going to use is:

因此,我们将使用的实际命令是:

php artisan make:request BlogPostRequest

This will create the BlogPostRequest class inside the app/Http/Requests folder. Notice that we append “Request” to the class name, which makes it more clear that we have a form request instead of a model for example.

这将在app / Http / Requests文件夹中创建BlogPostRequest类。 请注意,我们在类名后附加了“请求”,这使我们更清楚地知道有一个表单请求而不是一个模型。

Inside the controller we find two methods: authorize() and rules().With the authorize() method it is possible to handle authorisation for the store and update request. But this leaves us handling authorisation of other requests on the controller (such as the index or show request) inside the controller. Therefore authorisation is better handled within another dedicated class, for example with resource policies. When handling authorisation outside the form request, we can just remove the authorize() method inside the form request class.

在控制器内部,我们找到两个方法:authorize()和rules()。使用authorize()方法可以处理存储和更新请求的授权。 但这使我们可以处理控制器内控制器上其他请求的授权(例如索引或显示请求)。 因此,可以在另一个专用类中更好地处理授权,例如使用资源策略。 在表单请求之外处理授权时,我们只需删除表单请求类内的authorize()方法即可。

We can now copy the rules array from the controller to the form request, which results in this code:

现在,我们可以将规则数组从控制器复制到表单请求,这将导致以下代码:

class BlogPostRequest extends FormRequest
{
public function rules()
{
return [ 'title' => 'required|unique:posts|max:255',
'body' => 'required',
'date' => 'sometimes|date',
];
}
}

Now we need to tell our controller to use this form request. Luckily this is made very simple by just changing the type-hinted $request variable in the method signature:

现在我们需要告诉我们的控制器使用此表单请求。 幸运的是,只需更改方法签名中带有类型提示的$request变量,就可以非常简单地完成此操作:

namespace App\Http\Controllers;use App\Http\Requests\BlogPostRequest;class
{BlogPostRequest $request)
{ $validatedData = $request->validated();
// The new blog post is valid...
}
}

In our controller, we call the $request->validated() method which receives all validated input. We do not need to worry about validation in our controller anymore, and we are sure that we only receive input from fields that are defined inside the form request.

在我们的控制器中,我们调用$request->validated()方法,该方法接收所有已验证的输入。 我们不再需要担心在控制器中进行验证,并且我们确保仅从表单请求中定义的字段中接收输入。

规则数组 (Array of rules)

In our form request, all rules for each field are within a single | delimited string. But when we are going to use these rules for validation of the update request, we need to make a small modification. We now have a rule unique:posts which allows us to have a unique title for each blog post. But when we want to update for example the body of the blog post, the current rule on the title would produce an error. Changing the unique rule means we have to change the whole ruleset of the title field, which is rather pointless for the other rules on this field. Therefore it is better to use an array of rules:

在我们的表单请求中,每个字段的所有规则都在一个| 分隔字符串。 但是,当我们要使用这些规则来验证更新请求时,我们需要进行一些小的修改。 现在,我们有了一个unique:posts规则,该规则使我们可以为每个博客帖子使用唯一的标题。 但是,例如当我们要更新博客文章的正文时,标题上的当前规则将产生错误。 更改唯一规则意味着我们必须更改标题字段的整个规则集,对于该字段上的其他规则而言,这毫无意义。 因此,最好使用一系列规则:

public function rules()
{
return [ 'title' => ['required', 'unique:posts', 'max:255'],
'body' => ['required'],
'date' => ['sometimes', 'date'],
];
}

相同的表单请求更新请求 (The same Form Request for update requests)

Our form request is now able to validate input for storing new blog posts, but for updating the blog post a small modification is necessary. We need to tell the unique validation rule that the current title can be ignored while checking that the title is unique.

现在,我们的表单请求能够验证用于存储新博客帖子的输入,但是为了更新博客帖子,需要进行一些小的修改。 我们需要告诉唯一验证规则,在检查标题唯一时可以忽略当前标题。

First of all, we are going to change the 'unique:posts' rule and define the rule fluently like this: Rule::unique('posts'). This is fine for the store method, but for the update method, we need to add the ignore method like this: Rule::unique('posts')->ignore($this->post->id);

首先,我们将更改'unique:posts'规则,并像这样流畅地定义该规则: Rule::unique('posts') 。 这对于s​​tore方法来说很好,但是对于update方法,我们需要像这样添加ignore方法: Rule::unique('posts')->ignore($this->post->id);

By the way, the $this->post comes from the controller where $post is type hinted in the updated method signature:

顺便说一下, $this->post来自控制器,其中$post在更新的方法签名中被提示类型:

namespace App\Http\Controllers;use App\Http\Requests\BlogPostRequest;use App\Models\Post;class
{
// public function update(BlogPostRequest $request, Post $post)
{
$validatedData = $request->validated();
// The updated blog post is valid...
}
}

So we have two different kinds of unique rules for the title field: one for the store and another for the update action. We know that a store request is always done with a POST request method, while an update request uses a PUT or PATCH method. This allows us to adapt the rule depending on the current request method:

因此,对于标题字段,我们有两种不同的唯一规则:一种用于商店,另一种用于更新操作。 我们知道,存储请求始终使用POST请求方法完成,而更新请求使用PUT或PATCH方法。 这使我们可以根据当前的请求方法来调整规则:

namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class BlogPostRequest extends FormRequest
{
public function rules()
{ $rule_title_unique = Rule::unique('posts', 'name');
if ($this->method() !== 'POST') {
$rule_title_unique->ignore($this->post->id);
}

return [
'title' => ['required', $rule_title_unique, 'max:255'],
'body' => ['required'],
'date' => ['sometimes', 'date'],
];
}
}

To break it down, we assign the rule to a variable. If the current request method is not POST, we know it is an update request so we add->ignore($this->post-id) to the rule. The variable is then used inside the array with rules that will be used to validate the request.

为了分解它,我们将规则分配给一个变量。 如果当前请求方法不是POST,则我们知道它是一个更新请求,因此我们在规则中添加->ignore($this->post-id) 。 然后,将该变量与将用于验证请求的规则一起在数组内使用。

Preparing the input for validation

准备输入进行验证

Let’s say we have a date field in our form where users enter a date in the form of d/m/Y while the date field in our database is in the format Y-m-d. For this we can use the prepareForValidation method. It would also be possible to modify our data after validation, but as we will see later on it makes more sense to format the data before validation, preferably in the format used in our database.

假设我们在表单中有一个日期字段,其中用户以d/m/Y形式输入日期,而我们数据库中的日期字段则为Ymd 。 为此,我们可以使用prepareForValidation方法。 在验证之后也可以修改我们的数据,但是正如我们稍后将看到的,在验证之前格式化数据更有意义,最好采用数据库中使用的格式。

namespace App\Http\Requests;
use Carbon\Carbon;
class BlogPostRequest extends FormRequest
{
// protected function prepareForValidation()
{
$this->merge([
'date' => Carbon::createFromFormat(
'd/m/Y', $this->date
)->toDateString(),
]);
}
}

The Carbon toDateString() method in the example above gives us a date in the Y-m-d format.

上例中的Carbon toDateString()方法为我们提供了Ymd格式的日期。

With this our form request is complete. We now have a separate class with one rule set which we can use for handling both storing and updating blog posts via web requests.

这样,我们的表格要求就完成了。 现在,我们有了一个带有一个规则集的单独的类,该类可用于通过Web请求处理存储和更新博客文章。

API请求的相同表单请求 (The same Form Request for API requests)

When your project is going to add support for API requests next to web requests, you probably going to add a new controller for handling these API requests. Luckily we have abstracted our rules, so we can just reuse our form request class in our API controller:

当您的项目要在Web请求旁边添加对API请求的支持时,您可能会添加一个新的控制器来处理这些API请求。 幸运的是,我们已经抽象了规则,因此我们可以在API控制器中重用表单请求类:

namespace App\Http\Api\Controllers;use App\Http\Requests\BlogPostRequest;
use App\Models\Post;class
{
{ $validatedData = $request->validated();
// The new blog post is valid
} public function update(BlogPostRequest $request, Post $post)
{
$validatedData = $request->validated();
// The updated blog post is valid
}
}

This API controller is almost identical as the controller we had before, but the responses from the API controller will be most likely JSON formatted, while the web controller will answer the request with a view. So a separate controller for the API requests makes sense.

这个API控制器与我们以前使用的控制器几乎相同,但是来自API控制器的响应很可能将采用JSON格式,而Web控制器将使用视图来响应请求。 因此,针对API请求的单独控制器是有意义的。

We are reusing our BlogPostRequest, but in our API we expect the date field in requests always in the Y-m-d format, as it makes more sense for API requests to work with this format. This means we don’t need to modify the input from the API request as we do now in the prepareForValidation method of our form request. We could add another form request to handle the API requests without the prepareForValidation method, but then again we have just a copy of our validation rules, which isn’t very maintainable.

我们正在重用BlogPostRequest,但是在我们的API中,我们希望请求中的日期字段始终为Ymd格式,因为API请求更适合使用此格式。 这意味着我们不需要像现在在表单请求的prepareForValidation方法中所做的那样修改API请求的输入。 我们可以添加另一个表单请求来处理API请求,而无需prepareForValidation方法,但是同样,我们只有验证规则的副本,该副本不是很容易维护。

Most likely your API endpoints are prefixed with api/, so we can use this information to handle API requests inside our form request like this:

您的API端点很可能以api/为前缀,因此我们可以使用此信息来处理表单请求中的API请求,如下所示:

namespace App\Http\Requests;
use Carbon\Carbon;
class BlogPostRequest extends FormRequest
{
//
protected function prepareForValidation()
{ if (request()->is('api/*')) {
return;
}
$this->merge([
'date' => Carbon::createFromFormat(
'd/m/Y', $this->date
)->toDateString(),
]);
}
}

We now check for an API request inside the prepareForValidation method, and exit early when an API request is detected. This way the date field input will only be modified for web requests. This is also the reason why we change the input before handling the validation, we can now use the same rules to validate both the API and web requests.

现在,我们在prepareForValidation方法内检查API请求,并在检测到API请求时提早退出。 这样,日期字段输入将仅针对Web请求进行修改。 这也是我们在处理验证之前更改输入的原因,我们现在可以使用相同的规则来验证API和Web请求。

With these techniques we can use one form request class containing one set of rules which handles storing and updating blog posts, both coming from a web or an API request. When the rules should be changed, we only need to modify these rules once for all four request types.

通过这些技术,我们可以使用一个表单请求类,其中包含一组规则,这些规则用于处理来自Web或API请求的博客文章的存储和更新。 当应该更改规则时,我们只需为所有四种请求类型修改一次这些规则。

在控制器中编写更漂亮的代码 (Write even more beautiful code in your controllers)

Do you want to get even more out of form requests? As these are just classes, they are an excellent candidate to add more methods involving the request. You can find out more in this highly recommended blog post by Marcel Pociot:Laravel Form Requests - more than validation

您是否希望获得更多的表格要求? 由于这些只是类,因此它们是添加涉及该请求的更多方法的理想选择。 您可以在Marcel Pociot强烈推荐的博客文章中找到更多信息: Laravel表单请求-不仅仅是验证

If you’ve got some thoughts to share about this method or other code improvements, you can reach me on Twitter or leave a comment below.

如果您有关于此方法或其他代码改进的想法,可以在Twitter上与我联系或在下面发表评论。

翻译自: https://medium.com/@jasper.briers/improve-your-code-with-form-requests-in-laravel-d25cb739dd3c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值