3

Short ver

What I want to achieve is to use session as a dependency of a class, ideally with Laravel's service provider. Is that possible?

Long ver

Recently I found that session can't be accessed by service providers, because...

in Laravel the session is handled by StartSession middleware that executes after all the service providers boot phase

(reference)

This is quite inconvenient for me. I've been creating an e-commerce site where the shopping cart is using session, and session is supposed to keep the items that the customer has picked up.

Here is how the constructor of the cart used to look like...

class Cart
{
    private $items;

    public function __construct($items)
    {
        $this->items = $items;
    }
    //other functions...
}

And, here is AppServiceProvider.

class AppServiceProvider extends ServiceProvider
{
//boot

public function register()
{        
    $this->app->bind(Cart::class, function () {
        return new Cart(session('cart'));
    });
}

As already mentioned, session('cart') can't be accessed by a service provider.

So, I put session('cart') directly to Cart's constructor.

class Cart
{
    private $items;

    public function __construct()
    {
        $this->items = session('cart');
    }
}

In this way, Cart is always dependent on session... but, with this approach, Cart's dependency (i.e. session) can't be resolved with Service provider, which can't access session (as already mentioned). With this approach, it's necessary to use new keyword in order to instantiate Cart.

i.e.

class SomeController extends Controller
{
    private $cart;

    public function __construct(Cart $cart)
    {
        $this->cart = $cart;
    }

    public index()
    {
        $this->cart;
        //This will get null in its $items property,
        //because Service Providers can't access session

        $cart = new Cart();
        //This can get items from session()
    }
}

It's not really ideal to use new Cart(). I'd like to inject Cart as a dependency into controllers.

Is there any other way to use session as a dependency of a class (other than putting it to a constructor directly)?

Any advice will be appreciated.

Hiroki
  • 3,893
  • 13
  • 51
  • 90

1 Answers1

5

To me it seems like injecting a Session is quite a common use-case and it's a bit weird the documentation for this is lacking.

I've experimented a bit with Laravel 5.8 sessions and might have a solution for you. It seems like you can typehint the Illuminate\Session\Store class and use that to retrieve your data. This would result into something like the following class:

namespace App\Test;

use Illuminate\Session\Store;

class Cart
{
    /**
     * @var \Illuminate\Session\Store
     */
    private $session;

    public function __construct(Store $session)
    {
        $this->session = $session;
    }

    public function getItems(): array
    {
        return $this->session->get('cart') ?? [];
    }

    public function addItem(string $item): void
    {
        $this->session->push('cart', $item);
    }
}

This class is automatically resolved by the Laravel dependency container. However if you would like to do this yourself your service provider would look something like:

    public function register()
    {
        $this->app->bind(Cart::class, function (Container $container) {
            return new Cart($container->get(Store::class));
        });
    }

I think the gotcha of this whole thing is that session values can not be accessed directly in the service provider, but you can access the service which gives you access to said values.

Do you think this solution would fit your use case?

PtrTon
  • 3,705
  • 2
  • 14
  • 24