How to autowire a standalone Symfony console application
Tags: commandlineinterface, dependencyinjection, symfony, symfonycomponents
Published: 2020-12-11 03:47:46

---
title: How to autowire a standalone Symfony console application
published: true
date: 2020-12-10 07:34:42 UTC
tags: CommandlineInterface,DependencyInjection,Symfony,SymfonyComponents
canonical_url: https://carlalexander.ca/symfony-console-application-autowire/
---

Working with the [Symfony console component](https://symfony.com/doc/current/components/console.html) has always been a delightful experience. It makes it super easy to build a [console application](https://en.wikipedia.org/wiki/Console_application). You might need one while working with a framework. (Like [Symfony](https://symfony.com/) or [Laravel](https://laravel.com/)) Or you might just need to build a standalone console application.

That said, there are differences between building a standalone console application and building one with a framework. The biggest one is the use of [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). Most standalone Symfony console applications that you can find out there don't use it.

That's a shame because the [Symfony dependency injection component](https://symfony.com/doc/current/components/dependency_injection.html) is full of awesome features. For the [Ymir](https://ymirapp.com) console application, I decided to look at how to build a console application using it. It's made building console applications even better.

<!--more-->

# A quick overview of the Symfony dependency injection component

Let's take a brief look at how to use the Symfony dependency injection component before integrating it. Central to the component is the [service container](https://symfony.com/doc/current/service_container.html). To build the service container, you need to use the `ContainerBuilder` class.

Now, you can manually build the container by defining parameters and services yourself. That said, it's much more common to use the [Symfony config component](https://symfony.com/doc/current/components/config.html) to do it. The component lets you use YAML (with the help of the [YAML component](https://symfony.com/doc/current/components/yaml.html), XML or plain PHP files to configure the container.

This is what we're trying to do in this article. We want to configure a dependency injection container using a YAML file. (Feel free to use the file type that you prefer!) And then we want to compile the container and start the application using it.

```php
$container->compile();

$container->get(Application::class)->run();
```

Above is all the code that you'd need to start the console application. No need to add any commands or deal with any dependencies. The configured container would have everything wired and ready to go for you.

# Composer dependencies

With this small intro out of the way, Let's start with Composer dependencies that we'll need for this console application. We've mentioned them all already.

```json
{
    "require": {
        "symfony/config": "^4.4",
        "symfony/console": "^4.4",
        "symfony/dependency-injection": "^4.4",
        "symfony/yaml": "^4.4"
    }
}
```

Above is the relevant section of the `composer.json` file for these dependencies. We need four Symfony components. These are the config, console, dependency injection and YAML components. 

As mentioned earlier, the YAML component is optional. If you want to use PHP or XML to define your services, you don't need it. But for this article, we will use YAML for the services configuration so the dependency is necessary.

Another thing is that we're going to use [version 4.4](https://symfony.com/releases/4.4) of the Symfony components. This is current [long-term support](https://en.wikipedia.org/wiki/Long-term_support) version of Symfony. So the code that you'll see will be good until the end of 2023. (And possibly longer.)

# Creating the executable

Once the dependencies added and downloaded, we can start working on the console application. The primary interaction point with it is an [executable file](https://en.wikipedia.org/wiki/Executable). Below is the base executable file that we're going to use for our console application:

```php
#!/usr/bin/env php
<?php

require __DIR__.'/vendor/autoload.php';
```

Our PHP script starts with `#!` which is known as a [hashbang](https://en.wikipedia.org/wiki/Shebang_(Unix)). A hashbang tells a [Unix-like](https://en.wikipedia.org/wiki/Unix-like) operating system that it should interpret a text file as an executable file. (You also need to make it executable using `chmod +x`.) Following the hashbang is the interpreter that we want Unix to use.

Here, we want that interpreter to be PHP. We could use `/usr/bin/php`, but instead we used `/usr/bin/env php`. The reason to use [`env`](https://en.wikipedia.org/wiki/Env) is that the script will not work if PHP isn't in the `/usr/bin` directory. `env` will search for the `php` executable and then launch it. 

`env` is also useful if you have multiple PHP interpreters installed. This is especially common in local development. In that scenario, `env` will always pick the default PHP interpreter that you've chosen.

After the hashbang, the rest of our executable looks like a regular PHP script. We have the `<?php` opening tag. We then [require](https://www.php.net/manual/en/function.require.php) the autoload file generated by Composer.

## Adding the container

Now that we have our base executable, the next step is to add the dependency injection container. We want to configure and compile the container in the executable before running the console application. Let's look at what that looks like.

```php
#!/usr/bin/env php
<?php

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

require __DIR__.'/vendor/autoload.php';

$container = new ContainerBuilder();

$loader = new YamlFileLoader($container, new FileLocator());
$loader->load(__DIR__.'/config/services.yml');

$container->compile();
```

We first start by instantiating a `ContainerBuilder` object. We then use that `ContainerBuilder` object to create a `YamlFileLoader` object. This is the configuration loader that we're going to use to configure our dependency injection container.

The `YamlFileLoader` object will load the `services.yml` file found in the `config` directory. The directory and file names are a personal choice. You can name the YAML file whatever you want and put it wherever you want really. 

After loading it, we call the `compiler` method. This compiles the `ContainerBuilder` object and turns it into a `Container` object. You cannot use the container until you've done this.

## Adding the base dependency injection container configuration file

Now, you could also load multiple configuration files if you wanted. But for this article, we're just going to use `services.yml`. Here's what it contains so far.

```yml
services:
  _defaults:
    autowire: true
```

The `_defaults` section is where you define the default configuration options for the services in your container. The most important one is `autowire` which you want to set to `true`. This is what turns on the amazing [autowiring feature](https://symfony.com/doc/current/service_container/autowiring.html). 

When `autowire` is `true`, the container will take over the handling of the dependencies for your services. It'll read the type hints of the constructor of each service. It'll then pass it the right dependencies when it creates the object the first time. (It's a really magical thing!)

# Creating the Application class

Now that we have our compiled `Container` object, we can move on to our base `Application` class. You can see it below extending the Symfony `Application` class. We renamed it to `BaseApplication` to prevent a naming conflict.

```php
namespace Console;

use Symfony\Component\Console\Application as BaseApplication;

class Application extends BaseApplication
{
    // ...
}
```

Empty like this, a console application doesn't do much. If you ran the `list` command for it, you'd get this:

<img src="https://carlalexander.ca/uploads/2020/12/basic-list-command.png" alt="" width="1698" height="1144" class="aligncenter size-full wp-image-2930" />

There are only two available commands: `help` and `list`. This is how all Symfony console applications start out with. So what we really need to do now is add commands.

## Adding commands to the application

Adding commands to an application is straightforward. All that you have to do is call the `add` method with the Symfony `Command` object you want to add. In our `Application` class, this could look something like this:

```php
namespace Console;

use Symfony\Component\Console\Application as BaseApplication;

class Application extends BaseApplication
{
    /**
     * Constructor.
     */
    public function __construct(array $commands = [])
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }
}
``` 

As you can see above, we added a constructor to the `Application` class. The constructor has a single parameter called `commands`. We use it to pass an array of `Command` objects to the constructor. We then loop through each one and add them using the  `add` method.

Right now, we'd have to supply that array of `Command` objects to the constructor ourselves. But what we really want is for the dependency injection container to handle all that for us. It should find all the `Command` objects and pass them to the `Application` constructor.

### Creating a service tag for command objects

To do that, we're going to use a [service tag](https://symfony.com/doc/current/service_container/tags.html) to identify all our `Command` objects. We can do that easily using the `_instanceof` option. Here's what it looks like in the `services.yml` file.

```yml
services:
  _defaults:
    autowire: true

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

We added the `_instanceof` section below the `_default` one. Under that section, you want to add the [fully qualified name](https://www.php.net/manual/en/language.namespaces.rules.php) of the class or interface that we want to add a tag to. For this, we want to use the fully qualified of the Symfony `Command` class because it doesn't have an interface. That fully qualified name is `Symfony\Component\Console\Command\Command`.

Below that, we have the `tags` that we want all the Symfony `Command` objects to have. We just have one for this. It's `command`.

### Using the service tag

Next, we need to configure the dependency injection container. Right now, it doesn't know that we want it to pass all the `Command` objects to the `Application` class constructor. To tell it to use our service tag, we have to modify the `services.yml`  file like so:

```yml
services:
  _defaults:
    autowire: true

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

  Console\Application:
    public: true
    arguments:
      - !tagged command
```

Above you can see that we added the fully qualified name of the `Application` class as a new section. Below, we added the `arguments` section which lets you define the arguments to pass to a class constructor. We then used `!tagged command` as the first argument.

At this point, if you try to run your console application, you'll get an error like this:

```
PHP Fatal error:  Uncaught TypeError: Argument 1 passed to Console\Application::__construct() must be of the type array, object given
```

That's because the dependency injection container doesn't pass an array to our `Application` class constructor. It passes an object implementing the [`Traversable`](https://www.php.net/manual/en/class.traversable.php) interface. This is a built-in PHP interface that lets an object work with a [`foreach`](https://www.php.net/manual/en/control-structures.foreach.php) loop.

```php
namespace Console;

use Symfony\Component\Console\Application as BaseApplication;

class Application extends BaseApplication
{
    /**
     * Constructor.
     */
    public function __construct(iterable $commands = [])
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }
}
```

To solve this issue, we need to change the type hint of the `Application` class constructor. If you want, you can use the `Traversable` interface. A better option is the [`iterable`](https://www.php.net/manual/en/language.types.iterable.php) pseudo-type which is what the code above uses. But regardless of the one you use, once you update the constructor, the error will be gone.

# Adding the application to the executable

At this point, you have everything that you need to run a console application using the dependency injection container. We just need to update the executable so we get the `Application` object from the container. You can see that below.

```php
#!/usr/bin/env php
<?php

use Console\Application;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

require __DIR__.'/vendor/autoload.php';

$container = new ContainerBuilder();

$loader = new YamlFileLoader($container, new FileLocator());
$loader->load(__DIR__.'/config/services.yml');

$container->compile();

exit($container->get(Application::class)->run());
```

At the top, we added the `use` statement to import the `Application` class. But really, what we care about at the bottom. It's the call to the [`exit`](https://www.php.net/manual/en/function.exit.php) function. 

The `exit` function just terminated the PHP script using the given `status`. The `status` just comes from getting the `Application` object from the container. And then we call the `run` method which runs the console application and terminates.

To see a fully working version of a console application using this technique, I invite you to look at the code of the [Ymir console application on GitHub](https://github.com/ymirapp/cli).

[Source](https://carlalexander.ca/symfony-console-application-autowire/)