taniarascia / laconia

🏺 ‎ A minimalist MVC framework.

Home Page:https://laconia.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Separate SESSION array from controllers

halfer opened this issue · comments

commented

This is similar to #2, but may need a bit of experimentation to get right.

I would start off with adding something similar to GET/POST:

protected $sessionValues = [];

public function setSessionValues(array $sessionValues) {
   $this->sessionValues = $sessionValues;
}

protected function getSessionValue($key) {
    return isset($this->getSessionValue[$key]) ? $this->getSessionValue[$key] : null;
}

You will also want to modify keys separately:

public function setSessionValue($key, $value) {
   $this->sessionValues[$key] = $value;
}

However, this array is unusual in that when you reset it, you want it to modify the $SESSION PHP var. To do that, I believe you can use "pass by reference" on the original setter, like so:

// The ampersand indicates pass by ref
// See https://stackoverflow.com/a/885
public function setSessionValues(array &$sessionValues) {

This will allow you to disconnect your controllers $SESSION too, again helping future testing. Of course, check your web app still works after making any sweeping changes.

I did not entirely know how to set the $sessionValues as the $_SESSION superglobal or if I should, but I did create a model of a Session class for handling all login, logout, authentication, and setting/getting of $_SESSION values. Session class. Still need to look at how to make these classes protected but still usable by the controller.

commented

OK, great stuff. I see you instantiate this class in your front controller (index.php) which is good, but then that instance is not used for anything. I would recommend injecting that into your controller parent (Controller) so you do not have to instantiate it again in Login and Logout.

The change you have here (wrapping your session stuff in a Session class) means that your controllers are more testable, since they are no longer dependent on $_SESSION. However, now your Session class depends on it, so cannot yet be unit tested. This is not terrible - your app is more testable already, and sometimes you can make the strategic decision to test some parts and not others.

However, if you want to get bonus points 🥇 , implement my setSessionValues() suggestion, but now in the Session class rather than Controller. The pass-by-ref would still be needed.

I took your advice and added a constructor to the Controller abstract class that will take the $session as a parameter, so now I'm only instantiating the Session class once, and using $this->session throughout the rest of the app.

However, I was unable to figure out how to get the bonus points and use a sessionValues array on the Session class, as session_start() needs to be initiated, but that happens in the Session constructor, so I can't pass $_SESSION through as a parameter.

commented

Cool! I'd suggest adding this to your Controller at the start:

protected $session;

In PHP, you don't have to declare class properties, but if you do not, they become public by default. Since we're aiming for encapsulation, it is best to hide them from external access - we use a getter if we actually need that.

as session_start() needs to be initiated, but that happens in the Session constructor, so I can't pass $_SESSION through as a parameter.

Ooh, good point. OK, in that case, I have another idea. We want to be able to use the global var $_SESSION normally, and something local when in test mode.

I would firstly declare a local session array in this class:

protected $session = [];

Then, I suggest you go through the class and replace all cases of $_SESSION with $this->session, so it is self-contained. This will stop it tracking the PHP session system for now, but the fix is below.

I would then convert the Session to abstract so it has to be extended, and then require the child to specify how to start a session. I would also drop the constructor, since we don't want to call session_start in a test (there is an OO guideline that says one should only do very minimal set-up in controllers anyway).

So, in your abstract class, have something like this:

abstract public function initSession();

This will require this to be implemented in children. Then, let's have two child classes:

class TestSession extends Session {
    // Does nothing
    public function initSession() {
    }
}

You could also have a WebSession, which is what you instantiate in the front controller:

class WebSession extends Session {
    public function initSession() {
        session_start();
        // The class property becomes a reference to the session var using '&'
        $this->session = &$_SESSION;
    }
}

Finally when you create this class in the index.php you will have to initialise it:

$session = new WebSession();
$session->initSession();

(Rather than an abstract parent and two children, you could put the empty initSession() in the parent for testing, keep this class non-abstract, and only have one child, for WebSession. Which of these approaches you prefer is a matter of taste).