元の記事
Introducing View Components in Laravel, an alternative to View Composers
はじめに
この投稿は↑の記事を和訳したものです。
本文
ソフトウェアの開発においての「ベストプラクティス」のひとつは、必要に応じてアプリケーション内の異なる場所で再利用可能なコードを書くことです。
Laravel上で運営されているサイトがあったとして、サイトのブログをハイライトをするウィジェットをサイドバーに表示する必要があると仮定しましょう。
APIのレスポンスを元にハイライトを生成する場合、ホームページのコントローラーは以下のように実装する事ができます。
<?php
class HomeController extends Controller {
protected $blog;
public function __construct(BlogRepository $blog)
{
$this->blog = $blog
}
public function index()
{
return view('blog', [
'posts' => $blog->latest(),
'highlights' => $blog->highlights()
]);
}
}
一見するとクリーンで問題ないコードに見えますが、以下のコンタクトページの例のように、同じ変数"highlights"をサイトの全てのページに渡さないといけないため大変です。
<?php
class ContactPageController extends Controller {
protected $blog;
public function __construct(BlogRepository $blog)
{
$this->blog = $blog
}
public function index()
{
return view('contact', [
'highlights' => $blog->highlights()
]);
}
}
Laraveのサイトに20のコントローラーがあった場合、同じコードが20箇所重複し、メンテナンスのコストがかさみます。
LaravelのView Composerを利用
View Composerを利用することで、ロジックをコントローラーの外に出す事ができ、特定のViewにのみデータを渡す事でコードを改善できます。
<?php
class HighLightsComposer
{
protected $users;
public function __construct(BlogRepository $blog)
{
$this->blog = $blog
}
public function compose(View $view)
{
$view->with('highlights', $this->blog->highlights());
}
}
サービスプロバイダーには以下のように設定します。
<?php
class ComposerServiceProvider extends ServiceProvider
{
public function boot()
{
View::composer(
'highlights', 'App\Http\ViewComposers\HighlighsComposer'
);
}
}
これに伴い、以下のようにコントローラーをリファクタリングする事が出来ます。
<?php
class HomeController extends Controller {
protected $blog;
public function __construct(BlogRepository $blog)
{
$this->blog = $blog
}
public function index()
{
return view('blog', [
'posts' => $blog->latest()
]);
}
}
class ContactPageController extends Controller {
public function index()
{
return view('contact');
}
}
View Composerについて考える
一見何の問題もないように見えますが、View Composer を利用する手法について少し考えてみましょう。
View Composerを利用する際のプロセスが少し魔法染みていて、特にLaravel初心者にとっては不明瞭な箇所が多々あります。
“highlights” ウィジェットのコンテントを静的なものに変更してくれという顧客の要望があった場合、↑の例では“highlights.blade.php” のコンテンツを更新するだけで対応する事ができます。.
アプリケーションは期待通りに動作しますが、APIの呼び出しはバックエンド(View Composer内)で実行される事になります。
Viewからロジックを削除しても関係がなく、Viewの名前を変更したり、サービスプロバイダーを更新してViewComposerからのAPIの呼び出しを止めたりしなければなりません。
View Componentを利用する
以下の手法は元の記事の筆者である Jeffが現在携わっているプロジェクトの1つで実践しているものです。
この手法の目的は、様々なリソースから取得されるダイナミックなデータを元に生成される共通のViewを再利用できるようにすることです。
Highlights コンポーネントクラスを作成
View Componentのクラスはインターフェースやコントラクトを共有する事でreturnされるデータのタイプを指定することもできます。このケースだと、Laravelのコントラクト、Htmlable を利用するのが良いでしょう。
<?php
namespace App\ViewComponents;
use Illuminate\Support\Facades\View;
use Illuminate\Contracts\Support\Htmlable;
class HighlightsComponent implements Htmlable
{
protected $blog;
public function __construct(BlogRepository $blog)
{
$this->blog = $blog;
}
public function toHtml()
{
return View::make('highlights')
->with('highlights', $this->blog->highlights())
->render();
}
}
View Componentをレンダリングするために、新しいブレードのディレクティブを作成します。上記のクラスで依存性の注入 (Dependency Injection)を利用しているので、制御の反転 (Inversion of Control)を利用し依存関係を解決するのが良いでしょう。
<?php
class AppServiceProvider extends ServiceProvider
{
public function register()
{
Blade::directive('render', function ($component) {
return "<?php echo (app($component))->toHtml(); ?>";
});
}
}
最後に、以下のようにしてHTMLのマークアップを返すView Componentをアプリケーション内のどのViewでもレンダリングする事ができます。
// home.blade.php
@render(\App\ViewComponents\HighlightsComponent::class)
まとめ
この手法を利用する事で、ダイナミックなデータを扱う複雑なコンポーネントをアプリケーション内のどのViewでも再利用する事ができます。
View Componentのロジックは、ブレードのディレクティブ @render()
を用いて呼ばれた時にのみ実行されます。
大きなチームの中で開発を行う場合、チームの誰かがViewのウィジェットのコードを変更してもバックエンドのコードを更新する必要がないので、スムーズなアプリケーションの開発を期待できます。