Uh oh!
There was an error while loading. Please reload this page.
Improve dependency injection guidance - #1640
Conversation
✅ Deploy Preview for moodledevdocs ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
6aa705d to
832f067CompareClarify constructor injection, autowiring, container definitions, and application boundaries, using Calendar as a canonical core example.
832f067 to
116e82aComparetimhunt
commented
Aug 18, 2026
Thanks for writing this @cameron1729, it looks like important advice about best practice (about which I certainly know less than I should, and I just learned things.) I am going to add a few comments about suggested improvements, but I will leave a full review to someone more knowledgeable. |
| Constructor injection is one form of dependency injection. Dependencies can also be supplied through methods, including setters, or through properties. Moodle code should normally use constructor injection for required collaborators so that every instance is complete and its dependencies remain explicit. | ||
| Constructor injection is not a feature of Moodle's DI container. For required collaborators, it is a long-established PHP design practice that has been widely used for well over a decade. A class declares only its immediate dependencies and remains unaware of whether they were supplied by application code, a test, hand-written composition code, or a container. This makes the class easier to understand, reuse, and test. It also makes the class naturally compatible with Moodle's container, which can use its constructor types to assemble the object graph. The [unit testing example](#testing-a-class-without-a-container) shows the same class being constructed directly with test doubles. |
There was a problem hiding this comment.
https://xkcd.com/285/ - for people who want to know more, and to strengthen your argument that this is "a long-established PHP design practice", it would be good to link to an in-depth discussion of DI somewhere on the web, if you know a good one.
| timecreated: $this->clock->time(), | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Given that a good example is worth a thousand words, I am going to say, I did not find this the most intuitive example. It took me some time to pick it apart, and I might not have been motivated to do that.
I can see you have tried to make the example quite generic (mod_example, local, service, .... It might actually be better to go more specific, to make the purpose of things clearer. I can't tell what sort of thing create_for_user is creating. It does not even return the thing created.
So, if there are any ways you can make this example more self-explanatory, or self-motivating, I think it might be worth putting some time into that.
| Where `\core\di::get()` is called must be considered carefully. Moodle spans more than two decades of architectural styles, so its entry points are not all composed in the same way. | ||
| Start by identifying the operation's application boundary: the outermost component code invoked for that operation. Common boundaries include route controller methods, external functions, hook callbacks, scheduled task `execute()` methods, and CLI or legacy PHP scripts. Runtime values such as validated request parameters and the current user ID enter at the boundary. It then invokes the top-level application objects for that operation. The classes called beneath it are inside the application's object graph; they are not boundaries merely because they call other classes. |
There was a problem hiding this comment.
I would not call them 'legacy PHP scripts' yet. As you say, this is how Moodle has worked for 20+ years. They are currently the main entry point into most of Moodle, so I would list them first, perhaps ("top level PHP scripts like course/view.php).
cameron1729
commented
Aug 18, 2026
Thank you very much for taking the time to read this @timhunt - your feedback as someone wanting to learn more about DI and best practice is extremely valuable. This is something I'm working on in my personal time, as well as an accompanying example in core which I hope can serve as a kind of reference implementation. So I might be slow to get back to your points/comments but I will definitely be using your suggestions to improve this document :) Thanks again. |
timhunt
commented
Aug 18, 2026
Thanks @cameron1729. It might be best to get this first version published, and open a new issue for further improvements later. And, when this is published, I wonder if you can get a shout-out in integration exposed? Having the docs is one thing. Even better if people actually read them! :-) |
Background
Existing DI documentation focuses heavily on accessing Moodle's DI container, which can blur the distinction between constructor injection and using the container as a service locator.
This distinction is becoming increasingly important as the core container sees wider adoption. Moodle needs clear guidance showing that classes should declare only their immediate dependencies, code to contracts, and remain unaware of the container. Container lookups should be restricted to composition roots or compatibility boundaries.
The Calendar API is a useful canonical example. It has used constructor injection since Moodle 3.3, before Moodle had a native DI container, and already contains a substantial graph of factories, mappers, policies, retrieval strategies, and output services. There is work in progress to migrate it to native DI - and the documentation here references that work.
Changes
This PR:
Related work
Important
This PR documents Calendar APIs introduced by MDL-89216 and coding-standard rules introduced by moodlehq/moodle-cs#228. It must not be merged before those have landed.
Moodle
maincurrently targets Moodle 5.3, scheduled for release on 5 October 2026. If MDL-89216 is integrated for Moodle 5.3, this PR should merge after its integration and before the 5.3 developer documentation is forked. If the issue moves to a later release, this PR should be held until the developer documentation targets the release which contains it.