前提
Laravel ではルーティングパラメータから Model を bind させることができる。
RouteServiceProvider
public function boot()
{
parent::boot();
// ルーティングで {user} を指定すると下記コールバックの $userName に渡され
// その戻り値を Controller の引数に渡すことができる
Route::bind('user', function ($userName) {
return resolve(MyApp\Domain\Repositories\UserRepository::class)->findByName($userName) ?? abort(404);
});
}
詳しくは 公式ドキュメント(Routing # Explicit Binding) の "Customizing The Resolution Logic" を参照。
しかし、 bind のためのロジックは ServiceProvier の関心事ではなく、そのコードがここに出てくるのはあまりよろしくない。
Route::bind()
の中身を見てみると
実は Route::bind()
の第2引数には callback だけでなく文字列を渡すことができる。
過程は省略するがRoute::bind()
の第2引数に文字列を渡した場合、以下のメソッドの引数 $binding
となる。
Illuminate\Routing\RouteBinding::createClassBindig
protected static function createClassBinding($container, $binding)
{
return function ($value, $route) use ($container, $binding) {
// If the binding has an @ sign, we will assume it's being used to delimit
// the class name from the bind method name. This allows for bindings
// to run multiple bind methods in a single class for convenience.
list($class, $method) = Str::parseCallback($binding, 'bind');
$callable = [$container->make($class), $method];
return call_user_func($callable, $value, $route);
};
}
bind()
というメソッドを持ったクラス名、あるいはソース中のコメントにある通りクラス名@メソッド名
を指定すれば、bind のためのロジックを指定することができる。
bind のためのロジックを別のクラスに切り出す
上記より、次のようなクラスを作成すれば、
MyApp\Application\Routing\Binders\User
namespace MyApp\Application\Routing\Binders;
use MyApp\Domain\Repositories\UserRepository;
class User
{
protected $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public function bind($userName)
{
return $this->repository->findByName($userName) ?? abort(404);
}
}
RouteServiceProvider 側では次のようにして使うことができる。
RouteServiceProvider
public function boot()
{
parent::boot();
Route::bind('user', MyApp\Application\Routing\Binders\User::class);
}
これで、ServiceProvider の関心事の外である「bind のためのロジック」をカプセル化することができる。