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.