Skip to content

Commit 2c06b11

Browse files
committed
[docs] Add \core\formatting and DI docs for MDL-80072
1 parent eb7ffb3 commit 2c06b11

File tree

3 files changed

+384
-16
lines changed

3 files changed

+384
-16
lines changed

docs/apis/core/di/index.md

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
---
2+
title: Dependency Injection
3+
tags:
4+
- DI
5+
- Container
6+
- PSR-11
7+
- PSR
8+
description: The use of PSR-11 compatible Dependency Injection in Moodle
9+
---
10+
11+
import {
12+
Since,
13+
ValidExample,
14+
InvalidExample,
15+
Tabs,
16+
TabItem,
17+
} from '@site/src/components';
18+
19+
<Since version="4.4" issueNumber="MDL-80072" />
20+
21+
Moodle supports the use of [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible Dependency Injection, accessed using the `\core\di` class, which internally makes use of [PHP-DI](https://php-di.org).
22+
23+
Most class instances can be fetched using their class name without any manual configuration. Support for configuration of constructor arguments is also possible, but is generally discouraged.
24+
25+
Dependencies are stored using a string id attribute, which is typically the class or interface name of the dependency. Use of other arbitrary id values is strongly discouraged.
26+
27+
## Fetching dependencies
28+
29+
When accessing dependencies within a class, it is advisable to inject them into the constructor, for example:
30+
31+
```php title="Fetching a instance of the \core\http_client class from within a class"
32+
class my_thing {
33+
public function __construct(
34+
protected readonly \core\http_client $client,
35+
) {
36+
}
37+
}
38+
```
39+
40+
For legacy code, or for scripts accessing an injected class, Moodle provides a wrapper around the PSR-11 Container implementation which can be used to fetch dependencies:
41+
42+
```php title="Fetching dependencies using the DI container"
43+
// Fetching an instance of the \core\http_client class outside of a class.
44+
$client = \core\di::get(\core\http_client::class);
45+
46+
// Fetching an instance of a class which is managed using DI.
47+
$thing = \core\di::get(my_thing::class);
48+
```
49+
50+
:::tip Constructor Property Promotion and Readonly properties
51+
52+
When using constructor-based injection, you can simplify your dependency injection by making use of [Constructor Property Promotion](https://stitcher.io/blog/constructor-promotion-in-php-8), and [Readonly properties](https://stitcher.io/blog/php-81-readonly-properties).
53+
54+
The use of readonly properties is also highly recommended as it ensures that dependencies cannot be inadvertently changed.
55+
56+
These language features are available in all Moodle versions supporting Dependency Injection.
57+
58+
```php
59+
class example_without_promotion {
60+
protected \core\http_client $client;
61+
62+
public function __construct(
63+
\core\http_client $client,
64+
) {
65+
$this->client = $client;
66+
}
67+
}
68+
69+
class example_with_promotion {
70+
public function __construct(
71+
protected readonly \core\http_client $client,
72+
) {
73+
}
74+
}
75+
```
76+
77+
:::
78+
79+
## Configuring dependencies
80+
81+
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.
82+
83+
<Tabs>
84+
85+
<TabItem value="config" label="Hook configuration">
86+
87+
The callback must be linked to the hook by specifying a callback in the plugin's `hooks.php` file:
88+
89+
```php title="mod/example/db/hooks.php"
90+
<?php
91+
$callbacks = [
92+
[
93+
'hook' => \core\hook\di_configuration::class,
94+
'callback' => \mod_example\hook_listener::class . '::inject_dependenices',
95+
],
96+
];
97+
```
98+
99+
</TabItem>
100+
101+
<TabItem value="hook" label="Hook listener">
102+
103+
The hook listener consists of a static method on a class.
104+
105+
```php title="mod/example/classes/hook_listener.php"
106+
<?php
107+
108+
namespace mod_example;
109+
110+
use core\hook\di_configuration;
111+
112+
class hook_listener {
113+
public static function inject_dependencies(di_configuration $hook): void {
114+
$hook->add_definition(
115+
id: complex_client::class,
116+
definition: function (
117+
\moodle_database $db,
118+
): complex_client {
119+
global $CFG;
120+
121+
return new complex_client(
122+
db: $db,
123+
name: $CFG->some_value,
124+
);
125+
}
126+
)
127+
}
128+
}
129+
```
130+
131+
</TabItem>
132+
133+
</Tabs>
134+
135+
## Mocking dependencies in Unit Tests
136+
137+
One of the most convenient features of Dependency Injection is the ability to provide a mocked version of the dependency during unit testing.
138+
139+
Moodle resets the Dependency Injection Container between each unit test, which means that little-to-no cleanup is required.
140+
141+
```php title="Injecting a Mocked dependency"
142+
<?php
143+
namespace mod_example;
144+
145+
use GuzzleHttp\Handler\MockHandler;
146+
use GuzzleHttp\HandlerStack;
147+
use GuzzleHttp\Middleware;
148+
use GuzzleHttp\Psr7\Response;
149+
150+
class example_test extends \advanced_testcase {
151+
public function test_the_thing(): void {
152+
// Mock our responses to the http_client.
153+
$handlerstack = HandlerStack::create(new MockHandler([
154+
new Response(200, [], json_encode(['name' => 'Colin'])),
155+
]));
156+
157+
// Inject the mock.
158+
\core\di::set(
159+
\core\http_client::class,
160+
new http_client(['handler' => $handlerstack]),
161+
);
162+
163+
// Call a method on the example class.
164+
// This method uses \core\di to fetch the client and use it to fetch data.
165+
$example \core\di::get(example::class);
166+
$result = $example->do_the_thing();
167+
168+
// The result will be based on the mock response.
169+
$this->assertEquals('Colin', $result->get_name());
170+
}
171+
}
172+
```
173+
174+
## Injecting dependencies
175+
176+
Dependencies can be usually be easily injected into classes which are themselves loaded using Dependency Injection.
177+
178+
In most cases in Moodle, this should be via the class constructor, for example:
179+
180+
```php title="Injecting via the constructor"
181+
class thing_manager {
182+
public function __construct(
183+
protected readonly \moodle_database $db,
184+
) {
185+
}
186+
187+
public function get_things(): array {
188+
return $this->db->get_records('example_things');
189+
}
190+
}
191+
192+
// Fetching the injected class from legacy code:
193+
$manager = \core\di::get(thing_manager::class);
194+
$things = $manager->get_things();
195+
196+
// Using it in a child class:
197+
class other_thing {
198+
public function __construct(
199+
protected readonly thing_manager $manager,
200+
) {
201+
}
202+
203+
public function manage_things(): void {
204+
$this->manager->get_things();
205+
}
206+
}
207+
```
208+
209+
:::warning A note on injecting the Container
210+
211+
It is generally inadvisable to inject the Container itself. Please do not inject the `\Psr\Container\ContainerInterface`.
212+
213+
:::
214+
215+
## Advanced usage
216+
217+
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.
218+
219+
### Resetting the Container
220+
221+
The Container is normally instantiated during the bootstrap phase of a script. In normal use it is not reset and there should be no need to reset it, however it is _possible_ to reset it if required. This usage is intended to be used for situations such as Unit Testing.
222+
223+
:::tip Unit testing
224+
225+
The container is already reset after each test when running unit tests. It is not necessary nor recommended to so manually.
226+
227+
:::
228+
229+
```php title="Resetting the Container"
230+
\core\di::reset_container():
231+
```
232+
233+
:::danger
234+
235+
Resetting an actively-used container can lead to unintended consequences.
236+
237+
:::

docs/apis/subsystems/output/index.md

+3-15
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,11 @@ The key parameter for this function is:
221221

222222
#### format_text()
223223

224-
```php
225-
function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseid_do_not_use = null)
226-
```
227-
228224
This function should be used to:
229225

230226
- print **any html/plain/markdown/moodle text**, needing any of the features below. It is mainly used for long strings like posts, answers, glossary items, etc.
231227
- filter content through Moodle or 3rd party language filters for multi-language support. Not to be confused with [get_string](https://docs.moodle.org/dev/String_API#get_string.28.29) which is used to access localized strings in Moodle and its language packs. Together, these functions enable Moodle multi-language support .
232-
Note that this function is really **heavy** because it supports **cleaning** of dangerous contents, delegates processing to enabled **content filter**s, supports different **formats** of text (HTML, PLAIN, MARKDOWN, MOODLE) and performs a lot of **automatic conversions** like adding smilies, build links. Also, it includes a strong **cache mechanism** (DB based) that will alleviate the server from a lot of work processing the same texts time and again.
228+
Note that this function is really **heavy** because it supports **cleaning** of dangerous contents, delegates processing to enabled **content filters**, supports different **formats** of text (HTML, PLAIN, MARKDOWN, MOODLE) and performs a lot of **automatic conversions** like adding smilies, build links. Also, it includes a strong **cache mechanism** (DB based) that will alleviate the server from a lot of work processing the same texts time and again.
233229

234230
Some interesting parameters for this function are:
235231

@@ -241,18 +237,12 @@ Some interesting parameters for this function are:
241237
- `options->context`: If text is filtered (and this happens by default), it is very important to specify context (id or object) for applying filters. If context is not specified it will be taken from `$PAGE->context` and may potentially result in displaying the same text differently on different pages. For example all module-related information should have module context even when it appears in course-level reports, all course-related information such as name and description should have course context even when they are displayed on front page or system pages.
242238
- `options->param`: To decide if you want every paragraph automatically enclosed between html paragraph tags (`<p>...</p>`) (defaults to true). This option only applies to `FORMAT_MOODLE`.
243239
- `options->newlines`: To decide if line feeds in text should be converted to html newlines (`<br />`) (defaults to true). This option only applies to `FORMAT_MOODLE`.
244-
- `options->nocache`: If true the string will not be cached and will be formatted every call. Default false.
245240
- `options->overflowdiv`*: If set to true the formatted text will be encased in a div with the class no-overflow before being returned. Default false.
246241
- `options->allowid` : If true then id attributes will not be removed, even when using HTML Purifier. Default false.
247242
- `options->blanktarget` : If true all `<a>` tags will have `target="_blank"` added unless target is explicitly specified. Default false.
248-
- **courseid_do_not_use**: This parameter was earlier used to help applying filters but now is replaced with more precise `$options->context`, see above
249243

250244
#### format_string()
251245

252-
```php
253-
function format_string ($string, $striplinks = true, $options = null)
254-
```
255-
256246
This function should be used to:
257247

258248
- print **short non-html strings that need filter processing** (activity titles, post subjects, glossary concepts...). If the string contains HTML, it will be filtered out. If you want the HTML, use `format_text()` instead.
@@ -269,14 +259,12 @@ Some interesting parameters for this function are:
269259
- `options->escape`: To decide if you want to escape HTML entities. True by default.
270260
- `options->filter`: To decide if you want to allow filters to process the text (defaults to true). This is ignored by `FORMAT_PLAIN` for which filters are never applied.
271261

272-
:::note
273-
In earlier versions of Moodle, the third argument was integer `$courseid`. It is still supported for legacy - if the third argument is an integer instead of an array or object, it is considered to be course id and is this course's context is passed to the filters being applied.
274-
:::
275-
276262
### Simple elements rendering
277263

278264
:::important
265+
279266
Those methods are designed to replace the old ```html_writer::tag(...)``` methods. Even if many of them are just wrappers around the old methods, they are more semantic and could be overridden by component renderers.
267+
280268
:::
281269

282270
While to render complex elements, you should use [templates](../../../guides/templates/index.md), some simple elements can be rendered using the following functions:

0 commit comments

Comments
 (0)