Kdyby\Console: Dependecy Injection? Why? Or why not?

4 years ago

Honza Kuchař
Backer | 1650
+
0
-

Hi! I would like to ask, why or why not it is possible or not possible recommended to use DI in Console commands?

This example from documentation contains magic reference to Models\NewsletterSender. (you are not able to figure out required dependencies from class header)

namespace App\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class SendNewslettersCommand extends Command
{
    protected function configure()
    {
        $this->setName('app:newsletter')
            ->setDescription('Sends the newsletter');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $newsletterSender = $this->getHelper('container')->getByType('Models\NewsletterSender');

        try {
            $newsletterSender->sendNewsletters();
            $output->writeLn('Newsletter sended');
            return 0; // zero return code means everything is ok

        } catch (\Nette\Mail\SmtpException $e) {
            $output->writeLn('<error>' . $e->getMessage() . '</error>');
            return 1; // non-zero return code means error
        }
    }
}

Why something like this is not supported not the best practice?

namespace App\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class SendNewslettersCommand extends Command
{
    /** @var Models\NewsletterSender */
    private $sender;

    public function __construct(Models\NewsletterSender $sender) {
        $this->sender = $sender;
    }

    protected function configure()
    {
        $this->setName('app:newsletter')
            ->setDescription('Sends the newsletter');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $newsletterSender = $this->sender;

        try {
            $newsletterSender->sendNewsletters();
            $output->writeLn('Newsletter sended');
            return 0; // zero return code means everything is ok

        } catch (\Nette\Mail\SmtpException $e) {
            $output->writeLn('<error>' . $e->getMessage() . '</error>');
            return 1; // non-zero return code means error
        }
    }
}

Using DI in Console commands has

  • Upsides:
    • more clear dependencies
  • Downsides:
    • even when you want to print help, you need to instantiate all dependencies
      • this can be solved by using inject*() methods, which can be injected before execute() method (too much magic?)
        • not that big issue; printing help is not business critical use-case :)
      • or using factories/accessors for complex classes

cc @FilipProcházka @Aurielle

4 years ago

Tomáš Votruba
Moderator | 1154
+
0
-

What happens when you use constructor? I use it this way with no obstacles.

4 years ago

David Matějka
Moderator | 5896
+
+4
-

this can be solved by using inject*() methods, which can be injected before execute() method (too much magic?)

https://github.com/…lication.php#L183 ;)

@TomášVotruba

even when you want to print help, you need to instantiate all dependencies

4 years ago

Honza Kuchař
Backer | 1650
+
0
-

Tomáš Votruba wrote:

What happens when you use constructor? I use it this way with no obstacles.

Thanks for response! I wansn't clear enough. I was asking for best practices and why this example is in documentation. (updated question)

4 years ago

Tomáš Votruba
Moderator | 1154
+
0
-

@HonzaMarek I'd only say profile. It depends on you application. If you have over 1000 commands, your app might perform slower with ctor injection. If you have 5 commands, it probably won't matter. There is no “best practice” in these matters.

That's why I wrote “I use it with no obstacles”.