I am pondering a few different approaches here and would really appreciate some input! I am considering the two choices below. There are 2 things going on there I have questions on.
Is it preferred to inject the dependencies into the constructor of the main "container" class, or to instead create new instances inside the container class?
In the second example, the class' dependencies are injected via constructor and then maintained within via a property of the class. Then when the methods (route(), render()) are called, the dependencies are called from within. I began with this approach, but am now favoring something more along the lines of the first example. I think the first example is preferable, but are there any benefits to using the DI approach in the second example?
There really is no need to store anything in the class as a property. I can probably rearrange everything to use that technique without much trouble, and I think I like it better. This way I can also move all of the of work out of the constructors, and simply access everything via method later. Am I on the right track here?
class App
{
private $config;
private $router;
private $renderer;
public function __construct(IConfig $config, IRouter $router, IRenderer $renderer)
{
$this->config = $config;
$this->router = $router;
$this->renderer = $renderer;
$this->run();
}
public function run()
{
$data = $this->router->route(new Request, $config->routes);
$this->renderer->render($data);
}
}
class App
{
private $config;
private $router;
private $renderer;
public function __construct()
{
$this->config = new Config;
$this->run();
}
public function run()
{
$this->router = new Router(new Request, $config->routes);
$this->router->route();
$this->renderer = new Renderer($this->router->getData());
$this->renderer->render();
}
}
Dependency Injection is a technique that facilitates loosely coupled object-oriented software systems. It is closely related to the Dependency Inversion Principle. In simple systems, references to collaborating objects are made directly within classes that need to refer to them.
Dependency Injection allows you to insert dependencies from outside the class. This feature of DI decreases class coupling and increases code reusability.
Struggling with dependency injection in PHP? Our guide breaks it down - you'll be using dependency injection in minutes. One popular and well known methodology of software development is called dependency injection, which helps facilitate the flow ensuring your software always has access to the tools it needs.
In order to over come from the problems of tight coupling between objects, spring framework uses dependency injection mechanism with the help of POJO/POJI model and through dependency injection its possible to achieve loose coupling.
It is better to inject dependencies into the constructor.
Creating instances within the constructor creates a tight coupling between the two classes. With a constructor with a clear signature like
public function __construct(IConfig $config, IRouter $router, IRenderer $renderer)
I can immediately tell what this component needs to do it's job.
Given a constructor like
public function __construct();
There is no clue what the component needs to function. It creates a strong coupling to specific implementations of your each your router, your request and to your renderer, none of which are apparent until you dig down into the guts of your class.
In summary the first approach is well documented, extendable and testable. the second approach is opaque, highly coupled, and not easily testable.
While Orangepill makes a good point, I thought I'd chip in, too. I tend to define my constructors with a clear constructor, too, but I don't expect the required objects to be passed when creating an instance.
Sometimes, you create an instance that retrieves data either from a DB, or some sort of Http request. In your case, the first example expects three dependencies to be passed, but who's to say that you'll always need all three of them?
Enter Lazy-Loading. The code sample below is quite lengthy, but it is (IMO) well worth looking into. If I use a service, I don't want to load all dependancies unless I'm sure I'll be using them. That's why I defined the constructor so that I can create an instance in either one of the following ways:
$foo = new MyService($configObj);
$bar = new MyService($configObj, null, $dbObj);//don't load curl (yet)
$baz = new MyService($configObj, $curlObj);//don't load db (yet)
If I wanted to run some test, I can still inject the dependencies when constructing my instance, or I can rely on a test-config object or I could use the setDb
and setCurl
methods, too:
$foo->setCurl($testCurl);
Sticking to the first way of constructing the instance, I can safely say that, if I only invoke the getViaCurl
method, the Db
class will never be loaded.
The getViaDb
method is a bit more elaborate (as is the getDb
method). I don't recommend you working with methods like that, but it's just to show you how flexible this approach can be. I can pass an array of parameters to the getViaDb
method, which can contain a custom connection. I can also pass a boolean that'll control what I do with that connection (use it for just this one call, or assign the connection to the MyService
instance.
I hope this isn't too unclear, but I am rather tired, so I'm not all too good at explaining this stuff ATM.
Here's the code, anyway... it should be pretty self explanatory.
class MyService
{
private $curl = null;
private $db = null;
private $conf = null;
public function __construct(Config $configObj, Curl $curlObj = null, Db $dbObj = null)
{
$this->conf = $configObj;//you'll see why I do need this in a minute
$this->curl = $curlObj;//might be null
$this->db = $dbObj;
}
public function getViaCurl(Something $useful)
{
$curl = $this->getCurl();//<-- this is where the magic happens
return $curl->request($useful);
}
public function getViaDb(array $params)
{
if (isset($params['custom']))
{
$db = $this->getDb($params['custom'], $params['switch']);
}
else
{//default
$db = $this->getDb();
}
return $db->query($params['request']);
}
public function getCurl()
{//return current Curl, or load default if none set
if ($this->curl === null)
{//fallback to default from $this->conf
$this->curl = new Curl($this->conf->getSection('CurlConf'));
}
return $this->curl;
}
public function setCurl(Curl $curlObj)
{//inject after instance is created here
if ($this->curl instanceof Curl)
{//close current connection
$this->curl->close();
}
$this->curl = $curlObj;
}
public function setDb(Db $dbObj)
{
if ($this->db instanceof Db)
{//commit & close
$this->db->commit();
$this->db->close();
}
$this->db = $dbObj;
}
//more elaborate, even:
public function getDb(Db $custom = null, $switch = false)
{
if ($custom && !!$swith === true)
{
$this->setDb($custom);
return $this->db;
}
if ($custom)
{//use custom Db, only this one time
return $custom;
}
if ($this->db === null)
{
$this->db = new Db($this->conf->getSection('Db'));
}
return $this->db;
}
}
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