Php 从其他类访问登录用户对象的最佳实践

Php 从其他类访问登录用户对象的最佳实践,php,mysql,session,pdo,Php,Mysql,Session,Pdo,这是一个关于从另一个类存储和访问对象的最佳实践的问题 我在PHP中使用一个简单的自制MVC范例,该类称为User,它有自己的方法和变量,基本上用作数据库抽象层 该类通过调用newUser($userID)实例化,该类从给定$userID的数据库检索数据,如果没有具有该ID的用户,则引发异常 每个页面都有自己的WebViewController类来管理页面的内容,在某些情况下,页面需要调用$loggedInUser相关函数,如WebViewController->displayUserFriend

这是一个关于从另一个类存储和访问对象的最佳实践的问题

我在PHP中使用一个简单的自制MVC范例,该类称为User,它有自己的方法和变量,基本上用作数据库抽象层

该类通过调用
new
User($userID)
实例化,该类从给定$userID的数据库检索数据,如果没有具有该ID的用户,则引发异常

每个页面都有自己的
WebViewController
类来管理页面的内容,在某些情况下,页面需要调用
$loggedInUser
相关函数,如
WebViewController->displayUserFriends()
,其外观可能如下:

<?php
class WebViewController extends WVCTemplate
{
    // Class vars and methods
    // ...

    public function displayUserFriends()
    {
        foreach($loggedInUser->getFriends() as $friend) {
            // Do something
            // ...
        }
    }
}
?>
class UserService
{
    private $authService;

    public function __construct(AuthService $authService)
    {
        $this->authService = $authService;
    }

    public function getUserFriends(User $user)
    {
        //...use DAO / models here...
    }

    public function getUserFriendsForCurrentUser()
    {
        $user = $this->authService->getCurrentUser();
        if (!$user)
            throw new DomainException('Must be logged in to do that.');

        return $this->getUserFriends($user);
    }
}


是否有一种(符合最佳实践的)方法将LoggedInUser存储为一种全局对象,这样就可以在任何类或
WebViewController
中访问它,而无需在所使用的每个类中实例化它?

我认为这是一个意见问题,真的。尽管如此,在这里我将提出两个可行的选择

选择1 …是创建将容纳当前用户的单例:

class SessionHolder
{
    private $user;

    public static function getCurrentUser()
    {
        return self::$user;
    }

    public static function setCurrentUser(User $user)
    {
        self::$user = $user;
    }
}
用法:

//Authentication does following:
SessionHolder::setCurrentUser($user);
//web view controller does following:
SessionHolder::getCurrentUser();
明显的优点是,在任何框架中都很容易将其合并。缺点是使用单例通常不是一个好主意,因为它们使测试代码变得非常困难(您无法测试它们)。跟踪您的依赖关系也很困难。要回答“我的控制器是否依赖于活动用户?”的问题,您需要手动搜索代码

我认为测试部分非常重要,因为你迟早会在工作中遇到TDD(如果你还没有),因此,知道如何处理它的特性是很好的

选择2 使用服务层。我认为这是组织你的项目最好的方法之一。例如,我经常在项目中使用以下结构:

  • 控制器-充当服务的代理,从
    \u GET
    \u POST
    等收集用户输入
  • 服务——例如,包含业务逻辑——哪些项目应该放入客户的订单中
  • 模型/DAO层-包含数据库逻辑,如如何获取数据
使用这样的体系结构,您可以按以下方式构造代码:

  • AuthService::getCurrentUser()
    -返回当前登录的用户
  • UserService::getUserFriends(User$User)
    -返回任意用户的好友
  • UserService::getUserFriendsForCurrentUser()
    -返回活动用户的好友
  • UserService::getUserFriendsForCurrentUser()
    使用
    AuthService::getCurrentUser()
  • UserService
    现在依赖于
    AuthService
  • 最后,您的控制器可以调用
    $this->userService->getUserFriendsForCurrentUser()
    来获取它们,并在将它们发送到视图层之前选择性地装饰它们
因此,您的代码可以如下所示:

<?php
class WebViewController extends WVCTemplate
{
    // Class vars and methods
    // ...

    public function displayUserFriends()
    {
        foreach($loggedInUser->getFriends() as $friend) {
            // Do something
            // ...
        }
    }
}
?>
class UserService
{
    private $authService;

    public function __construct(AuthService $authService)
    {
        $this->authService = $authService;
    }

    public function getUserFriends(User $user)
    {
        //...use DAO / models here...
    }

    public function getUserFriendsForCurrentUser()
    {
        $user = $this->authService->getCurrentUser();
        if (!$user)
            throw new DomainException('Must be logged in to do that.');

        return $this->getUserFriends($user);
    }
}
优点:您的代码是可测试的,而且代码看起来也很干净。只要浏览一下构造函数,依赖关系就很明显了。业务逻辑与控制器很好地分离,控制器就像代理一样

缺点:您需要生成更多的样板代码。它也不是真正的MVC。然而,既然你自己说你使用自制的MVC模式,我认为这种方法也值得一试


作为一个侧节点—正如您所看到的,我们使用构造函数将
AuthService
注入
UserService
。我认为值得一提的是:

  • 我们没有将singleton
    AuthService
    与静态方法一起使用,因为我们想测试我们的
    UserService
    类。为了测试它,我们可以使用mock,也可以不使用mock。在这种特殊情况下,我们希望模拟AuthService。这样做很简单,只需在测试中实现您的
    AuthService
    变体,并使用此存根创建
    UserService
    ,而不是真正的
    AuthService
    。如果您希望了解更多关于使代码可测试的良好实践,请参阅更多关于TDD的内容,因为我认为这不在本问题的范围之内

  • 重要的是要知道,您不应该每次需要使用
    UserService时都手动构造它的所有依赖项。这就是依赖注入的作用所在——您只需声明您的UserService需要什么,然后使用对象工厂检索其实例。(理想情况下,您只需要在整个项目中调用一次对象工厂,它将在运行时基础上构建整个依赖关系树)。它也在播放


    • 解决您的问题最优雅的方法就是使用。由于您可能需要多个控制器中的当前用户,因此最好创建一个
      AuthenticationService
      (或类似)来提供检查用户是否已登录以及获取当前登录用户的方法,并封装该公共功能。然后,您可以将服务实例注入所有需要它的控制器中

      有几个独立的PHP依赖项注入库:


      您看过吗?这很可能就是你要找的。但这只适用于实际拥有控制器实例的情况,因此所有控制器方法都必须是非静态的(例如displayUserFriends)。为什么它们是静态的?对不起。在类文件中,它们实际上不是静态的,我已经修复了原始问题中的代码以反映这一点。@FaKeller将当前登录的用户对象存储为superglobal
      $GLOBALS['CurrentUser']
      ?是的,我认为全局变量是一种不好的做法。他们是巴