如何构造依赖于要加载的session的对象
How to construct an object that depends on the session to be loaded
假设我们有一个显示随机列表的 20 部电影的网站。但是,登录用户可以 select 他们最喜欢的电影,因此将显示这些电影。此电影列表显示在主页和其他一些页面中。
为了遵循 DRY 原则,我们可以将这个逻辑封装在它自己的 class 中,然后在需要显示电影列表的地方注入这个 class。此 class 还将具有将在整个应用程序中使用的其他方法。例如,还有一种随机获取电影的方法。
class 可能如下所示(请注意这是一个简化的示例):
class MovieService
{
/** @var Collection $movies */
protected $movies;
public function __construct()
{
$this->movies = Auth::check() ? Auth::user()->favoriteMovies : $this->randomMovies();
}
public function getRandomMovies(): Collection
{
return $this->movies->random(20);
}
public function getOneRandom(): Movie {
return $this->movies->random();
}
protected function randomMovies() {
return Movie::inRandomOrder()->take(20)->get();
}
}
注意:请注意,这是一个例子,有些地方可以改进。
由于这个 class 可以在同一个请求中多次使用,因此最好在 IoC 容器中将其设为 singleton,这样实例化时 运行 的查询不会 运行 多次。
但是,现在我们遇到了一个问题。我们需要在控制器的私有方法中使用此 class。我们可以像 app()
或 App::make()
那样直接调用应用程序容器,但我们希望避免具有自定义依赖项的外观和全局助手。
class HomeController extends Controller
{
/** @var MovieService $movieService */
protected $movieService;
public function __construct(MovieService $movieService)
{
$this->movieService = $movieService;
}
public function index()
{
$movies = $this->getMovies();
return view('home', compact('movies'));
}
protected function getMovies()
{
// Let's imagine there's some extra logic here so that we would actually need this method.
return $this->movieService->getRandomMovies();
}
}
我们发现了一个问题。 控制器的构造函数在中间件管道之前运行,这意味着没有会话,因此没有用户标识。现在 MovieService
中的 Auth::check()
始终返回 false
,因此将始终显示默认电影。
你会怎样解决这个问题?
不将对象的构造函数用于逻辑,仅用于管理依赖关系更清晰。巧合的是,这也将通过将 Auth::check()
逻辑移至 getter 方法来解决您遇到的问题。除此之外,您还可以考虑注入 AuthManager
而不是依赖 Auth
外观,但这只是旁注。
class MovieService
{
/** @var AuthManager $auth */
protected $auth;
protected $movies;
public function __construct(Illuminate\Auth\AuthManager $auth)
{
$this->auth = $auth;
}
public function getRandomMovies(): Collection
{
return $this->getMoviesForCurrentUser()->random(20);
}
public function getOneRandom(): Movie {
return $this->getMoviesForCurrentUser()->random();
}
protected function randomMovies() {
if ($this->movies === null) {
$this->movies = Movie::inRandomOrder()->take(20)->get();
}
return $this->movies;
}
protected function getMoviesForCurrentUser() {
if ($this->auth->check()) {
return $this->auth->user->favoriteMovies;
}
return $this->randomMovies();
}
}
假设我们有一个显示随机列表的 20 部电影的网站。但是,登录用户可以 select 他们最喜欢的电影,因此将显示这些电影。此电影列表显示在主页和其他一些页面中。
为了遵循 DRY 原则,我们可以将这个逻辑封装在它自己的 class 中,然后在需要显示电影列表的地方注入这个 class。此 class 还将具有将在整个应用程序中使用的其他方法。例如,还有一种随机获取电影的方法。
class 可能如下所示(请注意这是一个简化的示例):
class MovieService
{
/** @var Collection $movies */
protected $movies;
public function __construct()
{
$this->movies = Auth::check() ? Auth::user()->favoriteMovies : $this->randomMovies();
}
public function getRandomMovies(): Collection
{
return $this->movies->random(20);
}
public function getOneRandom(): Movie {
return $this->movies->random();
}
protected function randomMovies() {
return Movie::inRandomOrder()->take(20)->get();
}
}
注意:请注意,这是一个例子,有些地方可以改进。
由于这个 class 可以在同一个请求中多次使用,因此最好在 IoC 容器中将其设为 singleton,这样实例化时 运行 的查询不会 运行 多次。
但是,现在我们遇到了一个问题。我们需要在控制器的私有方法中使用此 class。我们可以像 app()
或 App::make()
那样直接调用应用程序容器,但我们希望避免具有自定义依赖项的外观和全局助手。
class HomeController extends Controller
{
/** @var MovieService $movieService */
protected $movieService;
public function __construct(MovieService $movieService)
{
$this->movieService = $movieService;
}
public function index()
{
$movies = $this->getMovies();
return view('home', compact('movies'));
}
protected function getMovies()
{
// Let's imagine there's some extra logic here so that we would actually need this method.
return $this->movieService->getRandomMovies();
}
}
我们发现了一个问题。 控制器的构造函数在中间件管道之前运行,这意味着没有会话,因此没有用户标识。现在 MovieService
中的 Auth::check()
始终返回 false
,因此将始终显示默认电影。
你会怎样解决这个问题?
不将对象的构造函数用于逻辑,仅用于管理依赖关系更清晰。巧合的是,这也将通过将 Auth::check()
逻辑移至 getter 方法来解决您遇到的问题。除此之外,您还可以考虑注入 AuthManager
而不是依赖 Auth
外观,但这只是旁注。
class MovieService
{
/** @var AuthManager $auth */
protected $auth;
protected $movies;
public function __construct(Illuminate\Auth\AuthManager $auth)
{
$this->auth = $auth;
}
public function getRandomMovies(): Collection
{
return $this->getMoviesForCurrentUser()->random(20);
}
public function getOneRandom(): Movie {
return $this->getMoviesForCurrentUser()->random();
}
protected function randomMovies() {
if ($this->movies === null) {
$this->movies = Movie::inRandomOrder()->take(20)->get();
}
return $this->movies;
}
protected function getMoviesForCurrentUser() {
if ($this->auth->check()) {
return $this->auth->user->favoriteMovies;
}
return $this->randomMovies();
}
}