JRCSalter's avatar

I don't understand service containers

So I've watched this video many times now: https://laracasts.com/series/php-for-beginners-2023-edition/episodes/35 and I still don't understand the purpose of it.

First of all, the bootstrap file is included in another file, and part of the reason we are given as to why we would want to use service containers is to not repeat code, so why can't we just save the databse object to a variable, and include the file whenever we need it, rather than making a brand new class?

Second, what is the purpose of the App class? All it does is do exactly what the Container class does. Except we can now call it statically. I haven't tested it yet, but couldn't we just make the Container methods static if that's what we want to achieve? But would this even be necessary? Is there even a difference between $db = $container->resolve( 'Core\Database' ) and $db = App::resolve( 'Core\Database' )?

I've tried to look online for help with this, but it's just made me even more confused, and the only common thread appears to be that people don't seem to want to write $var = new Object, and I don't understand why that's an issue. Most guides throw a load of buzzwords at me, but don't seem to show the 'wrong' way and showing why using Service Containers is better.

This all just seems like needless confusion to me.

1 like
31 replies
LaryAI's avatar
Level 58

The purpose of service containers in Laravel is to manage the creation and resolution of objects or dependencies. It helps in organizing and centralizing the creation of objects, making it easier to manage and maintain the codebase.

Regarding your first question, using a service container allows you to define the dependencies once and then resolve them whenever needed. This helps in avoiding code duplication and ensures that the same instance of an object is used throughout the application. If you were to save the database object to a variable and include the file whenever needed, you would end up creating multiple instances of the object, which can lead to inconsistencies and unnecessary resource usage.

As for the second question, the purpose of the App class in Laravel is to provide a convenient way to access the service container. It acts as a facade, allowing you to access the container and resolve dependencies without having to manually create an instance of the container each time. The App class provides a static interface to the container, making it easier to use and reducing the need for boilerplate code.

While it is possible to make the container methods static, it is generally not recommended. The container is designed to manage the lifecycle of objects and handle dependencies, and making the methods static would limit its flexibility and make it harder to test and mock dependencies. By using the App facade or resolving dependencies through the container instance, you can take advantage of Laravel's dependency injection system and benefit from features like automatic resolution and dependency management.

To summarize, service containers in Laravel provide a way to manage dependencies and avoid code duplication. They help in organizing and centralizing the creation of objects, making the codebase more maintainable and flexible. The App class acts as a facade to the container, providing a convenient way to access and resolve dependencies.

JRCSalter's avatar

@LaryAI Not sure if anyone else can help here, but this doesn't really clear things up all that much. I can understand the idea of resolving the object when needed, but why? The only cases I can think of is if it's expensive on resources, and may not be needed; or if you're resolving it more than once in a script, but I can't think of an instance where you might want to do that.

I don't understand how calling a class statically is any easier, or why making the Container class static would limit its flexibility.

jlrdw's avatar

An instance is always a new class. Static works a little different.

To use a model in a controller, you have to have an instance of the model.

Basically all of the background stuff happening is just to:

$something = new myMdel;  // whatever the name is.

But the framework removes you from having to use a new keyword all over the place.

jlrdw's avatar

@JRCSalter

But what's the problem with the new keyword? Nothing, but modern frameworks use dependency injection, same in java, .net core, etc.

You can still use the new key word if that's want you want.

An older custom framework I had and updated used the new key word, but I went ahead and wrote a custom DI container. Just easier for me now to use DI.

JRCSalter's avatar

@jlrdw But why is it easier? What does Dependency injection have to do with new?

JRCSalter's avatar

@jlrdw Sorry, but that doesn't really help to answer my question. The trouble I'm having is that all of these explanations here and in that guide go on about how something is 'easier', or 'avoids' doing something, but in order for me to understand this fully, I'd need some examples of the 'wrong' way to do it, along with examples of exactly how Service Containers can be beneficial, rather than simply saying it's easier or reduces code. Because this certainly doesn't seem easier to me at this time, and it appears to add a lot more complexity to the code that doesn't seem necessary on the surface.

1 like
JussiMannisto's avatar

@JRCSalter The App class is one of Laravel's Facades. Facades are just a way of using many of Laravel features. From the documentation:

Facades have many benefits. They provide a terse, memorable syntax that allows you to use Laravel's features without remembering long class names that must be injected or configured manually. Furthermore, because of their unique usage of PHP's dynamic methods, they are easy to test.

