How do I programmatically instantiate a class I "registerService()"ed?

For my user backend app I am trying to use dependency injection. I registered all of its dependencies with registerService() but I need to create the main class itself so I can register it in app.php, with:

$userBackend = <what do I do here?>;
\OC::$server->getUserManager()->registerBackend($userBackend);

How can I create an instance of this class without first instantiating its parameters which would negate dependency injection?

\OC::$server->query(\OCA\YourApp\YourUserBackend::class) and replace \OCA\YourApp\YourUserBackend with the actual full class path

1 Like

This works, thanks! Is there documentation for this, I haven’t found?

I found that @nickvergessen’s solution does not always work. I changed the class being instantiated to have an argument that is an abstract class and then Nextcloud’s dependency injection did not work anymore. After 2 hours of looking at the code with a debugger I realized that you should actually start it like that:

\OC::$server->getUserManager()->registerBackend(\OC::$server->query(‘UserBackend’));

So instead of passing the class you pass the name (string) of the service you registered. Then NC actually looks at the registered service and recurses down to its arguments and there you can properly handle the instantiation of the concrete classes.

That sounds like s solvable issue, can you give a link to your repo?

The DI changes are still in a local dev branch, but the current master branch contains everything but the updated Application.php, which looks like this:

<?php

namespace OCA\UserBackendSqlRaw\AppInfo;

use OCA\UserBackendSqlRaw\Config;
use \OCP\AppFramework\App;
use \OCA\UserBackendSqlRaw\UserBackend;
use \OCA\UserBackendSqlRaw\Dbs\Mariadb;
use \OCA\UserBackendSqlRaw\Dbs\Postgresql;

class Application extends App {

	public function __construct(array $urlParams = array()) {
		parent::__construct('user_backend_sql_raw', $urlParams);

		$container = $this->getContainer();

		$container->registerService('Logger', function ($c) {
			return $c->query('ServerContainer')->getLogger();
		});

		$container->registerService('NextcloudConfig', function ($c) {
			return $c->query('ServerContainer')->getConfig();
		});

		$container->registerService('AppConfig', function ($c) {
			return new Config(
				$c->query('Logger'),
				$c->query('NextcloudConfig')
			);
		});

		$container->registerService('Database', function ($c) {

			if ($c->query('appConfig')->getDbType() === 'mariadb') {
				return new Mariadb($c->query('AppConfig'));
			} else {
				// PostgreSQL is default
				return new Postgresql($c->query('AppConfig'));
			}
		});

		$container->registerService('UserBackend', function ($c) {
			return new UserBackend(
				$c->query('Logger'),
				$c->query('AppConfig'),
				$c->query('Database')
			);
		});


		\OC::$server->getUserManager()->registerBackend(\OC::$server->query('UserBackend'));
	}
}

As you can see I changed it to call the class by its name, i.e. using a string parameter. Db became an abstract class and the DI code could not instantiate it when I used

\OC::$server->query(\OCA\UserBackendSqlRaw\UserBackend::class)

Replace:

$container->registerService('Database', function ($c) {

With

$container->registerService(\OCA\UserBackendSqlRaw\Db::class, function ($c) {

Then it should™ work

Would this be better than my current solution?

Then you should be able to automatically inject everything again and instead of a magic string, you can use your class name again

Hm, can you help me understand why specifying ‘Database’ as the service name does not work? Shouldn’t Container::query('someStringIChose') just run the closure I registered with registerService('someStringIChose', $closure).

Is this a bug or am I missing something?

Something else that might be a bug, but I can’t tell because I don’t completely understand the code is that when I use ‘Database’ as the name instead of the specifying the class, as you suggested, the class that needs to instantiated is recognized correctly at first, instantiation fails because it is an abstract class, but then another query method is run against “db” (instead of “Db”). This fails and the exception error message also complains that the class “db” is missing, which is of course true, because it should have looked for Db.

Maybe a screenshot of this state in debug mode helps:

You can see in line 61 getName() returns db in lowercase.