Laravel view models [翻译]

视图模型是简化 controllermodel 代码的抽象。视图模型负责向视图提供数据,否则这些数据将直接来自 controllermodel。它们可以更好地分离关注点,并为开发人员提供更多灵活性。

从本质上讲,视图模型是一些简单的类,可以获取一些数据,并将其转换为可用于视图的内容。在这篇文章中,我将向您展示该模式的基本原理,我们将看看它们如何集成到 Laravel 项目中,最后我将向您展示在 Spatie 公司,项目中使用该模式。

让我们开始吧。假设您有一个表单来创建一个带有 category [类别] 的博客文章。您需要一种方法来填充视图中的选择框以及 category 选项。controller 必须提供这些。

public function create()
{
    return view('blog.form', [
        'categories' => Category::all(),
    ]);
}

以上示例适用于 create 方法,但不要忘记我们也应该能够编辑现有帖子。

public function edit(Post $post)
{
    return view('blog.form', [
        'post' => $post,
        'categories' => Category::all(),
    ]);
}

接下来是一项新的业务要求:应限制用户允许发布的类别。换句话说:应根据用户限制类别选择。

return view('blog.form', [
    'categories' => Category::allowedForUser(
        current_user()
    )->get(),
]);

这种方法不能扩展。您必须在 createedit 方法中更改代码。你能想象当你需要在帖子中添加标签时会发生什么吗?或者,如果有另一个特殊的管理表单来创建和编辑帖子?

下一个解决方案是让 post 模型本身提供类别,如下所示:

class Post extends Model
{
    public static function allowedCategories(): Collection
    {
        return Category::query()
            ->allowedForUser(current_user())
            ->get();
    }
}

虽然这种情况经常发生在 Laravel 项目中,但有很多原因可以解释为什么这是一个坏主意。让我们关注我们案例中相关的问题:它仍然允许重复。

说有一个新的模型 News 也需要相同的类别选择。这会导致重复,但在模型级别而不是在控制器中。

另一种选择是将方法放在 User 模型上。这最有意义,但也使维护更难。想象一下,我们正在使用前面提到的标签。他们不依赖于用户。现在我们需要从用户模型中获取类别,并从其他地方获取标签。

我希望使用模型很清楚,做为视图的数据提供者这并不是银弹。

总之,无论您何时尝试从中获取类别,总会出现一些代码重复。这使得维护和推理代码变得更加困难。

这是视图模型发挥作用的地方。它们封装了所有这些逻辑,以便可以在不同的地方重用它们。他们只能一对一:为视图提供正确的数据。

class PostFormViewModel
{
   public function __construct(
       User $user,
       Post $post = null
   ) {
       $this->user = $user;
       $this->post = $post;
   }

   public function post(): Post
   {
       return $this->post ?? new Post();
   }

   public function categories(): Collection
   {
       return Category::allowedForUser($this->user)->get();
   }
}

让我们说出这样一个类的几个关键特征:

  • 注入所有依赖项,这为外部提供了最大的灵活性。
  • 视图模型公开了视图可以使用的一些方法。
  • post 方法会提供新帖子或现有帖子,具体取决于您是创建还是编辑帖子。

这就是控制器的样子:

class PostsController
{
    public function create()
    {
        $viewModel = new PostFormViewModel(
            current_user()
        );

        return view('blog.form', compact('viewModel'));
    }

    public function edit(Post $post)
    {
        $viewModel = new PostFormViewModel(
            current_user(),
            $post
        );

        return view('blog.form', compact('viewModel'));
    }
}

最后视图可以像这样使用它:

<input value="{{ $viewModel->post()->title }}" />
<input value="{{ $viewModel->post()->body }}" />

<select>
    @foreach ($viewModel->categories() as $category)
        <option value="{{ $category->id }}">
            {{ $category->name }}
        </option>
    @endforeach
</select>

这些是使用视图模型的两个好处:

  • 它们封装了逻辑
  • 它们可以在多种环境中重用

在 Laravel 中查看模型

前面的示例显示了一个带有一些方法的简单类。这足以使用该模式,但在 Laravel 项目中,我们可以添加一些细节。

例如,如果视图模型实现 Arrayable,则可以将视图模型直接传递给 view 函数。

public function create()
{
    $viewModel = new PostFormViewModel(
        current_user()
    );

    return view('blog.form', $viewModel);
}

该视图现在可以直接使用视图模型的属性,如 $post$categories。上一个示例现在看起来像这样:

<input value="{{ $post->title }}" />
<input value="{{ $post->body }}" />

<select>
    @foreach ($categories as $category)
        <option value="{{ $categroy->id}}">
            {{ $category->name }}
        </option>
    @endforeach
</select>

您还可以通过实现 Responsable 将视图模型本身作为 JSON 数据返回。当您通过 AJAX 调用保存表单并希望在调用完成后使用最新数据重新填充表单时,这非常有用。

public function update(Request $request, Post $post)
{
    // Update the post...

    return new PostFormViewModel(
        current_user(),
        $post
    );
}

您可能会在视图模型和 Laravel 资源之间看到相似之处。请记住,当视图模型可以提供他们想要的任何数据时,资源会在模型上一对一映射。

在我们的一个项目中,我们实际上在视图模型中使用资源!

class PostViewModel
{
    // ...

    public function values(): array
    {
        return PostResource::make(
            $this->post ?? new Post()
        )->resolve();
    }
}

最后,在这个项目中,我们正在使用需要 JSON 数据的 Vue 表单组件。在调用魔术 getter 时,我们创建了一个抽象来提供这个 JSON 数据而不是对象或数组:

abstract class ViewModel
{
    // ...

    public function __get($name): ?string
    {
        $name = Str::camel($name);

        // Some validation...

        $values = $this->{$name}();

        if (! is_string($values)) {
            return json_encode($values);
        }

        return $values;
    }
}

我们可以调用它们的属性并获取 JSON,而不是调用视图模型方法。

<select-field
    label="{{ __('Post category') }}"
    name="post_category_id"
    :options="{{ $postViewModel->post_catrgories}}"
></select-field>

等等,view composers 怎么样?

我听到了!关于该主题,有一篇完整的博客文章。你可以在这里 阅读它

总之,视图模型可以是在控制器中处理数据的可行替代方案。它们允许更好的可重用性并封装不属于控制器的逻辑。

使用它们时,您也不仅仅限于表格。在 Spatie,我们还使用它们来填充 facet 过滤器选项,基于用户当前正在使用的复杂上下文。

我建议尝试这种模式。顺便说一句,你不需要任何东西。上面列出的所有 Laravel 花招都是可选的,可以根据你的使用情况添加。

如果你想使用 Laravel 花招,我们有一个扩展包:spatie/laravel-view-models :grinning:。

原文 https://stitcher.io/blog/laravel-view-mode...

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!