The App facade provides many other functions than just service container access.

JRCSalter's avatar

@JussiMannisto I've been looking at this for ages now and I still don't understand the point of facades. With some quick testing, I can get the same outcome without a facade.

The idea of not needing to remember long class names doesn't make sense, because you still include the class name when you resolve it e.g. App::resolve('Core\Database'); compared to $container->resolve( 'Core\Database' ); It's practically the same code. How does a facade help with this? Plus, injection or configuration appears to be handled by the container, so again, how does the facade help with that? How are they easy to test?

JussiMannisto's avatar

First of all, the bootstrap file is included in another file, and part of the reason we are given as to why we would want to use service containers is to not repeat code, so why can't we just save the databse object to a variable, and include the file whenever we need it, rather than making a brand new class?

You're not making brand new classes. You're binding objects to the container, and retrieving objects from the container. Laravel can automatically retrieve and inject the objects in a lot of cases.

You could manually include objects from an external files in every class. But it would soon get horribly messy. Each class would depend on files in specific locations. If you copied that class to another project, it wouldn't work. With dependency injection there's no issue: each class tells you which dependencies they need, and you give it to them. And with service containers this can be automated.

This ties into an important concept called inversion of control. Individual components often should not control how their dependencies are implemented or created. They are created elsewhere and passed to the components as needed. This makes your components a lot more flexible and loosely coupled.

You don't use IoC for everything. But for environment-specific things like database connections, it should definitely be used. Individual classes should not have to worry about spinning up database connections.

JRCSalter's avatar

@JussiMannisto Can you provide examples of this? I have trouble understanding this kind of thing without seeing an example of the code.

JRCSalter's avatar

@JussiMannisto I'm confused with what you said here:

'You could manually include objects from an external files in every class. But it would soon get horribly messy. Each class would depend on files in specific locations. If you copied that class to another project, it wouldn't work. '

That's still going to be a problem with or without containers though, isn't it? The container is defined in the bootstrap.php file, and if you saved it as a variable, you could then use it in other files. Sure, those files wouldn't work if you moved them to another project, but the same can be said for any file right?

What appears to be a simple example:

 $config = require basePath( 'config.php' );
  $db = new Database( $config[ 'database' ] );

Then include this file whenever you need it, and use the $db variable. This seems simple to me.

So why is that different from:

$container = new Container();

$container->bind( 'Core\Database', function() {
  $config = require basePath( 'config.php' );
  return new Database( $config[ 'database' ] );
});

App::setContainer( $container );

And then in another file you have:

$db = App::resolve( 'Core\Database' );

Not to mention defining the Container and App classes. This actually seems like a lot more code, which is supposedly what containers are meant to prevent.

JussiMannisto's avatar

@JRCSalter I think it's easiest to give you some examples from Laravel while correcting some misconceptions.

Facades

The idea of not needing to remember long class names doesn't make sense, because you still include the class name when you resolve it e.g. App::resolve('Core\Database');

You've misunderstood what they mean. They're talking about facades in general, not resolving dependencies. I'll demonstrate what they mean with the Cache facade. Here's how it's used:

Cache::put('key', $value, 10);

You don't need to know the default cache driver, how the cache repositories are constructed, or what actual classes are being used under the hood. Hence, you don't need to remember long class names. You just use the facade.

You could also build the cache repository object yourself and use it, but that takes some learning and lines of code. And what if one day your team decides to move from the default file-based driver to something like Redis? How do you build a Redis repository? I don't know because I don't need to. With facades you don't need to touch your code at all. You change a couple of lines of config and the framework does the rest.

You don't need to use facades. But they can make your life a lot easier.

Service container

That's still going to be a problem with or without containers though, isn't it? The container is defined in the bootstrap.php file, and if you saved it as a variable, you could then use it in other files.

Most of the time you don't deal with the service container directly. You simply type hint what you need and the framework automatically resolves and injects the dependencies. Let's say your app has an SmsSender service. Once you've bound it to the service container, you can use it anywhere in your codebase:

class SomeController extends Controller {

	public function send(Request $request, SmsSender $smsSender) {
		// The $smsSender is automatically injected and you can use it here.
		// You can also inject it in the constructor.
	}

}

A service container also makes it easy to bind services as singletons, i.e. single instances that are reused. You suggested loading the config and creating a new DB connection object where needed. Imagine your web route has 10 middlewares, a controller and some service classes. With your method, you might have to open several separate DB connections on every request. With a service container you can reuse the same connection everywhere. (Not that you have do any of this yourself in Laravel)

