I've created a command like this, but I would like to refactor it to follow the DI principle:
public function handle()
{
$productChoice = $this->argument('product');
if ($productChoice == 1) {
$product = new ProductA();
} elseif ($productChoice == 2) {
$product = new ProductB();
} else {
$this->error('Invalid product choice. Use 1 for ProductA or 2 for ProductB.');
return;
}
if ($product instanceof ProductI) {
$this->info($product->details());
} else {
$this->error('The selected product does not implement the ProductI interface.');
}
}
As you can see, the command is dependent on productA and productB. And my question is how to refactor it.
Here are some thoughts but I'm not sure which one is better or if there is a better way:
1- Create factory class like the following and inject it to the command class
class ProductFactory
{
public function createProduct($productChoice)
{
switch ($productChoice) {
case 1:
return new ProductA();
case 2:
return new ProductB();
default:
return null;
}
}
}
2- Creating a Service provider like the following and use it in the command like $product = $this->app->make("Product{$productChoice}")
public function register()
{
$this->app->bind('Product1', function () {
return new ProductA();
});
$this->app->bind('Product2', function () {
return new ProductB();
});
}
What you need is a mapping from an integer to a class.
In that case, I would prefer the factory you suggested to keep it clean and simple, but with some small modifications to the creation method:
ProductFactory::createFromId($productChoice)
.choice
toid
(since choice is no property of the product itself).int
.ProductI
.match
statement instead ofswitch
.null
but throw an exception instead.You can also decide to return an
EmptyProduct
orDefaultProduct
that implementsProductI
when the id is invalid.As suggested by @matiaslauriti, you can also create a
createFromProductEnum
method in the factory when you are using PHP 8.1 or higher, like this:Then you can create the object with
ProductFactory::createFromProductEnum(ProductEnum::from($productChoice))
.