Skip to content

Kévin Dunglas

Founder of Les-Tilleuls.coop (worker-owned cooperative). Creator of API Platform, FrankenPHP, Mercure.rocks, Vulcain.rocks and of some Symfony components.

Menu
  • Talks
  • Resume
  • Sponsor me
  • Contact
Menu

New in Symfony 2.8/3.0: services autowiring

Posted on October 19, 2015October 20, 2015 by Kévin Dunglas

Symfony 10 years

Symfony 3.0, the next major version of our preferred PHP framework, will be released in a few weeks. Basically, it shares the same code base as Symfony 2.8 but all deprecated features coming from older versions have been removed to simplify the framework and its maintenance:

Symfony 3 deprecation progress report. #symfony pic.twitter.com/Nz4pMZZwU4

— Symfony News (@symfony_en) October 6, 2015

Symfony 2.8 and 3.0 also come with a lot of new features including (but not limited to) the (awesome) Guard authentication system, LDAP support or a component to guess types of PHP properties. In this post we’ll discover another interesting feature proudly sponsored by Les-Tilleuls.coop I’ve added to the Dependency Injection Component: autowiring.

The #Symfony Dependency Injection Component now supports autowiring! https://t.co/6imXHSReGU #PHP

— Kévin Dunglas (@dunglas) October 3, 2015

Introduction

Autowiring allows to register services in the container with minimal configuration. It is practical in the field of rapid application development, when designing prototypes and in early stages of large projects. It makes it easy to bootstrap an app service graph and eases refactoring:

@dunglas right, that's an interesting aspect for cache invalidation as well: a change in the class itself should trigger a container build.

— Matthias Noback @matthiasnoback (@matthiasnoback) August 30, 2015

A demo containing all code snippets shown in this article is available in a dedicated GitHub repository.

Let’s see how it works. To do so we will build a fake API publishing statutes on a Twitter feed obfuscated with ROT13 (a special case of the Caesar cipher).

Start by creating a ROT13 transformer class:

<?php

// src/AppBundle/Rot13Transformer.php

namespace AppBundle;

class Rot13Transformer
{
    public function transform($value)
    {
        return str_rot13($value);
    }
}

And now a Twitter client using this transformer:

<?php

// src/AppBundle/TwitterClient.php

namespace AppBundle;

class TwitterClient
{
    private $rot13Transformer;

    public function __construct(Rot13Transformer $rot13Transformer)
    {
        $this->rot13Transformer = $rot13Transformer;
    }

    public function tweetInRot13($user, $key, $status)
    {
        $transformedStatus = $this->rot13Transformer->transform($status);

        // Connect to Twitter and send the encoded status
    }
}

The Dependency Injection Component is now able to automatically register the dependencies of this TwitterClient class. The twitter_client service definition just need to be marked as autowired:

# config/services.yml

services:
    twitter_client:
        class:    AppBundle\TwitterClient
        autowire: true

The autowiring subsystem will parse the constructor of the TwitterClient class and detects its dependencies that way. Here it will find and fill the need for an instance of a Rot13Transformer.

If an existing service definition (and only one – see below) is of the needed type, it will inject it. Here it’s not the case, but the subsystem is smart enough to automatically register a private service for the Rot13Transformer class and set it as first argument of the twitter_client  service. Again, it can work only if there is one class of the given type. If there are several classes of the same type, you must fallback to the explicit service definition or register a default implementation (I’ll present this feature in a few line).

As you can see, the autowiring feature drastically reduces the amount of configuration required to define a service. No more arguments section! It also makes it easy to change the dependencies of the TwitterClient class: just add or remove typehinted arguments in the constructor and you’re done. There is no need anymore to search and edit related service definitions.

Here is a typical controller using the twitter_client services:

<?php

// src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class DefaultController extends Controller
{
    /**
     * @Route("/tweet")
     * @Method("POST")
     */
    public function tweetAction(Request $request)
    {
        $user = $request->request->get('user');
        $key = $request->request->get('key');
        $status = $request->request->get('status');

        if (!$user || !$key || !$status) {
            throw new BadRequestHttpException();
        }

        $this->get('twitter_client')->tweetInRot13($user, $key, $status);

        return new Response('OK');
    }
}

You can give a try to the API with curl:

curl -d “user=kevin&key=ABCD&status=Salut” http://localhost:8000/tweet

It should return OK.

Working with interfaces

This is nice but when the application grows, it’s recommended to code against abstractions instead of implementations: it allows to easily replace some dependencies without modifying the class depending of them.

To follow this best practice, constructor arguments must be typehinted with interfaces and not concrete classes. It allows to replace easily the current implementation if necessary.

