Question

I have a question regarding the best place to store fairly complicated application logic.

Say I want to allow a user to log into the site. The process of logging in should entail the following steps:

  1. From the form field, hash the user's email
  2. Look up the user's email hash in the auth table to make sure a user exists (auth table stores only encrypted email, email hash, user_id, and password hash)
  3. If user is found, then validate their password
  4. Regenerate the session id
  5. Store the new session in the database

Using the data mapper pattern, I have the following three models which are involved in this process

/User/
 - User.php
 - UserMapper.php

/Auth/
 - Auth.php
 - AuthMapper.php

/Session/
 - Session.php
 - SessionMapper.php

Thus a function which logs the user in would look something like this:

function login($email, $password)
{
    $security = new \Lib\Security;
    $authMapper = new \Models\Auth\AuthMapper($this->db);
    $userMapper = new \Models\User\UserMapper($this->db);
    $session = new \Models\Session\Session;
    $sessionMapper = new \Models\Session\SessionMapper($this->db);

    $email_hash = $security->simpleHash($email);

    if (!$auth = $authMapper->fetchWhere('email_hash', $email_hash))
    {
        echo 'User doesnt exist'; 
        return;
    }

    if (!$auth->verifyPassword($password))
    {
        echo 'Password not correct'; 
        return;
    }

    $user = $userMapper->fetchById($auth->user_id);

    $session->createUserSession($user);

    $sessionMapper->save($session);
}

There are a few concerns here. First is the lack of dependency injection. The second is that this a cumbersome chunk of code to use every place I might want to provide login functionality.

So where should this logic live? In a controller? In the User domain object? In the Auth domain object? That seems kind of circular - the whole point of the data mapper is so that the domain object doesn't deal with persistence of even itself, let alone OTHER objects.... Should it be placed in a User or Auth service layer within either the /User/ or /Auth/ models?

I'm a bit lost as to the best practice for this sort of thing.

Also keep in mind I'm going this for learning purposes, so I don't want to just use something like Symfony.

Was it helpful?

Solution

To answer my own question, I've decided that the best place for this is to create an AccountController that accepts a LoginHandlerInterface interface as a constructor argument.

The AccountController then looks like this:

namespace App\Controllers;

class AccountController
{
    protected $LoginHandler;

    public function __construct(\Framework\Interfaces\LoginHandlerInterface $LoginHandler)
    {
        $this->LoginHandler = $LoginHandler;
    }

    public function login()
    {
        if (/* ...  form validation stuff ... */)
        {
            try 
            {
                $this->LoginHandler->login($email, $password);
            }
            catch (\Framework\Exceptions\Login $e) 
            {
                // Assign login errors to template etc...
            } 
        }
    }
}

Then whichever LoginHandler I end up using, has everything it needs to do all of the logging in (looking up the user, validating the password, updating the session etc). This keeps my AccountController clean, flexible, and testable.

I inject the desired LoginHandler (and RegistrationHandler, which I haven't shown here) via a configuration in an IoC container that auto-resolves constructor dependencies.

OTHER TIPS

Auth should handle the login if it fails returns false, if it's true do the session's logic and returns true.

So in your controller you will do something like if(Auth->login($email,$password))

ps: For this type of workflow, i prefer to use Singleton pattern ( tho it ruins Unit Testing ), but i do think it will suit you better.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top