Example of service binding

Here's a simplified real life example that demonstrates how the service container makes you write less code, and some other benefits. One of our projects is a fairly big web app with around 500 routes, ~150 controllers, and many jobs and commands. We have a unified way of reporting issues to the dev team. There's an ErrorReporter interface with a single report() method, something like this:

interface ErrorReporter {
	public function report($someParameters);
}

Then we have an EmailErrorReporter class that implements that interface:

class EmailErrorReporter implements ErrorReporter {

	public function __construct($someEmailParameters) {
		...
	}
	
	public function report($someParameters) {
		...
	}

}

We'd bind that to the service container as the ErrorReporter service:

$this->app->singleton(ErrorReporter::class, function($app) {	
	return new EmailErrorReporter(...);
});

... and then use it anywhere as needed:

public function __construct(ErrorReporter $reporter) {
	...
}

The reporter is used in maybe 50 classes. None of them have to know how the reporter is implemented. When we decided to switch from email reports to Slack notifications, we only had to create a new SlackErrorReporter class and bind that as the ErrorReporter service. We didn't have to touch any of the classes actually using the service.

Now imagine if we had manually constructed the EmailErrorReporter object everywhere instead of doing it once in the binding. It would've been annoying.

Inversion of control

I want to emphasize why it's almost always better to pass dependencies down to your classes rather than load them from the classes themselves. Your code actually shows an example this. It's this part:

$db = new Database( $config[ 'database' ] );

Configuration is passed to the constructor instead of the object loading it from some hard-coded location. It could also do that, but then it would be a crappy class because it would only work if you stored your config in a specific way in a specific place in the filesystem. You couldn't use the class in a separate script or project with a different structure without modifying it.

It's much better to pass dependencies down to classes. A top-down approach makes low-level classes independent and agnostic of the environment. If the classes load files from hard-coded locations themselves, then it's like they have these tentacles that hold on to parts of the environment that they depend on. And once you have hundreds or thousands of files like that, the whole thing becomes an entangled mess where you can't change simple things without breaking the app in 200 different places. I've dealt with codebases like that and it's not fun.

2 likes
JRCSalter's avatar

@Snapey Sorry, but that hasn't helped at all. I don't even know how to properly explain what I don't understand. I mean, I think I can understand the how, but not the why. At 45:33 in that video, they talk about binding arrays, but why would you do that? Why can't you just make a simple array, that sounds so much simpler. And that's just one example.

What I need is an example of code that doesn't use containers, and one that does, and shows the benefits it provides, but none of these guides seem to do that. i enjoy programmming, and eventally want to do this as a career, but this is driving me nuts that I can't seem to understand what appears to be such an important concept.

jlrdw's avatar

@JRCSalter this might help.

  • Old way - you create a new instance every where you need a class used.

  • New way - It's handled for you in the framework.

So I suggest going to Github and study the framework code to see how everything is done.

Bear in mind, learning all this stuff can take months or even years.

There is a difference in using a framework and knowing how to write a framework.

Edit:

A facade is a __callstatic method.

And the chapter in the documentation I referred you to clearly shows the new keyword being used:

$service = new Transistor(new PodcastParser);

Also see this article by @jeffreyway: https://code.tutsplus.com/dependency-injection-huh--net-26903t

And perhaps write a small framework and make some DI classes. Coding is the only way to truly learn it.

Snapey's avatar

@JRCSalter

If you start writing tests, you will appreciate the ability to switch out classes for fakes or mocks without having to alter the code you are trying to test

Otherwise, be assured, that until you start writing service providers or your own packages, knowing about how the container works won't really impact you, you will just use it without being aware of it.

JRCSalter's avatar

@jlrdw I've looked at some of the code, and it just looks confusing. There's so much going on, where everything calls something else that calls something else and so on, and I get lost.

I think I'm beginning to understand the concept a little bit though. That article was probably the most helpful one I've read on the subject.

I still don't understand the point of facades though. It just seems like we're duplicating the code to no benefit. I can't see why we can't make the entire Container class static. I've just tested it and it works fine.

Edit:

After looking into this a bit more, I'm back to being confused as to why we would use containers. Why not put the definition into its own function.

Instead of:

Container::bind( 'Core\Database', function() {
  $config = require basePath( 'config.php' );
  return new Database( $config[ 'database' ] );
});

