diff --git a/docs/apis/core/di/index.md b/docs/apis/core/di/index.md
index 0529877c91..48ee6610d3 100644
--- a/docs/apis/core/di/index.md
+++ b/docs/apis/core/di/index.md
@@ -68,6 +68,19 @@ class example_with_promotion {
:::
+## Creating instances {/* #creating-instances */}
+
+
+
+Use `\core\di::make()` to create a new instance of a class on each call, with dependencies resolved by the container. `\core\di::get()` returns a shared instance.
+
+Pass constructor argument overrides as a named array in the optional second argument:
+
+```php
+$thing = \core\di::make(my_thing::class);
+$thing = \core\di::make(my_thing::class, ['client' => $client]);
+```
+
## Configuring dependencies {/* #configuring-dependencies */}
In some rare cases you may need to supply additional configuration for a dependency to work properly. This is usually in the case of legacy code, and can be achieved with the `\core\hook\di_configuration` hook.
@@ -204,6 +217,21 @@ It is generally inadvisable to inject the Container itself. Please do not inject
:::
+## Attribute-based injection {/* #attribute-based-injection */}
+
+
+
+Use `#[\DI\Attribute\Inject]` to inject dependencies into typed properties. This is the recommended approach for controllers.
+
+```php
+class example_class {
+ #[\DI\Attribute\Inject]
+ private \core\formatting $formatter;
+}
+
+$example = \core\di::get(example_class::class);
+```
+
## Advanced usage {/* #advanced-usage */}
All usage of the Container _should_ be via `\core\di`, which is a wrapper around the currently-active Container implementation. In normal circumstances it is not necessary to access the underlying Container implementation directly and such usage is generally discouraged.
diff --git a/docs/apis/subsystems/task/index.md b/docs/apis/subsystems/task/index.md
index 4689e0e5aa..fe147324ea 100644
--- a/docs/apis/subsystems/task/index.md
+++ b/docs/apis/subsystems/task/index.md
@@ -54,6 +54,55 @@ Adhoc tasks are great for situations such as:
## Usage {/* #usage */}
+### Dependency injection {/* #dependency-injection */}
+
+
+
+Adhoc and scheduled tasks can declare dependencies in their constructors using [dependency injection](../../core/di/index.md#fetching-dependencies). Moodle's DI container supplies the dependencies when it creates the task:
+
+```php
+namespace mod_example\task;
+
+class do_something extends \core\task\adhoc_task {
+ public function __construct(
+ protected readonly \core\clock $clock,
+ ) {
+ }
+
+ public function execute(): void {
+ $now = $this->clock->time();
+ mtrace("Task started at {$now}");
+ }
+}
+```
+
+In tests, use `\core\di::set()` to replace the clock in the container:
+
+```php
+$clock = $this->createMock(\core\clock::class);
+$clock->method('time')->willReturn(1234567890);
+\core\di::set(\core\clock::class, $clock);
+
+// The manager creates the task via the container, which supplies the mock clock.
+$task = \core\task\manager::adhoc_task_from_record((object) [
+ 'classname' => \mod_example\task\do_something::class,
+]);
+
+$this->expectOutputString("Task started at 1234567890\n");
+$task->execute();
+```
+
+You can also test the task directly by passing the mock clock to its constructor:
+
+```php
+$clock = $this->createMock(\core\clock::class);
+$clock->method('time')->willReturn(1234567890);
+
+$task = new \mod_example\task\do_something($clock);
+$this->expectOutputString("Task started at 1234567890\n");
+$task->execute();
+```
+
### Failures and error handling {/* #failures-and-error-handling */}
A task, either scheduled or adhoc, can sometimes fail. An example would be updating an RSS field when the network is temporarily down. This is handled by the task system automatically - all the failing task needs to do is throw an exception. The task will be retried after 1 minute. If the task keeps failing, the retry algorithm will add more time between each successive attempts up to a max of 24 hours.