Making a symfony third party bundle extensible
Tags: symfony, php, extensibility
Published: 2023-02-05 10:01:37

When developing an external component which can be used in other projects, we would want to make it extensible so other developers can add their own capabilities.

In this post I would like to share how to drive it when developing a [symfony third party bundle](https://symfony.com/doc/current/bundles/best_practices.html). This is a short example and the idea can be extrapolated to other languages or frameworks.

Let's imagine we are developing a bundle which allows us to send notifications by using three channels: _Email_, _Push notification_ and _sms_. Our bundle define an interface which looks like this:

```php
   interface NotificationInterface {
      public function sendNotification(array $data): bool;
      public function getName(): string;
   }
```
Each of our notifications handlers will look like this:

```php
   class EmailNotificationHandler implements NotificationInterface {

       public function sendNotification(array $data): void
       {
        /* handler email notification. Throw an exception in 
        case on error */
       }

       public function getName(): string
       {
          return 'email';
       }
   }
```

The other ones (sms and push) will look as email one but its _sendNotification_ implementation would be different.

Now, It's time to tell our bundle to tag all services which implements _NofiticationInterface_ properly. To do it, we need to register it in our extension class (under _DependencyInjection_ folder)

```php
  class MyBundleExtension extends Extension
  {

    /**
     * @throws \Exception
     */
      public function load(array $configs, ContainerBuilder $container)
    {
        // ... load services from file (services.xml, services.yaml or services.php)

        $container->registerForAutoconfiguration(NotificationInterface::class)->addTag('my_bundle.notification');

        // other loads ....
    }
  }
```

Now, in our project, we can use symfony _tagged_iterator_ shortcut to get an [iterable](https://www.php.net/manual/es/language.types.iterable.php) holding all notification handlers. We can do it simply by binding a variable in our _services.yaml_ file:

```yaml
   
   services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        bind:
            $notificationHandlers: !tagged_iterator my_bundle.notification

```
Now, if we inject _$notificationHandlers_ in any of our services we will have access to all notification handlers. If we would create a new notification handler, for instance, _TelegramNotificationHandler_, it would be tagged as the other ones since it would implement _NotificationInterface_ and it would be available in _$notificationHandlers_ iterable

With this, we follow the _open-closed principle_ since we can add other notification handlers without modifying the existing ones.

> Tagged iterators are really useful to create central repositories of services which are easily accesible. In my recently published book, I use them to create an accesible collection of api operations. If you want to know more, you can find the book [here](https://www.amazon.com/dp/B0D79KGL7P)