From c31f6fa4ede90dfecb2f675f5538928afd584030 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 23 Feb 2024 22:13:43 +0800 Subject: [PATCH] [docs] Add docs for attribute_helper and attribute-described hooks --- docs/apis/core/hooks/index.md | 126 ++++++++++++++++++++++++++-------- docs/devupdate.md | 74 ++++++++++++++++++++ 2 files changed, 170 insertions(+), 30 deletions(-) diff --git a/docs/apis/core/hooks/index.md b/docs/apis/core/hooks/index.md index d4d4a581b6..ad587356f8 100644 --- a/docs/apis/core/hooks/index.md +++ b/docs/apis/core/hooks/index.md @@ -6,7 +6,7 @@ tags: - core --- -import { Since, ValidExample } from '@site/src/components'; +import { Since, ValidExample, Tabs, TabItem } from '@site/src/components'; @@ -62,7 +62,17 @@ to/from any other plugins. The exact type of information flow facilitated by hoo Information passed between subsystem and plugins is encapsulated in arbitrary PHP class instances. These can be in any namespace, but generally speaking they should be placed in the `some_component\hook\*` -namespace. Where possible, hooks are expected to implement the `core\hook\described_hook` interface. +namespace. + +Hooks are encouraged to describe themselves and to provide relevant metadata to make them easier to use and discover. There are two ways to describe a hook: + +- implement the `\core\hook\described_hook` interface, which has two methods: + - `get_description(): string;` + - `get_tags(): array;` +- add an instance of the following attributes to the class: + - `\core\attribute\label(string $description)` + - `\core\attribute\tags($a, $set, $of, $tags, ...)` + - `\core\attribute\hook\replaces_callbacks('a_list_of_legacy_callbacks', 'that_this_hook_replaces')` ### Hook callback @@ -138,17 +148,52 @@ Imagine mod_activity plugin wants to notify other plugins that it finished insta then mod_activity plugin developer adds a new hook and calls it at the end of plugin installation process. + + + + +```php title="/mod/activity/classes/hook/installation_finished.php" + + + + ```php title="/mod/activity/classes/hook/installation_finished.php" + + + ```php title="/mod/activity/db/install.php" + + + +```php title="/lib/classes/hook/after_config.php" + + + + ```php title="/lib/classes/hook/after_config.php" + + + The hook needs to be emitted immediately after the current callback execution code, and an extra parameter `$migratedtohook` must be set to true in the call to `get_plugins_with_function()`. @@ -364,22 +439,14 @@ Since the hook is an arbitrary PHP object, it is possible to create any range of namespace core\hook; -final class block_delete_pre implements described_hook, deprecated_callback_replacement { - public static function get_hook_description(): string { - return 'A hook dispatched just before a block instance is deleted'; - } +use core\attribute; +#[attribute\label('A hook dispatched just before a block instance is deleted')] +#[attribute\hook\replaces_callbacks('pre_block_delete')] +final class block_delete_pre { public function __construct( - protected stdClass $blockinstance, + public readonly \stdClass $blockinstance, ) {} - - public function get_instance(): stdClass { - return $this->blockinstance; - } - - public static function get_deprecated_plugin_callbacks(): array { - return ['pre_block_delete']; - } } ``` @@ -412,23 +479,17 @@ To make use of Stoppable events, the hook simply needs to implement the `Psr\Eve namespace core\hook; +use core\attribute; + +#[attribute\label('A hook dispatched just before a block instance is deleted')] +#[attribute\hook\replaces_callbacks('pre_block_delete')] final class block_delete_pre implements - described_hook, - deprecated_callback_replacement. Psr\EventDispatcher\StoppableEventInterface { - public static function get_hook_description(): string { - return 'A hook dispatched just before a block instance is deleted'; - } - public function __construct( - protected stdClass $blockinstance, + public readonly \stdClass $blockinstance, ) {} - public function get_instance(): stdClass { - return $this->blockinstance; - } - public function isPropagationStopped(): bool { return $this->stopped; } @@ -436,10 +497,6 @@ final class block_delete_pre implements public function stop(): void { $this->stopped = true; } - - public static function get_deprecated_plugin_callbacks(): array { - return ['pre_block_delete']; - } } ``` @@ -457,3 +514,12 @@ class callbacks { } } ``` + +## Tips and Tricks + +Whilst not being formal requirements, you are encouraged to: + +- describe and tag your hook as appropriate using either: + - the `\core\hook\described_hook` interface; or + - the `\core\attribute\label` and `\core\attribute\tags` attributes +- make use of constructor property promotion combined with readonly properties to reduce unnecessary boilerplate. diff --git a/docs/devupdate.md b/docs/devupdate.md index 2ba8d07567..a8be5a8baf 100644 --- a/docs/devupdate.md +++ b/docs/devupdate.md @@ -25,6 +25,80 @@ This page highlights the important changes that are coming in Moodle 4.4 for dev Support for PSR-11 compatible Containers has been introduced and can be accessed via the `\core\di` class. Read the [full documentation](./apis/core/di/index.md) for information on how to use Moodle's DI infrastructure. +### Attributes + + + +PHP 8.0 introduced support for the Attribute language feature and Moodle is beginning to make use of this in small but helpful ways. + +To simplify this adoption, a new `\core\attribute_helper` class has been created with methods to quickly and easily fetch the `\ReflectionAttribute` for any relevant attributes. + +The new methods can search for an attribute using a reference, which can be: + +- a string, whose name represents a global function, a class, or a class combined with a method, property, constant, or enum +- an instantiated object +- an array whose first value is either a string or object, and whose optional second value is a child of the first value + +```php title="Fetching attributes" +newInstance(); + +// Get an instance of a label on the \example class. +$label = attribute_helper::one_from(example::class, label::class)?->newInstance(); + +// Get an instance of a label on an instance of the \example class. +$example = new example(); +$label = attribute_helper::one_from($example, label::class)?->newInstance(); + +// Get an instance of a label on a constant, property, or method of the \example class. +$label = attribute_helper::one_from([example::class, 'some_child'], label::class)?->newInstance(); + +// Get an instance of a label on a constant, property, or method of an instance of the \example class. +$example = new example(); +$label = attribute_helper::one_from([$example, 'some_child'], label::class)?->newInstance(); +``` + +Other variations of the above are also possible. + +### Hooks + + + +Attribute-based alternatives to the `\core\hook\described_hook`, and `\core\hook\deprecated_callback_replacement` interfaces are now supported. + +- the `\core\attribute\label` attribute can be used to add a string-based description of the hook. +- the `\core\attribute\tags` attribute can be used to add one or more tags to describe the hook. +- the `\core\attribute\hook\replaces_callbacks` attribute can be used to add one or more replaced callbacks. + +```php +