$db = Container::resolve( 'Core\Database' );

Why not:

function core_database() {
  $config = require basePath( 'config.php' );
  return new Database( $config[ 'database' ] );
};

$db = core_database();
jlrdw's avatar

@JRCSalter

I still don't understand the point of facades though. It just seems like we're duplicating the code to no benefit.

You are calling the same code, I explained __callstatic is used.

class::method

Is just nicer looking than:

class->method

__callstatic calls instance methods without needing the new keyword, but you still need the use statement.

I suggest start coding, and all will fall into place.

JRCSalter's avatar

@jlrdw What you seem to be suggesting is that facades are purely an aesthetic choice akin to using spaces or tabs to indent, but I'm sure people can't be suggesting to build an entirely new class just because the code looks better.

There must be some kind of functionality that it provides beyond whether you use two colons, or a hyphen and angle bracket.

jlrdw's avatar

@JRCSalter

Nope, just some prefer the Scope Resolution Operator, me included.

But __callstatic also makes it easier to use some classes.

Again, don't worry about all this stuff, start coding and learn.

If you don't want to use facades, then don't.

Edit:

Have you looked up __callstatic in the php manual to better understand it.

JRCSalter's avatar

@jlrdw It's not about whether or not I want to use facades, but the original video I linked to suggested they were used to reduce code, and if you have to create an entirely new class just to change two characters because it looks better, then it doesn't reduce code, and instead adds unneeded complexity.

Also, I don't understand what __callstatic has to do with this. That method wasn't used in the above video.

martinbean's avatar

Also, I don't understand what __callstatic has to do with this.

@JRCSalter Because you’re going far too many steps ahead. Learn about the container. Then start concerning yourself about facades. You don’t have to use facades.

Facades are just static proxies for something in the container. __callStatic is relevant because facades change convert method calls like this…

Sms::send('+15551234567', 'Hello from ACME');

…to something like…

$smsService = $this->container->make(SmsServiceInterface::class);

$smsService->send('+15551234567', 'Hello from Acme');

…under the hood. It’s a way of using services from the container without having to inject them or instantiate them in the class/method you want to use them.

JRCSalter's avatar

@martinbean 'Because you’re going far too many steps ahead.'

OK, maybe I am going to far ahead. I don't know. I have no idea what I'm doing. I've finished the Beginners PHP and Laravel courses, but don't know where to go from there. It seems there's a load of concepts that I'm yet to learn, but there doesn't appear to be any intermediate or advanced continuations of those courses (yet), and it's mentioned in that video that this entire concept is an advanced one anyway. It's entirely likely that I'm missing something more basic.

jlrdw's avatar

@JRCSalter

but don't know where to go from there.

Take the laravel from scratch course. Also start coding and look up things in the documentation, and the API as needed.

How do you think we learned this stuff, by coding practice projects.

Also actually work some of the examples from the documentation.

Edit:

There are also some good Youtube videos.

JRCSalter's avatar

@jlrdw I've taken the Laravel course, but there's so much to learn that I don't know what I don't know. You say to look up things as needed, but how do I know when they're needed? How do I know that I'm not just coding something that is already covered?

I've already created a minimum working site using Laravel, and am working on another site that I plan to write in raw PHP. I understand it's unlikely that I'll use raw PHP in the real world, but I wanted to do it as an exercise to see if I understand the underlying concepts of a framework, which is why I'm asking about all this. I'm also making my way through the Laravel documentation, but I always find manuals a poor way to learn a concept; they often tell you how, but not why, and if they do tell you the why, I often find it difficult to understand. Heck, even after all of you trying your best to be as helpful as possible, I'm still struggling; a manual isn't likely to help me understand the why any better.

So at the moment, I am creating a raw PHP site that uses a static container, with no facade. Other than the whole containers thing, I understand most of the rest of the concepts fairly well.

jlrdw's avatar

@JRCSalter you should actually code as you learn, not just view the videos.

If you are confused on a concept, look it up.

martinbean's avatar

OK, maybe I am going to far ahead. I don't know. I have no idea what I'm doing.

@JRCSalter Yes. So you need to slow down and focus. This thread is a prime example. You created it saying you don’t understand service containers, but then ended up going on about facades and static calls and whatnot. Reign it back. If you don’t understand the service container, then focus on that first. Forget about facades. You need to understand the service container before even thinking about facades.

Please or to participate in this conversation.