Let’s introduce a Rot13TransformerInterface:

<?php

// src/AppBundle/Rot13TransformerInterface.php

namespace AppBundle;

interface Rot13TransformerInterface
{
    public function transform($value);
}

Then edit Rot13Transformer to make it implementing the new interface:

// ...

class Rot13Transformer implements Rot13TransformerInterface

// ...

And update TwitterClient  to depend of this new interface:

class TwitterClient
{
    // ...

    public function __construct(Rot13TransformerInterface $rot13Transformer)
    {
         // ...
    }

    // ...
}

Finally the service definition must be updated because, obviously, the autowiring subsystem isn’t able to find itself the interface implementation to register:

# app/config/services.yml

services:
    rot13_transformer:
        class: AppBundle\Rot13Transformer

    twitter_client:
        class:    AppBundle\TwitterClient
        autowire: true

The autowiring subsystem detects that the rot13_transformer service implements the Rot13TransformerInterface and injects it automatically. Even when using interfaces (and you should), building the service graph and refactoring the project is easier than with standard definitions.

Dealing with multiple implementations of the same type

Last but not least, the autowiring feature allows to specify the default implementation of a given type. Let’s introduce a new implementation of the Rot13TransformerInterface returning the result of the ROT13 transformation uppercased:

<?php

// src/AppBundle/UppercaseRot13Transformer.php

namespace AppBundle;

class UppercaseRot13Transformer implements Rot13TransformerInterface
{
    private $rot13transformer;

    public function __construct(Rot13TransformerInterface $rot13transformer)
    {
        $this->rot13transformer = $rot13transformer;
    }

    public function transform($value)
    {
        return strtoupper($this->rot13transformer->transform($value));
    }
}

This class is intended to decorate the standard ROT13 transformer (or any other implementation) and return it uppercased.

We can now refactor the controller to add another endpoint leveraging this new transformer:

<?php

// src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

class DefaultController extends Controller
{
    /**
     * @Route("/tweet")
     * @Method("POST")
     */
    public function tweetAction(Request $request)
    {
        return $this->tweet($request, 'twitter_client');
    }

    /**
     * @Route("/tweet-uppercase")
     * @Method("POST")
     */
    public function tweetUppercaseAction(Request $request)
    {
        return $this->tweet($request, 'uppercase_twitter_client');
    }

    private function tweet(Request $request, $service)
    {
        $user = $request->request->get('user');
        $key = $request->request->get('key');
        $status = $request->request->get('status');

        if (!$user || !$key || !$status) {
            throw new BadRequestHttpException();
        }

        $this->get($service)->tweetInRot13($user, $key, $status);

        return new Response('OK');
    }
}

The last step is to update service definitions to register this new implementation and a Twitter client using it:

# app/config/services.yml

services:
    rot13_transformer:
        class: AppBundle\Rot13Transformer
        autowiring_types: AppBundle\Rot13TransformerInterface

    twitter_client:
        class:    AppBundle\TwitterClient
        autowire: true

    uppercase_rot13_transformer:
        class: AppBundle\UppercaseRot13Transformer
        autowire: true

    uppercase_twitter_client:
        class: AppBundle\TwitterClient
        arguments: [ @uppercase_rot13_transformer ]

It deserves some explanations. We now have 2 services implementing the Rot13TransformerInterface. The autowiring subsystem cannot guess the which one to use, this leads to errors like:

  [Symfony\Component\DependencyInjection\Exception\RuntimeException]                                           
  Unable to autowire argument of type "AppBundle\Rot13TransformerInterface" for the service "twitter_client".

Fortunately, the autowiring_types key is here to specify which implementation to use by default. This key can take a list of types if necessary (using a YAML array).

Thanks to this setting, the rot13_transformer service is automatically injected as argument of the uppercase_rot13_transformer and twitter_client services. For the uppercase_twitter_client, we use a standard service definition to inject the specific uppercase_rot13_transformer  service.

You now know everything you need to use the new autowiring feature! As this feature is directly available in the Dependency Injection Component, you can leverage it in any project using it, including Drupal 8, API Platform or BackBee once the component have been upgraded to 2.8+.

As for other RAD features such as the FrameworkBundle controller or annotations, keep in mind to not use autowiring in public bundles nor in large projects with complex maintenance needs.

Related posts:

  1. ADR pattern, autowiring, DunglasActionBundle: Symfony controllers redesigned
  2. DunglasActionBundle: Symfony controllers, redesigned
  3. Using PSR-7 in Symfony
  4. Making the Symfony PropertyAccess Component 84% faster in 2.8… and counting

