cakePHP 5: Dependency Injection - Inject existing service into Component

Hi there,

I an using cakePHP 5.2.7. and registred in my Application.php a service:

$container
    ->add(GenericProvider::class, function () {
        return new GenericProvider([
            'clientId'                => 'xxx',
            'clientSecret'            => 'xxx',
            'redirectUri'             => 'https://account.xyz.com/oauth/callback',
            'urlAuthorize'            => 'https://auth.xyz.com/authorize',
            'urlAccessToken'          => 'https://auth.xyz.com/token',
            'urlResourceOwnerDetails' => 'https://auth.xyz.com/userinfo',
            'pkceMethod'              => GenericProvider::PKCE_METHOD_S256,
        ]);
    });

Some of you may recognize this is e generic league/oauth2-client.

I also build a small Component: OauthComponent.php to do some basic tasks.

Now I want to inject the GenericProvider::class into the OauthComponent.php.

In the official documentation, I found this:
Dependency Injection - 5.x
For me, this means I have to do it in Application::service like this way:

$container->add(OauthComponent::class)
    ->addArgument(ComponentRegistry::class)
    ->addArgument(GenericProvider::class);

and can get the GenericProvider by constructor:

class OauthComponent extends Component
{
    protected GenericProvider $provider;        // OAuth2-client

    public function __construct(ComponentRegistry $registry, GenericProvider $provider, array $config = [])
    {
        parent::__construct($registry, $config);
        $this->provider = $provider;
    }

    ...
    ...
}

But now I get the error:

App\Controller\Component\OauthComponent::__construct(): Argument #3 ($config) must be of type array, Cake\Controller\ComponentRegistry given

and when I toggle the arguments of [internal]in App\Controller\Component\OauthComponent->__construct I can see the following:

object(Cake\Controller\ComponentRegistry) {
}

object(League\OAuth2\Client\Provider\GenericProvider) {
}

object(Cake\Controller\ComponentRegistry) {
}

object(League\OAuth2\Client\Provider\GenericProvider) {
}

[ ]

It looks like ComponentRegistry and GenericProvider are registered twice and during injecting it into the compoent, first 3 arguments are used which will mean that the empty array (last argument) is not assigned correctly.

Any ideas? Many thanks to all whe read this :slight_smile:

Most likely a bug in component loading.

When a component is registered in the container with arguments, the ComponentRegistry::_create() method tries to add those arguments again by calling reflectArguments() and then addArguments().

The logic should either:

  1. Not call reflectArguments() if arguments are already configured, OR
  2. Use extend() differently to replace rather than append arguments, OR
  3. Clear existing arguments before adding new ones

The bug is at src/Controller/ComponentRegistry.php:156-164. It should check if arguments are already configured before trying to add more, or it should replace the existing configuration rather than extending it.

The user should not manually add arguments in Application.php - they should just register the component class itself:

$container->add(OauthComponent::class);

And let the reflectArguments() call handle the dependency resolution. The documentation example is misleading too maybe.

1 Like

Can you check if this branch works? Fix up DI for components. by dereuromark · Pull Request #18938 · cakephp/cakephp · GitHub

1 Like

Hello dereuromark,

thank you for your quick response to this. I changed the ComponentRegistry and its working now. Thank you!

But, as you mentioned that it should be simply possible to register the component call itself like:

$container->add(OauthComponent::class);

Thats not working, I got an Exception:

Component App\Controller\Component\OauthComponent is registered in container but cannot be resolved.

So I am doing it like the documentation says:

$container->add(OauthComponent::class)
            ->addArgument(ComponentRegistry::class)
            ->addArgument(GenericProvider::class);


Another bug I just noticed is (and I don’t know if its problem depending on the change in ComponentRegistry), if Controller and Component have same names like

OauthController;
OauthComponent;

I got completely weird results and errors saying:

[Cake\ORM\Exception\MissingTableClassException] Table class for alias Oauth could not be found. in /cakephp/src/ORM/Locator/TableLocator.php on line 249

Exception Attributes: array (
0 => ‘for alias Oauth’,
)

Its weired because I am not using TableLocator anywhere in OauthController or OauthComponent. For now I fixed it changing name of OauthController to something different.

Did you see the PR I linked? Did you check if that one solves it?

Hello dereuromark,

yes, I see the PR and tried it out. Its working! Thank you! (I wrote it also in my last reply ;))

But there are some special cases I described in the previous reply, maybe its also interesting for you.

You might want to add these things to the PR and actual code discussion, and not keep it inside a forum itself :slight_smile:

1 Like

Ok, done it. I was not sure if its ok to post things there

@der-dave To fix your MissingTableClassException exception set the $defaultTable property of the controller to empty string.

By default that property gets set to a model name based on the controller name (so Oauth in your case). Then when you access $this→Oauth in your controller, since that property is not explicitly declare the magic __get() method of the controller is called which first tries to load a table class based on the $defaultTable property. So setting it to empty string will prevent that and the magic __get() will then move on to checking for a component with that name, which you do have.

1 Like

Oh, thank you, that solved it. Didn’t know about this. Great!