Search code examples
phpoopdependency-injectionsuperglobals

Dependency Injection with Superglobals?


I'm using dependency injection wherever possible in my application.

I've got a Request class that works with superglobals, but to me it feels a little bit weird; injecting superglobals. Isn't it safe to say that superglobals will always be there and instead of injecting them I could just call them directly in the class (tightly coupled)?

Class: Request

public function __construct($get, $post, $server) { ... }

Class: Session

public function __construct() { session_start(); }

This last one is a little bit tricky because in this constructor I am starting the session with session_start() and $_SESSION is only defined after the session has been started, so I cannot inject that. However, I could set it with a setter after instantiating the class.

How should I work with superglobals when it comes to OOP?

Do I just couple them tightly with classes or should I still just inject them.


Solution

  • Your code is kind of confusing. What you can do is this:

    class Request {
        public function __construct($getData, $postData, $serverData) { ... }
    }
    
    new Request($_GET, $_POST, $_SERVER);
    

    When it comes to sessions, you'd better wrap it into an object:

    class Session {
        public function __construct() {
            if (!isset($_SESSION)) session_start();
        }
    
        // ...
    }
    

    Another approach is to use Lazy Initialization:

    class LazySession {
        private $isStarted = false;
        public function __construct() {
    
        }
    
        private function verifyAndStart() {
            if (!isset($_SESSION)) session_start();
        }
    
        public function set(...) {
            $this->verifyAndStart();
            // ...
        }
    
        public function set(...) {
            $this->verifyAndStart();
            // ...
        }
    
        // ...
    }
    

    Then you can inject it anywhere, but the session will only be created when needed. Notice that the session MUST be created before any output.

    Edit:

    I was in a hurry when first answered this, but just to make things clear: you cannot decouple everything. What should be decoupled is the implementation of session variables access and its client objects.

    For example, you define something like this:

    interface SessionStorageInterface {
        public function get($key);
        public function set($key, $value);
        public function remove($key);
        public function clear();
    }
    

    If you don't want to rewrite PHP built in session system, you'd have to do something like:

    final class DefaultSessionStorage implements SessionStorageInterface {
        protected function start() {
            if (!isset($_SESSION))
                session_start();
        }
    
        public function set($key, $value) {
            $_SESSION[(string) $key] = $value;
        }
    
        //...
    }
    

    If you want to change this and hold your session in a database, you could create something like:

    final class DbSessionStorage implements SessionStorageInterface {
        private $id;
        private $dbStorage;
        private $definition;
    
        public function __construct($sessionId, DbStorage $storage, StorageDefinition $def) {
            $this->id = $sessionId;
            $this->dbStorage = $storage;
            $this->definition = $def;
        }
    
        public function set($key, $value) {
            $data = [
                $this->def->getField(StorageDefinition::IDENTIFIER) => $this->id,
                $this->def->getField(StorageDefinition::KEY) => $key,
                $this->def->getField(StorageDefinition::KEY) => $value,
            ];
    
            try {
                $this->dbStorage->save($def->getContainer(), $data);
            } catch (Exception $e) {
                // does something...
            }
        }
        // other methods omitted for brevity...
    }
    

    The important thing is that when you have some class that depends on a session storage, you declare it like this:

    class SomeClass {
        public function __construct(SessionStorageInterface $storage) {
            // ...
        }
    }
    
    $obj1 = new SomeClass(new DefaultSessionStorage());
    $obj2 = new SomeClass(new DbSessionStorage(...));