Improve your factories in Symfony
Tags: solid, symfony, factory, container
Published: 2022-03-03 14:12:27

Sometimes we need to retrieve some kind of handler class that depending on an attribute is different.

Thanks to [Factory pattern](https://designpatternsphp.readthedocs.io/en/latest/Creational/StaticFactory/README.html) we can solve this scenario in an elegant way... but we can improve it through the Service Container.

In this case we are going to see how to do it with Symfony Service Container, but this could be achieved by any other Service Container implementation.

## Example scenario

Let's imagine a (more or less) real situation and work with it: We have an endpoint that depending on payload parameter "channel", we will send a "message" through different possible communication channels (email, sms, letter...).

To make things easier, we will do not cover additional parameters as `to` or `phone_number` etc.

Example payload:

```json
{
  "channel": "email",
  "message": "Hello world"
}
```

### Using a simple factory

The easiest way to solve this is creating a factory class that solves that logic:

```php
final class ChannelFactory
{
    public static function create(string $type): ChannelInterface
    {
      switch($type) {
        case 'email':
          return new EmailChannelHandler();
        break;
        case 'sms':
          return new SmsChannelHandler();
        break;
      }

      throw new InvalidArgumentException('Unknown channel given');
    }
}
```

**Pros**
- Easy to read if only 2 or 3 different types

**Cons**
- Difficult to read if lot of types
- You have to add code to add a new type

### Using a simple factory through service container

Let's make it more scalable and maintainable!

We can have an array of compatible handlers and decide which one is the correct. You need to add the related type to the Channel handler with some method like `getType()`.

```php
final class ChannelFactory
{
    public function __construct(private ChannelInterface ...$channels)
    {
    }

    public function create(string $type): ChannelInterface
    {
      foreach($this->channels as $channel) {
        if ($type === $channel::getType()) {
          return new $channel;
        }
      }

      throw new InvalidArgumentException('Unknown channel given');
    }
}
```

And now, in our `services.yml` file we initialize it as follows:

```yaml
App\ChannelFactory:
    arguments:
      - App\EmailChannelHandler
      - App\SmsChannelHandler
```

**Pros**
- You can add as many handlers as you want without changing php code
- Easy to add new handlers

**Cons**
- Maybe a little bit less easy to read for a noob

## Conclusion

Service Container is a pattern really powerful and nowadays we have some amazing implementations that can help us a lot.

It's worth to use them and give to our application more flexibility even adding a little complexity for newcomers.

### Updated! (2022-03-07)

As [Valentin Silvestre](https://dev.to/vasilvestre) suggested, there is a better way to set up the container to make it even more automagically:

```yaml
_instanceof:
        App\ChannelHandlerInterface:
            tags: ['app.channel_handler']

App\ChannelFactory:
    arguments:
      - !tagged_iterator app.channel_handler
```

This is an awesome way to tell to service container that all implementations of `ChannelHandlerInterface` should be loaded in our factory, so we don't need to write each one in a list.

Thanks Valentin!