Search code examples
phpsymfony

How Do I Solve: Cannot autowire service "App\Core\Validator\ValidatorService" Error In Symfony 7


Cannot autowire service "App\Core\Validator\ValidatorService": argument "$validators" of method "__construct()" references interface "App\Core\Validator\ValidatorInterface" but no such service exists. Did you create an instantiable class that implements this interface?

How do I solve the error above.

I want to inject multiple instances of an interface into a service. See the links to two articles: Today I Learnt — How to inject multiple instances of an interface in a service and How to inject multiple instances of an interface in a service using Symfony 5

I created a 'ValidatorInterface' which I implemented in two classes, 'WelcomeEmailValidator' and 'LoginEmailValidator' classes.

I then created a Service class, to use the 'ValidatorInterface' like a service. See the code below:

<?php declare(strict_types = 1);

namespace App\Core\Validator;

use App\Core\MailStruct;

interface ValidatorInterface
{  
    /**
     * support: checks if the validator class supports this type of email 
     *
     * @param  string $type
     * @return bool
     */
    public function support(string $type): bool;

    /**
     * validate: checks if the request data is valid
     *
     * @param  MailStruct $mail
     * @return void
     */
    public function validate(MailStruct $mail): void;
}

<?php declare(strict_types = 1);

namespace App\Core\Validator;

use App\Core\EmailType;
use App\Core\MailStruct;
use App\Core\Validator\ValidatorInterface;


class LoginMessageValidator implements ValidatorInterface
{
    public function support(string $type): bool
    {
        return $type === EmailType::LOGIN_EMAIL;  
    }

    public function validate(MailStruct $mail): void
    {
        //dd(EmailType::LOGIN_EMAIL);
    }
    
}
<?php declare(strict_types = 1);

namespace App\Core\Validator;

use App\Core\EmailType;
use App\Core\MailStruct;
use App\Core\Validator\ValidatorInterface;

class WelcomeMessageValidator implements ValidatorInterface
{
    public function support(string $type): bool
    {
        return $type === EmailType::WELCOME_EMAIL;  
    }

    public function validate(MailStruct $mail): void
    {
        dd(EmailType::WELCOME_EMAIL);
    }
    
}
<?php declare(strict_types = 1);

namespace App\Core\Validator;

use App\Core\MailStruct;
use App\Core\Validator\ValidatorInterface;

class ValidatorService
{

    private ValidatorInterface $validators;

    public function __construct(ValidatorInterface $validators)
    {
        $this->validators = $validators;    
    }
    
    public function validate(MailStruct $mail)
    {
        foreach($this->validators as $validator){
            $validator->validate($mail);
        }
    }
}

I am using auto wiring for the project but for this situation, I get the error Cannot autowire service


Solution

  • As i wrote in my comments, the current issue is that you created an interface ValidatorInterface with multiple implementations: LoginMessageValidator & WelcomeMessageValidator. When typehinting with ValidatorInterface, Symfony cannot autowire because it does not know which implementation it should pass.

    Following the articles you linked, we can see that you missed some steps in the configuration and set a wrong typing in the ValidatorService constructor: it does not need 1 implementation of the interface but multiple ones (in an array or some iterable type):

    class ValidatorService
    {
        /** @var ValidatorInterface[] */
        private array $validators;
    
        /**
         * Notice the `...` operator (2nd article): allows to type arguments in a list
         * The first article suggests another typing you can also try
         */
        public function __construct(ValidatorInterface ...$validators)
        {
            $this->validators = $validators;    
        }
        
        // ...
    }
    

    The rest of both articles work with service tagging in your config/services.yaml file.
    First you want all of the classes implementing ValidatorInterface to have a tag (let's call it app.validator):

    services:
        _instanceof:
            App\Core\Validator\ValidatorInterface:
                tags: ['app.validator']
    

    Then you want every tagged service to be collected to pass them as the $validators argument of ValidatorService:

    services:
        # ...
        
        App\Core\Validator\ValidatorService:
            arguments:
                $validators: !tagged_iterator app.validator