Which pattern should I use for my unique instance of the User class?

前端 未结 6 824
醉梦人生
醉梦人生 2021-01-14 18:51

I have this User class

class User{
  private    $logged = false;
  private    $id;

  public function User() {
      //> Check if the user is logged in wi         


        
相关标签:
6条回答
  • 2021-01-14 19:28

    The influence of Misko Hevery is pretty strong on me. So is his newable - injectable distinction. A user is not an injectable but a newable. What are the responsibilities of a user: should he be able to tell of himself whether he is logged in or not? There's a post of him where he talks about a similar problem: a credit card and charging it(self?). It happens to be a post about singletons, what you would like to make it:

    http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/

    That would leave it to an service to check whether the user is logged in or not, what rights he has on the site.

    It also means your architecture would change, your problem will become different (passing around the user?, where is it needed?, how will you have access to the 'checking user is logged in' service, ...).

    0 讨论(0)
  • 2021-01-14 19:31
    1. since everyone else is weighing in on this, singletons are not evil. i even read the "liars" article, and he's using a contrived example of non-modular design and poor dependency inheritance.

    i think you should consider a singleton factory pattern, where a singleton factory (Auth) provides a login() method which returns a User class, as well as methods for saving state between HTTP requests on that User.

    This will have the benefits of separating the security and session functionality from the User functionality. Additionally using the factory, you can have multiple types of users without the rest of the system needing to understand which object to request before the db is examined

    class auth {
        private static $auth = null;
        private $user = null;
    
        // must use getAuth();
        private __construct(){};
    
        public getAuth() {
            if (is_null($this->auth) {
                 $this->auth = new auth();
            }
            return $this->auth;       
        }
    
        public function login($user,$pass) {
            ... // check db for user,
    
            if ($dbrow->user_type == 'admin') {
                $this->user = new admin_user($dbrow);
            } else {
                $this->user = new normal_user($dbrow);
            }
    
            $this->user->setSession($db->getsession());
        }
    
        public function getUser() {
            return $this->user;
        }
    
        public function saveSession() {
            // store $this->user session in db
        }
    
        public function saveUser() {
            // store $this->user changes in db
        }
        ...
    }
    

    the user class itself become a data structure, simply enforcing security and business rules, and maybe formatting some data for output purposes.

    class normal_user extends user {
        ... getters and setters
        public function getName() {}
        public function setEmail() {}
        public function setprofile() {}
    }
    

    all db, state and security concerns are centralized in the auth. the only way to create a user object (legally) is to run auth->login().

    you are still allowed to do

    $me = new normal_user();
    $me->setName();
    echo $me->getName();
    

    but there is no way for a new coder to save this in the db since it's not referenced in $auth->user;

    you can then create a function in auth to consume user objects to create new users (on signup)

    ...
    public function create(user $user) {
        // validate $user
        $this->user = $user;
        $this->saveUser();
    }
    ...
    

    you just need to make sure you run the save functions at the end of execution... possibly in a destructor()

    simple

    0 讨论(0)
  • 2021-01-14 19:33

    Patterns are supposed to be a helpful guide, like a library of previously successful software abstractions. Too often these days people view patterns as being some kind of religion where things are either "right" or "wrong" regardless of the context of the program.

    Think about what you want to achieve and map in out in a way that makes sense to you. Fuggering about with minute distinctions between this pattern and that pattern misses the point, and it won't get your program written. Learn by doing!

    HTH.

    0 讨论(0)
  • 2021-01-14 19:44

    Singletons are not evil. Bad usages of singletons are evil. The reason people have come to dislike this pattern so much (even going to the extent of calling it an anti-pattern, whatever that is), is due to improper use:

    Too many inexperienced people make a class a singleton when they find they don't need more than one instance of a class. But the question isn't if you need only a single instance of the class, but whether more than one instance would break your code. So ask yourself this question: would your code break if there were more User instances? If not, then maybe you shouldn't bother. :)

    There are legitimate uses of singletons. There are those people who fear this pattern like the plague and consider it always to be bad, without realizing that sometimes it can be very helpful. In the words of a much more experinced programmer than me, "singletons are like morphine: they can give you a real boost, but use them the wrong way and they an become a problem themselves". If you want me to go into some details as to when singletons could be a good choice, leave a comment to this answer. :)

    0 讨论(0)
  • 2021-01-14 19:45

    Not sure why all the arguing up top. Seems like a perfectly reasonable question to me.

    The key here is to use static members of the User class. Static methods are your friends, regardless of what some may say:

    class User
    {
       private    $logged = false;
       private    $id;
    
       private static $_currentUser;
       public static function currentUser()
       {
         if (empty(self::$_currentUser))
         {
             @session_start();
             if (array_key_exists('current_user', $_SESSION))
             {
                 self::$_currentUser = $_SESSION['current_user'];
             }
             else
             {
               // force login in or whatever else.
               // if you log in, make sure to call User::_setCurrentUser(); 
                 return null; //or some special 'empty' user.
             }
         }
         return self::$_currentUser;
      }
      // you may consider making this public, but it is private because it is a bit
      // more secure that way.  
      private static function _setCurrentUser(User $user)
      {
         self::$_currentUser = $user;
         $_SESSION['current_user'] = $user;
      }
    
      public function User() {
       //> Check if the user is logged in with a cookie-database and set $logged=true;
      }  
    
      public function isLogged() {}
      public function editPerms() {}
    
      //> other methods    
    }
    
    // Usage
    $pUser = User::currentUser();
    
    0 讨论(0)
  • 2021-01-14 19:54

    It is always hard to answer architectural questions without the context. In this case it is pretty important how the User objects are persisted (where do they come from?) and how is the client code organized. I will assume a MVC architecture because it's trendy this days. Also I suppose your user objects will have more responsibility as only authentication (you mention some permission control here, but it's still not clear enough).

    I would push the authentication responsibility to a service and just pass it around as needed. Here is some sample code.

    class AuthenticationService {
        /**
         * @var User
         */
        private $currentUser;
    
        public function __construct(Request $request) {
            // check if the request has an user identity
            // create a user object or do nothing otherwise
        }
    
        public function getCurrentUser() {
            return $this->currentUser;
        }
    }
    
    class User {
        public function editPerms(){}
    }
    
    // the client code
    class Controller {
        private $auth;
    
        public function __construct(AuthenticationService $auth) {
            $this->auth = $auth;
        }
    
        public function handleRequest() {
            $currentUser = $this->auth->getCurrentUser();
    
            if ($currentUser === null) { // of course you could use Null Object Pattern
                // no user is logged in
            }
    
            // do something with the user object
        }
    }
    

    So the answer to your question is: you need proper dependency injection through out your whole application. The only object you get from the server is a request. The dependency injection container injects it into the AuthenticationService and the latter gets injected into your controller. No singletons, no static methods, no global variables. The dependencies are tracked in the DI container and are injected as needed. Also the DI container makes sure your service is instantiated only once.

    The article "Container-Managed Application Design, Prelude: Where does the Container Belong?" may clarify some DI concepts.

    0 讨论(0)
提交回复
热议问题