Nextcloud apps need some level of bootstrapping code to be run when a request reaches the Nextcloud server. This code wires up interfaces and classes for the dependency injection container, registers hook and event listeners and runs other important tasks.
Currently, most of this can be done by implementing an
Application class that
represents your app. In its constructor, you do whatever is required for your
app to run. That looks something like this:
While this approach is perfectly valid and good enough for small to medium-sized apps, the bootstrapping code of larger Nextcloud apps can become badly organized, especially if you want to group code into logical units or blocks.
Service providers can help decoupling and splitting this code into several classes following the single responsibility principle. I first learned about service providers when working on a larger application using the Laravel framework. Laravel is a PHP web framework with a wide range of available services, which are all bootstrapped and loaded via service provider classes. This makes the code base more manageable and prevents code being loaded that is actually not needed for a particular request. Thus, this not only helps organize code but speeds up the application.
Another important aspect of service providers is that they are run to a known state in the application lifecycle. The two available methods to overwrite and thus to hook into this process are
boot. The former is meant to be used to register services for the dependency injection container. The latter one is meant for tasks like registering events. This logical separation makes sure that event aren’t fired before their dependencies (other services which service provider has not been invoked yet) are ready.
I studied the exposed interface of Laravel’s service providers and some of the implementation and realised that it’s actually pretty straight forward to implement such an infrastructure for Nextcloud apps. Therefore I’ve taken those ideas and created a similar
ServiceProvider class. To allow partial implementation, I chose to go with an
abstract class. The two methods bodies of
boot are empty by default and shall be overwritten as needed. In contrast to Laravel service providers, which are clever enough to have dependency injection for the
boot method, I’ve chosen to pass the
Application instance and and instance of the
IAppContainer as parameter of both methods. This should give enough flexibility as the app container even allows to query the server container, which is the container for services of the Nextcloud server. This is the resulting base class:
And an implementation of this class would look like this:
In my opinion that is clean code which is easy to read and understand. For those who target 100% code coverage this gives the opportunity to easily unit test the service registration mocks could be passed as parameters.
There is one thing left to bring this to life: we have to wire it up with our
Application class. For this, I chose to implement a convenience trait, that provides two methods for application classes. Again, inspired by how well the Laravel framework was designed, there should be very little code needed to do this. Hence I chose to read an instance property
providers, which is an array of provider class names. The trait reads it and loads the service provider instances to call the methods. The implementation is as simple as this:
lib ├── AppInfo │ └── Application.php ├── Command │ └── AddCommand.php ├── Contracts │ └── ICalculator.php ├── Http │ └── Middleware │ ├── AuthorizationMiddleware.php │ ├── ExceptionReportingMiddleware.php │ └── ValidationMiddleware.php ├── Provider │ ├── CalculatorServiceProvider.php │ ├── EventServiceProvider.php │ ├── MiddlewareServiceProvider.php │ └── RouteServiceProvider.php └── Service └── CalculatorService.php