Unable to resolve service error

Let me see if I can explain exactly what issue I am facing. When I started using Laminas I installed the skeleton application and various packages that I wanted to look into. One of those was Laminas-Di. Since then I have not used any of the functionality of laminas-di. My issue is that I now have Laminas-Authentication working as expected. The identity plugin works as expected. Laminas-session is working as expected (more or less but that is a different topic). My issue became apparent when I started removing components that I thought were not being used. If I comment out the entry in config/modules.config.php for laminas\Di I immediately get a
Unable to resolve service “Laminas\Authentication\AuthenticationService” to a factory
Error. Providing a factory for the service does not resolve the issue. I have provided a factory mapped to the service name AuthenticationService::class and setup an alias to map the AuthenticationServiceInterface to the class as well which also does not resolve the issue.
My question is this. Why should removing Laminas\Di have anything whatsoever to do with Laminas Authentication?

laminas-di is definitely not needed for laminas-authentication or laminas-session.

Unfortunately we don’t know your application and therefore we don’t know if you are using laminas-di somewhere or not.
What does the factory look like for authentication service?

Another option is to check your code with composer-unused:

It simply created an instance and returned it. I know I am not, have not used any of the Di functionality other than what would be autowired if any. If I remove laminas-di via composer. the application dies with the reported can not resolve service for the authenticationservice. Later today I will run the composer-unused and report back. However, I can assure you that I use none of it.

ok ran the composer unused and as I thought it reports that laminas-di is not in use. And thanks for the help @froschdesign!

Maybe the configuration cache is involved. laminas-development-mode can help here:

im in development mode, there is no configuration being cached. My composer require shows a laminas/laminas-mvc-plugins but does not show a entry for the identity plugin, but I am taking it that is a meta package. I honestly am at a loss here… I have an entry in require for laminas authentication but do not have an entry in the modules.config.php array for laminas auth but I really wouldnt expect one since the package does not have a factory nor does it have a config provider. When I use it its just as the example usage from the docs, I simply create a new instance. Ive created many many factories and usually have no issue, even if its replacing a component class that is already used by the framework so I understand how to get into the workflow. But this has me stumped.

If you use the MVC skeleton application and if you install the meta package laminas-mvc-plugins then all plugins will be registered in modules.config.php because the laminas-component-installer is used in the background.

This is correct, laminas-authentication does not provide a module support.

This also applies to us, so please remember that we are not wizards or gods. We can’t see your code, an error message or a stack trace. So all we can do is ask and make assumptions.

What about running a composer request, that shows why laminas/laminas-di is used. If there is a dependency in other packages, composer can show you with

> composer why laminas/laminas-di

If there is no dependency in the used composer packages I guess you 're using some classes of the laminas/laminas-di package on your own in your application. Perhaps in some of your factories? Have you checked, if classes of laminas/laminas-di are still referenced in your application? Unused use statements? Static string references?

The identity plugin comes from laminas/laminas-mvc-plugin-identity. The identity plugin required the laminas/laminas-authentication package for itself. There is no need to mention the authentication in the modules.config.php file because the dependency is in the plugin. For a deeper dive in how the authentication dependency is used just have a look in the Laminas\Mvc\Plugin\Identity\IdentityFactory class. You can start a composer why request on your own, to check all dependencies or occurances of the laminas/laminas-authentication package.

:+1:t3:
(I had completely forgotten about it!)

Here we have to be careful: The dependency between the identity plugin and laminas-authentication has nothing to do with module registration of laminas-mvc / laminas-modulemanager!

I will reproduce the error today at some point so that I can post the error and the stack trace (im tired at the moment, been up nearly 20 hrs).

@ezkimo I am aware. I have read all of the related code. I am 100% positive that I do not use the laminas-di anywhere. I have ran a project wide search for any use statements related to laminas-di and none of them are being used.

@froschdesign Correct. The identity plugin only checks to see if there is a service. And if there is for either AuthenticationService::class or AuthenticationServiceInterface::class then it calls the plugins setAuthenticationService and passes an instance from the container. So, you would think that somewhere there has to be a creation of said AuthenticationService. If the auth component does not provide a factory, nor a config provider where does that instance come from? The plugins invoke checks to make sure the passed service is an instance of the interface. I have not provided a factory, the package does not provide a factory and when I tried it was not used… Even though it was mapped under the AuthenticationService::class identifier… When the laminas-di package is loaded it just works. When I comment it out, it fails. So, my question I suppose should be. Where, in a normal situation is the AuthenticationService created? Because from what I am seeing the plugin can not work without one.

I had understood that you had created a factory for the authentication service!
You must create a factory yourself!

Therefore my first question was:

What does the factory look like for authentication service?

1 Like

Ok, well. I did, right after I removed the laminas-di and it caused the error. At the moment there is no factory for laminas auth and its working as expected.Now remember I have laminas-di loading right now. If I remove it it breaks the application. When I try to provide a factory, which I did under the correct service name it does not correct the issue.

There has to be some sort of service manager factory entry in the application config like …

...
'service_manager' => [
        'factories' => [
            Laminas\Authentication\AuthenticationService::class => 
                Path\To\Your\AuthenticationServiceFactory::class, 
        ],
],
...

That 's the most common way to implement authentication in your application. @froschdesign already asked the right question. What does the factory look like for authentication service?

1 Like

Here is an example for the factory:

namespace User\Authentication;

