Laravel view models [翻譯]

Jarvis42發表於2019-01-20

檢視模型是簡化 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 協議》,轉載必須註明作者和本文連結

相關文章