【问题标题】:How to construct an object that depends on the session to be loaded如何构造一个依赖于要加载的会话的对象
【发布时间】:2019-12-31 10:49:09
【问题描述】:

假设我们有一个显示 20 部电影的随机列表的网站。但是,登录用户可以选择他们最喜欢的电影,因此将显示这些电影。此电影列表显示在主页和其他一些页面中。

为了遵循 DRY 原则,我们可以将这个逻辑封装在它自己的类中,然后将这个类注入到任何需要显示电影列表的地方。此类还将具有将在整个应用程序中使用的其他方法。例如,还有一种方法可以获取一个随机电影。

该类可能如下所示(请注意这是一个简化示例):

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();
    }

}

注意:请注意,这是一个示例,有些地方可以改进。

由于此类可以在同一个请求中多次使用,因此最好将其设置为 IoC 容器中的 singleton,以便实例化时运行的查询不会运行不止一次。

但是,现在我们遇到了一个问题。我们需要在控制器的私有方法中使用此类。我们可以直接调用应用容器,如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,因此将始终显示默认电影。

你会怎么做才能解决这个问题?

【问题讨论】:

  • 在实例化MovieService之前有没有试过在构造函数中调用中间件?
  • 相关: github.com/laravel/framework/issues/15072 - 当它被改变时,这是一个相当热门的话题。
  • @Script47 你能举个例子吗?
  • $this->movieService = [...] 之前尝试添加$this->middleware('<middleware-here>')
  • @Script47 我认为这不会做任何事情。会话中间件始终运行,因此没有必要像 $this->middleware() 那样调用它,因为依赖项仍在控制器中,并且仍会在中间件之前运行。

标签: php laravel oop ioc-container


【解决方案1】:

不将对象的构造函数用于逻辑,仅用于管理依赖项,这样更简洁。巧合的是,这也将通过将 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();
    }

}

【讨论】:

  • 是的,这会解决它,但它会产生一个新问题。每次调用其中一个方法时,都会运行 SQL 查询来检索电影。这就是我试图通过首先初始化受保护的类属性然后使该类成为单例来解决的问题。我完全同意你的观点,你的方式更干净。我想知道如何多次运行这些查询。
  • 您可以调整randomMovies(),以便它将检索到的结果存储在内存中(或任何其他形式的缓存)并在存在时返回。我已经用一个这样做的例子更新了代码 sn-p
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 2014-07-14
  • 2011-10-26
  • 1970-01-01
  • 2017-08-14
相关资源
最近更新 更多