I am writing some reusable library, which contains few classes. One of them needs to have a dependency, because of some more complex logic and I want to delegate the responsibility of that class somewhere else (another class).
I don't want to create a bundle e.g. Symfony Bundle which could handle my dependency injection and provide a simple way to integrate it with a client code. My goal is to provide reusable and framework independent solution.
I am using composer and I've read about DI containers like php-di. There is a demo application example by php-di, but it doesn't fit into my requirements.
Fragment of library code
<?php
class W3CAnalyzer implements WebStandardAnalyzer
{
private $httpClient;
public function __construct(HttpClient $httpClient)
{
$this->httpClient = $httpClient;
}
public function analyze(string $url): WC3AnalysisMetaData
{
$siteContent = $this->httpClient->getContent($url);
//further logic there
}
}
As you can see W3CAnalyzer
contains the dependency HttpClient
class.
I will somehow configure a DI container to register and resolve that dependency.
Sample client code
<?php
class WebStandardController
{
private $webStandardAnalyzer;
public function __construct(WebStandardAnalyzer $webStandardAnalyzer)
{
$this->webStandardAnalyzer = $webStandardAnalyzer;
}
}
In client project, programmer will register W3CAnalyzer
to be injected into controller as WebStandardAnalyzer
using his DI configurations (it doesn't really matter how).
If I use some DI configuration and container in my library internally, how can I integrate this configuration with the client code, so there will be a possibility to register library class and all of its dependencies would have been resolved then? I don't know yet, how I will organize DI inside my library (this is actually the first part of the problem), I want to somehow use the php-di library mentioned above.
To summarize, how to organize DI inside non-framework reusable library, so that library could be used inside every other project (no matter which framework is used) and its (that library) dependencies would also be resolved during runtime of that project.
Thank you!
Enterprise Library is a good example of a library that really takes advantage of a dependency injection container without being hard coupled to one.
Types of DI There are three main styles of dependency injection, according to Fowler: Constructor Injection (also known as Type 3), Setter Injection (also known as Type 2), and Interface Injection (also known as Type 1).
There are three types of dependency injection — constructor injection, method injection, and property injection.
class W3CAnalyzerFactory
{
public function build()
{
$Subject = new W3CAnalyzer(new HttpClient);
return $Subject
}
}
The above is how I make use of the Factory pattern, and had great success with it. If new HttpClient
is not sufficient for your code (that is, it needs to be built, too), then do the following:
class HttpClientFactory
{
public function build()
{
//modify this to actually construct the HttpClient object, with
//constructor and/or setter injections
$Subject = new HttpClient();
return $Subject
}
}
Then, your W3CAnalyzerFactory
will look like:
class W3CAnalyzerFactory
{
public function build()
{
$Subject = new W3CAnalyzer(
(new HttpClientFactory)
->build()
);
return $Subject
}
}
Then, someone using your library can do something like the following:
$WebStandardController = new WebStandardController(
(new W3CAnalyzerFactory())
->build()
);
Or, however they want using their own DI setup. As you said, it doesn't matter how they inject it, just that they can using the W3CAnalyzerFactory
class, and that everything is then ready to go.
Tip: For all objects in my library and/or application I always made a factory class, even if there was nothing injected. It feels wordy at first, but has a couple benefits.
HttpClient
) so that it DOES now need to have something injected, then everything that uses HttpClientFactory
is already done, and you don't need go through and replace all your new HttpClient()
lines with (new HttpClientFactory)->build()
.new HttpClient
or (new HttpClientFactory)->build()
? Don't think about it, just use the factory.)Creating these factories when you create the implementations is what I recommend, as it gets it out of the way and doesn't require you to come back later and try to think of how to string things together.
I think this approach is the simplest for dependency injection when you don't want to, or can't use a container to assist you.
Sidenote: The reason I assign the object to $Subject
and then return $Subject
a separate line instead of just doing return new Whatever...
is because I tend to prefer setter injection, which means I need to do things to the object after it is constructed. You do what works best for your library and/or application though. The choice of the word "subject" as the variable name comes from calling my objects in my unit tests $SUT
for "Subject Under Test". These aren't under test, so I just called them $Subject
to have a nice consistent name that helps me easily see what object is the one being constructed. Again, that habit of mine is totally optional.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With