Reputation: 3461
I'm fairly new to dependency injection, and I've come upon a situation where I have a class which is basically just functional programming -- one method in it (createPayment
) requires 9 dependencies, and the other method (paypalApiEndpoint
) only requires 1 of those dependencies:
class Paypal
{
private $restAPIClient;
private $db;
private $logger;
private $paypalGateway;
private $paymentGateway;
private $ordersGateway;
private $usersGateway;
private $resourcesGateway;
private $configGateway;
public function __constructor($restAPIClient, $db, $logger, // ...6 more, each for the private vars)
{
$this->restAPIClient = $restAPIClient;
$this->db= $db;
$this->logger= $logger;
// ... 6 more
}
// this method uses all 9 dependencies from the constructor
public function createPaypalPayment()
{
// insert purchase info into `payment` db table. Requires $paymentGateway and $db
// make paypal API call. Requires $restAPIClient, $db, $paypalGateway, $logger
// based on API call results, update `orders`, `users`, `payment`, `resources`, and `config` db tables. Requires $db, $logger, $paymentGateway, $ordersGateway, $usersGateway, $resourcesGateway, $configGateway
}
// this method only requires 1 of the dependencies, $paypalGateway
public function paypalApiEndpoint()
{
// get a value from our db table `paypal`, and return it. Requires $paypalGateway
}
}
In many places throughout my codebase I only need to use the paypalApiEndpoint
method. The problem is, in order to use that method I have to first create 9 objects (the dependencies of class Paypal), then I can use those to create the Paypal object, and then finally I can use the simple paypalApiEndpoint
method. Needing to create 10 objects in order to this very simple method seems overboard.
Is there a better way?
Upvotes: 1
Views: 675
Reputation: 15361
You don't have to inject all your dependencies in the constructor. You can have setter injectors for optional parameters.
public function setRestAPIClient($restAPIClient) {
$this->restAPIClient = $restAPIClient;
};
In your example, paypalApiEndpoint is a good candidate to be turned into either a class constant and/or a static method that would then allow you to use it via:
class PayPal {
private static $paypalGateway = null;
public static setPaypalGateway($paypalGateway) {
self::paypalGateway = $paypalGateway;
}
public __construct(.... all params) {
self::paypalGateway = $paypalGateway;
$this->otherDependency = $otherDependency;
}
}
For static use you just have to call the static setter 1 time, then use as much as you want within script execution.
Paypal::setPaypalGateway($ppgateway);
$endpoint = Paypal::paypalApiEndpoint();
Last but not least, you might want to look into use of a DI Container Component.
Some DIC's now feature autowiring which can determine what they need to load at calltime using typehints.
There is a standard PSR standard for how DI Containers should work and Symfony2's DIC, Laravel's DIC and PHP-DI for example, all conform to those standards and can easily be used to handle class loading within your application.
As mentioned, PHP-DI is another DIC component you might want to look at as well.
Upvotes: 1
Reputation: 2115
// this method only requires 1 of the dependencies, $paypalGateway
public function paypalApiEndpoint()
{
// get a value from our db table `paypal`, and return it. Requires $paypalGateway
}
If the only requirement is the paypalGateway
, then move the implementation to the paypalGateway
class or an extension.
That doesn't mean you would have to remove the method from the Paypal
class:
public function paypalApiEndpoint()
{
return $this->paypalGateway->paypalApiEndpoint();
}
Further, your constructor has lots of parameters. I believe it would be better to have a Builder class with all the dependencies, using setters, that would be injected as an argument:
https://jlordiales.me/2012/12/13/the-builder-pattern-in-practice/
Or implement composites of related data reducing the parameters number:
https://refactoring.guru/smells/data-clumps
Taking into account that the Paypal
constructor has lots of parameters:
https://refactoring.guru/smells/long-parameter-list
If there are several unrelated data elements, sometimes you can merge them into a single parameter object via Introduce Parameter Object.
https://refactoring.guru/introduce-parameter-object
By consolidating parameters in a single class, you can also move the methods for handling this data there as well, freeing the other methods from this code.
(...) see whether there is any point in moving a part of the method (or sometimes even the whole method) to a parameter object class. If so, use Move Method or Extract Method.
This means that if you only need to invoke paypalApiEndpoint
, you may use only the paypalGateway
class for your purposes, with less dependancies than the Paypal
class.
Upvotes: 2