use Laminas\Authentication\Adapter\DbTable\CallbackCheckAdapter;
use Laminas\Authentication\AuthenticationService;
use Laminas\Db\Adapter\Adapter;
use Laminas\Db\Adapter\AdapterInterface;
use Psr\Container\ContainerInterface;
use Webmozart\Assert\Assert;

final class AuthenticationServiceFactory
{
    public function __invoke(
        ContainerInterface $container
    ): AuthenticationService {
        $databaseAdapter = $container->get(AdapterInterface::class);
        Assert::isInstanceOf($databaseAdapter, Adapter::class);

        $credentialValidationCallback =
            static function (
                string $dbCredential,
                string $requestCredential
            ): bool {
                return password_verify($requestCredential, $dbCredential);
            };

        $authAdapter = new CallbackCheckAdapter(
            $databaseAdapter,
            'user', // table name
            'email', // identity column
            'password', // credential column
            $credentialValidationCallback
        );

        return new AuthenticationService(null, $authAdapter);
    }
}
1 Like

Ok, please understand. Ive written like 100 plus factories. I understand what they are, how to create them and how to make them work. There is no factory for laminas authentication in the application at this moment. At all. Anywhere. here is the service manager config from all three modules in the application:

  'service_manager'    => [
      'factories' => [
          Db\DbGateway\LogGateway::class => Db\DbGateway\Factory\LogGatewayFactory::class,
          Model\Settings::class          => Model\Factory\SettingsFactory::class,
          Model\Theme::class             => InvokableFactory::class,
          Service\Email::class           => Service\Factory\EmailFactory::class,
          SaveHandlerInterface::class    => Session\SaveHandlerFactory::class,
      ],
  ],

'service_manager'   => [
    'aliases'   => [
        'navigation' => Navigation::class,
    ],
    'factories' => [
        Navigation::class                      => DefaultNavigationFactory::class,
        Db\PageGateway::class                  => Db\Factory\PageGatewayFactory::class,
        Db\Listener\PageGatewayListener::class => Db\Listener\PageGatewayListenerFactory::class,
        Model\Page::class                      => Model\Factory\PageFactory::class,
    ],
],
    'service_manager'    => [
        'aliases'   => [
            'UserInterface'                     => Service\UserServiceInterface::class,
            Service\UserServiceInterface::class => Service\UserService::class,
        ],
        'factories' => [
            AclInterface::class                    => Acl\AclFactory::class,
            Db\Listener\UserGatewayListener::class => Db\Listener\UserGatewayListenerFactory::class,
            Db\UserGateway::class                  => Db\Factory\UserGatewayFactory::class,
            Model\Roles::class                     => InvokableFactory::class,
            Model\Guest::class                     => InvokableFactory::class,
            Service\UserService::class             => Service\Factory\UserServiceFactory::class,
        ],
    ],

Relevant code from the login method:

        $authService = new AuthService();
        $authService->setAdapter($authAdapter);

Yes sir, I am aware. Mine was very similar, just did not have the assert But even without all of that. Just returning an instance should have corrected the error I would think since the service can be created without passing it anything. I will look at it some more later in the day. I truly appreciate the help. But that still leaves me with the question of how in the world is it working now.

Looks strange.
The normal usage in a controller is something like this: (pseudo code)

/** @var \Laminas\Authentication\AuthenticationService $authenticationService */
$authenticationService = $this->plugin('identity')->getAuthenticationService();

if ($this->getRequest()->isPost()) {
    $loginForm->setData((array)$this->getRequest()->getPost());

    if ($loginForm->isValid()) {
        $data = (array)$loginForm->getData();

        /** @var \Laminas\Authentication\Adapter\DbTable\AbstractAdapter $adapter */
        $adapter = $authenticationService->getAdapter();
        $adapter->setIdentity($data['username'])
            ->setCredential($data['password']);

        if ($authenticationService->authenticate()->isValid()) {
            // The default storage is session
            /** @var \Laminas\Authentication\Storage\Session $storage */
            $storage = $this->authenticationService->getStorage();
            $result  = $adapter->getResultRowObject(['role']);

            $storage->write(new UserEntity($result->role));

            return $this->redirect()->toRoute('…');
        }
    }
}

That is in a Model. It just simply returns a authentication result.

Update. I set a break point and tracked down the laminas-di, and as I suspected (took some digging to find it). The autowiring for laminas-di is indeed creating the authentication service instance via laminas\di\definition\reflection\classdefinition. The part that has me worried is that its also creating an instance of Laminas\EventManager\EventManager, which I do not understand why it would be doing that.

Something else I dont understand… Is how is the application using that instance that is created via laminas-di? I guess it gets added to the container? it would have too. Indeed, there is an service manager entry for the authentication service. So, at least now I know exactly how that is happening :smiley: Now to just fix it :smile: Thank you both @ezkimo and @froschdesign. Believe it or not, just being able to talk it out helped more than you know. And the pointers on the factory will help too :slight_smile:

If laminas-di is registered as module for your application then the auto-wiring is active via the related abstract factory Laminas\Di\Container\ServiceManager\AutowireFactory::class.
This means that the factory jumps in if nothing else is defined or found in the application service container.

Yep, which for total noobs, like I was at the time, it fixes things you do not realize is actually broken. Like I said way earlier in the conversation. Laminas-di got installed when I first started this learning experience and has just been hanging out in this particular project. I got to looking into it and thought that I would remove it from this project, since other than it being loaded, it was not intentionally being used. When I removed it. the application reported the error. Which now, I have the feeling, since that is the sequence of events its going to reveal alot more issues than just the authentication service when I start down the road to fixing the issue.