11 thoughts on “New in Symfony 2.8/3.0: services autowiring”

  1. Sullivan SENECHAL says:
    October 19, 2015 at 12:42 pm

    Very interesting part, thanks for the work!

    What about having autowire option default to true on our Symfony app? Is that possible?

    Reply
    1. Kévin Dunglas says:
      October 19, 2015 at 1:02 pm

      It’s not possible (yet). We chosen to do it that way to ensure backward compatibility and to avoid edge cases.

      Reply
      1. Théo FIDRY says:
        October 19, 2015 at 3:31 pm

        What about an option on FrameworkBundle to enable auto-wiring project wide? (set to false by default for BC)

        Reply
        1. Kévin Dunglas says:
          October 19, 2015 at 4:12 pm

          The problem with such global states is that a service definition (or a bundle using autowiring, even if it’s a bad practice) can work on a project but not on another depending of the config.

          We try to avoid such behaviors.

          Reply
    2. Tomas Votruba says:
      October 28, 2015 at 1:17 pm

      I think you can write simple DependencyInjection Extension for that. Take all service definitions (or filter them as you like) and set them $autowire => true.

      Reply
      1. Sullivan SENECHAL says:
        February 5, 2016 at 11:34 am

        Also check if service definition already has argument. If yes, don’t set autowiring.

        Reply
        1. dunglas says:
          February 5, 2016 at 11:38 am

          Take a look at https://github.com/dunglas/DunglasActionBundle

          Reply
      2. Sullivan SENECHAL says:
        February 5, 2016 at 11:42 am

        You put an example of `autowiring_types` with a custom service class, but how to deal with vendor classes.

        Concret case, with a service that need TranslatorInterface, I get:

        “`
        Unable to autowire argument of type “Symfony\Component\Translation\TranslatorInterface” for the service “twig.ssl_certificate_extension”.
        “`

        Reply
        1. dunglas says:
          February 5, 2016 at 11:46 am

          Vendor classes must be fixed in the vendor or their definition must be overrided in your app.

          We updated core classes of Symfony and “officials” bundles but it looks like we missed some components like Translator. Can you open a PR or open a bug in Symfony for this one?

          Reply
  2. Pingback: DunglasActionBundle: Symfony controllers, redesigned - expert Symfony et e-commerce - Lille
  3. Tomas Votruba says:
    March 12, 2016 at 5:18 am

    If you are looking for Autowiring in Controllers, you will love this bundle: http://www.tomasvotruba.cz/blog/2016/03/10/autowired-controllers-as-services-for-lazy-people

    Reply

Leave a ReplyCancel reply

Social

  • Bluesky
  • GitHub
  • LinkedIn
  • Mastodon
  • X
  • YouTube

Links

  • API Platform
  • FrankenPHP
  • Les-Tilleuls.coop
  • Mercure.rocks
  • Vulcain.rocks

Subscribe to this blog

Top Posts & Pages

  • JSON Columns and Doctrine DBAL 3 Upgrade
  • Securely Access Private Git Repositories and Composer Packages in Docker Builds
  • Preventing CORS Preflight Requests Using Content Negotiation
  • FrankenPHP: The Modern Php App Server, written in Go
  • Symfony's New Native Docker Support (Symfony World)
  • Develop Faster With FrankenPHP
  • How to debug Xdebug... or any other weird bug in PHP
  • HTTP compression in PHP (new Symfony AssetMapper feature)
  • PHP and Symfony Apps As Standalone Binaries
  • Generate a Symfony password hash from the command line

Tags

Apache API API Platform Buzz Caddy Docker Doctrine FrankenPHP Go Google GraphQL HTTP/2 Hydra hypermedia Hébergement Javascript JSON-LD Kubernetes La Coopérative des Tilleuls Les-Tilleuls.coop Lille Linux Mac Mercure Mercure.rocks Messagerie Instantanée MySQL performance PHP Punk Rock Python React REST Rock'n'Roll Schema.org Security SEO SEO Symfony Symfony Live Sécurité Ubuntu Web 2.0 webperf XML

Archives

Categories

  • DevOps (84)
    • Ubuntu (68)
  • Go (17)
  • JavaScript (46)
  • Mercure (7)
  • Opinions (91)
  • PHP (170)
    • API Platform (77)
    • FrankenPHP (9)
    • Laravel (1)
    • Symfony (97)
    • Wordpress (6)
  • Python (14)
  • Security (15)
  • SEO (25)
  • Talks (46)
© 2025 Kévin Dunglas | Powered by Minimalist Blog WordPress Theme