Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Flerex's avatar

How to initialize a class in its constructor with session data, while being able to inject it in a controller's constructor

I am developing a site that allows to save a visitor's preferences, whether they're logged in or viewing the site as a guest.

To make things easier, I abstracted some logic around this under a helper class (a service). This class can be injected anywhere throughout my code, and will provide methods like $helper->getSettings(), so we can obtain the current visitor's settings without bothering about where they come from (User model, the cookie, the defaults).

Here's a dummy example of how the class would look like:

class Helper
{

    /** @var array $settings */
    protected $settings;

    public function __construct()
    {
        if (Auth::check()) {
            $this->settings = Auth::user()->settings;
        } else if (Cookie::has('settings')) { // Data from guest users is stored in a cookie
            $this->settings = Cookie::get('settings');
        } else {
            $this->settings = [];
        }
    }
    
    public function getSettings() {
        return $this->settings;
    }
    
    // More methods that make use of $this->settings
    
}

Note: This class is bound to the Service Container as a singleton.

I am currently using this class in a controller's private method. As I cannot directly inject this dependency in the private method's signature, I have to inject it from within the controller's constructor, like this:

class MyController extends Controller
{


    /** @var Helper $helper */
    protected $helper;

    public function __construct(Helper $helper)
    {
        $this->helper = $helper;
    }

    private function somePrivateMethod() {
        // Using $this->helper
    }

    //

}

Now, the problem is the following:

I am injecting this class into the controller by using its constructor. As the controller's constructors are run before the middleware pipeline, there's no session and, hence, authentication yet, so my Helper class will not work as expected.

To solve this I have thought of some alternatives:

  • Creating a initialize() method in the Helper class that would check if the class has been initialized. This method would be called before any of the other methods in the helper. This option, however, feels very dirty and prone to error as I could forget to add this check.
  • Using the app() global helper, but I am not a fan of globals and would like to avoid them if possible.

As I'm not very happy about my options I come to you to enlighten me with your wisdom. What would you do?

0 likes
17 replies
jlrdw's avatar

I usually just have a "use" statement:

use App\Helpers\ChkAuth;

But in a helper I use static methods. The one "use" statement makes all methods available.

If you need instance methods, you could setup a __callStatic(). I have done this in cakephp.

As example I can call from a scope:

    public static function chkRole($role = null)
    {
        $userrole = Auth::user()->role;
        $checkrole = explode(',', $userrole);
        if (in_array($role, $checkrole)) {
            return true;
        }
        return false;
    }

If seeing if the role is an admin the query is build to show all, if just user, the query then just shows that user's data.

Flerex's avatar

Thanks for your answer @jlrdw!

When you say to use a use statement I suppose you mean to create the instance of the class by using new, right? If that is the case, I would rather not, as I am currently making this class a singleton using the service container. This is for efficiency reasons, as every time the helper is instantiated it runs multiple SQL queries (in the example I provided there would be only one query, but it would still not be ideal).

Flerex's avatar

I can't use static methods as the Helper class has state. In fact there are class attributes. In a static method I wouldn't be able to access those attributes.

jlrdw's avatar

See updated answer, it talks about singleton. I use Auth and session in helper, just needs "use" statement.

Like above:

$userrole = Auth::user()->role;
Flerex's avatar

So, you're saying I should delegate the initialization of the Helper class to the methods in that class so I can use the session because they will be called in a situation that the session would already be created?

Snapey's avatar

perhaps use your initialize method, but call it from middleware?

You would then only need to do it once, avoiding the need to check in each of your methods?

jlrdw's avatar

If you don't use middleware as suggested, Just the "use" statement is required in a class to make the static methods available.

If you need another example, let me know. But perhaps middleware would work as well. I just use other frameworks also, and stay away from framework "biased" code.

Flerex's avatar

What @snapey proposes is an interesting approach, but for some reason I think it feels dirty, as it happens to work just because it is a singleton. If it weren't, then you wouldn't be able to get every single instance of the class.

Regarding what you're proposing @jlrdw, the problem I see is what I already contemplated in the main post. By delegating the logic into their own method and then hiding it under a trait, I would still have to remember every time that a public method is created to run this one to check if the class has been initialized before and, if not, do it again. I think you're forgetting that the class has state and that there's a private $settings attribute that I have to set different values depending on whether the user is logged in or not.

jlrdw's avatar

I did not know you were also going to use a trait. But look at how Auth is handled. A facade is used. Maybe you could search the API, and even make a facade. After all Taylor did it for authentication.

I also do similar in a custom framework, but use a __callStatic() in a method.

But deep in the jungles of the vendor folder, is how Taylor does it also. In fact I learned this and other things from Taylor in updating other things.

Also try to google:

site:laracasts.com your search term
Flerex's avatar

@jlrdw No, I'm not using traits. I missunderstood you. I just don't know where you're going with your static method. Maybe if you provided some code based on the example I provided it would make it much more clearer to me what you're trying to say. But I would like to avoid static methods as they're generally considered a bad practice.

Flerex's avatar

I am not sure, actually. I liked what you proposed as it would be a way to initialize when it actually can, but what would you do if it weren't a singleton? Because this problem could arise in a situation were I wouldn't want/need a singleton and I would be back at the same problem.

Snapey's avatar

You would need an array bound to the container, and then call the initialize/boot method on each

jlrdw's avatar

You said you want to avoid static methods, that's what __callStatic() is for.

As I said the API will show how Taylor uses it.

jlrdw's avatar

I don't have a good laravel example, but here is a cake php example.

Here is what a session read looks like in cakephp 4

$this->request->getSession()->read('areturn');

I, in a helper needed a static call, so I wrote a helper class:

<?php

namespace App\Helpers;

use Cake\Http\Session;

class SessionHelper
{
    public static function __callStatic($method, $params)
    {
        $instance = Session::class;
        $c = new $instance;
        return $c->$method(...array_values($params));
    }
}

So now I can use:

    public static function authId()
    {
        $authid = session::read('authid');
        return $authid;
    }

In other words, my helper is a static method which does not allow me using session as an instance method.

Just showed so you can see example.

P.S.

This part:

    public static function __callStatic($method, $params)
    {
        $instance = Session::class;
        $c = new $instance;
        return $c->$method(...array_values($params));
    }

I learned from Taylor.

Flerex's avatar
Flerex
OP
Best Answer
Level 1

After some research I found that the best way to solve this is the following.

First, the Auth related logic should not be placed in the controller, as it is considered a bad practice (because of things like this). This logic should be placed elsewhere and leave the constructor just to build the dependencies of the object.

With this said, by having a getter method (even tho it is protected) we can check if the attribute that we're trying to access has been initialized and, if not, doing it right away.

class Helper
{

    /** @var array $settings */
    protected $settings;

    protected getSettings() {
        if ($this->settings === null) {
            if (Auth::check()) {
                $this->settings = Auth::user()->settings;
            } else if (Cookie::has('settings')) { // Data from guest users is stored in a cookie
                $this->settings = Cookie::get('settings');
            } else {
                $this->settings = [];
            }
        }

        return $this->settings;
    }
    
    public function getDefinedSettings() {
        return $this->getSettings();
    }
    
    // More methods that make use of $this->getSettings()
    
}

Please or to participate in this conversation.