How I used service container to create a flexible cart that uses database or session implementation depending on user authentication state, whether logged in or not.
I was developing an eCommerce website which involved buying products and making payments straight from the website itself. As a standard practice, this type of websites have a cart, in which users can add multiple products and later on, after finding all the products they need they finalize the purchase. Now this website had to specific requirements for the cart.
- Should be persisted even after the user closes the browser, or disconnects from the internet, for any visitor.
- Should allow products to be added to the cart, when user is logged in or not, but when checking out, they should log in or register first and once done continue with checking out with the same cart products, seamlessly.
I had to have a method that will fit both criteria, solve both requirements. For that I thought I would store cart products in a session for non authenticated users and in a database for authenticated users.
Sessions are best because they are encapsulate data based on a specific browser instance, they are persistent and volatile. Which means I wouldn’t worry about managing data per client, and I wouldn’t worry about removal of data after some time. The bad part being that when users change browser or device, then the cart information is lost.
For authenticated users, I would have to create a table for cart and add the user_id column so each user can have their own cart, encapsulation. Also storing the cart inside the database means later can be used for website metrics, reporting and analysis.
When adding to cart, I have to call a method that first checks if user is authenticated, then if is authenticated add to database, and if not authenticated, add to the session. I thought this was too much logic to put into a controller. So I decided I would write a contract for cart manager, then have two classes implement the contract, database cart class, and session cart class.
The cart manager contract declares methods for the cart operations.
Then the session cart class implements the contracts using Laravel session.
The basic example show implementation of three methods. The other methods are below, couldn’t show them for spacing concerns 😉. Then we have the database cart implementing the same methods, but now using Laravel eloquent.
The database cart also implements the methods. Now using Laravel Eloquent. The final challenge was using the classes inside the controller. I didn’t want to if statements as I did in some of my previous projects, but now I wanted to abide by the SOLID principles. So I decided to use dependency injection.
Dependency Injection and Service Container
“The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are “injected” into the class via the constructor or, in some cases, “setter” methods.” — from Laravel Website
In simple terms, I wouldn’t have to write if statements inside the controller, the controller will only deal with adding data to cart, it doesn’t have to know how the actual adding works. This is done by Laravel convention using service providers. Inside service providers there is an $app property that can be used to add dependencies to the service containers and their resolutions.
But in this project I couldn’t, I had to check if the users are authenticated or not. And this can’t be done inside service provider, since the the authentication handlers are called after the all the service providers are called, which means user instance cannot be obtained inside them. Instead Tyler Otwell insists on usage of middle-ware for this purpose instead trying to create a hack. And Laravel has an app() for that exposes an app instance anywhere in the application, which means we can bind our implementations inside the middle-ware. For this I created a cart resolution middle-ware, inside which I put the bindings for the cart implementations.
Then I registered the middle-ware inside the Kernel in web middle-ware array. The reason being, I wanted the authentication to be performed for web requests.
Finally, the controller
Inside the controller constructor I will now inject my cart manager contract, from there Laravel using reflection will check inside the service container for any bindings for that contract, then provide the appropriate instance.
Now the controller is very neat and the implementation is SOLID. The $cart instance can be used inside the controller methods, without if statements or any implementation boilerplate. Also if we have to implement another cart we just have to create a class that implements the cart manager contract, then add binding inside the service container.
“Everyday life is like programming, I guess. If you love something you can put beauty into it.”
― Donald Knuth