Search code examples
phpsymfonysymfony6

Symfony 6.4 - Unable to declare an abstract form type as a service


In my Symfony 6.4 project, I'm trying to declare an abstract service in services.yaml by doing the following :

config/services.yaml

imports:
- { resource: services/ }
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.

config/services/forms.yaml

services:
  _defaults:
    autowire: true
    autoconfigure: true

  # parent form
  App\Admin\Form\HostingFormAbstract:
    arguments:
      $authorizationChecker: '@security.authorization_checker'
      $tokenStorage: '@security.token_storage'
      $phpHandlers: '%php_handlers%'
      $nodeJsHandlers: '%nodeJs_handlers%'
    abstract:  true

  # children forms
  App\Admin\Form\HostingFormCreate:
    parent: App\Admin\Form\HostingFormAbstract
    class: App\Admin\Form\HostingFormCreate
    calls:
      - [setGroupManager, ['@App\Services\Managers\GroupManager']]
    tags: [form.type]

  App\Admin\Form\HostingFormUpdate:
    parent: App\Admin\Form\HostingFormAbstract
    class: App\Admin\Form\HostingFormUpdate
    calls:
      - [setHostnameManager, ['@App\Services\Managers\HostnameManager']]
    tags: [form.type]
...

The HostingFormAbstract class is extending AbstractType from symfony/form package :

namespace App\Admin\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints\NotBlank;

abstract class HostingFormAbstract extends AbstractType
{
    
    private TokenStorageInterface $tokenStorage;
    private AuthorizationCheckerInterface $authorizationChecker;
    private array $phpHandlers;
    private array $nodeJsHandlers;

    public function __construct(TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, array $phpHandlers, array $nodeJsHandlers)
    {
        $this->tokenStorage = $tokenStorage;
        $this->authorizationChecker = $authorizationChecker;
        $this->phpHandlers = $phpHandlers;
        $this->nodeJsHandlers = $nodeJsHandlers;
    }
...

And with that, I get the following error while going on my symfony app :

The service "App\Admin\Form\HostingFormAbstract" tagged "form.type" must not be abstract.

For more context, I'm rewriting a Symfony 3.4 app into a new Symfony 6.4 app.

It was working fine on 3.4, but now it seems that in services.yaml, you can't declare an abstract service/class extending the AbstractType class.

I tried to add a custom tag in the yaml for this service, but it didn't change anything.

I didn't find any workaround on Symfony documentation, and it seems odd to me that you can't do that. Maybe I missed something.

If anyone as an idea on how to do this...

Thanks !

Edit, working solution :

It seems that there is something wrong with my multiple .yaml files registering the services.

Here is what I had to do in order to have my service registered :

  • I moved all the services of config/services/forms.yaml in config/services.yaml
  • I added autoconfigure: false to my App\Admin\Form\HostingFormAbstract service

config/services.yaml

imports:
- { resource: services/ }

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.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'
 ...

App\Admin\Form\HostingFormAbstract:
    class: App\Admin\Form\HostingFormAbstract
    autoconfigure: false
    arguments:
        $authorizationChecker: '@security.authorization_checker'
        $tokenStorage: '@security.token_storage'
        $phpHandlers: '%php_handlers%'
        $nodeJsHandlers: '%nodeJs_handlers%'
    abstract: true

App\Admin\Form\HostingFormCreate:
    parent: App\Admin\Form\HostingFormAbstract
    class: App\Admin\Form\HostingFormCreate
    calls:
        - [ setGroupManager, [ '@App\Services\Managers\GroupManager' ] ]
    tags: [ form.type ]

App\Admin\Form\HostingFormUpdate:
    parent: App\Admin\Form\HostingFormAbstract
    class: App\Admin\Form\HostingFormUpdate
    calls:
        - [ setHostnameManager, [ '@App\Services\Managers\HostnameManager' ] ]
    tags: [ form.type ]

Solution

  • It's the default behavior of autoconfigure option, setting the form.type tag to all FormTypeInterface classes under the service scope.

    However, you can exclude any service from been auto-tagged by setting autoconfigure: false like this:

    App\Admin\Form\HostingFormAbstract:
        autoconfigure: false
        // ...
    

    and then the error should disappear.