Autowire a standalone Symfony console app with the DI container

I was working on a project for a client with a huge, old-school inventory system that had to sync with WooCommerce. The only way to get data out of it was a daily CSV dump. Total mess. We decided the only sane path was a standalone CLI tool, something we could drop on a cron job and let run. The Symfony console component is my go-to for this, but as the tool grew, so did the complexity.

My first pass at the commands was straightforward. Just instantiate whatever services I needed in the constructor. But by the time I added the third command for handling product variations, the dependency chain was getting ugly. I was passing the same half-dozen objects around everywhere. It was fragile and a pain to test. This is a classic sign you need proper Symfony console autowiring.

Wiring dependencies by hand gets painful fast. Change one constructor and you have to fix it in ten other places. The cleaner option is to let Symfony’s dependency injection container do that work for you.

Setting up Symfony console autowiring

Most tutorials on standalone Symfony console apps skip dependency injection, which is a mistake. The reason to pull in a framework component is to actually use what it offers. First, you need the right Composer packages.

{
    "require": {
        "symfony/config": "^5.4",
        "symfony/console": "^5.4",
        "symfony/dependency-injection": "^5.4",
        "symfony/yaml": "^5.4"
    }
}

Next you need a configuration file that tells the container how to behave. I usually make a config directory with a services.yml file inside. In it, you tell the container to autowire everything by default and to tag all your console commands.

services:
  _defaults:
    autowire: true
    autoconfigure: true

  App\:
    resource: '../src/*'

  App\Application:
    public: true
    arguments:
      - !tagged console.command

  _instanceof:
    Symfony\Component\Console\Command\Command:
      tags: ['console.command']

The _instanceof section applies the console.command tag to any class that extends the base Symfony Command. Then you tell the main Application class to accept any service tagged with console.command as an argument. The container finds all of them and injects them for you, so you skip the manual $application->add(new MyCommand(...)) calls. This setup borrows heavily from a post on carlalexander.ca.

So, what’s the point?

Yeah, it’s a bit of boilerplate. You create the config file and the main executable once, and after that, adding a new command is trivial:

  • Create a new command class that extends Symfony\Component\Console\Command\Command.
  • Type-hint your dependencies in the constructor.
  • The container automatically finds the class, injects its dependencies, and adds it to the application. Done.

This saves you a maintenance headache later. The code is cleaner and decoupled, and it’s far easier to test because you can mock dependencies freely. That is how you end up with a maintainable CLI tool rather than a throwaway script.

This stuff gets complicated fast. If you’re tired of debugging someone else’s mess and you just want your site to work, drop my team a line. We’ve probably seen it before.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.