How to get an object of the OC\DB\Connection (or OC\DB\MigrationService)?

I am just working my way to some testing of an app. The goal is to increase the code coverage in general. Now I had to tweak a migration and want to write an integration test to check it.

Obviously, I need an object of type \OC\DB\MigrationService to perform the migrations involved. If you have a better idea, please tell me.

  • I can create a new instance of that class but I need an OC\DB\Connection. How can I get that one?
  • I could get the instance from the DI container. However, my attempt was disturbed by the fact that the generated MigrationService was not built for the correct app (the app name was something generic). Any good advice?

I am aware that I should not use the private classes of the NC core but I need to tweak the tables/migrations. Also, the main code is free of private method calls, only the test code needs the OC\ classes.

Ideally, you know of an app as an example that I can have a look at and I can clone their behavior. Thank you very much!

Could you fetch the application via the server container $app = \OC::$server->get(\MyApp\AppInfo\Application::class)`` and then run $app->getContainer()`? \OCP\AppFramework\App::getContainer should give you the app’s container. Fetch the migration service there and it should be built for the correct app.

Hello @ChristophWurst. Thanks for the heads up. I tried it here and as you can see in the CI test, there is an error emitted:

OCP\AppFramework\QueryException: Could not resolve appName! Class appName does not exist

/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:115
/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:126
/nextcloud/lib/private/ServerContainer.php:162
/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:87
/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:101
/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:109
/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:126
/nextcloud/lib/private/ServerContainer.php:162
/nextcloud/lib/private/AppFramework/DependencyInjection/DIContainer.php:434
/nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php:56
/nextcloud/apps/cookbook/tests/Integration/Setup/Migrations/Version000000Date20210701093123Test.php:49

The same happens when I replace $app = new App('cookbook'); by

use OCA\Cookbook\AppInfo\Application;
// ...
$app = \OC::$server->get(Application::class);

It seems that the dependency injector is looking for an unset string appName.

If you have a debugger attached you could step through \OC\AppFramework\DependencyInjection\DIContainer::query. The app container can’t load the service for some reason, then the fallback mechanism is to go into the server container. That server container does not have the appName parameter → :boom:

I attached a debugger to the tester and I can now step through the code. Unfortunately, I do not really get the idea of how the DI mechanism should instantiate new objects. In \OC\AppFramework\DependencyInjection\DIContainer::query the class \OC\DB\MigrationService is requested. The request is first forwarded to queryNoFallback which just checks if in the local container is already an appropriate object existing. There is nothing found ($firstException):

OCP\AppFramework\QueryException: Could not resolve OC\DB\MigrationService! Class can not be instantiated in /nextcloud/lib/private/AppFramework/DependencyInjection/DIContainer.php:462
Stack trace:
#0 /nextcloud/lib/private/AppFramework/DependencyInjection/DIContainer.php(431): OC\AppFramework\DependencyInjection\DIContainer->queryNoFallback('OC\\DB\\Migration...')
#1 /nextcloud/lib/private/AppFramework/Utility/SimpleContainer.php(56): OC\AppFramework\DependencyInjection\DIContainer->query('OC\\DB\\Migration...')
#2 /nextcloud/apps/cookbook/tests/Integration/Setup/Migrations/Version000000Date20210701093123Test.php(50): OC\AppFramework\Utility\SimpleContainer->get('OC\\DB\\Migration...')
#3 /nextcloud/apps/cookbook/vendor/phpunit/phpunit/src/Framework/TestCase.php(1088): tests\Integration\Setup\Migrations\Version000000Date20210701093123Test->setUp()
#4 /nextcloud/apps/cookbook/vendor/phpunit/phpunit/src/Framework/TestResult.php(703): PHPUnit\Framework\TestCase->runBare()
[...]

After that, the server is queried with autoloading enabled. There the typical problem from my last post arises.

So, do I need to register the MigrationService similar to other services with the DIContainer (see the constructor)?

Yes, I think you have to create your own instance. Checking the usages of \OC\DB\MigrationService::__construct I realize we don’t use DI to build an instance.

So, @ChristophWurst, I think, I need to do something like the following. Did I understand you correctly?

When running this code against the 19 and 20 versions of NC (ok, 19 is EOL but 20 is still valid), I cannot obtain a \OC\DB\Connection instance due to a lacking array params (see these logs of the CI). Can I somehow pre-populate this array from the configuration (from config/config.php) with appropriate values or will I have to manually do this (how?!?)?

Do you run this before Nextcloud is even initialized and installed? https://github.com/nextcloud/server/blob/39931cab94512fb3f9eece0f03f66fc1b88a933b/lib/private/Server.php#L810-L822 registers the db connection.

Hello @ChristophWurst, sorry for the late answer, I had some pressing things at hand recently.

I am not sure, I get your point here.

The nextcloud was installed using occ maintenance:install .... So the config file in config/config.php should be (according to my understanding) built correctly. So, I consider the NC core installed. Do you agree?

The term initialized I am not so sure. This is something that should be done for each PHP invocation and most probably will be done in the index.php or its descendants somewhere in the core. Are we on the same page here or are you thinking of something else?
I did not do this initialization manually. As I am running an integration test, I am not running the default index.php script but rather build a minimal (and not complete) set of objects to test my DUT against. I thought the autoloader will pull in all required dependencies but this is obviously not the case. I might well have missed just a call to initialize the classes/instances but I do not see the point where to set the wrench so to call.

Was there something changed in 20 → 21? Because the 21 and master (of the server) are working just plainly fine.

Yes.

With initialized I mean that the internals of Nextcloud are loaded. If I’m not mistaken then most of this happens when lib/base.php is included. Afterwards Nextcloud is ready.

Nothing I’m aware of.

OK, I have a bootstrap code that includes the /tests/bootstrap.php from the NC server which in fact includes the lib/base.php. This no problem.


Just realized I was dumb as hell :blush: :weary:.

I did not check the API in depth.During comparing the version 20 with the version 21 I just now realized that version 21 hat both OC\DB\Connection and OCP\DB\IDBConnection but the earlier versions only had the OCP\DB\IDBConnection interface. Consequently, the MigrationService constructors need different types.

After replacing this in the test code it ran through directly. Sorry, @ChristophWurst for the inconvenience and for thinking this throught.

1 Like

No worries. We’ve all been there at least once :wink: