Which pattern should I use for my unique instance

2019-02-18 17:40发布

I have this User class

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

  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    
}

Well now considering I can't have more than 1 user logged (of course because we are talking for a single http request) in Where should i store the ref of my istance?

This is the case where singleton would be useful but these days everyone say singleton is evil (like static methods).

I could do a $GLOBALS['currentUser'] = new User(); and having it accesible everywhere but I think this is worse than a singleton.

So what Can I do?
Please note I don't need to save this instance between requests. I just need a way to access this instance in my framework within the same request.

If you want to know what i do now for all of my Helper Objects is a Service Container (that's considered as well bad):

function app($class) {      //> Sample
    static $refs = array();

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage app('User')->methods();

(IE what symfony does)

6条回答
霸刀☆藐视天下
2楼-- · 2019-02-18 18:10

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();
查看更多
祖国的老花朵
3楼-- · 2019-02-18 18:12

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. :)

查看更多
甜甜的少女心
4楼-- · 2019-02-18 18:22

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.

查看更多
唯我独甜
5楼-- · 2019-02-18 18:25
  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

查看更多
Viruses.
6楼-- · 2019-02-18 18:27

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.

查看更多
ゆ 、 Hurt°
7楼-- · 2019-02-18 18: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, ...).

查看更多
登录 后发表回答