3

I have a User class, this class handles things like:

  • login (UserAuthenticateModel)
  • Handle user sessions: user login tokens, fetching user from session if set in session, ect' (UserSessionsModel)
  • Fetch & Set user data to populate $this user object. (UserFetchModel)
  • Update & change my user data (to this user object - and in the database) (UserUpdateModel)

I'm asking out loud and please correct me if I'm wrong:

  1. Is every point a single responsibility of some sort of "something that handle only one thing" inside the "User" class and needs a different class?
  2. Does my User class need to be under "classes" folder, since it's just an object of "something", and my other classes like "UserSessions" and "UserAuthenticate" should be considered as a model?

I'll describe 2 examples with some code:

  1. Setting sessios: Insead of just pasting a whole bunch of code in my construct that "does something with sessions", I use a UserSessionsModel specifically with the method that does whatever it has to do - get data from session if exist. single responability - Setting/changing my user sessions, and populating my user object. and some of this session handling methods might be used elsewhere (example 2).

Code ex:

User class 
{

    public function __construct()
    {
        # look for a user in the session if set
        $this->getFromSession();
    }

    public function getFromSession() 
    {
        # Create UserSession obj
        if ( !$this->UserSessions instanceof UserSessions )
            $this->UserSessions = new \myapp\Models\User\UserSessionsModel();

        $this->UserSessions->getUserFromSession($this);
    }

}

class UserSessionsModel {

    public function getUserFromSession(User $User)
    {
        if (Session::exists(Config::$user)) {

            $session = unserialize(Session::get(Config::$user));

            $User->id             = $session->id; 
            $User->firstName      = $session->firstName; 
            $User->lastName       = $session->lastName; 
            $User->userName       = $session->userName; 
            $User->email          = $session->email; 
            $User->lastLogin      = $session->lastLogin; 
            $User->password       = $session->password; 
            $User->ip             = $session->ip; 
            $User->loginTimestamp = $session->loginTimestamp; 
            $User->isLoggedIn     = $session->isLoggedIn; 

            return $User;
        }
    }



 public function setUserToSession(User $User)
    {
        Session::set(Config::$user, serialize($User));
    }

}
  1. Logging in: This has 2 parts:
    • First to authenticate the user credentials
    • Set security sessions (token, loggedin flag, ect'.) - using the UserSessionModel from example 1 - since it handles the same "subject". So both are under the login method (one insice an other or separately - this example handles them the first way) and both are 2 different responsibilities - one is checking a user authentication, the second is handeling the sessions/tokens.

code ex:

User class {

    // after setting $this->username & $this->password to this object
    public function login()
    {
        if ( !$this->UserAuthenticator instanceof \myapp\Models\User\UserAuthenticatorModel )
            $this->UserAuthenticatorModel = new \myapp\Models\User\UserAuthenticatorModel();

        return $this->UserAuthenticatorModel->login($this);
    }
}


class UserAuthenticatorModel extends Model
{

    public function login(User $User)
    {
        try {

            # Connect to new database with $User->username & $User->password
            ... 

            # If status is connected 
            if ($newConnection) {

                # Check for user credentials data 
                $userData = $this->UserLoginValidation($User->userName, $User->password); 

                # If the result isn't a valid array - EXEPTION  
                if ( (!is_array($userData)) || (empty($userData)) )
                    throw new LoginException("Invalid username ({$User->userName}) or password ({$User->password})");

                if ($userData["require_password_change"] === 1) {
                    $this->set($User, $userData);
                    return true;
                }

                $this->set($User, $userData);
                # Set logged-in security sessions
                $User->UserSessions = new UserSessionsModel();
                $User->UserSessions->setSecuritySession($User);
                # Set User obj to session
                $User->UserSessions->UserToSession($User);

                # Update last_login for this user
                $this->updateLastLogin($User->id, $User->loginTimestamp);

                return true;

            } else {
                throw new LoginException('User does not exist');
                return false;
            }

        } catch (LoginException $e) {
            $e->log($e);
            return false;
        }
    }

Using PHP here btw.

1 Answer 1

2

I would split this up differently. NOTE: I'm not using any particular language below, the syntax is just used to highlight the organizational ideas.

Authentication should be it's own thing. I normally have authentication return authentication data, and use that authentication data to get the current user. The authentication token can be bound to future requests in lots of ways, but for the sake of this example, I'll have the authentication data passed in.

class AuthenticationRequest {
    async AuthData login(credentials: Credentials) {
    }

    logout(authData: AuthData) {
    }
}

Now that authentication is done, I'll use another request class for getting and setting the user.

class UserRequest {
    async User fetchCurrentUser(authData: AuthData) {
    }

    async User updateUser(user: User, authData: AuthData) {
    }
}

This turns Credentials, AuthData, and User into plain old data objects.

class Credentials {
    userName: String
    password: String
}

class AuthData {
    token: Data
    loginTimestamp: Date
    userId: String // Maybe not needed.
    ip: IPAddress
}

class User {
    id: String
    userName: String
    firstName: String
    lastName: String
    email: String
    lastLogin: Date
}

The basic flow is

credentials = Credentials()
credentials->userName = "Foo"
credentials->password = "Bar"

authenticationRequest = AuthenticationRequest()
authData = await authenticationRequest->login(credentials)
credentials = NULL // Release the credentials as soon a possible

userRequest = UserRequest()
user = await userRequest->fetchCurrentUser(authData)

user->firstName = "Bob"
user->lastName = "Roberts"

// NOTE: Assigning user to the result of updateUser() allows the
//       server to control the properties of the updated user.
user = await userRequest->updateUser(user, authData)

authenticationRequest->logout(authData)
authData = NULL
10
  • Which code syntax is this? Python? just out of curiosity. session = await authenticationRequest->login(credentials) happens only if authenticationRequest = AuthenticationRequest() returns true? (just to make sure i understand. saving invalid user credentials to the session doesn't make sence...) Having a credentials object setting to it username and pass at the begining is awesome. I sometimes do that too and get bad code reviews "why would you do that"? I'm guessing that the last code part is set in (for example) a login controller as a loginFormSubmit()?
    – Kar19
    Mar 19, 2019 at 14:25
  • Where you say you have a Session class, is that a Session class specifically for the UserSession handling, or a general class for Sessions? Because I do have a Session class, but with static methods of setting/getting/checkingIfAlreadySet sessions. Why is my code structure isn't ok?
    – Kar19
    Mar 19, 2019 at 14:25
  • From what i know, I need to have a class responsible for only one thing, so if it's setting sessions for the User, so it a UserSession class, and not to use a general class of Sessions and have all the logics there
    – Kar19
    Mar 19, 2019 at 14:31
  • @Kar19 The pseudo code I used was a mixture of Swift/C++/C#. Just a mishmash to show the objects and the relationships. Mar 19, 2019 at 17:50
  • @Kar19 The session I created was for managing the authentication token and knowing which user is logged in. If you are doing that in another class, you don't need Session. Mar 19, 2019 at 18:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.