ADR pattern, autowiring, DunglasActionBundle: Symfony controllers redesigned

Here are the slides of the talk I gave yesterday at the Symfony meetup (sfPot) of Lille. I was presenting DunglasActionBundle, a refinement of the the Symfony controller system and talking about services autowiring and the ADR pattern.

DunglasActionBundle 0.3.0 released!

DunglasActionBundle is an alternative controller system for the Symfony framework relying on autowiring and inspirited by the ADR pattern. Thanks to this bundle, controllers, commands and event listeners are automatically detected and registered as services. All their dependencies are also automatically registered. See the GitHub repository for an extensive documentation.

A lot of work have been done in  during the last months to improve the reliability and the usability of the bundle. Today I’m proud to announce a new release of this bundle (0.3.0). It is available on GitHub and includes the following changes:

  • Actions and controllers (src/*Bundle/Action and src/*Bundle/Controller directories), commands ( src/*Bundle/Command) and event listeners ( src/*Bundle/EventListener) are auto-registered by default
  • The name of auto-registered services are now the Fully Qualified Name of the registred class
  • Directories containing classes to auto-register as services are now configurable
  • Directories of third-party bundles (vendor/) are not tracked by default anymore
  • It’s not necessary anymore to clear the cache manually when a new class is added or removed from a tracked directory
  • If the class of an automatically registred service implements the ContainerAwareInterface, the container is automatically injected into it
  • It’s now possible to automatically add custom tags to auto-registered services
  • The custom routing annotation loader has been removed thanks to a bug fix in Symfony core (this feature still works out of the box)
  • The “autodiscover” feature has been removed, paths must be configured explicitly (regex supported)
  • The bundle can now be configured using the XML format
  • PHP 5.5 support has been added
  • Lot of a bug fixes

Thanks to @Ener-Getick, @stof and @weaverryan for their contributions and good design advices.

If you like this bundle, please give it a star on GitHub!

 

DunglasActionBundle: Symfony controllers, redesigned

Today is my birthday, but – unusually – I offer the gift: DunglasActionBundle – a replacement for the Symfony controller subsystem.

Since few months, a lot of discussions and experimentations are occurring in the Symfony world to find a better and moderner way to  create controllers.

During the past summer, I’ve already switched the API Platform project from the traditional controller system to a variant of the ADR pattern.

Thanks to the support of autowiring I’ve introduced in the version 2.8 of the Dependency Injection Component, it’s now possible to create a generic (and I hope superior) replacement for the controller system of the full stack framework.

With this new system, no more inherited controller class, no more traits, only a plain old callable!

It is as convenient as the original but doesn’t suffer from its drawbacks:

  • Action classes are automatically registered as services by the bundle
  • Dependencies of action classes are explicitly injected in the constructor (no more ugly access to the service container)
  • Dependencies of action classes are automatically injected using the autowiring feature of the Dependency Injection Component
  • Only one action per class thanks to the __invoke() method (but you’re still free to create classes with more than 1 action if you want to)
  • 100% compatible with common libraries and bundles including SensioFrameworkExtraBundle annotations

DunglasActionBundle allows to create reusable, framework agnostic (especially when used with the PSR-7 bridge) and easy to unit test actions.

Guess what, it plays very well with the new Symfony micro framework too!

Installation

As usual, use Composer to install this bundle:

Then add the bundle in your application kernel:

Optional: to use the @Route annotation add the following lines in app/config/routing.yml:

Usage

  1. Create an invokable class in the Action\ namespace of your bundle:

There is not step 2! You’re already done.

All classes inside of the Action/ directory of your project bundles are automatically registered as services. By convention, those services follow this pattern: action.The\Fully\Qualified\Class\Name.

For instance, the class in the example is automatically registered with the name action.AppBundle\Action\MyAction.

Thanks to the autowiring feature of the Dependency Injection Component, you can just typehint dependencies you need in the constructor, they will be automatically injected.

Service definition can easily be customized by explicitly defining a service named according to the same convention:

This bundle also hooks into the Routing Component (if it is available): when the @Route annotation is used as in the example, the route is automatically registered: the bundle guesses the service to map with the path specified in the annotation.

Dive into the TestBundle to discover more examples such as using custom services with ease (no configuration at all) or classes containing several actions.

Using the Symfony Micro Framework

You might be interested to see how this bundle can be used together with the Symfony “Micro” framework.

Here we go:

Amazing isn’t it?

Want to see a more advanced example? Checkout our test micro kernel.

You like this bundle? Give it a star on GitHub!

New in Symfony 2.8/3.0: services autowiring

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 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.

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:

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:

And now a Twitter client using this transformer:

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:

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:

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:

Then edit Rot13Transformer to make it implementing the new interface:

And update TwitterClient  to depend of this new interface:

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

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:

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:

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

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:

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.