Dependency Injection
Dependency Injection is a method of decoupling code. The end result is code that's easier to test, easier to modify in the future, and easier to reason about without having to look at the inner workings to understand what's going on.
In this article we'll cover what exactly dependency injection is, how it works using Cora's provided classes, and best practices including potential pitfalls.
An Introduction to Dependency Injection
General purpose info - not Cora specific
When writing applications, it's common to write code that utilizes other code. For instance, if you are writing a section of your app that sends out emails, you may decide to utilize a 3rd party library such as Amazon's SES (Simple Email Service) to handle the delivery of the emails for you. Or, you may write a class that manages your Users' permissions and reuse that User class across many areas of your app. Code packaging and reuse is an extremely fundamental part of all programming paradigms.
Dependency Injection is the process of passing in (aka "injecting") from an external source the dependencies a piece of code relies on. This is important because it decouples to some extent the pieces of code. Let's look at an example of code that DOES NOT use dependency injection:
class Example
{
// Dependencies
protected $mailer;
public function __construct()
{
$this->mailer = new Mailer();
}
}
Notice the "new" declaration which is creating the dependent object. The fact that the dependency is getting created by the class which needs it means it isn't coming from an external source. So why is this generally considered bad? To answer that question imagine you are going to run some automated tests on your code and at some point you call "$mailer->send()" to send out an email. During testing you don't want emails getting sent out to people, so you want to stub out the Mailer class using a fake version that won't fire off the email. The problem is, with the setup shown above, you can't easily stub out (aka create a double of) the Mailer class because it's getting created in the constructor.
Let's look at the same code, but have it use injection so the dependency is getting passed in:
class Example
{
// Dependencies
protected $mailer;
public function __construct($mailer)
{
$this->mailer = $mailer;
}
}
Now with this code, if we need to pass a fake or modified version of any of the Mailer class in, we can! Below we'll pretend we're in our test function and pass our Example class a fake Mailer dependency.
public function myTest()
{
$example = new Example(new FakeMailer());
}
As long as FakeMailer adheres to the same Interface as Mailer, the Example class will be able to use it just fine without issue, but FakeMailer's send() method will presumably not send out real emails. This concept is referred to as dependency inversion due to the fact that Example is no longer directly dependent on the Mailer class, instead it's dependent on the Mailer Interface.
Ok, let's pretend you don't care about testing (I see that smirk...). Is dependency injection still worth doing for other reasons? Yes. The same concept we just showed that can help with testing also can apply to modifying your code in the future. Let's pretend your Example class logs whenever it does something:
class Example
{
// Dependencies
protected $mailer;
protected $logger;
public function __construct($mailer)
{
$this->mailer = $mailer;
$this->logger = new DatabaseLogger(); // Logs to a database.
}
public function doWork()
{
$this->mailer->send('Super Awesome Email');
$this->logger->log('A super awesome email was sent out.');
}
}
However, let's say that under certain conditions you don't want to log to a database, but instead want to log to a file. What now? You hardcoded "DatabaseLogger" in, so replacing it with say a "FileLogger" isn't possible unless you implement a setLogger() method (which is another form of dependency injection). Let's again change it so we inject a logger in through the constructor. With the setup seen below, you could easily pass in a Database or File based logger as necessary depending on your needs:
class Example
{
// Dependencies
protected $mailer;
protected $logger;
public function __construct($mailer, $logger)
{
$this->mailer = $mailer;
$this->logger = $logger;
}
public function doWork()
{
$this->mailer->send('Super Awesome Email');
$this->logger->log('A super awesome email was sent out.');
}
}
The Dependency Inversion Principle
General purpose info - not Cora specific
What we did in the previous section with the injection of the Logging classes, even though we didn't explicitely define the interface, was an example of the Dependency Inversion Principle.
In order to fully understand the principle, it's important to first understand the concepts of Black Box programming and Interfaces:
Concept #1: Black Box programming
The concept of "black box" programming is probably more widely understood in the world of compiled languages than it is in interpreted language communities, but it's an important general programming concept no matter what. Basically, Black Box programming says you shouldn't need to worry about HOW a code library accomplishes a task internally, you should be able to just look at the interface contract and use it. By "contract" we're talking a function/library says give me "X" input and I'll give you "Y" output. How does it take "X" input and generate "Y" output from it? WE DON'T CARE. The internals are a black box. There could be magical unicorns in there and giant hampster wheels, we just don't care. What matters is the contract between us and that black box of code which governs its usage.
In some compiled languages, you're forced to utilize this concept as you literally can't see the source code, and are only given the public interface.
In interpreted languages like PHP, you can open up the library and inspect how it works internally... however, you really SHOULDN'T NEED TO. As we'll soon discuss, other developers should be able to look at your interface and use the code just from that.
Concept #2: Interfaces
Interfaces are a set of public methods that form the contract between you and the library/class/module/function of code you're using. Interfaces state that if you call method "X" and provide it with parameters "Y", it will do/return "Z" as a result. The most important take away is that Interfaces are independent of the implementation. Going back to the logging example from earlier, if we define a LoggingInterface as such:
interface LoggingInterface
{
/**
* Logs the given message to a persistance layer.
*
* @param string $message The message to log.
* @return void
*/
public function log($message);
}
Then the two possible logging classes for database and file logging would implement that Interface:
class DatabaseLogger implements LoggingInterface
{
public function log($message)
{
// Code to log to a database...
}
}
class FileLogger implements LoggingInterface
{
public function log($message)
{
// Code to log to a file...
}
}
So just to reiterate, Interfaces are independent of any implementation. Rather they represent a contract between you and that piece of software which you can use. As long as you have a clear contract, the internal implmentation details shouldn't matter.
Dependency Inversion
Ok, now that we have that common understanding in place, let's unravel how the Dependency Inversion Principle actually works:
This principle states that:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions. - Martin, Robert C. (2003). Agile Software Development, Principles, Patterns, and Practices. Prentice Hall. pp. 127–131. ISBN 978-0135974445.
What this principle encourages is thinking about your software system in terms of not just a set of Objects, but rather as a set of Objects AND Interactions between them. Furthermore, thinking about the interactions without regard for the actual implementation. If following the principle 100%, no object would ever be directly tied to another object, but instead the dependency for both would be tied to a set of interactions (an Interface).
The following diagram shows this change in dependencies in a visual way:
In the first diagram, class B is directly dependent on the presence of class A. In the second diagram, class B has been changed to have a dependency on an Interface X, an interface which class A implements. In this way, if class A is passed to class B, class B's dependency is satisfied. Of course, with the change to a dependency on an Interface, any other class that implements Interface X would also satisfy class B.
Hopefully those explanations and the Logging example help with understanding how you can improve the adaptability of your application by decoupling direct dependencies between classes. That said, I WOULDN'T necessarily recommend you run out and start making an Interface for everything. The Dependency Inversion principle is a concept that I would argue is important to understand and think about, but also one which you should feel free to break.
In terms of PHP, implementing generic interfaces everywhere in your application can actually make it harder to understand and maintain. Before bothering with an explicit Interface you should examine whether you think you'll ever have another class that uses that same interface, whether you'll need to mock that object, and whether your testing tools even require it for mocking. Just implementing an interface over a class for the sake of having an interface does not by itself reduce coupling in a meaningful way. You really have to be purposeful in thinking about the potential abstraction of interactions and when implementing an Interface will help you achieve a less coupled design.
Injection Without Tools and the Challenges Therein
General purpose info - not Cora specific
By now we've hopefully established the merits of dependency injection, and we've also given a simple example or two. But let's dive just a little deeper into how to accomplish it WITHOUT any 3rd party assistance from Cora or other tools. This should hopefully not only be educational but also highlight why having tools like Cora's Container class around to help are nice.
Let's reuse our Example class from earlier that takes both a Mailer and a Logger object as dependencies, but with a couple modifications.
class Example
{
// Dependencies
protected $mailer;
protected $logger;
public function __construct($mailer, $logger)
{
$this->mailer = $mailer;
$this->logger = $logger;
}
public function createUser($name)
{
$user = new User($name);
$this->mailer->send('Welcome', $user);
$this->logger->log('A new user was created');
}
}
Notice that we're creating a new User object with a name that we won't know until runtime. Needing to dynamically create objects within classes at runtime is a common issue, but the fact that we use the "new" operator should raise a red flag. The way it's written above, the Example class has a dependency on the User class, but it's not clear that this dependency exists because we're not passing the User class in through the constructor! No bueno! Ok, so maybe we need to pass the User object in through the constructor... which feels a little weird... but continuing that thought... what if we needed to create multiple User objects in a loop? Whoa, well then passing in a single User object through the constructor definitely wouldn't make sense. In this scenario of having to create objects dynamically at runtime the solution is to use a Factory. Let's add one more dependency in the form of a UserFactory and make that change:
class Example
{
// Dependencies
protected $mailer;
protected $logger;
protected $userFactory;
public function __construct($mailer, $logger, $userFactory)
{
$this->mailer = $mailer;
$this->logger = $logger;
$this->userFactory = $userFactory;
}
public function createUser($name)
{
$user = $userFactory->make($name);
$this->mailer->send('Welcome', $user);
$this->logger->log('A new user was created');
}
}
Much Better. Now it's clear we have a dependency on the UserFactory class and we're able to create as many User objects at runtime as we want.
Now let's talk about creating an instance of our Example class. To perform the injection and create a new Example object in the simplest sense, we would just do something like follows:
public function someMethod()
{
$example = new Example(new Mailer(), new FileLogger(), new UserFactory());
}
Ok, fair enough. However, this is starting to feel kinda verbose don't you agree? I mean, if we only have to create an Example object once or twice in our app... sure. But what if Example is something that gets used in a bunch of places and we want to make this simpler on ourselves? Well the next logical step would be to create an Example factory and use it like so:
class ExampleFactory
{
public static function make()
{
return new Example(new Mailer(), new FileLogger(), new UserFactory());
}
}
class SomeController
{
public function someMethod()
{
$example = ExampleFactory::make();
}
}
Now those pesky dependencies are being handled by our Factory and we just have to ask it to make us a new object. Easy. Time to go home and drink a beer.
But what if our Example class' dependencies also had dependencies?
class ExampleFactory
{
public static function make()
{
return new Example(new Mailer(new Dependency1, new Dependency2, new Dependency3), new FileLogger(new Dependency1, new Dependency2, new Dependency3), new UserFactory());
}
}
Holy moly Batman! We just got crazy up in here. Let's just get this out of the way, the above is not a good idea. Not only is it hard to read, but if the dependencies of any of those classes ever change, then all the factories that use that class would then also have to be edited. The solution here would be to create some more factories:
class ExampleFactory
{
public static function make()
{
return new Example(MailerFactory::make(), new FileLoggerFactory::make(), new UserFactory());
}
}
Ok, that looks much better. The downside now is we just had to add a bunch of factory classes to our file system that don't do anything except make us an object. For a small project this is no biggie... however, for larger projects, these factories can end up being (IMO) quite a bit of file system clutter. We'll call this handling injection via Concrete (as opposed to Abstract) Factories.
Pros and Cons of Injection with Concrete Factories
Pros:
- This form of handling injection works well with IntelliSense. This means you should be able to quickly open files to see what's going on.
- Dependencies are known before runtime. The PHP Engine will throw an error at you if a needed dependency is missing. Likewise, if you were using a compiled language, you'd get a compile time error.
Cons:
- Can cause a lot of extra files to be added to your app's file directory.
- The additional bloat can potentially make it harder to navigate and understand the app.
- Logic for handling dependencies is spread out over many files.
An Introduction to Dependency Injection Containers
Both General purpose info and Cora specific implementation
A Dependency Injection Container ("Container" for short) is a tool that lets you define how to create objects (including any dependencies they need), and then it will handle the work of creating instances of those objects for you. If this sounds oddly similar to a Factory, that's because it is. The difference is that while a factory specializes in creating a specific type of resource, a Container is a general purpose tool that could potentially give you back many different types of objects.
Cora provides a Container class which can be used to assist with dependency injection and avoid the need for a bunch of factory files. To create a Container, define a resource, and get an instance of that resource works like this:
// Create a Container
$container = new \Cora\Container();
// Register a Service
$container->database = function($c) {
return new \Cora\Database();
};
// Grab an object from it
$db = $container->database;
Now this example is very simple in that we're only defining a single resource and there's no dependencies. Let's dig a little deeper into how we can solve the issues with needing a bunch of extra factories that we ran into earlier when doing injection without any tools.
Injection Using a Container
Both General purpose info and Cora specific implementation
So once again we're going back to our Example class which is already setup for dependency injection and looks like so:
class Example
{
// Dependencies
protected $mailer;
protected $logger;
protected $userFactory;
public function __construct($mailer, $logger, $userFactory)
{
$this->mailer = $mailer;
$this->logger = $logger;
$this->userFactory = $userFactory;
}
public function createUser($name)
{
$user = $userFactory->make($name);
$this->mailer->send('Welcome', $user);
$this->logger->log('A new user was created');
}
}
The task before us is to create an instance of the Example class, and in the process handle the injection of its dependencies. The manual way of doing it, which we want to rework, looks like this:
class SomeController
{
public function someMethod()
{
$example = new Example(
new Mailer(new Dependency1, new Dependency2, new Dependency3),
new FileLogger(new Dependency1, new Dependency2, new Dependency3),
new UserFactory()
);
}
}
To start out, we need to create a Container and register any resources our app will use:
// Create a Container
$container = new \Cora\Container();
// Register Resources
$container->dependency1 = function($c) {
return new Dependency1();
}
$container->dependency2 = function($c) {
return new Dependency2();
}
$container->dependency3 = function($c) {
return new Dependency3();
}
$container->{Example::class} = function($c) {
return new Example($c->{Mailer::class}, $c->{FileLogger::class}, $c->{UserFactory::class});
};
$container->{FileLogger::class} = function($c) {
return new FileLogger($c->dependency1, $c->dependency2, $c->dependency3);
}
$container->{Mailer::class} = function($c) {
return new Mailer($c->dependency1, $c->dependency2, $c->dependency3);
}
$container->{UserFactory::class} = function($c) {
return new UserFactory();
}
Here we defined all the classes we've used in our example. This is probably the worst part of using a Dependency Injection Container, as you have to register any classes you plan on using and tell it how to create instances of them. In the above example I choose to organize the resources alphabetically, but I'd encourage you to do whatever makes sense to you. You could add comment blocks that break your definitions into different sections or utililze a sub-container (see section further down in this document), whatever works.
Before we move on, a few quick things to point out about Cora's implementation. The function that defines each resource is known as a Closure. The "$c" parameter that gets passed in to those closures (as you've probably realized) is a self reference to the Container. Each resource is identified by a name, which can be provided as a hard coded string (see Dependency1, Dependency2, Dependency3) or as a generated string by using curly brackets and getting the name of the class it will return such as "Example::class". The "::class" constant was introduced to PHP in version 5.5, so check if you can use it. Identifying the resource using the ::class constant works well with Intellisense in code editors, which makes inspecting a file to look at the interface a breeze.
I'd recommend registering all resources in one place, as this makes reasoning about what resources are available the easiest. Once you have your resources registered, you need to pass your Container to whatever code controls execution flow. In the case of Cora (and many other MVC frameworks) this would be your Controller, and it's usually passed in through the constructor. Modifying our class to use the Container it would now look like:
class SomeController
{
protected $app; // Our Container.
public function __construct($container)
{
// Container gets passed in.
$this->app = $container;
}
public function someMethod()
{
// Get a new Example object
$example = $this->app->example();
}
}
And just like that, our code to grab a new Example object becomes super clean, with all the dependency injections handled for us. I'd say it cost us some pre-setup work to accomplish that, except our other option was to make multiple factory files with the definitions in them, so really this beautiful result cost us nothing extra. Also we get to avoid adding a bunch of factory files to the project. Yay!
There's still usage questions to be answered, so let's continue on...
Service Locator (anti)Pattern
General purpose info - not Cora specific
So some people choose to solve the problem of dependency injection by using a Service Locator (which is a type of Container, but either globally available or passed around between objects) in a way known as the Service Locator Pattern. While a Service Locator / Container can be a useful tool (as just previously described), the Service Locator Pattern is something which should be avoided. Let's dive into that pattern (which many call an "anti" pattern because it causes more problems than it solves) and how it works.
The idea behind this pattern is simple and follows this thought process:
Why bother doing individual injection of each dependency into your classes? Why not just inject the Service Locator and grab what's needed out of that?"
There's more than one variation on how this pattern can be used, but let's change our Example class to use one of the worst:
class Example
{
// Dependencies
protected $app;
public function __construct($container)
{
$this->app = $container;
}
public function createUser($name)
{
$user = $this->app->userFactory->make($name);
$this->app->mailer->send('Welcome', $user);
$this->app->logger->log('A new user was created');
}
}
Alright, you might be looking at that and thinking it looks fairly clean. Not only that, but the logic behind this pattern might seem to make sense. If the Container is the tool we're using to create objects like a factory, why not just pass it in? Seems like extra work for the same result to define specific dependencies to get injected! However, this way of thinking is a trap.
The biggest problem with the above pattern of passing in a Service Locator and then grabbing what you need out of it, when you need it, is that it's not clear what the dependencies of our class are!!! Now, sure, in a tiny example like we're using here... you can just look and see. But for a real usage scenario, all you'd be able to easily see is the container getting passed in. In order to figure out what the actual dependencies are, you'd have to read through all the methods and look at what's getting grabbed out! This is SUPER bad for having your code be understandable, and will make it much more likely you'll introduce breaking changes without realizing it.
We can refactor to a better version of the pattern like so:
class Example
{
// Dependencies
protected $mailer;
protected $logger;
protected $userFactory;
public function __construct($container)
{
$this->mailer = $container->mailer;
$this->logger = $container->logger;
$this->userFactory = $container->userFactory;
}
public function createUser($name)
{
$user = $userFactory->make($name);
$this->mailer->send('Welcome', $user);
$this->logger->log('A new user was created');
}
}
In this version, at least we're pulling out the services we need in the constructor, so in an interpreted language like PHP that let's us see the source code, we can more easily figure out the dependencies (that's ASSUMING nobody grabs additional stuff out outside the constructor... which is worrying door to have open). However, it still doesn't solve all the issues. First off, let's take a step back from the actual implementation and focus JUST on the interface for our example class. If you were shown the constructor signature for this class without the implementation, would you be able to reason anything about what it does or what it's dependencies are?
public function __construct($container)
^ That signature tells you nothing. Nothing! Regardless of the fact that it "works" because it's simple to look at the implemenation in PHP, doesn't make it a good idea. Furthermore, continuing with our line of thinking about the contracts between different software components and black boxes, by passing in a Service Locator you are giving that class access to EVERYTHING. It's like handing over the master key. Handing over that kind of power, but then expecting the class to only use the tiny bit it needs is BAD PRACTICE. Can you get away with it? Probably. Especially in a personal project where you're the only developer. But is a good idea? No.
Not only will you have to worry about that junior developer who ends up working on your app later down the road grabbing things out in the wrong places, it will also make reasoning about the app harder for you too! Passing in the dependencies explicitly forces you as the developer to think about the interfaces between your software components, who's job it is to do what, and what they need to do that job. Just arbitrarily passing around the power to do anything in your application runs the risk of creating spaghetti code.
And the last couple reasons I'll mention on why this is an anti-pattern are that:
- It creates a dependency on the container (a minor concern)
- It ruins the benefit of being able to substitute a dependency that we had by doing normal injection (more important)
On the first point, if you are just using the Container to handle injection for you from within your Controllers, then theoretically you could remove the Container and switch back to a plain factory implementation without having to change your class files at all. However, once you start grabbing things out of the Service Locator / Container from within your classes, now you've made your application much more dependent on it.
To the second point, a major benefit of dependency injection is the ability to swap dependencies out with other versions, something you lose if you use the Service Locator Pattern. If you wanted to substitute a dependency before running a test, you'd have to redefine the service's definition before running the test, which could be more of a hassle.
Avoiding the (anti)Pattern
General purpose info that's not Cora specific
So if you have a sharp eye for details, you might have noticed something that looks like a contradiction. In the previous section about basic injection using a Container, we injected our Container into our Controller, and then used it to create a new object within a method like so:
// GOOD EXAMPLE
class SomeController
{
protected $app; // Our Container
public function __construct($container)
{
// Container gets passed in.
$this->app = $container;
}
public function someMethod()
{
// Get a new Example object
$example = $this->app->example();
}
}
However, in the next section right after that when talking about the Service Locator Anti-Pattern, it's stated that injecting the container and grabbing resources out within methods is a terrible practice! This was the code shown as bad practice:
// BAD EXAMPLE
class Example
{
// Dependencies
protected $app;
public function __construct($container)
{
$this->app = $container;
}
public function createUser($name)
{
$user = $this->app->userFactory->make($name);
$this->app->mailer->send('Welcome', $user);
$this->app->logger->log('A new user was created');
}
}
The answer is it depends on the roles and responsibilities of the code in question.
First off, notice that the "BAD" example above is injection of the container into a class file belonging to our application, while the "GOOD" example is using the container in the context of a Controller. If you think about it, at SOME POINT, SOMEWHERE, actual objects have to get created before you can do dependency injection using them. Whether it's using a factory's make() method or grabbing a resource out of a Container, at some point somebody has to make those creation calls without having the objects handed to them.
Hypothetically, let's say we wanted to avoid grabbing things out of the Container from within a Controller method, and instead wanted to do dependency injection for our Controllers (SideNote: Cora actually has this capability). Normally, the Router in Cora maps a URL to a Controller method directly, such that the first line in the Controller method is the real "start" point in the application (obviously not counting the framework setup code). But let's say we create a new "Pre-Controller" logic layer, and have our router map to it rather than our Controller method directly.
This Pre-Controller now will handle making the calls to create the objects and then inject those dependencies into our Controller! Now our Controller is not tied down with those pesky dependencies! Haha, we're geniuses! Except wait, now our Pre-Controller isn't benefitting from injection! No worries, we'll just create a Pre-Pre-Controller and have it inject into the Pre-Controller and... Ok, you get the point. At some point the buck stops and someone has to make the creation calls without benefitting from injection.
Although there are different flavors of the MVC pattern, in the context of the Cora framework, a Controller's job is to take any user input, coordinate any calls to backend classes, and then return a response back to the user. A Controller should be kept "thin" such that it doesn't implement any of the logic that powers your app, it just coordinates between the user and the domain logic. In this sense, it's the Controller's role and responsibility to make the necessary creation calls and coordinate the high level logic flow into the app by handing off execution control. Which is why, combined with the fact that it handles user input and wouldn't typically be unit tested anyway, that it's perfectly legitimate for it to be using the Container.
On the other hand, the Example class would be a lower level of logic that should use dependency injection for the reasons already discussed.
Creating Abstract Factories
Info Specific to the Cora Framework
So we've established that it's best practice to only grab resources out of a Container from within a Controller. Any lower level classes should receive the dependencies they need through injection. But that leaves the question of how do you deal with classes that need to make objects? For example:
class A
{
public function foo()
{
$results = [];
while ($condition) {
$results[] = new Item();
}
}
}
Obviously we can't pass in "Item" as a dependency through the constructor, we need to create a variable number of Item objects. Passing in the Container itself is also a no-go, as we've discussed how that's a bad practice. The easy and proper solution is to again utilize a Factory like so:
class A
{
protected $itemFactory;
public function __construct($itemFactory)
{
$this->itemFactory = $itemFactory;
}
public function foo()
{
$results = [];
while ($condition) {
$results[] = $itemFactory->make();
}
}
}
Now the class has a dependency on an ItemFactory who's job it is to create Item objects (all good and proper). However, creating an ItemFactory class adds another file to our filesystem, and also potentially creates code duplication if we end up defining how to create an Item object in both our Container and our ItemFactory. The question we should be asking now is, if we are already defining how to instantiate an object in our Container, can we just reuse that same logic in a Factory? The answer is "yes", and the solution is Cora's AbstractFactory class.
An Abstract Factory works just like any other Factory, the difference being the type of object it makes can be dynamically defined. In our case, we want to create an instance of an AbstractFactory that uses our "Item" definition from the Container to define the type of objects it returns. Cora's Container class provides a "getFactory" method for this exact purpose. By calling getFactory and passing it the name of a resource, you'll be given back an AbstractFactory configured to return that type of object.
We can use our Dependency Injection Container to define "class A" so that it receives a factory for Item like so:
// Create a Container
$container = new \Cora\Container();
// Define "Item"
$container->{Item::class} = function($c) {
return new Item();
};
// Define "class A"
$container->{A::class} = function($c) {
return new A($c->getFactory(Item::class));
};
And of course using it, we know the Container handles the injection of the factory for us:
class SomeController
{
protected $app; // Our Container
public function __construct($container)
{
// Container gets passed in.
$this->app = $container;
}
public function someMethod()
{
// Get a new A object
$objectA = $this->app->{A::class};
}
}
Recap
So just to recap the issue of dynamically creating objects from within a class:
class A
{
public function foo()
{
$results = [];
while ($condition) {
$results[] = new B(new C()); // Option 1 - BAD, using "new" keyword in class.
$results[] = $this->app->B(); // Option 2 - BAD, using Service Locator pattern.
$results[] = $BFactory->make(); // Option 3 - GOOD. Factory making object from scratch.
}
}
}
Utilizing Repositories Instead
So one important clarification I think is needed to avoid confusion when utilizing Cora's ORM. Above we list out 3 options for making an object dynamically, with a Factory being the correct method of doing so. However, there is a 4th option which is also valid, which has to do with fetching an object through a Repository.
Just like a Factory's role is to create new objects, a Repository's role is to retrieve objects based off data fetched from a persistance layer (usually a database). Under the hood, a Repository will utilize a Factory to create objects out of the data that gets fetched. Why is this important? Because I don't want anyone getting confused about the roles of a Repository vs a Factory. Both return objects, a repository is just a little higher level of logic and is only concerned with creating objects from existing data, not new objects from scratch.
Grabbing objects from a Repository at runtime is perfectly acceptable. Although before using one in a loop, it is important to keep in mind each Repository call will be running a persistance layer (i.e. database) query/search - so keep performance in mind. But we could modify our "class A" dependency to utilize a Repository instead of a plain Factory by doing something like so:
class A
{
protected $itemRepo;
public function __construct($itemRepo)
{
$this->itemRepo = $itemRepo;
}
public function foo()
{
$results = $itemRepo->findAll($condition); // Option 4 - GOOD.
}
}
Creating Static Services
Info Specific to the Cora Framework
Cora by default creates a new object anytime a resource is requested from it. This is the opposite methodology as some other Containers such as Pimple. If you want a resource to always return the same object, you'll need to either assign a value explicitly or define the resource as a Singleton.
Define as Singleton:
In Container Setup:
<?php
$container = new \Cora\Container();
$container->singleton('singleUser', function($c, $email) {
return new \Models\User($email);
});
Result in Usage:
$obj1 = $repo->singleUser('Johnny@gmail.com');
$obj2 = $repo->singleUser('Bobby@gmail.com');
$obj3 = $repo->singleUser;
echo $obj1->email."<br>"; // Outputs "Johnny@gmail.com"
echo $obj2->email."<br>"; // Outputs "Johnny@gmail.com"
echo $obj3->email."<br>"; // Outputs "Johnny@gmail.com"
Resources can be fetched out of Cora's Container either as a resource offset or as a method call. In the above example we use both. Because the closure requires an email for the User object, the first two times we fetch an object we pass along an email address. The last time we forgo passing any runtime input, which if this weren't defined as a Singleton would cause an exception, but in this case doesn't matter.
Let's explain what's going on here. When defining resources in the Container using a closure, no resources are created until you ask for them. So when the first call:
$obj1 = $repo->singleUser('Johnny@gmail.com');
happens, there's no object in the container for that resource. It creates the resource as requested, and before handing it back checks if the resource was defined as a Singleton. If yes, then it stores the object created for return on any future requests for that resource. This is why the result of the above code is three pointers to the same object with an email of "Johnny@gmail.com".
As a sidenote, I'd recommend avoiding using Singletons for objects that require runtime input like the above example. The fact that you can ask for a User object and pass in an email only to have that email disregarded as happened with "Bobby@gmail.com" above could be a point of confusion for developers. I may make that throw an exception in the future... so you've been warned.
Assign Explicit Value:
In Container Setup:
<?php
$container = new \Cora\Container();
$container->singleUser = new \Models\User('BobbyJones@gmail.com');
Result in Usage:
$obj1 = $repo->singleUser('Johnny@gmail.com');
$obj2 = $repo->singleUser;
echo $obj1->email."<br>"; // Outputs "BobbyJones@gmail.com"
echo $obj2->email."<br>"; // Outputs "BobbyJones@gmail.com"
In this case, we assign an object to the "singleUser" offset of the Container directly with no closures involved. When asked for that resource later on, the Container will return the object already created.
Using Runtime Inputs
Info Specific to the Cora Framework
This has been covered indirectly many times throughout examples in this article, but let's quickly cover how to pass runtime inputs to a Container resource. A closure that defines a Container resource always must accept a reference to the Container as the first argument. In all our examples this Container reference is denoted by the variable "c":
$container->{Example::class} = function($c) {
return new Example($c->{Mailer::class});
};
$container->example2 = function($c) {
return new Example2();
};
As seen above, it's using this "$c" Container reference within the closure that allows defining the dependencies that need to be passed in to the object getting created. However, it's important to understand that this self reference to the Container will be given to the closure AUTOMATICALLY. You don't need to do anything except keep the first argument to the closure reserved for it.
Which brings us to passing runtime input to the closure. To pass runtime input, just add new variables after the "$c" like so:
$container->{Example::class} = function($c, $type, $amount) {
return new Example($type, $c->{Mailer::class}, $amount);
};
$container->example2 = function($c, $name) {
return new Example2($name);
};
Then to get an instance of our Example classes we'd do:
$example = $container->{Example::class}('MyType', 50);
$example2 = $container->example2('someName');
Notice we're not passing passing any Container reference, that get's handled for us. You just pass along any custom parameters you've defined.
Sub-Containers
Info Specific to the Cora Framework
There may be times where you want to better organize the resources in your Container or modify the definitions of a certain type of resource in a loop. To that end, Containers have the capability to be nested such that any children still have access to the resources in the parent Container, even though they can be interated over separately.
Here's an example:
<?php
// Create main Container
$container = new \Cora\Container();
// Define some resources in main container
$container->load = function($c) {
return new \Cora\App\Load();
};
$container->mailer = function($c) {
return new \Cora\Mailer($c->PHPMailer());
};
$container->PHPMailer = function($c) {
return new \PHPMailer;
};
// Define sub-Container
$container->listeners = new \Cora\Container($container);
// Define sub-sub-Container
$container->listeners->emails = new \Cora\Container($container->listeners);
// Tell the container to return the listeners as closures.
$container->listeners->emails->returnClosure(true);
// Define a resource in the sub-sub-Container. Uses dependencies defined in the top level Container.
$container->listeners->emails->sendPasswordResetToken = function($c) {
return new \Listeners\Emails\SendPasswordResetToken($c->mailer, $c->load);
};
In the above example we define two sub-containers. The first, "listeners" is a child of the main Container, and the second, "emails" is a child of listeners. Each time we pass in the parent Container in the constructor. Finally, we see in the "sendPasswordResetToken" definition that we can use resources defined in any parent Container up the chain. To get an instance of "sendPasswordResetToken" you do:
$listener = $container->listeners->emails->sendPasswordResetToken;
If you wanted to perform any actions on one of these sub-groupings of resources you could do so like this:
/**
* Loop through all our email listeners and stub them all.
*/
foreach ($this->app->listeners->emails as $listener => $v) {
$this->app->listeners->emails->$listener = function($c) {
return $c->PHPUnit->getMockBuilder('\Cora\Listener')->getMock();
};
}
One other thing we were able to do with a sub-container is define that all resources defined within that Container should return the closure directly, rather than resolve it into an object. We did that with the line:
// Tell the container to return the listeners as closures.
$container->listeners->emails->returnClosure(true);